bởi zinc

Keila is a self-hostable newsletter platform for creating sign-up forms and sending campaigns via SMTP, AWS SES, Sendgrid, Mailgun, or Postmark. An open-source alternative to Mailchimp for teams of any size.
This block provides a rich block-based campaign editor built on EditorJS, designed for composing newsletter campaigns with structured content blocks (text, image, button, layout columns, social icons). The typical buyer is a developer embedding a newsletter editor UI into a Node.js/TypeScript web application or integrating Keila's frontend editor widgets into their own Phoenix-like or custom backend project.
assets/js/campaign-editors/block/index.js - Main BlockEditor class; mounts EditorJS with all configured toolsassets/js/campaign-editors/block/blocks/button/index.js - Custom EditorJS block: clickable button with label, URL, and centering toggleassets/js/campaign-editors/block/blocks/image/index.js - Custom EditorJS block: image with caption, alt, title, width, link, and alignment tunesassets/js/campaign-editors/block/blocks/layout/index.js - Custom EditorJS block: multi-column grid layout hosting nested EditorJS instancesassets/js/campaign-editors/block/blocks/social-icons/index.js - Custom EditorJS block: row of social media icons with configurable URLs and colorsassets/js/campaign-editors/block/blocks/separator.js - Custom EditorJS block: horizontal rule separatorassets/js/campaign-editors/block/tools/alignment.js - Inline toolbar tool for text alignmentassets/js/campaign-editors/block/tools/text-color.js - Inline toolbar tool for text colorassets/js/campaign-editors/block/tunes/alignment.js - Block tune for per-block alignment controlassets/js/campaign-editors/markdown/index.js - ProseMirror-based Markdown editor variantassets/css/ - SCSS stylesheets including editor-specific stylesassets/js/hooks/ - Phoenix LiveView hooks for wiring editors to the DOMpriv/ - Elixir/Phoenix static assets and templateslib/ - Elixir application source (campaigns, contacts, mailers, etc.)config/ - Elixir app configurationnpm install @editorjs/editorjs @editorjs/header @editorjs/nested-list @editorjs/quote
No native iOS/Android build steps required. These are pure JavaScript packages. If you use the Markdown editor variant (campaign-editors/markdown/), you will also need:
Khởi chạy sandbox cách ly và chạy phía máy chủ — không cần cài đặt cục bộ.
Tetrees AI Review cho phiên bản này
This Elixir, JavaScript cli / script completed archive review with strong static results. Structure, dependency manifests, documentation, functional source, and common risk patterns were checked by the Tetrees verification pipeline; runtime phases are stated separately.
Deterministic AVCP artifact review
Quy trình avcp-2026-08-04.1 · SHA-256 6459bb7b7d0bce94…
This version-scoped review deterministically inspects the submitted archive for structure, dependencies, documentation, functional source, and common malicious or high-risk signals. Build and test phases are reported as passed only after an isolated sandbox audition. It is not a guarantee of perfect security.
Đã đánh giá 4 thg 8, 2026
Đưa sản phẩm này thẳng vào AI IDE, trình tạo web hoặc cloud IDE của bạn.
Kết nối Tetrees với AI IDE tương thích để liệt kê sản phẩm bạn sở hữu và nhận ZIP đã xác minh mà không cấp quyền tải lên của người bán.
Chưa có đánh giá.
Sign in to join the discussion
Loading discussion…
npm install prosemirror-state prosemirror-view prosemirror-model prosemirror-inputrules prosemirror-keymap markdown-it
Copy the source/assets/js/campaign-editors/block/ directory into your project, e.g. src/editors/block/.
Ensure your bundler (Webpack, Vite, esbuild) resolves the internal relative imports correctly. No path alias is required since all imports are relative.
Add a container element to your HTML that the editor will mount into, and a sibling element with the id block-container-assets containing the icon/label elements the blocks read via document.querySelector:
<div id="block-container-assets" style="display:none">
<span class="editor-placeholder">Start writing...</span>
<span class="editor-button-title">Button</span>
<span class="icon-button-alt"><!-- SVG --></span>
<span class="editor-button-make-full-width-label">Full width</span>
<span class="editor-button-make-centered-label">Centered</span>
<span class="editor-layout-placeholder">Add content...</span>
<!-- additional icon/label spans required by Image and SocialIcons blocks -->
</div>
<div id="editor-mount"></div>
<textarea id="editor-source" style="display:none">{"blocks":[]}</textarea>
BlockEditor:import BlockEditor from "./editors/block/index.js"
const source = document.getElementById("editor-source") as HTMLTextAreaElement
const editor = new BlockEditor("editor-mount", source)
#block-container-assets.class BlockEditor {
constructor(place: string, source: HTMLTextAreaElement): BlockEditor
}
place is the id of the DOM element to mount into. source is a <textarea> whose .value is a JSON string of EditorJS block data ({"blocks": [...], "version": "..."}). Instantiate once per editor mount point. The editor reads initial data from source.value on construction.
class Button {
constructor({ data, block }: { data: { label: string | null, url: string | null, centered: boolean }, block: any }): Button
static get toolbox(): { title: string, icon: string }
renderSettings(): Array<{ icon: string, label: string, onActivate: () => void, closeOnActivate: boolean }>
save(_blockContent: HTMLElement): { label: string, url: string, centered: boolean }
toggleCentered(): void
}
Register as an EditorJS tool under key "button". Use when you need a styled CTA link block inside the editor. renderSettings() exposes a toggle for full-width vs. centered rendering.
class Layout {
constructor({ data, config, api, block }: {
data: { blocks: any[], columns: number, ratio: string },
config: { tools: Record<string, any> },
api: any,
block: any
}): Layout
render(): HTMLElement
save(_blockContent: HTMLElement): Promise<{ blocks: any[], columns: number, ratio: string }>
}
Renders a CSS grid with columns columns and a ratio string like "1-1" or "1-2". Each column hosts a nested EditorJS instance initialized from config.tools. Use when multi-column newsletter layouts are required.
class Image {
constructor({ data, api, block }: {
data: { caption: string | null, alt: string | null, title: string | null, width: string | null, image: { id: string | null, src: string | null }, link: { url: string | null }, tunes?: { alignment?: string } },
api: any,
block: any
}): Image
render(): HTMLElement
save(_blockContent: HTMLElement): object
}
Supports left/center/right alignment via tunes, a link URL, caption, and an image picker integrated with the asset system via #block-container-assets. Use for inline images within newsletter blocks.
Mount the block editor onto a form textarea to capture campaign content:
import BlockEditor from "./editors/block/index.js"
document.addEventListener("DOMContentLoaded", () => {
const sourceEl = document.getElementById("campaign-body") as HTMLTextAreaElement
if (!sourceEl.value) {
sourceEl.value = JSON.stringify({ time: Date.now(), blocks: [], version: "2.28.0" })
}
new BlockEditor("campaign-editor", sourceEl)
})
Load saved campaign data from your backend and restore it into the editor:
import BlockEditor from "./editors/block/index.js"
async function mountEditor(campaignId: string) {
const res = await fetch(`/api/campaigns/${campaignId}/body`)
const json = await res.json()
const sourceEl = document.getElementById("editor-source") as HTMLTextAreaElement
sourceEl.value = JSON.stringify(json)
new BlockEditor("editor-mount", sourceEl)
}
mountEditor("abc123")
Use the Button block directly in your own EditorJS instance without the full BlockEditor wrapper:
import EditorJS from "@editorjs/editorjs"
import Button from "./editors/block/blocks/button/index.js"
const editor = new EditorJS({
holder: "my-editor",
tools: {
button: {
class: Button
}
},
data: {
blocks: [
{
type: "button",
data: { label: "Sign up now", url: "https://example.com", centered: true }
}
]
}
})
editor.save().then(output => {
console.log(output)
})
assets/js/campaign-editors/block/index.js - Orchestrates all tools and tunes into a single EditorJS configuration; the only entry point buyers should import.assets/js/campaign-editors/block/blocks/button/index.js - Self-contained button block; reads DOM for labels, handles centering toggle via renderSettings.assets/js/campaign-editors/block/blocks/image/index.js - Image block with alignment tunes, ResizeObserver for responsive display, and caption/link fields.assets/js/campaign-editors/block/blocks/layout/index.js - Creates nested EditorJS instances per column; forwards config.tools so nested editors share the same tool set.assets/js/campaign-editors/block/blocks/social-icons/index.js - Manages a list of known social platforms with brand colors; renders icon rows with per-icon URL/color overrides.assets/js/campaign-editors/block/blocks/separator.js - Minimal block rendering an <hr> with no configurable data.assets/js/campaign-editors/block/tools/alignment.js - Inline toolbar tool that wraps selected content in an alignment span.assets/js/campaign-editors/block/tools/text-color.js - Inline toolbar tool applying a color style to selected text.assets/js/campaign-editors/block/tunes/alignment.js - Block-level tune adding alignment CSS classes to entire blocks.assets/js/campaign-editors/markdown/ - Alternative ProseMirror-based editor for markdown campaigns; independent of the block editor.assets/css/ - SCSS styles including _markdown_wysiwyg.scss and app.scss for editor chrome.lib/ - Elixir/Phoenix backend: campaign delivery, contacts, forms, API. Not used on the JS side.config/ - Elixir runtime and environment config. Irrelevant to the JS editor.priv/ - Phoenix static assets, migrations, gettext translations.document.querySelector("#block-container-assets .editor-button-title") returns null: All blocks call document.querySelector at render/toolbox time against a hidden DOM node. Mount this node before instantiating BlockEditor or you will get null-reference errors.Layout does not propagate changes: Layout calls this.block.dispatchChange() in the nested editor's onChange. If you override onChange at the top level, ensure you do not replace the inner handler; use the top-level onChange config key only.save() on Layout is async: Layout.save() calls editor.save() on each nested instance, returning a Promise. Callers of the top-level editor.save() must await to get fully resolved column data.Layout uses grid-cols-2, grid-cols-3, col-span-1, etc. built at runtime from the ratio string. Add them to your Tailwind safelist or rely on the _requiredClasses comment pattern already present in the source.@editorjs/nested-list vs @editorjs/list version mismatch: The source imports @editorjs/nested-list specifically, not the plain list package. Installing @editorjs/list instead will cause a missing-tool error at runtime.esModuleInterop), ensure @editorjs/editorjs is not transpiled as CommonJS or you will see EditorJS is not a constructor.I have purchased the Keila Block Campaign Editor source code located in the `source/` directory.
I also have a `USAGE.md` integration guide in the same directory.
The upstream project is `keila` (https://github.com/pentacent/keila).
The main entry point is `source/assets/js/campaign-editors/block/index.js`, which exports a
default class `BlockEditor`.
Please integrate this editor into my existing project step by step:
1. Read `USAGE.md` fully before making any changes.
2. Install all required npm dependencies listed in the `## Required dependencies` section.
3. Copy the block editor source files into `src/editors/block/` in my project.
4. Add the required `#block-container-assets` DOM node to my HTML template using the structure
shown in `USAGE.md` under `## Project setup`.
5. Create a TypeScript module `src/mountEditor.ts` that imports `BlockEditor` from
`src/editors/block/index.js` and mounts it to a `<div id="editor-mount">` in my page,
reading initial data from a `<textarea id="editor-source">`.
6. Confirm that the `Layout` block receives the correct `config.tools` object so nested
editors share the same tool configuration as the top-level editor.
7. Point out any Tailwind safelist entries I need to add based on the dynamic class names
used in `Layout`.
Show me each file you create or modify.
Keila is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). Any project that incorporates this source and is accessible over a network must make its full source code available under the same license. See source/LICENSE.md for the full text.
Upstream repository: https://github.com/pentacent/keila
This product’s unique contribution is clean room implementation of valuable code block into AVCP compatible standard.
Hướng dẫn cài đặt đầy đủ và prompt tích hợp sẽ mở khóa sau khi mua.
PHP, Laravel & Business Scripts
Miễn phí