remons-markdown-editor 1.0.0-beta.4 → 1.0.0-beta.6

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 (32) hide show
  1. package/dist/App.d.ts +2 -0
  2. package/dist/MarkdownEditor.d.ts +23 -0
  3. package/dist/components/Dialog/EditDialog.d.ts +21 -0
  4. package/dist/components/Editor/codemirrorExtensions.d.ts +12 -0
  5. package/dist/components/Editor/containerWidget.d.ts +20 -0
  6. package/dist/components/Editor/index.d.ts +16 -0
  7. package/dist/components/Panel/SplitPanels.d.ts +7 -0
  8. package/dist/components/Preview/index.d.ts +10 -0
  9. package/dist/components/Toolbar/buttons.d.ts +34 -0
  10. package/dist/components/Toolbar/config.d.ts +6 -0
  11. package/dist/components/Toolbar/hooks/useToolbar.d.ts +14 -0
  12. package/dist/components/Toolbar/index.d.ts +13 -0
  13. package/dist/components/Toolbar/renderers/TablePicker/TableDropdown.d.ts +5 -0
  14. package/dist/components/Toolbar/renderers/TablePicker/index.d.ts +9 -0
  15. package/dist/components/Toolbar/renderers/ToolbarItemRenderer.d.ts +7 -0
  16. package/dist/hooks/useFullscreen.d.ts +4 -0
  17. package/dist/hooks/useRegisterToolbar.d.ts +51 -0
  18. package/dist/hooks/useScrollSync.d.ts +5 -0
  19. package/dist/index.cjs +1314 -1191
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.css +248 -8
  22. package/dist/index.d.ts +5 -157
  23. package/dist/index.js +1289 -1169
  24. package/dist/index.js.map +1 -1
  25. package/dist/main.d.ts +0 -0
  26. package/dist/registry/componentRegistry.d.ts +34 -0
  27. package/dist/types/index.d.ts +76 -0
  28. package/dist/utils/parseContainer.d.ts +18 -0
  29. package/dist/utils/writeContainer.d.ts +16 -0
  30. package/package.json +7 -7
  31. package/dist/index.css.map +0 -1
  32. package/dist/index.d.cts +0 -157
package/dist/index.cjs CHANGED
@@ -1,1263 +1,1386 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
1
3
  var __create = Object.create;
2
4
  var __defProp = Object.defineProperty;
3
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
7
  var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
9
  var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
18
  };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
- // If the importer is in node compatibility mode or this is not an ESM
21
- // file that has been converted to a CommonJS file using a Babel-
22
- // compatible transform (i.e. "__esModule" has not been set), then set
23
- // "default" to the CommonJS "module.exports" for node compatibility.
24
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
- mod
26
- ));
27
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
-
29
- // src/index.ts
30
- var index_exports = {};
31
- __export(index_exports, {
32
- MarkdownEditor: () => MarkdownEditor_default,
33
- useRegisterToolbar: () => useRegisterToolbar
34
- });
35
- module.exports = __toCommonJS(index_exports);
36
-
37
- // src/MarkdownEditor.tsx
38
- var import_react10 = require("react");
39
- var import_antd6 = require("antd");
40
-
41
- // src/components/Editor/index.tsx
42
- var import_react = require("react");
43
- var import_codemirror = require("codemirror");
44
- var import_state3 = require("@codemirror/state");
45
-
46
- // src/components/Editor/codemirrorExtensions.ts
47
- var import_view2 = require("@codemirror/view");
48
- var import_commands = require("@codemirror/commands");
49
- var import_lang_markdown = require("@codemirror/lang-markdown");
50
- var import_theme_one_dark = require("@codemirror/theme-one-dark");
51
- var import_state2 = require("@codemirror/state");
52
-
53
- // src/components/Editor/containerWidget.ts
54
- var import_view = require("@codemirror/view");
55
- var import_state = require("@codemirror/state");
56
-
57
- // src/utils/parseContainer.ts
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let react = require("react");
25
+ react = __toESM(react, 1);
26
+ let antd = require("antd");
27
+ let codemirror = require("codemirror");
28
+ let _codemirror_state = require("@codemirror/state");
29
+ let _codemirror_view = require("@codemirror/view");
30
+ let _codemirror_commands = require("@codemirror/commands");
31
+ let _codemirror_lang_markdown = require("@codemirror/lang-markdown");
32
+ let _codemirror_theme_one_dark = require("@codemirror/theme-one-dark");
33
+ let react_jsx_runtime = require("react/jsx-runtime");
34
+ let remons_render_markdown = require("remons-render-markdown");
35
+ remons_render_markdown = __toESM(remons_render_markdown, 1);
36
+ require("remons-render-markdown/dist/index.css");
37
+ let _ant_design_icons = require("@ant-design/icons");
38
+ //#region src/utils/parseContainer.ts
39
+ /**
40
+ * 解析容器属性 JSON 字符串。
41
+ * 支持标准 JSON 和宽松格式: { key: 'value', key2: 'value2' }
42
+ */
58
43
  function parseContainerProps(jsonStr) {
59
- var _a, _b, _c;
60
- try {
61
- const props = {};
62
- const trimmed = jsonStr.trim();
63
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
64
- const inner = trimmed.slice(1, -1).trim();
65
- if (!inner) return props;
66
- const matches = inner.matchAll(/(?:["']?)(\w+)(?:["']?)\s*:\s*(?:"([^"]*)"|'([^']*)'|(\d+))/g);
67
- for (const m of matches) {
68
- props[m[1]] = (_c = (_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : m[4]) != null ? _c : "";
69
- }
70
- }
71
- return props;
72
- } catch (e) {
73
- return {};
74
- }
44
+ try {
45
+ const props = {};
46
+ const trimmed = jsonStr.trim();
47
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
48
+ const inner = trimmed.slice(1, -1).trim();
49
+ if (!inner) return props;
50
+ const matches = inner.matchAll(/(?:["']?)(\w+)(?:["']?)\s*:\s*(?:"([^"]*)"|'([^']*)'|(\d+))/g);
51
+ for (const m of matches) props[m[1]] = m[2] ?? m[3] ?? m[4] ?? "";
52
+ }
53
+ return props;
54
+ } catch {
55
+ return {};
56
+ }
75
57
  }
76
58
  function findContainers(docText) {
77
- const containers = [];
78
- const lines = docText.split("\n");
79
- let i = 0;
80
- while (i < lines.length) {
81
- const line = lines[i];
82
- const match = line.match(/^:::(\w+)$/);
83
- if (match) {
84
- const typeName = match[1];
85
- let charPos = 0;
86
- for (let l = 0; l < i; l++) {
87
- charPos += lines[l].length + 1;
88
- }
89
- let props = {};
90
- let propsLineFrom = 0;
91
- let propsLineTo = 0;
92
- let propsLineIndex = i + 1;
93
- if (propsLineIndex < lines.length) {
94
- const propsLine = lines[propsLineIndex].trim();
95
- const propsMatch = propsLine.match(/^\{[\s\S]*\}$/);
96
- if (propsMatch) {
97
- props = parseContainerProps(propsLine);
98
- propsLineFrom = charPos + line.length + 1;
99
- propsLineTo = propsLineFrom + lines[propsLineIndex].length;
100
- }
101
- }
102
- let endPos = 0;
103
- for (let j = i + 1; j < lines.length; j++) {
104
- if (lines[j].trim() === ":::") {
105
- let endCharPos = 0;
106
- for (let l = 0; l < j; l++) {
107
- endCharPos += lines[l].length + 1;
108
- }
109
- endPos = endCharPos;
110
- i = j + 1;
111
- break;
112
- }
113
- }
114
- containers.push({
115
- startPos: charPos,
116
- typeName,
117
- propsLineFrom,
118
- propsLineTo,
119
- props,
120
- endPos
121
- });
122
- }
123
- i++;
124
- }
125
- return containers;
59
+ const containers = [];
60
+ const lines = docText.split("\n");
61
+ let i = 0;
62
+ while (i < lines.length) {
63
+ const line = lines[i];
64
+ const match = line.match(/^:::(\w+)$/);
65
+ if (match) {
66
+ const typeName = match[1];
67
+ let charPos = 0;
68
+ for (let l = 0; l < i; l++) charPos += lines[l].length + 1;
69
+ let props = {};
70
+ let propsLineFrom = 0;
71
+ let propsLineTo = 0;
72
+ let propsLineIndex = i + 1;
73
+ if (propsLineIndex < lines.length) {
74
+ const propsLine = lines[propsLineIndex].trim();
75
+ if (propsLine.match(/^\{[\s\S]*\}$/)) {
76
+ props = parseContainerProps(propsLine);
77
+ propsLineFrom = charPos + line.length + 1;
78
+ propsLineTo = propsLineFrom + lines[propsLineIndex].length;
79
+ }
80
+ }
81
+ let endPos = 0;
82
+ for (let j = i + 1; j < lines.length; j++) if (lines[j].trim() === ":::") {
83
+ let endCharPos = 0;
84
+ for (let l = 0; l < j; l++) endCharPos += lines[l].length + 1;
85
+ endPos = endCharPos;
86
+ i = j + 1;
87
+ break;
88
+ }
89
+ containers.push({
90
+ startPos: charPos,
91
+ typeName,
92
+ propsLineFrom,
93
+ propsLineTo,
94
+ props,
95
+ endPos
96
+ });
97
+ }
98
+ i++;
99
+ }
100
+ return containers;
126
101
  }
127
-
128
- // src/registry/componentRegistry.tsx
102
+ //#endregion
103
+ //#region src/registry/componentRegistry.tsx
104
+ /**
105
+ * 组件注册中心,用于管理自定义组件的 schema。
106
+ * 每个容器组件(如 :::linkCard)在此注册其可编辑字段。
107
+ */
129
108
  var ComponentRegistry = class {
130
- constructor() {
131
- this.schemas = /* @__PURE__ */ new Map();
132
- }
133
- /**
134
- * 注册组件 schema
135
- */
136
- register(name, schema) {
137
- this.schemas.set(name, schema);
138
- }
139
- /**
140
- * 根据组件名获取已注册的 schema
141
- */
142
- get(name) {
143
- return this.schemas.get(name);
144
- }
145
- /**
146
- * 检查组件类型是否已注册
147
- */
148
- has(name) {
149
- return this.schemas.has(name);
150
- }
151
- /**
152
- * 获取所有已注册的组件名
153
- */
154
- getAllNames() {
155
- return Array.from(this.schemas.keys());
156
- }
157
- /**
158
- * 获取所有已注册的 schema
159
- */
160
- getAll() {
161
- return Array.from(this.schemas.values());
162
- }
163
- /**
164
- * 移除组件注册
165
- */
166
- unregister(name) {
167
- this.schemas.delete(name);
168
- }
109
+ schemas = /* @__PURE__ */ new Map();
110
+ /**
111
+ * 注册组件 schema
112
+ */
113
+ register(name, schema) {
114
+ this.schemas.set(name, schema);
115
+ }
116
+ /**
117
+ * 根据组件名获取已注册的 schema
118
+ */
119
+ get(name) {
120
+ return this.schemas.get(name);
121
+ }
122
+ /**
123
+ * 检查组件类型是否已注册
124
+ */
125
+ has(name) {
126
+ return this.schemas.has(name);
127
+ }
128
+ /**
129
+ * 获取所有已注册的组件名
130
+ */
131
+ getAllNames() {
132
+ return Array.from(this.schemas.keys());
133
+ }
134
+ /**
135
+ * 获取所有已注册的 schema
136
+ */
137
+ getAll() {
138
+ return Array.from(this.schemas.values());
139
+ }
140
+ /**
141
+ * 移除组件注册
142
+ */
143
+ unregister(name) {
144
+ this.schemas.delete(name);
145
+ }
169
146
  };
170
147
  var componentRegistry = new ComponentRegistry();
171
- var componentRegistry_default = componentRegistry;
172
-
173
- // src/components/Editor/containerWidget.ts
174
- var containerEditCallback = import_state.Facet.define({
175
- combine: (values) => values[0]
176
- });
177
- var ContainerHeaderWidget = class extends import_view.WidgetType {
178
- constructor(typeName) {
179
- super();
180
- this.typeName = typeName;
181
- }
182
- toDOM() {
183
- var _a, _b;
184
- const container = document.createElement("span");
185
- container.className = "container-widget-header";
186
- const text = document.createElement("span");
187
- text.className = "container-widget-text";
188
- text.textContent = `:::${this.typeName}`;
189
- const button = document.createElement("button");
190
- button.className = "container-edit-btn";
191
- button.title = `\u7F16\u8F91 ${(_b = (_a = componentRegistry_default.get(this.typeName)) == null ? void 0 : _a.label) != null ? _b : this.typeName}`;
192
- button.textContent = "\u270E";
193
- button.__typeName = this.typeName;
194
- container.appendChild(text);
195
- container.appendChild(button);
196
- return container;
197
- }
198
- eq(other) {
199
- return this.typeName === other.typeName;
200
- }
201
- ignoreEvent() {
202
- return false;
203
- }
148
+ //#endregion
149
+ //#region src/components/Editor/containerWidget.ts
150
+ /**
151
+ * 自定义 facet 用于传递编辑回调
152
+ */
153
+ var containerEditCallback = _codemirror_state.Facet.define({ combine: (values) => values[0] });
154
+ /**
155
+ * 只读的容器头行 Widget(显示 :::typeName + 编辑按钮)
156
+ * 使用 Decoration.replace 替换原始文本,使其不可直接编辑
157
+ */
158
+ var ContainerHeaderWidget = class extends _codemirror_view.WidgetType {
159
+ typeName;
160
+ constructor(typeName) {
161
+ super();
162
+ this.typeName = typeName;
163
+ }
164
+ toDOM() {
165
+ const container = document.createElement("span");
166
+ container.className = "container-widget-header";
167
+ const text = document.createElement("span");
168
+ text.className = "container-widget-text";
169
+ text.textContent = `:::${this.typeName}`;
170
+ const button = document.createElement("button");
171
+ button.className = "container-edit-btn";
172
+ button.title = `编辑 ${componentRegistry.get(this.typeName)?.label ?? this.typeName}`;
173
+ button.textContent = "✎";
174
+ button.__typeName = this.typeName;
175
+ container.appendChild(text);
176
+ container.appendChild(button);
177
+ return container;
178
+ }
179
+ eq(other) {
180
+ return this.typeName === other.typeName;
181
+ }
182
+ ignoreEvent() {
183
+ return false;
184
+ }
204
185
  };
205
- var ContainerPropsWidget = class extends import_view.WidgetType {
206
- constructor(propsText) {
207
- super();
208
- this.propsText = propsText;
209
- }
210
- toDOM() {
211
- const span = document.createElement("span");
212
- span.className = "container-widget-props";
213
- span.textContent = this.propsText;
214
- return span;
215
- }
216
- eq(other) {
217
- return this.propsText === other.propsText;
218
- }
219
- ignoreEvent() {
220
- return false;
221
- }
186
+ /**
187
+ * 只读的容器属性行 Widget(显示 { key: value, ... })
188
+ * 使用 Decoration.replace 替换原始文本,使其不可直接编辑
189
+ */
190
+ var ContainerPropsWidget = class extends _codemirror_view.WidgetType {
191
+ propsText;
192
+ constructor(propsText) {
193
+ super();
194
+ this.propsText = propsText;
195
+ }
196
+ toDOM() {
197
+ const span = document.createElement("span");
198
+ span.className = "container-widget-props";
199
+ span.textContent = this.propsText;
200
+ return span;
201
+ }
202
+ eq(other) {
203
+ return this.propsText === other.propsText;
204
+ }
205
+ ignoreEvent() {
206
+ return false;
207
+ }
222
208
  };
223
- var ContainerCloseWidget = class extends import_view.WidgetType {
224
- toDOM() {
225
- const span = document.createElement("span");
226
- span.className = "container-widget-text";
227
- span.textContent = ":::";
228
- return span;
229
- }
230
- eq() {
231
- return true;
232
- }
233
- ignoreEvent() {
234
- return false;
235
- }
209
+ /**
210
+ * 只读的容器结束行 Widget(显示 :::)
211
+ * 使用 Decoration.replace 替换原始文本,使其不可直接编辑
212
+ */
213
+ var ContainerCloseWidget = class extends _codemirror_view.WidgetType {
214
+ toDOM() {
215
+ const span = document.createElement("span");
216
+ span.className = "container-widget-text";
217
+ span.textContent = ":::";
218
+ return span;
219
+ }
220
+ eq() {
221
+ return true;
222
+ }
223
+ ignoreEvent() {
224
+ return false;
225
+ }
236
226
  };
227
+ /**
228
+ * 查找文档中的容器,创建 Decoration 装饰物
229
+ * 将 ::: 头行、{} 属性行和 ::: 结束行替换为只读 widget,使其无法在编辑器中直接编辑
230
+ */
237
231
  function findContainerDecorations(state) {
238
- const docText = state.doc.toString();
239
- const containers = findContainers(docText);
240
- const decorations = [];
241
- for (const container of containers) {
242
- const headerLine = state.doc.lineAt(container.startPos);
243
- decorations.push({
244
- from: headerLine.from,
245
- to: headerLine.to,
246
- value: import_view.Decoration.replace({
247
- widget: new ContainerHeaderWidget(container.typeName)
248
- })
249
- });
250
- if (container.propsLineFrom > 0 && container.propsLineTo > 0) {
251
- const propsLine = state.doc.lineAt(container.propsLineFrom);
252
- const propsText = propsLine.text;
253
- decorations.push({
254
- from: propsLine.from,
255
- to: propsLine.to,
256
- value: import_view.Decoration.replace({
257
- widget: new ContainerPropsWidget(propsText)
258
- })
259
- });
260
- }
261
- if (container.endPos > 0) {
262
- const endLine = state.doc.lineAt(container.endPos);
263
- decorations.push({
264
- from: endLine.from,
265
- to: endLine.to,
266
- value: import_view.Decoration.replace({
267
- widget: new ContainerCloseWidget()
268
- })
269
- });
270
- }
271
- }
272
- return decorations;
232
+ const containers = findContainers(state.doc.toString());
233
+ const decorations = [];
234
+ for (const container of containers) {
235
+ const headerLine = state.doc.lineAt(container.startPos);
236
+ decorations.push({
237
+ from: headerLine.from,
238
+ to: headerLine.to,
239
+ value: _codemirror_view.Decoration.replace({ widget: new ContainerHeaderWidget(container.typeName) })
240
+ });
241
+ if (container.propsLineFrom > 0 && container.propsLineTo > 0) {
242
+ const propsLine = state.doc.lineAt(container.propsLineFrom);
243
+ const propsText = propsLine.text;
244
+ decorations.push({
245
+ from: propsLine.from,
246
+ to: propsLine.to,
247
+ value: _codemirror_view.Decoration.replace({ widget: new ContainerPropsWidget(propsText) })
248
+ });
249
+ }
250
+ if (container.endPos > 0) {
251
+ const endLine = state.doc.lineAt(container.endPos);
252
+ decorations.push({
253
+ from: endLine.from,
254
+ to: endLine.to,
255
+ value: _codemirror_view.Decoration.replace({ widget: new ContainerCloseWidget() })
256
+ });
257
+ }
258
+ }
259
+ return decorations;
273
260
  }
274
- var containerWidgetPlugin = import_view.ViewPlugin.fromClass(
275
- class {
276
- constructor(view) {
277
- this.decorations = import_view.Decoration.set(findContainerDecorations(view.state), true);
278
- }
279
- update(update) {
280
- if (update.docChanged || update.selectionSet) {
281
- this.decorations = import_view.Decoration.set(findContainerDecorations(update.state), true);
282
- }
283
- }
284
- },
285
- {
286
- decorations: (value) => value.decorations,
287
- eventHandlers: {
288
- // 点击编辑按钮时触发回调
289
- click: (event, view) => {
290
- const target = event.target;
291
- if (target.classList.contains("container-edit-btn")) {
292
- const typeName = target.__typeName;
293
- if (typeName) {
294
- const docText = view.state.doc.toString();
295
- const containers = findContainers(docText);
296
- const buttonPos = view.posAtDOM(target);
297
- for (const container of containers) {
298
- const headerLine = view.state.doc.lineAt(container.startPos);
299
- if (buttonPos >= headerLine.from - 5 && buttonPos <= headerLine.to + 5) {
300
- const callback = view.state.facet(containerEditCallback);
301
- if (callback) {
302
- callback(view, container.typeName, container.propsLineFrom, container.propsLineTo, { ...container.props });
303
- }
304
- return true;
305
- }
306
- }
307
- }
308
- return false;
309
- }
310
- return false;
311
- }
312
- }
313
- }
314
- );
315
-
316
- // src/components/Toolbar/buttons.tsx
261
+ /**
262
+ * 容器编辑 ViewPlugin
263
+ * 将容器的 ::: 头行和 {} 属性行替换为只读 widget,使其无法在编辑器中直接编辑
264
+ */
265
+ var containerWidgetPlugin = _codemirror_view.ViewPlugin.fromClass(class {
266
+ decorations;
267
+ constructor(view) {
268
+ this.decorations = _codemirror_view.Decoration.set(findContainerDecorations(view.state), true);
269
+ }
270
+ update(update) {
271
+ if (update.docChanged || update.selectionSet) this.decorations = _codemirror_view.Decoration.set(findContainerDecorations(update.state), true);
272
+ }
273
+ }, {
274
+ decorations: (value) => value.decorations,
275
+ eventHandlers: { click: (event, view) => {
276
+ const target = event.target;
277
+ if (target.classList.contains("container-edit-btn")) {
278
+ if (target.__typeName) {
279
+ const containers = findContainers(view.state.doc.toString());
280
+ const buttonPos = view.posAtDOM(target);
281
+ for (const container of containers) {
282
+ const headerLine = view.state.doc.lineAt(container.startPos);
283
+ if (buttonPos >= headerLine.from - 5 && buttonPos <= headerLine.to + 5) {
284
+ const callback = view.state.facet(containerEditCallback);
285
+ if (callback) callback(view, container.typeName, container.propsLineFrom, container.propsLineTo, { ...container.props });
286
+ return true;
287
+ }
288
+ }
289
+ }
290
+ return false;
291
+ }
292
+ return false;
293
+ } }
294
+ });
295
+ //#endregion
296
+ //#region src/components/Toolbar/buttons.tsx
297
+ /**
298
+ * 在光标位置插入文本
299
+ */
317
300
  function insertAtCursor(view, before, after = "", placeholder = "") {
318
- const { from, to } = view.state.selection.main;
319
- const selectedText = view.state.doc.sliceString(from, to);
320
- const insert = selectedText ? `${before}${selectedText}${after}` : `${before}${placeholder}${after}`;
321
- view.dispatch({ changes: { from, to, insert } });
322
- if (!selectedText && placeholder) {
323
- view.dispatch({ selection: { anchor: from + before.length } });
324
- }
301
+ const { from, to } = view.state.selection.main;
302
+ const selectedText = view.state.doc.sliceString(from, to);
303
+ const insert = selectedText ? `${before}${selectedText}${after}` : `${before}${placeholder}${after}`;
304
+ view.dispatch({ changes: {
305
+ from,
306
+ to,
307
+ insert
308
+ } });
309
+ if (!selectedText && placeholder) view.dispatch({ selection: { anchor: from + before.length } });
325
310
  }
311
+ /**
312
+ * 在当前行行首插入文本
313
+ */
326
314
  function insertAtLineStart(view, text) {
327
- const { from } = view.state.selection.main;
328
- const line = view.state.doc.lineAt(from);
329
- view.dispatch({ changes: { from: line.from, to: line.from, insert: text } });
330
- view.dispatch({ selection: { anchor: line.from + text.length } });
315
+ const { from } = view.state.selection.main;
316
+ const line = view.state.doc.lineAt(from);
317
+ view.dispatch({ changes: {
318
+ from: line.from,
319
+ to: line.from,
320
+ insert: text
321
+ } });
322
+ view.dispatch({ selection: { anchor: line.from + text.length } });
331
323
  }
324
+ /**
325
+ * 上传图片
326
+ * @param file - 要上传的文件
327
+ * @param uploadConfig - 上传配置
328
+ */
332
329
  async function uploadFileToServer(file, uploadConfig) {
333
- var _a;
334
- if (uploadConfig.onUploadImage) {
335
- return uploadConfig.onUploadImage(file);
336
- }
337
- if (uploadConfig.uploadUrl) {
338
- const formData = new FormData();
339
- formData.append("file", file);
340
- const response = await fetch(uploadConfig.uploadUrl, {
341
- method: "POST",
342
- body: formData
343
- });
344
- if (!response.ok) return null;
345
- const result = await response.json();
346
- if (uploadConfig.onGetUploadUrl) {
347
- return uploadConfig.onGetUploadUrl(result);
348
- }
349
- return ((_a = result.data) == null ? void 0 : _a.path) || null;
350
- }
351
- return null;
330
+ if (uploadConfig.onUploadImage) return uploadConfig.onUploadImage(file);
331
+ if (uploadConfig.uploadUrl) {
332
+ const formData = new FormData();
333
+ formData.append("file", file);
334
+ const response = await fetch(uploadConfig.uploadUrl, {
335
+ method: "POST",
336
+ body: formData
337
+ });
338
+ if (!response.ok) return null;
339
+ const result = await response.json();
340
+ if (uploadConfig.onGetUploadUrl) return uploadConfig.onGetUploadUrl(result);
341
+ return result.data?.path || null;
342
+ }
343
+ return null;
352
344
  }
345
+ /**
346
+ * 判断是否配置了图片上传
347
+ */
353
348
  function isUploadConfigured(uploadConfig) {
354
- return !!((uploadConfig == null ? void 0 : uploadConfig.uploadUrl) || (uploadConfig == null ? void 0 : uploadConfig.onUploadImage));
349
+ return !!(uploadConfig?.uploadUrl || uploadConfig?.onUploadImage);
355
350
  }
351
+ /**
352
+ * 图片上传:创建隐藏的 file input 触发选择,上传后插入 markdown 图片语法
353
+ * @param view - CodeMirror 编辑器实例
354
+ * @param uploadConfig - 上传配置(包含 uploadUrl 或 onUploadImage)
355
+ */
356
356
  function uploadImage(view, uploadConfig) {
357
- const input = document.createElement("input");
358
- input.type = "file";
359
- input.accept = "image/*";
360
- input.style.display = "none";
361
- input.addEventListener("change", async () => {
362
- if (!input.files || input.files.length === 0) return;
363
- const file = input.files[0];
364
- const { from, to } = view.state.selection.main;
365
- const selectedText = view.state.doc.sliceString(from, to) || "\u63CF\u8FF0";
366
- const url = await uploadFileToServer(file, uploadConfig);
367
- if (url) {
368
- const insert = `![${selectedText}](${url})`;
369
- view.dispatch({ changes: { from, to, insert } });
370
- }
371
- input.remove();
372
- });
373
- document.body.appendChild(input);
374
- input.click();
357
+ const input = document.createElement("input");
358
+ input.type = "file";
359
+ input.accept = "image/*";
360
+ input.style.display = "none";
361
+ input.addEventListener("change", async () => {
362
+ if (!input.files || input.files.length === 0) return;
363
+ const file = input.files[0];
364
+ const { from, to } = view.state.selection.main;
365
+ const selectedText = view.state.doc.sliceString(from, to) || "描述";
366
+ const url = await uploadFileToServer(file, uploadConfig);
367
+ if (url) {
368
+ const insert = `![${selectedText}](${url})`;
369
+ view.dispatch({ changes: {
370
+ from,
371
+ to,
372
+ insert
373
+ } });
374
+ }
375
+ input.remove();
376
+ });
377
+ document.body.appendChild(input);
378
+ input.click();
375
379
  }
380
+ /**
381
+ * 用前缀和后缀包裹选中文本
382
+ */
376
383
  function wrapSelection(view, prefix, suffix) {
377
- const { from, to, empty } = view.state.selection.main;
378
- if (empty) return;
379
- const selected = view.state.doc.sliceString(from, to);
380
- view.dispatch({ changes: { from, to, insert: `${prefix}${selected}${suffix}` } });
384
+ const { from, to, empty } = view.state.selection.main;
385
+ if (empty) return;
386
+ const selected = view.state.doc.sliceString(from, to);
387
+ view.dispatch({ changes: {
388
+ from,
389
+ to,
390
+ insert: `${prefix}${selected}${suffix}`
391
+ } });
381
392
  }
393
+ /**
394
+ * 切换包裹:选中的文本已包含标签则移除,否则添加
395
+ */
382
396
  function toggleWrap(view, openTag, closeTag) {
383
- const { from, to, empty } = view.state.selection.main;
384
- const doc = view.state.doc;
385
- if (!empty) {
386
- const selected = doc.sliceString(from, to);
387
- if (selected.startsWith(openTag) && selected.endsWith(closeTag)) {
388
- const inner = selected.slice(openTag.length, -closeTag.length);
389
- view.dispatch({ changes: { from, to, insert: inner } });
390
- return;
391
- }
392
- }
393
- {
394
- const cursorPos = empty ? from : to;
395
- const line = doc.lineAt(cursorPos);
396
- const lineText = doc.sliceString(line.from, line.to);
397
- const openIdx = lineText.indexOf(openTag);
398
- const closeIdx = lineText.lastIndexOf(closeTag);
399
- if (openIdx >= 0 && closeIdx > openIdx) {
400
- const absOpen = line.from + openIdx;
401
- const absClose = line.from + closeIdx;
402
- if (cursorPos >= absOpen + openTag.length && cursorPos <= absClose) {
403
- const inner = doc.sliceString(absOpen + openTag.length, absClose);
404
- view.dispatch({
405
- changes: {
406
- from: absOpen,
407
- to: absClose + closeTag.length,
408
- insert: inner
409
- }
410
- });
411
- return;
412
- }
413
- }
414
- }
415
- wrapSelection(view, openTag, closeTag);
397
+ const { from, to, empty } = view.state.selection.main;
398
+ const doc = view.state.doc;
399
+ if (!empty) {
400
+ const selected = doc.sliceString(from, to);
401
+ if (selected.startsWith(openTag) && selected.endsWith(closeTag)) {
402
+ const inner = selected.slice(openTag.length, -closeTag.length);
403
+ view.dispatch({ changes: {
404
+ from,
405
+ to,
406
+ insert: inner
407
+ } });
408
+ return;
409
+ }
410
+ }
411
+ {
412
+ const cursorPos = empty ? from : to;
413
+ const line = doc.lineAt(cursorPos);
414
+ const lineText = doc.sliceString(line.from, line.to);
415
+ const openIdx = lineText.indexOf(openTag);
416
+ const closeIdx = lineText.lastIndexOf(closeTag);
417
+ if (openIdx >= 0 && closeIdx > openIdx) {
418
+ const absOpen = line.from + openIdx;
419
+ const absClose = line.from + closeIdx;
420
+ if (cursorPos >= absOpen + openTag.length && cursorPos <= absClose) {
421
+ const inner = doc.sliceString(absOpen + openTag.length, absClose);
422
+ view.dispatch({ changes: {
423
+ from: absOpen,
424
+ to: absClose + closeTag.length,
425
+ insert: inner
426
+ } });
427
+ return;
428
+ }
429
+ }
430
+ }
431
+ wrapSelection(view, openTag, closeTag);
416
432
  }
417
-
418
- // src/components/Editor/codemirrorExtensions.ts
419
- var uploadConfigFacet = import_state2.Facet.define({
420
- combine: (values) => values[values.length - 1]
421
- });
422
- var messageFacet = import_state2.Facet.define({
423
- combine: (values) => values[values.length - 1]
424
- });
425
- var pasteHandler = import_view2.EditorView.domEventHandlers({
426
- paste: (event, view) => {
427
- var _a;
428
- const files = (_a = event.clipboardData) == null ? void 0 : _a.files;
429
- if (!files) return false;
430
- const imageFiles = Array.from(files).filter((f) => f.type.startsWith("image/"));
431
- if (imageFiles.length === 0) return false;
432
- event.preventDefault();
433
- const uploadConfig = view.state.facet(uploadConfigFacet);
434
- const showMessage = view.state.facet(messageFacet);
435
- if (!isUploadConfigured(uploadConfig)) {
436
- showMessage == null ? void 0 : showMessage();
437
- return true;
438
- }
439
- const { from, to } = view.state.selection.main;
440
- let offset = 0;
441
- for (const file of imageFiles) {
442
- uploadFileToServer(file, uploadConfig).then((url) => {
443
- if (url) {
444
- const name = file.name.replace(/\.[^.]+$/, "");
445
- const insert = `![${name}](${url})`;
446
- view.dispatch({ changes: { from: from + offset, to: to + offset, insert } });
447
- offset += insert.length;
448
- }
449
- });
450
- }
451
- return true;
452
- }
453
- });
433
+ //#endregion
434
+ //#region src/components/Editor/codemirrorExtensions.ts
435
+ /** 图片上传配置 facet */
436
+ var uploadConfigFacet = _codemirror_state.Facet.define({ combine: (values) => values[values.length - 1] });
437
+ /** 未配置上传时的提示回调 facet */
438
+ var messageFacet = _codemirror_state.Facet.define({ combine: (values) => values[values.length - 1] });
439
+ /** 粘贴事件处理:拦截图片并上传 */
440
+ var pasteHandler = _codemirror_view.EditorView.domEventHandlers({ paste: (event, view) => {
441
+ const files = event.clipboardData?.files;
442
+ if (!files) return false;
443
+ const imageFiles = Array.from(files).filter((f) => f.type.startsWith("image/"));
444
+ if (imageFiles.length === 0) return false;
445
+ event.preventDefault();
446
+ const uploadConfig = view.state.facet(uploadConfigFacet);
447
+ const showMessage = view.state.facet(messageFacet);
448
+ if (!isUploadConfigured(uploadConfig)) {
449
+ showMessage?.();
450
+ return true;
451
+ }
452
+ const { from, to } = view.state.selection.main;
453
+ let offset = 0;
454
+ for (const file of imageFiles) uploadFileToServer(file, uploadConfig).then((url) => {
455
+ if (url) {
456
+ const insert = `![${file.name.replace(/\.[^.]+$/, "")}](${url})`;
457
+ view.dispatch({ changes: {
458
+ from: from + offset,
459
+ to: to + offset,
460
+ insert
461
+ } });
462
+ offset += insert.length;
463
+ }
464
+ });
465
+ return true;
466
+ } });
454
467
  var getCodeMirrorExtensions = (opts) => {
455
- const extensions = [
456
- (0, import_lang_markdown.markdown)(),
457
- import_theme_one_dark.oneDark,
458
- import_view2.keymap.of([...import_commands.defaultKeymap, import_commands.indentWithTab]),
459
- import_view2.EditorView.lineWrapping,
460
- pasteHandler,
461
- import_view2.EditorView.baseTheme({
462
- ".cm-scroller": {
463
- height: "100%",
464
- overflow: "auto"
465
- },
466
- // 容器头行 Widget 样式
467
- ".container-widget-header": {
468
- display: "inline",
469
- fontSize: "inherit",
470
- fontFamily: "inherit"
471
- },
472
- ".container-widget-text": {
473
- fontWeight: "bold",
474
- color: "#79c0ff"
475
- },
476
- // 容器属性行 Widget 样式
477
- ".container-widget-props": {
478
- display: "inline",
479
- fontSize: "inherit",
480
- fontFamily: "inherit",
481
- color: "#a5d6ff"
482
- },
483
- // 容器编辑按钮样式
484
- ".container-edit-btn": {
485
- display: "inline-block",
486
- padding: "0 4px",
487
- fontSize: "12px",
488
- color: "#58a6ff",
489
- background: "transparent",
490
- border: "none",
491
- borderRadius: "4px",
492
- cursor: "pointer",
493
- marginRight: "4px",
494
- verticalAlign: "middle"
495
- },
496
- ".container-edit-btn:hover": {
497
- background: "rgba(88, 166, 255, 0.16)"
498
- }
499
- })
500
- ];
501
- extensions.push(containerWidgetPlugin);
502
- if (opts == null ? void 0 : opts.onEditContainer) {
503
- extensions.push(containerEditCallback.of(opts.onEditContainer));
504
- }
505
- if (opts == null ? void 0 : opts.uploadConfig) {
506
- extensions.push(uploadConfigFacet.of(opts.uploadConfig));
507
- }
508
- if (opts == null ? void 0 : opts.onMessageNoUploadConfig) {
509
- extensions.push(messageFacet.of(opts.onMessageNoUploadConfig));
510
- }
511
- return extensions;
468
+ const extensions = [
469
+ (0, _codemirror_lang_markdown.markdown)(),
470
+ _codemirror_theme_one_dark.oneDark,
471
+ _codemirror_view.keymap.of([..._codemirror_commands.defaultKeymap, _codemirror_commands.indentWithTab]),
472
+ _codemirror_view.EditorView.lineWrapping,
473
+ pasteHandler,
474
+ _codemirror_view.EditorView.baseTheme({
475
+ ".cm-scroller": {
476
+ height: "100%",
477
+ overflow: "auto"
478
+ },
479
+ ".container-widget-header": {
480
+ display: "inline",
481
+ fontSize: "inherit",
482
+ fontFamily: "inherit"
483
+ },
484
+ ".container-widget-text": {
485
+ fontWeight: "bold",
486
+ color: "#79c0ff"
487
+ },
488
+ ".container-widget-props": {
489
+ display: "inline",
490
+ fontSize: "inherit",
491
+ fontFamily: "inherit",
492
+ color: "#a5d6ff"
493
+ },
494
+ ".container-edit-btn": {
495
+ display: "inline-block",
496
+ padding: "0 4px",
497
+ fontSize: "12px",
498
+ color: "#58a6ff",
499
+ background: "transparent",
500
+ border: "none",
501
+ borderRadius: "4px",
502
+ cursor: "pointer",
503
+ marginRight: "4px",
504
+ verticalAlign: "middle"
505
+ },
506
+ ".container-edit-btn:hover": { background: "rgba(88, 166, 255, 0.16)" }
507
+ })
508
+ ];
509
+ extensions.push(containerWidgetPlugin);
510
+ if (opts?.onEditContainer) extensions.push(containerEditCallback.of(opts.onEditContainer));
511
+ if (opts?.uploadConfig) extensions.push(uploadConfigFacet.of(opts.uploadConfig));
512
+ if (opts?.onMessageNoUploadConfig) extensions.push(messageFacet.of(opts.onMessageNoUploadConfig));
513
+ return extensions;
512
514
  };
513
-
514
- // src/components/Editor/index.tsx
515
- var import_jsx_runtime = require("react/jsx-runtime");
515
+ //#endregion
516
+ //#region src/components/Editor/index.tsx
516
517
  function Editor({ value, onChange, onScrollReady, onViewReady, onEditContainer, uploadConfig, onMessageNoUploadConfig }) {
517
- const editorRef = (0, import_react.useRef)(null);
518
- const viewRef = (0, import_react.useRef)(null);
519
- (0, import_react.useEffect)(() => {
520
- if (!editorRef.current) return;
521
- const extensions = [
522
- import_codemirror.basicSetup,
523
- ...getCodeMirrorExtensions({ onEditContainer, uploadConfig, onMessageNoUploadConfig }),
524
- import_codemirror.EditorView.updateListener.of((update) => {
525
- if (update.docChanged) {
526
- onChange(update.state.doc.toString());
527
- }
528
- })
529
- ];
530
- const state = import_state3.EditorState.create({
531
- doc: value,
532
- extensions
533
- });
534
- viewRef.current = new import_codemirror.EditorView({
535
- state,
536
- parent: editorRef.current
537
- });
538
- if (onScrollReady && viewRef.current) {
539
- onScrollReady(viewRef.current.scrollDOM);
540
- }
541
- if (onViewReady) {
542
- onViewReady(viewRef.current);
543
- }
544
- return () => {
545
- var _a;
546
- (_a = viewRef.current) == null ? void 0 : _a.destroy();
547
- };
548
- }, []);
549
- (0, import_react.useEffect)(() => {
550
- if (viewRef.current && viewRef.current.state.doc.toString() !== value) {
551
- viewRef.current.dispatch({
552
- changes: { from: 0, to: viewRef.current.state.doc.length, insert: value }
553
- });
554
- }
555
- }, [value]);
556
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: editorRef, className: "editor" });
518
+ const editorRef = (0, react.useRef)(null);
519
+ const viewRef = (0, react.useRef)(null);
520
+ (0, react.useEffect)(() => {
521
+ if (!editorRef.current) return;
522
+ const extensions = [
523
+ codemirror.basicSetup,
524
+ ...getCodeMirrorExtensions({
525
+ onEditContainer,
526
+ uploadConfig,
527
+ onMessageNoUploadConfig
528
+ }),
529
+ codemirror.EditorView.updateListener.of((update) => {
530
+ if (update.docChanged) onChange(update.state.doc.toString());
531
+ })
532
+ ];
533
+ const state = _codemirror_state.EditorState.create({
534
+ doc: value,
535
+ extensions
536
+ });
537
+ viewRef.current = new codemirror.EditorView({
538
+ state,
539
+ parent: editorRef.current
540
+ });
541
+ if (onScrollReady && viewRef.current) onScrollReady(viewRef.current.scrollDOM);
542
+ if (onViewReady) onViewReady(viewRef.current);
543
+ return () => {
544
+ viewRef.current?.destroy();
545
+ };
546
+ }, []);
547
+ (0, react.useEffect)(() => {
548
+ if (viewRef.current && viewRef.current.state.doc.toString() !== value) viewRef.current.dispatch({ changes: {
549
+ from: 0,
550
+ to: viewRef.current.state.doc.length,
551
+ insert: value
552
+ } });
553
+ }, [value]);
554
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
555
+ ref: editorRef,
556
+ className: "editor"
557
+ });
557
558
  }
558
-
559
- // src/components/Preview/index.tsx
560
- var import_react2 = require("react");
561
- var import_remons_render_markdown = __toESM(require("remons-render-markdown"), 1);
562
- var import_dist = require("remons-render-markdown/dist/index.css");
563
- var import_jsx_runtime2 = require("react/jsx-runtime");
564
- var Preview = (0, import_react2.forwardRef)(({ content, previewOptions }, ref) => {
565
- (0, import_react2.useEffect)(() => {
566
- (0, import_remons_render_markdown.initHighlighter)({
567
- ...import_remons_render_markdown.languagesCommon,
568
- ...(previewOptions == null ? void 0 : previewOptions.languages) ? previewOptions == null ? void 0 : previewOptions.languages : {}
569
- });
570
- }, [previewOptions == null ? void 0 : previewOptions.languages]);
571
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, className: "preview", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_remons_render_markdown.default, { content, ...previewOptions }) });
559
+ //#endregion
560
+ //#region src/components/Preview/index.tsx
561
+ var Preview = (0, react.forwardRef)(({ content, previewOptions }, ref) => {
562
+ (0, react.useEffect)(() => {
563
+ (0, remons_render_markdown.initHighlighter)({
564
+ ...remons_render_markdown.languagesCommon,
565
+ ...previewOptions?.languages ? previewOptions?.languages : {}
566
+ });
567
+ }, [previewOptions?.languages]);
568
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
569
+ ref,
570
+ className: "preview",
571
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(remons_render_markdown.default, {
572
+ content,
573
+ ...previewOptions
574
+ })
575
+ });
572
576
  });
573
577
  Preview.displayName = "Preview";
574
- var Preview_default = Preview;
575
-
576
- // src/components/Panel/SplitPanels.tsx
577
- var import_antd = require("antd");
578
- var import_jsx_runtime3 = require("react/jsx-runtime");
578
+ //#endregion
579
+ //#region src/components/Panel/SplitPanels.tsx
579
580
  function SplitPanels({ leftPanel, rightPanel }) {
580
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_antd.Splitter, { className: "split-panels", children: [
581
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_antd.Splitter.Panel, { defaultSize: "50%", min: "20%", max: "80%", children: leftPanel }),
582
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_antd.Splitter.Panel, { children: rightPanel })
583
- ] });
581
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(antd.Splitter, {
582
+ className: "split-panels",
583
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Splitter.Panel, {
584
+ defaultSize: "50%",
585
+ min: "20%",
586
+ max: "80%",
587
+ children: leftPanel
588
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Splitter.Panel, { children: rightPanel })]
589
+ });
584
590
  }
585
-
586
- // src/components/Toolbar/index.tsx
587
- var import_react6 = require("react");
588
- var import_antd4 = require("antd");
589
- var import_icons4 = require("@ant-design/icons");
590
-
591
- // src/components/Toolbar/hooks/useToolbar.ts
592
- var import_react3 = require("react");
593
-
594
- // src/components/Toolbar/config.ts
595
- var import_icons = require("@ant-design/icons");
591
+ //#endregion
592
+ //#region src/components/Toolbar/config.ts
593
+ /**
594
+ * 创建工具栏配置
595
+ * @param options - 配置选项
596
+ */
596
597
  function createToolbarConfig(options) {
597
- const hasUploadConfig = isUploadConfigured(options);
598
- return [
599
- {
600
- id: "heading",
601
- icon: import_icons.FontSizeOutlined,
602
- label: "\u6807\u9898",
603
- children: [
604
- { id: "heading1", label: "\u6807\u9898 1", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "# ") },
605
- { id: "heading2", label: "\u6807\u9898 2", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "## ") },
606
- { id: "heading3", label: "\u6807\u9898 3", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "### ") },
607
- { id: "heading4", label: "\u6807\u9898 4", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "#### ") },
608
- { id: "heading5", label: "\u6807\u9898 5", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "##### ") },
609
- { id: "heading6", label: "\u6807\u9898 6", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "###### ") }
610
- ]
611
- },
612
- { separatorBefore: true },
613
- { id: "bold", label: "\u52A0\u7C97", icon: import_icons.BoldOutlined, handler: (v) => wrapSelection(v, "**", "**") },
614
- { id: "italic", label: "\u659C\u4F53", icon: import_icons.ItalicOutlined, handler: (v) => wrapSelection(v, "*", "*") },
615
- { id: "strikethrough", label: "\u5220\u9664\u7EBF", icon: import_icons.StrikethroughOutlined, handler: (v) => wrapSelection(v, "~~", "~~") },
616
- { id: "underline", label: "\u4E0B\u5212\u7EBF", icon: import_icons.UnderlineOutlined, handler: (v) => wrapSelection(v, "<u>", "</u>") },
617
- { separatorBefore: true },
618
- { id: "ul", label: "\u65E0\u5E8F\u5217\u8868", icon: import_icons.UnorderedListOutlined, handler: (v) => insertAtLineStart(v, "- ") },
619
- { id: "ol", label: "\u6709\u5E8F\u5217\u8868", icon: import_icons.OrderedListOutlined, handler: (v) => insertAtLineStart(v, "1. ") },
620
- { separatorBefore: true },
621
- { id: "link", label: "\u94FE\u63A5", icon: import_icons.LinkOutlined, handler: (v) => insertAtCursor(v, "[", "](url)", "\u6587\u672C") },
622
- // 图片按钮:未配置上传时禁用并提示
623
- {
624
- id: "image",
625
- label: hasUploadConfig ? "\u56FE\u7247" : "\u8BF7\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u5730\u5740",
626
- icon: import_icons.PictureOutlined,
627
- disabled: !hasUploadConfig,
628
- handler: hasUploadConfig ? (v) => uploadImage(v, { uploadUrl: options == null ? void 0 : options.uploadUrl, onUploadImage: options == null ? void 0 : options.onUploadImage }) : void 0
629
- },
630
- { separatorBefore: true },
631
- { id: "code", label: "\u884C\u5185\u4EE3\u7801", icon: import_icons.FieldStringOutlined, handler: (v) => wrapSelection(v, "`", "`") },
632
- {
633
- id: "codeblock",
634
- label: "\u4EE3\u7801\u5757",
635
- icon: import_icons.CodeOutlined,
636
- handler: (v) => {
637
- const { from, to } = v.state.selection.main;
638
- const selected = v.state.doc.sliceString(from, to);
639
- const insert = selected ? "```\n" + selected + "\n```" : "```\n\n```";
640
- v.dispatch({ changes: { from, to, insert } });
641
- }
642
- },
643
- { id: "quote", label: "\u5F15\u7528", icon: import_icons.BlockOutlined, handler: (v) => insertAtLineStart(v, "> ") },
644
- {
645
- id: "hr",
646
- label: "\u5206\u5272\u7EBF",
647
- icon: import_icons.MinusOutlined,
648
- handler: (v) => {
649
- const { from } = v.state.selection.main;
650
- v.dispatch({ changes: { from, insert: "\n---\n" } });
651
- }
652
- }
653
- ];
598
+ const hasUploadConfig = isUploadConfigured(options);
599
+ return [
600
+ {
601
+ id: "heading",
602
+ icon: _ant_design_icons.FontSizeOutlined,
603
+ label: "标题",
604
+ children: [
605
+ {
606
+ id: "heading1",
607
+ label: "标题 1",
608
+ icon: _ant_design_icons.FontSizeOutlined,
609
+ handler: (v) => insertAtLineStart(v, "# ")
610
+ },
611
+ {
612
+ id: "heading2",
613
+ label: "标题 2",
614
+ icon: _ant_design_icons.FontSizeOutlined,
615
+ handler: (v) => insertAtLineStart(v, "## ")
616
+ },
617
+ {
618
+ id: "heading3",
619
+ label: "标题 3",
620
+ icon: _ant_design_icons.FontSizeOutlined,
621
+ handler: (v) => insertAtLineStart(v, "### ")
622
+ },
623
+ {
624
+ id: "heading4",
625
+ label: "标题 4",
626
+ icon: _ant_design_icons.FontSizeOutlined,
627
+ handler: (v) => insertAtLineStart(v, "#### ")
628
+ },
629
+ {
630
+ id: "heading5",
631
+ label: "标题 5",
632
+ icon: _ant_design_icons.FontSizeOutlined,
633
+ handler: (v) => insertAtLineStart(v, "##### ")
634
+ },
635
+ {
636
+ id: "heading6",
637
+ label: "标题 6",
638
+ icon: _ant_design_icons.FontSizeOutlined,
639
+ handler: (v) => insertAtLineStart(v, "###### ")
640
+ }
641
+ ]
642
+ },
643
+ { separatorBefore: true },
644
+ {
645
+ id: "bold",
646
+ label: "加粗",
647
+ icon: _ant_design_icons.BoldOutlined,
648
+ handler: (v) => wrapSelection(v, "**", "**")
649
+ },
650
+ {
651
+ id: "italic",
652
+ label: "斜体",
653
+ icon: _ant_design_icons.ItalicOutlined,
654
+ handler: (v) => wrapSelection(v, "*", "*")
655
+ },
656
+ {
657
+ id: "strikethrough",
658
+ label: "删除线",
659
+ icon: _ant_design_icons.StrikethroughOutlined,
660
+ handler: (v) => wrapSelection(v, "~~", "~~")
661
+ },
662
+ {
663
+ id: "underline",
664
+ label: "下划线",
665
+ icon: _ant_design_icons.UnderlineOutlined,
666
+ handler: (v) => wrapSelection(v, "<u>", "</u>")
667
+ },
668
+ { separatorBefore: true },
669
+ {
670
+ id: "ul",
671
+ label: "无序列表",
672
+ icon: _ant_design_icons.UnorderedListOutlined,
673
+ handler: (v) => insertAtLineStart(v, "- ")
674
+ },
675
+ {
676
+ id: "ol",
677
+ label: "有序列表",
678
+ icon: _ant_design_icons.OrderedListOutlined,
679
+ handler: (v) => insertAtLineStart(v, "1. ")
680
+ },
681
+ { separatorBefore: true },
682
+ {
683
+ id: "link",
684
+ label: "链接",
685
+ icon: _ant_design_icons.LinkOutlined,
686
+ handler: (v) => insertAtCursor(v, "[", "](url)", "文本")
687
+ },
688
+ {
689
+ id: "image",
690
+ label: hasUploadConfig ? "图片" : "请配置图片上传地址",
691
+ icon: _ant_design_icons.PictureOutlined,
692
+ disabled: !hasUploadConfig,
693
+ handler: hasUploadConfig ? (v) => uploadImage(v, {
694
+ uploadUrl: options?.uploadUrl,
695
+ onUploadImage: options?.onUploadImage
696
+ }) : void 0
697
+ },
698
+ { separatorBefore: true },
699
+ {
700
+ id: "code",
701
+ label: "行内代码",
702
+ icon: _ant_design_icons.FieldStringOutlined,
703
+ handler: (v) => wrapSelection(v, "`", "`")
704
+ },
705
+ {
706
+ id: "codeblock",
707
+ label: "代码块",
708
+ icon: _ant_design_icons.CodeOutlined,
709
+ handler: (v) => {
710
+ const { from, to } = v.state.selection.main;
711
+ const selected = v.state.doc.sliceString(from, to);
712
+ const insert = selected ? "```\n" + selected + "\n```" : "```\n\n```";
713
+ v.dispatch({ changes: {
714
+ from,
715
+ to,
716
+ insert
717
+ } });
718
+ }
719
+ },
720
+ {
721
+ id: "quote",
722
+ label: "引用",
723
+ icon: _ant_design_icons.BlockOutlined,
724
+ handler: (v) => insertAtLineStart(v, "> ")
725
+ },
726
+ {
727
+ id: "hr",
728
+ label: "分割线",
729
+ icon: _ant_design_icons.MinusOutlined,
730
+ handler: (v) => {
731
+ const { from } = v.state.selection.main;
732
+ v.dispatch({ changes: {
733
+ from,
734
+ insert: "\n---\n"
735
+ } });
736
+ }
737
+ }
738
+ ];
654
739
  }
655
-
656
- // src/components/Toolbar/hooks/useToolbar.ts
740
+ //#endregion
741
+ //#region src/components/Toolbar/hooks/useToolbar.ts
657
742
  function useToolbar(editorView, options) {
658
- const { extraItems, uploadConfig } = options || {};
659
- const config = (0, import_react3.useMemo)(() => {
660
- const base = createToolbarConfig(uploadConfig);
661
- return extraItems ? [...base, ...extraItems] : base;
662
- }, [extraItems, uploadConfig]);
663
- const handleClick = (0, import_react3.useCallback)((button) => {
664
- if (!editorView) return;
665
- if (button.wrapTags) {
666
- const [openTag, closeTag] = button.wrapTags;
667
- toggleWrap(editorView, openTag, closeTag);
668
- return;
669
- }
670
- if (button.handler) {
671
- editorView.focus();
672
- button.handler(editorView, editorView.state);
673
- }
674
- }, [editorView]);
675
- return { config, handleClick };
743
+ const { extraItems, uploadConfig } = options || {};
744
+ return {
745
+ config: (0, react.useMemo)(() => {
746
+ return createToolbarConfig(uploadConfig);
747
+ }, [uploadConfig]),
748
+ extraItemsConfig: (0, react.useMemo)(() => {
749
+ return extraItems || [];
750
+ }, [extraItems]),
751
+ handleClick: (0, react.useCallback)((button) => {
752
+ if (!editorView) return;
753
+ if (button.wrapTags) {
754
+ const [openTag, closeTag] = button.wrapTags;
755
+ toggleWrap(editorView, openTag, closeTag);
756
+ return;
757
+ }
758
+ if (button.handler) {
759
+ editorView.focus();
760
+ button.handler(editorView, editorView.state);
761
+ }
762
+ }, [editorView])
763
+ };
676
764
  }
677
-
678
- // src/components/Toolbar/renderers/ToolbarItemRenderer.tsx
679
- var import_antd2 = require("antd");
680
- var import_icons2 = require("@ant-design/icons");
681
- var import_jsx_runtime4 = require("react/jsx-runtime");
765
+ //#endregion
766
+ //#region src/components/Toolbar/renderers/ToolbarItemRenderer.tsx
682
767
  function ToolbarItemRenderer({ item, onClick }) {
683
- const Icon = item.icon;
684
- if (item.children && item.children.length > 0) {
685
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
686
- item.separatorBefore && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "toolbar-separator" }),
687
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "toolbar-group", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_antd2.Tooltip, { title: item.label, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
688
- import_antd2.Dropdown,
689
- {
690
- menu: {
691
- items: item.children.map((child) => {
692
- var _a;
693
- return {
694
- type: "item",
695
- key: (_a = child.id) != null ? _a : "",
696
- label: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { onClick: () => onClick(child), children: [
697
- child.icon && (() => {
698
- const ChildIcon = child.icon;
699
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChildIcon, {});
700
- })(),
701
- " ",
702
- child.label
703
- ] })
704
- };
705
- })
706
- },
707
- placement: "bottomLeft",
708
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_antd2.Button, { className: "toolbar-button", type: "text", size: "small", children: [
709
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, {}),
710
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_icons2.DownOutlined, { style: { marginLeft: 2, fontSize: 10 } })
711
- ] })
712
- }
713
- ) }) })
714
- ] });
715
- }
716
- const isDisabled = item.disabled;
717
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
718
- item.separatorBefore && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "toolbar-separator" }),
719
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "toolbar-group", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_antd2.Tooltip, { title: item.label, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
720
- import_antd2.Button,
721
- {
722
- id: item.id,
723
- className: "toolbar-button",
724
- type: "text",
725
- size: "small",
726
- disabled: isDisabled,
727
- onClick: () => !isDisabled && onClick(item),
728
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, {})
729
- }
730
- ) }) })
731
- ] });
768
+ const Icon = item.icon;
769
+ if (item.children && item.children.length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [item.separatorBefore && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "toolbar-separator" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
770
+ className: "toolbar-group",
771
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Tooltip, {
772
+ title: item.label,
773
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Dropdown, {
774
+ menu: { items: item.children.map((child) => ({
775
+ type: "item",
776
+ key: child.id ?? "",
777
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
778
+ onClick: () => onClick(child),
779
+ children: [
780
+ child.icon && (() => {
781
+ const ChildIcon = child.icon;
782
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChildIcon, {});
783
+ })(),
784
+ " ",
785
+ child.label
786
+ ]
787
+ })
788
+ })) },
789
+ placement: "bottomLeft",
790
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(antd.Button, {
791
+ className: "toolbar-button",
792
+ type: "text",
793
+ size: "small",
794
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ant_design_icons.DownOutlined, { style: {
795
+ marginLeft: 2,
796
+ fontSize: 10
797
+ } })]
798
+ })
799
+ })
800
+ })
801
+ })] });
802
+ const isDisabled = item.disabled;
803
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [item.separatorBefore && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "toolbar-separator" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
804
+ className: "toolbar-group",
805
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Tooltip, {
806
+ title: item.label,
807
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Button, {
808
+ id: item.id,
809
+ className: "toolbar-button",
810
+ type: "text",
811
+ size: "small",
812
+ disabled: isDisabled,
813
+ onClick: () => !isDisabled && onClick(item),
814
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {})
815
+ })
816
+ })
817
+ })] });
732
818
  }
733
-
734
- // src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
735
- var import_react5 = __toESM(require("react"), 1);
736
- var import_antd3 = require("antd");
737
- var import_icons3 = require("@ant-design/icons");
738
-
739
- // src/components/Toolbar/renderers/TablePicker/index.tsx
740
- var import_react4 = require("react");
741
-
742
- // src/components/Toolbar/renderers/TablePicker/index.module.less
743
- var index_module_default = {};
744
-
745
- // src/components/Toolbar/renderers/TablePicker/index.tsx
746
- var import_jsx_runtime5 = require("react/jsx-runtime");
819
+ var index_module_default = {
820
+ container: "index-module__container___l6BT-",
821
+ grid: "index-module__grid___tCZ-r",
822
+ cell: "index-module__cell___vTDQz",
823
+ cellActive: "index-module__cellActive___0EYvU",
824
+ hint: "index-module__hint___dTObk",
825
+ label: "index-module__label___2CmK1"
826
+ };
827
+ //#endregion
828
+ //#region src/components/Toolbar/renderers/TablePicker/index.tsx
747
829
  var MAX_ROW = 8;
748
830
  var MAX_COL = 8;
749
831
  function TablePicker({ onSelect }) {
750
- const [hoverSize, setHoverSize] = (0, import_react4.useState)({ row: 0, col: 0 });
751
- const [confirmedSize, setConfirmedSize] = (0, import_react4.useState)({ row: 0, col: 0 });
752
- const maxColRef = (0, import_react4.useRef)(0);
753
- const prevRowRef = (0, import_react4.useRef)(0);
754
- const timerIdRef = (0, import_react4.useRef)(null);
755
- const refreshTimerRef = (0, import_react4.useRef)(null);
756
- const commitRef = (0, import_react4.useRef)(null);
757
- const clearTimer = (0, import_react4.useCallback)(() => {
758
- if (timerIdRef.current) {
759
- clearTimeout(timerIdRef.current);
760
- timerIdRef.current = null;
761
- }
762
- }, []);
763
- const clearFlash = (0, import_react4.useCallback)(() => {
764
- if (refreshTimerRef.current) {
765
- clearInterval(refreshTimerRef.current);
766
- refreshTimerRef.current = null;
767
- }
768
- }, []);
769
- (0, import_react4.useEffect)(
770
- () => () => {
771
- if (timerIdRef.current) clearTimeout(timerIdRef.current);
772
- if (refreshTimerRef.current) clearInterval(refreshTimerRef.current);
773
- },
774
- []
775
- );
776
- function extend(col, row) {
777
- if (row < prevRowRef.current || col < maxColRef.current) {
778
- maxColRef.current = 0;
779
- }
780
- if (col > maxColRef.current) {
781
- maxColRef.current = col;
782
- }
783
- setHoverSize({ row, col });
784
- setConfirmedSize({ row, col: maxColRef.current });
785
- if (row === 1) {
786
- commitRef.current = { cols: maxColRef.current, rows: 1 };
787
- }
788
- }
789
- function flash(col, dir, limit, finalCallback) {
790
- let current = dir === 1 ? 1 : limit;
791
- const step = dir === 1 ? 1 : -1;
792
- refreshTimerRef.current = setInterval(() => {
793
- setHoverSize({ row: current, col });
794
- if (current > confirmedSize.row || dir === -1) {
795
- maxColRef.current = Math.min(MAX_COL, col);
796
- }
797
- current += step;
798
- if (dir === 1 && current > limit || dir === -1 && current < 1) {
799
- clearInterval(refreshTimerRef.current);
800
- refreshTimerRef.current = null;
801
- const finalRow = current - step;
802
- setHoverSize({ row: finalRow, col });
803
- finalCallback(col, finalRow);
804
- }
805
- }, 50);
806
- }
807
- function onCellEnterCheck(index) {
808
- clearTimer();
809
- clearFlash();
810
- const row = Math.floor(index / MAX_COL) + 1;
811
- const col = index % MAX_COL + 1;
812
- const prevRow = prevRowRef.current;
813
- const isJump = Math.abs(row - prevRow) > 1;
814
- const isDown = row > prevRow;
815
- prevRowRef.current = row;
816
- if (!isJump) {
817
- extend(col, row);
818
- } else if (isDown) {
819
- if (col > maxColRef.current || maxColRef.current >= MAX_COL) {
820
- const finalRow = Math.min(row, MAX_ROW);
821
- flash(col, 1, finalRow, (c, r) => {
822
- setConfirmedSize({ row: r, col: c });
823
- });
824
- } else {
825
- extend(col, row);
826
- }
827
- } else {
828
- maxColRef.current = 0;
829
- flash(col, -1, row, (c, r) => {
830
- setConfirmedSize({ row: r, col: c });
831
- if (r === 1) {
832
- commitRef.current = { cols: Math.min(MAX_COL, col), rows: 1 };
833
- setConfirmedSize({ row: 1, col: Math.min(MAX_COL, col) });
834
- }
835
- });
836
- }
837
- }
838
- function onMouseDown() {
839
- const size = commitRef.current || { cols: maxColRef.current, rows: confirmedSize.row };
840
- if (size.rows > 0 && size.cols > 0) {
841
- onSelect(size);
842
- }
843
- }
844
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
845
- "div",
846
- {
847
- className: index_module_default.container,
848
- onMouseDown,
849
- children: [
850
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
851
- "div",
852
- {
853
- className: index_module_default.grid,
854
- style: { gridTemplateColumns: `repeat(${MAX_COL}, 24px)` },
855
- children: Array.from({ length: MAX_ROW * MAX_COL }).map((_, i) => {
856
- const row = Math.floor(i / MAX_COL) + 1;
857
- const col = i % MAX_COL + 1;
858
- const isSelected = row <= hoverSize.row && col <= maxColRef.current;
859
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
860
- "div",
861
- {
862
- className: `${index_module_default.cell} ${isSelected ? index_module_default.cellActive : ""}`,
863
- onMouseEnter: () => onCellEnterCheck(i)
864
- },
865
- i
866
- );
867
- })
868
- }
869
- ),
870
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: index_module_default.label, children: [
871
- hoverSize.row,
872
- " x ",
873
- maxColRef.current || 1
874
- ] })
875
- ]
876
- }
877
- );
832
+ const [hoverSize, setHoverSize] = (0, react.useState)({
833
+ row: 0,
834
+ col: 0
835
+ });
836
+ const [confirmedSize, setConfirmedSize] = (0, react.useState)({
837
+ row: 0,
838
+ col: 0
839
+ });
840
+ const maxColRef = (0, react.useRef)(0);
841
+ const prevRowRef = (0, react.useRef)(0);
842
+ const timerIdRef = (0, react.useRef)(null);
843
+ const refreshTimerRef = (0, react.useRef)(null);
844
+ const commitRef = (0, react.useRef)(null);
845
+ const clearTimer = (0, react.useCallback)(() => {
846
+ if (timerIdRef.current) {
847
+ clearTimeout(timerIdRef.current);
848
+ timerIdRef.current = null;
849
+ }
850
+ }, []);
851
+ const clearFlash = (0, react.useCallback)(() => {
852
+ if (refreshTimerRef.current) {
853
+ clearInterval(refreshTimerRef.current);
854
+ refreshTimerRef.current = null;
855
+ }
856
+ }, []);
857
+ (0, react.useEffect)(() => () => {
858
+ if (timerIdRef.current) clearTimeout(timerIdRef.current);
859
+ if (refreshTimerRef.current) clearInterval(refreshTimerRef.current);
860
+ }, []);
861
+ /**
862
+ * 扩展选中区域至 (col, row)
863
+ */
864
+ function extend(col, row) {
865
+ if (row < prevRowRef.current || col < maxColRef.current) maxColRef.current = 0;
866
+ if (col > maxColRef.current) maxColRef.current = col;
867
+ setHoverSize({
868
+ row,
869
+ col
870
+ });
871
+ setConfirmedSize({
872
+ row,
873
+ col: maxColRef.current
874
+ });
875
+ if (row === 1) commitRef.current = {
876
+ cols: maxColRef.current,
877
+ rows: 1
878
+ };
879
+ }
880
+ /**
881
+ * 跳跃动画效果
882
+ */
883
+ function flash(col, dir, limit, finalCallback) {
884
+ let current = dir === 1 ? 1 : limit;
885
+ const step = dir === 1 ? 1 : -1;
886
+ refreshTimerRef.current = setInterval(() => {
887
+ setHoverSize({
888
+ row: current,
889
+ col
890
+ });
891
+ if (current > confirmedSize.row || dir === -1) maxColRef.current = Math.min(MAX_COL, col);
892
+ current += step;
893
+ if (dir === 1 && current > limit || dir === -1 && current < 1) {
894
+ clearInterval(refreshTimerRef.current);
895
+ refreshTimerRef.current = null;
896
+ const finalRow = current - step;
897
+ setHoverSize({
898
+ row: finalRow,
899
+ col
900
+ });
901
+ finalCallback(col, finalRow);
902
+ }
903
+ }, 50);
904
+ }
905
+ /**
906
+ * 核心逻辑:鼠标进入格子
907
+ */
908
+ function onCellEnterCheck(index) {
909
+ clearTimer();
910
+ clearFlash();
911
+ const row = Math.floor(index / MAX_COL) + 1;
912
+ const col = index % MAX_COL + 1;
913
+ const prevRow = prevRowRef.current;
914
+ const isJump = Math.abs(row - prevRow) > 1;
915
+ const isDown = row > prevRow;
916
+ prevRowRef.current = row;
917
+ if (!isJump) extend(col, row);
918
+ else if (isDown) {
919
+ if (col > maxColRef.current || maxColRef.current >= MAX_COL) flash(col, 1, Math.min(row, MAX_ROW), (c, r) => {
920
+ setConfirmedSize({
921
+ row: r,
922
+ col: c
923
+ });
924
+ });
925
+ else extend(col, row);
926
+ } else {
927
+ maxColRef.current = 0;
928
+ flash(col, -1, row, (c, r) => {
929
+ setConfirmedSize({
930
+ row: r,
931
+ col: c
932
+ });
933
+ if (r === 1) {
934
+ commitRef.current = {
935
+ cols: Math.min(MAX_COL, col),
936
+ rows: 1
937
+ };
938
+ setConfirmedSize({
939
+ row: 1,
940
+ col: Math.min(MAX_COL, col)
941
+ });
942
+ }
943
+ });
944
+ }
945
+ }
946
+ function onMouseDown() {
947
+ const size = commitRef.current || {
948
+ cols: maxColRef.current,
949
+ rows: confirmedSize.row
950
+ };
951
+ if (size.rows > 0 && size.cols > 0) onSelect(size);
952
+ }
953
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
954
+ className: index_module_default.container,
955
+ onMouseDown,
956
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
957
+ className: index_module_default.grid,
958
+ style: { gridTemplateColumns: `repeat(${MAX_COL}, 24px)` },
959
+ children: Array.from({ length: 64 }).map((_, i) => {
960
+ const row = Math.floor(i / MAX_COL) + 1;
961
+ const col = i % MAX_COL + 1;
962
+ const isSelected = row <= hoverSize.row && col <= maxColRef.current;
963
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
964
+ className: `${index_module_default.cell} ${isSelected ? index_module_default.cellActive : ""}`,
965
+ onMouseEnter: () => onCellEnterCheck(i)
966
+ }, i);
967
+ })
968
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
969
+ className: index_module_default.label,
970
+ children: [
971
+ hoverSize.row,
972
+ " x ",
973
+ maxColRef.current || 1
974
+ ]
975
+ })]
976
+ });
878
977
  }
879
-
880
- // src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
881
- var import_jsx_runtime6 = require("react/jsx-runtime");
978
+ //#endregion
979
+ //#region src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
882
980
  function TableDropdown({ editorView }) {
883
- const [open, setOpen] = import_react5.default.useState(false);
884
- const openCountRef = (0, import_react5.useRef)(0);
885
- const handleTableSelect = (0, import_react5.useCallback)((rows, cols) => {
886
- if (!editorView || rows <= 0 || cols <= 0) return;
887
- editorView.focus();
888
- const headerCells = Array(cols).fill(null).map((_, i) => ` \u5217${i + 1} `).join("|");
889
- const separator = Array(cols).fill(" --- ").join("|");
890
- const dataCell = Array(cols).fill(" \u5185\u5BB9 ").join("|");
891
- const dataRows = Array(rows).fill(`|${dataCell}|`).join("\n");
892
- const table = `
893
- |${headerCells}|
894
- |${separator}|
895
- ${dataRows}
896
- `;
897
- const { from } = editorView.state.selection.main;
898
- editorView.dispatch({ changes: { from, insert: table } });
899
- }, [editorView]);
900
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
901
- import_antd3.Dropdown,
902
- {
903
- trigger: ["click"],
904
- open,
905
- onOpenChange: setOpen,
906
- popupRender: () => {
907
- openCountRef.current++;
908
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: 4 }, onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
909
- TablePicker,
910
- {
911
- onSelect: (size) => {
912
- handleTableSelect(size.rows, size.cols);
913
- setOpen(false);
914
- }
915
- },
916
- openCountRef.current
917
- ) });
918
- },
919
- placement: "bottomLeft",
920
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_antd3.Button, { className: "toolbar-button", type: "text", size: "small", title: "\u8868\u683C", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons3.TableOutlined, {}) })
921
- }
922
- );
981
+ const [open, setOpen] = react.default.useState(false);
982
+ const openCountRef = (0, react.useRef)(0);
983
+ const handleTableSelect = (0, react.useCallback)((rows, cols) => {
984
+ if (!editorView || rows <= 0 || cols <= 0) return;
985
+ editorView.focus();
986
+ const headerCells = Array(cols).fill(null).map((_, i) => ` 列${i + 1} `).join("|");
987
+ const separator = Array(cols).fill(" --- ").join("|");
988
+ const dataCell = Array(cols).fill(" 内容 ").join("|");
989
+ const table = `\n|${headerCells}|\n|${separator}|\n${Array(rows).fill(`|${dataCell}|`).join("\n")}\n`;
990
+ const { from } = editorView.state.selection.main;
991
+ editorView.dispatch({ changes: {
992
+ from,
993
+ insert: table
994
+ } });
995
+ }, [editorView]);
996
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Dropdown, {
997
+ trigger: ["click"],
998
+ open,
999
+ onOpenChange: setOpen,
1000
+ popupRender: () => {
1001
+ openCountRef.current++;
1002
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1003
+ style: { padding: 4 },
1004
+ onClick: (e) => e.stopPropagation(),
1005
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TablePicker, { onSelect: (size) => {
1006
+ handleTableSelect(size.rows, size.cols);
1007
+ setOpen(false);
1008
+ } }, openCountRef.current)
1009
+ });
1010
+ },
1011
+ placement: "bottomLeft",
1012
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Button, {
1013
+ className: "toolbar-button",
1014
+ type: "text",
1015
+ size: "small",
1016
+ title: "表格",
1017
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ant_design_icons.TableOutlined, {})
1018
+ })
1019
+ });
923
1020
  }
924
-
925
- // src/components/Toolbar/index.tsx
926
- var import_jsx_runtime7 = require("react/jsx-runtime");
1021
+ //#endregion
1022
+ //#region src/components/Toolbar/index.tsx
927
1023
  function Toolbar({ editorView, fullscreen = true, onToggleFullscreen, extraItems, uploadConfig }) {
928
- const { config, handleClick } = useToolbar(editorView, { extraItems, uploadConfig });
929
- const renderFullscreen = (0, import_react6.useCallback)(() => {
930
- if (!onToggleFullscreen) return null;
931
- const Icon = fullscreen ? import_icons4.FullscreenExitOutlined : import_icons4.FullscreenOutlined;
932
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
933
- import_antd4.Button,
934
- {
935
- className: "toolbar-button",
936
- type: "text",
937
- size: "small",
938
- title: fullscreen ? "\u9000\u51FA\u5168\u5C4F" : "\u5168\u5C4F",
939
- onClick: () => {
940
- onToggleFullscreen();
941
- },
942
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, {})
943
- }
944
- );
945
- }, [fullscreen, onToggleFullscreen]);
946
- function isSeparator(item) {
947
- return !!(!item.id && item.separatorBefore);
948
- }
949
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "toolbar", children: [
950
- config.map((item, idx) => {
951
- if (isSeparator(item)) {
952
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "toolbar-separator" }, `sep-${idx}`);
953
- }
954
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
955
- ToolbarItemRenderer,
956
- {
957
- item,
958
- onClick: handleClick
959
- },
960
- item.id
961
- );
962
- }),
963
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TableDropdown, { editorView }),
964
- renderFullscreen()
965
- ] });
1024
+ const { config, extraItemsConfig, handleClick } = useToolbar(editorView, {
1025
+ extraItems,
1026
+ uploadConfig
1027
+ });
1028
+ const renderFullscreen = (0, react.useCallback)(() => {
1029
+ if (!onToggleFullscreen) return null;
1030
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Button, {
1031
+ className: "toolbar-button",
1032
+ type: "text",
1033
+ size: "small",
1034
+ title: fullscreen ? "退出全屏" : "全屏",
1035
+ onClick: () => {
1036
+ onToggleFullscreen();
1037
+ },
1038
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(fullscreen ? _ant_design_icons.FullscreenExitOutlined : _ant_design_icons.FullscreenOutlined, {})
1039
+ });
1040
+ }, [fullscreen, onToggleFullscreen]);
1041
+ function isSeparator(item) {
1042
+ return !!(!item.id && item.separatorBefore);
1043
+ }
1044
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1045
+ className: "toolbar",
1046
+ children: [
1047
+ config.map((item, idx) => {
1048
+ if (isSeparator(item)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "toolbar-separator" }, `sep-${idx}`);
1049
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarItemRenderer, {
1050
+ item,
1051
+ onClick: handleClick
1052
+ }, item.id);
1053
+ }),
1054
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TableDropdown, { editorView }),
1055
+ extraItemsConfig?.map((item, idx) => {
1056
+ if (isSeparator(item)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "toolbar-separator" }, `extra-sep-${idx}`);
1057
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarItemRenderer, {
1058
+ item,
1059
+ onClick: handleClick
1060
+ }, item.id);
1061
+ }),
1062
+ renderFullscreen()
1063
+ ]
1064
+ });
966
1065
  }
967
-
968
- // src/components/Dialog/EditDialog.tsx
969
- var import_react7 = require("react");
970
- var import_antd5 = require("antd");
971
- var import_jsx_runtime8 = require("react/jsx-runtime");
1066
+ //#endregion
1067
+ //#region src/components/Dialog/EditDialog.tsx
1068
+ /**
1069
+ * 属性编辑弹窗组件
1070
+ * 根据组件 schema 的 renderDialog 函数渲染弹窗内容
1071
+ */
972
1072
  function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
973
- const schema = componentRegistry_default.get(typeName);
974
- const [formProps, setFormProps] = (0, import_react7.useState)({});
975
- const mergedProps = (0, import_react7.useMemo)(() => {
976
- var _a;
977
- if (!schema) return currentProps;
978
- const defaults = {};
979
- for (const field of schema.fields) {
980
- defaults[field.key] = (_a = field.defaultValue) != null ? _a : "";
981
- }
982
- return { ...defaults, ...currentProps };
983
- }, [schema, currentProps]);
984
- const handleDialogChange = (key, value) => {
985
- setFormProps((prev) => ({ ...prev, [key]: value }));
986
- };
987
- const handleOk = () => {
988
- const finalProps = visible ? { ...mergedProps, ...formProps } : mergedProps;
989
- onSave(finalProps);
990
- setFormProps({});
991
- };
992
- const handleClose = () => {
993
- setFormProps({});
994
- onClose();
995
- };
996
- if (!schema) {
997
- return null;
998
- }
999
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1000
- import_antd5.Modal,
1001
- {
1002
- title: `${schema.label} \u5C5E\u6027\u7F16\u8F91`,
1003
- open: visible,
1004
- onOk: handleOk,
1005
- onCancel: handleClose,
1006
- okText: "\u4FDD\u5B58",
1007
- cancelText: "\u53D6\u6D88",
1008
- children: schema.renderDialog(visible ? { ...mergedProps, ...formProps } : mergedProps, handleDialogChange)
1009
- }
1010
- );
1073
+ const schema = componentRegistry.get(typeName);
1074
+ const [formProps, setFormProps] = (0, react.useState)({});
1075
+ const mergedProps = (0, react.useMemo)(() => {
1076
+ if (!schema) return currentProps;
1077
+ const defaults = {};
1078
+ for (const field of schema.fields || []) defaults[field.key] = field.defaultValue ?? "";
1079
+ return {
1080
+ ...defaults,
1081
+ ...currentProps
1082
+ };
1083
+ }, [schema, currentProps]);
1084
+ const handleDialogChange = (key, value) => {
1085
+ setFormProps((prev) => ({
1086
+ ...prev,
1087
+ [key]: value
1088
+ }));
1089
+ };
1090
+ const handleOk = () => {
1091
+ onSave(visible ? {
1092
+ ...mergedProps,
1093
+ ...formProps
1094
+ } : mergedProps);
1095
+ setFormProps({});
1096
+ };
1097
+ const handleClose = () => {
1098
+ setFormProps({});
1099
+ onClose();
1100
+ };
1101
+ if (!schema || !schema.renderDialog) return null;
1102
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Modal, {
1103
+ title: `${schema.label} 属性编辑`,
1104
+ open: visible,
1105
+ onOk: handleOk,
1106
+ onCancel: handleClose,
1107
+ okText: "保存",
1108
+ cancelText: "取消",
1109
+ children: schema.renderDialog(visible ? {
1110
+ ...mergedProps,
1111
+ ...formProps
1112
+ } : mergedProps, handleDialogChange)
1113
+ });
1011
1114
  }
1012
-
1013
- // src/utils/writeContainer.ts
1115
+ //#endregion
1116
+ //#region src/utils/writeContainer.ts
1117
+ /**
1118
+ * 从属性对象生成 JSON 属性行。
1119
+ * 格式: { key: 'value', key2: 'value2' }
1120
+ */
1014
1121
  function writeContainerProps(props) {
1015
- const entries = Object.entries(props).map(([k, v]) => `${k}: '${v}'`).join(", ");
1016
- return entries ? `{ ${entries} }` : "{}";
1122
+ const entries = Object.entries(props).map(([k, v]) => `${k}: '${v}'`).join(", ");
1123
+ return entries ? `{ ${entries} }` : "{}";
1017
1124
  }
1125
+ /**
1126
+ * 更新编辑器文档中的容器属性。
1127
+ * 替换 JSON 属性行(::: 打开行之后的一行)为新属性。
1128
+ */
1018
1129
  function updateContainerProps(view, propsLineFrom, propsLineTo, newProps) {
1019
- const newPropsLine = writeContainerProps(newProps);
1020
- view.dispatch({
1021
- changes: {
1022
- from: propsLineFrom,
1023
- to: propsLineTo,
1024
- insert: newPropsLine
1025
- }
1026
- });
1130
+ const newPropsLine = writeContainerProps(newProps);
1131
+ view.dispatch({ changes: {
1132
+ from: propsLineFrom,
1133
+ to: propsLineTo,
1134
+ insert: newPropsLine
1135
+ } });
1027
1136
  }
1137
+ /**
1138
+ * 在当前光标位置插入新容器。
1139
+ * 创建 :::type 打开行 + JSON 属性 + ::: 关闭行的结构。
1140
+ */
1028
1141
  function insertContainer(view, typeName, defaultProps) {
1029
- const { from } = view.state.selection.main;
1030
- const propsLine = writeContainerProps(defaultProps);
1031
- const container = `:::${typeName}
1032
- ${propsLine}
1033
- :::
1034
- `;
1035
- view.dispatch({ changes: { from, insert: container } });
1142
+ const { from } = view.state.selection.main;
1143
+ const container = `:::${typeName}\n${writeContainerProps(defaultProps)}\n:::\n`;
1144
+ view.dispatch({ changes: {
1145
+ from,
1146
+ insert: container
1147
+ } });
1036
1148
  }
1037
-
1038
- // src/MarkdownEditor.tsx
1039
- var import_icons5 = require("@ant-design/icons");
1040
-
1041
- // src/hooks/useFullscreen.ts
1042
- var import_react8 = require("react");
1149
+ //#endregion
1150
+ //#region src/hooks/useFullscreen.ts
1043
1151
  function useFullscreen() {
1044
- const [fullscreen, setFullscreen] = (0, import_react8.useState)(false);
1045
- const toggleFullscreen = (0, import_react8.useCallback)(() => {
1046
- if (document.fullscreenElement) {
1047
- document.exitFullscreen().catch((e) => {
1048
- console.error("\u9000\u51FA\u5168\u5C4F\u5931\u8D25:", e);
1049
- });
1050
- } else {
1051
- const el = document.documentElement;
1052
- const requestMethod = el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen || el.msRequestFullscreen;
1053
- if (requestMethod) {
1054
- requestMethod.call(el).catch((e) => {
1055
- console.error("\u8FDB\u5165\u5168\u5C4F\u5931\u8D25:", e);
1056
- });
1057
- }
1058
- }
1059
- }, []);
1060
- (0, import_react8.useEffect)(() => {
1061
- const handleFullscreenChange = () => {
1062
- setFullscreen(!!document.fullscreenElement);
1063
- };
1064
- document.addEventListener("fullscreenchange", handleFullscreenChange);
1065
- document.addEventListener("webkitfullscreenchange", handleFullscreenChange);
1066
- return () => {
1067
- document.removeEventListener("fullscreenchange", handleFullscreenChange);
1068
- document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
1069
- };
1070
- }, []);
1071
- return { fullscreen, toggleFullscreen };
1152
+ const [fullscreen, setFullscreen] = (0, react.useState)(false);
1153
+ const toggleFullscreen = (0, react.useCallback)(() => {
1154
+ if (document.fullscreenElement) document.exitFullscreen().catch((e) => {
1155
+ console.error("退出全屏失败:", e);
1156
+ });
1157
+ else {
1158
+ const el = document.documentElement;
1159
+ const requestMethod = el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen || el.msRequestFullscreen;
1160
+ if (requestMethod) requestMethod.call(el).catch((e) => {
1161
+ console.error("进入全屏失败:", e);
1162
+ });
1163
+ }
1164
+ }, []);
1165
+ (0, react.useEffect)(() => {
1166
+ const handleFullscreenChange = () => {
1167
+ setFullscreen(!!document.fullscreenElement);
1168
+ };
1169
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
1170
+ document.addEventListener("webkitfullscreenchange", handleFullscreenChange);
1171
+ return () => {
1172
+ document.removeEventListener("fullscreenchange", handleFullscreenChange);
1173
+ document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
1174
+ };
1175
+ }, []);
1176
+ return {
1177
+ fullscreen,
1178
+ toggleFullscreen
1179
+ };
1072
1180
  }
1073
-
1074
- // src/hooks/useScrollSync.ts
1075
- var import_react9 = require("react");
1181
+ //#endregion
1182
+ //#region src/hooks/useScrollSync.ts
1183
+ /**
1184
+ * 滚动同步 Hook
1185
+ * 将源滚动容器的滚动位置同步到目标预览容器
1186
+ */
1076
1187
  function useScrollSync(sourceRef, targetRef) {
1077
- const rafIdRef = (0, import_react9.useRef)(null);
1078
- (0, import_react9.useEffect)(() => {
1079
- const source = sourceRef.current;
1080
- const target = targetRef.current;
1081
- if (!source || !target) return;
1082
- const listener = () => {
1083
- if (rafIdRef.current) return;
1084
- rafIdRef.current = requestAnimationFrame(() => {
1085
- const s = sourceRef.current;
1086
- const t = targetRef.current;
1087
- if (!s || !t) {
1088
- rafIdRef.current = null;
1089
- return;
1090
- }
1091
- const sourceScrollHeight = s.scrollHeight - s.clientHeight;
1092
- if (sourceScrollHeight <= 0) {
1093
- rafIdRef.current = null;
1094
- return;
1095
- }
1096
- const ratio = s.scrollTop / sourceScrollHeight;
1097
- const targetScrollHeight = t.scrollHeight - t.clientHeight;
1098
- if (targetScrollHeight > 0) {
1099
- t.scrollTop = ratio * targetScrollHeight;
1100
- }
1101
- rafIdRef.current = null;
1102
- });
1103
- };
1104
- source.addEventListener("scroll", listener, { passive: true });
1105
- return () => {
1106
- source.removeEventListener("scroll", listener);
1107
- if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
1108
- };
1109
- }, [sourceRef, targetRef]);
1188
+ const rafIdRef = (0, react.useRef)(null);
1189
+ (0, react.useEffect)(() => {
1190
+ const source = sourceRef.current;
1191
+ const target = targetRef.current;
1192
+ if (!source || !target) return;
1193
+ const listener = () => {
1194
+ if (rafIdRef.current) return;
1195
+ rafIdRef.current = requestAnimationFrame(() => {
1196
+ const s = sourceRef.current;
1197
+ const t = targetRef.current;
1198
+ if (!s || !t) {
1199
+ rafIdRef.current = null;
1200
+ return;
1201
+ }
1202
+ const sourceScrollHeight = s.scrollHeight - s.clientHeight;
1203
+ if (sourceScrollHeight <= 0) {
1204
+ rafIdRef.current = null;
1205
+ return;
1206
+ }
1207
+ const ratio = s.scrollTop / sourceScrollHeight;
1208
+ const targetScrollHeight = t.scrollHeight - t.clientHeight;
1209
+ if (targetScrollHeight > 0) t.scrollTop = ratio * targetScrollHeight;
1210
+ rafIdRef.current = null;
1211
+ });
1212
+ };
1213
+ source.addEventListener("scroll", listener, { passive: true });
1214
+ return () => {
1215
+ source.removeEventListener("scroll", listener);
1216
+ if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
1217
+ };
1218
+ }, [sourceRef, targetRef]);
1110
1219
  }
1111
-
1112
- // src/MarkdownEditor.tsx
1113
- var import_jsx_runtime9 = require("react/jsx-runtime");
1114
- var MarkdownEditor_default = (0, import_react10.forwardRef)(function MarkdownEditor({
1115
- defaultValue = "",
1116
- value,
1117
- onChange,
1118
- showPreview = true,
1119
- fullscreen: fullscreenProp = false,
1120
- previewOptions,
1121
- uploadConfig
1122
- }, ref) {
1123
- const [content, setContent] = (0, import_react10.useState)(value != null ? value : defaultValue);
1124
- const [editorView, setEditorView] = (0, import_react10.useState)(null);
1125
- const scrollDOMRef = (0, import_react10.useRef)(null);
1126
- const previewElRef = (0, import_react10.useRef)(null);
1127
- const { fullscreen, toggleFullscreen } = useFullscreen();
1128
- useScrollSync(scrollDOMRef, previewElRef);
1129
- const [dialogVisible, setDialogVisible] = (0, import_react10.useState)(false);
1130
- const [dialogTypeName, setDialogTypeName] = (0, import_react10.useState)("");
1131
- const [dialogProps, setDialogProps] = (0, import_react10.useState)({});
1132
- const [dialogPropsRange, setDialogPropsRange] = (0, import_react10.useState)([0, 0]);
1133
- (0, import_react10.useEffect)(() => {
1134
- if (value !== void 0) {
1135
- setContent(value);
1136
- }
1137
- }, [value]);
1138
- const internalSetContent = (0, import_react10.useCallback)((newContent) => {
1139
- setContent(newContent);
1140
- onChange == null ? void 0 : onChange(newContent);
1141
- }, [onChange]);
1142
- (0, import_react10.useImperativeHandle)(ref, () => ({
1143
- getContent: () => content
1144
- }), [content]);
1145
- const extraToolbarItems = componentRegistry_default.getAll().map((schema) => {
1146
- var _a, _b, _c;
1147
- const defaultProps = {};
1148
- for (const field of (_a = schema == null ? void 0 : schema.fields) != null ? _a : []) {
1149
- defaultProps[field.key] = (_b = field.defaultValue) != null ? _b : "";
1150
- }
1151
- const iconNode = (_c = schema.icon) != null ? _c : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_icons5.ReadOutlined, {});
1152
- return {
1153
- id: `container-${schema.name}`,
1154
- label: schema.label,
1155
- icon: () => iconNode,
1156
- handler: (view, _state) => insertContainer(view, schema.name, defaultProps)
1157
- };
1158
- });
1159
- const handleScrollReady = (0, import_react10.useCallback)((scrollDOM) => {
1160
- scrollDOMRef.current = scrollDOM;
1161
- }, []);
1162
- const handlePreviewRef = (0, import_react10.useCallback)((node) => {
1163
- previewElRef.current = node;
1164
- }, []);
1165
- const handleViewReady = (0, import_react10.useCallback)((view) => {
1166
- setEditorView(view);
1167
- }, []);
1168
- const handleEditContainer = (0, import_react10.useCallback)((_view, typeName, propsLineFrom, propsLineTo, currentProps) => {
1169
- setDialogTypeName(typeName);
1170
- setDialogProps({ ...currentProps });
1171
- setDialogPropsRange([propsLineFrom, propsLineTo]);
1172
- setDialogVisible(true);
1173
- }, []);
1174
- const handleDialogSave = (0, import_react10.useCallback)((newProps) => {
1175
- setDialogVisible(false);
1176
- if (editorView) {
1177
- updateContainerProps(editorView, dialogPropsRange[0], dialogPropsRange[1], newProps);
1178
- }
1179
- }, [editorView, dialogPropsRange]);
1180
- const handleDialogClose = (0, import_react10.useCallback)(() => {
1181
- setDialogVisible(false);
1182
- }, []);
1183
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: fullscreen ? "app fullscreen" : "app", children: [
1184
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1185
- Toolbar,
1186
- {
1187
- editorView,
1188
- fullscreen,
1189
- onToggleFullscreen: fullscreenProp ? toggleFullscreen : void 0,
1190
- extraItems: extraToolbarItems,
1191
- uploadConfig
1192
- }
1193
- ),
1194
- showPreview ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1195
- SplitPanels,
1196
- {
1197
- leftPanel: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1198
- Editor,
1199
- {
1200
- value: content,
1201
- onChange: internalSetContent,
1202
- onScrollReady: handleScrollReady,
1203
- onViewReady: handleViewReady,
1204
- onEditContainer: handleEditContainer,
1205
- uploadConfig,
1206
- onMessageNoUploadConfig: () => import_antd6.message.warning("\u8BF7\u5148\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u529F\u80FD")
1207
- }
1208
- ),
1209
- rightPanel: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1210
- Preview_default,
1211
- {
1212
- content,
1213
- ref: handlePreviewRef,
1214
- previewOptions
1215
- }
1216
- )
1217
- }
1218
- ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1219
- Editor,
1220
- {
1221
- value: content,
1222
- onChange: internalSetContent,
1223
- onScrollReady: handleScrollReady,
1224
- onViewReady: handleViewReady,
1225
- onEditContainer: handleEditContainer,
1226
- uploadConfig,
1227
- onMessageNoUploadConfig: () => import_antd6.message.warning("\u8BF7\u5148\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u529F\u80FD")
1228
- }
1229
- ),
1230
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1231
- EditDialog,
1232
- {
1233
- visible: dialogVisible,
1234
- typeName: dialogTypeName,
1235
- currentProps: dialogProps,
1236
- onSave: handleDialogSave,
1237
- onClose: handleDialogClose
1238
- }
1239
- )
1240
- ] });
1220
+ //#endregion
1221
+ //#region src/MarkdownEditor.tsx
1222
+ var MarkdownEditor_default = (0, react.forwardRef)(function MarkdownEditor({ defaultValue = "", value, onChange, showPreview = true, fullscreen: fullscreenProp = false, previewOptions, uploadConfig }, ref) {
1223
+ const [content, setContent] = (0, react.useState)(value ?? defaultValue);
1224
+ const [editorView, setEditorView] = (0, react.useState)(null);
1225
+ const scrollDOMRef = (0, react.useRef)(null);
1226
+ const previewElRef = (0, react.useRef)(null);
1227
+ const { fullscreen, toggleFullscreen } = useFullscreen();
1228
+ useScrollSync(scrollDOMRef, previewElRef);
1229
+ const [dialogVisible, setDialogVisible] = (0, react.useState)(false);
1230
+ const [dialogTypeName, setDialogTypeName] = (0, react.useState)("");
1231
+ const [dialogProps, setDialogProps] = (0, react.useState)({});
1232
+ const [dialogPropsRange, setDialogPropsRange] = (0, react.useState)([0, 0]);
1233
+ (0, react.useEffect)(() => {
1234
+ if (value !== void 0) setContent(value);
1235
+ }, [value]);
1236
+ const internalSetContent = (0, react.useCallback)((newContent) => {
1237
+ setContent(newContent);
1238
+ onChange?.(newContent);
1239
+ }, [onChange]);
1240
+ (0, react.useImperativeHandle)(ref, () => ({ getContent: () => content }), [content]);
1241
+ const extraToolbarItems = componentRegistry.getAll().map((schema) => {
1242
+ const defaultProps = {};
1243
+ for (const field of schema?.fields ?? []) defaultProps[field.key] = field.defaultValue ?? "";
1244
+ const iconNode = schema.icon ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ant_design_icons.ReadOutlined, {});
1245
+ return {
1246
+ id: `container-${schema.name}`,
1247
+ label: schema.label,
1248
+ icon: () => iconNode,
1249
+ handler: (view, _state) => insertContainer(view, schema.name, defaultProps)
1250
+ };
1251
+ });
1252
+ const handleScrollReady = (0, react.useCallback)((scrollDOM) => {
1253
+ scrollDOMRef.current = scrollDOM;
1254
+ }, []);
1255
+ const handlePreviewRef = (0, react.useCallback)((node) => {
1256
+ previewElRef.current = node;
1257
+ }, []);
1258
+ const handleViewReady = (0, react.useCallback)((view) => {
1259
+ setEditorView(view);
1260
+ }, []);
1261
+ const handleEditContainer = (0, react.useCallback)((_view, typeName, propsLineFrom, propsLineTo, currentProps) => {
1262
+ setDialogTypeName(typeName);
1263
+ setDialogProps({ ...currentProps });
1264
+ setDialogPropsRange([propsLineFrom, propsLineTo]);
1265
+ setDialogVisible(true);
1266
+ }, []);
1267
+ const handleDialogSave = (0, react.useCallback)((newProps) => {
1268
+ setDialogVisible(false);
1269
+ if (editorView) updateContainerProps(editorView, dialogPropsRange[0], dialogPropsRange[1], newProps);
1270
+ }, [editorView, dialogPropsRange]);
1271
+ const handleDialogClose = (0, react.useCallback)(() => {
1272
+ setDialogVisible(false);
1273
+ }, []);
1274
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1275
+ className: fullscreen ? "app fullscreen" : "app",
1276
+ children: [
1277
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toolbar, {
1278
+ editorView,
1279
+ fullscreen,
1280
+ onToggleFullscreen: fullscreenProp ? toggleFullscreen : void 0,
1281
+ extraItems: extraToolbarItems,
1282
+ uploadConfig
1283
+ }),
1284
+ showPreview ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SplitPanels, {
1285
+ leftPanel: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Editor, {
1286
+ value: content,
1287
+ onChange: internalSetContent,
1288
+ onScrollReady: handleScrollReady,
1289
+ onViewReady: handleViewReady,
1290
+ onEditContainer: handleEditContainer,
1291
+ uploadConfig,
1292
+ onMessageNoUploadConfig: () => antd.message.warning("请先配置图片上传功能")
1293
+ }),
1294
+ rightPanel: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Preview, {
1295
+ content,
1296
+ ref: handlePreviewRef,
1297
+ previewOptions
1298
+ })
1299
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Editor, {
1300
+ value: content,
1301
+ onChange: internalSetContent,
1302
+ onScrollReady: handleScrollReady,
1303
+ onViewReady: handleViewReady,
1304
+ onEditContainer: handleEditContainer,
1305
+ uploadConfig,
1306
+ onMessageNoUploadConfig: () => antd.message.warning("请先配置图片上传功能")
1307
+ }),
1308
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditDialog, {
1309
+ visible: dialogVisible,
1310
+ typeName: dialogTypeName,
1311
+ currentProps: dialogProps,
1312
+ onSave: handleDialogSave,
1313
+ onClose: handleDialogClose
1314
+ })
1315
+ ]
1316
+ });
1241
1317
  });
1242
-
1243
- // src/hooks/useRegisterToolbar.ts
1244
- var import_react11 = require("react");
1318
+ //#endregion
1319
+ //#region src/hooks/useRegisterToolbar.ts
1320
+ /**
1321
+ * 注册自定义容器组件到工具栏。支持单个或批量注册。
1322
+ * 支持两种模式:
1323
+ *
1324
+ * 1. 容器模式(linkCard):name + fields + renderDialog
1325
+ * 插入 :::name 格式的容器,支持弹窗编辑属性
1326
+ *
1327
+ * 2. 标签包裹模式:name + wrapTags
1328
+ * 插入简单的标签包裹文本,如 <plugin-container>...</plugin-container>
1329
+ *
1330
+ * @example 容器模式
1331
+ * ```tsx
1332
+ * const schema: ComponentSchema = {
1333
+ * name: 'alert',
1334
+ * label: '警告框',
1335
+ * icon: <AlertOutlined />,
1336
+ * fields: [{ key: 'level', defaultValue: 'info' }],
1337
+ * renderDialog: (props, onChange) => (
1338
+ * <Input value={props.level} onChange={e => onChange('level', e.target.value)} />
1339
+ * ),
1340
+ * }
1341
+ *
1342
+ * function MyPage() {
1343
+ * useRegisterToolbar(schema)
1344
+ * return <MarkdownEditor />
1345
+ * }
1346
+ * ```
1347
+ *
1348
+ * @example 标签包裹模式
1349
+ * ```tsx
1350
+ * function MyPage() {
1351
+ * useRegisterToolbar({
1352
+ * name: 'plugin-container',
1353
+ * label: '自定义容器',
1354
+ * icon: <AlertOutlined />,
1355
+ * wrapTags: ['<plugin-container>', '</plugin-container>'],
1356
+ * })
1357
+ * return <MarkdownEditor />
1358
+ * }
1359
+ * ```
1360
+ *
1361
+ * @example 批量注册
1362
+ * ```tsx
1363
+ * function MyPage() {
1364
+ * useRegisterToolbar([schema1, schema2, schema3])
1365
+ * return <MarkdownEditor />
1366
+ * }
1367
+ * ```
1368
+ */
1245
1369
  function useRegisterToolbar(schemas) {
1246
- const schemaList = Array.isArray(schemas) ? schemas : [schemas];
1247
- (0, import_react11.useEffect)(() => {
1248
- schemaList.forEach((schema) => {
1249
- componentRegistry_default.register(schema.name, schema);
1250
- });
1251
- return () => {
1252
- schemaList.forEach((schema) => {
1253
- componentRegistry_default.unregister(schema.name);
1254
- });
1255
- };
1256
- }, [schemaList]);
1370
+ const schemaList = Array.isArray(schemas) ? schemas : [schemas];
1371
+ (0, react.useEffect)(() => {
1372
+ schemaList.forEach((schema) => {
1373
+ componentRegistry.register(schema.name, schema);
1374
+ });
1375
+ return () => {
1376
+ schemaList.forEach((schema) => {
1377
+ componentRegistry.unregister(schema.name);
1378
+ });
1379
+ };
1380
+ }, [schemaList]);
1257
1381
  }
1258
- // Annotate the CommonJS export names for ESM import in node:
1259
- 0 && (module.exports = {
1260
- MarkdownEditor,
1261
- useRegisterToolbar
1262
- });
1382
+ //#endregion
1383
+ exports.MarkdownEditor = MarkdownEditor_default;
1384
+ exports.useRegisterToolbar = useRegisterToolbar;
1385
+
1263
1386
  //# sourceMappingURL=index.cjs.map