remons-markdown-editor 1.0.0-beta.2 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +386 -377
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +0 -220
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +84 -25
- package/dist/index.d.ts +84 -25
- package/dist/index.js +381 -373
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/readme.md +164 -7
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/MarkdownEditor.tsx
|
|
2
|
-
import { useState as
|
|
2
|
+
import { useState as useState4, useRef as useRef5, useCallback as useCallback6, useEffect as useEffect6, forwardRef as forwardRef2, useImperativeHandle } from "react";
|
|
3
|
+
import { message } from "antd";
|
|
3
4
|
|
|
4
5
|
// src/components/Editor/index.tsx
|
|
5
6
|
import { useEffect, useRef } from "react";
|
|
@@ -11,6 +12,7 @@ import { EditorView, keymap } from "@codemirror/view";
|
|
|
11
12
|
import { defaultKeymap, indentWithTab } from "@codemirror/commands";
|
|
12
13
|
import { markdown } from "@codemirror/lang-markdown";
|
|
13
14
|
import { oneDark } from "@codemirror/theme-one-dark";
|
|
15
|
+
import { Facet as Facet2 } from "@codemirror/state";
|
|
14
16
|
|
|
15
17
|
// src/components/Editor/containerWidget.ts
|
|
16
18
|
import { ViewPlugin, Decoration, WidgetType } from "@codemirror/view";
|
|
@@ -88,8 +90,6 @@ function findContainers(docText) {
|
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
// src/registry/componentRegistry.tsx
|
|
91
|
-
import { Input, Select } from "antd";
|
|
92
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
93
93
|
var ComponentRegistry = class {
|
|
94
94
|
constructor() {
|
|
95
95
|
this.schemas = /* @__PURE__ */ new Map();
|
|
@@ -132,80 +132,6 @@ var ComponentRegistry = class {
|
|
|
132
132
|
}
|
|
133
133
|
};
|
|
134
134
|
var componentRegistry = new ComponentRegistry();
|
|
135
|
-
componentRegistry.register("linkCard", {
|
|
136
|
-
name: "linkCard",
|
|
137
|
-
label: "\u94FE\u63A5\u5361\u7247",
|
|
138
|
-
fields: [
|
|
139
|
-
{ key: "title", defaultValue: "\u94FE\u63A5\u6807\u9898" },
|
|
140
|
-
{ key: "link", defaultValue: "https://example.com" },
|
|
141
|
-
{ key: "description", defaultValue: "\u94FE\u63A5\u63CF\u8FF0" },
|
|
142
|
-
{ key: "theme", defaultValue: "default" }
|
|
143
|
-
],
|
|
144
|
-
renderDialog: (props, onChange) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
145
|
-
/* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
|
|
146
|
-
/* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u6807\u9898" }),
|
|
147
|
-
/* @__PURE__ */ jsx(Input, { value: props.title, onChange: (e) => onChange("title", e.target.value) })
|
|
148
|
-
] }),
|
|
149
|
-
/* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
|
|
150
|
-
/* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u94FE\u63A5" }),
|
|
151
|
-
/* @__PURE__ */ jsx(Input, { value: props.link, onChange: (e) => onChange("link", e.target.value) })
|
|
152
|
-
] }),
|
|
153
|
-
/* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
|
|
154
|
-
/* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u63CF\u8FF0" }),
|
|
155
|
-
/* @__PURE__ */ jsx(Input.TextArea, { rows: 3, value: props.description, onChange: (e) => onChange("description", e.target.value) })
|
|
156
|
-
] }),
|
|
157
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
158
|
-
/* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u4E3B\u9898" }),
|
|
159
|
-
/* @__PURE__ */ jsx(
|
|
160
|
-
Select,
|
|
161
|
-
{
|
|
162
|
-
value: props.theme,
|
|
163
|
-
onChange: (v) => onChange("theme", v),
|
|
164
|
-
options: [
|
|
165
|
-
{ label: "\u9ED8\u8BA4", value: "default" },
|
|
166
|
-
{ label: "\u4E3B\u8981", value: "primary" },
|
|
167
|
-
{ label: "\u8B66\u544A", value: "warning" }
|
|
168
|
-
]
|
|
169
|
-
}
|
|
170
|
-
)
|
|
171
|
-
] })
|
|
172
|
-
] })
|
|
173
|
-
});
|
|
174
|
-
componentRegistry.register("note", {
|
|
175
|
-
name: "note",
|
|
176
|
-
label: "\u6CE8\u91CA",
|
|
177
|
-
fields: [
|
|
178
|
-
{ key: "type", defaultValue: "info" },
|
|
179
|
-
{ key: "title", defaultValue: "\u6CE8\u610F" },
|
|
180
|
-
{ key: "content", defaultValue: "\u5907\u6CE8\u5185\u5BB9" }
|
|
181
|
-
],
|
|
182
|
-
renderDialog: (props, onChange) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
183
|
-
/* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
|
|
184
|
-
/* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u7C7B\u578B" }),
|
|
185
|
-
/* @__PURE__ */ jsx(
|
|
186
|
-
Select,
|
|
187
|
-
{
|
|
188
|
-
value: props.type,
|
|
189
|
-
onChange: (v) => onChange("type", v),
|
|
190
|
-
options: [
|
|
191
|
-
{ label: "\u4FE1\u606F", value: "info" },
|
|
192
|
-
{ label: "\u63D0\u793A", value: "tip" },
|
|
193
|
-
{ label: "\u8B66\u544A", value: "warning" },
|
|
194
|
-
{ label: "\u5371\u9669", value: "danger" }
|
|
195
|
-
]
|
|
196
|
-
}
|
|
197
|
-
)
|
|
198
|
-
] }),
|
|
199
|
-
/* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
|
|
200
|
-
/* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u6807\u9898" }),
|
|
201
|
-
/* @__PURE__ */ jsx(Input, { value: props.title, onChange: (e) => onChange("title", e.target.value) })
|
|
202
|
-
] }),
|
|
203
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
204
|
-
/* @__PURE__ */ jsx("div", { style: { marginBottom: 4 }, children: "\u5185\u5BB9" }),
|
|
205
|
-
/* @__PURE__ */ jsx(Input.TextArea, { rows: 3, value: props.content, onChange: (e) => onChange("content", e.target.value) })
|
|
206
|
-
] })
|
|
207
|
-
] })
|
|
208
|
-
});
|
|
209
135
|
var componentRegistry_default = componentRegistry;
|
|
210
136
|
|
|
211
137
|
// src/components/Editor/containerWidget.ts
|
|
@@ -351,13 +277,151 @@ var containerWidgetPlugin = ViewPlugin.fromClass(
|
|
|
351
277
|
}
|
|
352
278
|
);
|
|
353
279
|
|
|
280
|
+
// src/components/Toolbar/buttons.tsx
|
|
281
|
+
function insertAtCursor(view, before, after = "", placeholder = "") {
|
|
282
|
+
const { from, to } = view.state.selection.main;
|
|
283
|
+
const selectedText = view.state.doc.sliceString(from, to);
|
|
284
|
+
const insert = selectedText ? `${before}${selectedText}${after}` : `${before}${placeholder}${after}`;
|
|
285
|
+
view.dispatch({ changes: { from, to, insert } });
|
|
286
|
+
if (!selectedText && placeholder) {
|
|
287
|
+
view.dispatch({ selection: { anchor: from + before.length } });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function insertAtLineStart(view, text) {
|
|
291
|
+
const { from } = view.state.selection.main;
|
|
292
|
+
const line = view.state.doc.lineAt(from);
|
|
293
|
+
view.dispatch({ changes: { from: line.from, to: line.from, insert: text } });
|
|
294
|
+
view.dispatch({ selection: { anchor: line.from + text.length } });
|
|
295
|
+
}
|
|
296
|
+
async function uploadFileToServer(file, uploadConfig) {
|
|
297
|
+
var _a;
|
|
298
|
+
if (uploadConfig.onUploadImage) {
|
|
299
|
+
return uploadConfig.onUploadImage(file);
|
|
300
|
+
}
|
|
301
|
+
if (uploadConfig.uploadUrl) {
|
|
302
|
+
const formData = new FormData();
|
|
303
|
+
formData.append("file", file);
|
|
304
|
+
const response = await fetch(uploadConfig.uploadUrl, {
|
|
305
|
+
method: "POST",
|
|
306
|
+
body: formData
|
|
307
|
+
});
|
|
308
|
+
if (!response.ok) return null;
|
|
309
|
+
const result = await response.json();
|
|
310
|
+
if (uploadConfig.onGetUploadUrl) {
|
|
311
|
+
return uploadConfig.onGetUploadUrl(result);
|
|
312
|
+
}
|
|
313
|
+
return ((_a = result.data) == null ? void 0 : _a.path) || null;
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
function isUploadConfigured(uploadConfig) {
|
|
318
|
+
return !!((uploadConfig == null ? void 0 : uploadConfig.uploadUrl) || (uploadConfig == null ? void 0 : uploadConfig.onUploadImage));
|
|
319
|
+
}
|
|
320
|
+
function uploadImage(view, uploadConfig) {
|
|
321
|
+
const input = document.createElement("input");
|
|
322
|
+
input.type = "file";
|
|
323
|
+
input.accept = "image/*";
|
|
324
|
+
input.style.display = "none";
|
|
325
|
+
input.addEventListener("change", async () => {
|
|
326
|
+
if (!input.files || input.files.length === 0) return;
|
|
327
|
+
const file = input.files[0];
|
|
328
|
+
const { from, to } = view.state.selection.main;
|
|
329
|
+
const selectedText = view.state.doc.sliceString(from, to) || "\u63CF\u8FF0";
|
|
330
|
+
const url = await uploadFileToServer(file, uploadConfig);
|
|
331
|
+
if (url) {
|
|
332
|
+
const insert = ``;
|
|
333
|
+
view.dispatch({ changes: { from, to, insert } });
|
|
334
|
+
}
|
|
335
|
+
input.remove();
|
|
336
|
+
});
|
|
337
|
+
document.body.appendChild(input);
|
|
338
|
+
input.click();
|
|
339
|
+
}
|
|
340
|
+
function wrapSelection(view, prefix, suffix) {
|
|
341
|
+
const { from, to, empty } = view.state.selection.main;
|
|
342
|
+
if (empty) return;
|
|
343
|
+
const selected = view.state.doc.sliceString(from, to);
|
|
344
|
+
view.dispatch({ changes: { from, to, insert: `${prefix}${selected}${suffix}` } });
|
|
345
|
+
}
|
|
346
|
+
function toggleWrap(view, openTag, closeTag) {
|
|
347
|
+
const { from, to, empty } = view.state.selection.main;
|
|
348
|
+
const doc = view.state.doc;
|
|
349
|
+
if (!empty) {
|
|
350
|
+
const selected = doc.sliceString(from, to);
|
|
351
|
+
if (selected.startsWith(openTag) && selected.endsWith(closeTag)) {
|
|
352
|
+
const inner = selected.slice(openTag.length, -closeTag.length);
|
|
353
|
+
view.dispatch({ changes: { from, to, insert: inner } });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
{
|
|
358
|
+
const cursorPos = empty ? from : to;
|
|
359
|
+
const line = doc.lineAt(cursorPos);
|
|
360
|
+
const lineText = doc.sliceString(line.from, line.to);
|
|
361
|
+
const openIdx = lineText.indexOf(openTag);
|
|
362
|
+
const closeIdx = lineText.lastIndexOf(closeTag);
|
|
363
|
+
if (openIdx >= 0 && closeIdx > openIdx) {
|
|
364
|
+
const absOpen = line.from + openIdx;
|
|
365
|
+
const absClose = line.from + closeIdx;
|
|
366
|
+
if (cursorPos >= absOpen + openTag.length && cursorPos <= absClose) {
|
|
367
|
+
const inner = doc.sliceString(absOpen + openTag.length, absClose);
|
|
368
|
+
view.dispatch({
|
|
369
|
+
changes: {
|
|
370
|
+
from: absOpen,
|
|
371
|
+
to: absClose + closeTag.length,
|
|
372
|
+
insert: inner
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
wrapSelection(view, openTag, closeTag);
|
|
380
|
+
}
|
|
381
|
+
|
|
354
382
|
// src/components/Editor/codemirrorExtensions.ts
|
|
383
|
+
var uploadConfigFacet = Facet2.define({
|
|
384
|
+
combine: (values) => values[values.length - 1]
|
|
385
|
+
});
|
|
386
|
+
var messageFacet = Facet2.define({
|
|
387
|
+
combine: (values) => values[values.length - 1]
|
|
388
|
+
});
|
|
389
|
+
var pasteHandler = EditorView.domEventHandlers({
|
|
390
|
+
paste: (event, view) => {
|
|
391
|
+
var _a;
|
|
392
|
+
const files = (_a = event.clipboardData) == null ? void 0 : _a.files;
|
|
393
|
+
if (!files) return false;
|
|
394
|
+
const imageFiles = Array.from(files).filter((f) => f.type.startsWith("image/"));
|
|
395
|
+
if (imageFiles.length === 0) return false;
|
|
396
|
+
event.preventDefault();
|
|
397
|
+
const uploadConfig = view.state.facet(uploadConfigFacet);
|
|
398
|
+
const showMessage = view.state.facet(messageFacet);
|
|
399
|
+
if (!isUploadConfigured(uploadConfig)) {
|
|
400
|
+
showMessage == null ? void 0 : showMessage();
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
const { from, to } = view.state.selection.main;
|
|
404
|
+
let offset = 0;
|
|
405
|
+
for (const file of imageFiles) {
|
|
406
|
+
uploadFileToServer(file, uploadConfig).then((url) => {
|
|
407
|
+
if (url) {
|
|
408
|
+
const name = file.name.replace(/\.[^.]+$/, "");
|
|
409
|
+
const insert = ``;
|
|
410
|
+
view.dispatch({ changes: { from: from + offset, to: to + offset, insert } });
|
|
411
|
+
offset += insert.length;
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
});
|
|
355
418
|
var getCodeMirrorExtensions = (opts) => {
|
|
356
419
|
const extensions = [
|
|
357
420
|
markdown(),
|
|
358
421
|
oneDark,
|
|
359
422
|
keymap.of([...defaultKeymap, indentWithTab]),
|
|
360
423
|
EditorView.lineWrapping,
|
|
424
|
+
pasteHandler,
|
|
361
425
|
EditorView.baseTheme({
|
|
362
426
|
".cm-scroller": {
|
|
363
427
|
height: "100%",
|
|
@@ -402,19 +466,25 @@ var getCodeMirrorExtensions = (opts) => {
|
|
|
402
466
|
if (opts == null ? void 0 : opts.onEditContainer) {
|
|
403
467
|
extensions.push(containerEditCallback.of(opts.onEditContainer));
|
|
404
468
|
}
|
|
469
|
+
if (opts == null ? void 0 : opts.uploadConfig) {
|
|
470
|
+
extensions.push(uploadConfigFacet.of(opts.uploadConfig));
|
|
471
|
+
}
|
|
472
|
+
if (opts == null ? void 0 : opts.onMessageNoUploadConfig) {
|
|
473
|
+
extensions.push(messageFacet.of(opts.onMessageNoUploadConfig));
|
|
474
|
+
}
|
|
405
475
|
return extensions;
|
|
406
476
|
};
|
|
407
477
|
|
|
408
478
|
// src/components/Editor/index.tsx
|
|
409
|
-
import { jsx
|
|
410
|
-
function Editor({ value, onChange, onScrollReady, onViewReady, onEditContainer }) {
|
|
479
|
+
import { jsx } from "react/jsx-runtime";
|
|
480
|
+
function Editor({ value, onChange, onScrollReady, onViewReady, onEditContainer, uploadConfig, onMessageNoUploadConfig }) {
|
|
411
481
|
const editorRef = useRef(null);
|
|
412
482
|
const viewRef = useRef(null);
|
|
413
483
|
useEffect(() => {
|
|
414
484
|
if (!editorRef.current) return;
|
|
415
485
|
const extensions = [
|
|
416
486
|
basicSetup,
|
|
417
|
-
...getCodeMirrorExtensions({ onEditContainer }),
|
|
487
|
+
...getCodeMirrorExtensions({ onEditContainer, uploadConfig, onMessageNoUploadConfig }),
|
|
418
488
|
EditorView2.updateListener.of((update) => {
|
|
419
489
|
if (update.docChanged) {
|
|
420
490
|
onChange(update.state.doc.toString());
|
|
@@ -447,30 +517,33 @@ function Editor({ value, onChange, onScrollReady, onViewReady, onEditContainer }
|
|
|
447
517
|
});
|
|
448
518
|
}
|
|
449
519
|
}, [value]);
|
|
450
|
-
return /* @__PURE__ */
|
|
520
|
+
return /* @__PURE__ */ jsx("div", { ref: editorRef, className: "editor" });
|
|
451
521
|
}
|
|
452
522
|
|
|
453
523
|
// src/components/Preview/index.tsx
|
|
454
|
-
import { forwardRef } from "react";
|
|
455
|
-
import { registerAll, excludedSelectors } from "remons-markdown-plugins";
|
|
524
|
+
import { forwardRef, useEffect as useEffect2 } from "react";
|
|
456
525
|
import RenderMarkdown, { languagesCommon, initHighlighter } from "remons-render-markdown";
|
|
457
|
-
import "remons-markdown-plugins/style.css";
|
|
458
526
|
import "remons-render-markdown/dist/index.css";
|
|
459
|
-
import { jsx as
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
527
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
528
|
+
var Preview = forwardRef(({ content, previewOptions }, ref) => {
|
|
529
|
+
useEffect2(() => {
|
|
530
|
+
initHighlighter({
|
|
531
|
+
...languagesCommon,
|
|
532
|
+
...(previewOptions == null ? void 0 : previewOptions.languages) ? previewOptions == null ? void 0 : previewOptions.languages : {}
|
|
533
|
+
});
|
|
534
|
+
}, [previewOptions == null ? void 0 : previewOptions.languages]);
|
|
535
|
+
return /* @__PURE__ */ jsx2("div", { ref, className: "preview", children: /* @__PURE__ */ jsx2(RenderMarkdown, { content, ...previewOptions }) });
|
|
463
536
|
});
|
|
464
537
|
Preview.displayName = "Preview";
|
|
465
538
|
var Preview_default = Preview;
|
|
466
539
|
|
|
467
540
|
// src/components/Panel/SplitPanels.tsx
|
|
468
541
|
import { Splitter } from "antd";
|
|
469
|
-
import { jsx as
|
|
542
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
470
543
|
function SplitPanels({ leftPanel, rightPanel }) {
|
|
471
|
-
return /* @__PURE__ */
|
|
472
|
-
/* @__PURE__ */
|
|
473
|
-
/* @__PURE__ */
|
|
544
|
+
return /* @__PURE__ */ jsxs(Splitter, { className: "split-panels", children: [
|
|
545
|
+
/* @__PURE__ */ jsx3(Splitter.Panel, { defaultSize: "50%", min: "20%", max: "80%", children: leftPanel }),
|
|
546
|
+
/* @__PURE__ */ jsx3(Splitter.Panel, { children: rightPanel })
|
|
474
547
|
] });
|
|
475
548
|
}
|
|
476
549
|
|
|
@@ -496,105 +569,10 @@ import {
|
|
|
496
569
|
BlockOutlined,
|
|
497
570
|
MinusOutlined,
|
|
498
571
|
FieldStringOutlined,
|
|
499
|
-
FontSizeOutlined
|
|
500
|
-
TagsOutlined
|
|
572
|
+
FontSizeOutlined
|
|
501
573
|
} from "@ant-design/icons";
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
function insertAtCursor(view, before, after = "", placeholder = "") {
|
|
505
|
-
const { from, to } = view.state.selection.main;
|
|
506
|
-
const selectedText = view.state.doc.sliceString(from, to);
|
|
507
|
-
const insert = selectedText ? `${before}${selectedText}${after}` : `${before}${placeholder}${after}`;
|
|
508
|
-
view.dispatch({ changes: { from, to, insert } });
|
|
509
|
-
if (!selectedText && placeholder) {
|
|
510
|
-
view.dispatch({ selection: { anchor: from + before.length } });
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
function insertAtLineStart(view, text) {
|
|
514
|
-
const { from } = view.state.selection.main;
|
|
515
|
-
const line = view.state.doc.lineAt(from);
|
|
516
|
-
view.dispatch({ changes: { from: line.from, to: line.from, insert: text } });
|
|
517
|
-
view.dispatch({ selection: { anchor: line.from + text.length } });
|
|
518
|
-
}
|
|
519
|
-
async function uploadFileToServer(file) {
|
|
520
|
-
var _a;
|
|
521
|
-
const formData = new FormData();
|
|
522
|
-
formData.append("file", file);
|
|
523
|
-
const response = await fetch("https://remons.cn:3008/content/uploadMarkdownImg", {
|
|
524
|
-
method: "POST",
|
|
525
|
-
body: formData
|
|
526
|
-
});
|
|
527
|
-
if (!response.ok) return null;
|
|
528
|
-
const result = await response.json();
|
|
529
|
-
if (result.code === 200 && result.success && ((_a = result.data) == null ? void 0 : _a.path)) {
|
|
530
|
-
return `https://remons.cn:3008${result.data.path}`;
|
|
531
|
-
}
|
|
532
|
-
return null;
|
|
533
|
-
}
|
|
534
|
-
function uploadImage(view) {
|
|
535
|
-
const input = document.createElement("input");
|
|
536
|
-
input.type = "file";
|
|
537
|
-
input.accept = "image/*";
|
|
538
|
-
input.style.display = "none";
|
|
539
|
-
input.addEventListener("change", async () => {
|
|
540
|
-
if (!input.files || input.files.length === 0) return;
|
|
541
|
-
const file = input.files[0];
|
|
542
|
-
const { from, to } = view.state.selection.main;
|
|
543
|
-
const selectedText = view.state.doc.sliceString(from, to) || "\u63CF\u8FF0";
|
|
544
|
-
const url = await uploadFileToServer(file);
|
|
545
|
-
if (url) {
|
|
546
|
-
const insert = ``;
|
|
547
|
-
view.dispatch({ changes: { from, to, insert } });
|
|
548
|
-
}
|
|
549
|
-
input.remove();
|
|
550
|
-
});
|
|
551
|
-
document.body.appendChild(input);
|
|
552
|
-
input.click();
|
|
553
|
-
}
|
|
554
|
-
function wrapSelection(view, prefix, suffix) {
|
|
555
|
-
const { from, to, empty } = view.state.selection.main;
|
|
556
|
-
if (empty) return;
|
|
557
|
-
const selected = view.state.doc.sliceString(from, to);
|
|
558
|
-
view.dispatch({ changes: { from, to, insert: `${prefix}${selected}${suffix}` } });
|
|
559
|
-
}
|
|
560
|
-
function toggleWrap(view, openTag, closeTag) {
|
|
561
|
-
const { from, to, empty } = view.state.selection.main;
|
|
562
|
-
const doc = view.state.doc;
|
|
563
|
-
if (!empty) {
|
|
564
|
-
const selected = doc.sliceString(from, to);
|
|
565
|
-
if (selected.startsWith(openTag) && selected.endsWith(closeTag)) {
|
|
566
|
-
const inner = selected.slice(openTag.length, -closeTag.length);
|
|
567
|
-
view.dispatch({ changes: { from, to, insert: inner } });
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
{
|
|
572
|
-
const cursorPos = empty ? from : to;
|
|
573
|
-
const line = doc.lineAt(cursorPos);
|
|
574
|
-
const lineText = doc.sliceString(line.from, line.to);
|
|
575
|
-
const openIdx = lineText.indexOf(openTag);
|
|
576
|
-
const closeIdx = lineText.lastIndexOf(closeTag);
|
|
577
|
-
if (openIdx >= 0 && closeIdx > openIdx) {
|
|
578
|
-
const absOpen = line.from + openIdx;
|
|
579
|
-
const absClose = line.from + closeIdx;
|
|
580
|
-
if (cursorPos >= absOpen + openTag.length && cursorPos <= absClose) {
|
|
581
|
-
const inner = doc.sliceString(absOpen + openTag.length, absClose);
|
|
582
|
-
view.dispatch({
|
|
583
|
-
changes: {
|
|
584
|
-
from: absOpen,
|
|
585
|
-
to: absClose + closeTag.length,
|
|
586
|
-
insert: inner
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
wrapSelection(view, openTag, closeTag);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// src/components/Toolbar/config.ts
|
|
597
|
-
function createToolbarConfig() {
|
|
574
|
+
function createToolbarConfig(options) {
|
|
575
|
+
const hasUploadConfig = isUploadConfigured(options);
|
|
598
576
|
return [
|
|
599
577
|
{
|
|
600
578
|
id: "heading",
|
|
@@ -619,7 +597,14 @@ function createToolbarConfig() {
|
|
|
619
597
|
{ id: "ol", label: "\u6709\u5E8F\u5217\u8868", icon: OrderedListOutlined, handler: (v) => insertAtLineStart(v, "1. ") },
|
|
620
598
|
{ separatorBefore: true },
|
|
621
599
|
{ id: "link", label: "\u94FE\u63A5", icon: LinkOutlined, handler: (v) => insertAtCursor(v, "[", "](url)", "\u6587\u672C") },
|
|
622
|
-
|
|
600
|
+
// 图片按钮:未配置上传时禁用并提示
|
|
601
|
+
{
|
|
602
|
+
id: "image",
|
|
603
|
+
label: hasUploadConfig ? "\u56FE\u7247" : "\u8BF7\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u5730\u5740",
|
|
604
|
+
icon: PictureOutlined,
|
|
605
|
+
disabled: !hasUploadConfig,
|
|
606
|
+
handler: hasUploadConfig ? (v) => uploadImage(v, { uploadUrl: options == null ? void 0 : options.uploadUrl, onUploadImage: options == null ? void 0 : options.onUploadImage }) : void 0
|
|
607
|
+
},
|
|
623
608
|
{ separatorBefore: true },
|
|
624
609
|
{ id: "code", label: "\u884C\u5185\u4EE3\u7801", icon: FieldStringOutlined, handler: (v) => wrapSelection(v, "`", "`") },
|
|
625
610
|
{
|
|
@@ -634,7 +619,6 @@ function createToolbarConfig() {
|
|
|
634
619
|
}
|
|
635
620
|
},
|
|
636
621
|
{ id: "quote", label: "\u5F15\u7528", icon: BlockOutlined, handler: (v) => insertAtLineStart(v, "> ") },
|
|
637
|
-
{ id: "textContainer", label: "\u6587\u5B57\u5BB9\u5668", icon: TagsOutlined, handler: (v) => toggleWrap(v, "<text-container>", "</text-container>") },
|
|
638
622
|
{
|
|
639
623
|
id: "hr",
|
|
640
624
|
label: "\u5206\u5272\u7EBF",
|
|
@@ -648,13 +632,20 @@ function createToolbarConfig() {
|
|
|
648
632
|
}
|
|
649
633
|
|
|
650
634
|
// src/components/Toolbar/hooks/useToolbar.ts
|
|
651
|
-
function useToolbar(editorView,
|
|
635
|
+
function useToolbar(editorView, options) {
|
|
636
|
+
const { extraItems, uploadConfig } = options || {};
|
|
652
637
|
const config = useMemo(() => {
|
|
653
|
-
const base = createToolbarConfig();
|
|
654
|
-
return extraItems ? [...base,
|
|
655
|
-
}, [extraItems]);
|
|
638
|
+
const base = createToolbarConfig(uploadConfig);
|
|
639
|
+
return extraItems ? [...base, ...extraItems] : base;
|
|
640
|
+
}, [extraItems, uploadConfig]);
|
|
656
641
|
const handleClick = useCallback((button) => {
|
|
657
|
-
if (
|
|
642
|
+
if (!editorView) return;
|
|
643
|
+
if (button.wrapTags) {
|
|
644
|
+
const [openTag, closeTag] = button.wrapTags;
|
|
645
|
+
toggleWrap(editorView, openTag, closeTag);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (button.handler) {
|
|
658
649
|
editorView.focus();
|
|
659
650
|
button.handler(editorView, editorView.state);
|
|
660
651
|
}
|
|
@@ -663,15 +654,15 @@ function useToolbar(editorView, extraItems) {
|
|
|
663
654
|
}
|
|
664
655
|
|
|
665
656
|
// src/components/Toolbar/renderers/ToolbarItemRenderer.tsx
|
|
666
|
-
import { Button, Dropdown } from "antd";
|
|
657
|
+
import { Button, Dropdown, Tooltip } from "antd";
|
|
667
658
|
import { DownOutlined } from "@ant-design/icons";
|
|
668
|
-
import { Fragment
|
|
659
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
669
660
|
function ToolbarItemRenderer({ item, onClick }) {
|
|
670
661
|
const Icon = item.icon;
|
|
671
662
|
if (item.children && item.children.length > 0) {
|
|
672
|
-
return /* @__PURE__ */
|
|
673
|
-
item.separatorBefore && /* @__PURE__ */
|
|
674
|
-
/* @__PURE__ */
|
|
663
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
664
|
+
item.separatorBefore && /* @__PURE__ */ jsx4("div", { className: "toolbar-separator" }),
|
|
665
|
+
/* @__PURE__ */ jsx4("div", { className: "toolbar-group", children: /* @__PURE__ */ jsx4(Tooltip, { title: item.label, children: /* @__PURE__ */ jsx4(
|
|
675
666
|
Dropdown,
|
|
676
667
|
{
|
|
677
668
|
menu: {
|
|
@@ -680,10 +671,10 @@ function ToolbarItemRenderer({ item, onClick }) {
|
|
|
680
671
|
return {
|
|
681
672
|
type: "item",
|
|
682
673
|
key: (_a = child.id) != null ? _a : "",
|
|
683
|
-
label: /* @__PURE__ */
|
|
674
|
+
label: /* @__PURE__ */ jsxs2("span", { onClick: () => onClick(child), children: [
|
|
684
675
|
child.icon && (() => {
|
|
685
676
|
const ChildIcon = child.icon;
|
|
686
|
-
return /* @__PURE__ */
|
|
677
|
+
return /* @__PURE__ */ jsx4(ChildIcon, {});
|
|
687
678
|
})(),
|
|
688
679
|
" ",
|
|
689
680
|
child.label
|
|
@@ -692,28 +683,29 @@ function ToolbarItemRenderer({ item, onClick }) {
|
|
|
692
683
|
})
|
|
693
684
|
},
|
|
694
685
|
placement: "bottomLeft",
|
|
695
|
-
children: /* @__PURE__ */
|
|
696
|
-
/* @__PURE__ */
|
|
697
|
-
/* @__PURE__ */
|
|
686
|
+
children: /* @__PURE__ */ jsxs2(Button, { className: "toolbar-button", type: "text", size: "small", children: [
|
|
687
|
+
/* @__PURE__ */ jsx4(Icon, {}),
|
|
688
|
+
/* @__PURE__ */ jsx4(DownOutlined, { style: { marginLeft: 2, fontSize: 10 } })
|
|
698
689
|
] })
|
|
699
690
|
}
|
|
700
|
-
) })
|
|
691
|
+
) }) })
|
|
701
692
|
] });
|
|
702
693
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
/* @__PURE__ */
|
|
694
|
+
const isDisabled = item.disabled;
|
|
695
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
696
|
+
item.separatorBefore && /* @__PURE__ */ jsx4("div", { className: "toolbar-separator" }),
|
|
697
|
+
/* @__PURE__ */ jsx4("div", { className: "toolbar-group", children: /* @__PURE__ */ jsx4(Tooltip, { title: item.label, children: /* @__PURE__ */ jsx4(
|
|
706
698
|
Button,
|
|
707
699
|
{
|
|
708
700
|
id: item.id,
|
|
709
701
|
className: "toolbar-button",
|
|
710
702
|
type: "text",
|
|
711
703
|
size: "small",
|
|
712
|
-
|
|
713
|
-
onClick: () => onClick(item),
|
|
714
|
-
children: /* @__PURE__ */
|
|
704
|
+
disabled: isDisabled,
|
|
705
|
+
onClick: () => !isDisabled && onClick(item),
|
|
706
|
+
children: /* @__PURE__ */ jsx4(Icon, {})
|
|
715
707
|
}
|
|
716
|
-
) })
|
|
708
|
+
) }) })
|
|
717
709
|
] });
|
|
718
710
|
}
|
|
719
711
|
|
|
@@ -723,33 +715,36 @@ import { Button as Button2, Dropdown as Dropdown2 } from "antd";
|
|
|
723
715
|
import { TableOutlined } from "@ant-design/icons";
|
|
724
716
|
|
|
725
717
|
// src/components/Toolbar/renderers/TablePicker/index.tsx
|
|
726
|
-
import { useState, useRef as useRef2, useEffect as
|
|
718
|
+
import { useState, useRef as useRef2, useEffect as useEffect3, useCallback as useCallback2 } from "react";
|
|
727
719
|
|
|
728
720
|
// src/components/Toolbar/renderers/TablePicker/index.module.less
|
|
729
721
|
var index_module_default = {};
|
|
730
722
|
|
|
731
723
|
// src/components/Toolbar/renderers/TablePicker/index.tsx
|
|
732
|
-
import { jsx as
|
|
733
|
-
var MAX_ROW =
|
|
734
|
-
var MAX_COL =
|
|
735
|
-
var REFRESH_TIME = 200;
|
|
724
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
725
|
+
var MAX_ROW = 8;
|
|
726
|
+
var MAX_COL = 8;
|
|
736
727
|
function TablePicker({ onSelect }) {
|
|
737
728
|
const [hoverSize, setHoverSize] = useState({ row: 0, col: 0 });
|
|
738
|
-
const [prevHoverSize, setPrevHoverSize] = useState({ row: 0, col: 0 });
|
|
739
729
|
const [confirmedSize, setConfirmedSize] = useState({ row: 0, col: 0 });
|
|
740
730
|
const maxColRef = useRef2(0);
|
|
731
|
+
const prevRowRef = useRef2(0);
|
|
741
732
|
const timerIdRef = useRef2(null);
|
|
742
733
|
const refreshTimerRef = useRef2(null);
|
|
743
734
|
const commitRef = useRef2(null);
|
|
744
|
-
const clearTimer = useCallback2((
|
|
735
|
+
const clearTimer = useCallback2(() => {
|
|
745
736
|
if (timerIdRef.current) {
|
|
737
|
+
clearTimeout(timerIdRef.current);
|
|
746
738
|
timerIdRef.current = null;
|
|
747
739
|
}
|
|
748
|
-
|
|
749
|
-
|
|
740
|
+
}, []);
|
|
741
|
+
const clearFlash = useCallback2(() => {
|
|
742
|
+
if (refreshTimerRef.current) {
|
|
743
|
+
clearInterval(refreshTimerRef.current);
|
|
744
|
+
refreshTimerRef.current = null;
|
|
750
745
|
}
|
|
751
746
|
}, []);
|
|
752
|
-
|
|
747
|
+
useEffect3(
|
|
753
748
|
() => () => {
|
|
754
749
|
if (timerIdRef.current) clearTimeout(timerIdRef.current);
|
|
755
750
|
if (refreshTimerRef.current) clearInterval(refreshTimerRef.current);
|
|
@@ -757,25 +752,21 @@ function TablePicker({ onSelect }) {
|
|
|
757
752
|
[]
|
|
758
753
|
);
|
|
759
754
|
function extend(col, row) {
|
|
755
|
+
if (row < prevRowRef.current || col < maxColRef.current) {
|
|
756
|
+
maxColRef.current = 0;
|
|
757
|
+
}
|
|
760
758
|
if (col > maxColRef.current) {
|
|
761
759
|
maxColRef.current = col;
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
setConfirmedSize({ row, col: maxColRef.current });
|
|
768
|
-
}
|
|
769
|
-
if (row === 1) {
|
|
770
|
-
commitRef.current = { cols: col, rows: 1 };
|
|
771
|
-
setConfirmedSize({ row: 1, col });
|
|
772
|
-
}
|
|
760
|
+
}
|
|
761
|
+
setHoverSize({ row, col });
|
|
762
|
+
setConfirmedSize({ row, col: maxColRef.current });
|
|
763
|
+
if (row === 1) {
|
|
764
|
+
commitRef.current = { cols: maxColRef.current, rows: 1 };
|
|
773
765
|
}
|
|
774
766
|
}
|
|
775
767
|
function flash(col, dir, limit, finalCallback) {
|
|
776
768
|
let current = dir === 1 ? 1 : limit;
|
|
777
769
|
const step = dir === 1 ? 1 : -1;
|
|
778
|
-
let timer = 0;
|
|
779
770
|
refreshTimerRef.current = setInterval(() => {
|
|
780
771
|
setHoverSize({ row: current, col });
|
|
781
772
|
if (current > confirmedSize.row || dir === -1) {
|
|
@@ -784,45 +775,43 @@ function TablePicker({ onSelect }) {
|
|
|
784
775
|
current += step;
|
|
785
776
|
if (dir === 1 && current > limit || dir === -1 && current < 1) {
|
|
786
777
|
clearInterval(refreshTimerRef.current);
|
|
787
|
-
|
|
788
|
-
|
|
778
|
+
refreshTimerRef.current = null;
|
|
779
|
+
const finalRow = current - step;
|
|
780
|
+
setHoverSize({ row: finalRow, col });
|
|
781
|
+
finalCallback(col, finalRow);
|
|
789
782
|
}
|
|
790
783
|
}, 50);
|
|
791
|
-
clearTimeout(timer);
|
|
792
|
-
return () => {
|
|
793
|
-
clearInterval(refreshTimerRef.current);
|
|
794
|
-
};
|
|
795
784
|
}
|
|
796
785
|
function onCellEnterCheck(index) {
|
|
797
786
|
clearTimer();
|
|
787
|
+
clearFlash();
|
|
798
788
|
const row = Math.floor(index / MAX_COL) + 1;
|
|
799
789
|
const col = index % MAX_COL + 1;
|
|
800
|
-
const
|
|
801
|
-
|
|
790
|
+
const prevRow = prevRowRef.current;
|
|
791
|
+
const isJump = Math.abs(row - prevRow) > 1;
|
|
792
|
+
const isDown = row > prevRow;
|
|
793
|
+
prevRowRef.current = row;
|
|
794
|
+
if (!isJump) {
|
|
802
795
|
extend(col, row);
|
|
803
|
-
} else {
|
|
804
|
-
if (
|
|
796
|
+
} else if (isDown) {
|
|
797
|
+
if (col > maxColRef.current || maxColRef.current >= MAX_COL) {
|
|
805
798
|
const finalRow = Math.min(row, MAX_ROW);
|
|
806
799
|
flash(col, 1, finalRow, (c, r) => {
|
|
807
800
|
setConfirmedSize({ row: r, col: c });
|
|
808
801
|
});
|
|
809
|
-
} else
|
|
810
|
-
|
|
811
|
-
setConfirmedSize({ row: r, col: c });
|
|
812
|
-
if (r === 1) {
|
|
813
|
-
commitRef.current = { cols: Math.min(MAX_COL, col), rows: 1 };
|
|
814
|
-
setConfirmedSize({ row: 1, col: Math.min(MAX_COL, col) });
|
|
815
|
-
}
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
if (col >= MAX_COL && row >= prevHoverSize.row) {
|
|
820
|
-
if (row !== prevHoverSize.row) {
|
|
821
|
-
maxColRef.current = 0;
|
|
822
|
-
setPrevHoverSize({ row: 0, col: 0 });
|
|
802
|
+
} else {
|
|
803
|
+
extend(col, row);
|
|
823
804
|
}
|
|
805
|
+
} else {
|
|
806
|
+
maxColRef.current = 0;
|
|
807
|
+
flash(col, -1, row, (c, r) => {
|
|
808
|
+
setConfirmedSize({ row: r, col: c });
|
|
809
|
+
if (r === 1) {
|
|
810
|
+
commitRef.current = { cols: Math.min(MAX_COL, col), rows: 1 };
|
|
811
|
+
setConfirmedSize({ row: 1, col: Math.min(MAX_COL, col) });
|
|
812
|
+
}
|
|
813
|
+
});
|
|
824
814
|
}
|
|
825
|
-
setPrevHoverSize({ row, col });
|
|
826
815
|
}
|
|
827
816
|
function onMouseDown() {
|
|
828
817
|
const size = commitRef.current || { cols: maxColRef.current, rows: confirmedSize.row };
|
|
@@ -830,13 +819,13 @@ function TablePicker({ onSelect }) {
|
|
|
830
819
|
onSelect(size);
|
|
831
820
|
}
|
|
832
821
|
}
|
|
833
|
-
return /* @__PURE__ */
|
|
822
|
+
return /* @__PURE__ */ jsxs3(
|
|
834
823
|
"div",
|
|
835
824
|
{
|
|
836
825
|
className: index_module_default.container,
|
|
837
826
|
onMouseDown,
|
|
838
827
|
children: [
|
|
839
|
-
/* @__PURE__ */
|
|
828
|
+
/* @__PURE__ */ jsx5(
|
|
840
829
|
"div",
|
|
841
830
|
{
|
|
842
831
|
className: index_module_default.grid,
|
|
@@ -845,7 +834,7 @@ function TablePicker({ onSelect }) {
|
|
|
845
834
|
const row = Math.floor(i / MAX_COL) + 1;
|
|
846
835
|
const col = i % MAX_COL + 1;
|
|
847
836
|
const isSelected = row <= hoverSize.row && col <= maxColRef.current;
|
|
848
|
-
return /* @__PURE__ */
|
|
837
|
+
return /* @__PURE__ */ jsx5(
|
|
849
838
|
"div",
|
|
850
839
|
{
|
|
851
840
|
className: `${index_module_default.cell} ${isSelected ? index_module_default.cellActive : ""}`,
|
|
@@ -856,7 +845,7 @@ function TablePicker({ onSelect }) {
|
|
|
856
845
|
})
|
|
857
846
|
}
|
|
858
847
|
),
|
|
859
|
-
/* @__PURE__ */
|
|
848
|
+
/* @__PURE__ */ jsxs3("div", { className: index_module_default.label, children: [
|
|
860
849
|
hoverSize.row,
|
|
861
850
|
" x ",
|
|
862
851
|
maxColRef.current || 1
|
|
@@ -867,7 +856,7 @@ function TablePicker({ onSelect }) {
|
|
|
867
856
|
}
|
|
868
857
|
|
|
869
858
|
// src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
|
|
870
|
-
import { jsx as
|
|
859
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
871
860
|
function TableDropdown({ editorView }) {
|
|
872
861
|
const [open, setOpen] = React.useState(false);
|
|
873
862
|
const openCountRef = useRef3(0);
|
|
@@ -886,7 +875,7 @@ ${dataRows}
|
|
|
886
875
|
const { from } = editorView.state.selection.main;
|
|
887
876
|
editorView.dispatch({ changes: { from, insert: table } });
|
|
888
877
|
}, [editorView]);
|
|
889
|
-
return /* @__PURE__ */
|
|
878
|
+
return /* @__PURE__ */ jsx6(
|
|
890
879
|
Dropdown2,
|
|
891
880
|
{
|
|
892
881
|
trigger: ["click"],
|
|
@@ -894,7 +883,7 @@ ${dataRows}
|
|
|
894
883
|
onOpenChange: setOpen,
|
|
895
884
|
popupRender: () => {
|
|
896
885
|
openCountRef.current++;
|
|
897
|
-
return /* @__PURE__ */
|
|
886
|
+
return /* @__PURE__ */ jsx6("div", { style: { padding: 4 }, onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx6(
|
|
898
887
|
TablePicker,
|
|
899
888
|
{
|
|
900
889
|
onSelect: (size) => {
|
|
@@ -906,44 +895,41 @@ ${dataRows}
|
|
|
906
895
|
) });
|
|
907
896
|
},
|
|
908
897
|
placement: "bottomLeft",
|
|
909
|
-
children: /* @__PURE__ */
|
|
898
|
+
children: /* @__PURE__ */ jsx6(Button2, { className: "toolbar-button", type: "text", size: "small", title: "\u8868\u683C", children: /* @__PURE__ */ jsx6(TableOutlined, {}) })
|
|
910
899
|
}
|
|
911
900
|
);
|
|
912
901
|
}
|
|
913
902
|
|
|
914
903
|
// src/components/Toolbar/index.tsx
|
|
915
|
-
import {
|
|
916
|
-
function Toolbar({ editorView, fullscreen, onToggleFullscreen, extraItems }) {
|
|
917
|
-
const { config, handleClick } = useToolbar(editorView, extraItems);
|
|
904
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
905
|
+
function Toolbar({ editorView, fullscreen = true, onToggleFullscreen, extraItems, uploadConfig }) {
|
|
906
|
+
const { config, handleClick } = useToolbar(editorView, { extraItems, uploadConfig });
|
|
918
907
|
const renderFullscreen = useCallback4(() => {
|
|
919
908
|
if (!onToggleFullscreen) return null;
|
|
920
909
|
const Icon = fullscreen ? FullscreenExitOutlined : FullscreenOutlined;
|
|
921
|
-
return /* @__PURE__ */
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
)
|
|
936
|
-
] });
|
|
910
|
+
return /* @__PURE__ */ jsx7(
|
|
911
|
+
Button3,
|
|
912
|
+
{
|
|
913
|
+
className: "toolbar-button",
|
|
914
|
+
type: "text",
|
|
915
|
+
size: "small",
|
|
916
|
+
title: fullscreen ? "\u9000\u51FA\u5168\u5C4F" : "\u5168\u5C4F",
|
|
917
|
+
onClick: () => {
|
|
918
|
+
onToggleFullscreen();
|
|
919
|
+
},
|
|
920
|
+
children: /* @__PURE__ */ jsx7(Icon, {})
|
|
921
|
+
}
|
|
922
|
+
);
|
|
937
923
|
}, [fullscreen, onToggleFullscreen]);
|
|
938
924
|
function isSeparator(item) {
|
|
939
925
|
return !!(!item.id && item.separatorBefore);
|
|
940
926
|
}
|
|
941
|
-
return /* @__PURE__ */
|
|
927
|
+
return /* @__PURE__ */ jsxs4("div", { className: "toolbar", children: [
|
|
942
928
|
config.map((item, idx) => {
|
|
943
929
|
if (isSeparator(item)) {
|
|
944
|
-
return /* @__PURE__ */
|
|
930
|
+
return /* @__PURE__ */ jsx7("div", { className: "toolbar-separator" }, `sep-${idx}`);
|
|
945
931
|
}
|
|
946
|
-
return /* @__PURE__ */
|
|
932
|
+
return /* @__PURE__ */ jsx7(
|
|
947
933
|
ToolbarItemRenderer,
|
|
948
934
|
{
|
|
949
935
|
item,
|
|
@@ -952,8 +938,7 @@ function Toolbar({ editorView, fullscreen, onToggleFullscreen, extraItems }) {
|
|
|
952
938
|
item.id
|
|
953
939
|
);
|
|
954
940
|
}),
|
|
955
|
-
/* @__PURE__ */
|
|
956
|
-
/* @__PURE__ */ jsx8(TableDropdown, { editorView }),
|
|
941
|
+
/* @__PURE__ */ jsx7(TableDropdown, { editorView }),
|
|
957
942
|
renderFullscreen()
|
|
958
943
|
] });
|
|
959
944
|
}
|
|
@@ -961,7 +946,7 @@ function Toolbar({ editorView, fullscreen, onToggleFullscreen, extraItems }) {
|
|
|
961
946
|
// src/components/Dialog/EditDialog.tsx
|
|
962
947
|
import { useState as useState2, useMemo as useMemo2 } from "react";
|
|
963
948
|
import { Modal } from "antd";
|
|
964
|
-
import { jsx as
|
|
949
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
965
950
|
function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
|
|
966
951
|
const schema = componentRegistry_default.get(typeName);
|
|
967
952
|
const [formProps, setFormProps] = useState2({});
|
|
@@ -989,7 +974,7 @@ function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
|
|
|
989
974
|
if (!schema) {
|
|
990
975
|
return null;
|
|
991
976
|
}
|
|
992
|
-
return /* @__PURE__ */
|
|
977
|
+
return /* @__PURE__ */ jsx8(
|
|
993
978
|
Modal,
|
|
994
979
|
{
|
|
995
980
|
title: `${schema.label} \u5C5E\u6027\u7F16\u8F91`,
|
|
@@ -1030,35 +1015,11 @@ ${propsLine}
|
|
|
1030
1015
|
|
|
1031
1016
|
// src/MarkdownEditor.tsx
|
|
1032
1017
|
import { ReadOutlined } from "@ant-design/icons";
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
onChange,
|
|
1038
|
-
showPreview = true,
|
|
1039
|
-
fullscreen: fullscreenProp = false
|
|
1040
|
-
}, ref) {
|
|
1041
|
-
const [content, setContent] = useState3(value != null ? value : defaultValue);
|
|
1042
|
-
const [editorView, setEditorView] = useState3(null);
|
|
1043
|
-
const scrollDOMRef = useRef4(null);
|
|
1044
|
-
const previewElRef = useRef4(null);
|
|
1018
|
+
|
|
1019
|
+
// src/hooks/useFullscreen.ts
|
|
1020
|
+
import { useState as useState3, useEffect as useEffect4, useCallback as useCallback5 } from "react";
|
|
1021
|
+
function useFullscreen() {
|
|
1045
1022
|
const [fullscreen, setFullscreen] = useState3(false);
|
|
1046
|
-
const [dialogVisible, setDialogVisible] = useState3(false);
|
|
1047
|
-
const [dialogTypeName, setDialogTypeName] = useState3("");
|
|
1048
|
-
const [dialogProps, setDialogProps] = useState3({});
|
|
1049
|
-
const [dialogPropsRange, setDialogPropsRange] = useState3([0, 0]);
|
|
1050
|
-
useEffect3(() => {
|
|
1051
|
-
if (value !== void 0) {
|
|
1052
|
-
setContent(value);
|
|
1053
|
-
}
|
|
1054
|
-
}, [value]);
|
|
1055
|
-
const internalSetContent = useCallback5((newContent) => {
|
|
1056
|
-
setContent(newContent);
|
|
1057
|
-
onChange == null ? void 0 : onChange(newContent);
|
|
1058
|
-
}, [onChange]);
|
|
1059
|
-
useImperativeHandle(ref, () => ({
|
|
1060
|
-
getContent: () => content
|
|
1061
|
-
}), [content]);
|
|
1062
1023
|
const toggleFullscreen = useCallback5(() => {
|
|
1063
1024
|
if (document.fullscreenElement) {
|
|
1064
1025
|
document.exitFullscreen().catch((e) => {
|
|
@@ -1074,7 +1035,7 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
|
|
|
1074
1035
|
}
|
|
1075
1036
|
}
|
|
1076
1037
|
}, []);
|
|
1077
|
-
|
|
1038
|
+
useEffect4(() => {
|
|
1078
1039
|
const handleFullscreenChange = () => {
|
|
1079
1040
|
setFullscreen(!!document.fullscreenElement);
|
|
1080
1041
|
};
|
|
@@ -1085,40 +1046,29 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
|
|
|
1085
1046
|
document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
|
|
1086
1047
|
};
|
|
1087
1048
|
}, []);
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
icon: () => iconNode,
|
|
1099
|
-
handler: (view, _state) => insertContainer(view, schema.name, defaultProps)
|
|
1100
|
-
};
|
|
1101
|
-
});
|
|
1102
|
-
const handleScrollReady = useCallback5((scrollDOM) => {
|
|
1103
|
-
scrollDOMRef.current = scrollDOM;
|
|
1104
|
-
}, []);
|
|
1105
|
-
useEffect3(() => {
|
|
1106
|
-
const source = scrollDOMRef.current;
|
|
1107
|
-
const target = previewElRef.current;
|
|
1049
|
+
return { fullscreen, toggleFullscreen };
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// src/hooks/useScrollSync.ts
|
|
1053
|
+
import { useEffect as useEffect5, useRef as useRef4 } from "react";
|
|
1054
|
+
function useScrollSync(sourceRef, targetRef) {
|
|
1055
|
+
const rafIdRef = useRef4(null);
|
|
1056
|
+
useEffect5(() => {
|
|
1057
|
+
const source = sourceRef.current;
|
|
1058
|
+
const target = targetRef.current;
|
|
1108
1059
|
if (!source || !target) return;
|
|
1109
|
-
let rafId = null;
|
|
1110
1060
|
const listener = () => {
|
|
1111
|
-
if (
|
|
1112
|
-
|
|
1113
|
-
const s =
|
|
1114
|
-
const t =
|
|
1061
|
+
if (rafIdRef.current) return;
|
|
1062
|
+
rafIdRef.current = requestAnimationFrame(() => {
|
|
1063
|
+
const s = sourceRef.current;
|
|
1064
|
+
const t = targetRef.current;
|
|
1115
1065
|
if (!s || !t) {
|
|
1116
|
-
|
|
1066
|
+
rafIdRef.current = null;
|
|
1117
1067
|
return;
|
|
1118
1068
|
}
|
|
1119
1069
|
const sourceScrollHeight = s.scrollHeight - s.clientHeight;
|
|
1120
1070
|
if (sourceScrollHeight <= 0) {
|
|
1121
|
-
|
|
1071
|
+
rafIdRef.current = null;
|
|
1122
1072
|
return;
|
|
1123
1073
|
}
|
|
1124
1074
|
const ratio = s.scrollTop / sourceScrollHeight;
|
|
@@ -1126,78 +1076,136 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
|
|
|
1126
1076
|
if (targetScrollHeight > 0) {
|
|
1127
1077
|
t.scrollTop = ratio * targetScrollHeight;
|
|
1128
1078
|
}
|
|
1129
|
-
|
|
1079
|
+
rafIdRef.current = null;
|
|
1130
1080
|
});
|
|
1131
1081
|
};
|
|
1132
1082
|
source.addEventListener("scroll", listener, { passive: true });
|
|
1133
1083
|
return () => {
|
|
1134
1084
|
source.removeEventListener("scroll", listener);
|
|
1135
|
-
if (
|
|
1085
|
+
if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
|
|
1086
|
+
};
|
|
1087
|
+
}, [sourceRef, targetRef]);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/MarkdownEditor.tsx
|
|
1091
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1092
|
+
var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
|
|
1093
|
+
defaultValue = "",
|
|
1094
|
+
value,
|
|
1095
|
+
onChange,
|
|
1096
|
+
showPreview = true,
|
|
1097
|
+
fullscreen: fullscreenProp = false,
|
|
1098
|
+
previewOptions,
|
|
1099
|
+
uploadConfig
|
|
1100
|
+
}, ref) {
|
|
1101
|
+
const [content, setContent] = useState4(value != null ? value : defaultValue);
|
|
1102
|
+
const [editorView, setEditorView] = useState4(null);
|
|
1103
|
+
const scrollDOMRef = useRef5(null);
|
|
1104
|
+
const previewElRef = useRef5(null);
|
|
1105
|
+
const { fullscreen, toggleFullscreen } = useFullscreen();
|
|
1106
|
+
useScrollSync(scrollDOMRef, previewElRef);
|
|
1107
|
+
const [dialogVisible, setDialogVisible] = useState4(false);
|
|
1108
|
+
const [dialogTypeName, setDialogTypeName] = useState4("");
|
|
1109
|
+
const [dialogProps, setDialogProps] = useState4({});
|
|
1110
|
+
const [dialogPropsRange, setDialogPropsRange] = useState4([0, 0]);
|
|
1111
|
+
useEffect6(() => {
|
|
1112
|
+
if (value !== void 0) {
|
|
1113
|
+
setContent(value);
|
|
1114
|
+
}
|
|
1115
|
+
}, [value]);
|
|
1116
|
+
const internalSetContent = useCallback6((newContent) => {
|
|
1117
|
+
setContent(newContent);
|
|
1118
|
+
onChange == null ? void 0 : onChange(newContent);
|
|
1119
|
+
}, [onChange]);
|
|
1120
|
+
useImperativeHandle(ref, () => ({
|
|
1121
|
+
getContent: () => content
|
|
1122
|
+
}), [content]);
|
|
1123
|
+
const extraToolbarItems = componentRegistry_default.getAll().map((schema) => {
|
|
1124
|
+
var _a, _b, _c;
|
|
1125
|
+
const defaultProps = {};
|
|
1126
|
+
for (const field of (_a = schema == null ? void 0 : schema.fields) != null ? _a : []) {
|
|
1127
|
+
defaultProps[field.key] = (_b = field.defaultValue) != null ? _b : "";
|
|
1128
|
+
}
|
|
1129
|
+
const iconNode = (_c = schema.icon) != null ? _c : /* @__PURE__ */ jsx9(ReadOutlined, {});
|
|
1130
|
+
return {
|
|
1131
|
+
id: `container-${schema.name}`,
|
|
1132
|
+
label: schema.label,
|
|
1133
|
+
icon: () => iconNode,
|
|
1134
|
+
handler: (view, _state) => insertContainer(view, schema.name, defaultProps)
|
|
1136
1135
|
};
|
|
1137
|
-
}
|
|
1138
|
-
const
|
|
1136
|
+
});
|
|
1137
|
+
const handleScrollReady = useCallback6((scrollDOM) => {
|
|
1138
|
+
scrollDOMRef.current = scrollDOM;
|
|
1139
|
+
}, []);
|
|
1140
|
+
const handlePreviewRef = useCallback6((node) => {
|
|
1139
1141
|
previewElRef.current = node;
|
|
1140
1142
|
}, []);
|
|
1141
|
-
const handleViewReady =
|
|
1143
|
+
const handleViewReady = useCallback6((view) => {
|
|
1142
1144
|
setEditorView(view);
|
|
1143
1145
|
}, []);
|
|
1144
|
-
const handleEditContainer =
|
|
1146
|
+
const handleEditContainer = useCallback6((_view, typeName, propsLineFrom, propsLineTo, currentProps) => {
|
|
1145
1147
|
setDialogTypeName(typeName);
|
|
1146
1148
|
setDialogProps({ ...currentProps });
|
|
1147
1149
|
setDialogPropsRange([propsLineFrom, propsLineTo]);
|
|
1148
1150
|
setDialogVisible(true);
|
|
1149
1151
|
}, []);
|
|
1150
|
-
const handleDialogSave =
|
|
1152
|
+
const handleDialogSave = useCallback6((newProps) => {
|
|
1151
1153
|
setDialogVisible(false);
|
|
1152
1154
|
if (editorView) {
|
|
1153
1155
|
updateContainerProps(editorView, dialogPropsRange[0], dialogPropsRange[1], newProps);
|
|
1154
1156
|
}
|
|
1155
1157
|
}, [editorView, dialogPropsRange]);
|
|
1156
|
-
const handleDialogClose =
|
|
1158
|
+
const handleDialogClose = useCallback6(() => {
|
|
1157
1159
|
setDialogVisible(false);
|
|
1158
1160
|
}, []);
|
|
1159
|
-
return /* @__PURE__ */
|
|
1160
|
-
/* @__PURE__ */
|
|
1161
|
+
return /* @__PURE__ */ jsxs5("div", { className: fullscreen ? "app fullscreen" : "app", children: [
|
|
1162
|
+
/* @__PURE__ */ jsx9(
|
|
1161
1163
|
Toolbar,
|
|
1162
1164
|
{
|
|
1163
1165
|
editorView,
|
|
1164
1166
|
fullscreen,
|
|
1165
1167
|
onToggleFullscreen: fullscreenProp ? toggleFullscreen : void 0,
|
|
1166
|
-
extraItems: extraToolbarItems
|
|
1168
|
+
extraItems: extraToolbarItems,
|
|
1169
|
+
uploadConfig
|
|
1167
1170
|
}
|
|
1168
1171
|
),
|
|
1169
|
-
showPreview ? /* @__PURE__ */
|
|
1172
|
+
showPreview ? /* @__PURE__ */ jsx9(
|
|
1170
1173
|
SplitPanels,
|
|
1171
1174
|
{
|
|
1172
|
-
leftPanel: /* @__PURE__ */
|
|
1175
|
+
leftPanel: /* @__PURE__ */ jsx9(
|
|
1173
1176
|
Editor,
|
|
1174
1177
|
{
|
|
1175
1178
|
value: content,
|
|
1176
1179
|
onChange: internalSetContent,
|
|
1177
1180
|
onScrollReady: handleScrollReady,
|
|
1178
1181
|
onViewReady: handleViewReady,
|
|
1179
|
-
onEditContainer: handleEditContainer
|
|
1182
|
+
onEditContainer: handleEditContainer,
|
|
1183
|
+
uploadConfig,
|
|
1184
|
+
onMessageNoUploadConfig: () => message.warning("\u8BF7\u5148\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u529F\u80FD")
|
|
1180
1185
|
}
|
|
1181
1186
|
),
|
|
1182
|
-
rightPanel: /* @__PURE__ */
|
|
1187
|
+
rightPanel: /* @__PURE__ */ jsx9(
|
|
1183
1188
|
Preview_default,
|
|
1184
1189
|
{
|
|
1185
1190
|
content,
|
|
1186
|
-
ref: handlePreviewRef
|
|
1191
|
+
ref: handlePreviewRef,
|
|
1192
|
+
previewOptions
|
|
1187
1193
|
}
|
|
1188
1194
|
)
|
|
1189
1195
|
}
|
|
1190
|
-
) : /* @__PURE__ */
|
|
1196
|
+
) : /* @__PURE__ */ jsx9(
|
|
1191
1197
|
Editor,
|
|
1192
1198
|
{
|
|
1193
1199
|
value: content,
|
|
1194
1200
|
onChange: internalSetContent,
|
|
1195
1201
|
onScrollReady: handleScrollReady,
|
|
1196
1202
|
onViewReady: handleViewReady,
|
|
1197
|
-
onEditContainer: handleEditContainer
|
|
1203
|
+
onEditContainer: handleEditContainer,
|
|
1204
|
+
uploadConfig,
|
|
1205
|
+
onMessageNoUploadConfig: () => message.warning("\u8BF7\u5148\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u529F\u80FD")
|
|
1198
1206
|
}
|
|
1199
1207
|
),
|
|
1200
|
-
/* @__PURE__ */
|
|
1208
|
+
/* @__PURE__ */ jsx9(
|
|
1201
1209
|
EditDialog,
|
|
1202
1210
|
{
|
|
1203
1211
|
visible: dialogVisible,
|
|
@@ -1211,10 +1219,10 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
|
|
|
1211
1219
|
});
|
|
1212
1220
|
|
|
1213
1221
|
// src/hooks/useRegisterToolbar.ts
|
|
1214
|
-
import { useEffect as
|
|
1222
|
+
import { useEffect as useEffect7 } from "react";
|
|
1215
1223
|
function useRegisterToolbar(schemas) {
|
|
1216
1224
|
const schemaList = Array.isArray(schemas) ? schemas : [schemas];
|
|
1217
|
-
|
|
1225
|
+
useEffect7(() => {
|
|
1218
1226
|
schemaList.forEach((schema) => {
|
|
1219
1227
|
componentRegistry_default.register(schema.name, schema);
|
|
1220
1228
|
});
|