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.
@@ -33,6 +33,18 @@ export declare function toggleTaskList(view: EditorView): void;
33
33
  * @param insertText - 未匹配时插入的前缀文本
34
34
  */
35
35
  export declare function toggleLinePrefix(view: EditorView, pattern: RegExp, insertText: string): void;
36
+ /**
37
+ * Tab:列表行缩进一级(4 空格)
38
+ * - 普通行返回 false,继续走默认缩进
39
+ * - 有序列表缩进后重排序号,子列表从 1 开始
40
+ */
41
+ export declare function indentMarkdownList(view: EditorView): boolean;
42
+ /**
43
+ * Shift-Tab:列表行反缩进一级
44
+ * - 普通行返回 false,继续走默认缩进
45
+ * - 有序列表反缩进后并入上一层并重排序号
46
+ */
47
+ export declare function dedentMarkdownList(view: EditorView): boolean;
36
48
  /**
37
49
  * 上传图片
38
50
  * @param file - 要上传的文件
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ let _codemirror_view = require("@codemirror/view");
30
30
  let _codemirror_commands = require("@codemirror/commands");
31
31
  let _codemirror_lang_markdown = require("@codemirror/lang-markdown");
32
32
  let _codemirror_theme_one_dark = require("@codemirror/theme-one-dark");
33
+ let _codemirror_language = require("@codemirror/language");
33
34
  let react_jsx_runtime = require("react/jsx-runtime");
34
35
  let remons_render_markdown = require("remons-render-markdown");
35
36
  remons_render_markdown = __toESM(remons_render_markdown, 1);
@@ -430,6 +431,116 @@ function toggleLinePrefix(view, pattern, insertText) {
430
431
  }
431
432
  view.dispatch({ changes });
432
433
  }
434
+ /** Markdown 列表行:缩进 + 列表标记(无序/有序)+ 空白 */
435
+ var LIST_LINE_RE = /^(\s*)([-*+]|\d+\.)([ \t]+)/;
436
+ /** 列表缩进台阶:4 个空格,确保缩进后能被解析为子列表 */
437
+ var LIST_INDENT_WIDTH = 4;
438
+ /** 判断位置是否位于代码块内(代码块内不参与列表缩进逻辑) */
439
+ function isInCodeBlock(state, pos) {
440
+ let node = (0, _codemirror_language.syntaxTree)(state).resolveInner(pos, 1);
441
+ while (node) {
442
+ if (node.name === "FencedCode" || node.name === "CodeBlock") return true;
443
+ node = node.parent;
444
+ }
445
+ return false;
446
+ }
447
+ /**
448
+ * 向上查找同缩进的前一个列表项,返回其序号
449
+ * - 跳过更深的子列表行和空行
450
+ * - 遇到非列表内容或更浅层列表时结束
451
+ */
452
+ function findPrevSiblingOrder(state, plans, lineNo, indent) {
453
+ const doc = state.doc;
454
+ for (let n = lineNo - 1; n >= 1; n--) {
455
+ const plan = plans.get(n);
456
+ if (plan) {
457
+ if (plan.nextIndent === indent) return plan.nextOrder;
458
+ if (plan.nextIndent < indent) return null;
459
+ continue;
460
+ }
461
+ const text = doc.line(n).text;
462
+ if (!text.trim()) continue;
463
+ const m = text.match(LIST_LINE_RE);
464
+ if (!m) return null;
465
+ const lineIndent = m[1].length;
466
+ if (lineIndent === indent) return /^\d+\.$/.test(m[2]) ? parseInt(m[2], 10) : null;
467
+ if (lineIndent < indent) return null;
468
+ }
469
+ return null;
470
+ }
471
+ /**
472
+ * 计算列表行缩进/反缩进的变更
473
+ * - 仅当所有选区行都是列表行时生效,否则返回 null 交给默认缩进行为
474
+ * - 有序列表缩进后重新编号,保证新一级子列表从 1 开始
475
+ */
476
+ function planListIndent(view, direction) {
477
+ const { state } = view;
478
+ const doc = state.doc;
479
+ const plans = /* @__PURE__ */ new Map();
480
+ for (const range of state.selection.ranges) {
481
+ const first = doc.lineAt(range.from).number;
482
+ const last = doc.lineAt(range.to).number;
483
+ for (let n = first; n <= last; n++) {
484
+ if (plans.has(n)) continue;
485
+ const line = doc.line(n);
486
+ if (isInCodeBlock(state, line.from)) return null;
487
+ const m = line.text.match(LIST_LINE_RE);
488
+ if (!m) return null;
489
+ const indent = m[1].length;
490
+ plans.set(n, {
491
+ indent,
492
+ nextIndent: direction === 1 ? indent + LIST_INDENT_WIDTH : Math.max(0, indent - LIST_INDENT_WIDTH),
493
+ order: /^\d+\.$/.test(m[2]) ? parseInt(m[2], 10) : null,
494
+ nextOrder: null
495
+ });
496
+ }
497
+ }
498
+ if (plans.size === 0) return null;
499
+ const ordered = [...plans.entries()].sort((a, b) => a[0] - b[0]);
500
+ for (const [lineNo, info] of ordered) {
501
+ if (info.order === null) continue;
502
+ const prevOrder = findPrevSiblingOrder(state, plans, lineNo, info.nextIndent);
503
+ info.nextOrder = prevOrder === null ? 1 : prevOrder + 1;
504
+ }
505
+ return ordered.map(([lineNo, info]) => {
506
+ const line = doc.line(lineNo);
507
+ const m = line.text.match(LIST_LINE_RE);
508
+ const useNextOrder = (direction === 1 || info.indent > 0) && info.order !== null && info.nextOrder !== null && info.order !== info.nextOrder;
509
+ return {
510
+ from: line.from,
511
+ to: line.from + m[0].length,
512
+ insert: " ".repeat(info.nextIndent) + (useNextOrder ? `${info.nextOrder}.` : m[2]) + m[3]
513
+ };
514
+ });
515
+ }
516
+ /**
517
+ * Tab:列表行缩进一级(4 空格)
518
+ * - 普通行返回 false,继续走默认缩进
519
+ * - 有序列表缩进后重排序号,子列表从 1 开始
520
+ */
521
+ function indentMarkdownList(view) {
522
+ const changes = planListIndent(view, 1);
523
+ if (!changes) return false;
524
+ view.dispatch({
525
+ changes,
526
+ userEvent: "input.indent"
527
+ });
528
+ return true;
529
+ }
530
+ /**
531
+ * Shift-Tab:列表行反缩进一级
532
+ * - 普通行返回 false,继续走默认缩进
533
+ * - 有序列表反缩进后并入上一层并重排序号
534
+ */
535
+ function dedentMarkdownList(view) {
536
+ const changes = planListIndent(view, -1);
537
+ if (!changes) return false;
538
+ view.dispatch({
539
+ changes,
540
+ userEvent: "delete.dedent"
541
+ });
542
+ return true;
543
+ }
433
544
  /**
434
545
  * 上传图片
435
546
  * @param file - 要上传的文件
@@ -577,7 +688,18 @@ var getCodeMirrorExtensions = (opts) => {
577
688
  const extensions = [
578
689
  (0, _codemirror_lang_markdown.markdown)(),
579
690
  _codemirror_theme_one_dark.oneDark,
580
- _codemirror_view.keymap.of([..._codemirror_commands.defaultKeymap, _codemirror_commands.indentWithTab]),
691
+ _codemirror_view.keymap.of([
692
+ {
693
+ key: "Tab",
694
+ run: indentMarkdownList
695
+ },
696
+ {
697
+ key: "Shift-Tab",
698
+ run: dedentMarkdownList
699
+ },
700
+ ..._codemirror_commands.defaultKeymap,
701
+ _codemirror_commands.indentWithTab
702
+ ]),
581
703
  _codemirror_view.EditorView.lineWrapping,
582
704
  pasteHandler,
583
705
  _codemirror_view.EditorView.baseTheme({
@@ -715,6 +837,8 @@ function Quote() {
715
837
  function InlineCode() {
716
838
  return IconComponent("inlinecode");
717
839
  }
840
+ /** 未配置图片上传时的提示文案 */
841
+ var UPLOAD_CONFIG_MISSING_MESSAGE = "请先配置图片上传功能";
718
842
  /**
719
843
  * 创建工具栏配置
720
844
  * @param options - 配置选项
@@ -812,13 +936,9 @@ function createToolbarConfig(options) {
812
936
  },
813
937
  {
814
938
  id: "image",
815
- label: hasUploadConfig ? "图片" : "请配置图片上传地址",
939
+ label: "图片",
816
940
  icon: _ant_design_icons.PictureOutlined,
817
- disabled: !hasUploadConfig,
818
- handler: hasUploadConfig ? (v) => uploadImage(v, {
819
- uploadUrl: options?.uploadUrl,
820
- onUploadImage: options?.onUploadImage
821
- }) : void 0
941
+ handler: hasUploadConfig && options ? (v) => uploadImage(v, options) : () => antd.message.warning(UPLOAD_CONFIG_MISSING_MESSAGE)
822
942
  },
823
943
  { separatorBefore: true },
824
944
  {