remons-markdown-editor 1.0.0-beta.3 → 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 +377 -373
- 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 +89 -37
- package/dist/index.d.ts +89 -37
- package/dist/index.js +368 -365
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +103 -4
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,14 +517,14 @@ 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
524
|
import { forwardRef, useEffect as useEffect2 } from "react";
|
|
455
525
|
import RenderMarkdown, { languagesCommon, initHighlighter } from "remons-render-markdown";
|
|
456
526
|
import "remons-render-markdown/dist/index.css";
|
|
457
|
-
import { jsx as
|
|
527
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
458
528
|
var Preview = forwardRef(({ content, previewOptions }, ref) => {
|
|
459
529
|
useEffect2(() => {
|
|
460
530
|
initHighlighter({
|
|
@@ -462,18 +532,18 @@ var Preview = forwardRef(({ content, previewOptions }, ref) => {
|
|
|
462
532
|
...(previewOptions == null ? void 0 : previewOptions.languages) ? previewOptions == null ? void 0 : previewOptions.languages : {}
|
|
463
533
|
});
|
|
464
534
|
}, [previewOptions == null ? void 0 : previewOptions.languages]);
|
|
465
|
-
return /* @__PURE__ */
|
|
535
|
+
return /* @__PURE__ */ jsx2("div", { ref, className: "preview", children: /* @__PURE__ */ jsx2(RenderMarkdown, { content, ...previewOptions }) });
|
|
466
536
|
});
|
|
467
537
|
Preview.displayName = "Preview";
|
|
468
538
|
var Preview_default = Preview;
|
|
469
539
|
|
|
470
540
|
// src/components/Panel/SplitPanels.tsx
|
|
471
541
|
import { Splitter } from "antd";
|
|
472
|
-
import { jsx as
|
|
542
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
473
543
|
function SplitPanels({ leftPanel, rightPanel }) {
|
|
474
|
-
return /* @__PURE__ */
|
|
475
|
-
/* @__PURE__ */
|
|
476
|
-
/* @__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 })
|
|
477
547
|
] });
|
|
478
548
|
}
|
|
479
549
|
|
|
@@ -499,105 +569,10 @@ import {
|
|
|
499
569
|
BlockOutlined,
|
|
500
570
|
MinusOutlined,
|
|
501
571
|
FieldStringOutlined,
|
|
502
|
-
FontSizeOutlined
|
|
503
|
-
TagsOutlined
|
|
572
|
+
FontSizeOutlined
|
|
504
573
|
} from "@ant-design/icons";
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
function insertAtCursor(view, before, after = "", placeholder = "") {
|
|
508
|
-
const { from, to } = view.state.selection.main;
|
|
509
|
-
const selectedText = view.state.doc.sliceString(from, to);
|
|
510
|
-
const insert = selectedText ? `${before}${selectedText}${after}` : `${before}${placeholder}${after}`;
|
|
511
|
-
view.dispatch({ changes: { from, to, insert } });
|
|
512
|
-
if (!selectedText && placeholder) {
|
|
513
|
-
view.dispatch({ selection: { anchor: from + before.length } });
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
function insertAtLineStart(view, text) {
|
|
517
|
-
const { from } = view.state.selection.main;
|
|
518
|
-
const line = view.state.doc.lineAt(from);
|
|
519
|
-
view.dispatch({ changes: { from: line.from, to: line.from, insert: text } });
|
|
520
|
-
view.dispatch({ selection: { anchor: line.from + text.length } });
|
|
521
|
-
}
|
|
522
|
-
async function uploadFileToServer(file) {
|
|
523
|
-
var _a;
|
|
524
|
-
const formData = new FormData();
|
|
525
|
-
formData.append("file", file);
|
|
526
|
-
const response = await fetch("https://remons.cn:3008/content/uploadMarkdownImg", {
|
|
527
|
-
method: "POST",
|
|
528
|
-
body: formData
|
|
529
|
-
});
|
|
530
|
-
if (!response.ok) return null;
|
|
531
|
-
const result = await response.json();
|
|
532
|
-
if (result.code === 200 && result.success && ((_a = result.data) == null ? void 0 : _a.path)) {
|
|
533
|
-
return `https://remons.cn:3008${result.data.path}`;
|
|
534
|
-
}
|
|
535
|
-
return null;
|
|
536
|
-
}
|
|
537
|
-
function uploadImage(view) {
|
|
538
|
-
const input = document.createElement("input");
|
|
539
|
-
input.type = "file";
|
|
540
|
-
input.accept = "image/*";
|
|
541
|
-
input.style.display = "none";
|
|
542
|
-
input.addEventListener("change", async () => {
|
|
543
|
-
if (!input.files || input.files.length === 0) return;
|
|
544
|
-
const file = input.files[0];
|
|
545
|
-
const { from, to } = view.state.selection.main;
|
|
546
|
-
const selectedText = view.state.doc.sliceString(from, to) || "\u63CF\u8FF0";
|
|
547
|
-
const url = await uploadFileToServer(file);
|
|
548
|
-
if (url) {
|
|
549
|
-
const insert = ``;
|
|
550
|
-
view.dispatch({ changes: { from, to, insert } });
|
|
551
|
-
}
|
|
552
|
-
input.remove();
|
|
553
|
-
});
|
|
554
|
-
document.body.appendChild(input);
|
|
555
|
-
input.click();
|
|
556
|
-
}
|
|
557
|
-
function wrapSelection(view, prefix, suffix) {
|
|
558
|
-
const { from, to, empty } = view.state.selection.main;
|
|
559
|
-
if (empty) return;
|
|
560
|
-
const selected = view.state.doc.sliceString(from, to);
|
|
561
|
-
view.dispatch({ changes: { from, to, insert: `${prefix}${selected}${suffix}` } });
|
|
562
|
-
}
|
|
563
|
-
function toggleWrap(view, openTag, closeTag) {
|
|
564
|
-
const { from, to, empty } = view.state.selection.main;
|
|
565
|
-
const doc = view.state.doc;
|
|
566
|
-
if (!empty) {
|
|
567
|
-
const selected = doc.sliceString(from, to);
|
|
568
|
-
if (selected.startsWith(openTag) && selected.endsWith(closeTag)) {
|
|
569
|
-
const inner = selected.slice(openTag.length, -closeTag.length);
|
|
570
|
-
view.dispatch({ changes: { from, to, insert: inner } });
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
{
|
|
575
|
-
const cursorPos = empty ? from : to;
|
|
576
|
-
const line = doc.lineAt(cursorPos);
|
|
577
|
-
const lineText = doc.sliceString(line.from, line.to);
|
|
578
|
-
const openIdx = lineText.indexOf(openTag);
|
|
579
|
-
const closeIdx = lineText.lastIndexOf(closeTag);
|
|
580
|
-
if (openIdx >= 0 && closeIdx > openIdx) {
|
|
581
|
-
const absOpen = line.from + openIdx;
|
|
582
|
-
const absClose = line.from + closeIdx;
|
|
583
|
-
if (cursorPos >= absOpen + openTag.length && cursorPos <= absClose) {
|
|
584
|
-
const inner = doc.sliceString(absOpen + openTag.length, absClose);
|
|
585
|
-
view.dispatch({
|
|
586
|
-
changes: {
|
|
587
|
-
from: absOpen,
|
|
588
|
-
to: absClose + closeTag.length,
|
|
589
|
-
insert: inner
|
|
590
|
-
}
|
|
591
|
-
});
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
wrapSelection(view, openTag, closeTag);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// src/components/Toolbar/config.ts
|
|
600
|
-
function createToolbarConfig() {
|
|
574
|
+
function createToolbarConfig(options) {
|
|
575
|
+
const hasUploadConfig = isUploadConfigured(options);
|
|
601
576
|
return [
|
|
602
577
|
{
|
|
603
578
|
id: "heading",
|
|
@@ -622,7 +597,14 @@ function createToolbarConfig() {
|
|
|
622
597
|
{ id: "ol", label: "\u6709\u5E8F\u5217\u8868", icon: OrderedListOutlined, handler: (v) => insertAtLineStart(v, "1. ") },
|
|
623
598
|
{ separatorBefore: true },
|
|
624
599
|
{ id: "link", label: "\u94FE\u63A5", icon: LinkOutlined, handler: (v) => insertAtCursor(v, "[", "](url)", "\u6587\u672C") },
|
|
625
|
-
|
|
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
|
+
},
|
|
626
608
|
{ separatorBefore: true },
|
|
627
609
|
{ id: "code", label: "\u884C\u5185\u4EE3\u7801", icon: FieldStringOutlined, handler: (v) => wrapSelection(v, "`", "`") },
|
|
628
610
|
{
|
|
@@ -637,7 +619,6 @@ function createToolbarConfig() {
|
|
|
637
619
|
}
|
|
638
620
|
},
|
|
639
621
|
{ id: "quote", label: "\u5F15\u7528", icon: BlockOutlined, handler: (v) => insertAtLineStart(v, "> ") },
|
|
640
|
-
{ id: "textContainer", label: "\u6587\u5B57\u5BB9\u5668", icon: TagsOutlined, handler: (v) => toggleWrap(v, "<text-container>", "</text-container>") },
|
|
641
622
|
{
|
|
642
623
|
id: "hr",
|
|
643
624
|
label: "\u5206\u5272\u7EBF",
|
|
@@ -651,13 +632,20 @@ function createToolbarConfig() {
|
|
|
651
632
|
}
|
|
652
633
|
|
|
653
634
|
// src/components/Toolbar/hooks/useToolbar.ts
|
|
654
|
-
function useToolbar(editorView,
|
|
635
|
+
function useToolbar(editorView, options) {
|
|
636
|
+
const { extraItems, uploadConfig } = options || {};
|
|
655
637
|
const config = useMemo(() => {
|
|
656
|
-
const base = createToolbarConfig();
|
|
657
|
-
return extraItems ? [...base,
|
|
658
|
-
}, [extraItems]);
|
|
638
|
+
const base = createToolbarConfig(uploadConfig);
|
|
639
|
+
return extraItems ? [...base, ...extraItems] : base;
|
|
640
|
+
}, [extraItems, uploadConfig]);
|
|
659
641
|
const handleClick = useCallback((button) => {
|
|
660
|
-
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) {
|
|
661
649
|
editorView.focus();
|
|
662
650
|
button.handler(editorView, editorView.state);
|
|
663
651
|
}
|
|
@@ -666,15 +654,15 @@ function useToolbar(editorView, extraItems) {
|
|
|
666
654
|
}
|
|
667
655
|
|
|
668
656
|
// src/components/Toolbar/renderers/ToolbarItemRenderer.tsx
|
|
669
|
-
import { Button, Dropdown } from "antd";
|
|
657
|
+
import { Button, Dropdown, Tooltip } from "antd";
|
|
670
658
|
import { DownOutlined } from "@ant-design/icons";
|
|
671
|
-
import { Fragment
|
|
659
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
672
660
|
function ToolbarItemRenderer({ item, onClick }) {
|
|
673
661
|
const Icon = item.icon;
|
|
674
662
|
if (item.children && item.children.length > 0) {
|
|
675
|
-
return /* @__PURE__ */
|
|
676
|
-
item.separatorBefore && /* @__PURE__ */
|
|
677
|
-
/* @__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(
|
|
678
666
|
Dropdown,
|
|
679
667
|
{
|
|
680
668
|
menu: {
|
|
@@ -683,10 +671,10 @@ function ToolbarItemRenderer({ item, onClick }) {
|
|
|
683
671
|
return {
|
|
684
672
|
type: "item",
|
|
685
673
|
key: (_a = child.id) != null ? _a : "",
|
|
686
|
-
label: /* @__PURE__ */
|
|
674
|
+
label: /* @__PURE__ */ jsxs2("span", { onClick: () => onClick(child), children: [
|
|
687
675
|
child.icon && (() => {
|
|
688
676
|
const ChildIcon = child.icon;
|
|
689
|
-
return /* @__PURE__ */
|
|
677
|
+
return /* @__PURE__ */ jsx4(ChildIcon, {});
|
|
690
678
|
})(),
|
|
691
679
|
" ",
|
|
692
680
|
child.label
|
|
@@ -695,28 +683,29 @@ function ToolbarItemRenderer({ item, onClick }) {
|
|
|
695
683
|
})
|
|
696
684
|
},
|
|
697
685
|
placement: "bottomLeft",
|
|
698
|
-
children: /* @__PURE__ */
|
|
699
|
-
/* @__PURE__ */
|
|
700
|
-
/* @__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 } })
|
|
701
689
|
] })
|
|
702
690
|
}
|
|
703
|
-
) })
|
|
691
|
+
) }) })
|
|
704
692
|
] });
|
|
705
693
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
/* @__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(
|
|
709
698
|
Button,
|
|
710
699
|
{
|
|
711
700
|
id: item.id,
|
|
712
701
|
className: "toolbar-button",
|
|
713
702
|
type: "text",
|
|
714
703
|
size: "small",
|
|
715
|
-
|
|
716
|
-
onClick: () => onClick(item),
|
|
717
|
-
children: /* @__PURE__ */
|
|
704
|
+
disabled: isDisabled,
|
|
705
|
+
onClick: () => !isDisabled && onClick(item),
|
|
706
|
+
children: /* @__PURE__ */ jsx4(Icon, {})
|
|
718
707
|
}
|
|
719
|
-
) })
|
|
708
|
+
) }) })
|
|
720
709
|
] });
|
|
721
710
|
}
|
|
722
711
|
|
|
@@ -732,24 +721,27 @@ import { useState, useRef as useRef2, useEffect as useEffect3, useCallback as us
|
|
|
732
721
|
var index_module_default = {};
|
|
733
722
|
|
|
734
723
|
// src/components/Toolbar/renderers/TablePicker/index.tsx
|
|
735
|
-
import { jsx as
|
|
736
|
-
var MAX_ROW =
|
|
737
|
-
var MAX_COL =
|
|
738
|
-
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;
|
|
739
727
|
function TablePicker({ onSelect }) {
|
|
740
728
|
const [hoverSize, setHoverSize] = useState({ row: 0, col: 0 });
|
|
741
|
-
const [prevHoverSize, setPrevHoverSize] = useState({ row: 0, col: 0 });
|
|
742
729
|
const [confirmedSize, setConfirmedSize] = useState({ row: 0, col: 0 });
|
|
743
730
|
const maxColRef = useRef2(0);
|
|
731
|
+
const prevRowRef = useRef2(0);
|
|
744
732
|
const timerIdRef = useRef2(null);
|
|
745
733
|
const refreshTimerRef = useRef2(null);
|
|
746
734
|
const commitRef = useRef2(null);
|
|
747
|
-
const clearTimer = useCallback2((
|
|
735
|
+
const clearTimer = useCallback2(() => {
|
|
748
736
|
if (timerIdRef.current) {
|
|
737
|
+
clearTimeout(timerIdRef.current);
|
|
749
738
|
timerIdRef.current = null;
|
|
750
739
|
}
|
|
751
|
-
|
|
752
|
-
|
|
740
|
+
}, []);
|
|
741
|
+
const clearFlash = useCallback2(() => {
|
|
742
|
+
if (refreshTimerRef.current) {
|
|
743
|
+
clearInterval(refreshTimerRef.current);
|
|
744
|
+
refreshTimerRef.current = null;
|
|
753
745
|
}
|
|
754
746
|
}, []);
|
|
755
747
|
useEffect3(
|
|
@@ -760,25 +752,21 @@ function TablePicker({ onSelect }) {
|
|
|
760
752
|
[]
|
|
761
753
|
);
|
|
762
754
|
function extend(col, row) {
|
|
755
|
+
if (row < prevRowRef.current || col < maxColRef.current) {
|
|
756
|
+
maxColRef.current = 0;
|
|
757
|
+
}
|
|
763
758
|
if (col > maxColRef.current) {
|
|
764
759
|
maxColRef.current = col;
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
setConfirmedSize({ row, col: maxColRef.current });
|
|
771
|
-
}
|
|
772
|
-
if (row === 1) {
|
|
773
|
-
commitRef.current = { cols: col, rows: 1 };
|
|
774
|
-
setConfirmedSize({ row: 1, col });
|
|
775
|
-
}
|
|
760
|
+
}
|
|
761
|
+
setHoverSize({ row, col });
|
|
762
|
+
setConfirmedSize({ row, col: maxColRef.current });
|
|
763
|
+
if (row === 1) {
|
|
764
|
+
commitRef.current = { cols: maxColRef.current, rows: 1 };
|
|
776
765
|
}
|
|
777
766
|
}
|
|
778
767
|
function flash(col, dir, limit, finalCallback) {
|
|
779
768
|
let current = dir === 1 ? 1 : limit;
|
|
780
769
|
const step = dir === 1 ? 1 : -1;
|
|
781
|
-
let timer = 0;
|
|
782
770
|
refreshTimerRef.current = setInterval(() => {
|
|
783
771
|
setHoverSize({ row: current, col });
|
|
784
772
|
if (current > confirmedSize.row || dir === -1) {
|
|
@@ -787,45 +775,43 @@ function TablePicker({ onSelect }) {
|
|
|
787
775
|
current += step;
|
|
788
776
|
if (dir === 1 && current > limit || dir === -1 && current < 1) {
|
|
789
777
|
clearInterval(refreshTimerRef.current);
|
|
790
|
-
|
|
791
|
-
|
|
778
|
+
refreshTimerRef.current = null;
|
|
779
|
+
const finalRow = current - step;
|
|
780
|
+
setHoverSize({ row: finalRow, col });
|
|
781
|
+
finalCallback(col, finalRow);
|
|
792
782
|
}
|
|
793
783
|
}, 50);
|
|
794
|
-
clearTimeout(timer);
|
|
795
|
-
return () => {
|
|
796
|
-
clearInterval(refreshTimerRef.current);
|
|
797
|
-
};
|
|
798
784
|
}
|
|
799
785
|
function onCellEnterCheck(index) {
|
|
800
786
|
clearTimer();
|
|
787
|
+
clearFlash();
|
|
801
788
|
const row = Math.floor(index / MAX_COL) + 1;
|
|
802
789
|
const col = index % MAX_COL + 1;
|
|
803
|
-
const
|
|
804
|
-
|
|
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) {
|
|
805
795
|
extend(col, row);
|
|
806
|
-
} else {
|
|
807
|
-
if (
|
|
796
|
+
} else if (isDown) {
|
|
797
|
+
if (col > maxColRef.current || maxColRef.current >= MAX_COL) {
|
|
808
798
|
const finalRow = Math.min(row, MAX_ROW);
|
|
809
799
|
flash(col, 1, finalRow, (c, r) => {
|
|
810
800
|
setConfirmedSize({ row: r, col: c });
|
|
811
801
|
});
|
|
812
|
-
} else
|
|
813
|
-
|
|
814
|
-
setConfirmedSize({ row: r, col: c });
|
|
815
|
-
if (r === 1) {
|
|
816
|
-
commitRef.current = { cols: Math.min(MAX_COL, col), rows: 1 };
|
|
817
|
-
setConfirmedSize({ row: 1, col: Math.min(MAX_COL, col) });
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
if (col >= MAX_COL && row >= prevHoverSize.row) {
|
|
823
|
-
if (row !== prevHoverSize.row) {
|
|
824
|
-
maxColRef.current = 0;
|
|
825
|
-
setPrevHoverSize({ row: 0, col: 0 });
|
|
802
|
+
} else {
|
|
803
|
+
extend(col, row);
|
|
826
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
|
+
});
|
|
827
814
|
}
|
|
828
|
-
setPrevHoverSize({ row, col });
|
|
829
815
|
}
|
|
830
816
|
function onMouseDown() {
|
|
831
817
|
const size = commitRef.current || { cols: maxColRef.current, rows: confirmedSize.row };
|
|
@@ -833,13 +819,13 @@ function TablePicker({ onSelect }) {
|
|
|
833
819
|
onSelect(size);
|
|
834
820
|
}
|
|
835
821
|
}
|
|
836
|
-
return /* @__PURE__ */
|
|
822
|
+
return /* @__PURE__ */ jsxs3(
|
|
837
823
|
"div",
|
|
838
824
|
{
|
|
839
825
|
className: index_module_default.container,
|
|
840
826
|
onMouseDown,
|
|
841
827
|
children: [
|
|
842
|
-
/* @__PURE__ */
|
|
828
|
+
/* @__PURE__ */ jsx5(
|
|
843
829
|
"div",
|
|
844
830
|
{
|
|
845
831
|
className: index_module_default.grid,
|
|
@@ -848,7 +834,7 @@ function TablePicker({ onSelect }) {
|
|
|
848
834
|
const row = Math.floor(i / MAX_COL) + 1;
|
|
849
835
|
const col = i % MAX_COL + 1;
|
|
850
836
|
const isSelected = row <= hoverSize.row && col <= maxColRef.current;
|
|
851
|
-
return /* @__PURE__ */
|
|
837
|
+
return /* @__PURE__ */ jsx5(
|
|
852
838
|
"div",
|
|
853
839
|
{
|
|
854
840
|
className: `${index_module_default.cell} ${isSelected ? index_module_default.cellActive : ""}`,
|
|
@@ -859,7 +845,7 @@ function TablePicker({ onSelect }) {
|
|
|
859
845
|
})
|
|
860
846
|
}
|
|
861
847
|
),
|
|
862
|
-
/* @__PURE__ */
|
|
848
|
+
/* @__PURE__ */ jsxs3("div", { className: index_module_default.label, children: [
|
|
863
849
|
hoverSize.row,
|
|
864
850
|
" x ",
|
|
865
851
|
maxColRef.current || 1
|
|
@@ -870,7 +856,7 @@ function TablePicker({ onSelect }) {
|
|
|
870
856
|
}
|
|
871
857
|
|
|
872
858
|
// src/components/Toolbar/renderers/TablePicker/TableDropdown.tsx
|
|
873
|
-
import { jsx as
|
|
859
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
874
860
|
function TableDropdown({ editorView }) {
|
|
875
861
|
const [open, setOpen] = React.useState(false);
|
|
876
862
|
const openCountRef = useRef3(0);
|
|
@@ -889,7 +875,7 @@ ${dataRows}
|
|
|
889
875
|
const { from } = editorView.state.selection.main;
|
|
890
876
|
editorView.dispatch({ changes: { from, insert: table } });
|
|
891
877
|
}, [editorView]);
|
|
892
|
-
return /* @__PURE__ */
|
|
878
|
+
return /* @__PURE__ */ jsx6(
|
|
893
879
|
Dropdown2,
|
|
894
880
|
{
|
|
895
881
|
trigger: ["click"],
|
|
@@ -897,7 +883,7 @@ ${dataRows}
|
|
|
897
883
|
onOpenChange: setOpen,
|
|
898
884
|
popupRender: () => {
|
|
899
885
|
openCountRef.current++;
|
|
900
|
-
return /* @__PURE__ */
|
|
886
|
+
return /* @__PURE__ */ jsx6("div", { style: { padding: 4 }, onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx6(
|
|
901
887
|
TablePicker,
|
|
902
888
|
{
|
|
903
889
|
onSelect: (size) => {
|
|
@@ -909,44 +895,41 @@ ${dataRows}
|
|
|
909
895
|
) });
|
|
910
896
|
},
|
|
911
897
|
placement: "bottomLeft",
|
|
912
|
-
children: /* @__PURE__ */
|
|
898
|
+
children: /* @__PURE__ */ jsx6(Button2, { className: "toolbar-button", type: "text", size: "small", title: "\u8868\u683C", children: /* @__PURE__ */ jsx6(TableOutlined, {}) })
|
|
913
899
|
}
|
|
914
900
|
);
|
|
915
901
|
}
|
|
916
902
|
|
|
917
903
|
// src/components/Toolbar/index.tsx
|
|
918
|
-
import {
|
|
919
|
-
function Toolbar({ editorView, fullscreen, onToggleFullscreen, extraItems }) {
|
|
920
|
-
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 });
|
|
921
907
|
const renderFullscreen = useCallback4(() => {
|
|
922
908
|
if (!onToggleFullscreen) return null;
|
|
923
909
|
const Icon = fullscreen ? FullscreenExitOutlined : FullscreenOutlined;
|
|
924
|
-
return /* @__PURE__ */
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
}
|
|
938
|
-
)
|
|
939
|
-
] });
|
|
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
|
+
);
|
|
940
923
|
}, [fullscreen, onToggleFullscreen]);
|
|
941
924
|
function isSeparator(item) {
|
|
942
925
|
return !!(!item.id && item.separatorBefore);
|
|
943
926
|
}
|
|
944
|
-
return /* @__PURE__ */
|
|
927
|
+
return /* @__PURE__ */ jsxs4("div", { className: "toolbar", children: [
|
|
945
928
|
config.map((item, idx) => {
|
|
946
929
|
if (isSeparator(item)) {
|
|
947
|
-
return /* @__PURE__ */
|
|
930
|
+
return /* @__PURE__ */ jsx7("div", { className: "toolbar-separator" }, `sep-${idx}`);
|
|
948
931
|
}
|
|
949
|
-
return /* @__PURE__ */
|
|
932
|
+
return /* @__PURE__ */ jsx7(
|
|
950
933
|
ToolbarItemRenderer,
|
|
951
934
|
{
|
|
952
935
|
item,
|
|
@@ -955,8 +938,7 @@ function Toolbar({ editorView, fullscreen, onToggleFullscreen, extraItems }) {
|
|
|
955
938
|
item.id
|
|
956
939
|
);
|
|
957
940
|
}),
|
|
958
|
-
/* @__PURE__ */
|
|
959
|
-
/* @__PURE__ */ jsx8(TableDropdown, { editorView }),
|
|
941
|
+
/* @__PURE__ */ jsx7(TableDropdown, { editorView }),
|
|
960
942
|
renderFullscreen()
|
|
961
943
|
] });
|
|
962
944
|
}
|
|
@@ -964,7 +946,7 @@ function Toolbar({ editorView, fullscreen, onToggleFullscreen, extraItems }) {
|
|
|
964
946
|
// src/components/Dialog/EditDialog.tsx
|
|
965
947
|
import { useState as useState2, useMemo as useMemo2 } from "react";
|
|
966
948
|
import { Modal } from "antd";
|
|
967
|
-
import { jsx as
|
|
949
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
968
950
|
function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
|
|
969
951
|
const schema = componentRegistry_default.get(typeName);
|
|
970
952
|
const [formProps, setFormProps] = useState2({});
|
|
@@ -992,7 +974,7 @@ function EditDialog({ visible, typeName, currentProps, onSave, onClose }) {
|
|
|
992
974
|
if (!schema) {
|
|
993
975
|
return null;
|
|
994
976
|
}
|
|
995
|
-
return /* @__PURE__ */
|
|
977
|
+
return /* @__PURE__ */ jsx8(
|
|
996
978
|
Modal,
|
|
997
979
|
{
|
|
998
980
|
title: `${schema.label} \u5C5E\u6027\u7F16\u8F91`,
|
|
@@ -1033,36 +1015,11 @@ ${propsLine}
|
|
|
1033
1015
|
|
|
1034
1016
|
// src/MarkdownEditor.tsx
|
|
1035
1017
|
import { ReadOutlined } from "@ant-design/icons";
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
onChange,
|
|
1041
|
-
showPreview = true,
|
|
1042
|
-
fullscreen: fullscreenProp = false,
|
|
1043
|
-
previewOptions
|
|
1044
|
-
}, ref) {
|
|
1045
|
-
const [content, setContent] = useState3(value != null ? value : defaultValue);
|
|
1046
|
-
const [editorView, setEditorView] = useState3(null);
|
|
1047
|
-
const scrollDOMRef = useRef4(null);
|
|
1048
|
-
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() {
|
|
1049
1022
|
const [fullscreen, setFullscreen] = useState3(false);
|
|
1050
|
-
const [dialogVisible, setDialogVisible] = useState3(false);
|
|
1051
|
-
const [dialogTypeName, setDialogTypeName] = useState3("");
|
|
1052
|
-
const [dialogProps, setDialogProps] = useState3({});
|
|
1053
|
-
const [dialogPropsRange, setDialogPropsRange] = useState3([0, 0]);
|
|
1054
|
-
useEffect4(() => {
|
|
1055
|
-
if (value !== void 0) {
|
|
1056
|
-
setContent(value);
|
|
1057
|
-
}
|
|
1058
|
-
}, [value]);
|
|
1059
|
-
const internalSetContent = useCallback5((newContent) => {
|
|
1060
|
-
setContent(newContent);
|
|
1061
|
-
onChange == null ? void 0 : onChange(newContent);
|
|
1062
|
-
}, [onChange]);
|
|
1063
|
-
useImperativeHandle(ref, () => ({
|
|
1064
|
-
getContent: () => content
|
|
1065
|
-
}), [content]);
|
|
1066
1023
|
const toggleFullscreen = useCallback5(() => {
|
|
1067
1024
|
if (document.fullscreenElement) {
|
|
1068
1025
|
document.exitFullscreen().catch((e) => {
|
|
@@ -1089,40 +1046,29 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
|
|
|
1089
1046
|
document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
|
|
1090
1047
|
};
|
|
1091
1048
|
}, []);
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
icon: () => iconNode,
|
|
1103
|
-
handler: (view, _state) => insertContainer(view, schema.name, defaultProps)
|
|
1104
|
-
};
|
|
1105
|
-
});
|
|
1106
|
-
const handleScrollReady = useCallback5((scrollDOM) => {
|
|
1107
|
-
scrollDOMRef.current = scrollDOM;
|
|
1108
|
-
}, []);
|
|
1109
|
-
useEffect4(() => {
|
|
1110
|
-
const source = scrollDOMRef.current;
|
|
1111
|
-
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;
|
|
1112
1059
|
if (!source || !target) return;
|
|
1113
|
-
let rafId = null;
|
|
1114
1060
|
const listener = () => {
|
|
1115
|
-
if (
|
|
1116
|
-
|
|
1117
|
-
const s =
|
|
1118
|
-
const t =
|
|
1061
|
+
if (rafIdRef.current) return;
|
|
1062
|
+
rafIdRef.current = requestAnimationFrame(() => {
|
|
1063
|
+
const s = sourceRef.current;
|
|
1064
|
+
const t = targetRef.current;
|
|
1119
1065
|
if (!s || !t) {
|
|
1120
|
-
|
|
1066
|
+
rafIdRef.current = null;
|
|
1121
1067
|
return;
|
|
1122
1068
|
}
|
|
1123
1069
|
const sourceScrollHeight = s.scrollHeight - s.clientHeight;
|
|
1124
1070
|
if (sourceScrollHeight <= 0) {
|
|
1125
|
-
|
|
1071
|
+
rafIdRef.current = null;
|
|
1126
1072
|
return;
|
|
1127
1073
|
}
|
|
1128
1074
|
const ratio = s.scrollTop / sourceScrollHeight;
|
|
@@ -1130,60 +1076,115 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
|
|
|
1130
1076
|
if (targetScrollHeight > 0) {
|
|
1131
1077
|
t.scrollTop = ratio * targetScrollHeight;
|
|
1132
1078
|
}
|
|
1133
|
-
|
|
1079
|
+
rafIdRef.current = null;
|
|
1134
1080
|
});
|
|
1135
1081
|
};
|
|
1136
1082
|
source.addEventListener("scroll", listener, { passive: true });
|
|
1137
1083
|
return () => {
|
|
1138
1084
|
source.removeEventListener("scroll", listener);
|
|
1139
|
-
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)
|
|
1140
1135
|
};
|
|
1141
|
-
}
|
|
1142
|
-
const
|
|
1136
|
+
});
|
|
1137
|
+
const handleScrollReady = useCallback6((scrollDOM) => {
|
|
1138
|
+
scrollDOMRef.current = scrollDOM;
|
|
1139
|
+
}, []);
|
|
1140
|
+
const handlePreviewRef = useCallback6((node) => {
|
|
1143
1141
|
previewElRef.current = node;
|
|
1144
1142
|
}, []);
|
|
1145
|
-
const handleViewReady =
|
|
1143
|
+
const handleViewReady = useCallback6((view) => {
|
|
1146
1144
|
setEditorView(view);
|
|
1147
1145
|
}, []);
|
|
1148
|
-
const handleEditContainer =
|
|
1146
|
+
const handleEditContainer = useCallback6((_view, typeName, propsLineFrom, propsLineTo, currentProps) => {
|
|
1149
1147
|
setDialogTypeName(typeName);
|
|
1150
1148
|
setDialogProps({ ...currentProps });
|
|
1151
1149
|
setDialogPropsRange([propsLineFrom, propsLineTo]);
|
|
1152
1150
|
setDialogVisible(true);
|
|
1153
1151
|
}, []);
|
|
1154
|
-
const handleDialogSave =
|
|
1152
|
+
const handleDialogSave = useCallback6((newProps) => {
|
|
1155
1153
|
setDialogVisible(false);
|
|
1156
1154
|
if (editorView) {
|
|
1157
1155
|
updateContainerProps(editorView, dialogPropsRange[0], dialogPropsRange[1], newProps);
|
|
1158
1156
|
}
|
|
1159
1157
|
}, [editorView, dialogPropsRange]);
|
|
1160
|
-
const handleDialogClose =
|
|
1158
|
+
const handleDialogClose = useCallback6(() => {
|
|
1161
1159
|
setDialogVisible(false);
|
|
1162
1160
|
}, []);
|
|
1163
|
-
return /* @__PURE__ */
|
|
1164
|
-
/* @__PURE__ */
|
|
1161
|
+
return /* @__PURE__ */ jsxs5("div", { className: fullscreen ? "app fullscreen" : "app", children: [
|
|
1162
|
+
/* @__PURE__ */ jsx9(
|
|
1165
1163
|
Toolbar,
|
|
1166
1164
|
{
|
|
1167
1165
|
editorView,
|
|
1168
1166
|
fullscreen,
|
|
1169
1167
|
onToggleFullscreen: fullscreenProp ? toggleFullscreen : void 0,
|
|
1170
|
-
extraItems: extraToolbarItems
|
|
1168
|
+
extraItems: extraToolbarItems,
|
|
1169
|
+
uploadConfig
|
|
1171
1170
|
}
|
|
1172
1171
|
),
|
|
1173
|
-
showPreview ? /* @__PURE__ */
|
|
1172
|
+
showPreview ? /* @__PURE__ */ jsx9(
|
|
1174
1173
|
SplitPanels,
|
|
1175
1174
|
{
|
|
1176
|
-
leftPanel: /* @__PURE__ */
|
|
1175
|
+
leftPanel: /* @__PURE__ */ jsx9(
|
|
1177
1176
|
Editor,
|
|
1178
1177
|
{
|
|
1179
1178
|
value: content,
|
|
1180
1179
|
onChange: internalSetContent,
|
|
1181
1180
|
onScrollReady: handleScrollReady,
|
|
1182
1181
|
onViewReady: handleViewReady,
|
|
1183
|
-
onEditContainer: handleEditContainer
|
|
1182
|
+
onEditContainer: handleEditContainer,
|
|
1183
|
+
uploadConfig,
|
|
1184
|
+
onMessageNoUploadConfig: () => message.warning("\u8BF7\u5148\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u529F\u80FD")
|
|
1184
1185
|
}
|
|
1185
1186
|
),
|
|
1186
|
-
rightPanel: /* @__PURE__ */
|
|
1187
|
+
rightPanel: /* @__PURE__ */ jsx9(
|
|
1187
1188
|
Preview_default,
|
|
1188
1189
|
{
|
|
1189
1190
|
content,
|
|
@@ -1192,17 +1193,19 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
|
|
|
1192
1193
|
}
|
|
1193
1194
|
)
|
|
1194
1195
|
}
|
|
1195
|
-
) : /* @__PURE__ */
|
|
1196
|
+
) : /* @__PURE__ */ jsx9(
|
|
1196
1197
|
Editor,
|
|
1197
1198
|
{
|
|
1198
1199
|
value: content,
|
|
1199
1200
|
onChange: internalSetContent,
|
|
1200
1201
|
onScrollReady: handleScrollReady,
|
|
1201
1202
|
onViewReady: handleViewReady,
|
|
1202
|
-
onEditContainer: handleEditContainer
|
|
1203
|
+
onEditContainer: handleEditContainer,
|
|
1204
|
+
uploadConfig,
|
|
1205
|
+
onMessageNoUploadConfig: () => message.warning("\u8BF7\u5148\u914D\u7F6E\u56FE\u7247\u4E0A\u4F20\u529F\u80FD")
|
|
1203
1206
|
}
|
|
1204
1207
|
),
|
|
1205
|
-
/* @__PURE__ */
|
|
1208
|
+
/* @__PURE__ */ jsx9(
|
|
1206
1209
|
EditDialog,
|
|
1207
1210
|
{
|
|
1208
1211
|
visible: dialogVisible,
|
|
@@ -1216,10 +1219,10 @@ var MarkdownEditor_default = forwardRef2(function MarkdownEditor({
|
|
|
1216
1219
|
});
|
|
1217
1220
|
|
|
1218
1221
|
// src/hooks/useRegisterToolbar.ts
|
|
1219
|
-
import { useEffect as
|
|
1222
|
+
import { useEffect as useEffect7 } from "react";
|
|
1220
1223
|
function useRegisterToolbar(schemas) {
|
|
1221
1224
|
const schemaList = Array.isArray(schemas) ? schemas : [schemas];
|
|
1222
|
-
|
|
1225
|
+
useEffect7(() => {
|
|
1223
1226
|
schemaList.forEach((schema) => {
|
|
1224
1227
|
componentRegistry_default.register(schema.name, schema);
|
|
1225
1228
|
});
|