remons-markdown-editor 1.0.0-beta.2 → 1.0.0-beta.21

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 (34) 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/Icon/index.d.ts +2 -0
  8. package/dist/components/Panel/SplitPanels.d.ts +7 -0
  9. package/dist/components/Preview/index.d.ts +10 -0
  10. package/dist/components/Toolbar/buttons.d.ts +59 -0
  11. package/dist/components/Toolbar/config.d.ts +6 -0
  12. package/dist/components/Toolbar/hooks/useToolbar.d.ts +14 -0
  13. package/dist/components/Toolbar/index.d.ts +13 -0
  14. package/dist/components/Toolbar/renderers/TablePicker/TableDropdown.d.ts +5 -0
  15. package/dist/components/Toolbar/renderers/TablePicker/index.d.ts +9 -0
  16. package/dist/components/Toolbar/renderers/ToolbarItemRenderer.d.ts +7 -0
  17. package/dist/hooks/useFullscreen.d.ts +4 -0
  18. package/dist/hooks/useRegisterToolbar.d.ts +101 -0
  19. package/dist/hooks/useScrollSync.d.ts +6 -0
  20. package/dist/index.cjs +1612 -1202
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.css +184 -113
  23. package/dist/index.d.ts +5 -98
  24. package/dist/index.js +1588 -1182
  25. package/dist/index.js.map +1 -1
  26. package/dist/main.d.ts +0 -0
  27. package/dist/registry/componentRegistry.d.ts +34 -0
  28. package/dist/types/index.d.ts +148 -0
  29. package/dist/utils/parseContainer.d.ts +18 -0
  30. package/dist/utils/writeContainer.d.ts +16 -0
  31. package/package.json +77 -81
  32. package/readme.md +309 -91
  33. package/dist/index.css.map +0 -1
  34. package/dist/index.d.cts +0 -98
package/dist/index.cjs CHANGED
@@ -1,1254 +1,1664 @@
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_react8 = require("react");
39
-
40
- // src/components/Editor/index.tsx
41
- var import_react = require("react");
42
- var import_codemirror = require("codemirror");
43
- var import_state2 = require("@codemirror/state");
44
-
45
- // src/components/Editor/codemirrorExtensions.ts
46
- var import_view2 = require("@codemirror/view");
47
- var import_commands = require("@codemirror/commands");
48
- var import_lang_markdown = require("@codemirror/lang-markdown");
49
- var import_theme_one_dark = require("@codemirror/theme-one-dark");
50
-
51
- // src/components/Editor/containerWidget.ts
52
- var import_view = require("@codemirror/view");
53
- var import_state = require("@codemirror/state");
54
-
55
- // 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
+ */
56
43
  function parseContainerProps(jsonStr) {
57
- var _a, _b, _c;
58
- try {
59
- const props = {};
60
- const trimmed = jsonStr.trim();
61
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
62
- const inner = trimmed.slice(1, -1).trim();
63
- if (!inner) return props;
64
- const matches = inner.matchAll(/(?:["']?)(\w+)(?:["']?)\s*:\s*(?:"([^"]*)"|'([^']*)'|(\d+))/g);
65
- for (const m of matches) {
66
- props[m[1]] = (_c = (_b = (_a = m[2]) != null ? _a : m[3]) != null ? _b : m[4]) != null ? _c : "";
67
- }
68
- }
69
- return props;
70
- } catch (e) {
71
- return {};
72
- }
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
+ }
73
57
  }
74
58
  function findContainers(docText) {
75
- const containers = [];
76
- const lines = docText.split("\n");
77
- let i = 0;
78
- while (i < lines.length) {
79
- const line = lines[i];
80
- const match = line.match(/^:::(\w+)$/);
81
- if (match) {
82
- const typeName = match[1];
83
- let charPos = 0;
84
- for (let l = 0; l < i; l++) {
85
- charPos += lines[l].length + 1;
86
- }
87
- let props = {};
88
- let propsLineFrom = 0;
89
- let propsLineTo = 0;
90
- let propsLineIndex = i + 1;
91
- if (propsLineIndex < lines.length) {
92
- const propsLine = lines[propsLineIndex].trim();
93
- const propsMatch = propsLine.match(/^\{[\s\S]*\}$/);
94
- if (propsMatch) {
95
- props = parseContainerProps(propsLine);
96
- propsLineFrom = charPos + line.length + 1;
97
- propsLineTo = propsLineFrom + lines[propsLineIndex].length;
98
- }
99
- }
100
- let endPos = 0;
101
- for (let j = i + 1; j < lines.length; j++) {
102
- if (lines[j].trim() === ":::") {
103
- let endCharPos = 0;
104
- for (let l = 0; l < j; l++) {
105
- endCharPos += lines[l].length + 1;
106
- }
107
- endPos = endCharPos;
108
- i = j + 1;
109
- break;
110
- }
111
- }
112
- containers.push({
113
- startPos: charPos,
114
- typeName,
115
- propsLineFrom,
116
- propsLineTo,
117
- props,
118
- endPos
119
- });
120
- }
121
- i++;
122
- }
123
- 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;
124
101
  }
125
-
126
- // src/registry/componentRegistry.tsx
127
- var import_antd = require("antd");
128
- var import_jsx_runtime = require("react/jsx-runtime");
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
- componentRegistry.register("linkCard", {
172
- name: "linkCard",
173
- label: "\u94FE\u63A5\u5361\u7247",
174
- fields: [
175
- { key: "title", defaultValue: "\u94FE\u63A5\u6807\u9898" },
176
- { key: "link", defaultValue: "https://example.com" },
177
- { key: "description", defaultValue: "\u94FE\u63A5\u63CF\u8FF0" },
178
- { key: "theme", defaultValue: "default" }
179
- ],
180
- renderDialog: (props, onChange) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
181
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 12 }, children: [
182
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: 4 }, children: "\u6807\u9898" }),
183
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Input, { value: props.title, onChange: (e) => onChange("title", e.target.value) })
184
- ] }),
185
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 12 }, children: [
186
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: 4 }, children: "\u94FE\u63A5" }),
187
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Input, { value: props.link, onChange: (e) => onChange("link", e.target.value) })
188
- ] }),
189
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 12 }, children: [
190
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: 4 }, children: "\u63CF\u8FF0" }),
191
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Input.TextArea, { rows: 3, value: props.description, onChange: (e) => onChange("description", e.target.value) })
192
- ] }),
193
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
194
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: 4 }, children: "\u4E3B\u9898" }),
195
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
196
- import_antd.Select,
197
- {
198
- value: props.theme,
199
- onChange: (v) => onChange("theme", v),
200
- options: [
201
- { label: "\u9ED8\u8BA4", value: "default" },
202
- { label: "\u4E3B\u8981", value: "primary" },
203
- { label: "\u8B66\u544A", value: "warning" }
204
- ]
205
- }
206
- )
207
- ] })
208
- ] })
209
- });
210
- componentRegistry.register("note", {
211
- name: "note",
212
- label: "\u6CE8\u91CA",
213
- fields: [
214
- { key: "type", defaultValue: "info" },
215
- { key: "title", defaultValue: "\u6CE8\u610F" },
216
- { key: "content", defaultValue: "\u5907\u6CE8\u5185\u5BB9" }
217
- ],
218
- renderDialog: (props, onChange) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
219
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 12 }, children: [
220
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: 4 }, children: "\u7C7B\u578B" }),
221
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
222
- import_antd.Select,
223
- {
224
- value: props.type,
225
- onChange: (v) => onChange("type", v),
226
- options: [
227
- { label: "\u4FE1\u606F", value: "info" },
228
- { label: "\u63D0\u793A", value: "tip" },
229
- { label: "\u8B66\u544A", value: "warning" },
230
- { label: "\u5371\u9669", value: "danger" }
231
- ]
232
- }
233
- )
234
- ] }),
235
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 12 }, children: [
236
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: 4 }, children: "\u6807\u9898" }),
237
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Input, { value: props.title, onChange: (e) => onChange("title", e.target.value) })
238
- ] }),
239
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
240
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: 4 }, children: "\u5185\u5BB9" }),
241
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Input.TextArea, { rows: 3, value: props.content, onChange: (e) => onChange("content", e.target.value) })
242
- ] })
243
- ] })
244
- });
245
- var componentRegistry_default = componentRegistry;
246
-
247
- // src/components/Editor/containerWidget.ts
248
- var containerEditCallback = import_state.Facet.define({
249
- combine: (values) => values[0]
250
- });
251
- var ContainerHeaderWidget = class extends import_view.WidgetType {
252
- constructor(typeName) {
253
- super();
254
- this.typeName = typeName;
255
- }
256
- toDOM() {
257
- var _a, _b;
258
- const container = document.createElement("span");
259
- container.className = "container-widget-header";
260
- const text = document.createElement("span");
261
- text.className = "container-widget-text";
262
- text.textContent = `:::${this.typeName}`;
263
- const button = document.createElement("button");
264
- button.className = "container-edit-btn";
265
- button.title = `\u7F16\u8F91 ${(_b = (_a = componentRegistry_default.get(this.typeName)) == null ? void 0 : _a.label) != null ? _b : this.typeName}`;
266
- button.textContent = "\u270E";
267
- button.__typeName = this.typeName;
268
- container.appendChild(text);
269
- container.appendChild(button);
270
- return container;
271
- }
272
- eq(other) {
273
- return this.typeName === other.typeName;
274
- }
275
- ignoreEvent() {
276
- return false;
277
- }
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
+ }
278
185
  };
279
- var ContainerPropsWidget = class extends import_view.WidgetType {
280
- constructor(propsText) {
281
- super();
282
- this.propsText = propsText;
283
- }
284
- toDOM() {
285
- const span = document.createElement("span");
286
- span.className = "container-widget-props";
287
- span.textContent = this.propsText;
288
- return span;
289
- }
290
- eq(other) {
291
- return this.propsText === other.propsText;
292
- }
293
- ignoreEvent() {
294
- return false;
295
- }
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
+ }
296
208
  };
297
- var ContainerCloseWidget = class extends import_view.WidgetType {
298
- toDOM() {
299
- const span = document.createElement("span");
300
- span.className = "container-widget-text";
301
- span.textContent = ":::";
302
- return span;
303
- }
304
- eq() {
305
- return true;
306
- }
307
- ignoreEvent() {
308
- return false;
309
- }
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
+ }
310
226
  };
227
+ /**
228
+ * 查找文档中的容器,创建 Decoration 装饰物
229
+ * 将 ::: 头行、{} 属性行和 ::: 结束行替换为只读 widget,使其无法在编辑器中直接编辑
230
+ */
311
231
  function findContainerDecorations(state) {
312
- const docText = state.doc.toString();
313
- const containers = findContainers(docText);
314
- const decorations = [];
315
- for (const container of containers) {
316
- const headerLine = state.doc.lineAt(container.startPos);
317
- decorations.push({
318
- from: headerLine.from,
319
- to: headerLine.to,
320
- value: import_view.Decoration.replace({
321
- widget: new ContainerHeaderWidget(container.typeName)
322
- })
323
- });
324
- if (container.propsLineFrom > 0 && container.propsLineTo > 0) {
325
- const propsLine = state.doc.lineAt(container.propsLineFrom);
326
- const propsText = propsLine.text;
327
- decorations.push({
328
- from: propsLine.from,
329
- to: propsLine.to,
330
- value: import_view.Decoration.replace({
331
- widget: new ContainerPropsWidget(propsText)
332
- })
333
- });
334
- }
335
- if (container.endPos > 0) {
336
- const endLine = state.doc.lineAt(container.endPos);
337
- decorations.push({
338
- from: endLine.from,
339
- to: endLine.to,
340
- value: import_view.Decoration.replace({
341
- widget: new ContainerCloseWidget()
342
- })
343
- });
344
- }
345
- }
346
- 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;
347
260
  }
348
- var containerWidgetPlugin = import_view.ViewPlugin.fromClass(
349
- class {
350
- constructor(view) {
351
- this.decorations = import_view.Decoration.set(findContainerDecorations(view.state), true);
352
- }
353
- update(update) {
354
- if (update.docChanged || update.selectionSet) {
355
- this.decorations = import_view.Decoration.set(findContainerDecorations(update.state), true);
356
- }
357
- }
358
- },
359
- {
360
- decorations: (value) => value.decorations,
361
- eventHandlers: {
362
- // 点击编辑按钮时触发回调
363
- click: (event, view) => {
364
- const target = event.target;
365
- if (target.classList.contains("container-edit-btn")) {
366
- const typeName = target.__typeName;
367
- if (typeName) {
368
- const docText = view.state.doc.toString();
369
- const containers = findContainers(docText);
370
- const buttonPos = view.posAtDOM(target);
371
- for (const container of containers) {
372
- const headerLine = view.state.doc.lineAt(container.startPos);
373
- if (buttonPos >= headerLine.from - 5 && buttonPos <= headerLine.to + 5) {
374
- const callback = view.state.facet(containerEditCallback);
375
- if (callback) {
376
- callback(view, container.typeName, container.propsLineFrom, container.propsLineTo, { ...container.props });
377
- }
378
- return true;
379
- }
380
- }
381
- }
382
- return false;
383
- }
384
- return false;
385
- }
386
- }
387
- }
388
- );
389
-
390
- // src/components/Editor/codemirrorExtensions.ts
391
- var getCodeMirrorExtensions = (opts) => {
392
- const extensions = [
393
- (0, import_lang_markdown.markdown)(),
394
- import_theme_one_dark.oneDark,
395
- import_view2.keymap.of([...import_commands.defaultKeymap, import_commands.indentWithTab]),
396
- import_view2.EditorView.lineWrapping,
397
- import_view2.EditorView.baseTheme({
398
- ".cm-scroller": {
399
- height: "100%",
400
- overflow: "auto"
401
- },
402
- // 容器头行 Widget 样式
403
- ".container-widget-header": {
404
- display: "inline",
405
- fontSize: "inherit",
406
- fontFamily: "inherit"
407
- },
408
- ".container-widget-text": {
409
- fontWeight: "bold",
410
- color: "#79c0ff"
411
- },
412
- // 容器属性行 Widget 样式
413
- ".container-widget-props": {
414
- display: "inline",
415
- fontSize: "inherit",
416
- fontFamily: "inherit",
417
- color: "#a5d6ff"
418
- },
419
- // 容器编辑按钮样式
420
- ".container-edit-btn": {
421
- display: "inline-block",
422
- padding: "0 4px",
423
- fontSize: "12px",
424
- color: "#58a6ff",
425
- background: "transparent",
426
- border: "none",
427
- borderRadius: "4px",
428
- cursor: "pointer",
429
- marginRight: "4px",
430
- verticalAlign: "middle"
431
- },
432
- ".container-edit-btn:hover": {
433
- background: "rgba(88, 166, 255, 0.16)"
434
- }
435
- })
436
- ];
437
- extensions.push(containerWidgetPlugin);
438
- if (opts == null ? void 0 : opts.onEditContainer) {
439
- extensions.push(containerEditCallback.of(opts.onEditContainer));
440
- }
441
- return extensions;
442
- };
443
-
444
- // src/components/Editor/index.tsx
445
- var import_jsx_runtime2 = require("react/jsx-runtime");
446
- function Editor({ value, onChange, onScrollReady, onViewReady, onEditContainer }) {
447
- const editorRef = (0, import_react.useRef)(null);
448
- const viewRef = (0, import_react.useRef)(null);
449
- (0, import_react.useEffect)(() => {
450
- if (!editorRef.current) return;
451
- const extensions = [
452
- import_codemirror.basicSetup,
453
- ...getCodeMirrorExtensions({ onEditContainer }),
454
- import_codemirror.EditorView.updateListener.of((update) => {
455
- if (update.docChanged) {
456
- onChange(update.state.doc.toString());
457
- }
458
- })
459
- ];
460
- const state = import_state2.EditorState.create({
461
- doc: value,
462
- extensions
463
- });
464
- viewRef.current = new import_codemirror.EditorView({
465
- state,
466
- parent: editorRef.current
467
- });
468
- if (onScrollReady && viewRef.current) {
469
- onScrollReady(viewRef.current.scrollDOM);
470
- }
471
- if (onViewReady) {
472
- onViewReady(viewRef.current);
473
- }
474
- return () => {
475
- var _a;
476
- (_a = viewRef.current) == null ? void 0 : _a.destroy();
477
- };
478
- }, []);
479
- (0, import_react.useEffect)(() => {
480
- if (viewRef.current && viewRef.current.state.doc.toString() !== value) {
481
- viewRef.current.dispatch({
482
- changes: { from: 0, to: viewRef.current.state.doc.length, insert: value }
483
- });
484
- }
485
- }, [value]);
486
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: editorRef, className: "editor" });
487
- }
488
-
489
- // src/components/Preview/index.tsx
490
- var import_react2 = require("react");
491
- var import_remons_markdown_plugins = require("remons-markdown-plugins");
492
- var import_remons_render_markdown = __toESM(require("remons-render-markdown"), 1);
493
- var import_style = require("remons-markdown-plugins/style.css");
494
- var import_dist = require("remons-render-markdown/dist/index.css");
495
- var import_jsx_runtime3 = require("react/jsx-runtime");
496
- (0, import_remons_render_markdown.initHighlighter)(import_remons_render_markdown.languagesCommon);
497
- var Preview = (0, import_react2.forwardRef)(({ content }, ref) => {
498
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref, className: "preview", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_remons_render_markdown.default, { content, isSlotMermaid: true, excludedSelectors: import_remons_markdown_plugins.excludedSelectors, customRenderers: [(md) => md.use(import_remons_markdown_plugins.registerAll)] }) });
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: {
276
+ mousedown: (event, view) => {
277
+ if (event.target.classList.contains("container-edit-btn")) {
278
+ event.preventDefault();
279
+ return true;
280
+ }
281
+ return false;
282
+ },
283
+ click: (event, view) => {
284
+ const target = event.target;
285
+ if (target.classList.contains("container-edit-btn")) {
286
+ event.preventDefault();
287
+ event.stopPropagation();
288
+ if (target.__typeName) {
289
+ const containers = findContainers(view.state.doc.toString());
290
+ const buttonPos = view.posAtDOM(target);
291
+ for (const container of containers) {
292
+ const headerLine = view.state.doc.lineAt(container.startPos);
293
+ if (buttonPos >= headerLine.from - 5 && buttonPos <= headerLine.to + 5) {
294
+ const callback = view.state.facet(containerEditCallback);
295
+ if (callback) callback(view, container.typeName, container.propsLineFrom, container.propsLineTo, { ...container.props });
296
+ return true;
297
+ }
298
+ }
299
+ }
300
+ return false;
301
+ }
302
+ return false;
303
+ }
304
+ }
499
305
  });
500
- Preview.displayName = "Preview";
501
- var Preview_default = Preview;
502
-
503
- // src/components/Panel/SplitPanels.tsx
504
- var import_antd2 = require("antd");
505
- var import_jsx_runtime4 = require("react/jsx-runtime");
506
- function SplitPanels({ leftPanel, rightPanel }) {
507
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_antd2.Splitter, { className: "split-panels", children: [
508
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_antd2.Splitter.Panel, { defaultSize: "50%", min: "20%", max: "80%", children: leftPanel }),
509
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_antd2.Splitter.Panel, { children: rightPanel })
510
- ] });
511
- }
512
-
513
- // src/components/Toolbar/index.tsx
514
- var import_react6 = require("react");
515
- var import_antd5 = require("antd");
516
- var import_icons4 = require("@ant-design/icons");
517
-
518
- // src/components/Toolbar/hooks/useToolbar.ts
519
- var import_react3 = require("react");
520
-
521
- // src/components/Toolbar/config.ts
522
- var import_icons = require("@ant-design/icons");
523
-
524
- // src/components/Toolbar/buttons.tsx
306
+ //#endregion
307
+ //#region src/components/Toolbar/buttons.tsx
308
+ /**
309
+ * 在光标位置插入文本
310
+ */
525
311
  function insertAtCursor(view, before, after = "", placeholder = "") {
526
- const { from, to } = view.state.selection.main;
527
- const selectedText = view.state.doc.sliceString(from, to);
528
- const insert = selectedText ? `${before}${selectedText}${after}` : `${before}${placeholder}${after}`;
529
- view.dispatch({ changes: { from, to, insert } });
530
- if (!selectedText && placeholder) {
531
- view.dispatch({ selection: { anchor: from + before.length } });
532
- }
312
+ const { from, to } = view.state.selection.main;
313
+ const selectedText = view.state.doc.sliceString(from, to);
314
+ const insert = selectedText ? `${before}${selectedText}${after}` : `${before}${placeholder}${after}`;
315
+ view.dispatch({ changes: {
316
+ from,
317
+ to,
318
+ insert
319
+ } });
320
+ if (!selectedText && placeholder) view.dispatch({ selection: { anchor: from + before.length } });
533
321
  }
322
+ /**
323
+ * 在当前行行首插入文本
324
+ */
534
325
  function insertAtLineStart(view, text) {
535
- const { from } = view.state.selection.main;
536
- const line = view.state.doc.lineAt(from);
537
- view.dispatch({ changes: { from: line.from, to: line.from, insert: text } });
538
- view.dispatch({ selection: { anchor: line.from + text.length } });
326
+ const { from } = view.state.selection.main;
327
+ const line = view.state.doc.lineAt(from);
328
+ view.dispatch({ changes: {
329
+ from: line.from,
330
+ to: line.from,
331
+ insert: text
332
+ } });
333
+ view.dispatch({ selection: { anchor: line.from + text.length } });
334
+ }
335
+ /**
336
+ * 切换标题:
337
+ * - 当前行已是目标级别标题 → 移除行首 # 前缀(变回普通文本)
338
+ * - 当前行是其他级别标题 → 替换为目标级别
339
+ * - 当前行是普通文本 → 插入目标级别前缀
340
+ * @param view - CodeMirror 编辑器实例
341
+ * @param level - 标题级别(1-6)
342
+ */
343
+ function toggleHeading(view, level) {
344
+ const line = view.state.doc.lineAt(view.state.selection.main.from);
345
+ const m = line.text.match(/^(\s*)(#{1,6})(\s+|\s*$)/);
346
+ const indent = m ? m[1] : "";
347
+ const text = "#".repeat(level) + " ";
348
+ if (m && m[2].length === level) view.dispatch({ changes: {
349
+ from: line.from,
350
+ to: line.from + m[0].length,
351
+ insert: indent
352
+ } });
353
+ else {
354
+ view.dispatch({ changes: {
355
+ from: line.from,
356
+ to: m ? line.from + m[0].length : line.from,
357
+ insert: indent + text
358
+ } });
359
+ view.dispatch({ selection: { anchor: line.from + indent.length + text.length } });
360
+ }
361
+ }
362
+ /**
363
+ * 切换任务列表(checkbox 待办)
364
+ * - 支持多行:对选区内每一行应用 `- [ ] `
365
+ * - 普通列表项(如 `- 内容`)直接转换为 `- [ ] 内容`
366
+ * - 切换:若所有目标行都已是任务项,则移除行首前缀
367
+ */
368
+ function toggleTaskList(view) {
369
+ const doc = view.state.doc;
370
+ const { from, to } = view.state.selection.main;
371
+ const first = doc.lineAt(from);
372
+ const last = doc.lineAt(to);
373
+ const lines = [];
374
+ for (let n = first.number; n <= last.number; n++) lines.push(doc.line(n));
375
+ const taskRe = /^(\s*)([-*+])\s\[( |x)\]\s?/;
376
+ const allTask = lines.every((line) => taskRe.test(line.text));
377
+ const changes = [];
378
+ for (const line of lines) {
379
+ const taskMatch = line.text.match(taskRe);
380
+ if (taskMatch) changes.push({
381
+ from: line.from,
382
+ to: line.from + taskMatch[0].length,
383
+ insert: allTask ? "" : `${taskMatch[1]}${taskMatch[2]} [ ] `
384
+ });
385
+ else {
386
+ const bulletMatch = line.text.match(/^(\s*)([-*+])\s/);
387
+ if (bulletMatch) {
388
+ const idx = line.from + bulletMatch[1].length + bulletMatch[2].length;
389
+ changes.push({
390
+ from: idx,
391
+ to: idx + 1,
392
+ insert: " [ ] "
393
+ });
394
+ } else changes.push({
395
+ from: line.from,
396
+ to: line.from,
397
+ insert: "- [ ] "
398
+ });
399
+ }
400
+ }
401
+ view.dispatch({ changes });
402
+ }
403
+ /**
404
+ * 切换行首前缀(支持多行选区)
405
+ * - 已是对应前缀的行 → 移除前缀(变回普通文本)
406
+ * - 未加前缀的行 → 在行首插入前缀
407
+ * @param view - CodeMirror 编辑器实例
408
+ * @param pattern - 匹配行首前缀的正则
409
+ * @param insertText - 未匹配时插入的前缀文本
410
+ */
411
+ function toggleLinePrefix(view, pattern, insertText) {
412
+ const doc = view.state.doc;
413
+ const { from, to } = view.state.selection.main;
414
+ const first = doc.lineAt(from);
415
+ const last = doc.lineAt(Math.max(to, from));
416
+ const changes = [];
417
+ for (let n = first.number; n <= last.number; n++) {
418
+ const line = doc.line(n);
419
+ const m = line.text.match(pattern);
420
+ if (m) changes.push({
421
+ from: line.from,
422
+ to: line.from + m[0].length,
423
+ insert: ""
424
+ });
425
+ else changes.push({
426
+ from: line.from,
427
+ to: line.from,
428
+ insert: insertText
429
+ });
430
+ }
431
+ view.dispatch({ changes });
432
+ }
433
+ /**
434
+ * 上传图片
435
+ * @param file - 要上传的文件
436
+ * @param uploadConfig - 上传配置
437
+ */
438
+ async function uploadFileToServer(file, uploadConfig) {
439
+ if (uploadConfig.onUploadImage) return uploadConfig.onUploadImage(file);
440
+ if (uploadConfig.uploadUrl) {
441
+ const formData = new FormData();
442
+ formData.append("file", file);
443
+ const response = await fetch(uploadConfig.uploadUrl, {
444
+ method: "POST",
445
+ body: formData
446
+ });
447
+ if (!response.ok) return null;
448
+ const result = await response.json();
449
+ if (uploadConfig.onGetUploadUrl) return uploadConfig.onGetUploadUrl(result);
450
+ return result.data?.path || null;
451
+ }
452
+ return null;
539
453
  }
540
- async function uploadFileToServer(file) {
541
- var _a;
542
- const formData = new FormData();
543
- formData.append("file", file);
544
- const response = await fetch("https://remons.cn:3008/content/uploadMarkdownImg", {
545
- method: "POST",
546
- body: formData
547
- });
548
- if (!response.ok) return null;
549
- const result = await response.json();
550
- if (result.code === 200 && result.success && ((_a = result.data) == null ? void 0 : _a.path)) {
551
- return `https://remons.cn:3008${result.data.path}`;
552
- }
553
- return null;
454
+ /**
455
+ * 判断是否配置了图片上传
456
+ */
457
+ function isUploadConfigured(uploadConfig) {
458
+ return !!(uploadConfig?.uploadUrl || uploadConfig?.onUploadImage);
554
459
  }
555
- function uploadImage(view) {
556
- const input = document.createElement("input");
557
- input.type = "file";
558
- input.accept = "image/*";
559
- input.style.display = "none";
560
- input.addEventListener("change", async () => {
561
- if (!input.files || input.files.length === 0) return;
562
- const file = input.files[0];
563
- const { from, to } = view.state.selection.main;
564
- const selectedText = view.state.doc.sliceString(from, to) || "\u63CF\u8FF0";
565
- const url = await uploadFileToServer(file);
566
- if (url) {
567
- const insert = `![${selectedText}](${url})`;
568
- view.dispatch({ changes: { from, to, insert } });
569
- }
570
- input.remove();
571
- });
572
- document.body.appendChild(input);
573
- input.click();
460
+ /**
461
+ * 图片上传:创建隐藏的 file input 触发选择,上传后插入 markdown 图片语法
462
+ * @param view - CodeMirror 编辑器实例
463
+ * @param uploadConfig - 上传配置(包含 uploadUrl 或 onUploadImage)
464
+ */
465
+ function uploadImage(view, uploadConfig) {
466
+ const input = document.createElement("input");
467
+ input.type = "file";
468
+ input.accept = "image/*";
469
+ input.style.display = "none";
470
+ input.addEventListener("change", async () => {
471
+ if (!input.files || input.files.length === 0) return;
472
+ const file = input.files[0];
473
+ const { from, to } = view.state.selection.main;
474
+ const selectedText = view.state.doc.sliceString(from, to) || "描述";
475
+ const url = await uploadFileToServer(file, uploadConfig);
476
+ if (url) {
477
+ const insert = `![${selectedText}](${url})`;
478
+ view.dispatch({ changes: {
479
+ from,
480
+ to,
481
+ insert
482
+ } });
483
+ }
484
+ input.remove();
485
+ });
486
+ document.body.appendChild(input);
487
+ input.click();
574
488
  }
489
+ /**
490
+ * 用前缀和后缀包裹选中文本
491
+ */
575
492
  function wrapSelection(view, prefix, suffix) {
576
- const { from, to, empty } = view.state.selection.main;
577
- if (empty) return;
578
- const selected = view.state.doc.sliceString(from, to);
579
- view.dispatch({ changes: { from, to, insert: `${prefix}${selected}${suffix}` } });
493
+ const { from, to, empty } = view.state.selection.main;
494
+ if (empty) return;
495
+ const selected = view.state.doc.sliceString(from, to);
496
+ view.dispatch({ changes: {
497
+ from,
498
+ to,
499
+ insert: `${prefix}${selected}${suffix}`
500
+ } });
580
501
  }
502
+ /**
503
+ * 切换包裹:选中的文本已包含标签则移除,否则添加
504
+ */
581
505
  function toggleWrap(view, openTag, closeTag) {
582
- const { from, to, empty } = view.state.selection.main;
583
- const doc = view.state.doc;
584
- if (!empty) {
585
- const selected = doc.sliceString(from, to);
586
- if (selected.startsWith(openTag) && selected.endsWith(closeTag)) {
587
- const inner = selected.slice(openTag.length, -closeTag.length);
588
- view.dispatch({ changes: { from, to, insert: inner } });
589
- return;
590
- }
591
- }
592
- {
593
- const cursorPos = empty ? from : to;
594
- const line = doc.lineAt(cursorPos);
595
- const lineText = doc.sliceString(line.from, line.to);
596
- const openIdx = lineText.indexOf(openTag);
597
- const closeIdx = lineText.lastIndexOf(closeTag);
598
- if (openIdx >= 0 && closeIdx > openIdx) {
599
- const absOpen = line.from + openIdx;
600
- const absClose = line.from + closeIdx;
601
- if (cursorPos >= absOpen + openTag.length && cursorPos <= absClose) {
602
- const inner = doc.sliceString(absOpen + openTag.length, absClose);
603
- view.dispatch({
604
- changes: {
605
- from: absOpen,
606
- to: absClose + closeTag.length,
607
- insert: inner
608
- }
609
- });
610
- return;
611
- }
612
- }
613
- }
614
- wrapSelection(view, openTag, closeTag);
506
+ const { from, to, empty } = view.state.selection.main;
507
+ const doc = view.state.doc;
508
+ if (!empty) {
509
+ const selected = doc.sliceString(from, to);
510
+ if (selected.startsWith(openTag) && selected.endsWith(closeTag)) {
511
+ const inner = selected.slice(openTag.length, -closeTag.length);
512
+ view.dispatch({ changes: {
513
+ from,
514
+ to,
515
+ insert: inner
516
+ } });
517
+ return;
518
+ }
519
+ }
520
+ {
521
+ const cursorPos = empty ? from : to;
522
+ const line = doc.lineAt(cursorPos);
523
+ const lineText = doc.sliceString(line.from, line.to);
524
+ const openIdx = lineText.indexOf(openTag);
525
+ const closeIdx = lineText.lastIndexOf(closeTag);
526
+ if (openIdx >= 0 && closeIdx > openIdx) {
527
+ const absOpen = line.from + openIdx;
528
+ const absClose = line.from + closeIdx;
529
+ if (cursorPos >= absOpen + openTag.length && cursorPos <= absClose) {
530
+ const inner = doc.sliceString(absOpen + openTag.length, absClose);
531
+ view.dispatch({ changes: {
532
+ from: absOpen,
533
+ to: absClose + closeTag.length,
534
+ insert: inner
535
+ } });
536
+ return;
537
+ }
538
+ }
539
+ }
540
+ wrapSelection(view, openTag, closeTag);
615
541
  }
616
-
617
- // src/components/Toolbar/config.ts
618
- function createToolbarConfig() {
619
- return [
620
- {
621
- id: "heading",
622
- icon: import_icons.FontSizeOutlined,
623
- label: "\u6807\u9898",
624
- children: [
625
- { id: "heading1", label: "\u6807\u9898 1", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "# ") },
626
- { id: "heading2", label: "\u6807\u9898 2", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "## ") },
627
- { id: "heading3", label: "\u6807\u9898 3", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "### ") },
628
- { id: "heading4", label: "\u6807\u9898 4", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "#### ") },
629
- { id: "heading5", label: "\u6807\u9898 5", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "##### ") },
630
- { id: "heading6", label: "\u6807\u9898 6", icon: import_icons.FontSizeOutlined, handler: (v) => insertAtLineStart(v, "###### ") }
631
- ]
632
- },
633
- { separatorBefore: true },
634
- { id: "bold", label: "\u52A0\u7C97", icon: import_icons.BoldOutlined, handler: (v) => wrapSelection(v, "**", "**") },
635
- { id: "italic", label: "\u659C\u4F53", icon: import_icons.ItalicOutlined, handler: (v) => wrapSelection(v, "*", "*") },
636
- { id: "strikethrough", label: "\u5220\u9664\u7EBF", icon: import_icons.StrikethroughOutlined, handler: (v) => wrapSelection(v, "~~", "~~") },
637
- { id: "underline", label: "\u4E0B\u5212\u7EBF", icon: import_icons.UnderlineOutlined, handler: (v) => wrapSelection(v, "<u>", "</u>") },
638
- { separatorBefore: true },
639
- { id: "ul", label: "\u65E0\u5E8F\u5217\u8868", icon: import_icons.UnorderedListOutlined, handler: (v) => insertAtLineStart(v, "- ") },
640
- { id: "ol", label: "\u6709\u5E8F\u5217\u8868", icon: import_icons.OrderedListOutlined, handler: (v) => insertAtLineStart(v, "1. ") },
641
- { separatorBefore: true },
642
- { id: "link", label: "\u94FE\u63A5", icon: import_icons.LinkOutlined, handler: (v) => insertAtCursor(v, "[", "](url)", "\u6587\u672C") },
643
- { id: "image", label: "\u56FE\u7247", icon: import_icons.PictureOutlined, handler: (v) => uploadImage(v) },
644
- { separatorBefore: true },
645
- { id: "code", label: "\u884C\u5185\u4EE3\u7801", icon: import_icons.FieldStringOutlined, handler: (v) => wrapSelection(v, "`", "`") },
646
- {
647
- id: "codeblock",
648
- label: "\u4EE3\u7801\u5757",
649
- icon: import_icons.CodeOutlined,
650
- handler: (v) => {
651
- const { from, to } = v.state.selection.main;
652
- const selected = v.state.doc.sliceString(from, to);
653
- const insert = selected ? "```\n" + selected + "\n```" : "```\n\n```";
654
- v.dispatch({ changes: { from, to, insert } });
655
- }
656
- },
657
- { id: "quote", label: "\u5F15\u7528", icon: import_icons.BlockOutlined, handler: (v) => insertAtLineStart(v, "> ") },
658
- { id: "textContainer", label: "\u6587\u5B57\u5BB9\u5668", icon: import_icons.TagsOutlined, handler: (v) => toggleWrap(v, "<text-container>", "</text-container>") },
659
- {
660
- id: "hr",
661
- label: "\u5206\u5272\u7EBF",
662
- icon: import_icons.MinusOutlined,
663
- handler: (v) => {
664
- const { from } = v.state.selection.main;
665
- v.dispatch({ changes: { from, insert: "\n---\n" } });
666
- }
667
- }
668
- ];
542
+ //#endregion
543
+ //#region src/components/Editor/codemirrorExtensions.ts
544
+ /** 图片上传配置 facet */
545
+ var uploadConfigFacet = _codemirror_state.Facet.define({ combine: (values) => values[values.length - 1] });
546
+ /** 未配置上传时的提示回调 facet */
547
+ var messageFacet = _codemirror_state.Facet.define({ combine: (values) => values[values.length - 1] });
548
+ /** 粘贴事件处理:拦截图片并上传 */
549
+ var pasteHandler = _codemirror_view.EditorView.domEventHandlers({ paste: (event, view) => {
550
+ const files = event.clipboardData?.files;
551
+ if (!files) return false;
552
+ const imageFiles = Array.from(files).filter((f) => f.type.startsWith("image/"));
553
+ if (imageFiles.length === 0) return false;
554
+ event.preventDefault();
555
+ const uploadConfig = view.state.facet(uploadConfigFacet);
556
+ const showMessage = view.state.facet(messageFacet);
557
+ if (!isUploadConfigured(uploadConfig)) {
558
+ showMessage?.();
559
+ return true;
560
+ }
561
+ const { from, to } = view.state.selection.main;
562
+ let offset = 0;
563
+ for (const file of imageFiles) uploadFileToServer(file, uploadConfig).then((url) => {
564
+ if (url) {
565
+ const insert = `![${file.name.replace(/\.[^.]+$/, "")}](${url})`;
566
+ view.dispatch({ changes: {
567
+ from: from + offset,
568
+ to: to + offset,
569
+ insert
570
+ } });
571
+ offset += insert.length;
572
+ }
573
+ });
574
+ return true;
575
+ } });
576
+ var getCodeMirrorExtensions = (opts) => {
577
+ const extensions = [
578
+ (0, _codemirror_lang_markdown.markdown)(),
579
+ _codemirror_theme_one_dark.oneDark,
580
+ _codemirror_view.keymap.of([..._codemirror_commands.defaultKeymap, _codemirror_commands.indentWithTab]),
581
+ _codemirror_view.EditorView.lineWrapping,
582
+ pasteHandler,
583
+ _codemirror_view.EditorView.baseTheme({
584
+ ".cm-scroller": {
585
+ height: "100%",
586
+ overflow: "auto"
587
+ },
588
+ ".container-widget-header": {
589
+ display: "inline",
590
+ fontSize: "inherit",
591
+ fontFamily: "inherit"
592
+ },
593
+ ".container-widget-text": {
594
+ fontWeight: "bold",
595
+ color: "#79c0ff"
596
+ },
597
+ ".container-widget-props": {
598
+ display: "inline",
599
+ fontSize: "inherit",
600
+ fontFamily: "inherit",
601
+ color: "#a5d6ff"
602
+ },
603
+ ".container-edit-btn": {
604
+ display: "inline-block",
605
+ padding: "0 4px",
606
+ fontSize: "12px",
607
+ color: "#58a6ff",
608
+ background: "transparent",
609
+ border: "none",
610
+ borderRadius: "4px",
611
+ cursor: "pointer",
612
+ marginRight: "4px",
613
+ verticalAlign: "middle"
614
+ },
615
+ ".container-edit-btn:hover": { background: "rgba(88, 166, 255, 0.16)" }
616
+ })
617
+ ];
618
+ extensions.push(containerWidgetPlugin);
619
+ if (opts?.onEditContainer) extensions.push(containerEditCallback.of(opts.onEditContainer));
620
+ if (opts?.uploadConfig) extensions.push(uploadConfigFacet.of(opts.uploadConfig));
621
+ if (opts?.onMessageNoUploadConfig) extensions.push(messageFacet.of(opts.onMessageNoUploadConfig));
622
+ return extensions;
623
+ };
624
+ //#endregion
625
+ //#region src/components/Editor/index.tsx
626
+ function Editor({ value, onChange, onScrollReady, onViewReady, onEditContainer, uploadConfig, onMessageNoUploadConfig }) {
627
+ const editorRef = (0, react.useRef)(null);
628
+ const viewRef = (0, react.useRef)(null);
629
+ (0, react.useEffect)(() => {
630
+ if (!editorRef.current) return;
631
+ const extensions = [
632
+ codemirror.basicSetup,
633
+ ...getCodeMirrorExtensions({
634
+ onEditContainer,
635
+ uploadConfig,
636
+ onMessageNoUploadConfig
637
+ }),
638
+ codemirror.EditorView.updateListener.of((update) => {
639
+ if (update.docChanged) onChange(update.state.doc.toString());
640
+ })
641
+ ];
642
+ const state = _codemirror_state.EditorState.create({
643
+ doc: value,
644
+ extensions
645
+ });
646
+ viewRef.current = new codemirror.EditorView({
647
+ state,
648
+ parent: editorRef.current
649
+ });
650
+ if (onScrollReady && viewRef.current) onScrollReady(viewRef.current.scrollDOM);
651
+ if (onViewReady) onViewReady(viewRef.current);
652
+ return () => {
653
+ viewRef.current?.destroy();
654
+ };
655
+ }, []);
656
+ (0, react.useEffect)(() => {
657
+ if (viewRef.current && viewRef.current.state.doc.toString() !== value) viewRef.current.dispatch({ changes: {
658
+ from: 0,
659
+ to: viewRef.current.state.doc.length,
660
+ insert: value
661
+ } });
662
+ }, [value]);
663
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
664
+ ref: editorRef,
665
+ className: "editor"
666
+ });
669
667
  }
670
-
671
- // src/components/Toolbar/hooks/useToolbar.ts
672
- function useToolbar(editorView, extraItems) {
673
- const config = (0, import_react3.useMemo)(() => {
674
- const base = createToolbarConfig();
675
- return extraItems ? [...base, { separatorBefore: true }, ...extraItems] : base;
676
- }, [extraItems]);
677
- const handleClick = (0, import_react3.useCallback)((button) => {
678
- if (button.handler && editorView) {
679
- editorView.focus();
680
- button.handler(editorView, editorView.state);
681
- }
682
- }, [editorView]);
683
- return { config, handleClick };
668
+ //#endregion
669
+ //#region src/components/Preview/index.tsx
670
+ var Preview = (0, react.forwardRef)(({ content, previewOptions }, ref) => {
671
+ (0, react.useEffect)(() => {
672
+ (0, remons_render_markdown.initHighlighter)({
673
+ ...remons_render_markdown.languagesCommon,
674
+ ...previewOptions?.languages ? previewOptions?.languages : {}
675
+ });
676
+ }, [previewOptions?.languages]);
677
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
678
+ ref,
679
+ className: "preview",
680
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(remons_render_markdown.default, {
681
+ content,
682
+ ...previewOptions
683
+ })
684
+ });
685
+ });
686
+ Preview.displayName = "Preview";
687
+ //#endregion
688
+ //#region src/components/Panel/SplitPanels.tsx
689
+ var { Panel } = antd.Splitter;
690
+ function SplitPanels({ leftPanel, rightPanel }) {
691
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(antd.Splitter, {
692
+ className: "split-panels",
693
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Panel, {
694
+ defaultSize: "50%",
695
+ min: "20%",
696
+ max: "80%",
697
+ children: leftPanel
698
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Panel, { children: rightPanel })]
699
+ });
684
700
  }
685
-
686
- // src/components/Toolbar/renderers/ToolbarItemRenderer.tsx
687
- var import_antd3 = require("antd");
688
- var import_icons2 = require("@ant-design/icons");
689
- var import_jsx_runtime5 = require("react/jsx-runtime");
701
+ //#endregion
702
+ //#region src/components/Icon/index.tsx
703
+ var Icon = (0, _ant_design_icons.createFromIconfontCN)({ scriptUrl: "https://at.alicdn.com/t/c/font_5241307_xoojyr52dqn.js" });
704
+ function IconComponent(type, className) {
705
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
706
+ type: `icon-${type}`,
707
+ className
708
+ });
709
+ }
710
+ //#endregion
711
+ //#region src/components/Toolbar/config.ts
712
+ function Quote() {
713
+ return IconComponent("quote");
714
+ }
715
+ function InlineCode() {
716
+ return IconComponent("inlinecode");
717
+ }
718
+ /**
719
+ * 创建工具栏配置
720
+ * @param options - 配置选项
721
+ */
722
+ function createToolbarConfig(options) {
723
+ const hasUploadConfig = isUploadConfigured(options);
724
+ return [
725
+ {
726
+ id: "heading",
727
+ icon: _ant_design_icons.FontSizeOutlined,
728
+ label: "标题",
729
+ children: [
730
+ {
731
+ id: "heading1",
732
+ label: "H1",
733
+ handler: (v) => toggleHeading(v, 1)
734
+ },
735
+ {
736
+ id: "heading2",
737
+ label: "H2",
738
+ handler: (v) => toggleHeading(v, 2)
739
+ },
740
+ {
741
+ id: "heading3",
742
+ label: "H3",
743
+ handler: (v) => toggleHeading(v, 3)
744
+ },
745
+ {
746
+ id: "heading4",
747
+ label: "H4",
748
+ handler: (v) => toggleHeading(v, 4)
749
+ },
750
+ {
751
+ id: "heading5",
752
+ label: "H5",
753
+ handler: (v) => toggleHeading(v, 5)
754
+ },
755
+ {
756
+ id: "heading6",
757
+ label: "H6",
758
+ handler: (v) => toggleHeading(v, 6)
759
+ }
760
+ ]
761
+ },
762
+ { separatorBefore: true },
763
+ {
764
+ id: "bold",
765
+ label: "加粗",
766
+ icon: _ant_design_icons.BoldOutlined,
767
+ handler: (v) => toggleWrap(v, "**", "**")
768
+ },
769
+ {
770
+ id: "italic",
771
+ label: "斜体",
772
+ icon: _ant_design_icons.ItalicOutlined,
773
+ handler: (v) => toggleWrap(v, "*", "*")
774
+ },
775
+ {
776
+ id: "strikethrough",
777
+ label: "删除线",
778
+ icon: _ant_design_icons.StrikethroughOutlined,
779
+ handler: (v) => toggleWrap(v, "~~", "~~")
780
+ },
781
+ {
782
+ id: "underline",
783
+ label: "下划线",
784
+ icon: _ant_design_icons.UnderlineOutlined,
785
+ handler: (v) => toggleWrap(v, "<u>", "</u>")
786
+ },
787
+ { separatorBefore: true },
788
+ {
789
+ id: "ul",
790
+ label: "无序列表",
791
+ icon: _ant_design_icons.UnorderedListOutlined,
792
+ handler: (v) => toggleLinePrefix(v, /^(\s*)-(?!\s*\[)\s/, "- ")
793
+ },
794
+ {
795
+ id: "ol",
796
+ label: "有序列表",
797
+ icon: _ant_design_icons.OrderedListOutlined,
798
+ handler: (v) => toggleLinePrefix(v, /^(\s*)\d+\.\s/, "1. ")
799
+ },
800
+ {
801
+ id: "checkbox",
802
+ label: "待办",
803
+ icon: _ant_design_icons.CheckSquareOutlined,
804
+ handler: (v) => toggleTaskList(v)
805
+ },
806
+ { separatorBefore: true },
807
+ {
808
+ id: "link",
809
+ label: "链接",
810
+ icon: _ant_design_icons.LinkOutlined,
811
+ handler: (v) => insertAtCursor(v, "[", "](url)", "文本")
812
+ },
813
+ {
814
+ id: "image",
815
+ label: hasUploadConfig ? "图片" : "请配置图片上传地址",
816
+ icon: _ant_design_icons.PictureOutlined,
817
+ disabled: !hasUploadConfig,
818
+ handler: hasUploadConfig ? (v) => uploadImage(v, {
819
+ uploadUrl: options?.uploadUrl,
820
+ onUploadImage: options?.onUploadImage
821
+ }) : void 0
822
+ },
823
+ { separatorBefore: true },
824
+ {
825
+ id: "code",
826
+ label: "行内代码",
827
+ icon: InlineCode,
828
+ handler: (v) => wrapSelection(v, "`", "`")
829
+ },
830
+ {
831
+ id: "codeblock",
832
+ label: "代码块",
833
+ icon: _ant_design_icons.CodeOutlined,
834
+ handler: (v) => {
835
+ const { from, to } = v.state.selection.main;
836
+ const selected = v.state.doc.sliceString(from, to);
837
+ const insert = selected ? "```\n" + selected + "\n```" : "```\n\n```";
838
+ v.dispatch({ changes: {
839
+ from,
840
+ to,
841
+ insert
842
+ } });
843
+ }
844
+ },
845
+ {
846
+ id: "quote",
847
+ label: "引用",
848
+ icon: Quote,
849
+ handler: (v) => insertAtLineStart(v, "> ")
850
+ },
851
+ {
852
+ id: "hr",
853
+ label: "分割线",
854
+ icon: _ant_design_icons.MinusOutlined,
855
+ handler: (v) => {
856
+ const { from } = v.state.selection.main;
857
+ v.dispatch({ changes: {
858
+ from,
859
+ insert: "\n---\n"
860
+ } });
861
+ }
862
+ }
863
+ ];
864
+ }
865
+ //#endregion
866
+ //#region src/components/Toolbar/hooks/useToolbar.ts
867
+ function useToolbar(editorView, options) {
868
+ const { extraItems, uploadConfig } = options || {};
869
+ return {
870
+ config: (0, react.useMemo)(() => {
871
+ return createToolbarConfig(uploadConfig);
872
+ }, [uploadConfig]),
873
+ extraItemsConfig: (0, react.useMemo)(() => {
874
+ return extraItems || [];
875
+ }, [extraItems]),
876
+ handleClick: (0, react.useCallback)((button) => {
877
+ if (!editorView) return;
878
+ if (button.wrapTags) {
879
+ const [openTag, closeTag] = button.wrapTags;
880
+ toggleWrap(editorView, openTag, closeTag);
881
+ return;
882
+ }
883
+ if (button.handler) {
884
+ editorView.focus();
885
+ button.handler(editorView, editorView.state);
886
+ }
887
+ }, [editorView])
888
+ };
889
+ }
890
+ //#endregion
891
+ //#region src/components/Toolbar/renderers/ToolbarItemRenderer.tsx
690
892
  function ToolbarItemRenderer({ item, onClick }) {
691
- const Icon = item.icon;
692
- if (item.children && item.children.length > 0) {
693
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
694
- item.separatorBefore && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "toolbar-separator" }),
695
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "toolbar-group", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
696
- import_antd3.Dropdown,
697
- {
698
- menu: {
699
- items: item.children.map((child) => {
700
- var _a;
701
- return {
702
- type: "item",
703
- key: (_a = child.id) != null ? _a : "",
704
- label: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { onClick: () => onClick(child), children: [
705
- child.icon && (() => {
706
- const ChildIcon = child.icon;
707
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChildIcon, {});
708
- })(),
709
- " ",
710
- child.label
711
- ] })
712
- };
713
- })
714
- },
715
- placement: "bottomLeft",
716
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_antd3.Button, { className: "toolbar-button", type: "text", size: "small", title: item.label, children: [
717
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, {}),
718
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons2.DownOutlined, { style: { marginLeft: 2, fontSize: 10 } })
719
- ] })
720
- }
721
- ) })
722
- ] });
723
- }
724
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
725
- item.separatorBefore && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "toolbar-separator" }),
726
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "toolbar-group", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
727
- import_antd3.Button,
728
- {
729
- id: item.id,
730
- className: "toolbar-button",
731
- type: "text",
732
- size: "small",
733
- title: item.label,
734
- onClick: () => onClick(item),
735
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, {})
736
- }
737
- ) })
738
- ] });
893
+ const Icon = item.icon;
894
+ 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", {
895
+ className: "toolbar-group",
896
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Tooltip, {
897
+ title: item.label,
898
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Dropdown, {
899
+ menu: { items: item.children.map((child) => ({
900
+ type: "item",
901
+ key: child.id ?? "",
902
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
903
+ onClick: () => onClick(child),
904
+ children: [
905
+ child.icon && (() => {
906
+ const ChildIcon = child.icon;
907
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChildIcon, {});
908
+ })(),
909
+ " ",
910
+ child.label
911
+ ]
912
+ })
913
+ })) },
914
+ placement: "bottomLeft",
915
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(antd.Button, {
916
+ className: "toolbar-button",
917
+ type: "text",
918
+ size: "small",
919
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ant_design_icons.DownOutlined, { style: { fontSize: 10 } })]
920
+ })
921
+ })
922
+ })
923
+ })] });
924
+ const isDisabled = item.disabled;
925
+ 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", {
926
+ className: "toolbar-group",
927
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Tooltip, {
928
+ title: item.label,
929
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Button, {
930
+ id: item.id,
931
+ className: "toolbar-button",
932
+ type: "text",
933
+ size: "small",
934
+ disabled: isDisabled,
935
+ onClick: () => !isDisabled && onClick(item),
936
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {})
937
+ })
938
+ })
939
+ })] });
739
940
  }
740
-
741
- // src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
742
- var import_react5 = __toESM(require("react"), 1);
743
- var import_antd4 = require("antd");
744
- var import_icons3 = require("@ant-design/icons");
745
-
746
- // src/components/Toolbar/renderers/TablePicker/index.tsx
747
- var import_react4 = require("react");
748
-
749
- // src/components/Toolbar/renderers/TablePicker/index.module.less
750
- var index_module_default = {};
751
-
752
- // src/components/Toolbar/renderers/TablePicker/index.tsx
753
- var import_jsx_runtime6 = require("react/jsx-runtime");
754
- var MAX_ROW = 12;
755
- var MAX_COL = 12;
756
- var REFRESH_TIME = 200;
941
+ var index_module_default = {
942
+ container: "index-module__container___l6BT-",
943
+ grid: "index-module__grid___tCZ-r",
944
+ cell: "index-module__cell___vTDQz",
945
+ cellActive: "index-module__cellActive___0EYvU",
946
+ hint: "index-module__hint___dTObk",
947
+ label: "index-module__label___2CmK1"
948
+ };
949
+ //#endregion
950
+ //#region src/components/Toolbar/renderers/TablePicker/index.tsx
951
+ var MAX_ROW = 8;
952
+ var MAX_COL = 8;
757
953
  function TablePicker({ onSelect }) {
758
- const [hoverSize, setHoverSize] = (0, import_react4.useState)({ row: 0, col: 0 });
759
- const [prevHoverSize, setPrevHoverSize] = (0, import_react4.useState)({ row: 0, col: 0 });
760
- const [confirmedSize, setConfirmedSize] = (0, import_react4.useState)({ row: 0, col: 0 });
761
- const maxColRef = (0, import_react4.useRef)(0);
762
- const timerIdRef = (0, import_react4.useRef)(null);
763
- const refreshTimerRef = (0, import_react4.useRef)(null);
764
- const commitRef = (0, import_react4.useRef)(null);
765
- const clearTimer = (0, import_react4.useCallback)((flushFn) => {
766
- if (timerIdRef.current) {
767
- timerIdRef.current = null;
768
- }
769
- if (flushFn && maxColRef.current > 0) {
770
- timerIdRef.current = setTimeout(flushFn, REFRESH_TIME);
771
- }
772
- }, []);
773
- (0, import_react4.useEffect)(
774
- () => () => {
775
- if (timerIdRef.current) clearTimeout(timerIdRef.current);
776
- if (refreshTimerRef.current) clearInterval(refreshTimerRef.current);
777
- },
778
- []
779
- );
780
- function extend(col, row) {
781
- if (col > maxColRef.current) {
782
- maxColRef.current = col;
783
- setHoverSize({ row, col });
784
- setConfirmedSize({ row, col });
785
- } else {
786
- setHoverSize({ row, col });
787
- if (row !== confirmedSize.row) {
788
- setConfirmedSize({ row, col: maxColRef.current });
789
- }
790
- if (row === 1) {
791
- commitRef.current = { cols: col, rows: 1 };
792
- setConfirmedSize({ row: 1, col });
793
- }
794
- }
795
- }
796
- function flash(col, dir, limit, finalCallback) {
797
- let current = dir === 1 ? 1 : limit;
798
- const step = dir === 1 ? 1 : -1;
799
- let timer = 0;
800
- refreshTimerRef.current = setInterval(() => {
801
- setHoverSize({ row: current, col });
802
- if (current > confirmedSize.row || dir === -1) {
803
- maxColRef.current = Math.min(MAX_COL, col);
804
- }
805
- current += step;
806
- if (dir === 1 && current > limit || dir === -1 && current < 1) {
807
- clearInterval(refreshTimerRef.current);
808
- setHoverSize({ row: current - step, col });
809
- finalCallback(col, current - step);
810
- }
811
- }, 50);
812
- clearTimeout(timer);
813
- return () => {
814
- clearInterval(refreshTimerRef.current);
815
- };
816
- }
817
- function onCellEnterCheck(index) {
818
- clearTimer();
819
- const row = Math.floor(index / MAX_COL) + 1;
820
- const col = index % MAX_COL + 1;
821
- const isDown = row > hoverSize.row;
822
- if (Math.abs(row - hoverSize.row) <= 1) {
823
- extend(col, row);
824
- } else {
825
- if (isDown && (col > maxColRef.current || maxColRef.current >= MAX_COL)) {
826
- const finalRow = Math.min(row, MAX_ROW);
827
- flash(col, 1, finalRow, (c, r) => {
828
- setConfirmedSize({ row: r, col: c });
829
- });
830
- } else if (!isDown) {
831
- flash(col, -1, row, (c, r) => {
832
- setConfirmedSize({ row: r, col: c });
833
- if (r === 1) {
834
- commitRef.current = { cols: Math.min(MAX_COL, col), rows: 1 };
835
- setConfirmedSize({ row: 1, col: Math.min(MAX_COL, col) });
836
- }
837
- });
838
- }
839
- }
840
- if (col >= MAX_COL && row >= prevHoverSize.row) {
841
- if (row !== prevHoverSize.row) {
842
- maxColRef.current = 0;
843
- setPrevHoverSize({ row: 0, col: 0 });
844
- }
845
- }
846
- setPrevHoverSize({ row, col });
847
- }
848
- function onMouseDown() {
849
- const size = commitRef.current || { cols: maxColRef.current, rows: confirmedSize.row };
850
- if (size.rows > 0 && size.cols > 0) {
851
- onSelect(size);
852
- }
853
- }
854
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
855
- "div",
856
- {
857
- className: index_module_default.container,
858
- onMouseDown,
859
- children: [
860
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
861
- "div",
862
- {
863
- className: index_module_default.grid,
864
- style: { gridTemplateColumns: `repeat(${MAX_COL}, 24px)` },
865
- children: Array.from({ length: MAX_ROW * MAX_COL }).map((_, i) => {
866
- const row = Math.floor(i / MAX_COL) + 1;
867
- const col = i % MAX_COL + 1;
868
- const isSelected = row <= hoverSize.row && col <= maxColRef.current;
869
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
870
- "div",
871
- {
872
- className: `${index_module_default.cell} ${isSelected ? index_module_default.cellActive : ""}`,
873
- onMouseEnter: () => onCellEnterCheck(i)
874
- },
875
- i
876
- );
877
- })
878
- }
879
- ),
880
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: index_module_default.label, children: [
881
- hoverSize.row,
882
- " x ",
883
- maxColRef.current || 1
884
- ] })
885
- ]
886
- }
887
- );
954
+ const [hoverSize, setHoverSize] = (0, react.useState)({
955
+ row: 0,
956
+ col: 0
957
+ });
958
+ const [confirmedSize, setConfirmedSize] = (0, react.useState)({
959
+ row: 0,
960
+ col: 0
961
+ });
962
+ const maxColRef = (0, react.useRef)(0);
963
+ const prevRowRef = (0, react.useRef)(0);
964
+ const timerIdRef = (0, react.useRef)(null);
965
+ const refreshTimerRef = (0, react.useRef)(null);
966
+ const commitRef = (0, react.useRef)(null);
967
+ const clearTimer = (0, react.useCallback)(() => {
968
+ if (timerIdRef.current) {
969
+ clearTimeout(timerIdRef.current);
970
+ timerIdRef.current = null;
971
+ }
972
+ }, []);
973
+ const clearFlash = (0, react.useCallback)(() => {
974
+ if (refreshTimerRef.current) {
975
+ clearInterval(refreshTimerRef.current);
976
+ refreshTimerRef.current = null;
977
+ }
978
+ }, []);
979
+ (0, react.useEffect)(() => () => {
980
+ if (timerIdRef.current) clearTimeout(timerIdRef.current);
981
+ if (refreshTimerRef.current) clearInterval(refreshTimerRef.current);
982
+ }, []);
983
+ /**
984
+ * 扩展选中区域至 (col, row)
985
+ */
986
+ function extend(col, row) {
987
+ if (row < prevRowRef.current || col < maxColRef.current) maxColRef.current = 0;
988
+ if (col > maxColRef.current) maxColRef.current = col;
989
+ setHoverSize({
990
+ row,
991
+ col
992
+ });
993
+ setConfirmedSize({
994
+ row,
995
+ col: maxColRef.current
996
+ });
997
+ if (row === 1) commitRef.current = {
998
+ cols: maxColRef.current,
999
+ rows: 1
1000
+ };
1001
+ }
1002
+ /**
1003
+ * 跳跃动画效果
1004
+ */
1005
+ function flash(col, dir, limit, finalCallback) {
1006
+ let current = dir === 1 ? 1 : limit;
1007
+ const step = dir === 1 ? 1 : -1;
1008
+ refreshTimerRef.current = setInterval(() => {
1009
+ setHoverSize({
1010
+ row: current,
1011
+ col
1012
+ });
1013
+ if (current > confirmedSize.row || dir === -1) maxColRef.current = Math.min(MAX_COL, col);
1014
+ current += step;
1015
+ if (dir === 1 && current > limit || dir === -1 && current < 1) {
1016
+ clearInterval(refreshTimerRef.current);
1017
+ refreshTimerRef.current = null;
1018
+ const finalRow = current - step;
1019
+ setHoverSize({
1020
+ row: finalRow,
1021
+ col
1022
+ });
1023
+ finalCallback(col, finalRow);
1024
+ }
1025
+ }, 50);
1026
+ }
1027
+ /**
1028
+ * 核心逻辑:鼠标进入格子
1029
+ */
1030
+ function onCellEnterCheck(index) {
1031
+ clearTimer();
1032
+ clearFlash();
1033
+ const row = Math.floor(index / MAX_COL) + 1;
1034
+ const col = index % MAX_COL + 1;
1035
+ const prevRow = prevRowRef.current;
1036
+ const isJump = Math.abs(row - prevRow) > 1;
1037
+ const isDown = row > prevRow;
1038
+ prevRowRef.current = row;
1039
+ if (!isJump) extend(col, row);
1040
+ else if (isDown) {
1041
+ if (col > maxColRef.current || maxColRef.current >= MAX_COL) flash(col, 1, Math.min(row, MAX_ROW), (c, r) => {
1042
+ setConfirmedSize({
1043
+ row: r,
1044
+ col: c
1045
+ });
1046
+ });
1047
+ else extend(col, row);
1048
+ } else {
1049
+ maxColRef.current = 0;
1050
+ flash(col, -1, row, (c, r) => {
1051
+ setConfirmedSize({
1052
+ row: r,
1053
+ col: c
1054
+ });
1055
+ if (r === 1) {
1056
+ commitRef.current = {
1057
+ cols: Math.min(MAX_COL, col),
1058
+ rows: 1
1059
+ };
1060
+ setConfirmedSize({
1061
+ row: 1,
1062
+ col: Math.min(MAX_COL, col)
1063
+ });
1064
+ }
1065
+ });
1066
+ }
1067
+ }
1068
+ function onMouseDown() {
1069
+ const size = commitRef.current || {
1070
+ cols: maxColRef.current,
1071
+ rows: confirmedSize.row
1072
+ };
1073
+ if (size.rows > 0 && size.cols > 0) onSelect(size);
1074
+ }
1075
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1076
+ className: index_module_default.container,
1077
+ onMouseDown,
1078
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1079
+ className: index_module_default.grid,
1080
+ style: { gridTemplateColumns: `repeat(${MAX_COL}, 24px)` },
1081
+ children: Array.from({ length: 64 }).map((_, i) => {
1082
+ const row = Math.floor(i / MAX_COL) + 1;
1083
+ const col = i % MAX_COL + 1;
1084
+ const isSelected = row <= hoverSize.row && col <= maxColRef.current;
1085
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1086
+ className: `${index_module_default.cell} ${isSelected ? index_module_default.cellActive : ""}`,
1087
+ onMouseEnter: () => onCellEnterCheck(i)
1088
+ }, i);
1089
+ })
1090
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1091
+ className: index_module_default.label,
1092
+ children: [
1093
+ hoverSize.row,
1094
+ " x ",
1095
+ maxColRef.current || 1
1096
+ ]
1097
+ })]
1098
+ });
888
1099
  }
889
-
890
- // src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
891
- var import_jsx_runtime7 = require("react/jsx-runtime");
1100
+ //#endregion
1101
+ //#region src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
892
1102
  function TableDropdown({ editorView }) {
893
- const [open, setOpen] = import_react5.default.useState(false);
894
- const openCountRef = (0, import_react5.useRef)(0);
895
- const handleTableSelect = (0, import_react5.useCallback)((rows, cols) => {
896
- if (!editorView || rows <= 0 || cols <= 0) return;
897
- editorView.focus();
898
- const headerCells = Array(cols).fill(null).map((_, i) => ` \u5217${i + 1} `).join("|");
899
- const separator = Array(cols).fill(" --- ").join("|");
900
- const dataCell = Array(cols).fill(" \u5185\u5BB9 ").join("|");
901
- const dataRows = Array(rows).fill(`|${dataCell}|`).join("\n");
902
- const table = `
903
- |${headerCells}|
904
- |${separator}|
905
- ${dataRows}
906
- `;
907
- const { from } = editorView.state.selection.main;
908
- editorView.dispatch({ changes: { from, insert: table } });
909
- }, [editorView]);
910
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
911
- import_antd4.Dropdown,
912
- {
913
- trigger: ["click"],
914
- open,
915
- onOpenChange: setOpen,
916
- popupRender: () => {
917
- openCountRef.current++;
918
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: 4 }, onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
919
- TablePicker,
920
- {
921
- onSelect: (size) => {
922
- handleTableSelect(size.rows, size.cols);
923
- setOpen(false);
924
- }
925
- },
926
- openCountRef.current
927
- ) });
928
- },
929
- placement: "bottomLeft",
930
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_antd4.Button, { className: "toolbar-button", type: "text", size: "small", title: "\u8868\u683C", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_icons3.TableOutlined, {}) })
931
- }
932
- );
1103
+ const [open, setOpen] = react.default.useState(false);
1104
+ const openCountRef = (0, react.useRef)(0);
1105
+ const handleTableSelect = (0, react.useCallback)((rows, cols) => {
1106
+ if (!editorView || rows <= 0 || cols <= 0) return;
1107
+ editorView.focus();
1108
+ const headerCells = Array(cols).fill(null).map((_, i) => ` 列${i + 1} `).join("|");
1109
+ const separator = Array(cols).fill(" --- ").join("|");
1110
+ const dataCell = Array(cols).fill(" 内容 ").join("|");
1111
+ const table = `\n|${headerCells}|\n|${separator}|\n${Array(rows).fill(`|${dataCell}|`).join("\n")}\n`;
1112
+ const { from } = editorView.state.selection.main;
1113
+ editorView.dispatch({ changes: {
1114
+ from,
1115
+ insert: table
1116
+ } });
1117
+ }, [editorView]);
1118
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Dropdown, {
1119
+ trigger: ["click"],
1120
+ open,
1121
+ onOpenChange: setOpen,
1122
+ popupRender: () => {
1123
+ openCountRef.current++;
1124
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1125
+ style: { padding: 4 },
1126
+ onClick: (e) => e.stopPropagation(),
1127
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TablePicker, { onSelect: (size) => {
1128
+ handleTableSelect(size.rows, size.cols);
1129
+ setOpen(false);
1130
+ } }, openCountRef.current)
1131
+ });
1132
+ },
1133
+ placement: "bottomLeft",
1134
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Button, {
1135
+ className: "toolbar-button",
1136
+ type: "text",
1137
+ size: "small",
1138
+ title: "表格",
1139
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ant_design_icons.TableOutlined, {})
1140
+ })
1141
+ });
933
1142
  }
934
-
935
- // src/components/Toolbar/index.tsx
936
- var import_jsx_runtime8 = require("react/jsx-runtime");
937
- function Toolbar({ editorView, fullscreen, onToggleFullscreen, extraItems }) {
938
- const { config, handleClick } = useToolbar(editorView, extraItems);
939
- const renderFullscreen = (0, import_react6.useCallback)(() => {
940
- if (!onToggleFullscreen) return null;
941
- const Icon = fullscreen ? import_icons4.FullscreenExitOutlined : import_icons4.FullscreenOutlined;
942
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
943
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "toolbar-separator" }),
944
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
945
- import_antd5.Button,
946
- {
947
- className: "toolbar-button",
948
- type: "text",
949
- size: "small",
950
- title: fullscreen ? "\u9000\u51FA\u5168\u5C4F" : "\u5168\u5C4F",
951
- onClick: () => {
952
- onToggleFullscreen();
953
- },
954
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, {})
955
- }
956
- )
957
- ] });
958
- }, [fullscreen, onToggleFullscreen]);
959
- function isSeparator(item) {
960
- return !!(!item.id && item.separatorBefore);
961
- }
962
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "toolbar", children: [
963
- config.map((item, idx) => {
964
- if (isSeparator(item)) {
965
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "toolbar-separator" }, `sep-${idx}`);
966
- }
967
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
968
- ToolbarItemRenderer,
969
- {
970
- item,
971
- onClick: handleClick
972
- },
973
- item.id
974
- );
975
- }),
976
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "toolbar-separator" }),
977
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableDropdown, { editorView }),
978
- renderFullscreen()
979
- ] });
1143
+ //#endregion
1144
+ //#region src/components/Toolbar/index.tsx
1145
+ function Toolbar({ editorView, fullscreen = true, onToggleFullscreen, extraItems, uploadConfig }) {
1146
+ const { config, extraItemsConfig, handleClick } = useToolbar(editorView, {
1147
+ extraItems,
1148
+ uploadConfig
1149
+ });
1150
+ const renderFullscreen = (0, react.useCallback)(() => {
1151
+ if (!onToggleFullscreen) return null;
1152
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Button, {
1153
+ className: "toolbar-button",
1154
+ type: "text",
1155
+ size: "small",
1156
+ title: fullscreen ? "退出全屏" : "全屏",
1157
+ onClick: () => {
1158
+ onToggleFullscreen();
1159
+ },
1160
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(fullscreen ? _ant_design_icons.FullscreenExitOutlined : _ant_design_icons.FullscreenOutlined, {})
1161
+ });
1162
+ }, [fullscreen, onToggleFullscreen]);
1163
+ function isSeparator(item) {
1164
+ return !!(!item.id && item.separatorBefore);
1165
+ }
1166
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1167
+ className: "toolbar",
1168
+ children: [
1169
+ config.map((item, idx) => {
1170
+ if (isSeparator(item)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "toolbar-separator" }, `sep-${idx}`);
1171
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarItemRenderer, {
1172
+ item,
1173
+ onClick: handleClick
1174
+ }, item.id);
1175
+ }),
1176
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TableDropdown, { editorView }),
1177
+ extraItemsConfig?.map((item, idx) => {
1178
+ if (isSeparator(item)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "toolbar-separator" }, `extra-sep-${idx}`);
1179
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarItemRenderer, {
1180
+ item,
1181
+ onClick: handleClick
1182
+ }, item.id);
1183
+ }),
1184
+ renderFullscreen()
1185
+ ]
1186
+ });
980
1187
  }
981
-
982
- // src/components/Dialog/EditDialog.tsx
983
- var import_react7 = require("react");
984
- var import_antd6 = require("antd");
985
- var import_jsx_runtime9 = require("react/jsx-runtime");
1188
+ //#endregion
1189
+ //#region src/components/Dialog/EditDialog.tsx
1190
+ /**
1191
+ * 组件类型到 antd 表单组件的映射
1192
+ */
1193
+ var FormComponents = {
1194
+ input: antd.Input,
1195
+ textarea: antd.Input.TextArea,
1196
+ number: antd.InputNumber,
1197
+ select: antd.Select,
1198
+ switch: antd.Switch,
1199
+ slider: antd.Slider,
1200
+ rate: antd.Rate,
1201
+ color: antd.ColorPicker,
1202
+ date: antd.DatePicker,
1203
+ dateRange: antd.DatePicker.RangePicker
1204
+ };
1205
+ /**
1206
+ * 渲染对应类型的 antd 表单组件
1207
+ */
1208
+ function renderFormItemComponent(field) {
1209
+ const props = field.componentProps || {};
1210
+ const Component = field.componentType ? FormComponents[field.componentType] : antd.Input;
1211
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, { ...props });
1212
+ }
1213
+ /**
1214
+ * 根据 fields 自动生成 Form.Item 列表
1215
+ */
1216
+ function renderAutoForm(form, fields) {
1217
+ if (!fields || fields.length === 0) return null;
1218
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Form, {
1219
+ form,
1220
+ layout: "vertical",
1221
+ children: fields.map((field) => {
1222
+ if (!field.componentType) return null;
1223
+ const formItemProps = {
1224
+ name: field.key,
1225
+ label: field.name || field.key,
1226
+ valuePropName: field.componentType === "switch" ? "checked" : void 0,
1227
+ ...field.componentProps
1228
+ };
1229
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Form.Item, {
1230
+ ...formItemProps,
1231
+ children: renderFormItemComponent(field)
1232
+ }, field.key);
1233
+ })
1234
+ });
1235
+ }
1236
+ /**
1237
+ * 属性编辑弹窗组件
1238
+ * 根据组件 schema 的 renderDialog 函数渲染弹窗内容
1239
+ */
986
1240
  function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
987
- const schema = componentRegistry_default.get(typeName);
988
- const [formProps, setFormProps] = (0, import_react7.useState)({});
989
- const mergedProps = (0, import_react7.useMemo)(() => {
990
- var _a;
991
- if (!schema) return currentProps;
992
- const defaults = {};
993
- for (const field of schema.fields) {
994
- defaults[field.key] = (_a = field.defaultValue) != null ? _a : "";
995
- }
996
- return { ...defaults, ...currentProps };
997
- }, [schema, currentProps]);
998
- const handleDialogChange = (key, value) => {
999
- setFormProps((prev) => ({ ...prev, [key]: value }));
1000
- };
1001
- const handleOk = () => {
1002
- const finalProps = visible ? { ...mergedProps, ...formProps } : mergedProps;
1003
- onSave(finalProps);
1004
- setFormProps({});
1005
- };
1006
- const handleClose = () => {
1007
- setFormProps({});
1008
- onClose();
1009
- };
1010
- if (!schema) {
1011
- return null;
1012
- }
1013
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1014
- import_antd6.Modal,
1015
- {
1016
- title: `${schema.label} \u5C5E\u6027\u7F16\u8F91`,
1017
- open: visible,
1018
- onOk: handleOk,
1019
- onCancel: handleClose,
1020
- okText: "\u4FDD\u5B58",
1021
- cancelText: "\u53D6\u6D88",
1022
- children: schema.renderDialog(visible ? { ...mergedProps, ...formProps } : mergedProps, handleDialogChange)
1023
- }
1024
- );
1241
+ const schema = componentRegistry.get(typeName);
1242
+ const [formProps, setFormProps] = (0, react.useState)({});
1243
+ const [form] = antd.Form.useForm();
1244
+ const onSaveRef = (0, react.useRef)(onSave);
1245
+ onSaveRef.current = onSave;
1246
+ const dirtyRef = (0, react.useRef)(false);
1247
+ const mergedProps = (0, react.useMemo)(() => {
1248
+ if (!schema) return currentProps;
1249
+ const defaults = {};
1250
+ for (const field of schema.fields || []) defaults[field.key] = field.defaultValue ?? "";
1251
+ return {
1252
+ ...defaults,
1253
+ ...currentProps
1254
+ };
1255
+ }, [schema, currentProps]);
1256
+ (0, react.useEffect)(() => {
1257
+ if (visible) {
1258
+ form.setFieldsValue(mergedProps);
1259
+ setFormProps({});
1260
+ }
1261
+ }, [
1262
+ visible,
1263
+ mergedProps,
1264
+ form
1265
+ ]);
1266
+ const handleDialogChange = (key, value) => {
1267
+ setFormProps((prev) => ({
1268
+ ...prev,
1269
+ [key]: value
1270
+ }));
1271
+ };
1272
+ const handleOk = async () => {
1273
+ let finalProps = visible ? {
1274
+ ...mergedProps,
1275
+ ...formProps
1276
+ } : mergedProps;
1277
+ try {
1278
+ const formValues = await form.validateFields();
1279
+ finalProps = {
1280
+ ...finalProps,
1281
+ ...formValues
1282
+ };
1283
+ } catch {}
1284
+ onSaveRef.current(finalProps);
1285
+ dirtyRef.current = false;
1286
+ onClose();
1287
+ };
1288
+ const handleClose = () => {
1289
+ setFormProps({});
1290
+ dirtyRef.current = false;
1291
+ onClose();
1292
+ };
1293
+ if (!schema) return null;
1294
+ const contextSave = (props) => {
1295
+ setFormProps((prev) => ({
1296
+ ...prev,
1297
+ ...props
1298
+ }));
1299
+ dirtyRef.current = true;
1300
+ };
1301
+ const context = {
1302
+ props: mergedProps,
1303
+ form,
1304
+ onChange: handleDialogChange,
1305
+ onSave: contextSave
1306
+ };
1307
+ const renderDialogContent = () => {
1308
+ if (schema.renderDialog) return schema.renderDialog(context);
1309
+ if (schema.fields?.some((f) => f.componentType) && schema.fields) return renderAutoForm(form, schema.fields);
1310
+ return null;
1311
+ };
1312
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Modal, {
1313
+ title: `${schema.label} 属性编辑`,
1314
+ open: visible,
1315
+ onOk: handleOk,
1316
+ onCancel: handleClose,
1317
+ okText: "保存",
1318
+ cancelText: "取消",
1319
+ children: renderDialogContent()
1320
+ });
1025
1321
  }
1026
-
1027
- // src/utils/writeContainer.ts
1322
+ //#endregion
1323
+ //#region src/utils/writeContainer.ts
1324
+ /**
1325
+ * 从属性对象生成 JSON 属性行。
1326
+ * 格式: { key: 'value', key2: 'value2' }
1327
+ */
1028
1328
  function writeContainerProps(props) {
1029
- const entries = Object.entries(props).map(([k, v]) => `${k}: '${v}'`).join(", ");
1030
- return entries ? `{ ${entries} }` : "{}";
1329
+ const entries = Object.entries(props).map(([k, v]) => `${k}: '${v}'`).join(", ");
1330
+ return entries ? `{ ${entries} }` : "{}";
1031
1331
  }
1332
+ /**
1333
+ * 更新编辑器文档中的容器属性。
1334
+ * 替换 JSON 属性行(::: 打开行之后的一行)为新属性。
1335
+ */
1032
1336
  function updateContainerProps(view, propsLineFrom, propsLineTo, newProps) {
1033
- const newPropsLine = writeContainerProps(newProps);
1034
- view.dispatch({
1035
- changes: {
1036
- from: propsLineFrom,
1037
- to: propsLineTo,
1038
- insert: newPropsLine
1039
- }
1040
- });
1337
+ const newPropsLine = writeContainerProps(newProps);
1338
+ view.dispatch({ changes: {
1339
+ from: propsLineFrom,
1340
+ to: propsLineTo,
1341
+ insert: newPropsLine
1342
+ } });
1041
1343
  }
1344
+ /**
1345
+ * 在当前光标位置插入新容器。
1346
+ * 创建 :::type 打开行 + JSON 属性 + ::: 关闭行的结构。
1347
+ */
1042
1348
  function insertContainer(view, typeName, defaultProps) {
1043
- const { from } = view.state.selection.main;
1044
- const propsLine = writeContainerProps(defaultProps);
1045
- const container = `:::${typeName}
1046
- ${propsLine}
1047
- :::
1048
- `;
1049
- view.dispatch({ changes: { from, insert: container } });
1349
+ const { from } = view.state.selection.main;
1350
+ const container = `:::${typeName}\n${writeContainerProps(defaultProps)}\n:::\n`;
1351
+ view.dispatch({ changes: {
1352
+ from,
1353
+ insert: container
1354
+ } });
1050
1355
  }
1051
-
1052
- // src/MarkdownEditor.tsx
1053
- var import_icons5 = require("@ant-design/icons");
1054
- var import_jsx_runtime10 = require("react/jsx-runtime");
1055
- var MarkdownEditor_default = (0, import_react8.forwardRef)(function MarkdownEditor({
1056
- defaultValue = "",
1057
- value,
1058
- onChange,
1059
- showPreview = true,
1060
- fullscreen: fullscreenProp = false
1061
- }, ref) {
1062
- const [content, setContent] = (0, import_react8.useState)(value != null ? value : defaultValue);
1063
- const [editorView, setEditorView] = (0, import_react8.useState)(null);
1064
- const scrollDOMRef = (0, import_react8.useRef)(null);
1065
- const previewElRef = (0, import_react8.useRef)(null);
1066
- const [fullscreen, setFullscreen] = (0, import_react8.useState)(false);
1067
- const [dialogVisible, setDialogVisible] = (0, import_react8.useState)(false);
1068
- const [dialogTypeName, setDialogTypeName] = (0, import_react8.useState)("");
1069
- const [dialogProps, setDialogProps] = (0, import_react8.useState)({});
1070
- const [dialogPropsRange, setDialogPropsRange] = (0, import_react8.useState)([0, 0]);
1071
- (0, import_react8.useEffect)(() => {
1072
- if (value !== void 0) {
1073
- setContent(value);
1074
- }
1075
- }, [value]);
1076
- const internalSetContent = (0, import_react8.useCallback)((newContent) => {
1077
- setContent(newContent);
1078
- onChange == null ? void 0 : onChange(newContent);
1079
- }, [onChange]);
1080
- (0, import_react8.useImperativeHandle)(ref, () => ({
1081
- getContent: () => content
1082
- }), [content]);
1083
- const toggleFullscreen = (0, import_react8.useCallback)(() => {
1084
- if (document.fullscreenElement) {
1085
- document.exitFullscreen().catch((e) => {
1086
- console.error("\u9000\u51FA\u5168\u5C4F\u5931\u8D25:", e);
1087
- });
1088
- } else {
1089
- const el = document.documentElement;
1090
- const requestMethod = el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen || el.msRequestFullscreen;
1091
- if (requestMethod) {
1092
- requestMethod.call(el).catch((e) => {
1093
- console.error("\u8FDB\u5165\u5168\u5C4F\u5931\u8D25:", e);
1094
- });
1095
- }
1096
- }
1097
- }, []);
1098
- (0, import_react8.useEffect)(() => {
1099
- const handleFullscreenChange = () => {
1100
- setFullscreen(!!document.fullscreenElement);
1101
- };
1102
- document.addEventListener("fullscreenchange", handleFullscreenChange);
1103
- document.addEventListener("webkitfullscreenchange", handleFullscreenChange);
1104
- return () => {
1105
- document.removeEventListener("fullscreenchange", handleFullscreenChange);
1106
- document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
1107
- };
1108
- }, []);
1109
- const extraToolbarItems = componentRegistry_default.getAll().map((schema) => {
1110
- var _a, _b;
1111
- const defaultProps = {};
1112
- for (const field of schema.fields) {
1113
- defaultProps[field.key] = (_a = field.defaultValue) != null ? _a : "";
1114
- }
1115
- const iconNode = (_b = schema.icon) != null ? _b : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_icons5.ReadOutlined, {});
1116
- return {
1117
- id: `container-${schema.name}`,
1118
- label: schema.label,
1119
- icon: () => iconNode,
1120
- handler: (view, _state) => insertContainer(view, schema.name, defaultProps)
1121
- };
1122
- });
1123
- const handleScrollReady = (0, import_react8.useCallback)((scrollDOM) => {
1124
- scrollDOMRef.current = scrollDOM;
1125
- }, []);
1126
- (0, import_react8.useEffect)(() => {
1127
- const source = scrollDOMRef.current;
1128
- const target = previewElRef.current;
1129
- if (!source || !target) return;
1130
- let rafId = null;
1131
- const listener = () => {
1132
- if (rafId) return;
1133
- rafId = requestAnimationFrame(() => {
1134
- const s = scrollDOMRef.current;
1135
- const t = previewElRef.current;
1136
- if (!s || !t) {
1137
- rafId = null;
1138
- return;
1139
- }
1140
- const sourceScrollHeight = s.scrollHeight - s.clientHeight;
1141
- if (sourceScrollHeight <= 0) {
1142
- rafId = null;
1143
- return;
1144
- }
1145
- const ratio = s.scrollTop / sourceScrollHeight;
1146
- const targetScrollHeight = t.scrollHeight - t.clientHeight;
1147
- if (targetScrollHeight > 0) {
1148
- t.scrollTop = ratio * targetScrollHeight;
1149
- }
1150
- rafId = null;
1151
- });
1152
- };
1153
- source.addEventListener("scroll", listener, { passive: true });
1154
- return () => {
1155
- source.removeEventListener("scroll", listener);
1156
- if (rafId) cancelAnimationFrame(rafId);
1157
- };
1158
- }, [previewElRef.current]);
1159
- const handlePreviewRef = (0, import_react8.useCallback)((node) => {
1160
- previewElRef.current = node;
1161
- }, []);
1162
- const handleViewReady = (0, import_react8.useCallback)((view) => {
1163
- setEditorView(view);
1164
- }, []);
1165
- const handleEditContainer = (0, import_react8.useCallback)((_view, typeName, propsLineFrom, propsLineTo, currentProps) => {
1166
- setDialogTypeName(typeName);
1167
- setDialogProps({ ...currentProps });
1168
- setDialogPropsRange([propsLineFrom, propsLineTo]);
1169
- setDialogVisible(true);
1170
- }, []);
1171
- const handleDialogSave = (0, import_react8.useCallback)((newProps) => {
1172
- setDialogVisible(false);
1173
- if (editorView) {
1174
- updateContainerProps(editorView, dialogPropsRange[0], dialogPropsRange[1], newProps);
1175
- }
1176
- }, [editorView, dialogPropsRange]);
1177
- const handleDialogClose = (0, import_react8.useCallback)(() => {
1178
- setDialogVisible(false);
1179
- }, []);
1180
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: fullscreen ? "app fullscreen" : "app", children: [
1181
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1182
- Toolbar,
1183
- {
1184
- editorView,
1185
- fullscreen,
1186
- onToggleFullscreen: fullscreenProp ? toggleFullscreen : void 0,
1187
- extraItems: extraToolbarItems
1188
- }
1189
- ),
1190
- showPreview ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1191
- SplitPanels,
1192
- {
1193
- leftPanel: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1194
- Editor,
1195
- {
1196
- value: content,
1197
- onChange: internalSetContent,
1198
- onScrollReady: handleScrollReady,
1199
- onViewReady: handleViewReady,
1200
- onEditContainer: handleEditContainer
1201
- }
1202
- ),
1203
- rightPanel: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1204
- Preview_default,
1205
- {
1206
- content,
1207
- ref: handlePreviewRef
1208
- }
1209
- )
1210
- }
1211
- ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1212
- Editor,
1213
- {
1214
- value: content,
1215
- onChange: internalSetContent,
1216
- onScrollReady: handleScrollReady,
1217
- onViewReady: handleViewReady,
1218
- onEditContainer: handleEditContainer
1219
- }
1220
- ),
1221
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1222
- EditDialog,
1223
- {
1224
- visible: dialogVisible,
1225
- typeName: dialogTypeName,
1226
- currentProps: dialogProps,
1227
- onSave: handleDialogSave,
1228
- onClose: handleDialogClose
1229
- }
1230
- )
1231
- ] });
1356
+ //#endregion
1357
+ //#region src/hooks/useFullscreen.ts
1358
+ function useFullscreen() {
1359
+ const [fullscreen, setFullscreen] = (0, react.useState)(false);
1360
+ const toggleFullscreen = (0, react.useCallback)(() => {
1361
+ if (document.fullscreenElement) document.exitFullscreen().catch((e) => {
1362
+ console.error("退出全屏失败:", e);
1363
+ });
1364
+ else {
1365
+ const el = document.documentElement;
1366
+ const requestMethod = el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen || el.msRequestFullscreen;
1367
+ if (requestMethod) requestMethod.call(el).catch((e) => {
1368
+ console.error("进入全屏失败:", e);
1369
+ });
1370
+ }
1371
+ }, []);
1372
+ (0, react.useEffect)(() => {
1373
+ const handleFullscreenChange = () => {
1374
+ setFullscreen(!!document.fullscreenElement);
1375
+ };
1376
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
1377
+ document.addEventListener("webkitfullscreenchange", handleFullscreenChange);
1378
+ return () => {
1379
+ document.removeEventListener("fullscreenchange", handleFullscreenChange);
1380
+ document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
1381
+ };
1382
+ }, []);
1383
+ return {
1384
+ fullscreen,
1385
+ toggleFullscreen
1386
+ };
1387
+ }
1388
+ //#endregion
1389
+ //#region src/hooks/useScrollSync.ts
1390
+ /**
1391
+ * 双向滚动同步 Hook
1392
+ * 编辑器与预览区互相同步滚动位置(基于比例)
1393
+ * 使用 síncing 锁防止双向循环触发
1394
+ */
1395
+ function useScrollSync(sourceRef, targetRef) {
1396
+ const rafIdRef = (0, react.useRef)(null);
1397
+ const syncingRef = (0, react.useRef)(null);
1398
+ const syncScroll = (from, fromEl, toEl) => {
1399
+ if (rafIdRef.current) return;
1400
+ rafIdRef.current = requestAnimationFrame(() => {
1401
+ const fromScrollHeight = fromEl.scrollHeight - fromEl.clientHeight;
1402
+ if (fromScrollHeight <= 0) {
1403
+ rafIdRef.current = null;
1404
+ syncingRef.current = null;
1405
+ return;
1406
+ }
1407
+ const ratio = fromEl.scrollTop / fromScrollHeight;
1408
+ const toScrollHeight = toEl.scrollHeight - toEl.clientHeight;
1409
+ if (toScrollHeight > 0) {
1410
+ syncingRef.current = from;
1411
+ toEl.scrollTop = ratio * toScrollHeight;
1412
+ requestAnimationFrame(() => {
1413
+ syncingRef.current = null;
1414
+ });
1415
+ }
1416
+ rafIdRef.current = null;
1417
+ });
1418
+ };
1419
+ (0, react.useEffect)(() => {
1420
+ const source = sourceRef.current;
1421
+ const target = targetRef.current;
1422
+ if (!source || !target) return;
1423
+ const onSourceScroll = () => {
1424
+ if (syncingRef.current !== null) return;
1425
+ const s = sourceRef.current;
1426
+ const t = targetRef.current;
1427
+ syncScroll("source", s, t);
1428
+ };
1429
+ const onTargetScroll = () => {
1430
+ if (syncingRef.current !== null) return;
1431
+ const s = sourceRef.current;
1432
+ const t = targetRef.current;
1433
+ syncScroll("target", t, s);
1434
+ };
1435
+ source.addEventListener("scroll", onSourceScroll, { passive: true });
1436
+ target.addEventListener("scroll", onTargetScroll, { passive: true });
1437
+ return () => {
1438
+ source.removeEventListener("scroll", onSourceScroll);
1439
+ target.removeEventListener("scroll", onTargetScroll);
1440
+ if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
1441
+ };
1442
+ }, [sourceRef, targetRef]);
1443
+ }
1444
+ //#endregion
1445
+ //#region src/MarkdownEditor.tsx
1446
+ var MarkdownEditor_default = (0, react.forwardRef)(function MarkdownEditor({ defaultValue = "", value, onChange, showPreview = true, fullscreen: fullscreenProp = true, previewOptions, uploadConfig }, ref) {
1447
+ const [content, setContent] = (0, react.useState)(value ?? defaultValue);
1448
+ const [editorView, setEditorView] = (0, react.useState)(null);
1449
+ const scrollDOMRef = (0, react.useRef)(null);
1450
+ const previewElRef = (0, react.useRef)(null);
1451
+ const { fullscreen, toggleFullscreen } = useFullscreen();
1452
+ useScrollSync(scrollDOMRef, previewElRef);
1453
+ const [dialogVisible, setDialogVisible] = (0, react.useState)(false);
1454
+ const [dialogTypeName, setDialogTypeName] = (0, react.useState)("");
1455
+ const [dialogProps, setDialogProps] = (0, react.useState)({});
1456
+ const [dialogPropsRange, setDialogPropsRange] = (0, react.useState)([0, 0]);
1457
+ (0, react.useEffect)(() => {
1458
+ if (value !== void 0) setContent(value);
1459
+ }, [value]);
1460
+ const internalSetContent = (0, react.useCallback)((newContent) => {
1461
+ setContent(newContent);
1462
+ onChange?.(newContent);
1463
+ }, [onChange]);
1464
+ (0, react.useImperativeHandle)(ref, () => ({ getContent: () => content }), [content]);
1465
+ const extraToolbarItems = componentRegistry.getAll().flatMap((schema) => {
1466
+ if (schema.wrapTags) return {
1467
+ id: `wrap-${schema.name}`,
1468
+ label: schema.label,
1469
+ icon: () => schema.icon ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ant_design_icons.ReadOutlined, {}),
1470
+ wrapTags: schema.wrapTags
1471
+ };
1472
+ const defaultProps = {};
1473
+ for (const field of schema?.fields ?? []) defaultProps[field.key] = field.defaultValue ?? "";
1474
+ return {
1475
+ id: `container-${schema.name}`,
1476
+ label: schema.label,
1477
+ icon: () => schema.icon ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_ant_design_icons.ReadOutlined, {}),
1478
+ handler: (view, _state) => insertContainer(view, schema.name, defaultProps)
1479
+ };
1480
+ });
1481
+ const handleScrollReady = (0, react.useCallback)((scrollDOM) => {
1482
+ scrollDOMRef.current = scrollDOM;
1483
+ }, []);
1484
+ const handlePreviewRef = (0, react.useCallback)((node) => {
1485
+ previewElRef.current = node;
1486
+ }, []);
1487
+ const handleViewReady = (0, react.useCallback)((view) => {
1488
+ setEditorView(view);
1489
+ }, []);
1490
+ const handleEditContainer = (0, react.useCallback)((_view, typeName, propsLineFrom, propsLineTo, currentProps) => {
1491
+ setDialogTypeName(typeName);
1492
+ setDialogProps({ ...currentProps });
1493
+ setDialogPropsRange([propsLineFrom, propsLineTo]);
1494
+ setDialogVisible(true);
1495
+ }, []);
1496
+ const handleDialogSave = (0, react.useCallback)((newProps) => {
1497
+ if (editorView) updateContainerProps(editorView, dialogPropsRange[0], dialogPropsRange[1], newProps);
1498
+ }, [editorView, dialogPropsRange]);
1499
+ const handleDialogClose = (0, react.useCallback)(() => {
1500
+ setDialogVisible(false);
1501
+ }, []);
1502
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1503
+ className: fullscreen ? "markdown-editor-app fullscreen" : "markdown-editor-app",
1504
+ children: [
1505
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toolbar, {
1506
+ editorView,
1507
+ fullscreen,
1508
+ onToggleFullscreen: fullscreenProp ? toggleFullscreen : void 0,
1509
+ extraItems: extraToolbarItems,
1510
+ uploadConfig
1511
+ }),
1512
+ showPreview ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SplitPanels, {
1513
+ leftPanel: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Editor, {
1514
+ value: content,
1515
+ onChange: internalSetContent,
1516
+ onScrollReady: handleScrollReady,
1517
+ onViewReady: handleViewReady,
1518
+ onEditContainer: handleEditContainer,
1519
+ uploadConfig,
1520
+ onMessageNoUploadConfig: () => antd.message.warning("请先配置图片上传功能")
1521
+ }),
1522
+ rightPanel: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Preview, {
1523
+ content,
1524
+ ref: handlePreviewRef,
1525
+ previewOptions
1526
+ })
1527
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Editor, {
1528
+ value: content,
1529
+ onChange: internalSetContent,
1530
+ onScrollReady: handleScrollReady,
1531
+ onViewReady: handleViewReady,
1532
+ onEditContainer: handleEditContainer,
1533
+ uploadConfig,
1534
+ onMessageNoUploadConfig: () => antd.message.warning("请先配置图片上传功能")
1535
+ }),
1536
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditDialog, {
1537
+ visible: dialogVisible,
1538
+ typeName: dialogTypeName,
1539
+ currentProps: dialogProps,
1540
+ onSave: handleDialogSave,
1541
+ onClose: handleDialogClose
1542
+ })
1543
+ ]
1544
+ });
1232
1545
  });
1233
-
1234
- // src/hooks/useRegisterToolbar.ts
1235
- var import_react9 = require("react");
1546
+ //#endregion
1547
+ //#region src/hooks/useRegisterToolbar.ts
1548
+ /**
1549
+ * 注册自定义容器组件到工具栏。支持单个或批量注册。
1550
+ * 支持两种模式:
1551
+ *
1552
+ * 1. 容器模式(linkCard):name + fields + renderDialog
1553
+ * 插入 :::name 格式的容器,支持弹窗编辑属性
1554
+ *
1555
+ * 2. 标签包裹模式:name + wrapTags
1556
+ * 插入简单的标签包裹文本,如 <plugin-container>...</plugin-container>
1557
+ *
1558
+ * @example 容器模式 - 使用 fields 自动生成表单(最简)
1559
+ * ```tsx
1560
+ * const schema: ComponentSchema = {
1561
+ * name: 'alert',
1562
+ * label: '警告框',
1563
+ * icon: <AlertOutlined />,
1564
+ * fields: [
1565
+ * { key: 'type', name: '类型', componentType: 'select',
1566
+ * componentProps: { options: [{ value: 'price', label: '价格' }, { value: 'info', label: '信息' }] } },
1567
+ * { key: 'content', name: '内容', componentType: 'textarea',
1568
+ * componentProps: { rows: 3 } },
1569
+ * { key: 'enabled', name: '启用', componentType: 'switch' },
1570
+ * ],
1571
+ * }
1572
+ * // 支持的 componentType: 'input' | 'textarea' | 'number' | 'select' | 'switch' | 'slider' | 'rate' | 'color' | 'date' | 'dateRange'
1573
+ * ```
1574
+ *
1575
+ * @example 容器模式 - 使用 Form.Item 自动绑定(推荐)
1576
+ * ```tsx
1577
+ * const schema: ComponentSchema = {
1578
+ * name: 'alert',
1579
+ * label: '警告框',
1580
+ * icon: <AlertOutlined />,
1581
+ * fields: [{ key: 'level', defaultValue: 'info' }],
1582
+ * renderDialog: ({ form }) => (
1583
+ * <Form form={form} layout="vertical">
1584
+ * <Form.Item name="level" label="级别">
1585
+ * <Input />
1586
+ * </Form.Item>
1587
+ * </Form>
1588
+ * ),
1589
+ * }
1590
+ *
1591
+ * function MyPage() {
1592
+ * useRegisterToolbar(schema)
1593
+ * return <MarkdownEditor />
1594
+ * }
1595
+ * ```
1596
+ *
1597
+ * @example 容器模式 - 手动控制
1598
+ * ```tsx
1599
+ * const schema: ComponentSchema = {
1600
+ * name: 'alert',
1601
+ * label: '警告框',
1602
+ * icon: <AlertOutlined />,
1603
+ * fields: [{ key: 'level', defaultValue: 'info' }],
1604
+ * renderDialog: ({ props, onChange }) => (
1605
+ * <Input value={props.level} onChange={e => onChange('level', e.target.value)} />
1606
+ * ),
1607
+ * }
1608
+ * ```
1609
+ *
1610
+ * @example 容器模式 - 完全自定义弹窗
1611
+ * ```tsx
1612
+ * const schema: ComponentSchema = {
1613
+ * name: 'alert',
1614
+ * label: '警告框',
1615
+ * icon: <AlertOutlined />,
1616
+ * fields: [{ key: 'level', defaultValue: 'info' }],
1617
+ * renderDialog: ({ props, onSave }) => (
1618
+ * <MyCustomEditor
1619
+ * value={props.level}
1620
+ * onSave={({ level }) => onSave({ level })}
1621
+ * />
1622
+ * ),
1623
+ * }
1624
+ * ```
1625
+ *
1626
+ * @example 标签包裹模式
1627
+ * ```tsx
1628
+ * function MyPage() {
1629
+ * useRegisterToolbar({
1630
+ * name: 'plugin-container',
1631
+ * label: '自定义容器',
1632
+ * icon: <AlertOutlined />,
1633
+ * wrapTags: ['<plugin-container>', '</plugin-container>'],
1634
+ * })
1635
+ * return <MarkdownEditor />
1636
+ * }
1637
+ * ```
1638
+ *
1639
+ * @example 批量注册
1640
+ * ```tsx
1641
+ * function MyPage() {
1642
+ * useRegisterToolbar([schema1, schema2, schema3])
1643
+ * return <MarkdownEditor />
1644
+ * }
1645
+ * ```
1646
+ */
1236
1647
  function useRegisterToolbar(schemas) {
1237
- const schemaList = Array.isArray(schemas) ? schemas : [schemas];
1238
- (0, import_react9.useEffect)(() => {
1239
- schemaList.forEach((schema) => {
1240
- componentRegistry_default.register(schema.name, schema);
1241
- });
1242
- return () => {
1243
- schemaList.forEach((schema) => {
1244
- componentRegistry_default.unregister(schema.name);
1245
- });
1246
- };
1247
- }, [schemaList]);
1648
+ const schemaList = Array.isArray(schemas) ? schemas : [schemas];
1649
+ (0, react.useEffect)(() => {
1650
+ schemaList.forEach((schema) => {
1651
+ componentRegistry.register(schema.name, schema);
1652
+ });
1653
+ return () => {
1654
+ schemaList.forEach((schema) => {
1655
+ componentRegistry.unregister(schema.name);
1656
+ });
1657
+ };
1658
+ }, [schemaList]);
1248
1659
  }
1249
- // Annotate the CommonJS export names for ESM import in node:
1250
- 0 && (module.exports = {
1251
- MarkdownEditor,
1252
- useRegisterToolbar
1253
- });
1660
+ //#endregion
1661
+ exports.MarkdownEditor = MarkdownEditor_default;
1662
+ exports.useRegisterToolbar = useRegisterToolbar;
1663
+
1254
1664
  //# sourceMappingURL=index.cjs.map