mardora 1.2.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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/dist/chunk-3OCUX4OO.js +7690 -0
  4. package/dist/chunk-3OCUX4OO.js.map +1 -0
  5. package/dist/chunk-3ZOCCFDL.cjs +74 -0
  6. package/dist/chunk-3ZOCCFDL.cjs.map +1 -0
  7. package/dist/chunk-7JOEPNEV.cjs +7740 -0
  8. package/dist/chunk-7JOEPNEV.cjs.map +1 -0
  9. package/dist/chunk-BIKZQZ6W.js +33 -0
  10. package/dist/chunk-BIKZQZ6W.js.map +1 -0
  11. package/dist/chunk-EQJESPP2.js +234 -0
  12. package/dist/chunk-EQJESPP2.js.map +1 -0
  13. package/dist/chunk-G4SE26YY.js +70 -0
  14. package/dist/chunk-G4SE26YY.js.map +1 -0
  15. package/dist/chunk-KNDWF2DP.cjs +35 -0
  16. package/dist/chunk-KNDWF2DP.cjs.map +1 -0
  17. package/dist/chunk-MLBEBFHB.cjs +2971 -0
  18. package/dist/chunk-MLBEBFHB.cjs.map +1 -0
  19. package/dist/chunk-P7JFCYU3.js +905 -0
  20. package/dist/chunk-P7JFCYU3.js.map +1 -0
  21. package/dist/chunk-SWFUKJDO.cjs +243 -0
  22. package/dist/chunk-SWFUKJDO.cjs.map +1 -0
  23. package/dist/chunk-WFVCG4LD.cjs +926 -0
  24. package/dist/chunk-WFVCG4LD.cjs.map +1 -0
  25. package/dist/chunk-XL6WFGJT.js +2901 -0
  26. package/dist/chunk-XL6WFGJT.js.map +1 -0
  27. package/dist/editor/index.cjs +277 -0
  28. package/dist/editor/index.cjs.map +1 -0
  29. package/dist/editor/index.d.cts +186 -0
  30. package/dist/editor/index.d.ts +186 -0
  31. package/dist/editor/index.js +4 -0
  32. package/dist/editor/index.js.map +1 -0
  33. package/dist/index.cjs +405 -0
  34. package/dist/index.cjs.map +1 -0
  35. package/dist/index.d.cts +13 -0
  36. package/dist/index.d.ts +13 -0
  37. package/dist/index.js +8 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/lib/index.cjs +12 -0
  40. package/dist/lib/index.cjs.map +1 -0
  41. package/dist/lib/index.d.cts +16 -0
  42. package/dist/lib/index.d.ts +16 -0
  43. package/dist/lib/index.js +3 -0
  44. package/dist/lib/index.js.map +1 -0
  45. package/dist/mardora-DCwjomil.d.cts +640 -0
  46. package/dist/mardora-DCwjomil.d.ts +640 -0
  47. package/dist/plugins/index.cjs +104 -0
  48. package/dist/plugins/index.cjs.map +1 -0
  49. package/dist/plugins/index.d.cts +740 -0
  50. package/dist/plugins/index.d.ts +740 -0
  51. package/dist/plugins/index.js +7 -0
  52. package/dist/plugins/index.js.map +1 -0
  53. package/dist/preview/index.cjs +38 -0
  54. package/dist/preview/index.cjs.map +1 -0
  55. package/dist/preview/index.d.cts +101 -0
  56. package/dist/preview/index.d.ts +101 -0
  57. package/dist/preview/index.js +5 -0
  58. package/dist/preview/index.js.map +1 -0
  59. package/dist/types-NBsaxl4d.d.cts +71 -0
  60. package/dist/types-Pw2SWWAR.d.ts +71 -0
  61. package/package.json +92 -0
  62. package/src/editor/attachments/extension.ts +181 -0
  63. package/src/editor/attachments/format.ts +63 -0
  64. package/src/editor/attachments/index.ts +3 -0
  65. package/src/editor/attachments/types.ts +37 -0
  66. package/src/editor/heading-fold/config.ts +25 -0
  67. package/src/editor/heading-fold/extension.ts +268 -0
  68. package/src/editor/heading-fold/extract.ts +88 -0
  69. package/src/editor/heading-fold/index.ts +5 -0
  70. package/src/editor/heading-fold/theme.ts +85 -0
  71. package/src/editor/heading-fold/types.ts +24 -0
  72. package/src/editor/i18n.ts +13 -0
  73. package/src/editor/icons/index.ts +367 -0
  74. package/src/editor/index.ts +16 -0
  75. package/src/editor/mardora.ts +257 -0
  76. package/src/editor/media-lightbox-theme.ts +146 -0
  77. package/src/editor/media-lightbox.ts +125 -0
  78. package/src/editor/plugin.ts +294 -0
  79. package/src/editor/selection-toolbar/activation.ts +123 -0
  80. package/src/editor/selection-toolbar/commands.ts +279 -0
  81. package/src/editor/selection-toolbar/extension.ts +564 -0
  82. package/src/editor/selection-toolbar/i18n.ts +164 -0
  83. package/src/editor/selection-toolbar/index.ts +7 -0
  84. package/src/editor/selection-toolbar/menu.ts +252 -0
  85. package/src/editor/selection-toolbar/position.ts +43 -0
  86. package/src/editor/selection-toolbar/theme.ts +195 -0
  87. package/src/editor/selection-toolbar/types.ts +155 -0
  88. package/src/editor/slash/default-commands.ts +190 -0
  89. package/src/editor/slash/extension.ts +319 -0
  90. package/src/editor/slash/index.ts +7 -0
  91. package/src/editor/slash/insertions.ts +26 -0
  92. package/src/editor/slash/menu.ts +123 -0
  93. package/src/editor/slash/position.ts +61 -0
  94. package/src/editor/slash/query.ts +33 -0
  95. package/src/editor/slash/theme.ts +113 -0
  96. package/src/editor/slash/types.ts +40 -0
  97. package/src/editor/table-of-contents/extension.ts +202 -0
  98. package/src/editor/table-of-contents/extract.ts +53 -0
  99. package/src/editor/table-of-contents/index.ts +7 -0
  100. package/src/editor/table-of-contents/panel.ts +83 -0
  101. package/src/editor/table-of-contents/slug.ts +50 -0
  102. package/src/editor/table-of-contents/storage.ts +35 -0
  103. package/src/editor/table-of-contents/theme.ts +153 -0
  104. package/src/editor/table-of-contents/types.ts +44 -0
  105. package/src/editor/theme.ts +72 -0
  106. package/src/editor/utils.ts +176 -0
  107. package/src/editor/view-plugin.ts +189 -0
  108. package/src/index.ts +5 -0
  109. package/src/lib/index.ts +2 -0
  110. package/src/lib/input-handler.ts +47 -0
  111. package/src/plugins/code-plugin.theme.ts +545 -0
  112. package/src/plugins/code-plugin.ts +1892 -0
  113. package/src/plugins/emoji-plugin.ts +140 -0
  114. package/src/plugins/heading-plugin.ts +194 -0
  115. package/src/plugins/hr-plugin.ts +102 -0
  116. package/src/plugins/html-plugin.ts +353 -0
  117. package/src/plugins/image-plugin.ts +806 -0
  118. package/src/plugins/index.ts +71 -0
  119. package/src/plugins/inline-plugin.ts +311 -0
  120. package/src/plugins/link-plugin.ts +509 -0
  121. package/src/plugins/list-plugin.ts +492 -0
  122. package/src/plugins/math-plugin.ts +526 -0
  123. package/src/plugins/mermaid-plugin.ts +513 -0
  124. package/src/plugins/paragraph-plugin.ts +38 -0
  125. package/src/plugins/quote-plugin.ts +733 -0
  126. package/src/plugins/table-controls-theme.ts +126 -0
  127. package/src/plugins/table-controls.ts +423 -0
  128. package/src/plugins/table-model.ts +661 -0
  129. package/src/plugins/table-plugin.ts +2111 -0
  130. package/src/preview/context.ts +45 -0
  131. package/src/preview/css-generator.ts +64 -0
  132. package/src/preview/default-renderers.ts +29 -0
  133. package/src/preview/index.ts +29 -0
  134. package/src/preview/preview.ts +41 -0
  135. package/src/preview/renderer.ts +184 -0
  136. package/src/preview/syntax-theme.ts +112 -0
  137. package/src/preview/toc.ts +23 -0
  138. package/src/preview/types.ts +89 -0
@@ -0,0 +1,164 @@
1
+ import type { MardoraLocale } from "../i18n";
2
+ import type { SelectionToolbarBlockType } from "./types";
3
+
4
+ export type SelectionToolbarMessages = {
5
+ buttons: {
6
+ blockType: string;
7
+ bold: string;
8
+ italic: string;
9
+ strike: string;
10
+ underline: string;
11
+ code: string;
12
+ highlight: string;
13
+ color: string;
14
+ link: string;
15
+ orderedList: string;
16
+ unorderedList: string;
17
+ taskList: string;
18
+ };
19
+ panels: {
20
+ textColor: string;
21
+ highlightColor: string;
22
+ };
23
+ link: {
24
+ title: string;
25
+ url: string;
26
+ copy: string;
27
+ copied: string;
28
+ open: string;
29
+ remove: string;
30
+ invalid: string;
31
+ };
32
+ blockTypes: Record<SelectionToolbarBlockType, string>;
33
+ colors: {
34
+ defaultText: string;
35
+ gray: string;
36
+ red: string;
37
+ orange: string;
38
+ yellow: string;
39
+ green: string;
40
+ blue: string;
41
+ purple: string;
42
+ defaultHighlight: string;
43
+ yellowHighlight: string;
44
+ greenHighlight: string;
45
+ blueHighlight: string;
46
+ pinkHighlight: string;
47
+ purpleHighlight: string;
48
+ };
49
+ };
50
+
51
+ const selectionToolbarMessages: Record<MardoraLocale, SelectionToolbarMessages> = {
52
+ "zh-CN": {
53
+ buttons: {
54
+ blockType: "块类型",
55
+ bold: "加粗",
56
+ italic: "斜体",
57
+ strike: "删除线",
58
+ underline: "下划线",
59
+ code: "行内代码",
60
+ highlight: "高亮",
61
+ color: "文字颜色",
62
+ link: "链接",
63
+ orderedList: "有序列表",
64
+ unorderedList: "无序列表",
65
+ taskList: "任务列表",
66
+ },
67
+ panels: {
68
+ textColor: "文字颜色",
69
+ highlightColor: "高亮颜色",
70
+ },
71
+ link: {
72
+ title: "链接标题",
73
+ url: "链接 URL",
74
+ copy: "复制链接",
75
+ copied: "已复制",
76
+ open: "打开链接",
77
+ remove: "移除链接",
78
+ invalid: "请输入有效链接",
79
+ },
80
+ blockTypes: {
81
+ text: "文本",
82
+ "heading-1": "标题 1",
83
+ "heading-2": "标题 2",
84
+ "heading-3": "标题 3",
85
+ "heading-4": "标题 4",
86
+ "heading-5": "标题 5",
87
+ "heading-6": "标题 6",
88
+ },
89
+ colors: {
90
+ defaultText: "默认文字颜色",
91
+ gray: "灰色",
92
+ red: "红色",
93
+ orange: "橙色",
94
+ yellow: "黄色",
95
+ green: "绿色",
96
+ blue: "蓝色",
97
+ purple: "紫色",
98
+ defaultHighlight: "默认高亮",
99
+ yellowHighlight: "黄色高亮",
100
+ greenHighlight: "绿色高亮",
101
+ blueHighlight: "蓝色高亮",
102
+ pinkHighlight: "粉色高亮",
103
+ purpleHighlight: "紫色高亮",
104
+ },
105
+ },
106
+ "en-US": {
107
+ buttons: {
108
+ blockType: "Block type",
109
+ bold: "Bold",
110
+ italic: "Italic",
111
+ strike: "Strikethrough",
112
+ underline: "Underline",
113
+ code: "Inline code",
114
+ highlight: "Highlight",
115
+ color: "Text color",
116
+ link: "Link",
117
+ orderedList: "Numbered list",
118
+ unorderedList: "Bulleted list",
119
+ taskList: "To-do list",
120
+ },
121
+ panels: {
122
+ textColor: "Text color",
123
+ highlightColor: "Highlight color",
124
+ },
125
+ link: {
126
+ title: "Link title",
127
+ url: "Link URL",
128
+ copy: "Copy link",
129
+ copied: "Copied",
130
+ open: "Open link",
131
+ remove: "Remove link",
132
+ invalid: "Enter a valid link",
133
+ },
134
+ blockTypes: {
135
+ text: "Text",
136
+ "heading-1": "Heading 1",
137
+ "heading-2": "Heading 2",
138
+ "heading-3": "Heading 3",
139
+ "heading-4": "Heading 4",
140
+ "heading-5": "Heading 5",
141
+ "heading-6": "Heading 6",
142
+ },
143
+ colors: {
144
+ defaultText: "Default text color",
145
+ gray: "Gray",
146
+ red: "Red",
147
+ orange: "Orange",
148
+ yellow: "Yellow",
149
+ green: "Green",
150
+ blue: "Blue",
151
+ purple: "Purple",
152
+ defaultHighlight: "Default highlight",
153
+ yellowHighlight: "Yellow highlight",
154
+ greenHighlight: "Green highlight",
155
+ blueHighlight: "Blue highlight",
156
+ pinkHighlight: "Pink highlight",
157
+ purpleHighlight: "Purple highlight",
158
+ },
159
+ },
160
+ };
161
+
162
+ export function getSelectionToolbarMessages(locale: MardoraLocale): SelectionToolbarMessages {
163
+ return selectionToolbarMessages[locale];
164
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./types";
2
+ export * from "./position";
3
+ export * from "./commands";
4
+ export * from "./menu";
5
+ export * from "./theme";
6
+ export * from "./i18n";
7
+ export * from "./extension";
@@ -0,0 +1,252 @@
1
+ import { createMardoraIcon } from "../icons";
2
+ import type {
3
+ SelectionToolbarBlockType,
4
+ SelectionToolbarButton,
5
+ SelectionToolbarMenuCallbacks,
6
+ SelectionToolbarMenuState,
7
+ SelectionToolbarPaletteItem,
8
+ } from "./types";
9
+
10
+ function iconButton(button: SelectionToolbarButton, callbacks: SelectionToolbarMenuCallbacks): HTMLButtonElement {
11
+ const element = document.createElement("button");
12
+ element.type = "button";
13
+ element.className = button.active
14
+ ? "cm-mardora-selection-toolbar-button cm-mardora-selection-toolbar-button-active"
15
+ : "cm-mardora-selection-toolbar-button";
16
+ element.setAttribute("aria-label", button.label);
17
+ element.setAttribute("aria-pressed", String(!!button.active));
18
+ element.dataset.mardoraSelectionAction = button.id;
19
+ element.addEventListener("mousedown", (event) => {
20
+ event.preventDefault();
21
+ callbacks.onAction(button.id);
22
+ });
23
+
24
+ if (button.text) {
25
+ element.classList.add("cm-mardora-selection-toolbar-block-button");
26
+ const label = document.createElement("span");
27
+ label.className = "cm-mardora-selection-toolbar-button-text";
28
+ label.textContent = button.text;
29
+ element.appendChild(label);
30
+ } else if (button.id === "block-type") {
31
+ element.classList.add("cm-mardora-selection-toolbar-block-button");
32
+ const icon = createMardoraIcon(button.icon);
33
+ if (icon) element.appendChild(icon);
34
+ } else {
35
+ const icon = createMardoraIcon(button.icon);
36
+ if (icon) {
37
+ element.appendChild(icon);
38
+ } else {
39
+ element.textContent = button.label;
40
+ }
41
+ }
42
+ return element;
43
+ }
44
+
45
+ function blockTypeIcon(type: SelectionToolbarBlockType): HTMLElement {
46
+ const icon = document.createElement("span");
47
+ icon.className = "cm-mardora-selection-toolbar-block-menu-icon";
48
+ if (type === "text") {
49
+ const svg = createMardoraIcon("text-align-start");
50
+ if (svg) icon.appendChild(svg);
51
+ } else {
52
+ icon.textContent = `H${type.slice("heading-".length)}`;
53
+ }
54
+ return icon;
55
+ }
56
+
57
+ function appendBlockTypePanel(
58
+ root: HTMLElement,
59
+ state: SelectionToolbarMenuState,
60
+ callbacks: SelectionToolbarMenuCallbacks
61
+ ): void {
62
+ const list = document.createElement("div");
63
+ list.className = "cm-mardora-selection-toolbar-block-menu";
64
+ list.setAttribute("role", "menu");
65
+
66
+ for (const item of state.blockTypes) {
67
+ const button = document.createElement("button");
68
+ button.type = "button";
69
+ button.className =
70
+ item.type === state.blockType
71
+ ? "cm-mardora-selection-toolbar-block-item cm-mardora-selection-toolbar-block-item-active"
72
+ : "cm-mardora-selection-toolbar-block-item";
73
+ button.setAttribute("aria-label", item.label);
74
+ button.setAttribute("aria-pressed", String(item.type === state.blockType));
75
+ button.setAttribute("role", "menuitemradio");
76
+ button.dataset.mardoraBlockType = item.type;
77
+ button.addEventListener("mousedown", (event) => {
78
+ event.preventDefault();
79
+ callbacks.onBlockType(item.type);
80
+ });
81
+
82
+ const label = document.createElement("span");
83
+ label.className = "cm-mardora-selection-toolbar-block-menu-label";
84
+ label.textContent = item.label;
85
+
86
+ button.append(blockTypeIcon(item.type), label);
87
+ list.appendChild(button);
88
+ }
89
+
90
+ root.appendChild(list);
91
+ }
92
+
93
+ function divider(): HTMLSpanElement {
94
+ const element = document.createElement("span");
95
+ element.className = "cm-mardora-selection-toolbar-divider";
96
+ element.setAttribute("aria-hidden", "true");
97
+ return element;
98
+ }
99
+
100
+ function appendToolbarButtons(
101
+ root: HTMLElement,
102
+ buttons: SelectionToolbarButton[],
103
+ callbacks: SelectionToolbarMenuCallbacks
104
+ ): void {
105
+ const groups: SelectionToolbarButton[][] = [buttons.slice(0, 1), buttons.slice(1, 8), buttons.slice(8, 9), buttons.slice(9)];
106
+
107
+ groups.forEach((group, index) => {
108
+ if (group.length === 0) return;
109
+ if (index > 0) root.appendChild(divider());
110
+ for (const button of group) root.appendChild(iconButton(button, callbacks));
111
+ });
112
+ }
113
+
114
+ function paletteButton(
115
+ item: SelectionToolbarPaletteItem,
116
+ className: string,
117
+ callback: (value: string | null) => void
118
+ ): HTMLButtonElement {
119
+ const element = document.createElement("button");
120
+ element.type = "button";
121
+ element.className = className;
122
+ element.setAttribute("aria-label", item.label);
123
+ element.title = item.label;
124
+ element.dataset.mardoraSwatch = item.id;
125
+ if (item.value) element.style.setProperty("--mardora-swatch-color", item.value);
126
+ element.addEventListener("mousedown", (event) => {
127
+ event.preventDefault();
128
+ callback(item.value);
129
+ });
130
+ return element;
131
+ }
132
+
133
+ function appendPalette(
134
+ root: HTMLElement,
135
+ title: string,
136
+ items: SelectionToolbarPaletteItem[],
137
+ callback: (value: string | null) => void
138
+ ): void {
139
+ const group = document.createElement("div");
140
+ group.className = "cm-mardora-selection-toolbar-palette-group";
141
+
142
+ const label = document.createElement("div");
143
+ label.className = "cm-mardora-selection-toolbar-palette-label";
144
+ label.textContent = title;
145
+ group.appendChild(label);
146
+
147
+ const grid = document.createElement("div");
148
+ grid.className = "cm-mardora-selection-toolbar-swatch-grid";
149
+ for (const item of items) {
150
+ grid.appendChild(paletteButton(item, "cm-mardora-selection-toolbar-swatch", callback));
151
+ }
152
+ group.appendChild(grid);
153
+ root.appendChild(group);
154
+ }
155
+
156
+ function appendLinkAction(
157
+ actions: HTMLElement,
158
+ label: string,
159
+ iconName: string,
160
+ callback: () => void,
161
+ danger = false
162
+ ): void {
163
+ const button = document.createElement("button");
164
+ button.type = "button";
165
+ button.className = danger
166
+ ? "cm-mardora-selection-toolbar-link-button cm-mardora-selection-toolbar-link-button-danger"
167
+ : "cm-mardora-selection-toolbar-link-button";
168
+ button.setAttribute("aria-label", label);
169
+ const svg = createMardoraIcon(iconName);
170
+ if (svg) button.appendChild(svg);
171
+ button.addEventListener("mousedown", (event) => {
172
+ event.preventDefault();
173
+ callback();
174
+ });
175
+ actions.appendChild(button);
176
+ }
177
+
178
+ function appendLinkPanel(
179
+ root: HTMLElement,
180
+ state: SelectionToolbarMenuState,
181
+ callbacks: SelectionToolbarMenuCallbacks
182
+ ): void {
183
+ const title = document.createElement("input");
184
+ title.className = "cm-mardora-selection-toolbar-link-input";
185
+ title.setAttribute("aria-label", state.messages.link.title);
186
+ title.value = state.link.title;
187
+ title.addEventListener("input", () => callbacks.onLinkInput("title", title.value));
188
+ title.addEventListener("keydown", (event) => {
189
+ if (event.key === "Enter") callbacks.onLinkSubmit();
190
+ if (event.key === "Escape") callbacks.onCancelPanel();
191
+ });
192
+
193
+ const url = document.createElement("input");
194
+ url.className = "cm-mardora-selection-toolbar-link-input";
195
+ url.setAttribute("aria-label", state.messages.link.url);
196
+ url.value = state.link.url;
197
+ url.addEventListener("input", () => callbacks.onLinkInput("url", url.value));
198
+ url.addEventListener("keydown", (event) => {
199
+ if (event.key === "Enter") callbacks.onLinkSubmit();
200
+ if (event.key === "Escape") callbacks.onCancelPanel();
201
+ });
202
+
203
+ const actions = document.createElement("div");
204
+ actions.className = "cm-mardora-selection-toolbar-link-actions";
205
+
206
+ appendLinkAction(actions, state.link.copied ? state.messages.link.copied : state.messages.link.copy, "copy", callbacks.onLinkCopy);
207
+ appendLinkAction(actions, state.messages.link.open, "external-link", callbacks.onLinkOpen);
208
+ if (state.link.canRemove) appendLinkAction(actions, state.messages.link.remove, "trash-2", callbacks.onLinkRemove, true);
209
+
210
+ root.append(title, url);
211
+ if (state.link.error) {
212
+ const error = document.createElement("div");
213
+ error.className = "cm-mardora-selection-toolbar-error";
214
+ error.textContent = state.link.error;
215
+ root.appendChild(error);
216
+ }
217
+ root.appendChild(actions);
218
+ queueMicrotask(() => {
219
+ title.focus();
220
+ title.select();
221
+ });
222
+ }
223
+
224
+ export function createSelectionToolbarElement(
225
+ state: SelectionToolbarMenuState,
226
+ callbacks: SelectionToolbarMenuCallbacks
227
+ ): HTMLElement {
228
+ const root = document.createElement("div");
229
+ root.className =
230
+ state.panel === "toolbar" || state.panel === "block-type"
231
+ ? "cm-mardora-selection-toolbar"
232
+ : "cm-mardora-selection-toolbar cm-mardora-selection-toolbar-panel";
233
+ root.setAttribute("role", "toolbar");
234
+ root.addEventListener("mousedown", (event) => {
235
+ const target = event.target;
236
+ if (target instanceof HTMLElement && target.closest(".cm-mardora-selection-toolbar-link-input")) return;
237
+ event.preventDefault();
238
+ });
239
+
240
+ if (state.panel === "toolbar" || state.panel === "block-type") {
241
+ appendToolbarButtons(root, state.buttons, callbacks);
242
+ if (state.panel === "block-type") appendBlockTypePanel(root, state, callbacks);
243
+ } else if (state.panel === "link") {
244
+ appendLinkPanel(root, state, callbacks);
245
+ } else if (state.panel === "color") {
246
+ appendPalette(root, state.messages.panels.textColor, state.textColors, callbacks.onColor);
247
+ } else {
248
+ appendPalette(root, state.messages.panels.highlightColor, state.highlightColors, callbacks.onHighlight);
249
+ }
250
+
251
+ return root;
252
+ }
@@ -0,0 +1,43 @@
1
+ import type { SelectionToolbarLayout, SelectionToolbarLayoutInput, SelectionToolbarPlacement } from "./types";
2
+
3
+ const viewportPadding = 8;
4
+ const anchorGap = 8;
5
+
6
+ function clamp(value: number, min: number, max: number): number {
7
+ return Math.min(Math.max(value, min), max);
8
+ }
9
+
10
+ export function computeSelectionToolbarLayout(input: SelectionToolbarLayoutInput): SelectionToolbarLayout {
11
+ const selectionCenter = (input.anchor.left + input.anchor.right) / 2;
12
+ const boundary = input.boundary ?? {
13
+ left: viewportPadding,
14
+ right: input.viewport.width - viewportPadding,
15
+ top: viewportPadding,
16
+ bottom: input.viewport.height - viewportPadding,
17
+ };
18
+ const minLeft = Math.max(viewportPadding, boundary.left);
19
+ const maxLeft = Math.max(minLeft, Math.min(input.viewport.width - input.floating.width - viewportPadding, boundary.right - input.floating.width));
20
+ const left = clamp(Math.round(selectionCenter - input.floating.width / 2), minLeft, maxLeft);
21
+ const topLimit = Math.max(viewportPadding, boundary.top);
22
+ const bottomLimit = Math.min(input.viewport.height - viewportPadding, boundary.bottom);
23
+ const availableAbove = Math.max(1, input.anchor.top - anchorGap - topLimit);
24
+ const availableBelow = Math.max(1, bottomLimit - input.anchor.bottom - anchorGap);
25
+ const placement: SelectionToolbarPlacement =
26
+ availableAbove >= input.floating.height || availableAbove >= availableBelow ? "top" : "bottom";
27
+
28
+ if (placement === "top") {
29
+ return {
30
+ placement,
31
+ left,
32
+ top: Math.max(topLimit, Math.round(input.anchor.top - anchorGap - input.floating.height)),
33
+ maxHeight: availableAbove,
34
+ };
35
+ }
36
+
37
+ return {
38
+ placement,
39
+ left,
40
+ top: Math.round(input.anchor.bottom + anchorGap),
41
+ maxHeight: availableBelow,
42
+ };
43
+ }
@@ -0,0 +1,195 @@
1
+ import { EditorView } from "@codemirror/view";
2
+
3
+ export const selectionToolbarTheme = EditorView.baseTheme({
4
+ ".cm-mardora-selection-toolbar": {
5
+ position: "fixed",
6
+ zIndex: "1001",
7
+ display: "inline-flex",
8
+ alignItems: "center",
9
+ gap: "2px",
10
+ border: "1px solid rgba(24, 24, 27, 0.14)",
11
+ borderRadius: "10px",
12
+ background: "var(--mardora-selection-toolbar-bg, #ffffff)",
13
+ boxShadow: "0 14px 38px rgba(15, 23, 42, 0.16)",
14
+ padding: "4px",
15
+ color: "var(--mardora-selection-toolbar-fg, #18181b)",
16
+ fontFamily: "var(--font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif)",
17
+ userSelect: "none",
18
+ },
19
+ ".cm-mardora-selection-toolbar-panel": {
20
+ display: "flex",
21
+ flexDirection: "column",
22
+ alignItems: "stretch",
23
+ width: "336px",
24
+ gap: "6px",
25
+ padding: "8px",
26
+ },
27
+ ".cm-mardora-selection-toolbar-block-button": {
28
+ minWidth: "44px",
29
+ width: "44px",
30
+ fontWeight: "400",
31
+ },
32
+ ".cm-mardora-selection-toolbar-button-text": {
33
+ fontSize: "13px",
34
+ lineHeight: "1",
35
+ },
36
+ ".cm-mardora-selection-toolbar-button, .cm-mardora-selection-toolbar-link-button": {
37
+ display: "inline-flex",
38
+ alignItems: "center",
39
+ justifyContent: "center",
40
+ width: "30px",
41
+ height: "30px",
42
+ border: "0",
43
+ borderRadius: "7px",
44
+ background: "transparent",
45
+ color: "inherit",
46
+ cursor: "default",
47
+ padding: "0",
48
+ },
49
+ ".cm-mardora-selection-toolbar-button:hover, .cm-mardora-selection-toolbar-button-active": {
50
+ background: "var(--mardora-selection-toolbar-active, #f4f4f5)",
51
+ },
52
+ ".cm-mardora-selection-toolbar-button svg, .cm-mardora-selection-toolbar-link-button svg": {
53
+ width: "16px",
54
+ height: "16px",
55
+ strokeWidth: "2",
56
+ },
57
+ ".cm-mardora-selection-toolbar-divider": {
58
+ width: "1px",
59
+ height: "22px",
60
+ margin: "0 4px",
61
+ background: "var(--mardora-selection-toolbar-border, #e4e4e7)",
62
+ },
63
+ ".cm-mardora-selection-toolbar-block-menu": {
64
+ position: "absolute",
65
+ left: "0",
66
+ zIndex: "1002",
67
+ display: "flex",
68
+ flexDirection: "column",
69
+ gap: "1px",
70
+ boxSizing: "border-box",
71
+ width: "168px",
72
+ maxHeight: "var(--mardora-selection-toolbar-popover-max-height, 240px)",
73
+ overflowY: "auto",
74
+ border: "1px solid rgba(24, 24, 27, 0.14)",
75
+ borderRadius: "8px",
76
+ background: "var(--mardora-selection-toolbar-bg, #ffffff)",
77
+ boxShadow: "0 12px 28px rgba(15, 23, 42, 0.14)",
78
+ padding: "5px",
79
+ },
80
+ '.cm-mardora-selection-toolbar[data-mardora-selection-placement="bottom"] .cm-mardora-selection-toolbar-block-menu': {
81
+ top: "calc(100% + 6px)",
82
+ },
83
+ '.cm-mardora-selection-toolbar[data-mardora-selection-placement="top"] .cm-mardora-selection-toolbar-block-menu': {
84
+ bottom: "calc(100% + 6px)",
85
+ },
86
+ ".cm-mardora-selection-toolbar-block-item": {
87
+ display: "flex",
88
+ alignItems: "center",
89
+ gap: "8px",
90
+ border: "0",
91
+ borderRadius: "6px",
92
+ background: "transparent",
93
+ color: "inherit",
94
+ cursor: "default",
95
+ font: "inherit",
96
+ minHeight: "28px",
97
+ padding: "4px 8px",
98
+ textAlign: "left",
99
+ },
100
+ ".cm-mardora-selection-toolbar-block-item:hover, .cm-mardora-selection-toolbar-block-item-active": {
101
+ background: "var(--mardora-selection-toolbar-active, #f4f4f5)",
102
+ },
103
+ ".cm-mardora-selection-toolbar-block-menu-icon": {
104
+ display: "inline-flex",
105
+ alignItems: "center",
106
+ justifyContent: "center",
107
+ width: "22px",
108
+ color: "inherit",
109
+ fontSize: "12px",
110
+ fontWeight: "400",
111
+ lineHeight: "1",
112
+ },
113
+ ".cm-mardora-selection-toolbar-block-menu-label": {
114
+ fontSize: "13px",
115
+ lineHeight: "1.1",
116
+ },
117
+ ".cm-mardora-selection-toolbar-link-input": {
118
+ boxSizing: "border-box",
119
+ width: "100%",
120
+ border: "0",
121
+ borderRadius: "7px",
122
+ background: "var(--mardora-selection-toolbar-input, #f4f4f5)",
123
+ color: "inherit",
124
+ font: "inherit",
125
+ fontSize: "14px",
126
+ outline: "none",
127
+ padding: "8px 10px",
128
+ },
129
+ ".cm-mardora-selection-toolbar-link-actions": {
130
+ display: "flex",
131
+ alignItems: "center",
132
+ justifyContent: "flex-end",
133
+ gap: "6px",
134
+ },
135
+ ".cm-mardora-selection-toolbar-link-button:hover": {
136
+ background: "var(--mardora-selection-toolbar-active, #f4f4f5)",
137
+ },
138
+ ".cm-mardora-selection-toolbar-link-button-danger": {
139
+ color: "var(--mardora-selection-toolbar-danger, #dc2626)",
140
+ },
141
+ ".cm-mardora-selection-toolbar-link-button-danger:hover": {
142
+ background: "var(--mardora-selection-toolbar-danger-bg, #fee2e2)",
143
+ },
144
+ ".cm-mardora-selection-toolbar-error": {
145
+ color: "var(--mardora-selection-toolbar-danger, #dc2626)",
146
+ fontSize: "12px",
147
+ padding: "0 2px",
148
+ },
149
+ ".cm-mardora-selection-toolbar-palette-label": {
150
+ color: "var(--mardora-selection-toolbar-muted, #71717a)",
151
+ fontSize: "12px",
152
+ padding: "0 2px 4px",
153
+ },
154
+ ".cm-mardora-selection-toolbar-swatch-grid": {
155
+ display: "grid",
156
+ gridTemplateColumns: "repeat(8, 24px)",
157
+ gap: "6px",
158
+ },
159
+ ".cm-mardora-selection-toolbar-swatch": {
160
+ width: "24px",
161
+ height: "24px",
162
+ border: "1px solid var(--mardora-selection-toolbar-border, #e4e4e7)",
163
+ borderRadius: "6px",
164
+ background: "var(--mardora-swatch-color, transparent)",
165
+ boxShadow: "inset 0 0 0 1px rgba(255, 255, 255, 0.72)",
166
+ cursor: "default",
167
+ outline: "none",
168
+ padding: "0",
169
+ transition: "border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease",
170
+ },
171
+ ".cm-mardora-selection-toolbar-swatch:hover": {
172
+ borderColor: "var(--mardora-selection-toolbar-swatch-hover-border, #a1a1aa)",
173
+ boxShadow:
174
+ "0 0 0 3px var(--mardora-selection-toolbar-swatch-hover-ring, rgba(24, 24, 27, 0.08)), inset 0 0 0 1px rgba(255, 255, 255, 0.72)",
175
+ transform: "translateY(-1px)",
176
+ },
177
+ ".cm-mardora-selection-toolbar-swatch:focus-visible": {
178
+ borderColor: "var(--mardora-selection-toolbar-swatch-hover-border, #71717a)",
179
+ boxShadow:
180
+ "0 0 0 3px var(--mardora-selection-toolbar-swatch-hover-ring, rgba(24, 24, 27, 0.12)), inset 0 0 0 1px rgba(255, 255, 255, 0.72)",
181
+ },
182
+ "&dark .cm-mardora-selection-toolbar": {
183
+ "--mardora-selection-toolbar-bg": "#18181b",
184
+ "--mardora-selection-toolbar-fg": "#f4f4f5",
185
+ "--mardora-selection-toolbar-active": "#27272a",
186
+ "--mardora-selection-toolbar-border": "#3f3f46",
187
+ "--mardora-selection-toolbar-input": "#27272a",
188
+ "--mardora-selection-toolbar-muted": "#a1a1aa",
189
+ "--mardora-selection-toolbar-link": "#60a5fa",
190
+ "--mardora-selection-toolbar-danger": "#f87171",
191
+ "--mardora-selection-toolbar-danger-bg": "rgba(248, 113, 113, 0.18)",
192
+ "--mardora-selection-toolbar-swatch-hover-border": "#71717a",
193
+ "--mardora-selection-toolbar-swatch-hover-ring": "rgba(244, 244, 245, 0.12)",
194
+ },
195
+ });