remons-markdown-editor 1.0.0-beta.24 → 1.0.0-beta.25
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/components/Toolbar/buttons.d.ts +12 -0
- package/dist/index.cjs +127 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +127 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +3 -4
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Decoration, EditorView as EditorView$1, ViewPlugin, WidgetType, keymap
|
|
|
6
6
|
import { defaultKeymap, indentWithTab } from "@codemirror/commands";
|
|
7
7
|
import { markdown } from "@codemirror/lang-markdown";
|
|
8
8
|
import { oneDark } from "@codemirror/theme-one-dark";
|
|
9
|
+
import { syntaxTree } from "@codemirror/language";
|
|
9
10
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
11
|
import RenderMarkdown, { initHighlighter, languagesCommon } from "remons-render-markdown";
|
|
11
12
|
import "remons-render-markdown/dist/index.css";
|
|
@@ -405,6 +406,116 @@ function toggleLinePrefix(view, pattern, insertText) {
|
|
|
405
406
|
}
|
|
406
407
|
view.dispatch({ changes });
|
|
407
408
|
}
|
|
409
|
+
/** Markdown 列表行:缩进 + 列表标记(无序/有序)+ 空白 */
|
|
410
|
+
var LIST_LINE_RE = /^(\s*)([-*+]|\d+\.)([ \t]+)/;
|
|
411
|
+
/** 列表缩进台阶:4 个空格,确保缩进后能被解析为子列表 */
|
|
412
|
+
var LIST_INDENT_WIDTH = 4;
|
|
413
|
+
/** 判断位置是否位于代码块内(代码块内不参与列表缩进逻辑) */
|
|
414
|
+
function isInCodeBlock(state, pos) {
|
|
415
|
+
let node = syntaxTree(state).resolveInner(pos, 1);
|
|
416
|
+
while (node) {
|
|
417
|
+
if (node.name === "FencedCode" || node.name === "CodeBlock") return true;
|
|
418
|
+
node = node.parent;
|
|
419
|
+
}
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* 向上查找同缩进的前一个列表项,返回其序号
|
|
424
|
+
* - 跳过更深的子列表行和空行
|
|
425
|
+
* - 遇到非列表内容或更浅层列表时结束
|
|
426
|
+
*/
|
|
427
|
+
function findPrevSiblingOrder(state, plans, lineNo, indent) {
|
|
428
|
+
const doc = state.doc;
|
|
429
|
+
for (let n = lineNo - 1; n >= 1; n--) {
|
|
430
|
+
const plan = plans.get(n);
|
|
431
|
+
if (plan) {
|
|
432
|
+
if (plan.nextIndent === indent) return plan.nextOrder;
|
|
433
|
+
if (plan.nextIndent < indent) return null;
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
const text = doc.line(n).text;
|
|
437
|
+
if (!text.trim()) continue;
|
|
438
|
+
const m = text.match(LIST_LINE_RE);
|
|
439
|
+
if (!m) return null;
|
|
440
|
+
const lineIndent = m[1].length;
|
|
441
|
+
if (lineIndent === indent) return /^\d+\.$/.test(m[2]) ? parseInt(m[2], 10) : null;
|
|
442
|
+
if (lineIndent < indent) return null;
|
|
443
|
+
}
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* 计算列表行缩进/反缩进的变更
|
|
448
|
+
* - 仅当所有选区行都是列表行时生效,否则返回 null 交给默认缩进行为
|
|
449
|
+
* - 有序列表缩进后重新编号,保证新一级子列表从 1 开始
|
|
450
|
+
*/
|
|
451
|
+
function planListIndent(view, direction) {
|
|
452
|
+
const { state } = view;
|
|
453
|
+
const doc = state.doc;
|
|
454
|
+
const plans = /* @__PURE__ */ new Map();
|
|
455
|
+
for (const range of state.selection.ranges) {
|
|
456
|
+
const first = doc.lineAt(range.from).number;
|
|
457
|
+
const last = doc.lineAt(range.to).number;
|
|
458
|
+
for (let n = first; n <= last; n++) {
|
|
459
|
+
if (plans.has(n)) continue;
|
|
460
|
+
const line = doc.line(n);
|
|
461
|
+
if (isInCodeBlock(state, line.from)) return null;
|
|
462
|
+
const m = line.text.match(LIST_LINE_RE);
|
|
463
|
+
if (!m) return null;
|
|
464
|
+
const indent = m[1].length;
|
|
465
|
+
plans.set(n, {
|
|
466
|
+
indent,
|
|
467
|
+
nextIndent: direction === 1 ? indent + LIST_INDENT_WIDTH : Math.max(0, indent - LIST_INDENT_WIDTH),
|
|
468
|
+
order: /^\d+\.$/.test(m[2]) ? parseInt(m[2], 10) : null,
|
|
469
|
+
nextOrder: null
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (plans.size === 0) return null;
|
|
474
|
+
const ordered = [...plans.entries()].sort((a, b) => a[0] - b[0]);
|
|
475
|
+
for (const [lineNo, info] of ordered) {
|
|
476
|
+
if (info.order === null) continue;
|
|
477
|
+
const prevOrder = findPrevSiblingOrder(state, plans, lineNo, info.nextIndent);
|
|
478
|
+
info.nextOrder = prevOrder === null ? 1 : prevOrder + 1;
|
|
479
|
+
}
|
|
480
|
+
return ordered.map(([lineNo, info]) => {
|
|
481
|
+
const line = doc.line(lineNo);
|
|
482
|
+
const m = line.text.match(LIST_LINE_RE);
|
|
483
|
+
const useNextOrder = (direction === 1 || info.indent > 0) && info.order !== null && info.nextOrder !== null && info.order !== info.nextOrder;
|
|
484
|
+
return {
|
|
485
|
+
from: line.from,
|
|
486
|
+
to: line.from + m[0].length,
|
|
487
|
+
insert: " ".repeat(info.nextIndent) + (useNextOrder ? `${info.nextOrder}.` : m[2]) + m[3]
|
|
488
|
+
};
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Tab:列表行缩进一级(4 空格)
|
|
493
|
+
* - 普通行返回 false,继续走默认缩进
|
|
494
|
+
* - 有序列表缩进后重排序号,子列表从 1 开始
|
|
495
|
+
*/
|
|
496
|
+
function indentMarkdownList(view) {
|
|
497
|
+
const changes = planListIndent(view, 1);
|
|
498
|
+
if (!changes) return false;
|
|
499
|
+
view.dispatch({
|
|
500
|
+
changes,
|
|
501
|
+
userEvent: "input.indent"
|
|
502
|
+
});
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Shift-Tab:列表行反缩进一级
|
|
507
|
+
* - 普通行返回 false,继续走默认缩进
|
|
508
|
+
* - 有序列表反缩进后并入上一层并重排序号
|
|
509
|
+
*/
|
|
510
|
+
function dedentMarkdownList(view) {
|
|
511
|
+
const changes = planListIndent(view, -1);
|
|
512
|
+
if (!changes) return false;
|
|
513
|
+
view.dispatch({
|
|
514
|
+
changes,
|
|
515
|
+
userEvent: "delete.dedent"
|
|
516
|
+
});
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
408
519
|
/**
|
|
409
520
|
* 上传图片
|
|
410
521
|
* @param file - 要上传的文件
|
|
@@ -552,7 +663,18 @@ var getCodeMirrorExtensions = (opts) => {
|
|
|
552
663
|
const extensions = [
|
|
553
664
|
markdown(),
|
|
554
665
|
oneDark,
|
|
555
|
-
keymap.of([
|
|
666
|
+
keymap.of([
|
|
667
|
+
{
|
|
668
|
+
key: "Tab",
|
|
669
|
+
run: indentMarkdownList
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
key: "Shift-Tab",
|
|
673
|
+
run: dedentMarkdownList
|
|
674
|
+
},
|
|
675
|
+
...defaultKeymap,
|
|
676
|
+
indentWithTab
|
|
677
|
+
]),
|
|
556
678
|
EditorView$1.lineWrapping,
|
|
557
679
|
pasteHandler,
|
|
558
680
|
EditorView$1.baseTheme({
|
|
@@ -690,6 +812,8 @@ function Quote() {
|
|
|
690
812
|
function InlineCode() {
|
|
691
813
|
return IconComponent("inlinecode");
|
|
692
814
|
}
|
|
815
|
+
/** 未配置图片上传时的提示文案 */
|
|
816
|
+
var UPLOAD_CONFIG_MISSING_MESSAGE = "请先配置图片上传功能";
|
|
693
817
|
/**
|
|
694
818
|
* 创建工具栏配置
|
|
695
819
|
* @param options - 配置选项
|
|
@@ -787,13 +911,9 @@ function createToolbarConfig(options) {
|
|
|
787
911
|
},
|
|
788
912
|
{
|
|
789
913
|
id: "image",
|
|
790
|
-
label:
|
|
914
|
+
label: "图片",
|
|
791
915
|
icon: PictureOutlined,
|
|
792
|
-
|
|
793
|
-
handler: hasUploadConfig ? (v) => uploadImage(v, {
|
|
794
|
-
uploadUrl: options?.uploadUrl,
|
|
795
|
-
onUploadImage: options?.onUploadImage
|
|
796
|
-
}) : void 0
|
|
916
|
+
handler: hasUploadConfig && options ? (v) => uploadImage(v, options) : () => message.warning(UPLOAD_CONFIG_MISSING_MESSAGE)
|
|
797
917
|
},
|
|
798
918
|
{ separatorBefore: true },
|
|
799
919
|
{
|