dpk-editor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +269 -0
- package/dist/index.cjs +990 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +334 -0
- package/dist/index.d.ts +334 -0
- package/dist/index.js +962 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +386 -0
- package/package.json +79 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/shellBridge.ts","../src/extensions/PreserveStyles.ts","../src/extensions/editorExtensions.ts","../src/utils/presets.ts","../src/utils/resolveToolbarConfig.ts","../src/utils/escapeHtml.ts","../src/utils/buildButtonHtml.ts","../src/components/EmailButtonDialog.tsx","../src/components/Toolbar.tsx","../src/components/RichTextEditor.tsx","../src/components/EmailEditor.tsx"],"names":["jsx","LinkIcon","ImageIcon","jsxs","RichTextEditor","useMemo","useRef","useState","useEffect","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["import type { EmailHtmlDocument } from \"../types\";\n\n/**\n * Document-shell bridge utilities.\n *\n * A `contentEditable`/ProseMirror surface cannot host `<html>`/`<head>`/\n * `<body>` — it silently strips them and keeps only body-level flow content.\n * So `<EmailEditor>` edits only the **body fragment** and preserves the\n * surrounding document shell verbatim across edits.\n *\n * These are pure string utilities (no DOM required): they operate by locating\n * the `<body ...>` tag with a regex, so they work the same in the browser,\n * during SSR, and in tests.\n */\n\n// Matches the opening <body ...> tag (case-insensitive, any attributes,\n// across newlines). Non-greedy so it stops at the first `>`.\nconst BODY_OPEN_RE = /<body\\b[^>]*>/i;\n// Matches the closing </body> tag.\nconst BODY_CLOSE_RE = /<\\/body\\s*>/i;\n\n/**\n * Split a full email HTML document into `{ prefix, body, suffix }`.\n *\n * - `prefix` = everything up to and including the opening `<body ...>` tag.\n * - `body` = the inner body HTML (the editable fragment).\n * - `suffix` = the closing `</body>` tag plus everything after it.\n *\n * If the input has no `<body>` tag it is treated as a **bare fragment**: the\n * whole input becomes `body`, and `prefix`/`suffix` are empty strings.\n */\nexport function splitEmailHtml(html: string): EmailHtmlDocument {\n const input = html ?? \"\";\n\n const openMatch = input.match(BODY_OPEN_RE);\n if (!openMatch || openMatch.index === undefined) {\n // Bare fragment — no shell to preserve.\n return { prefix: \"\", body: input, suffix: \"\" };\n }\n\n const openStart = openMatch.index;\n const openEnd = openStart + openMatch[0].length;\n const prefix = input.slice(0, openEnd);\n\n const rest = input.slice(openEnd);\n const closeMatch = rest.match(BODY_CLOSE_RE);\n\n if (!closeMatch || closeMatch.index === undefined) {\n // Opening <body> but no closing tag — treat the remainder as body and\n // synthesize an empty suffix so a rejoin still nests inside the shell.\n return { prefix, body: rest, suffix: \"\" };\n }\n\n const body = rest.slice(0, closeMatch.index);\n const suffix = rest.slice(closeMatch.index);\n\n return { prefix, body, suffix };\n}\n\n/**\n * Reassemble a document from a shell and a (possibly edited) body fragment.\n *\n * If the shell has no surrounding markup (a bare fragment was originally\n * supplied) the body is returned as-is, so `onChange` emits in the same shape\n * the consumer provided `value`.\n */\nexport function joinEmailHtml(\n shell: EmailHtmlDocument,\n newBody: string,\n): string {\n const body = newBody ?? \"\";\n if (!shell.prefix && !shell.suffix) {\n return body;\n }\n return `${shell.prefix}${body}${shell.suffix}`;\n}\n\n/**\n * Convenience: extract just the editable body fragment from a full document\n * (or return the input unchanged if it is already a bare fragment).\n */\nexport function extractEmailBody(html: string): string {\n return splitEmailHtml(html).body;\n}\n","import { Extension } from \"@tiptap/react\";\n\n/**\n * A TipTap extension that preserves the inline `style` attribute on every node\n * and mark in the schema.\n *\n * TipTap (ProseMirror) strips any HTML attribute a node/mark does not declare.\n * For article editing that is fine; for **email** HTML it is lossy — buttons\n * lose their padding/background, headings lose their color, and so on. This\n * extension declares a global `style` attribute so arbitrary inline-styled\n * email HTML round-trips.\n *\n * It preserves `style` only on elements that map to a known node or mark\n * (paragraph, heading, list, blockquote, link, image, hr, …). Raw\n * `<table>`/`<td>` layouts are still flattened by ProseMirror's schema — those\n * survive via the document-shell bridge, not here.\n */\nexport const PreserveStyles = Extension.create({\n name: \"preserveStyles\",\n\n addGlobalAttributes() {\n return [\n {\n // ⚠️ MUST be the string shorthand \"*\" (= all nodes and marks).\n // NEVER [\"*\"] — an array means \"a type literally named *\", which\n // matches nothing and silently preserves no styles. That bug passes\n // tsc/eslint/build cleanly, so it is asserted against in the tests.\n types: \"*\",\n attributes: {\n style: {\n default: null,\n parseHTML: (element) => element.getAttribute(\"style\"),\n renderHTML: (attributes) =>\n attributes.style ? { style: attributes.style as string } : {},\n },\n },\n },\n ];\n },\n});\n\nexport default PreserveStyles;\n","import Highlight from \"@tiptap/extension-highlight\";\nimport Image from \"@tiptap/extension-image\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport TextAlign from \"@tiptap/extension-text-align\";\n// @tiptap/extension-text-style v3 re-exports both TextStyle and Color, so we\n// do NOT need @tiptap/extension-color as a separate dependency.\nimport { Color, TextStyle } from \"@tiptap/extension-text-style\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport type { Extensions } from \"@tiptap/react\";\n\nimport { PreserveStyles } from \"./PreserveStyles\";\n\n/**\n * The canonical extension set for the email editor.\n *\n * Notes / watch-outs baked in here:\n * - StarterKit v3 already bundles Link, Underline, lists, blockquote, code and\n * headings — we do NOT add @tiptap/extension-link or -underline separately\n * (that throws duplicate-extension warnings). Link is configured through\n * StarterKit.configure({ link: {...} }).\n * - Image is block-level and base64 is disabled (uploads should resolve to a\n * hosted URL, which is what email clients can render).\n * - PreserveStyles must be present so inline `style` survives the round-trip.\n * - Placeholder is NOT bundled by StarterKit v3, so it is added explicitly to\n * power the empty-state hint (it sets the `is-editor-empty` class and the\n * `data-placeholder` attribute that styles.css renders via `::before`).\n *\n * Exported as a factory so the React component and the headless tests build an\n * identical extension set. Pass the empty-state placeholder text in.\n */\nexport function createEmailExtensions(placeholder?: string): Extensions {\n return [\n StarterKit.configure({\n link: {\n openOnClick: false,\n autolink: true,\n HTMLAttributes: { rel: \"noopener noreferrer\" },\n },\n }),\n Image.configure({ inline: false, allowBase64: false }),\n TextAlign.configure({ types: [\"heading\", \"paragraph\"] }),\n TextStyle,\n Color,\n Highlight.configure({ multicolor: true }),\n Placeholder.configure({\n placeholder: placeholder ?? \"Write your email…\",\n // Emit data-placeholder so styles.css can render it via attr(); also\n // keep the default is-editor-empty class for the :first-child selector.\n showOnlyWhenEditable: true,\n }),\n PreserveStyles,\n ];\n}\n","/**\n * Preset block snippets — small, inline-styled, email-safe HTML fragments\n * inserted at the caret via `editor.chain().focus().insertContent(snippet)`.\n *\n * Every snippet uses inline styles only (no classes) so it survives both the\n * editor schema (via PreserveStyles) and downstream email clients.\n */\nexport const PRESET_PARAGRAPH =\n '<p style=\"margin:0 0 12px;color:#4b5563;line-height:1.6;\">Write your message here.</p>';\n\nexport const PRESET_DIVIDER =\n '<hr style=\"border:none;border-top:1px solid #e5e7eb;margin:20px 0;\" />';\n\nexport const PRESET_FOOTER =\n '<p style=\"margin:20px 0 0;color:#9ca3af;font-size:12px;\">© Your Company</p>';\n\nexport const PRESET_HEADING =\n '<h2 style=\"margin:0 0 12px;color:#111827;font-size:24px;line-height:1.3;\">Heading</h2>';\n\nexport const presets = {\n paragraph: PRESET_PARAGRAPH,\n divider: PRESET_DIVIDER,\n footer: PRESET_FOOTER,\n heading: PRESET_HEADING,\n} as const;\n","import type {\n AlignButtons,\n BlockButtons,\n HeadingButtons,\n InlineButtons,\n ListButtons,\n ResolvedToolbarConfig,\n ToolbarConfig,\n ToolbarGroup,\n} from \"../types\";\n\n/**\n * Resolve a loose `ToolbarConfig` into the flat `ResolvedToolbarConfig` the\n * Toolbar renders from. Rules:\n * - A group key may be a boolean or a per-button object (or omitted).\n * - Omitted / `true` / `{}` → group enabled, every button enabled.\n * - `false` → group disabled (and all its buttons disabled).\n * - An object → group enabled, each listed button overridden; unlisted buttons\n * default to `true`.\n */\nfunction resolveGroup<TButtons extends Record<string, boolean | undefined>>(\n group: ToolbarGroup<TButtons> | undefined,\n defaults: Required<TButtons>,\n): { enabled: boolean; buttons: Required<TButtons> } {\n // Omitted → all on.\n if (group === undefined || group === true) {\n return { enabled: true, buttons: { ...defaults } };\n }\n // Whole group off → disable every button too.\n if (group === false) {\n const off = {} as Required<TButtons>;\n for (const key of Object.keys(defaults) as (keyof TButtons)[]) {\n off[key] = false as Required<TButtons>[keyof TButtons];\n }\n return { enabled: false, buttons: off };\n }\n // Per-button object → group on, merge overrides over the all-on defaults.\n return { enabled: true, buttons: { ...defaults, ...group } };\n}\n\nconst INLINE_DEFAULTS: Required<InlineButtons> = {\n bold: true,\n italic: true,\n underline: true,\n strike: true,\n code: true,\n};\nconst HEADING_DEFAULTS: Required<HeadingButtons> = {\n h2: true,\n h3: true,\n h4: true,\n h5: true,\n h6: true,\n};\nconst LIST_DEFAULTS: Required<ListButtons> = {\n bullet: true,\n ordered: true,\n blockquote: true,\n};\nconst ALIGN_DEFAULTS: Required<AlignButtons> = {\n left: true,\n center: true,\n right: true,\n};\nconst BLOCK_DEFAULTS: Required<BlockButtons> = {\n paragraph: true,\n divider: true,\n footer: true,\n};\n\n/** A single-control group is on unless explicitly set to `false`. */\nfunction resolveToggle(value: boolean | undefined): boolean {\n return value !== false;\n}\n\nexport function resolveToolbarConfig(\n config?: ToolbarConfig,\n): ResolvedToolbarConfig {\n const c = config ?? {};\n return {\n inline: resolveGroup(c.inline, INLINE_DEFAULTS),\n headings: resolveGroup(c.headings, HEADING_DEFAULTS),\n lists: resolveGroup(c.lists, LIST_DEFAULTS),\n align: resolveGroup(c.align, ALIGN_DEFAULTS),\n link: resolveToggle(c.link),\n image: resolveToggle(c.image),\n button: resolveToggle(c.button),\n blocks: resolveGroup(c.blocks, BLOCK_DEFAULTS),\n html: resolveToggle(c.html),\n };\n}\n","/**\n * Escape a string for safe insertion into HTML text or a double-quoted\n * attribute value. Used by `buildButtonHtml` for the button label and href.\n */\nexport function escapeHtml(value: string): string {\n return String(value)\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","import type { EmailButtonConfig } from \"../types\";\nimport { escapeHtml } from \"./escapeHtml\";\n\n/**\n * Build email-safe HTML for a call-to-action button.\n *\n * Two non-obvious decisions, both load-bearing:\n *\n * 1. The anchor is wrapped in a `<p style=\"...;text-align:X\">`, **never** a\n * `<div>`. TipTap has no `div` node and unwraps a `<div>` into a paragraph,\n * losing the `text-align`. A paragraph is a real node whose `text-align`\n * the TextAlign extension preserves — so the alignment round-trips.\n *\n * 2. The button is an `<a style=\"display:inline-block;...\">` (or\n * `display:block` when full-width), not a `<button>`, because email clients\n * need bulletproof, inline-styled markup.\n *\n * Both the label and the href are HTML-escaped.\n */\nexport function buildButtonHtml(config: EmailButtonConfig): string {\n const {\n text,\n href,\n bgColor,\n textColor,\n align,\n radius,\n fullWidth,\n } = config;\n\n const safeText = escapeHtml(text || \"Button\");\n const safeHref = escapeHtml(href || \"#\");\n const safeRadius = Number.isFinite(radius) ? Math.max(0, radius) : 0;\n\n // Full-width buttons span the content area as a block and center their own\n // label; otherwise they hug their content as an inline-block.\n const display = fullWidth ? \"block\" : \"inline-block\";\n const anchorTextAlign = fullWidth ? \"text-align:center;\" : \"\";\n\n const anchorStyle = [\n `display:${display}`,\n \"padding:12px 24px\",\n `background-color:${bgColor}`,\n `color:${textColor}`,\n \"text-decoration:none\",\n \"font-weight:600\",\n \"font-family:Arial,Helvetica,sans-serif\",\n \"font-size:14px\",\n \"line-height:1.4\",\n `border-radius:${safeRadius}px`,\n anchorTextAlign,\n ]\n .filter(Boolean)\n .join(\";\");\n\n const paragraphStyle = `margin:16px 0;text-align:${align}`;\n\n return (\n `<p style=\"${paragraphStyle}\">` +\n `<a href=\"${safeHref}\" target=\"_blank\" rel=\"noopener noreferrer\" ` +\n `style=\"${anchorStyle}\">${safeText}</a>` +\n `</p>`\n );\n}\n","import { useEffect, useMemo, useRef, useState } from \"react\";\n\nimport type { EmailButtonConfig } from \"../types\";\nimport { buildButtonHtml } from \"../utils/buildButtonHtml\";\n\nexport type EmailButtonDialogProps = {\n /** Whether the dialog is open. */\n open: boolean;\n /** Called when the user confirms; receives the email-safe button HTML. */\n onConfirm: (html: string, config: EmailButtonConfig) => void;\n /** Called when the user dismisses the dialog (Esc / click-outside / cancel). */\n onClose: () => void;\n /** Optional initial values for the form. */\n initial?: Partial<EmailButtonConfig>;\n};\n\nconst DEFAULT_CONFIG: EmailButtonConfig = {\n text: \"Click here\",\n href: \"https://\",\n bgColor: \"#2563eb\",\n textColor: \"#ffffff\",\n align: \"left\",\n radius: 6,\n fullWidth: false,\n};\n\nconst BG_SWATCHES = [\n \"#2563eb\",\n \"#16a34a\",\n \"#dc2626\",\n \"#7c3aed\",\n \"#ea580c\",\n \"#0891b2\",\n \"#111827\",\n];\nconst TEXT_SWATCHES = [\"#ffffff\", \"#111827\", \"#f9fafb\", \"#1f2937\"];\n\n// <input type=color> only accepts #rrggbb. Named colors, #rgb shorthand, or\n// anything else must be coerced or it throws/blanks the control.\nconst HEX6_RE = /^#[0-9a-fA-F]{6}$/;\nfunction toColorInputValue(value: string): string {\n return HEX6_RE.test(value) ? value : \"#000000\";\n}\n\ntype ColorFieldProps = {\n label: string;\n value: string;\n swatches: string[];\n onChange: (value: string) => void;\n};\n\nfunction ColorField({ label, value, swatches, onChange }: ColorFieldProps) {\n return (\n <div className=\"rte-dialog-field\">\n <span className=\"rte-dialog-label\">{label}</span>\n <div className=\"rte-color-row\">\n {swatches.map((swatch) => (\n <button\n key={swatch}\n type=\"button\"\n className={\n \"rte-swatch\" + (value === swatch ? \" rte-swatch--active\" : \"\")\n }\n style={{ backgroundColor: swatch }}\n aria-label={`Use ${swatch}`}\n aria-pressed={value === swatch}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => onChange(swatch)}\n />\n ))}\n <input\n type=\"color\"\n className=\"rte-color-input\"\n value={toColorInputValue(value)}\n aria-label={`${label} picker`}\n onChange={(e) => onChange(e.target.value)}\n />\n <input\n type=\"text\"\n className=\"rte-hex-input\"\n value={value}\n aria-label={`${label} hex`}\n spellCheck={false}\n onChange={(e) => onChange(e.target.value)}\n />\n </div>\n </div>\n );\n}\n\n/**\n * Modal dialog for configuring an email CTA button. Supports Esc to close,\n * body-scroll lock while open, click-outside to dismiss, and a live preview of\n * the rendered button.\n */\nexport function EmailButtonDialog({\n open,\n onConfirm,\n onClose,\n initial,\n}: EmailButtonDialogProps) {\n const [config, setConfig] = useState<EmailButtonConfig>({\n ...DEFAULT_CONFIG,\n ...initial,\n });\n const overlayRef = useRef<HTMLDivElement>(null);\n\n // Reset the form to its initial state each time the dialog opens.\n useEffect(() => {\n if (open) {\n setConfig({ ...DEFAULT_CONFIG, ...initial });\n }\n }, [open, initial]);\n\n // Esc to close + body-scroll lock while open.\n useEffect(() => {\n if (!open) return;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n onClose();\n }\n };\n document.addEventListener(\"keydown\", onKeyDown);\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown);\n document.body.style.overflow = previousOverflow;\n };\n }, [open, onClose]);\n\n const previewHtml = useMemo(() => buildButtonHtml(config), [config]);\n\n if (!open) return null;\n\n const update = <K extends keyof EmailButtonConfig>(\n key: K,\n value: EmailButtonConfig[K],\n ) => setConfig((prev) => ({ ...prev, [key]: value }));\n\n const handleSubmit = () => {\n onConfirm(buildButtonHtml(config), config);\n };\n\n return (\n <div\n ref={overlayRef}\n className=\"rte-dialog-overlay\"\n role=\"presentation\"\n onMouseDown={(e) => {\n // Click-outside (on the overlay itself, not the panel) dismisses.\n if (e.target === overlayRef.current) onClose();\n }}\n >\n <div\n className=\"rte-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Insert button\"\n >\n <div className=\"rte-dialog-header\">\n <h2 className=\"rte-dialog-title\">Insert button</h2>\n <button\n type=\"button\"\n className=\"rte-dialog-close\"\n aria-label=\"Close\"\n onClick={onClose}\n >\n ×\n </button>\n </div>\n\n <div className=\"rte-dialog-body\">\n <label className=\"rte-dialog-field\">\n <span className=\"rte-dialog-label\">Text</span>\n <input\n type=\"text\"\n className=\"rte-text-input\"\n value={config.text}\n onChange={(e) => update(\"text\", e.target.value)}\n />\n </label>\n\n <label className=\"rte-dialog-field\">\n <span className=\"rte-dialog-label\">Link URL</span>\n <input\n type=\"text\"\n className=\"rte-text-input\"\n value={config.href}\n onChange={(e) => update(\"href\", e.target.value)}\n />\n </label>\n\n <ColorField\n label=\"Background color\"\n value={config.bgColor}\n swatches={BG_SWATCHES}\n onChange={(v) => update(\"bgColor\", v)}\n />\n\n <ColorField\n label=\"Text color\"\n value={config.textColor}\n swatches={TEXT_SWATCHES}\n onChange={(v) => update(\"textColor\", v)}\n />\n\n <div className=\"rte-dialog-field\">\n <span className=\"rte-dialog-label\">Alignment</span>\n <div className=\"rte-align-row\">\n {([\"left\", \"center\", \"right\"] as const).map((a) => (\n <button\n key={a}\n type=\"button\"\n className={\n \"rte-align-btn\" +\n (config.align === a ? \" rte-align-btn--active\" : \"\")\n }\n aria-pressed={config.align === a}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => update(\"align\", a)}\n >\n {a}\n </button>\n ))}\n </div>\n </div>\n\n <label className=\"rte-dialog-field\">\n <span className=\"rte-dialog-label\">\n Corner radius: {config.radius}px\n </span>\n <input\n type=\"range\"\n min={0}\n max={32}\n value={config.radius}\n onChange={(e) => update(\"radius\", Number(e.target.value))}\n />\n </label>\n\n <label className=\"rte-checkbox-field\">\n <input\n type=\"checkbox\"\n checked={config.fullWidth}\n onChange={(e) => update(\"fullWidth\", e.target.checked)}\n />\n <span>Full width</span>\n </label>\n\n <div className=\"rte-dialog-field\">\n <span className=\"rte-dialog-label\">Preview</span>\n <div\n className=\"rte-button-preview\"\n // Preview only — content is built from the same escaped builder.\n dangerouslySetInnerHTML={{ __html: previewHtml }}\n />\n </div>\n </div>\n\n <div className=\"rte-dialog-footer\">\n <button\n type=\"button\"\n className=\"rte-btn rte-btn--ghost\"\n onClick={onClose}\n >\n Cancel\n </button>\n <button\n type=\"button\"\n className=\"rte-btn rte-btn--primary\"\n onClick={handleSubmit}\n >\n Insert\n </button>\n </div>\n </div>\n </div>\n );\n}\n\nexport default EmailButtonDialog;\n","import type { Editor } from \"@tiptap/react\";\nimport { useEditorState } from \"@tiptap/react\";\nimport {\n AlignCenter,\n AlignLeft,\n AlignRight,\n Bold,\n Code,\n FileCode,\n Heading2,\n Heading3,\n Heading4,\n Heading5,\n Heading6,\n Image as ImageIcon,\n Italic,\n Link as LinkIcon,\n List,\n ListOrdered,\n Minus,\n MousePointerClick,\n PanelBottom,\n Pilcrow,\n Quote,\n Strikethrough,\n Underline,\n} from \"lucide-react\";\nimport { Fragment, type ReactNode } from \"react\";\n\nimport type { ResolvedToolbarConfig } from \"../types\";\n\ntype HeadingLevel = 2 | 3 | 4 | 5 | 6;\n\nexport type ToolbarProps = {\n editor: Editor;\n config: ResolvedToolbarConfig;\n /** Toggle the raw HTML source view. */\n htmlMode: boolean;\n onToggleHtml: () => void;\n /** Caller-provided handlers for the controls that need extra UI. */\n onLink: () => void;\n onImage: () => void;\n onButton: () => void;\n onInsertParagraph: () => void;\n onInsertDivider: () => void;\n onInsertFooter: () => void;\n};\n\ntype TbButtonProps = {\n onClick: () => void;\n active?: boolean;\n label: string;\n disabled?: boolean;\n children: ReactNode;\n};\n\nfunction TbButton({ onClick, active, label, disabled, children }: TbButtonProps) {\n return (\n <button\n type=\"button\"\n className={\"rte-tb-btn\" + (active ? \" rte-tb-btn--active\" : \"\")}\n // Prevent the button from stealing the editor's selection on press.\n onMouseDown={(e) => e.preventDefault()}\n onClick={onClick}\n aria-pressed={!!active}\n aria-label={label}\n title={label}\n disabled={disabled}\n >\n {children}\n </button>\n );\n}\n\nfunction Divider() {\n return <span className=\"rte-tb-divider\" aria-hidden=\"true\" />;\n}\n\nconst HEADING_ICONS: Record<HeadingLevel, ReactNode> = {\n 2: <Heading2 size={16} />,\n 3: <Heading3 size={16} />,\n 4: <Heading4 size={16} />,\n 5: <Heading5 size={16} />,\n 6: <Heading6 size={16} />,\n};\n\nexport function Toolbar({\n editor,\n config,\n htmlMode,\n onToggleHtml,\n onLink,\n onImage,\n onButton,\n onInsertParagraph,\n onInsertDivider,\n onInsertFooter,\n}: ToolbarProps) {\n // useEditorState re-renders only when the selected slice changes, keeping\n // active-state computation cheap even on large documents.\n const state = useEditorState({\n editor,\n selector: ({ editor: e }) => ({\n bold: e.isActive(\"bold\"),\n italic: e.isActive(\"italic\"),\n underline: e.isActive(\"underline\"),\n strike: e.isActive(\"strike\"),\n code: e.isActive(\"code\"),\n h2: e.isActive(\"heading\", { level: 2 }),\n h3: e.isActive(\"heading\", { level: 3 }),\n h4: e.isActive(\"heading\", { level: 4 }),\n h5: e.isActive(\"heading\", { level: 5 }),\n h6: e.isActive(\"heading\", { level: 6 }),\n bulletList: e.isActive(\"bulletList\"),\n orderedList: e.isActive(\"orderedList\"),\n blockquote: e.isActive(\"blockquote\"),\n alignLeft: e.isActive({ textAlign: \"left\" }),\n alignCenter: e.isActive({ textAlign: \"center\" }),\n alignRight: e.isActive({ textAlign: \"right\" }),\n link: e.isActive(\"link\"),\n image: e.isActive(\"image\"),\n }),\n });\n\n const headingActive: Record<HeadingLevel, boolean> = {\n 2: state.h2,\n 3: state.h3,\n 4: state.h4,\n 5: state.h5,\n 6: state.h6,\n };\n\n // In HTML source mode only the source toggle is interactive.\n const disabled = htmlMode;\n\n // Build each group as an array of rendered buttons, gated by both the group\n // `enabled` flag and the per-button flag. A group with zero visible buttons\n // contributes nothing (and gets no divider).\n const inline = config.inline.enabled\n ? [\n config.inline.buttons.bold && (\n <TbButton\n key=\"bold\"\n label=\"Bold\"\n active={state.bold}\n disabled={disabled}\n onClick={() => editor.chain().focus().toggleBold().run()}\n >\n <Bold size={16} />\n </TbButton>\n ),\n config.inline.buttons.italic && (\n <TbButton\n key=\"italic\"\n label=\"Italic\"\n active={state.italic}\n disabled={disabled}\n onClick={() => editor.chain().focus().toggleItalic().run()}\n >\n <Italic size={16} />\n </TbButton>\n ),\n config.inline.buttons.underline && (\n <TbButton\n key=\"underline\"\n label=\"Underline\"\n active={state.underline}\n disabled={disabled}\n onClick={() => editor.chain().focus().toggleUnderline().run()}\n >\n <Underline size={16} />\n </TbButton>\n ),\n config.inline.buttons.strike && (\n <TbButton\n key=\"strike\"\n label=\"Strikethrough\"\n active={state.strike}\n disabled={disabled}\n onClick={() => editor.chain().focus().toggleStrike().run()}\n >\n <Strikethrough size={16} />\n </TbButton>\n ),\n config.inline.buttons.code && (\n <TbButton\n key=\"code\"\n label=\"Inline code\"\n active={state.code}\n disabled={disabled}\n onClick={() => editor.chain().focus().toggleCode().run()}\n >\n <Code size={16} />\n </TbButton>\n ),\n ]\n : [];\n\n const headings = config.headings.enabled\n ? ([2, 3, 4, 5, 6] as const).map((level) =>\n config.headings.buttons[`h${level}` as keyof typeof config.headings.buttons] ? (\n <TbButton\n key={`h${level}`}\n label={`Heading ${level}`}\n active={headingActive[level]}\n disabled={disabled}\n onClick={() => editor.chain().focus().toggleHeading({ level }).run()}\n >\n {HEADING_ICONS[level]}\n </TbButton>\n ) : (\n false\n ),\n )\n : [];\n\n const lists = config.lists.enabled\n ? [\n config.lists.buttons.bullet && (\n <TbButton\n key=\"bullet\"\n label=\"Bullet list\"\n active={state.bulletList}\n disabled={disabled}\n onClick={() => editor.chain().focus().toggleBulletList().run()}\n >\n <List size={16} />\n </TbButton>\n ),\n config.lists.buttons.ordered && (\n <TbButton\n key=\"ordered\"\n label=\"Ordered list\"\n active={state.orderedList}\n disabled={disabled}\n onClick={() => editor.chain().focus().toggleOrderedList().run()}\n >\n <ListOrdered size={16} />\n </TbButton>\n ),\n config.lists.buttons.blockquote && (\n <TbButton\n key=\"quote\"\n label=\"Quote\"\n active={state.blockquote}\n disabled={disabled}\n onClick={() => editor.chain().focus().toggleBlockquote().run()}\n >\n <Quote size={16} />\n </TbButton>\n ),\n ]\n : [];\n\n const align = config.align.enabled\n ? [\n config.align.buttons.left && (\n <TbButton\n key=\"left\"\n label=\"Align left\"\n active={state.alignLeft}\n disabled={disabled}\n onClick={() => editor.chain().focus().setTextAlign(\"left\").run()}\n >\n <AlignLeft size={16} />\n </TbButton>\n ),\n config.align.buttons.center && (\n <TbButton\n key=\"center\"\n label=\"Align center\"\n active={state.alignCenter}\n disabled={disabled}\n onClick={() => editor.chain().focus().setTextAlign(\"center\").run()}\n >\n <AlignCenter size={16} />\n </TbButton>\n ),\n config.align.buttons.right && (\n <TbButton\n key=\"right\"\n label=\"Align right\"\n active={state.alignRight}\n disabled={disabled}\n onClick={() => editor.chain().focus().setTextAlign(\"right\").run()}\n >\n <AlignRight size={16} />\n </TbButton>\n ),\n ]\n : [];\n\n const inserts = [\n config.link && (\n <TbButton\n key=\"link\"\n label=\"Link\"\n active={state.link}\n disabled={disabled}\n onClick={onLink}\n >\n <LinkIcon size={16} />\n </TbButton>\n ),\n config.image && (\n <TbButton\n key=\"image\"\n label=\"Image\"\n active={state.image}\n disabled={disabled}\n onClick={onImage}\n >\n <ImageIcon size={16} />\n </TbButton>\n ),\n config.button && (\n <TbButton\n key=\"button\"\n label=\"Button\"\n disabled={disabled}\n onClick={onButton}\n >\n <MousePointerClick size={16} />\n </TbButton>\n ),\n ];\n\n const blocks = config.blocks.enabled\n ? [\n config.blocks.buttons.paragraph && (\n <TbButton\n key=\"paragraph\"\n label=\"Insert paragraph\"\n disabled={disabled}\n onClick={onInsertParagraph}\n >\n <Pilcrow size={16} />\n </TbButton>\n ),\n config.blocks.buttons.divider && (\n <TbButton\n key=\"divider\"\n label=\"Insert divider\"\n disabled={disabled}\n onClick={onInsertDivider}\n >\n <Minus size={16} />\n </TbButton>\n ),\n config.blocks.buttons.footer && (\n <TbButton\n key=\"footer\"\n label=\"Insert footer\"\n disabled={disabled}\n onClick={onInsertFooter}\n >\n <PanelBottom size={16} />\n </TbButton>\n ),\n ]\n : [];\n\n const html = config.html\n ? [\n <TbButton\n key=\"html\"\n label=\"HTML source\"\n active={htmlMode}\n onClick={onToggleHtml}\n >\n <FileCode size={16} />\n </TbButton>,\n ]\n : [];\n\n // Keep only groups that have at least one visible button, then render them\n // with a divider between each — so hidden groups leave no dangling dividers.\n const groups = [inline, headings, lists, align, inserts, blocks, html]\n .map((g) => g.filter(Boolean))\n .filter((g) => g.length > 0);\n\n return (\n <div className=\"rte-toolbar\" role=\"toolbar\" aria-label=\"Formatting\">\n {groups.map((buttons, i) => (\n <Fragment key={i}>\n {i > 0 && <Divider />}\n <div className=\"rte-tb-group\">{buttons}</div>\n </Fragment>\n ))}\n </div>\n );\n}\n\nexport default Toolbar;\n","import { EditorContent, useEditor } from \"@tiptap/react\";\nimport {\n forwardRef,\n useCallback,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { createEmailExtensions } from \"../extensions/editorExtensions\";\nimport type { ToolbarConfig } from \"../types\";\nimport { PRESET_DIVIDER, PRESET_FOOTER, PRESET_PARAGRAPH } from \"../utils/presets\";\nimport { resolveToolbarConfig } from \"../utils/resolveToolbarConfig\";\nimport { EmailButtonDialog } from \"./EmailButtonDialog\";\nimport { Toolbar } from \"./Toolbar\";\n\nexport type RichTextEditorHandle = {\n /** Insert raw HTML or plain text at the current caret position. */\n insertAtCaret: (htmlOrText: string) => void;\n};\n\nexport type RichTextEditorProps = {\n /** Body-level HTML fragment (no `<html>`/`<body>`). */\n value: string;\n /** Fires with the updated body-level HTML fragment. */\n onChange: (value: string) => void;\n /** Empty-state placeholder text. */\n placeholder?: string;\n /** Resolve an uploaded file to a hosted image URL. */\n onUploadImage?: (file: File) => Promise<string>;\n /** Extra class on the editor wrapper. */\n className?: string;\n /** Inline style applied to the editable surface (e.g. minHeight/height). */\n editorStyle?: React.CSSProperties;\n /** Which toolbar controls to render (defaults to everything on). */\n toolbar?: ToolbarConfig;\n};\n\n/**\n * The generic body-HTML rich-text editor: a toolbar plus an editable surface\n * operating on a plain HTML fragment. `<EmailEditor>` wraps this to add the\n * document-shell bridge and placeholder chips.\n */\nexport const RichTextEditor = forwardRef<\n RichTextEditorHandle,\n RichTextEditorProps\n>(function RichTextEditor(\n {\n value,\n onChange,\n placeholder = \"Write your email…\",\n onUploadImage,\n className,\n editorStyle,\n toolbar,\n },\n ref,\n) {\n const toolbarConfig = useMemo(\n () => resolveToolbarConfig(toolbar),\n [toolbar],\n );\n\n const extensions = useMemo(\n () => createEmailExtensions(placeholder),\n [placeholder],\n );\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [htmlMode, setHtmlMode] = useState(false);\n const [buttonDialogOpen, setButtonDialogOpen] = useState(false);\n\n const editor = useEditor({\n extensions,\n content: value,\n // REQUIRED for SSR/Next.js — rendering immediately causes a hydration\n // mismatch because the server has no DOM.\n immediatelyRender: false,\n editorProps: {\n attributes: {\n class: \"rte-content\",\n },\n },\n onUpdate: ({ editor: e }) => {\n onChange(e.getHTML());\n },\n });\n\n // Sync external `value` changes into the editor without fighting the user's\n // typing: only when it actually differs from the editor's current HTML, and\n // with emitUpdate:false so it does not re-fire onChange.\n if (editor && !htmlMode && value !== editor.getHTML()) {\n editor.commands.setContent(value, { emitUpdate: false });\n }\n\n useImperativeHandle(\n ref,\n () => ({\n insertAtCaret: (htmlOrText: string) => {\n editor?.chain().focus().insertContent(htmlOrText).run();\n },\n }),\n [editor],\n );\n\n const insertSnippet = useCallback(\n (snippet: string) => {\n editor?.chain().focus().insertContent(snippet).run();\n },\n [editor],\n );\n\n const handleLink = useCallback(() => {\n if (!editor) return;\n const previous = editor.getAttributes(\"link\").href as string | undefined;\n const url = window.prompt(\"Link URL\", previous ?? \"https://\");\n if (url === null) return; // cancelled\n if (url.trim() === \"\") {\n editor.chain().focus().extendMarkRange(\"link\").unsetLink().run();\n return;\n }\n editor\n .chain()\n .focus()\n .extendMarkRange(\"link\")\n .setLink({ href: url.trim() })\n .run();\n }, [editor]);\n\n const handleImageClick = useCallback(() => {\n if (!editor) return;\n if (onUploadImage) {\n fileInputRef.current?.click();\n } else {\n const url = window.prompt(\"Image URL\", \"https://\");\n if (url && url.trim()) {\n editor.chain().focus().setImage({ src: url.trim() }).run();\n }\n }\n }, [editor, onUploadImage]);\n\n const handleFileSelected = useCallback(\n async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n // Reset so selecting the same file again re-triggers change.\n e.target.value = \"\";\n if (!file || !onUploadImage || !editor) return;\n try {\n const src = await onUploadImage(file);\n if (src) editor.chain().focus().setImage({ src }).run();\n } catch (err) {\n // Surface upload failures without crashing the editor.\n console.error(\"dpk-editor: image upload failed\", err);\n }\n },\n [editor, onUploadImage],\n );\n\n const handleButtonConfirm = useCallback(\n (html: string) => {\n setButtonDialogOpen(false);\n editor?.chain().focus().insertContent(html).run();\n },\n [editor],\n );\n\n if (!editor) return null;\n\n return (\n <div className={\"rte-root\" + (className ? ` ${className}` : \"\")}>\n <Toolbar\n editor={editor}\n config={toolbarConfig}\n htmlMode={htmlMode}\n onToggleHtml={() => setHtmlMode((m) => !m)}\n onLink={handleLink}\n onImage={handleImageClick}\n onButton={() => setButtonDialogOpen(true)}\n onInsertParagraph={() => insertSnippet(PRESET_PARAGRAPH)}\n onInsertDivider={() => insertSnippet(PRESET_DIVIDER)}\n onInsertFooter={() => insertSnippet(PRESET_FOOTER)}\n />\n\n {htmlMode ? (\n <textarea\n className=\"rte-source\"\n style={editorStyle}\n value={value}\n spellCheck={false}\n onChange={(e) => onChange(e.target.value)}\n aria-label=\"HTML source\"\n />\n ) : (\n <EditorContent\n editor={editor}\n className=\"rte-editor\"\n style={editorStyle}\n />\n )}\n\n {onUploadImage && (\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n hidden\n onChange={handleFileSelected}\n />\n )}\n\n <EmailButtonDialog\n open={buttonDialogOpen}\n onConfirm={handleButtonConfirm}\n onClose={() => setButtonDialogOpen(false)}\n />\n </div>\n );\n});\n\nexport default RichTextEditor;\n","import { useCallback, useEffect, useMemo, useRef } from \"react\";\n\nimport type {\n EmailHtmlDocument,\n EmailPlaceholder,\n ToolbarConfig,\n} from \"../types\";\nimport { joinEmailHtml, splitEmailHtml } from \"../utils/shellBridge\";\nimport {\n RichTextEditor,\n type RichTextEditorHandle,\n} from \"./RichTextEditor\";\n\nexport type EmailEditorProps = {\n /** Full HTML document OR a bare body fragment — both are supported. */\n value: string;\n /** Fires with HTML in the same shape `value` was provided (shell re-applied). */\n onChange: (value: string) => void;\n /** Optional merge tokens; render a chip row that inserts at the caret. */\n placeholders?: EmailPlaceholder[];\n /** Resolve an uploaded file to a hosted image URL (else a URL prompt is used). */\n onUploadImage?: (file: File) => Promise<string>;\n /** Minimum height (px) of the editable surface. */\n minHeight?: number;\n /** Empty-state placeholder text for the editable surface. */\n placeholder?: string;\n /** Which toolbar controls to render (defaults to everything on). */\n toolbar?: ToolbarConfig;\n /** Extra class on the editor wrapper. */\n className?: string;\n};\n\n/**\n * The batteries-included email editor. Owns the document-shell bridge so the\n * surrounding `<!doctype>`/`<html>`/`<body style>` (and any `<table>` layout\n * inside the body) is preserved verbatim across edits, while only the body\n * fragment is handed to the underlying `<RichTextEditor>`.\n */\nexport function EmailEditor({\n value,\n onChange,\n placeholders,\n onUploadImage,\n minHeight = 288,\n placeholder,\n toolbar,\n className,\n}: EmailEditorProps) {\n // Recompute the shell from `value` on each render.\n const shell = useMemo<EmailHtmlDocument>(() => splitEmailHtml(value), [value]);\n\n // Keep the latest shell in a ref so the onChange closure always rejoins with\n // the current prefix/suffix. Assign the ref in an EFFECT (never during\n // render) — strict react-hooks lint flags a render-time ref write.\n const shellRef = useRef<EmailHtmlDocument>(shell);\n useEffect(() => {\n shellRef.current = shell;\n }, [shell]);\n\n const editorRef = useRef<RichTextEditorHandle>(null);\n\n const handleBodyChange = useCallback(\n (newBody: string) => {\n onChange(joinEmailHtml(shellRef.current, newBody));\n },\n [onChange],\n );\n\n const editorStyle = useMemo<React.CSSProperties>(\n () => ({ minHeight }),\n [minHeight],\n );\n\n const insertToken = useCallback((token: string) => {\n editorRef.current?.insertAtCaret(token);\n }, []);\n\n return (\n <div className={\"rte-email\" + (className ? ` ${className}` : \"\")}>\n {placeholders && placeholders.length > 0 && (\n <div className=\"rte-chips\" role=\"group\" aria-label=\"Merge tokens\">\n {placeholders.map((p) => (\n <button\n key={p.token}\n type=\"button\"\n className=\"rte-chip\"\n title={`Insert ${p.token}`}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => insertToken(p.token)}\n >\n {p.label}\n </button>\n ))}\n </div>\n )}\n\n <RichTextEditor\n ref={editorRef}\n value={shell.body}\n onChange={handleBodyChange}\n onUploadImage={onUploadImage}\n placeholder={placeholder}\n editorStyle={editorStyle}\n toolbar={toolbar}\n />\n </div>\n );\n}\n\nexport default EmailEditor;\n"]}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/* dpk-editor styles
|
|
2
|
+
* Import once in your app: import "dpk-editor/styles.css";
|
|
3
|
+
*
|
|
4
|
+
* Theme via CSS custom properties — override any of these on a parent element
|
|
5
|
+
* (or :root) to restyle without forking the CSS. Defaults below are neutral and
|
|
6
|
+
* do NOT depend on Tailwind/shadcn.
|
|
7
|
+
*/
|
|
8
|
+
.rte-root,
|
|
9
|
+
.rte-email {
|
|
10
|
+
--rte-accent: #2563eb;
|
|
11
|
+
--rte-accent-contrast: #ffffff;
|
|
12
|
+
--rte-border: #e5e7eb;
|
|
13
|
+
--rte-border-strong: #d1d5db;
|
|
14
|
+
--rte-bg: #ffffff;
|
|
15
|
+
--rte-bg-muted: #f9fafb;
|
|
16
|
+
--rte-fg: #111827;
|
|
17
|
+
--rte-fg-muted: #6b7280;
|
|
18
|
+
--rte-radius: 8px;
|
|
19
|
+
--rte-active-bg: #eff6ff;
|
|
20
|
+
--rte-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
|
21
|
+
Arial, sans-serif;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.rte-email {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
gap: 8px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.rte-root {
|
|
31
|
+
border: 1px solid var(--rte-border);
|
|
32
|
+
border-radius: var(--rte-radius);
|
|
33
|
+
background: var(--rte-bg);
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
font-family: var(--rte-font);
|
|
36
|
+
color: var(--rte-fg);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ---------- Placeholder / merge-token chips ---------- */
|
|
40
|
+
.rte-chips {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-wrap: wrap;
|
|
43
|
+
gap: 6px;
|
|
44
|
+
}
|
|
45
|
+
.rte-chip {
|
|
46
|
+
border: 1px solid var(--rte-border-strong);
|
|
47
|
+
background: var(--rte-bg-muted);
|
|
48
|
+
color: var(--rte-fg);
|
|
49
|
+
border-radius: 999px;
|
|
50
|
+
padding: 4px 10px;
|
|
51
|
+
font-size: 12px;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
font-family: var(--rte-font);
|
|
54
|
+
}
|
|
55
|
+
.rte-chip:hover {
|
|
56
|
+
border-color: var(--rte-accent);
|
|
57
|
+
color: var(--rte-accent);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ---------- Toolbar ---------- */
|
|
61
|
+
.rte-toolbar {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-wrap: wrap;
|
|
64
|
+
align-items: center;
|
|
65
|
+
gap: 2px;
|
|
66
|
+
padding: 6px;
|
|
67
|
+
border-bottom: 1px solid var(--rte-border);
|
|
68
|
+
background: var(--rte-bg-muted);
|
|
69
|
+
}
|
|
70
|
+
.rte-tb-group {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 2px;
|
|
74
|
+
}
|
|
75
|
+
.rte-tb-divider {
|
|
76
|
+
width: 1px;
|
|
77
|
+
align-self: stretch;
|
|
78
|
+
margin: 4px 4px;
|
|
79
|
+
background: var(--rte-border);
|
|
80
|
+
}
|
|
81
|
+
.rte-tb-btn {
|
|
82
|
+
display: inline-flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
width: 30px;
|
|
86
|
+
height: 30px;
|
|
87
|
+
border: 1px solid transparent;
|
|
88
|
+
border-radius: 6px;
|
|
89
|
+
background: transparent;
|
|
90
|
+
color: var(--rte-fg);
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
padding: 0;
|
|
93
|
+
}
|
|
94
|
+
.rte-tb-btn:hover:not(:disabled) {
|
|
95
|
+
background: var(--rte-bg);
|
|
96
|
+
border-color: var(--rte-border);
|
|
97
|
+
}
|
|
98
|
+
.rte-tb-btn--active {
|
|
99
|
+
background: var(--rte-active-bg);
|
|
100
|
+
border-color: var(--rte-accent);
|
|
101
|
+
color: var(--rte-accent);
|
|
102
|
+
}
|
|
103
|
+
.rte-tb-btn:disabled {
|
|
104
|
+
opacity: 0.4;
|
|
105
|
+
cursor: not-allowed;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* ---------- Editable surface ---------- */
|
|
109
|
+
.rte-editor {
|
|
110
|
+
padding: 0;
|
|
111
|
+
}
|
|
112
|
+
.rte-content {
|
|
113
|
+
padding: 16px;
|
|
114
|
+
outline: none;
|
|
115
|
+
line-height: 1.6;
|
|
116
|
+
color: var(--rte-fg);
|
|
117
|
+
font-family: var(--rte-font);
|
|
118
|
+
}
|
|
119
|
+
.rte-content:focus-visible {
|
|
120
|
+
outline: none;
|
|
121
|
+
}
|
|
122
|
+
.rte-editor:focus-within {
|
|
123
|
+
box-shadow: inset 0 0 0 2px var(--rte-accent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Empty-state placeholder.
|
|
127
|
+
* TipTap v3's Placeholder extension applies `is-empty` to each empty node and
|
|
128
|
+
* `is-editor-empty` to the editor root, and writes the label to the
|
|
129
|
+
* `data-placeholder` attribute. We render it via ::before on the first empty
|
|
130
|
+
* node. Both class names are covered so it works regardless of which one the
|
|
131
|
+
* node carries. */
|
|
132
|
+
.rte-content p.is-empty:first-child::before,
|
|
133
|
+
.rte-content .is-empty[data-placeholder]:first-child::before,
|
|
134
|
+
.rte-content.is-editor-empty p:first-child::before {
|
|
135
|
+
content: attr(data-placeholder);
|
|
136
|
+
color: var(--rte-fg-muted);
|
|
137
|
+
float: left;
|
|
138
|
+
height: 0;
|
|
139
|
+
pointer-events: none;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* ---------- Content typography ---------- */
|
|
143
|
+
.rte-content h1 { font-size: 30px; line-height: 1.25; margin: 0 0 12px; font-weight: 700; }
|
|
144
|
+
.rte-content h2 { font-size: 24px; line-height: 1.3; margin: 0 0 12px; font-weight: 700; }
|
|
145
|
+
.rte-content h3 { font-size: 20px; line-height: 1.35; margin: 0 0 10px; font-weight: 600; }
|
|
146
|
+
.rte-content h4 { font-size: 18px; line-height: 1.4; margin: 0 0 8px; font-weight: 600; }
|
|
147
|
+
.rte-content h5 { font-size: 16px; line-height: 1.4; margin: 0 0 8px; font-weight: 600; }
|
|
148
|
+
.rte-content h6 { font-size: 14px; line-height: 1.4; margin: 0 0 8px; font-weight: 600; }
|
|
149
|
+
.rte-content p { margin: 0 0 12px; }
|
|
150
|
+
|
|
151
|
+
.rte-content ul,
|
|
152
|
+
.rte-content ol {
|
|
153
|
+
margin: 0 0 12px;
|
|
154
|
+
padding-left: 24px;
|
|
155
|
+
}
|
|
156
|
+
.rte-content ul { list-style: disc; }
|
|
157
|
+
.rte-content ol { list-style: decimal; }
|
|
158
|
+
.rte-content li { margin: 2px 0; }
|
|
159
|
+
|
|
160
|
+
.rte-content blockquote {
|
|
161
|
+
margin: 0 0 12px;
|
|
162
|
+
padding-left: 14px;
|
|
163
|
+
border-left: 3px solid var(--rte-border-strong);
|
|
164
|
+
color: var(--rte-fg-muted);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.rte-content pre {
|
|
168
|
+
background: #0f172a;
|
|
169
|
+
color: #e2e8f0;
|
|
170
|
+
padding: 12px 14px;
|
|
171
|
+
border-radius: 6px;
|
|
172
|
+
overflow-x: auto;
|
|
173
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
174
|
+
font-size: 13px;
|
|
175
|
+
margin: 0 0 12px;
|
|
176
|
+
}
|
|
177
|
+
.rte-content code {
|
|
178
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
179
|
+
font-size: 0.9em;
|
|
180
|
+
background: var(--rte-bg-muted);
|
|
181
|
+
border: 1px solid var(--rte-border);
|
|
182
|
+
border-radius: 4px;
|
|
183
|
+
padding: 1px 4px;
|
|
184
|
+
}
|
|
185
|
+
.rte-content pre code {
|
|
186
|
+
background: none;
|
|
187
|
+
border: none;
|
|
188
|
+
padding: 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.rte-content a {
|
|
192
|
+
color: var(--rte-accent);
|
|
193
|
+
text-decoration: underline;
|
|
194
|
+
}
|
|
195
|
+
.rte-content img {
|
|
196
|
+
max-width: 100%;
|
|
197
|
+
height: auto;
|
|
198
|
+
border-radius: 4px;
|
|
199
|
+
}
|
|
200
|
+
.rte-content hr {
|
|
201
|
+
border: none;
|
|
202
|
+
border-top: 1px solid var(--rte-border);
|
|
203
|
+
margin: 16px 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* ---------- HTML source view ---------- */
|
|
207
|
+
.rte-source {
|
|
208
|
+
width: 100%;
|
|
209
|
+
box-sizing: border-box;
|
|
210
|
+
border: none;
|
|
211
|
+
padding: 16px;
|
|
212
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
213
|
+
font-size: 13px;
|
|
214
|
+
line-height: 1.5;
|
|
215
|
+
resize: vertical;
|
|
216
|
+
outline: none;
|
|
217
|
+
color: var(--rte-fg);
|
|
218
|
+
background: var(--rte-bg);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* ---------- Button dialog ---------- */
|
|
222
|
+
.rte-dialog-overlay {
|
|
223
|
+
position: fixed;
|
|
224
|
+
inset: 0;
|
|
225
|
+
background: rgba(15, 23, 42, 0.45);
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
z-index: 1000;
|
|
230
|
+
padding: 16px;
|
|
231
|
+
font-family: var(--rte-font);
|
|
232
|
+
}
|
|
233
|
+
.rte-dialog {
|
|
234
|
+
background: var(--rte-bg);
|
|
235
|
+
border-radius: 12px;
|
|
236
|
+
width: 100%;
|
|
237
|
+
max-width: 420px;
|
|
238
|
+
max-height: 90vh;
|
|
239
|
+
overflow-y: auto;
|
|
240
|
+
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.3);
|
|
241
|
+
color: var(--rte-fg);
|
|
242
|
+
}
|
|
243
|
+
.rte-dialog-header {
|
|
244
|
+
display: flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: space-between;
|
|
247
|
+
padding: 16px 20px;
|
|
248
|
+
border-bottom: 1px solid var(--rte-border);
|
|
249
|
+
}
|
|
250
|
+
.rte-dialog-title {
|
|
251
|
+
font-size: 16px;
|
|
252
|
+
font-weight: 600;
|
|
253
|
+
margin: 0;
|
|
254
|
+
}
|
|
255
|
+
.rte-dialog-close {
|
|
256
|
+
border: none;
|
|
257
|
+
background: none;
|
|
258
|
+
font-size: 22px;
|
|
259
|
+
line-height: 1;
|
|
260
|
+
cursor: pointer;
|
|
261
|
+
color: var(--rte-fg-muted);
|
|
262
|
+
}
|
|
263
|
+
.rte-dialog-body {
|
|
264
|
+
padding: 16px 20px;
|
|
265
|
+
display: flex;
|
|
266
|
+
flex-direction: column;
|
|
267
|
+
gap: 14px;
|
|
268
|
+
}
|
|
269
|
+
.rte-dialog-field {
|
|
270
|
+
display: flex;
|
|
271
|
+
flex-direction: column;
|
|
272
|
+
gap: 6px;
|
|
273
|
+
}
|
|
274
|
+
.rte-dialog-label {
|
|
275
|
+
font-size: 12px;
|
|
276
|
+
font-weight: 600;
|
|
277
|
+
color: var(--rte-fg-muted);
|
|
278
|
+
}
|
|
279
|
+
.rte-text-input,
|
|
280
|
+
.rte-hex-input {
|
|
281
|
+
border: 1px solid var(--rte-border-strong);
|
|
282
|
+
border-radius: 6px;
|
|
283
|
+
padding: 8px 10px;
|
|
284
|
+
font-size: 14px;
|
|
285
|
+
font-family: var(--rte-font);
|
|
286
|
+
color: var(--rte-fg);
|
|
287
|
+
background: var(--rte-bg);
|
|
288
|
+
}
|
|
289
|
+
.rte-hex-input {
|
|
290
|
+
width: 86px;
|
|
291
|
+
font-family: ui-monospace, monospace;
|
|
292
|
+
}
|
|
293
|
+
.rte-text-input:focus,
|
|
294
|
+
.rte-hex-input:focus {
|
|
295
|
+
outline: none;
|
|
296
|
+
border-color: var(--rte-accent);
|
|
297
|
+
}
|
|
298
|
+
.rte-color-row {
|
|
299
|
+
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
flex-wrap: wrap;
|
|
302
|
+
gap: 6px;
|
|
303
|
+
}
|
|
304
|
+
.rte-swatch {
|
|
305
|
+
width: 24px;
|
|
306
|
+
height: 24px;
|
|
307
|
+
border-radius: 6px;
|
|
308
|
+
border: 2px solid transparent;
|
|
309
|
+
cursor: pointer;
|
|
310
|
+
padding: 0;
|
|
311
|
+
}
|
|
312
|
+
.rte-swatch--active {
|
|
313
|
+
border-color: var(--rte-fg);
|
|
314
|
+
box-shadow: 0 0 0 2px var(--rte-bg), 0 0 0 4px var(--rte-accent);
|
|
315
|
+
}
|
|
316
|
+
.rte-color-input {
|
|
317
|
+
width: 32px;
|
|
318
|
+
height: 28px;
|
|
319
|
+
padding: 0;
|
|
320
|
+
border: 1px solid var(--rte-border-strong);
|
|
321
|
+
border-radius: 6px;
|
|
322
|
+
background: var(--rte-bg);
|
|
323
|
+
cursor: pointer;
|
|
324
|
+
}
|
|
325
|
+
.rte-align-row {
|
|
326
|
+
display: flex;
|
|
327
|
+
gap: 6px;
|
|
328
|
+
}
|
|
329
|
+
.rte-align-btn {
|
|
330
|
+
flex: 1;
|
|
331
|
+
border: 1px solid var(--rte-border-strong);
|
|
332
|
+
background: var(--rte-bg);
|
|
333
|
+
border-radius: 6px;
|
|
334
|
+
padding: 6px 0;
|
|
335
|
+
font-size: 13px;
|
|
336
|
+
text-transform: capitalize;
|
|
337
|
+
cursor: pointer;
|
|
338
|
+
color: var(--rte-fg);
|
|
339
|
+
font-family: var(--rte-font);
|
|
340
|
+
}
|
|
341
|
+
.rte-align-btn--active {
|
|
342
|
+
border-color: var(--rte-accent);
|
|
343
|
+
background: var(--rte-active-bg);
|
|
344
|
+
color: var(--rte-accent);
|
|
345
|
+
}
|
|
346
|
+
.rte-checkbox-field {
|
|
347
|
+
display: flex;
|
|
348
|
+
align-items: center;
|
|
349
|
+
gap: 8px;
|
|
350
|
+
font-size: 14px;
|
|
351
|
+
cursor: pointer;
|
|
352
|
+
}
|
|
353
|
+
.rte-button-preview {
|
|
354
|
+
border: 1px dashed var(--rte-border-strong);
|
|
355
|
+
border-radius: 8px;
|
|
356
|
+
padding: 12px;
|
|
357
|
+
background: var(--rte-bg-muted);
|
|
358
|
+
}
|
|
359
|
+
.rte-button-preview p {
|
|
360
|
+
margin: 0;
|
|
361
|
+
}
|
|
362
|
+
.rte-dialog-footer {
|
|
363
|
+
display: flex;
|
|
364
|
+
justify-content: flex-end;
|
|
365
|
+
gap: 8px;
|
|
366
|
+
padding: 14px 20px;
|
|
367
|
+
border-top: 1px solid var(--rte-border);
|
|
368
|
+
}
|
|
369
|
+
.rte-btn {
|
|
370
|
+
border-radius: 6px;
|
|
371
|
+
padding: 8px 14px;
|
|
372
|
+
font-size: 14px;
|
|
373
|
+
font-weight: 500;
|
|
374
|
+
cursor: pointer;
|
|
375
|
+
font-family: var(--rte-font);
|
|
376
|
+
border: 1px solid transparent;
|
|
377
|
+
}
|
|
378
|
+
.rte-btn--ghost {
|
|
379
|
+
background: var(--rte-bg);
|
|
380
|
+
border-color: var(--rte-border-strong);
|
|
381
|
+
color: var(--rte-fg);
|
|
382
|
+
}
|
|
383
|
+
.rte-btn--primary {
|
|
384
|
+
background: var(--rte-accent);
|
|
385
|
+
color: var(--rte-accent-contrast);
|
|
386
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dpk-editor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A React rich-text editor built on TipTap v3, purpose-built for composing HTML emails. Preserves inline styles, the full document shell, and produces email-safe (bulletproof) markup.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"react",
|
|
10
|
+
"tiptap",
|
|
11
|
+
"email",
|
|
12
|
+
"rich-text-editor",
|
|
13
|
+
"wysiwyg",
|
|
14
|
+
"html-email",
|
|
15
|
+
"editor"
|
|
16
|
+
],
|
|
17
|
+
"sideEffects": [
|
|
18
|
+
"*.css"
|
|
19
|
+
],
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js",
|
|
27
|
+
"require": "./dist/index.cjs"
|
|
28
|
+
},
|
|
29
|
+
"./styles.css": "./dist/styles.css"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"dev": "tsup --watch",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@tiptap/extension-highlight": "^3.23.6",
|
|
45
|
+
"@tiptap/extension-image": "^3.23.6",
|
|
46
|
+
"@tiptap/extension-placeholder": "^3.23.6",
|
|
47
|
+
"@tiptap/extension-text-align": "^3.23.6",
|
|
48
|
+
"@tiptap/extension-text-style": "^3.23.6",
|
|
49
|
+
"@tiptap/react": "^3.23.6",
|
|
50
|
+
"@tiptap/starter-kit": "^3.23.6",
|
|
51
|
+
"lucide-react": "^0.460.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"react": ">=18",
|
|
55
|
+
"react-dom": ">=18"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@eslint/js": "^9.17.0",
|
|
59
|
+
"@testing-library/dom": "^10.4.0",
|
|
60
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
61
|
+
"@testing-library/react": "^16.1.0",
|
|
62
|
+
"@testing-library/user-event": "^14.5.2",
|
|
63
|
+
"@types/react": "^19.0.0",
|
|
64
|
+
"@types/react-dom": "^19.0.0",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
|
66
|
+
"@typescript-eslint/parser": "^8.18.0",
|
|
67
|
+
"eslint": "^9.17.0",
|
|
68
|
+
"eslint-plugin-react-hooks": "^5.1.0",
|
|
69
|
+
"jsdom": "^25.0.1",
|
|
70
|
+
"react": "^19.0.0",
|
|
71
|
+
"react-dom": "^19.0.0",
|
|
72
|
+
"tsup": "^8.3.5",
|
|
73
|
+
"typescript": "^5.7.2",
|
|
74
|
+
"vitest": "^2.1.8"
|
|
75
|
+
},
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": ">=18"
|
|
78
|
+
}
|
|
79
|
+
}
|