@editora/core 1.0.1 → 1.0.2

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.
Files changed (121) hide show
  1. package/README.md +9 -0
  2. package/dist/A11yCheckerPlugin.native-CZKpi3uF.mjs +475 -0
  3. package/dist/A11yCheckerPlugin.native-CZKpi3uF.mjs.map +1 -0
  4. package/dist/AnchorPlugin.native-7es9PVZ9.mjs +340 -0
  5. package/dist/AnchorPlugin.native-7es9PVZ9.mjs.map +1 -0
  6. package/dist/BackgroundColorPlugin.native-Dip5uqTg.mjs +449 -0
  7. package/dist/BackgroundColorPlugin.native-Dip5uqTg.mjs.map +1 -0
  8. package/dist/BlockquotePlugin.native-JFmOLsxN.mjs +48 -0
  9. package/dist/BlockquotePlugin.native-JFmOLsxN.mjs.map +1 -0
  10. package/dist/BoldPlugin.native-BAzzoqU5.mjs +45 -0
  11. package/dist/BoldPlugin.native-BAzzoqU5.mjs.map +1 -0
  12. package/dist/CapitalizationPlugin.native-DOMsh5R7.mjs +79 -0
  13. package/dist/CapitalizationPlugin.native-DOMsh5R7.mjs.map +1 -0
  14. package/dist/ChecklistPlugin.native-Dccs3nLe.mjs +153 -0
  15. package/dist/ChecklistPlugin.native-Dccs3nLe.mjs.map +1 -0
  16. package/dist/ClearFormattingPlugin.native-BZPDHswo.mjs +27 -0
  17. package/dist/ClearFormattingPlugin.native-BZPDHswo.mjs.map +1 -0
  18. package/dist/CodePlugin.native-DD9xFIid.mjs +1679 -0
  19. package/dist/CodePlugin.native-DD9xFIid.mjs.map +1 -0
  20. package/dist/CodeSamplePlugin.native-DMbEdO9j.mjs +326 -0
  21. package/dist/CodeSamplePlugin.native-DMbEdO9j.mjs.map +1 -0
  22. package/dist/CommentsPlugin.native-2zQV8Ia4.mjs +473 -0
  23. package/dist/CommentsPlugin.native-2zQV8Ia4.mjs.map +1 -0
  24. package/dist/DirectionPlugin.native-Be7wCzkI.mjs +59 -0
  25. package/dist/DirectionPlugin.native-Be7wCzkI.mjs.map +1 -0
  26. package/dist/DocumentManagerPlugin.native-BvZL5CSG.mjs +116 -0
  27. package/dist/DocumentManagerPlugin.native-BvZL5CSG.mjs.map +1 -0
  28. package/dist/EmbedIframePlugin.native-ifr9KLdN.mjs +461 -0
  29. package/dist/EmbedIframePlugin.native-ifr9KLdN.mjs.map +1 -0
  30. package/dist/EmojisPlugin.native-D6mJSnSR.mjs +1033 -0
  31. package/dist/EmojisPlugin.native-D6mJSnSR.mjs.map +1 -0
  32. package/dist/FontFamilyPlugin.native-BzS_9qbM.mjs +106 -0
  33. package/dist/FontFamilyPlugin.native-BzS_9qbM.mjs.map +1 -0
  34. package/dist/FontSizePlugin.native-DkLMLPue.mjs +186 -0
  35. package/dist/FontSizePlugin.native-DkLMLPue.mjs.map +1 -0
  36. package/dist/FootnotePlugin.native-BciVc9W6.mjs +128 -0
  37. package/dist/FootnotePlugin.native-BciVc9W6.mjs.map +1 -0
  38. package/dist/FullscreenPlugin.native-ChXyxeNw.mjs +77 -0
  39. package/dist/FullscreenPlugin.native-ChXyxeNw.mjs.map +1 -0
  40. package/dist/HeadingPlugin.native-DrLYwQnQ.mjs +64 -0
  41. package/dist/HeadingPlugin.native-DrLYwQnQ.mjs.map +1 -0
  42. package/dist/HistoryPlugin.native-DoDRifCf.mjs +89 -0
  43. package/dist/HistoryPlugin.native-DoDRifCf.mjs.map +1 -0
  44. package/dist/IndentPlugin.native-CbFugPoi.mjs +133 -0
  45. package/dist/IndentPlugin.native-CbFugPoi.mjs.map +1 -0
  46. package/dist/ItalicPlugin.native-CQjjDyUL.mjs +43 -0
  47. package/dist/ItalicPlugin.native-CQjjDyUL.mjs.map +1 -0
  48. package/dist/LineHeightPlugin.native-CWQT2FIa.mjs +73 -0
  49. package/dist/LineHeightPlugin.native-CWQT2FIa.mjs.map +1 -0
  50. package/dist/LinkPlugin.native-BdAOV-iu.mjs +206 -0
  51. package/dist/LinkPlugin.native-BdAOV-iu.mjs.map +1 -0
  52. package/dist/ListPlugin.native-CLFU5AUQ.mjs +59 -0
  53. package/dist/ListPlugin.native-CLFU5AUQ.mjs.map +1 -0
  54. package/dist/MathPlugin.native-DE_ii-LA.mjs +182 -0
  55. package/dist/MathPlugin.native-DE_ii-LA.mjs.map +1 -0
  56. package/dist/MediaManagerPlugin.native-DaYFDzNM.mjs +533 -0
  57. package/dist/MediaManagerPlugin.native-DaYFDzNM.mjs.map +1 -0
  58. package/dist/MergeTagPlugin.native-CrxyThyn.mjs +178 -0
  59. package/dist/MergeTagPlugin.native-CrxyThyn.mjs.map +1 -0
  60. package/dist/PageBreakPlugin.native-DDjcDyRW.mjs +172 -0
  61. package/dist/PageBreakPlugin.native-DDjcDyRW.mjs.map +1 -0
  62. package/dist/PreviewPlugin.native-DBvfpmIv.mjs +322 -0
  63. package/dist/PreviewPlugin.native-DBvfpmIv.mjs.map +1 -0
  64. package/dist/PrintPlugin.native-BUpm52VJ.mjs +311 -0
  65. package/dist/PrintPlugin.native-BUpm52VJ.mjs.map +1 -0
  66. package/dist/SpecialCharactersPlugin.native-x7a2SWXc.mjs +731 -0
  67. package/dist/SpecialCharactersPlugin.native-x7a2SWXc.mjs.map +1 -0
  68. package/dist/SpellCheckPlugin.native-B7yTh0iE.mjs +465 -0
  69. package/dist/SpellCheckPlugin.native-B7yTh0iE.mjs.map +1 -0
  70. package/dist/StrikethroughPlugin.native-ChaZLaXw.mjs +43 -0
  71. package/dist/StrikethroughPlugin.native-ChaZLaXw.mjs.map +1 -0
  72. package/dist/TablePlugin.native-EEWXn1-s.mjs +491 -0
  73. package/dist/TablePlugin.native-EEWXn1-s.mjs.map +1 -0
  74. package/dist/TemplatePlugin.native-BlSn1c9h.mjs +564 -0
  75. package/dist/TemplatePlugin.native-BlSn1c9h.mjs.map +1 -0
  76. package/dist/TextAlignmentPlugin.native-CQIs1m7R.mjs +97 -0
  77. package/dist/TextAlignmentPlugin.native-CQIs1m7R.mjs.map +1 -0
  78. package/dist/TextColorPlugin.native-D6SmTglm.mjs +432 -0
  79. package/dist/TextColorPlugin.native-D6SmTglm.mjs.map +1 -0
  80. package/dist/UnderlinePlugin.native-QpIcK4L2.mjs +35 -0
  81. package/dist/UnderlinePlugin.native-QpIcK4L2.mjs.map +1 -0
  82. package/dist/documentManager-irzj9n3V.mjs +37627 -0
  83. package/dist/documentManager-irzj9n3V.mjs.map +1 -0
  84. package/dist/editorContainerHelpers-C7kdWnS0.mjs +27 -0
  85. package/dist/editorContainerHelpers-C7kdWnS0.mjs.map +1 -0
  86. package/dist/editora.min.js +14 -12
  87. package/dist/editora.min.js.map +1 -1
  88. package/dist/editora.umd.js +14 -12
  89. package/dist/editora.umd.js.map +1 -1
  90. package/dist/index-BF5RBhL9.js +4 -0
  91. package/dist/index-BF5RBhL9.js.map +1 -0
  92. package/dist/{index-BS4zT-KN.mjs → index-BPsf460l.mjs} +286 -162
  93. package/dist/index-BPsf460l.mjs.map +1 -0
  94. package/dist/index.cjs.js +3 -3
  95. package/dist/index.cjs.js.map +1 -1
  96. package/dist/index.es-CuicffkQ.mjs +6665 -0
  97. package/dist/index.es-CuicffkQ.mjs.map +1 -0
  98. package/dist/index.esm.js +117 -112
  99. package/dist/index.esm.js.map +1 -1
  100. package/dist/plugin-loader.js +55 -0
  101. package/dist/plugin-loader.js.map +1 -0
  102. package/dist/purify.es-CKpwg8Tk.mjs +471 -0
  103. package/dist/purify.es-CKpwg8Tk.mjs.map +1 -0
  104. package/dist/webcomponent-core.js +1243 -0
  105. package/dist/webcomponent-core.js.map +1 -0
  106. package/dist/webcomponent-core.min.css +1 -0
  107. package/dist/webcomponent-core.min.js +597 -0
  108. package/dist/webcomponent-core.min.js.map +1 -0
  109. package/dist/webcomponent.cjs.js +1 -1
  110. package/dist/webcomponent.esm.js +3 -3
  111. package/dist/webcomponent.js +1286 -0
  112. package/dist/webcomponent.js.map +1 -0
  113. package/dist/webcomponent.min.css +1 -0
  114. package/dist/webcomponent.min.js +337 -334
  115. package/dist/webcomponent.min.js.map +1 -1
  116. package/package.json +16 -4
  117. package/dist/index-BK2lHfHK.js +0 -2
  118. package/dist/index-BK2lHfHK.js.map +0 -1
  119. package/dist/index-BS4zT-KN.mjs.map +0 -1
  120. package/dist/webcomponent.umd.js +0 -4073
  121. package/dist/webcomponent.umd.js.map +0 -1
@@ -0,0 +1,97 @@
1
+ const c = ["P", "DIV", "H1", "H2", "H3", "H4", "H5", "H6", "LI", "BLOCKQUOTE", "PRE", "TD", "TH"];
2
+ function d(i) {
3
+ let t = i;
4
+ if (t.nodeType === Node.ELEMENT_NODE) {
5
+ const e = t;
6
+ if (c.includes(e.tagName))
7
+ return e;
8
+ }
9
+ for (; t; ) {
10
+ if (t.nodeType === Node.ELEMENT_NODE) {
11
+ const e = t;
12
+ if (c.includes(e.tagName))
13
+ return e;
14
+ if (e.hasAttribute("contenteditable"))
15
+ return null;
16
+ }
17
+ t = t.parentNode;
18
+ }
19
+ return null;
20
+ }
21
+ function f(i) {
22
+ const t = [], e = d(i.startContainer), l = d(i.endContainer);
23
+ if (!e && !l) return t;
24
+ if (i.collapsed)
25
+ return e && t.push(e), t;
26
+ if (e === l)
27
+ e && t.push(e);
28
+ else {
29
+ let s = e;
30
+ const o = /* @__PURE__ */ new Set();
31
+ for (e && (t.push(e), o.add(e)); s && s !== l && !o.has(l); ) {
32
+ let n = s.nextElementSibling;
33
+ for (; n; ) {
34
+ if (c.includes(n.tagName)) {
35
+ s = n, o.has(s) || (t.push(s), o.add(s));
36
+ break;
37
+ }
38
+ n = n.nextElementSibling;
39
+ }
40
+ if (!n) break;
41
+ }
42
+ l && !t.includes(l) && t.push(l);
43
+ }
44
+ return t;
45
+ }
46
+ const g = (i) => {
47
+ var o;
48
+ if (!i || !["left", "center", "right", "justify"].includes(i)) return !1;
49
+ const e = window.getSelection();
50
+ if (!e || e.rangeCount === 0) return !1;
51
+ const l = e.getRangeAt(0).cloneRange(), s = f(l);
52
+ if (s.length > 0) {
53
+ s.forEach((a) => {
54
+ a && (a.style.textAlign = i);
55
+ }), e.removeAllRanges(), e.addRange(l);
56
+ const n = (o = l.commonAncestorContainer.parentElement) == null ? void 0 : o.closest('[contenteditable="true"]');
57
+ n && n.dispatchEvent(new Event("input", { bubbles: !0 }));
58
+ } else
59
+ try {
60
+ const n = document.createElement("div");
61
+ n.style.textAlign = i;
62
+ const a = l.extractContents();
63
+ n.appendChild(a), l.insertNode(n);
64
+ const r = document.createRange();
65
+ r.selectNodeContents(n), e.removeAllRanges(), e.addRange(r);
66
+ const u = n.closest('[contenteditable="true"]');
67
+ u && u.dispatchEvent(new Event("input", { bubbles: !0 }));
68
+ } catch (n) {
69
+ return console.error("Failed to wrap content for alignment:", n), !1;
70
+ }
71
+ return !0;
72
+ }, m = () => ({
73
+ name: "textAlignment",
74
+ toolbar: [
75
+ {
76
+ label: "Text Alignment",
77
+ command: "setTextAlignment",
78
+ type: "inline-menu",
79
+ options: [
80
+ { label: "Left", value: "left" },
81
+ { label: "Center", value: "center" },
82
+ { label: "Right", value: "right" },
83
+ { label: "Justify", value: "justify" }
84
+ ],
85
+ icon: '<svg width="24" height="24" focusable="false"><path d="M5 5h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 1 1 0-2Zm0 4h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 1 1 0-2Zm0 8h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 0 1 0-2Zm0-4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 0 1 0-2Z" fill-rule="evenodd"></path></svg>'
86
+ }
87
+ ],
88
+ commands: {
89
+ setTextAlignment: g
90
+ },
91
+ keymap: {}
92
+ });
93
+ export {
94
+ m as TextAlignmentPlugin,
95
+ g as setTextAlignmentCommand
96
+ };
97
+ //# sourceMappingURL=TextAlignmentPlugin.native-CQIs1m7R.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextAlignmentPlugin.native-CQIs1m7R.mjs","sources":["../../plugins/text-alignment/src/TextAlignmentPlugin.native.ts"],"sourcesContent":["import { Plugin } from '@editora/core';\n\n/**\n * Text Alignment Plugin - Native Implementation\n *\n * Allows users to set text alignment (left, center, right, justify)\n * for selected text by applying CSS styles to block-level elements\n * or wrapping inline content in a div with alignment\n * \n * Uses modern CSS manipulation instead of deprecated execCommand for:\n * - More reliable behavior across browsers\n * - Better control over styling\n * - Consistent results\n */\n\n// Block-level elements that can have text-align applied\nconst BLOCK_LEVEL_TAGS = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'LI', 'BLOCKQUOTE', 'PRE', 'TD', 'TH'];\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Helper function to find the containing block-level element\n */\nfunction findContainingBlock(node: Node): HTMLElement | null {\n let current: Node | null = node;\n\n // If the node itself is a block element, return it\n if (current.nodeType === Node.ELEMENT_NODE) {\n const element = current as HTMLElement;\n if (BLOCK_LEVEL_TAGS.includes(element.tagName)) {\n return element;\n }\n }\n\n // Traverse up the DOM tree to find the nearest block element\n while (current) {\n if (current.nodeType === Node.ELEMENT_NODE) {\n const element = current as HTMLElement;\n if (BLOCK_LEVEL_TAGS.includes(element.tagName)) {\n return element;\n }\n // Stop at contenteditable boundary\n if (element.hasAttribute('contenteditable')) {\n return null;\n }\n }\n current = current.parentNode;\n }\n\n return null;\n}\n\n/**\n * Helper function to find all block elements that intersect with the range\n */\nfunction getBlocksInRange(range: Range): HTMLElement[] {\n const blocks: HTMLElement[] = [];\n const startBlock = findContainingBlock(range.startContainer);\n const endBlock = findContainingBlock(range.endContainer);\n\n // If no block elements found, return empty array\n if (!startBlock && !endBlock) return blocks;\n\n // If range is collapsed (just cursor), return the block containing the cursor\n if (range.collapsed) {\n if (startBlock) blocks.push(startBlock);\n return blocks;\n }\n\n // For actual selections, find all blocks between start and end\n if (startBlock === endBlock) {\n // Selection is within a single block\n if (startBlock) blocks.push(startBlock);\n } else {\n // Selection spans multiple blocks - find all blocks in between\n let current: HTMLElement | null = startBlock;\n const visited = new Set<HTMLElement>();\n \n if (startBlock) {\n blocks.push(startBlock);\n visited.add(startBlock);\n }\n \n while (current && current !== endBlock && !visited.has(endBlock as HTMLElement)) {\n let nextSibling = current.nextElementSibling as HTMLElement | null;\n \n // Find the next block-level element\n while (nextSibling) {\n if (BLOCK_LEVEL_TAGS.includes(nextSibling.tagName)) {\n current = nextSibling;\n if (!visited.has(current)) {\n blocks.push(current);\n visited.add(current);\n }\n break;\n }\n nextSibling = nextSibling.nextElementSibling as HTMLElement | null;\n }\n \n // If no more siblings, break\n if (!nextSibling) break;\n }\n \n // Add the end block if it's different and not already included\n if (endBlock && !blocks.includes(endBlock)) {\n blocks.push(endBlock);\n }\n }\n\n return blocks;\n}\n\n/**\n * Set text alignment command\n * Applies CSS text-align styles to block-level elements\n * If no block element found, wraps selection in a div with alignment\n */\nexport const setTextAlignmentCommand = (alignment?: string) => {\n if (!alignment) return false;\n\n const validAlignments = ['left', 'center', 'right', 'justify'];\n if (!validAlignments.includes(alignment)) return false;\n\n // Get current selection\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return false;\n\n const range = selection.getRangeAt(0).cloneRange();\n const blocks = getBlocksInRange(range);\n\n // If we found block elements, apply alignment to them\n if (blocks.length > 0) {\n blocks.forEach(block => {\n if (block) {\n block.style.textAlign = alignment;\n }\n });\n \n // Restore the selection\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Trigger input event to update editor state\n const contentElement = range.commonAncestorContainer.parentElement?.closest('[contenteditable=\"true\"]');\n if (contentElement) {\n contentElement.dispatchEvent(new Event('input', { bubbles: true }));\n }\n } else {\n // No block elements found - wrap selection in a div with alignment\n try {\n const div = document.createElement('div');\n div.style.textAlign = alignment;\n \n // Extract the selected content\n const contents = range.extractContents();\n div.appendChild(contents);\n \n // Insert the div at the range position\n range.insertNode(div);\n \n // Update the range to select the content inside the div\n const newRange = document.createRange();\n newRange.selectNodeContents(div);\n selection.removeAllRanges();\n selection.addRange(newRange);\n \n // Trigger input event\n const contentElement = div.closest('[contenteditable=\"true\"]');\n if (contentElement) {\n contentElement.dispatchEvent(new Event('input', { bubbles: true }));\n }\n } catch (error) {\n console.error('Failed to wrap content for alignment:', error);\n return false;\n }\n }\n\n return true;\n};\n\n// ============================================================================\n// Plugin Definition\n// ============================================================================\n\nexport const TextAlignmentPlugin = (): Plugin => ({\n name: 'textAlignment',\n\n toolbar: [\n {\n label: 'Text Alignment',\n command: 'setTextAlignment',\n type: 'inline-menu',\n options: [\n { label: 'Left', value: 'left' },\n { label: 'Center', value: 'center' },\n { label: 'Right', value: 'right' },\n { label: 'Justify', value: 'justify' }\n ],\n icon: '<svg width=\"24\" height=\"24\" focusable=\"false\"><path d=\"M5 5h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 1 1 0-2Zm0 4h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 1 1 0-2Zm0 8h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 0 1 0-2Zm0-4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 0 1 0-2Z\" fill-rule=\"evenodd\"></path></svg>'\n }\n ],\n\n commands: {\n setTextAlignment: setTextAlignmentCommand\n },\n\n keymap: {}\n});\n"],"names":["BLOCK_LEVEL_TAGS","findContainingBlock","node","current","element","getBlocksInRange","range","blocks","startBlock","endBlock","visited","nextSibling","setTextAlignmentCommand","alignment","_a","selection","block","contentElement","div","contents","newRange","error","TextAlignmentPlugin"],"mappings":"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"}
@@ -0,0 +1,432 @@
1
+ let r = null, f = null, g = null, i = "#000000";
2
+ const y = [
3
+ "#000000",
4
+ "#ffffff",
5
+ "#808080",
6
+ "#ff0000",
7
+ "#00ff00",
8
+ "#0000ff",
9
+ "#ffff00",
10
+ "#ff00ff",
11
+ "#00ffff",
12
+ "#ffa500",
13
+ "#800080",
14
+ "#ffc0cb"
15
+ ];
16
+ function u(e) {
17
+ try {
18
+ if (g) {
19
+ const s = window.getSelection();
20
+ s && (s.removeAllRanges(), s.addRange(g));
21
+ }
22
+ const o = window.getSelection();
23
+ if (!o || o.rangeCount === 0 || o.isCollapsed)
24
+ return !1;
25
+ const t = o.getRangeAt(0), l = t.startContainer.nodeType === Node.TEXT_NODE ? t.startContainer.parentElement : t.startContainer, c = t.endContainer.nodeType === Node.TEXT_NODE ? t.endContainer.parentElement : t.endContainer;
26
+ let n = null, a = l;
27
+ for (; a && a !== document.body; ) {
28
+ if (a.classList.contains("rte-text-color")) {
29
+ const s = document.createRange();
30
+ if (s.selectNodeContents(a), s.compareBoundaryPoints(Range.START_TO_START, t) <= 0 && s.compareBoundaryPoints(Range.END_TO_END, t) >= 0) {
31
+ n = a;
32
+ break;
33
+ }
34
+ }
35
+ a = a.parentElement;
36
+ }
37
+ if (n)
38
+ return n.style.color = e, !0;
39
+ const p = document.createElement("span");
40
+ p.style.color = e, p.className = "rte-text-color";
41
+ const h = t.extractContents();
42
+ return p.appendChild(h), t.insertNode(p), t.setStartAfter(p), t.collapse(!0), o.removeAllRanges(), o.addRange(t), !0;
43
+ } catch (o) {
44
+ return console.error("Failed to set text color:", o), !1;
45
+ }
46
+ }
47
+ function v() {
48
+ const e = window.getSelection();
49
+ if (!e || e.rangeCount === 0) return "#000000";
50
+ let t = e.getRangeAt(0).startContainer;
51
+ for (; t && t !== document.body; ) {
52
+ if (t.nodeType === Node.ELEMENT_NODE) {
53
+ const l = t, c = l.style.color || window.getComputedStyle(l).color;
54
+ if (c && c !== "rgb(0, 0, 0)")
55
+ return w(c);
56
+ }
57
+ t = t.parentNode;
58
+ }
59
+ return "#000000";
60
+ }
61
+ function w(e) {
62
+ if (e.startsWith("#")) return e;
63
+ const o = e.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
64
+ if (!o) return "#000000";
65
+ const t = parseInt(o[1]), l = parseInt(o[2]), c = parseInt(o[3]);
66
+ return "#" + [t, l, c].map((n) => {
67
+ const a = n.toString(16);
68
+ return a.length === 1 ? "0" + a : a;
69
+ }).join("");
70
+ }
71
+ function C(e) {
72
+ const o = window.getSelection();
73
+ o && o.rangeCount > 0 && (g = o.getRangeAt(0).cloneRange()), i = v(), r = document.createElement("div"), r.className = "rte-inline-color-picker", r.addEventListener("click", (l) => l.stopPropagation()), r.innerHTML = `
74
+ <div class="rte-color-picker-header">
75
+ <span class="rte-color-picker-title">Text Color</span>
76
+ <button class="rte-color-picker-close" aria-label="Close">×</button>
77
+ </div>
78
+
79
+ <div class="rte-color-picker-body">
80
+ <!-- Current Color Preview -->
81
+ <div class="rte-color-preview-section">
82
+ <div class="rte-color-preview-box" style="background-color: ${i}; ${i === "#ffffff" ? "border: 1px solid #ccc;" : ""}"></div>
83
+ <span class="rte-color-preview-label">${i.toUpperCase()}</span>
84
+ </div>
85
+
86
+ <!-- Preset Colors -->
87
+ <div class="rte-color-section">
88
+ <label class="rte-color-section-label">Colors</label>
89
+ <div class="rte-color-palette">
90
+ ${y.map((l) => `
91
+ <button
92
+ class="rte-color-swatch ${i === l ? "selected" : ""}"
93
+ style="background-color: ${l}; ${l === "#ffffff" ? "border: 1px solid #ccc;" : ""}"
94
+ data-color="${l}"
95
+ title="${l.toUpperCase()}"
96
+ aria-label="${l.toUpperCase()}"
97
+ ></button>
98
+ `).join("")}
99
+ </div>
100
+ </div>
101
+
102
+ <!-- Custom Color -->
103
+ <div class="rte-color-section">
104
+ <label class="rte-color-section-label">Custom</label>
105
+ <div class="rte-custom-color-inputs">
106
+ <input
107
+ type="color"
108
+ value="${i}"
109
+ class="rte-color-input-native"
110
+ aria-label="Color picker"
111
+ />
112
+ <input
113
+ type="text"
114
+ value="${i}"
115
+ placeholder="#000000"
116
+ pattern="^#[0-9A-Fa-f]{6}$"
117
+ class="rte-color-input-text"
118
+ aria-label="Hex color input"
119
+ />
120
+ </div>
121
+ </div>
122
+ </div>
123
+ `;
124
+ const t = e.getBoundingClientRect();
125
+ r.style.position = "absolute", r.style.top = `${t.bottom + window.scrollY + 4}px`, r.style.left = `${t.left + window.scrollX}px`, r.style.zIndex = "10000", document.body.appendChild(r), f = e, k();
126
+ }
127
+ function k() {
128
+ if (!r) return;
129
+ const e = r.querySelector(".rte-color-picker-close");
130
+ e == null || e.addEventListener("click", () => d()), r.querySelectorAll(".rte-color-swatch").forEach((c) => {
131
+ c.addEventListener("click", () => {
132
+ const n = c.getAttribute("data-color");
133
+ n && (i = n, u(n), d());
134
+ });
135
+ });
136
+ const t = r.querySelector(".rte-color-input-native");
137
+ t == null || t.addEventListener("change", (c) => {
138
+ const n = c.target.value;
139
+ i = n, u(n), d();
140
+ });
141
+ const l = r.querySelector(".rte-color-input-text");
142
+ l == null || l.addEventListener("change", (c) => {
143
+ const n = c.target.value;
144
+ /^#[0-9A-Fa-f]{6}$/.test(n) && (i = n, u(n), d());
145
+ }), t == null || t.addEventListener("input", (c) => {
146
+ const n = c.target.value;
147
+ i = n, x(n), b(n), T(n);
148
+ }), l == null || l.addEventListener("input", (c) => {
149
+ const n = c.target.value;
150
+ /^#[0-9A-Fa-f]{6}$/.test(n) && (i = n, x(n), b(n), E(n));
151
+ });
152
+ }
153
+ function x(e) {
154
+ if (!r) return;
155
+ const o = r.querySelector(".rte-color-preview-box"), t = r.querySelector(".rte-color-preview-label");
156
+ o && (o.style.backgroundColor = e, o.style.border = e === "#ffffff" ? "1px solid #ccc" : "none"), t && (t.textContent = e.toUpperCase());
157
+ }
158
+ function b(e) {
159
+ if (!r) return;
160
+ r.querySelectorAll(".rte-color-swatch").forEach((t) => {
161
+ t.getAttribute("data-color") === e ? t.classList.add("selected") : t.classList.remove("selected");
162
+ });
163
+ }
164
+ function E(e) {
165
+ if (!r) return;
166
+ const o = r.querySelector(".rte-color-input-native");
167
+ o && (o.value = e);
168
+ }
169
+ function T(e) {
170
+ if (!r) return;
171
+ const o = r.querySelector(".rte-color-input-text");
172
+ o && (o.value = e);
173
+ }
174
+ function d() {
175
+ r && (r.remove(), r = null), f = null, g = null;
176
+ }
177
+ function S() {
178
+ if (r)
179
+ return d(), !0;
180
+ const e = document.querySelector('[data-command="openTextColorPicker"]');
181
+ return e ? (C(e), !0) : !1;
182
+ }
183
+ function m() {
184
+ if (!window.__textColorPluginInitialized && (window.__textColorPluginInitialized = !0, document.addEventListener("click", (e) => {
185
+ if (r && f) {
186
+ const o = e.target;
187
+ !r.contains(o) && !f.contains(o) && d();
188
+ }
189
+ }), !document.getElementById("text-color-plugin-styles"))) {
190
+ const e = document.createElement("style");
191
+ e.id = "text-color-plugin-styles", e.textContent = `
192
+ .rte-inline-color-picker {
193
+ background: white;
194
+ border-radius: 8px;
195
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
196
+ width: 220px;
197
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
198
+ }
199
+
200
+ .rte-color-picker-header {
201
+ padding: 12px 16px;
202
+ border-bottom: 1px solid #eee;
203
+ display: flex;
204
+ justify-content: space-between;
205
+ align-items: center;
206
+ }
207
+
208
+ .rte-color-picker-title {
209
+ font-size: 14px;
210
+ font-weight: 600;
211
+ color: #333;
212
+ }
213
+
214
+ .rte-color-picker-close {
215
+ background: none;
216
+ border: none;
217
+ font-size: 24px;
218
+ cursor: pointer;
219
+ color: #999;
220
+ padding: 0;
221
+ width: 24px;
222
+ height: 24px;
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: center;
226
+ line-height: 1;
227
+ }
228
+
229
+ .rte-color-picker-close:hover {
230
+ color: #333;
231
+ }
232
+
233
+ .rte-color-picker-body {
234
+ padding: 8px;
235
+ }
236
+
237
+ .rte-color-preview-section {
238
+ display: flex;
239
+ align-items: center;
240
+ gap: 12px;
241
+ margin-bottom: 8px;
242
+ padding: 6px;
243
+ background-color: #f8f9fa;
244
+ border-radius: 6px;
245
+ border: 1px solid #e0e0e0;
246
+ }
247
+
248
+ .rte-color-preview-box {
249
+ width: 24px;
250
+ height: 24px;
251
+ border-radius: 4px;
252
+ flex-shrink: 0;
253
+ }
254
+
255
+ .rte-color-preview-label {
256
+ font-size: 13px;
257
+ font-weight: 500;
258
+ color: #666;
259
+ font-family: monospace;
260
+ }
261
+
262
+ .rte-color-section {
263
+ margin-bottom: 16px;
264
+ }
265
+
266
+ .rte-color-section:last-child {
267
+ margin-bottom: 0;
268
+ }
269
+
270
+ .rte-color-section-label {
271
+ display: block;
272
+ font-size: 12px;
273
+ font-weight: 600;
274
+ color: #666;
275
+ margin-bottom: 8px;
276
+ text-transform: uppercase;
277
+ letter-spacing: 0.5px;
278
+ }
279
+
280
+ .rte-color-palette {
281
+ display: grid;
282
+ grid-template-columns: repeat(7, 1fr);
283
+ gap: 6px;
284
+ max-width: 180px;
285
+ }
286
+
287
+ .rte-color-swatch {
288
+ width: 100%;
289
+ aspect-ratio: 1;
290
+ border: 1px solid #e0e0e0;
291
+ border-radius: 3px;
292
+ cursor: pointer;
293
+ transition: all 0.15s ease;
294
+ padding: 0;
295
+ min-height: 20px;
296
+ }
297
+
298
+ .rte-color-swatch:hover {
299
+ transform: scale(1.05);
300
+ border-color: #ccc;
301
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
302
+ }
303
+
304
+ .rte-color-swatch.selected {
305
+ border-color: #1976d2;
306
+ box-shadow: 0 0 0 1px rgba(25, 118, 210, 0.3);
307
+ }
308
+
309
+ .rte-custom-color-inputs {
310
+ display: flex;
311
+ gap: 8px;
312
+ }
313
+
314
+ .rte-color-input-native {
315
+ width: 50px;
316
+ height: 26px;
317
+ border: 1px solid #ddd;
318
+ border-radius: 4px;
319
+ cursor: pointer;
320
+ padding: 2px;
321
+ }
322
+
323
+ .rte-color-input-text {
324
+ flex: 1;
325
+ height: 26px;
326
+ width: 50px;
327
+ border: 1px solid #ddd;
328
+ border-radius: 4px;
329
+ padding: 0 12px;
330
+ font-size: 13px;
331
+ font-family: monospace;
332
+ }
333
+
334
+ .rte-color-input-text:focus {
335
+ outline: none;
336
+ border-color: #1976d2;
337
+ }
338
+
339
+ .rte-color-picker-footer {
340
+ padding: 12px 16px;
341
+ border-top: 1px solid #eee;
342
+ display: flex;
343
+ gap: 8px;
344
+ justify-content: flex-end;
345
+ }
346
+
347
+ .rte-btn-primary,
348
+ .rte-btn-secondary {
349
+ padding: 6px 16px;
350
+ border-radius: 4px;
351
+ font-size: 13px;
352
+ font-weight: 500;
353
+ cursor: pointer;
354
+ transition: all 0.2s;
355
+ border: none;
356
+ }
357
+
358
+ .rte-btn-primary {
359
+ background-color: #1976d2;
360
+ color: white;
361
+ }
362
+
363
+ .rte-btn-primary:hover {
364
+ background-color: #1565c0;
365
+ }
366
+
367
+ .rte-btn-secondary {
368
+ background-color: #f5f5f5;
369
+ color: #333;
370
+ border: 1px solid #ddd;
371
+ }
372
+
373
+ .rte-btn-secondary:hover {
374
+ background-color: #eeeeee;
375
+ }
376
+ `, document.head.appendChild(e);
377
+ }
378
+ }
379
+ document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", m) : setTimeout(m, 100);
380
+ const R = () => ({
381
+ name: "textColor",
382
+ marks: {
383
+ textColor: {
384
+ attrs: {
385
+ color: { default: "#000000" }
386
+ },
387
+ parseDOM: [
388
+ {
389
+ tag: "span[style*=color]",
390
+ getAttrs: (e) => {
391
+ const t = (e.getAttribute("style") || "").match(/color:\s*([^;]+)/);
392
+ return t ? { color: t[1] } : null;
393
+ }
394
+ },
395
+ {
396
+ tag: "font[color]",
397
+ getAttrs: (e) => {
398
+ const o = e.getAttribute("color");
399
+ return o ? { color: o } : null;
400
+ }
401
+ }
402
+ ],
403
+ toDOM: (e) => {
404
+ var o;
405
+ return [
406
+ "span",
407
+ {
408
+ style: `color: ${((o = e.attrs) == null ? void 0 : o.color) || "#000000"}`,
409
+ class: "rte-text-color"
410
+ },
411
+ 0
412
+ ];
413
+ }
414
+ }
415
+ },
416
+ toolbar: [
417
+ {
418
+ label: "Text Color",
419
+ command: "openTextColorPicker",
420
+ icon: '<svg width="24" height="24" focusable="false"><g fill-rule="evenodd"><path class="tox-icon-text-color__color" d="M3 18h18v3H3z" fill="currentColor"></path><path d="M8.7 16h-.8a.5.5 0 0 1-.5-.6l2.7-9c.1-.3.3-.4.5-.4h2.8c.2 0 .4.1.5.4l2.7 9a.5.5 0 0 1-.5.6h-.8a.5.5 0 0 1-.4-.4l-.7-2.2c0-.3-.3-.4-.5-.4h-3.4c-.2 0-.4.1-.5.4l-.7 2.2c0 .3-.2.4-.4.4Zm2.6-7.6-.6 2a.5.5 0 0 0 .5.6h1.6a.5.5 0 0 0 .5-.6l-.6-2c0-.3-.3-.4-.5-.4h-.4c-.2 0-.4.1-.5.4Z"></path></g></svg>'
421
+ }
422
+ ],
423
+ commands: {
424
+ openTextColorPicker: () => S(),
425
+ setTextColor: (e) => e ? u(e) : !1
426
+ },
427
+ keymap: {}
428
+ });
429
+ export {
430
+ R as TextColorPlugin
431
+ };
432
+ //# sourceMappingURL=TextColorPlugin.native-D6SmTglm.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextColorPlugin.native-D6SmTglm.mjs","sources":["../../plugins/text-color/src/TextColorPlugin.native.ts"],"sourcesContent":["import type { Plugin } from '@editora/core';\n\n/**\n * TextColorPlugin - Native Implementation\n *\n * Allows users to set text color with:\n * - Inline color picker with preset color swatches\n * - Custom color picker (native browser input)\n * - Hex color input\n * - Color preview\n * - Applies foreground color to selected text\n * \n * Features match React ColorPickerDialog component\n */\n\n// ============================================================================\n// Module-Level State\n// ============================================================================\nlet colorPickerElement: HTMLDivElement | null = null;\nlet currentButton: HTMLElement | null = null;\nlet savedRange: Range | null = null;\nlet selectedColor: string = '#000000';\n\n// ============================================================================\n// Preset Colors (matching React version) - Reduced set for smaller picker\n// ============================================================================\nconst PRESET_COLORS = [\n '#000000', '#ffffff', '#808080', '#ff0000', '#00ff00', '#0000ff',\n '#ffff00', '#ff00ff', '#00ffff', '#ffa500', '#800080', '#ffc0cb'\n];\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Apply text color to selection\n */\nfunction applyTextColor(color: string): boolean {\n try {\n // Restore saved selection if available\n if (savedRange) {\n const selection = window.getSelection();\n if (selection) {\n selection.removeAllRanges();\n selection.addRange(savedRange);\n }\n }\n\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {\n return false;\n }\n\n const range = selection.getRangeAt(0);\n\n // Check if the selection is entirely within existing color spans\n const startElement = range.startContainer.nodeType === Node.TEXT_NODE ? range.startContainer.parentElement : range.startContainer as Element;\n const endElement = range.endContainer.nodeType === Node.TEXT_NODE ? range.endContainer.parentElement : range.endContainer as Element;\n\n // Find the outermost color span that contains the entire selection\n let targetSpan: Element | null = null;\n let currentElement: Element | null = startElement;\n\n while (currentElement && currentElement !== document.body) {\n if (currentElement.classList.contains('rte-text-color')) {\n // Check if this span contains the entire selection\n const spanRange = document.createRange();\n spanRange.selectNodeContents(currentElement);\n \n // Check if the selection range is within this span's range\n if (spanRange.compareBoundaryPoints(Range.START_TO_START, range) <= 0 &&\n spanRange.compareBoundaryPoints(Range.END_TO_END, range) >= 0) {\n targetSpan = currentElement;\n break;\n }\n }\n currentElement = currentElement.parentElement;\n }\n\n // If we found a target span that contains the entire selection, just update its color\n if (targetSpan) {\n targetSpan.style.color = color;\n return true;\n }\n\n // No existing span contains the entire selection, create a new one\n const span = document.createElement('span');\n span.style.color = color;\n span.className = 'rte-text-color';\n\n const contents = range.extractContents();\n span.appendChild(contents);\n range.insertNode(span);\n\n // Move cursor after the colored text\n range.setStartAfter(span);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n\n return true;\n } catch (error) {\n console.error('Failed to set text color:', error);\n return false;\n }\n}\nfunction getCurrentTextColor(): string {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return '#000000';\n\n const range = selection.getRangeAt(0);\n let node: Node | null = range.startContainer;\n\n // Traverse up to find a span with color style\n while (node && node !== document.body) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as HTMLElement;\n const color = element.style.color || window.getComputedStyle(element).color;\n if (color && color !== 'rgb(0, 0, 0)') {\n // Convert rgb to hex\n return rgbToHex(color);\n }\n }\n node = node.parentNode;\n }\n\n return '#000000';\n}\n\n/**\n * Convert RGB color to hex\n */\nfunction rgbToHex(rgb: string): string {\n if (rgb.startsWith('#')) return rgb;\n \n const match = rgb.match(/^rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)$/);\n if (!match) return '#000000';\n\n const r = parseInt(match[1]);\n const g = parseInt(match[2]);\n const b = parseInt(match[3]);\n\n return '#' + [r, g, b].map(x => {\n const hex = x.toString(16);\n return hex.length === 1 ? '0' + hex : hex;\n }).join('');\n}\n\n/**\n * Create the inline color picker\n */\nfunction createColorPicker(button: HTMLElement): void {\n // Save current selection\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n savedRange = selection.getRangeAt(0).cloneRange();\n }\n\n // Get current color\n selectedColor = getCurrentTextColor();\n\n // Create picker element\n colorPickerElement = document.createElement('div');\n colorPickerElement.className = 'rte-inline-color-picker';\n colorPickerElement.addEventListener('click', (e) => e.stopPropagation());\n\n // Build picker content\n colorPickerElement.innerHTML = `\n <div class=\"rte-color-picker-header\">\n <span class=\"rte-color-picker-title\">Text Color</span>\n <button class=\"rte-color-picker-close\" aria-label=\"Close\">×</button>\n </div>\n \n <div class=\"rte-color-picker-body\">\n <!-- Current Color Preview -->\n <div class=\"rte-color-preview-section\">\n <div class=\"rte-color-preview-box\" style=\"background-color: ${selectedColor}; ${selectedColor === '#ffffff' ? 'border: 1px solid #ccc;' : ''}\"></div>\n <span class=\"rte-color-preview-label\">${selectedColor.toUpperCase()}</span>\n </div>\n\n <!-- Preset Colors -->\n <div class=\"rte-color-section\">\n <label class=\"rte-color-section-label\">Colors</label>\n <div class=\"rte-color-palette\">\n ${PRESET_COLORS.map(color => `\n <button\n class=\"rte-color-swatch ${selectedColor === color ? 'selected' : ''}\"\n style=\"background-color: ${color}; ${color === '#ffffff' ? 'border: 1px solid #ccc;' : ''}\"\n data-color=\"${color}\"\n title=\"${color.toUpperCase()}\"\n aria-label=\"${color.toUpperCase()}\"\n ></button>\n `).join('')}\n </div>\n </div>\n\n <!-- Custom Color -->\n <div class=\"rte-color-section\">\n <label class=\"rte-color-section-label\">Custom</label>\n <div class=\"rte-custom-color-inputs\">\n <input\n type=\"color\"\n value=\"${selectedColor}\"\n class=\"rte-color-input-native\"\n aria-label=\"Color picker\"\n />\n <input\n type=\"text\"\n value=\"${selectedColor}\"\n placeholder=\"#000000\"\n pattern=\"^#[0-9A-Fa-f]{6}$\"\n class=\"rte-color-input-text\"\n aria-label=\"Hex color input\"\n />\n </div>\n </div>\n </div>\n `;\n\n // Position picker below button\n const buttonRect = button.getBoundingClientRect();\n colorPickerElement.style.position = 'absolute';\n colorPickerElement.style.top = `${buttonRect.bottom + window.scrollY + 4}px`;\n colorPickerElement.style.left = `${buttonRect.left + window.scrollX}px`;\n colorPickerElement.style.zIndex = '10000';\n\n document.body.appendChild(colorPickerElement);\n currentButton = button;\n\n // Attach event listeners\n attachColorPickerListeners();\n}\n\n/**\n * Attach event listeners to color picker elements\n */\nfunction attachColorPickerListeners(): void {\n if (!colorPickerElement) return;\n\n // Close button\n const closeBtn = colorPickerElement.querySelector('.rte-color-picker-close');\n closeBtn?.addEventListener('click', () => closeColorPicker());\n\n // Color swatches - single click applies immediately\n const swatches = colorPickerElement.querySelectorAll('.rte-color-swatch');\n swatches.forEach(swatch => {\n swatch.addEventListener('click', () => {\n const color = swatch.getAttribute('data-color');\n if (color) {\n selectedColor = color;\n applyTextColor(color);\n closeColorPicker();\n }\n });\n });\n\n // Native color input - apply on change\n const nativeInput = colorPickerElement.querySelector('.rte-color-input-native') as HTMLInputElement;\n nativeInput?.addEventListener('change', (e) => {\n const color = (e.target as HTMLInputElement).value;\n selectedColor = color;\n applyTextColor(color);\n closeColorPicker();\n });\n\n // Text color input - apply on valid input\n const textInput = colorPickerElement.querySelector('.rte-color-input-text') as HTMLInputElement;\n textInput?.addEventListener('change', (e) => {\n const color = (e.target as HTMLInputElement).value;\n if (/^#[0-9A-Fa-f]{6}$/.test(color)) {\n selectedColor = color;\n applyTextColor(color);\n closeColorPicker();\n }\n });\n\n // Update preview on input (but don't apply yet)\n nativeInput?.addEventListener('input', (e) => {\n const color = (e.target as HTMLInputElement).value;\n selectedColor = color;\n updateColorPreview(color);\n updateSelectedSwatch(color);\n updateTextInput(color);\n });\n\n textInput?.addEventListener('input', (e) => {\n const color = (e.target as HTMLInputElement).value;\n if (/^#[0-9A-Fa-f]{6}$/.test(color)) {\n selectedColor = color;\n updateColorPreview(color);\n updateSelectedSwatch(color);\n updateNativeInput(color);\n }\n });\n}\n\n/**\n * Update color preview\n */\nfunction updateColorPreview(color: string): void {\n if (!colorPickerElement) return;\n \n const previewBox = colorPickerElement.querySelector('.rte-color-preview-box') as HTMLElement;\n const previewLabel = colorPickerElement.querySelector('.rte-color-preview-label') as HTMLElement;\n \n if (previewBox) {\n previewBox.style.backgroundColor = color;\n previewBox.style.border = color === '#ffffff' ? '1px solid #ccc' : 'none';\n }\n if (previewLabel) {\n previewLabel.textContent = color.toUpperCase();\n }\n}\n\n/**\n * Update selected swatch\n */\nfunction updateSelectedSwatch(color: string): void {\n if (!colorPickerElement) return;\n \n const swatches = colorPickerElement.querySelectorAll('.rte-color-swatch');\n swatches.forEach(swatch => {\n if (swatch.getAttribute('data-color') === color) {\n swatch.classList.add('selected');\n } else {\n swatch.classList.remove('selected');\n }\n });\n}\n\n/**\n * Update custom inputs\n */\nfunction updateCustomInputs(color: string): void {\n updateNativeInput(color);\n updateTextInput(color);\n}\n\nfunction updateNativeInput(color: string): void {\n if (!colorPickerElement) return;\n const nativeInput = colorPickerElement.querySelector('.rte-color-input-native') as HTMLInputElement;\n if (nativeInput) nativeInput.value = color;\n}\n\nfunction updateTextInput(color: string): void {\n if (!colorPickerElement) return;\n const textInput = colorPickerElement.querySelector('.rte-color-input-text') as HTMLInputElement;\n if (textInput) textInput.value = color;\n}\n\n/**\n * Close color picker\n */\nfunction closeColorPicker(): void {\n if (colorPickerElement) {\n colorPickerElement.remove();\n colorPickerElement = null;\n }\n currentButton = null;\n savedRange = null;\n}\n\n/**\n * Open color picker\n */\nfunction openTextColorPicker(): boolean {\n // Close any existing picker\n if (colorPickerElement) {\n closeColorPicker();\n return true;\n }\n\n // Find the text color button\n const button = document.querySelector('[data-command=\"openTextColorPicker\"]') as HTMLElement;\n if (!button) return false;\n\n createColorPicker(button);\n return true;\n}\n\n/**\n * Initialize plugin (called once when editor loads)\n */\nfunction initTextColorPlugin(): void {\n if ((window as any).__textColorPluginInitialized) {\n return;\n }\n\n (window as any).__textColorPluginInitialized = true;\n\n // Close picker when clicking outside\n document.addEventListener('click', (e) => {\n if (colorPickerElement && currentButton) {\n const target = e.target as Node;\n if (!colorPickerElement.contains(target) && !currentButton.contains(target)) {\n closeColorPicker();\n }\n }\n });\n\n // Add CSS styles\n if (!document.getElementById('text-color-plugin-styles')) {\n const styleElement = document.createElement('style');\n styleElement.id = 'text-color-plugin-styles';\n styleElement.textContent = `\n .rte-inline-color-picker {\n background: white;\n border-radius: 8px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n width: 220px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n }\n\n .rte-color-picker-header {\n padding: 12px 16px;\n border-bottom: 1px solid #eee;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .rte-color-picker-title {\n font-size: 14px;\n font-weight: 600;\n color: #333;\n }\n\n .rte-color-picker-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n }\n\n .rte-color-picker-close:hover {\n color: #333;\n }\n\n .rte-color-picker-body {\n padding: 8px;\n }\n\n .rte-color-preview-section {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 8px;\n padding: 6px;\n background-color: #f8f9fa;\n border-radius: 6px;\n border: 1px solid #e0e0e0;\n }\n\n .rte-color-preview-box {\n width: 24px;\n height: 24px;\n border-radius: 4px;\n flex-shrink: 0;\n }\n\n .rte-color-preview-label {\n font-size: 13px;\n font-weight: 500;\n color: #666;\n font-family: monospace;\n }\n\n .rte-color-section {\n margin-bottom: 16px;\n }\n\n .rte-color-section:last-child {\n margin-bottom: 0;\n }\n\n .rte-color-section-label {\n display: block;\n font-size: 12px;\n font-weight: 600;\n color: #666;\n margin-bottom: 8px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .rte-color-palette {\n display: grid;\n grid-template-columns: repeat(7, 1fr);\n gap: 6px;\n max-width: 180px;\n }\n\n .rte-color-swatch {\n width: 100%;\n aspect-ratio: 1;\n border: 1px solid #e0e0e0;\n border-radius: 3px;\n cursor: pointer;\n transition: all 0.15s ease;\n padding: 0;\n min-height: 20px;\n }\n\n .rte-color-swatch:hover {\n transform: scale(1.05);\n border-color: #ccc;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);\n }\n\n .rte-color-swatch.selected {\n border-color: #1976d2;\n box-shadow: 0 0 0 1px rgba(25, 118, 210, 0.3);\n }\n\n .rte-custom-color-inputs {\n display: flex;\n gap: 8px;\n }\n\n .rte-color-input-native {\n width: 50px;\n height: 26px;\n border: 1px solid #ddd;\n border-radius: 4px;\n cursor: pointer;\n padding: 2px;\n }\n\n .rte-color-input-text {\n flex: 1;\n height: 26px;\n width: 50px;\n border: 1px solid #ddd;\n border-radius: 4px;\n padding: 0 12px;\n font-size: 13px;\n font-family: monospace;\n }\n\n .rte-color-input-text:focus {\n outline: none;\n border-color: #1976d2;\n }\n\n .rte-color-picker-footer {\n padding: 12px 16px;\n border-top: 1px solid #eee;\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n }\n\n .rte-btn-primary,\n .rte-btn-secondary {\n padding: 6px 16px;\n border-radius: 4px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n border: none;\n }\n\n .rte-btn-primary {\n background-color: #1976d2;\n color: white;\n }\n\n .rte-btn-primary:hover {\n background-color: #1565c0;\n }\n\n .rte-btn-secondary {\n background-color: #f5f5f5;\n color: #333;\n border: 1px solid #ddd;\n }\n\n .rte-btn-secondary:hover {\n background-color: #eeeeee;\n }\n `;\n document.head.appendChild(styleElement);\n }\n}\n\n// Auto-initialize when DOM is ready\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initTextColorPlugin);\n} else {\n setTimeout(initTextColorPlugin, 100);\n}\n\n// ============================================================================\n// Plugin Definition\n// ============================================================================\n\nexport const TextColorPlugin = (): Plugin => ({\n name: 'textColor',\n\n marks: {\n textColor: {\n attrs: {\n color: { default: '#000000' }\n },\n parseDOM: [\n {\n tag: 'span[style*=color]',\n getAttrs: (node: HTMLElement) => {\n const style = node.getAttribute('style') || '';\n const colorMatch = style.match(/color:\\s*([^;]+)/);\n if (colorMatch) {\n return { color: colorMatch[1] };\n }\n return null;\n }\n },\n {\n tag: 'font[color]',\n getAttrs: (node: HTMLElement) => {\n const color = node.getAttribute('color');\n return color ? { color } : null;\n }\n }\n ],\n toDOM: (mark) => [\n 'span',\n {\n style: `color: ${mark.attrs?.color || '#000000'}`,\n class: 'rte-text-color'\n },\n 0\n ]\n }\n },\n\n toolbar: [\n {\n label: 'Text Color',\n command: 'openTextColorPicker',\n icon: '<svg width=\"24\" height=\"24\" focusable=\"false\"><g fill-rule=\"evenodd\"><path class=\"tox-icon-text-color__color\" d=\"M3 18h18v3H3z\" fill=\"currentColor\"></path><path d=\"M8.7 16h-.8a.5.5 0 0 1-.5-.6l2.7-9c.1-.3.3-.4.5-.4h2.8c.2 0 .4.1.5.4l2.7 9a.5.5 0 0 1-.5.6h-.8a.5.5 0 0 1-.4-.4l-.7-2.2c0-.3-.3-.4-.5-.4h-3.4c-.2 0-.4.1-.5.4l-.7 2.2c0 .3-.2.4-.4.4Zm2.6-7.6-.6 2a.5.5 0 0 0 .5.6h1.6a.5.5 0 0 0 .5-.6l-.6-2c0-.3-.3-.4-.5-.4h-.4c-.2 0-.4.1-.5.4Z\"></path></g></svg>'\n }\n ],\n\n commands: {\n openTextColorPicker: () => {\n return openTextColorPicker();\n },\n setTextColor: (color?: string) => {\n if (!color) return false;\n return applyTextColor(color);\n }\n },\n\n keymap: {}\n});\n"],"names":["colorPickerElement","currentButton","savedRange","selectedColor","PRESET_COLORS","applyTextColor","color","selection","range","startElement","endElement","targetSpan","currentElement","spanRange","span","contents","error","getCurrentTextColor","node","element","rgbToHex","rgb","match","r","g","b","x","hex","createColorPicker","button","e","buttonRect","attachColorPickerListeners","closeBtn","closeColorPicker","swatch","nativeInput","textInput","updateColorPreview","updateSelectedSwatch","updateTextInput","updateNativeInput","previewBox","previewLabel","openTextColorPicker","initTextColorPlugin","target","styleElement","TextColorPlugin","colorMatch","mark","_a"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ const e = () => ({
2
+ name: "underline",
3
+ // Schema definition for underline mark
4
+ marks: {
5
+ underline: {
6
+ parseDOM: [{ tag: "u" }],
7
+ toDOM: () => ["u", {}, 0]
8
+ }
9
+ },
10
+ // Toolbar button configuration (matching React version exactly)
11
+ toolbar: [
12
+ {
13
+ label: "Underline",
14
+ command: "toggleUnderline",
15
+ icon: '<svg width="24" height="24" focusable="false"><path d="M16 5c.6 0 1 .4 1 1v7c0 2.8-2.2 5-5 5s-5-2.2-5-5V6c0-.6.4-1 1-1s1 .4 1 1v7c0 1.7 1.3 3 3 3s3-1.3 3-3V6c0-.6.4-1 1-1ZM4 17h16c.6 0 1 .4 1 1s-.4 1-1 1H4a1 1 0 1 1 0-2Z" fill-rule="evenodd"></path></svg>',
16
+ shortcut: "Mod-u"
17
+ }
18
+ ],
19
+ // Native command implementations
20
+ commands: {
21
+ /**
22
+ * Toggle underline formatting on current selection
23
+ */
24
+ toggleUnderline: () => (document.execCommand("underline", !1), !0)
25
+ },
26
+ // Keyboard shortcuts
27
+ keymap: {
28
+ "Mod-u": "toggleUnderline",
29
+ "Mod-U": "toggleUnderline"
30
+ }
31
+ });
32
+ export {
33
+ e as UnderlinePlugin
34
+ };
35
+ //# sourceMappingURL=UnderlinePlugin.native-QpIcK4L2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UnderlinePlugin.native-QpIcK4L2.mjs","sources":["../../plugins/underline/src/UnderlinePlugin.native.ts"],"sourcesContent":["import { Plugin } from '@editora/core';\n\n/**\n * Underline Plugin - Native Implementation\n * \n * Adds underline formatting support with native command implementation.\n * Matches React version UI/UX exactly.\n */\nexport const UnderlinePlugin = (): Plugin => ({\n name: 'underline',\n \n // Schema definition for underline mark\n marks: {\n underline: {\n parseDOM: [{ tag: 'u' }],\n toDOM: () => ['u', {}, 0]\n }\n },\n \n // Toolbar button configuration (matching React version exactly)\n toolbar: [\n {\n label: 'Underline',\n command: 'toggleUnderline',\n icon: '<svg width=\"24\" height=\"24\" focusable=\"false\"><path d=\"M16 5c.6 0 1 .4 1 1v7c0 2.8-2.2 5-5 5s-5-2.2-5-5V6c0-.6.4-1 1-1s1 .4 1 1v7c0 1.7 1.3 3 3 3s3-1.3 3-3V6c0-.6.4-1 1-1ZM4 17h16c.6 0 1 .4 1 1s-.4 1-1 1H4a1 1 0 1 1 0-2Z\" fill-rule=\"evenodd\"></path></svg>',\n shortcut: 'Mod-u'\n }\n ],\n \n // Native command implementations\n commands: {\n /**\n * Toggle underline formatting on current selection\n */\n toggleUnderline: () => {\n document.execCommand('underline', false);\n return true;\n }\n },\n \n // Keyboard shortcuts\n keymap: {\n 'Mod-u': 'toggleUnderline',\n 'Mod-U': 'toggleUnderline'\n }\n});\n"],"names":["UnderlinePlugin"],"mappings":"AAQO,MAAMA,IAAkB,OAAe;AAAA,EAC5C,MAAM;AAAA;AAAA,EAGN,OAAO;AAAA,IACL,WAAW;AAAA,MACT,UAAU,CAAC,EAAE,KAAK,KAAK;AAAA,MACvB,OAAO,MAAM,CAAC,KAAK,CAAA,GAAI,CAAC;AAAA,IAAA;AAAA,EAC1B;AAAA;AAAA,EAIF,SAAS;AAAA,IACP;AAAA,MACE,OAAO;AAAA,MACP,SAAS;AAAA,MACT,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,EACZ;AAAA;AAAA,EAIF,UAAU;AAAA;AAAA;AAAA;AAAA,IAIR,iBAAiB,OACf,SAAS,YAAY,aAAa,EAAK,GAChC;AAAA,EACT;AAAA;AAAA,EAIF,QAAQ;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,EAAA;AAEb;"}