@gravity-ui/markdown-editor 15.34.4 → 15.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/build/cjs/bundle/MarkupEditorView.js +1 -1
  2. package/build/cjs/bundle/MarkupEditorView.js.map +1 -1
  3. package/build/cjs/bundle/ToolbarView.d.ts +1 -2
  4. package/build/cjs/bundle/ToolbarView.js +22 -10
  5. package/build/cjs/bundle/ToolbarView.js.map +1 -1
  6. package/build/cjs/bundle/WysiwygEditorView.d.ts +1 -1
  7. package/build/cjs/bundle/WysiwygEditorView.js +4 -4
  8. package/build/cjs/bundle/WysiwygEditorView.js.map +1 -1
  9. package/build/cjs/bundle/config/markup.js +1 -0
  10. package/build/cjs/bundle/config/markup.js.map +1 -1
  11. package/build/cjs/bundle/config/wysiwyg.js +2 -0
  12. package/build/cjs/bundle/config/wysiwyg.js.map +1 -1
  13. package/build/cjs/bundle/emoji.js +3 -0
  14. package/build/cjs/bundle/emoji.js.map +1 -1
  15. package/build/cjs/bundle/toolbar/wysiwyg/WToolbarColors.js +36 -2
  16. package/build/cjs/bundle/toolbar/wysiwyg/WToolbarColors.js.map +1 -1
  17. package/build/cjs/extensions/behavior/Placeholder/index.js +54 -20
  18. package/build/cjs/extensions/behavior/Placeholder/index.js.map +1 -1
  19. package/build/cjs/extensions/markdown/Lists/commands.d.ts +1 -0
  20. package/build/cjs/extensions/markdown/Lists/commands.js +14 -0
  21. package/build/cjs/extensions/markdown/Lists/commands.js.map +1 -1
  22. package/build/cjs/extensions/markdown/Lists/index.js +1 -0
  23. package/build/cjs/extensions/markdown/Lists/index.js.map +1 -1
  24. package/build/cjs/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd-auto-scroll.d.ts +27 -0
  25. package/build/cjs/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd-auto-scroll.js +81 -0
  26. package/build/cjs/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd-auto-scroll.js.map +1 -0
  27. package/build/cjs/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd.js +33 -0
  28. package/build/cjs/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd.js.map +1 -1
  29. package/build/cjs/modules/toolbars/items.js +2 -0
  30. package/build/cjs/modules/toolbars/items.js.map +1 -1
  31. package/build/cjs/modules/toolbars/types.d.ts +1 -0
  32. package/build/cjs/modules/toolbars/types.js.map +1 -1
  33. package/build/cjs/react-utils/memo.d.ts +1 -0
  34. package/build/cjs/react-utils/memo.js +8 -0
  35. package/build/cjs/react-utils/memo.js.map +1 -0
  36. package/build/cjs/styles/markdown.css +1 -1
  37. package/build/cjs/styles/styles.css +5 -0
  38. package/build/cjs/styles/yfm-overrides.css +1 -1
  39. package/build/cjs/toolbar/Toolbar.d.ts +4 -0
  40. package/build/cjs/toolbar/Toolbar.js +9 -4
  41. package/build/cjs/toolbar/Toolbar.js.map +1 -1
  42. package/build/cjs/toolbar/ToolbarButton.js +3 -3
  43. package/build/cjs/toolbar/ToolbarButton.js.map +1 -1
  44. package/build/cjs/toolbar/ToolbarButtonPopup.js +3 -3
  45. package/build/cjs/toolbar/ToolbarButtonPopup.js.map +1 -1
  46. package/build/cjs/toolbar/ToolbarGroup.js +5 -4
  47. package/build/cjs/toolbar/ToolbarGroup.js.map +1 -1
  48. package/build/cjs/toolbar/ToolbarListButton.js +9 -6
  49. package/build/cjs/toolbar/ToolbarListButton.js.map +1 -1
  50. package/build/cjs/toolbar/ToolbarRerender.d.ts +3 -0
  51. package/build/cjs/toolbar/ToolbarRerender.js +48 -0
  52. package/build/cjs/toolbar/ToolbarRerender.js.map +1 -0
  53. package/build/cjs/toolbar/context.d.ts +10 -0
  54. package/build/cjs/toolbar/context.js +12 -0
  55. package/build/cjs/toolbar/context.js.map +1 -0
  56. package/build/cjs/toolbar/hooks.d.ts +8 -0
  57. package/build/cjs/toolbar/hooks.js +68 -0
  58. package/build/cjs/toolbar/hooks.js.map +1 -0
  59. package/build/cjs/toolbar/index.d.ts +1 -0
  60. package/build/cjs/toolbar/index.js +4 -0
  61. package/build/cjs/toolbar/index.js.map +1 -1
  62. package/build/cjs/toolbar/types.d.ts +1 -0
  63. package/build/cjs/toolbar/types.js.map +1 -1
  64. package/build/cjs/version.js +1 -1
  65. package/build/cjs/version.js.map +1 -1
  66. package/build/cjs/view/hocs/withMermaid/index.js +6 -1
  67. package/build/cjs/view/hocs/withMermaid/index.js.map +1 -1
  68. package/build/esm/bundle/MarkupEditorView.js +1 -1
  69. package/build/esm/bundle/MarkupEditorView.js.map +1 -1
  70. package/build/esm/bundle/ToolbarView.d.ts +1 -2
  71. package/build/esm/bundle/ToolbarView.js +24 -12
  72. package/build/esm/bundle/ToolbarView.js.map +1 -1
  73. package/build/esm/bundle/WysiwygEditorView.d.ts +1 -1
  74. package/build/esm/bundle/WysiwygEditorView.js +3 -4
  75. package/build/esm/bundle/WysiwygEditorView.js.map +1 -1
  76. package/build/esm/bundle/config/markup.js +1 -0
  77. package/build/esm/bundle/config/markup.js.map +1 -1
  78. package/build/esm/bundle/config/wysiwyg.js +2 -0
  79. package/build/esm/bundle/config/wysiwyg.js.map +1 -1
  80. package/build/esm/bundle/emoji.js +3 -0
  81. package/build/esm/bundle/emoji.js.map +1 -1
  82. package/build/esm/bundle/toolbar/wysiwyg/WToolbarColors.js +36 -2
  83. package/build/esm/bundle/toolbar/wysiwyg/WToolbarColors.js.map +1 -1
  84. package/build/esm/extensions/behavior/Placeholder/index.js +55 -21
  85. package/build/esm/extensions/behavior/Placeholder/index.js.map +1 -1
  86. package/build/esm/extensions/markdown/Lists/commands.d.ts +1 -0
  87. package/build/esm/extensions/markdown/Lists/commands.js +13 -0
  88. package/build/esm/extensions/markdown/Lists/commands.js.map +1 -1
  89. package/build/esm/extensions/markdown/Lists/index.js +2 -1
  90. package/build/esm/extensions/markdown/Lists/index.js.map +1 -1
  91. package/build/esm/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd-auto-scroll.d.ts +27 -0
  92. package/build/esm/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd-auto-scroll.js +77 -0
  93. package/build/esm/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd-auto-scroll.js.map +1 -0
  94. package/build/esm/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd.js +33 -0
  95. package/build/esm/extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd.js.map +1 -1
  96. package/build/esm/modules/toolbars/items.js +2 -0
  97. package/build/esm/modules/toolbars/items.js.map +1 -1
  98. package/build/esm/modules/toolbars/types.d.ts +1 -0
  99. package/build/esm/modules/toolbars/types.js.map +1 -1
  100. package/build/esm/react-utils/memo.d.ts +1 -0
  101. package/build/esm/react-utils/memo.js +5 -0
  102. package/build/esm/react-utils/memo.js.map +1 -0
  103. package/build/esm/styles/markdown.css +1 -1
  104. package/build/esm/styles/styles.css +5 -0
  105. package/build/esm/styles/yfm-overrides.css +1 -1
  106. package/build/esm/toolbar/Toolbar.d.ts +4 -0
  107. package/build/esm/toolbar/Toolbar.js +9 -4
  108. package/build/esm/toolbar/Toolbar.js.map +1 -1
  109. package/build/esm/toolbar/ToolbarButton.js +3 -3
  110. package/build/esm/toolbar/ToolbarButton.js.map +1 -1
  111. package/build/esm/toolbar/ToolbarButtonPopup.js +3 -3
  112. package/build/esm/toolbar/ToolbarButtonPopup.js.map +1 -1
  113. package/build/esm/toolbar/ToolbarGroup.js +5 -4
  114. package/build/esm/toolbar/ToolbarGroup.js.map +1 -1
  115. package/build/esm/toolbar/ToolbarListButton.js +9 -6
  116. package/build/esm/toolbar/ToolbarListButton.js.map +1 -1
  117. package/build/esm/toolbar/ToolbarRerender.d.ts +3 -0
  118. package/build/esm/toolbar/ToolbarRerender.js +44 -0
  119. package/build/esm/toolbar/ToolbarRerender.js.map +1 -0
  120. package/build/esm/toolbar/context.d.ts +10 -0
  121. package/build/esm/toolbar/context.js +8 -0
  122. package/build/esm/toolbar/context.js.map +1 -0
  123. package/build/esm/toolbar/hooks.d.ts +8 -0
  124. package/build/esm/toolbar/hooks.js +64 -0
  125. package/build/esm/toolbar/hooks.js.map +1 -0
  126. package/build/esm/toolbar/index.d.ts +1 -0
  127. package/build/esm/toolbar/index.js +1 -0
  128. package/build/esm/toolbar/index.js.map +1 -1
  129. package/build/esm/toolbar/types.d.ts +1 -0
  130. package/build/esm/toolbar/types.js.map +1 -1
  131. package/build/esm/version.js +1 -1
  132. package/build/esm/version.js.map +1 -1
  133. package/build/esm/view/hocs/withMermaid/index.js +6 -1
  134. package/build/esm/view/hocs/withMermaid/index.js.map +1 -1
  135. package/build/styles.css +7 -2
  136. package/package.json +17 -79
  137. package/README.md +0 -89
@@ -1,10 +1,44 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { useLatest } from 'react-use';
4
+ import { isEqual } from "../../../lodash.js";
5
+ import { useToolbarContext } from "../../../toolbar/context.js";
2
6
  import { ToolbarColors } from "../custom/ToolbarColors.js";
3
7
  export const WToolbarColors = ({ disablePortal, className, editor, focus, onClick, }) => {
8
+ const { active, enabled, currentColor } = useColorsState(editor);
4
9
  const action = editor.actions.colorify;
5
- const currentColor = action.meta();
6
- return (_jsx(ToolbarColors, { active: action.isActive(), enable: action.isEnable(), currentColor: currentColor, exec: (color) => {
10
+ return (_jsx(ToolbarColors, { active: active, enable: enabled, currentColor: currentColor, exec: (color) => {
7
11
  action.run({ color: color === currentColor ? '' : color });
8
12
  }, disablePortal: disablePortal, className: className, focus: focus, onClick: onClick, withDefault: true }));
9
13
  };
14
+ function useColorsState(editor) {
15
+ const action = editor.actions.colorify;
16
+ const context = useToolbarContext();
17
+ const [state, setState] = useState({
18
+ active: false,
19
+ enabled: true,
20
+ currentColor: '',
21
+ });
22
+ const stateRef = useLatest(state);
23
+ useEffect(() => {
24
+ if (!context)
25
+ return undefined;
26
+ const onUpdate = () => {
27
+ const newState = {
28
+ active: action.isActive(),
29
+ enabled: action.isEnable(),
30
+ currentColor: action.meta(),
31
+ };
32
+ if (!isEqual(stateRef.current, newState)) {
33
+ setState(newState);
34
+ }
35
+ };
36
+ onUpdate();
37
+ context.eventBus.on('update', onUpdate);
38
+ return () => {
39
+ context.eventBus.off('update', onUpdate);
40
+ };
41
+ }, [action, context, stateRef]);
42
+ return state;
43
+ }
10
44
  //# sourceMappingURL=WToolbarColors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WToolbarColors.js","sourceRoot":"../../../../../src","sources":["bundle/toolbar/wysiwyg/WToolbarColors.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,aAAa,EAA0B,mCAAgC;AAK/E,MAAM,CAAC,MAAM,cAAc,GAAkC,CAAC,EAC1D,aAAa,EACb,SAAS,EACT,MAAM,EACN,KAAK,EACL,OAAO,GACV,EAAE,EAAE;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;IACvC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAEnC,OAAO,CACH,KAAC,aAAa,IACV,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EACzB,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EACzB,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;YACZ,MAAM,CAAC,GAAG,CAAC,EAAC,KAAK,EAAE,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,EAAC,CAAC,CAAC;QAC7D,CAAC,EACD,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,WAAW,SACb,CACL,CAAC;AACN,CAAC,CAAC","sourcesContent":["import {ToolbarColors, type ToolbarColorsProps} from '../custom/ToolbarColors';\nimport type {WToolbarBaseProps} from '../types';\n\nexport type WToolbarColorsProps = WToolbarBaseProps & Pick<ToolbarColorsProps, 'disablePortal'>;\n\nexport const WToolbarColors: React.FC<WToolbarColorsProps> = ({\n disablePortal,\n className,\n editor,\n focus,\n onClick,\n}) => {\n const action = editor.actions.colorify;\n const currentColor = action.meta();\n\n return (\n <ToolbarColors\n active={action.isActive()}\n enable={action.isEnable()}\n currentColor={currentColor}\n exec={(color) => {\n action.run({color: color === currentColor ? '' : color});\n }}\n disablePortal={disablePortal}\n className={className}\n focus={focus}\n onClick={onClick}\n withDefault\n />\n );\n};\n"]}
1
+ {"version":3,"file":"WToolbarColors.js","sourceRoot":"../../../../../src","sources":["bundle/toolbar/wysiwyg/WToolbarColors.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAC;AAE1C,OAAO,EAAC,SAAS,EAAC,MAAM,WAAW,CAAC;AAGpC,OAAO,EAAC,OAAO,EAAC,2BAAmB;AACnC,OAAO,EAAC,iBAAiB,EAAC,oCAA4B;AAEtD,OAAO,EAAC,aAAa,EAA0B,mCAAgC;AAK/E,MAAM,CAAC,MAAM,cAAc,GAAkC,CAAC,EAC1D,aAAa,EACb,SAAS,EACT,MAAM,EACN,KAAK,EACL,OAAO,GACV,EAAE,EAAE;IACD,MAAM,EAAC,MAAM,EAAE,OAAO,EAAE,YAAY,EAAC,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;IAEvC,OAAO,CACH,KAAC,aAAa,IACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;YACZ,MAAM,CAAC,GAAG,CAAC,EAAC,KAAK,EAAE,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,EAAC,CAAC,CAAC;QAC7D,CAAC,EACD,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,WAAW,SACb,CACL,CAAC;AACN,CAAC,CAAC;AAQF,SAAS,cAAc,CAAC,MAAqB;IACzC,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;IAEvC,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IAEpC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAc;QAC5C,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,EAAE;KACnB,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAElC,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAE/B,MAAM,QAAQ,GAAG,GAAG,EAAE;YAClB,MAAM,QAAQ,GAAG;gBACb,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;gBACzB,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE;gBAC1B,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE;aAC9B,CAAC;YAEF,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;gBACvC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;QACL,CAAC,CAAC;QAEF,QAAQ,EAAE,CAAC;QAEX,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAExC,OAAO,GAAG,EAAE;YACR,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC7C,CAAC,CAAC;IACN,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEhC,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["import {useEffect, useState} from 'react';\n\nimport {useLatest} from 'react-use';\n\nimport type {ActionStorage} from '#core';\nimport {isEqual} from 'src/lodash';\nimport {useToolbarContext} from 'src/toolbar/context';\n\nimport {ToolbarColors, type ToolbarColorsProps} from '../custom/ToolbarColors';\nimport type {WToolbarBaseProps} from '../types';\n\nexport type WToolbarColorsProps = WToolbarBaseProps & Pick<ToolbarColorsProps, 'disablePortal'>;\n\nexport const WToolbarColors: React.FC<WToolbarColorsProps> = ({\n disablePortal,\n className,\n editor,\n focus,\n onClick,\n}) => {\n const {active, enabled, currentColor} = useColorsState(editor);\n const action = editor.actions.colorify;\n\n return (\n <ToolbarColors\n active={active}\n enable={enabled}\n currentColor={currentColor}\n exec={(color) => {\n action.run({color: color === currentColor ? '' : color});\n }}\n disablePortal={disablePortal}\n className={className}\n focus={focus}\n onClick={onClick}\n withDefault\n />\n );\n};\n\ntype ColorsState = {\n active: boolean;\n enabled: boolean;\n currentColor: string;\n};\n\nfunction useColorsState(editor: ActionStorage): ColorsState {\n const action = editor.actions.colorify;\n\n const context = useToolbarContext();\n\n const [state, setState] = useState<ColorsState>({\n active: false,\n enabled: true,\n currentColor: '',\n });\n const stateRef = useLatest(state);\n\n useEffect(() => {\n if (!context) return undefined;\n\n const onUpdate = () => {\n const newState = {\n active: action.isActive(),\n enabled: action.isEnable(),\n currentColor: action.meta(),\n };\n\n if (!isEqual(stateRef.current, newState)) {\n setState(newState);\n }\n };\n\n onUpdate();\n\n context.eventBus.on('update', onUpdate);\n\n return () => {\n context.eventBus.off('update', onUpdate);\n };\n }, [action, context, stateRef]);\n\n return state;\n}\n"]}
@@ -1,10 +1,9 @@
1
1
  import { Plugin, PluginKey } from 'prosemirror-state';
2
2
  // @ts-ignore // TODO: fix cjs build
3
- import { findChildren, findParentNodeClosestToPos } from 'prosemirror-utils';
3
+ import { findParentNodeClosestToPos } from 'prosemirror-utils';
4
4
  import { Decoration, DecorationSet } from 'prosemirror-view';
5
5
  import { cn } from "../../../classname.js";
6
6
  import { isEqual } from "../../../lodash.js";
7
- import { isNodeEmpty } from "../../../utils/nodes.js";
8
7
  import { getPlaceholderContent } from "../../../utils/placeholder.js";
9
8
  import { isTextSelection } from "../../../utils/selection.js";
10
9
  import "./index.css";
@@ -36,10 +35,32 @@ export const createPlaceholder = (node, parent, focus) => {
36
35
  return placeholder;
37
36
  };
38
37
  const placeholderNeeded = (node) => {
39
- const childrenWithPlaceholderVisible = findChildren(node, (n) => Boolean(n.type.spec.placeholder?.alwaysVisible));
40
- return (isNodeEmpty(node) &&
41
- // If there are child nodes with constant placeholder - give them the priority
42
- !childrenWithPlaceholderVisible.length);
38
+ // Combine all checks in a single descendants pass
39
+ let hasAlwaysVisiblePlaceholder = false;
40
+ let hasNonEmptyContent = false;
41
+ node.descendants((n) => {
42
+ // Check for alwaysVisible placeholder
43
+ if (n.type.spec.placeholder?.alwaysVisible) {
44
+ hasAlwaysVisiblePlaceholder = true;
45
+ return false; // Stop traversal early
46
+ }
47
+ // Check if node has non-empty content (same logic as isNodeEmpty)
48
+ if (n.isAtom) {
49
+ hasNonEmptyContent = true;
50
+ return false; // Stop traversal early
51
+ }
52
+ if (n.isText && n.textContent) {
53
+ hasNonEmptyContent = true;
54
+ return false; // Stop traversal early
55
+ }
56
+ // Non-empty paragraph or other block with content
57
+ if (n.content.size > 0 && n.type.name !== 'paragraph') {
58
+ hasNonEmptyContent = true;
59
+ return false; // Stop traversal early
60
+ }
61
+ return true;
62
+ });
63
+ return !hasNonEmptyContent && !hasAlwaysVisiblePlaceholder;
43
64
  };
44
65
  const addDecoration = (widgetsMap, node, pos, parent, cursorPos, globalState) => {
45
66
  const placeholderSpec = node.type.spec.placeholder;
@@ -87,19 +108,19 @@ export const Placeholder = (builder, opts) => {
87
108
  },
88
109
  }), builder.Priority.VeryHigh);
89
110
  };
90
- function getPlaceholderWidgetSpecs(state) {
111
+ function getPlaceholderWidgetSpecs(state, pluginKeys) {
91
112
  const globalState = { hasFocus: false };
92
113
  const widgetsMap = {};
93
114
  const { selection } = state;
94
115
  const cursorPos = isTextSelection(selection) ? selection.$cursor?.pos : null;
95
- getPlaceholderPluginKeys(state.schema).forEach((f) => {
116
+ pluginKeys.forEach((f) => {
96
117
  // We use find because it can be used to iterate over the DecorationSet.
97
118
  f.getState(state)?.find(undefined, undefined, (spec) => {
98
119
  widgetsMap[spec.pos] = f;
99
120
  return false;
100
121
  });
101
122
  });
102
- // Fraw placeholder for all nodes where placeholder is alwaysVisible
123
+ // Draw placeholder for all nodes where placeholder is alwaysVisible
103
124
  const decorate = (node, pos, parent) => {
104
125
  const placeholderSpec = node.type.spec.placeholder;
105
126
  if (placeholderSpec && placeholderSpec.alwaysVisible && placeholderNeeded(node)) {
@@ -124,23 +145,36 @@ function getPlaceholderWidgetSpecs(state) {
124
145
  return { widgetSpecs, hasFocus: globalState.hasFocus };
125
146
  }
126
147
  function initState(state) {
127
- const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(state);
148
+ const pluginKeys = getPlaceholderPluginKeys(state.schema);
149
+ const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(state, pluginKeys);
128
150
  const decorationSet = DecorationSet.create(state.doc, widgetSpecs.map((widget) => Decoration.widget(widget.pos, widget.toDOM, widget.spec)));
129
- return { decorationSet, hasFocus };
151
+ return { decorationSet, hasFocus, pluginKeys };
130
152
  }
131
- function applyState(tr, oldPluginState, _oldState, newState) {
132
- const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(newState);
153
+ function applyState(tr, oldPluginState, oldState, newState) {
154
+ // Early return if document hasn't changed and selection hasn't changed
155
+ // This avoids unnecessary recalculation of decorations
156
+ if (!tr.docChanged && !tr.selectionSet) {
157
+ return oldPluginState;
158
+ }
159
+ // Reuse cached plugin keys if schema hasn't changed
160
+ const pluginKeys = oldState.schema === newState.schema
161
+ ? oldPluginState.pluginKeys
162
+ : getPlaceholderPluginKeys(newState.schema);
163
+ const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(newState, pluginKeys);
133
164
  const { decorationSet } = oldPluginState;
134
165
  const oldMappedSet = decorationSet.map(tr.mapping, tr.doc);
135
166
  // Find all decorations that are present in old and new set
136
- const decorationsThatDidNotChange = widgetSpecs.reduce((a, { pos, spec }) => {
167
+ const unchangedPositions = new Set();
168
+ const decorationsThatDidNotChange = [];
169
+ widgetSpecs.forEach(({ pos, spec }) => {
137
170
  const deco = oldMappedSet.find(pos, pos);
138
- if (deco.length && isEqual(deco[0].spec, spec))
139
- a.push(...deco);
140
- return a;
141
- }, []);
142
- // Those are decorations that are presenr only in new set
143
- const newAddedDecorations = widgetSpecs.filter(({ pos }) => !decorationsThatDidNotChange.map(({ from }) => from).includes(pos));
171
+ if (deco.length && isEqual(deco[0].spec, spec)) {
172
+ unchangedPositions.add(pos);
173
+ decorationsThatDidNotChange.push(...deco);
174
+ }
175
+ });
176
+ // Those are decorations that are present only in new set
177
+ const newAddedDecorations = widgetSpecs.filter(({ pos }) => !unchangedPositions.has(pos));
144
178
  // That is a set with decorations that are present in old set and absent in new set
145
179
  const notRelevantDecorations = oldMappedSet.remove(decorationsThatDidNotChange);
146
180
  let newSet = oldMappedSet;
@@ -150,6 +184,6 @@ function applyState(tr, oldPluginState, _oldState, newState) {
150
184
  // Add new decorations
151
185
  if (newAddedDecorations.length)
152
186
  newSet = newSet.add(tr.doc, newAddedDecorations.map((widget) => Decoration.widget(widget.pos, widget.toDOM, widget.spec)));
153
- return { decorationSet: newSet, hasFocus };
187
+ return { decorationSet: newSet, hasFocus, pluginKeys };
154
188
  }
155
189
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["extensions/behavior/Placeholder/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {Node, Schema} from 'prosemirror-model';\nimport {type EditorState, Plugin, PluginKey, type Transaction} from 'prosemirror-state';\n// @ts-ignore // TODO: fix cjs build\nimport {findChildren, findParentNodeClosestToPos} from 'prosemirror-utils';\nimport {Decoration, DecorationSet} from 'prosemirror-view';\n\nimport {cn} from '../../../classname';\nimport type {ExtensionAuto} from '../../../core';\nimport {isEqual} from '../../../lodash';\nimport {isNodeEmpty} from '../../../utils/nodes';\nimport {type PlaceholderOptions, getPlaceholderContent} from '../../../utils/placeholder';\nimport {isTextSelection} from '../../../utils/selection';\n\nimport './index.scss';\n\nconst getPlaceholderPluginKeys = (schema: Schema) => {\n const pluginKeys = [];\n for (const node in schema.nodes) {\n if (schema.nodes[node]) {\n const spec = schema.nodes[node].spec;\n if (spec.placeholder?.customPlugin) {\n pluginKeys.push(spec.placeholder.customPlugin);\n }\n }\n }\n\n return pluginKeys;\n};\n\nconst b = cn('placeholder');\n\nexport const createPlaceholder = (node: Node, parent: Node | null, focus?: boolean) => {\n const content = getPlaceholderContent(node, parent);\n if (!content) return null;\n\n const placeholder = document.createElement('div');\n placeholder.className = b({focus});\n\n const placeholderCursor = document.createElement('span');\n placeholderCursor.className = b('cursor');\n\n const placeholderText = document.createElement('span');\n placeholderText.className = b('text');\n placeholderText.textContent = content;\n\n placeholder.append(placeholderCursor, placeholderText);\n\n return placeholder;\n};\n\nconst placeholderNeeded = (node: Node) => {\n const childrenWithPlaceholderVisible = findChildren(node, (n: Node) =>\n Boolean(n.type.spec.placeholder?.alwaysVisible),\n );\n\n return (\n isNodeEmpty(node) &&\n // If there are child nodes with constant placeholder - give them the priority\n !childrenWithPlaceholderVisible.length\n );\n};\n\nconst addDecoration = (\n widgetsMap: WidgetsMap,\n node: Node,\n pos: number,\n parent: Node | null,\n cursorPos: number | null | undefined,\n globalState: ApplyGlobalState,\n) => {\n const placeholderSpec = node.type.spec.placeholder;\n const decorationPosition = pos + node.childCount + 1;\n\n // We do not add decoration if there is already a placeholder at this position\n if (!placeholderSpec || widgetsMap[decorationPosition]) return;\n\n if (placeholderSpec.customPlugin) {\n widgetsMap[decorationPosition] = placeholderSpec.customPlugin;\n return;\n }\n\n const focus = decorationPosition === cursorPos;\n\n const placeholderDOM = createPlaceholder(node, parent, focus);\n if (!placeholderDOM) return;\n\n if (focus) globalState.hasFocus = true;\n\n widgetsMap[decorationPosition] = {\n pos: decorationPosition,\n toDOM: placeholderDOM,\n spec: {focus},\n };\n};\n\ntype ApplyGlobalState = {hasFocus: boolean};\n\ntype DecoWidgetParameters = Parameters<typeof Decoration.widget>;\ntype WidgetSpec = {\n pos: DecoWidgetParameters[0];\n toDOM: DecoWidgetParameters[1];\n spec?: DecoWidgetParameters[2];\n};\n\ntype PlaceholderPluginState = {decorationSet: DecorationSet; hasFocus: boolean};\n\ntype WidgetsMap = Record<number, WidgetSpec | PluginKey>;\n\nconst pluginKey = new PluginKey<PlaceholderPluginState>('placeholder_plugin');\n\nexport const Placeholder: ExtensionAuto<PlaceholderOptions> = (builder, opts) => {\n builder.context.set('placeholder', opts);\n builder.addPlugin(\n () =>\n new Plugin<PlaceholderPluginState>({\n key: pluginKey,\n props: {\n attributes(state) {\n const attrs: Record<string, string> = {};\n if (pluginKey.getState(state)!.hasFocus) {\n // hide native cursor\n attrs.class = 'g-md-editor-hidecursor';\n }\n return attrs;\n },\n decorations(state) {\n return pluginKey.getState(state)?.decorationSet;\n },\n },\n state: {\n init: (_config, state) => initState(state),\n apply: applyState,\n },\n }),\n builder.Priority.VeryHigh,\n );\n};\n\nfunction getPlaceholderWidgetSpecs(state: EditorState) {\n const globalState: ApplyGlobalState = {hasFocus: false};\n const widgetsMap: WidgetsMap = {};\n const {selection} = state;\n const cursorPos = isTextSelection(selection) ? selection.$cursor?.pos : null;\n\n getPlaceholderPluginKeys(state.schema).forEach((f) => {\n // We use find because it can be used to iterate over the DecorationSet.\n f.getState(state)?.find(undefined, undefined, (spec) => {\n widgetsMap[spec.pos] = f;\n return false;\n });\n });\n\n // Fraw placeholder for all nodes where placeholder is alwaysVisible\n const decorate = (node: Node, pos: number, parent: Node | null) => {\n const placeholderSpec = node.type.spec.placeholder;\n\n if (placeholderSpec && placeholderSpec.alwaysVisible && placeholderNeeded(node)) {\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n };\n\n state.doc.descendants(decorate);\n\n const parentNode = findParentNodeClosestToPos(state.selection.$from, (node: Node) => {\n return Boolean(node.type.spec.placeholder);\n });\n\n const placeholderSpec = parentNode?.node.type.spec.placeholder;\n\n // Draw placeholder if it needs to be draw in the place of cursor\n if (\n parentNode &&\n placeholderNeeded(parentNode.node) &&\n placeholderSpec &&\n !placeholderSpec.alwaysVisible\n ) {\n const {node, pos, depth} = parentNode;\n const parent = depth > 0 ? state.selection.$from.node(depth - 1) : null;\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n\n const widgetSpecs = Object.values(widgetsMap).filter(\n (decoration) => !(decoration instanceof PluginKey),\n ) as WidgetSpec[];\n\n return {widgetSpecs, hasFocus: globalState.hasFocus};\n}\n\nfunction initState(state: EditorState): PlaceholderPluginState {\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(state);\n const decorationSet = DecorationSet.create(\n state.doc,\n widgetSpecs.map((widget) => Decoration.widget(widget.pos, widget.toDOM, widget.spec)),\n );\n\n return {decorationSet, hasFocus};\n}\n\nfunction applyState(\n tr: Transaction,\n oldPluginState: PlaceholderPluginState,\n _oldState: EditorState,\n newState: EditorState,\n): PlaceholderPluginState {\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(newState);\n const {decorationSet} = oldPluginState;\n const oldMappedSet = decorationSet.map(tr.mapping, tr.doc);\n\n // Find all decorations that are present in old and new set\n const decorationsThatDidNotChange = widgetSpecs.reduce((a: Decoration[], {pos, spec}) => {\n const deco = oldMappedSet.find(pos, pos);\n if (deco.length && isEqual(deco[0].spec, spec)) a.push(...deco);\n return a;\n }, []);\n\n // Those are decorations that are presenr only in new set\n const newAddedDecorations = widgetSpecs.filter(\n ({pos}) => !decorationsThatDidNotChange.map(({from}) => from).includes(pos),\n );\n\n // That is a set with decorations that are present in old set and absent in new set\n const notRelevantDecorations = oldMappedSet.remove(decorationsThatDidNotChange);\n let newSet = oldMappedSet;\n // Remove decorations that are not present in new set\n if (notRelevantDecorations.find().length) newSet = newSet.remove(notRelevantDecorations.find());\n // Add new decorations\n if (newAddedDecorations.length)\n newSet = newSet.add(\n tr.doc,\n newAddedDecorations.map((widget) =>\n Decoration.widget(widget.pos, widget.toDOM, widget.spec),\n ),\n );\n\n return {decorationSet: newSet, hasFocus};\n}\n\ndeclare module 'prosemirror-model' {\n interface NodeSpec {\n placeholder?: {\n content: string | ((node: Node, parent?: Node | null) => string | null);\n customPlugin?: PluginKey<DecorationSet>;\n alwaysVisible?: boolean;\n };\n }\n}\n\ndeclare global {\n namespace WysiwygEditor {\n interface Context {\n placeholder: PlaceholderOptions;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["extensions/behavior/Placeholder/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {Node, Schema} from 'prosemirror-model';\nimport {type EditorState, Plugin, PluginKey, type Transaction} from 'prosemirror-state';\n// @ts-ignore // TODO: fix cjs build\nimport {findParentNodeClosestToPos} from 'prosemirror-utils';\nimport {Decoration, DecorationSet} from 'prosemirror-view';\n\nimport {cn} from 'src/classname';\nimport type {ExtensionAuto} from 'src/core';\nimport {isEqual} from 'src/lodash';\nimport {type PlaceholderOptions, getPlaceholderContent} from 'src/utils/placeholder';\nimport {isTextSelection} from 'src/utils/selection';\n\nimport './index.scss';\n\nconst getPlaceholderPluginKeys = (schema: Schema) => {\n const pluginKeys = [];\n for (const node in schema.nodes) {\n if (schema.nodes[node]) {\n const spec = schema.nodes[node].spec;\n if (spec.placeholder?.customPlugin) {\n pluginKeys.push(spec.placeholder.customPlugin);\n }\n }\n }\n\n return pluginKeys;\n};\n\nconst b = cn('placeholder');\n\nexport const createPlaceholder = (node: Node, parent: Node | null, focus?: boolean) => {\n const content = getPlaceholderContent(node, parent);\n if (!content) return null;\n\n const placeholder = document.createElement('div');\n placeholder.className = b({focus});\n\n const placeholderCursor = document.createElement('span');\n placeholderCursor.className = b('cursor');\n\n const placeholderText = document.createElement('span');\n placeholderText.className = b('text');\n placeholderText.textContent = content;\n\n placeholder.append(placeholderCursor, placeholderText);\n\n return placeholder;\n};\n\nconst placeholderNeeded = (node: Node) => {\n // Combine all checks in a single descendants pass\n let hasAlwaysVisiblePlaceholder = false;\n let hasNonEmptyContent = false;\n\n node.descendants((n: Node) => {\n // Check for alwaysVisible placeholder\n if (n.type.spec.placeholder?.alwaysVisible) {\n hasAlwaysVisiblePlaceholder = true;\n return false; // Stop traversal early\n }\n\n // Check if node has non-empty content (same logic as isNodeEmpty)\n if (n.isAtom) {\n hasNonEmptyContent = true;\n return false; // Stop traversal early\n }\n\n if (n.isText && n.textContent) {\n hasNonEmptyContent = true;\n return false; // Stop traversal early\n }\n\n // Non-empty paragraph or other block with content\n if (n.content.size > 0 && n.type.name !== 'paragraph') {\n hasNonEmptyContent = true;\n return false; // Stop traversal early\n }\n\n return true;\n });\n\n return !hasNonEmptyContent && !hasAlwaysVisiblePlaceholder;\n};\n\nconst addDecoration = (\n widgetsMap: WidgetsMap,\n node: Node,\n pos: number,\n parent: Node | null,\n cursorPos: number | null | undefined,\n globalState: ApplyGlobalState,\n) => {\n const placeholderSpec = node.type.spec.placeholder;\n const decorationPosition = pos + node.childCount + 1;\n\n // We do not add decoration if there is already a placeholder at this position\n if (!placeholderSpec || widgetsMap[decorationPosition]) return;\n\n if (placeholderSpec.customPlugin) {\n widgetsMap[decorationPosition] = placeholderSpec.customPlugin;\n return;\n }\n\n const focus = decorationPosition === cursorPos;\n\n const placeholderDOM = createPlaceholder(node, parent, focus);\n if (!placeholderDOM) return;\n\n if (focus) globalState.hasFocus = true;\n\n widgetsMap[decorationPosition] = {\n pos: decorationPosition,\n toDOM: placeholderDOM,\n spec: {focus},\n };\n};\n\ntype ApplyGlobalState = {hasFocus: boolean};\n\ntype DecoWidgetParameters = Parameters<typeof Decoration.widget>;\ntype WidgetSpec = {\n pos: DecoWidgetParameters[0];\n toDOM: DecoWidgetParameters[1];\n spec?: DecoWidgetParameters[2];\n};\n\ntype PlaceholderPluginState = {\n decorationSet: DecorationSet;\n hasFocus: boolean;\n pluginKeys: PluginKey<DecorationSet>[];\n};\n\ntype WidgetsMap = Record<number, WidgetSpec | PluginKey>;\n\nconst pluginKey = new PluginKey<PlaceholderPluginState>('placeholder_plugin');\n\nexport const Placeholder: ExtensionAuto<PlaceholderOptions> = (builder, opts) => {\n builder.context.set('placeholder', opts);\n builder.addPlugin(\n () =>\n new Plugin<PlaceholderPluginState>({\n key: pluginKey,\n props: {\n attributes(state) {\n const attrs: Record<string, string> = {};\n if (pluginKey.getState(state)!.hasFocus) {\n // hide native cursor\n attrs.class = 'g-md-editor-hidecursor';\n }\n return attrs;\n },\n decorations(state) {\n return pluginKey.getState(state)?.decorationSet;\n },\n },\n state: {\n init: (_config, state) => initState(state),\n apply: applyState,\n },\n }),\n builder.Priority.VeryHigh,\n );\n};\n\nfunction getPlaceholderWidgetSpecs(state: EditorState, pluginKeys: PluginKey<DecorationSet>[]) {\n const globalState: ApplyGlobalState = {hasFocus: false};\n const widgetsMap: WidgetsMap = {};\n const {selection} = state;\n const cursorPos = isTextSelection(selection) ? selection.$cursor?.pos : null;\n\n pluginKeys.forEach((f) => {\n // We use find because it can be used to iterate over the DecorationSet.\n f.getState(state)?.find(undefined, undefined, (spec) => {\n widgetsMap[spec.pos] = f;\n return false;\n });\n });\n\n // Draw placeholder for all nodes where placeholder is alwaysVisible\n const decorate = (node: Node, pos: number, parent: Node | null) => {\n const placeholderSpec = node.type.spec.placeholder;\n\n if (placeholderSpec && placeholderSpec.alwaysVisible && placeholderNeeded(node)) {\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n };\n\n state.doc.descendants(decorate);\n\n const parentNode = findParentNodeClosestToPos(state.selection.$from, (node: Node) => {\n return Boolean(node.type.spec.placeholder);\n });\n\n const placeholderSpec = parentNode?.node.type.spec.placeholder;\n\n // Draw placeholder if it needs to be draw in the place of cursor\n if (\n parentNode &&\n placeholderNeeded(parentNode.node) &&\n placeholderSpec &&\n !placeholderSpec.alwaysVisible\n ) {\n const {node, pos, depth} = parentNode;\n const parent = depth > 0 ? state.selection.$from.node(depth - 1) : null;\n addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);\n }\n\n const widgetSpecs = Object.values(widgetsMap).filter(\n (decoration) => !(decoration instanceof PluginKey),\n ) as WidgetSpec[];\n\n return {widgetSpecs, hasFocus: globalState.hasFocus};\n}\n\nfunction initState(state: EditorState): PlaceholderPluginState {\n const pluginKeys = getPlaceholderPluginKeys(state.schema);\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(state, pluginKeys);\n const decorationSet = DecorationSet.create(\n state.doc,\n widgetSpecs.map((widget) => Decoration.widget(widget.pos, widget.toDOM, widget.spec)),\n );\n\n return {decorationSet, hasFocus, pluginKeys};\n}\n\nfunction applyState(\n tr: Transaction,\n oldPluginState: PlaceholderPluginState,\n oldState: EditorState,\n newState: EditorState,\n): PlaceholderPluginState {\n // Early return if document hasn't changed and selection hasn't changed\n // This avoids unnecessary recalculation of decorations\n if (!tr.docChanged && !tr.selectionSet) {\n return oldPluginState;\n }\n\n // Reuse cached plugin keys if schema hasn't changed\n const pluginKeys =\n oldState.schema === newState.schema\n ? oldPluginState.pluginKeys\n : getPlaceholderPluginKeys(newState.schema);\n\n const {widgetSpecs, hasFocus} = getPlaceholderWidgetSpecs(newState, pluginKeys);\n const {decorationSet} = oldPluginState;\n const oldMappedSet = decorationSet.map(tr.mapping, tr.doc);\n\n // Find all decorations that are present in old and new set\n const unchangedPositions = new Set<number>();\n const decorationsThatDidNotChange: Decoration[] = [];\n\n widgetSpecs.forEach(({pos, spec}) => {\n const deco = oldMappedSet.find(pos, pos);\n if (deco.length && isEqual(deco[0].spec, spec)) {\n unchangedPositions.add(pos);\n decorationsThatDidNotChange.push(...deco);\n }\n });\n\n // Those are decorations that are present only in new set\n const newAddedDecorations = widgetSpecs.filter(({pos}) => !unchangedPositions.has(pos));\n\n // That is a set with decorations that are present in old set and absent in new set\n const notRelevantDecorations = oldMappedSet.remove(decorationsThatDidNotChange);\n let newSet = oldMappedSet;\n // Remove decorations that are not present in new set\n if (notRelevantDecorations.find().length) newSet = newSet.remove(notRelevantDecorations.find());\n // Add new decorations\n if (newAddedDecorations.length)\n newSet = newSet.add(\n tr.doc,\n newAddedDecorations.map((widget) =>\n Decoration.widget(widget.pos, widget.toDOM, widget.spec),\n ),\n );\n\n return {decorationSet: newSet, hasFocus, pluginKeys};\n}\n\ndeclare module 'prosemirror-model' {\n interface NodeSpec {\n placeholder?: {\n content: string | ((node: Node, parent?: Node | null) => string | null);\n customPlugin?: PluginKey<DecorationSet>;\n alwaysVisible?: boolean;\n };\n }\n}\n\ndeclare global {\n namespace WysiwygEditor {\n interface Context {\n placeholder: PlaceholderOptions;\n }\n }\n}\n"]}
@@ -2,4 +2,5 @@ import { type NodeType } from 'prosemirror-model';
2
2
  import type { Command } from 'prosemirror-state';
3
3
  export declare function toList(listType: NodeType): Command;
4
4
  export declare const joinPrevList: Command;
5
+ export declare function liftEmptyListItem(itemType: NodeType): Command;
5
6
  export declare function sinkOnlySelectedListItem(itemType: NodeType): Command;
@@ -1,7 +1,9 @@
1
+ import { liftEmptyBlock } from 'prosemirror-commands';
1
2
  import { Fragment, Slice } from 'prosemirror-model';
2
3
  import { wrapInList } from 'prosemirror-schema-list';
3
4
  import { ReplaceAroundStep, liftTarget } from 'prosemirror-transform';
4
5
  import { joinPreviousBlock } from "../../../commands/join.js";
6
+ import { get$CursorAtBlockStart } from "../../../utils/selection.js";
5
7
  import { findAnyParentList, isListNode, isListOrItemNode } from "./utils.js";
6
8
  export function toList(listType) {
7
9
  return (state, dispatch) => {
@@ -19,6 +21,17 @@ export const joinPrevList = joinPreviousBlock({
19
21
  checkPrevNode: isListNode,
20
22
  skipNode: isListOrItemNode,
21
23
  });
24
+ export function liftEmptyListItem(itemType) {
25
+ return (state, dispatch) => {
26
+ const $cursor = get$CursorAtBlockStart(state.selection);
27
+ if (!$cursor ||
28
+ $cursor.parent.content.size !== 0 ||
29
+ $cursor.node(-1).type !== itemType ||
30
+ $cursor.node(-1).childCount !== 1)
31
+ return false;
32
+ return liftEmptyBlock(state, dispatch);
33
+ };
34
+ }
22
35
  /*
23
36
  Simplified `sinkListItem` from `prosemirror-schema-list` without `state`/`dispatch`,
24
37
  sinks list items deeper.
@@ -1 +1 @@
1
- {"version":3,"file":"commands.js","sourceRoot":"../../../../../src","sources":["extensions/markdown/Lists/commands.ts"],"names":[],"mappings":"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","sourcesContent":["import {Fragment, type NodeRange, type NodeType, Slice} from 'prosemirror-model';\nimport {wrapInList} from 'prosemirror-schema-list';\nimport type {Command, Transaction} from 'prosemirror-state';\nimport {ReplaceAroundStep, liftTarget} from 'prosemirror-transform';\n\nimport {joinPreviousBlock} from '../../../commands/join';\n\nimport {findAnyParentList, isListNode, isListOrItemNode} from './utils';\n\nexport function toList(listType: NodeType): Command {\n return (state, dispatch) => {\n const parentList = findAnyParentList(state.schema)(state.selection);\n if (parentList) {\n if (listType === parentList.node.type) return true;\n\n dispatch?.(state.tr.setNodeMarkup(parentList.pos, listType));\n return true;\n }\n return wrapInList(listType)(state, dispatch);\n };\n}\n\nexport const joinPrevList = joinPreviousBlock({\n checkPrevNode: isListNode,\n skipNode: isListOrItemNode,\n});\n\n/*\n Simplified `sinkListItem` from `prosemirror-schema-list` without `state`/`dispatch`,\n sinks list items deeper.\n */\nconst sink = (tr: Transaction, range: NodeRange, itemType: NodeType) => {\n const before = tr.mapping.map(range.start);\n const after = tr.mapping.map(range.end);\n const startIndex = tr.mapping.map(range.startIndex);\n\n const parent = range.parent;\n const nodeBefore = parent.child(startIndex - 1);\n\n const nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type === parent.type;\n const inner = Fragment.from(nestedBefore ? itemType.create() : null);\n const slice = new Slice(\n Fragment.from(itemType.create(null, Fragment.from(parent.type.create(null, inner)))),\n nestedBefore ? 3 : 1,\n 0,\n );\n\n tr.step(\n new ReplaceAroundStep(\n before - (nestedBefore ? 3 : 1),\n after,\n before,\n after,\n slice,\n 1,\n true,\n ),\n );\n return true;\n};\n\nexport function sinkOnlySelectedListItem(itemType: NodeType): Command {\n return ({tr, selection}, dispatch) => {\n const {$from, $to} = selection;\n const selectionRange = $from.blockRange(\n $to,\n (node) => node.childCount > 0 && node.firstChild!.type === itemType,\n );\n if (!selectionRange) {\n return false;\n }\n\n const {startIndex, parent, start, end} = selectionRange;\n if (startIndex === 0) {\n return false;\n }\n\n const nodeBefore = parent.child(startIndex - 1);\n if (nodeBefore.type !== itemType) {\n return false;\n }\n\n if (dispatch) {\n // lifts following list items sequentially to prepare correct nesting structure\n let currentEnd = end - 1;\n while (currentEnd > start) {\n const selectionEnd = tr.mapping.map($to.pos);\n\n const $candidateBlockEnd = tr.doc.resolve(currentEnd);\n const candidateBlockStartPos = $candidateBlockEnd.before($candidateBlockEnd.depth);\n const $candidateBlockStart = tr.doc.resolve(candidateBlockStartPos);\n const candidateBlockRange = $candidateBlockStart.blockRange($candidateBlockEnd);\n\n if (candidateBlockRange?.start) {\n const $rangeStart = tr.doc.resolve(candidateBlockRange.start);\n const shouldLift =\n candidateBlockRange.start > selectionEnd && isListNode($rangeStart.parent);\n\n if (shouldLift) {\n currentEnd = candidateBlockRange.start;\n\n const targetDepth = liftTarget(candidateBlockRange);\n if (targetDepth !== null) {\n tr.lift(candidateBlockRange, targetDepth);\n }\n }\n }\n\n currentEnd--;\n }\n\n // sinks the selected list item deeper into the list hierarchy\n sink(tr, selectionRange, itemType);\n\n dispatch(tr.scrollIntoView());\n return true;\n }\n return true;\n };\n}\n"]}
1
+ {"version":3,"file":"commands.js","sourceRoot":"../../../../../src","sources":["extensions/markdown/Lists/commands.ts"],"names":[],"mappings":"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","sourcesContent":["import {liftEmptyBlock} from 'prosemirror-commands';\nimport {Fragment, type NodeRange, type NodeType, Slice} from 'prosemirror-model';\nimport {wrapInList} from 'prosemirror-schema-list';\nimport type {Command, Transaction} from 'prosemirror-state';\nimport {ReplaceAroundStep, liftTarget} from 'prosemirror-transform';\n\nimport {joinPreviousBlock} from '../../../commands/join';\nimport {get$CursorAtBlockStart} from '../../../utils/selection';\n\nimport {findAnyParentList, isListNode, isListOrItemNode} from './utils';\n\nexport function toList(listType: NodeType): Command {\n return (state, dispatch) => {\n const parentList = findAnyParentList(state.schema)(state.selection);\n if (parentList) {\n if (listType === parentList.node.type) return true;\n\n dispatch?.(state.tr.setNodeMarkup(parentList.pos, listType));\n return true;\n }\n return wrapInList(listType)(state, dispatch);\n };\n}\n\nexport const joinPrevList = joinPreviousBlock({\n checkPrevNode: isListNode,\n skipNode: isListOrItemNode,\n});\n\nexport function liftEmptyListItem(itemType: NodeType): Command {\n return (state, dispatch) => {\n const $cursor = get$CursorAtBlockStart(state.selection);\n if (\n !$cursor ||\n $cursor.parent.content.size !== 0 ||\n $cursor.node(-1).type !== itemType ||\n $cursor.node(-1).childCount !== 1\n )\n return false;\n\n return liftEmptyBlock(state, dispatch);\n };\n}\n\n/*\n Simplified `sinkListItem` from `prosemirror-schema-list` without `state`/`dispatch`,\n sinks list items deeper.\n */\nconst sink = (tr: Transaction, range: NodeRange, itemType: NodeType) => {\n const before = tr.mapping.map(range.start);\n const after = tr.mapping.map(range.end);\n const startIndex = tr.mapping.map(range.startIndex);\n\n const parent = range.parent;\n const nodeBefore = parent.child(startIndex - 1);\n\n const nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type === parent.type;\n const inner = Fragment.from(nestedBefore ? itemType.create() : null);\n const slice = new Slice(\n Fragment.from(itemType.create(null, Fragment.from(parent.type.create(null, inner)))),\n nestedBefore ? 3 : 1,\n 0,\n );\n\n tr.step(\n new ReplaceAroundStep(\n before - (nestedBefore ? 3 : 1),\n after,\n before,\n after,\n slice,\n 1,\n true,\n ),\n );\n return true;\n};\n\nexport function sinkOnlySelectedListItem(itemType: NodeType): Command {\n return ({tr, selection}, dispatch) => {\n const {$from, $to} = selection;\n const selectionRange = $from.blockRange(\n $to,\n (node) => node.childCount > 0 && node.firstChild!.type === itemType,\n );\n if (!selectionRange) {\n return false;\n }\n\n const {startIndex, parent, start, end} = selectionRange;\n if (startIndex === 0) {\n return false;\n }\n\n const nodeBefore = parent.child(startIndex - 1);\n if (nodeBefore.type !== itemType) {\n return false;\n }\n\n if (dispatch) {\n // lifts following list items sequentially to prepare correct nesting structure\n let currentEnd = end - 1;\n while (currentEnd > start) {\n const selectionEnd = tr.mapping.map($to.pos);\n\n const $candidateBlockEnd = tr.doc.resolve(currentEnd);\n const candidateBlockStartPos = $candidateBlockEnd.before($candidateBlockEnd.depth);\n const $candidateBlockStart = tr.doc.resolve(candidateBlockStartPos);\n const candidateBlockRange = $candidateBlockStart.blockRange($candidateBlockEnd);\n\n if (candidateBlockRange?.start) {\n const $rangeStart = tr.doc.resolve(candidateBlockRange.start);\n const shouldLift =\n candidateBlockRange.start > selectionEnd && isListNode($rangeStart.parent);\n\n if (shouldLift) {\n currentEnd = candidateBlockRange.start;\n\n const targetDepth = liftTarget(candidateBlockRange);\n if (targetDepth !== null) {\n tr.lift(candidateBlockRange, targetDepth);\n }\n }\n }\n\n currentEnd--;\n }\n\n // sinks the selected list item deeper into the list hierarchy\n sink(tr, selectionRange, itemType);\n\n dispatch(tr.scrollIntoView());\n return true;\n }\n return true;\n };\n}\n"]}
@@ -2,7 +2,7 @@ import { liftListItem, splitListItem } from 'prosemirror-schema-list';
2
2
  import { withLogAction } from "../../../utils/keymap.js";
3
3
  import { ListsSpecs, blType, liType, olType } from "./ListsSpecs/index.js";
4
4
  import { actions } from "./actions.js";
5
- import { joinPrevList, sinkOnlySelectedListItem, toList } from "./commands.js";
5
+ import { joinPrevList, liftEmptyListItem, sinkOnlySelectedListItem, toList } from "./commands.js";
6
6
  import { ListAction } from "./const.js";
7
7
  import { ListsInputRulesExtension } from "./inputrules.js";
8
8
  import { collapseListsPlugin } from "./plugins/CollapseListsPlugin.js";
@@ -20,6 +20,7 @@ export const Lists = (builder, opts) => {
20
20
  return {
21
21
  Tab: sinkOnlySelectedListItem(liType(schema)),
22
22
  'Shift-Tab': liftListItem(liType(schema)),
23
+ Backspace: liftEmptyListItem(liType(schema)),
23
24
  'Mod-[': liftListItem(liType(schema)),
24
25
  'Mod-]': sinkOnlySelectedListItem(liType(schema)),
25
26
  ...bindings,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["extensions/markdown/Lists/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAC;AAGpE,OAAO,EAAC,aAAa,EAAC,iCAA8B;AAEpD,OAAO,EAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAC,8BAAqB;AAChE,OAAO,EAAC,OAAO,EAAC,qBAAkB;AAClC,OAAO,EAAC,YAAY,EAAE,wBAAwB,EAAE,MAAM,EAAC,sBAAmB;AAC1E,OAAO,EAAC,UAAU,EAAC,mBAAgB;AACnC,OAAO,EAAC,wBAAwB,EAA8B,wBAAqB;AACnF,OAAO,EAAC,mBAAmB,EAAC,yCAAsC;AAClE,OAAO,EAAC,gBAAgB,EAAC,sCAAmC;AAE5D,OAAO,EAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAC,8BAAqB;AAQzE,MAAM,CAAC,MAAM,KAAK,GAAgC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;IAChE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAExB,OAAO,CAAC,SAAS,CAAC,CAAC,EAAC,MAAM,EAAC,EAAE,EAAE;QAC3B,MAAM,EAAC,KAAK,EAAE,KAAK,EAAC,GAAG,IAAI,IAAI,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAW,EAAE,CAAC;QAC5B,IAAI,KAAK;YAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACjF,IAAI,KAAK;YAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAElF,OAAO;YACH,GAAG,EAAE,wBAAwB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC7C,WAAW,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAEzC,OAAO,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACrC,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAEjD,GAAG,QAAQ;SACd,CAAC;IACN,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAE1B,OAAO,CAAC,SAAS,CACb,CAAC,EAAC,MAAM,EAAC,EAAE,EAAE,CAAC,CAAC;QACX,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpC,SAAS,EAAE,YAAY;KAC1B,CAAC,EACF,OAAO,CAAC,QAAQ,CAAC,GAAG,CACvB,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,EAAC,mBAAmB,EAAE,IAAI,EAAE,YAAY,EAAC,CAAC,CAAC;IAEjF,OAAO,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAEpC,OAAO,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAEvC,OAAO;SACF,SAAS,CAAC,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC;SACxD,SAAS,CAAC,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,aAAa,CAAC;SAC1D,SAAS,CAAC,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC;SACxD,SAAS,CAAC,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;AAClE,CAAC,CAAC","sourcesContent":["import {liftListItem, splitListItem} from 'prosemirror-schema-list';\n\nimport type {Action, ExtensionAuto, Keymap} from '../../../core';\nimport {withLogAction} from '../../../utils/keymap';\n\nimport {ListsSpecs, blType, liType, olType} from './ListsSpecs';\nimport {actions} from './actions';\nimport {joinPrevList, sinkOnlySelectedListItem, toList} from './commands';\nimport {ListAction} from './const';\nimport {ListsInputRulesExtension, type ListsInputRulesOptions} from './inputrules';\nimport {collapseListsPlugin} from './plugins/CollapseListsPlugin';\nimport {mergeListsPlugin} from './plugins/MergeListsPlugin';\n\nexport {ListNode, ListsAttr, blType, liType, olType} from './ListsSpecs';\n\nexport type ListsOptions = {\n ulKey?: string | null;\n olKey?: string | null;\n ulInputRules?: ListsInputRulesOptions['bulletListInputRule'];\n};\n\nexport const Lists: ExtensionAuto<ListsOptions> = (builder, opts) => {\n builder.use(ListsSpecs);\n\n builder.addKeymap(({schema}) => {\n const {ulKey, olKey} = opts ?? {};\n const bindings: Keymap = {};\n if (ulKey) bindings[ulKey] = withLogAction('bulletList', toList(blType(schema)));\n if (olKey) bindings[olKey] = withLogAction('orderedList', toList(olType(schema)));\n\n return {\n Tab: sinkOnlySelectedListItem(liType(schema)),\n 'Shift-Tab': liftListItem(liType(schema)),\n\n 'Mod-[': liftListItem(liType(schema)),\n 'Mod-]': sinkOnlySelectedListItem(liType(schema)),\n\n ...bindings,\n };\n }, builder.Priority.High);\n\n builder.addKeymap(\n ({schema}) => ({\n Enter: splitListItem(liType(schema)),\n Backspace: joinPrevList,\n }),\n builder.Priority.Low,\n );\n\n builder.use(ListsInputRulesExtension, {bulletListInputRule: opts?.ulInputRules});\n\n builder.addPlugin(mergeListsPlugin);\n\n builder.addPlugin(collapseListsPlugin);\n\n builder\n .addAction(ListAction.ToBulletList, actions.toBulletList)\n .addAction(ListAction.ToOrderedList, actions.toOrderedList)\n .addAction(ListAction.SinkListItem, actions.sinkListItem)\n .addAction(ListAction.LiftListItem, actions.liftListItem);\n};\n\ndeclare global {\n namespace WysiwygEditor {\n interface Actions {\n [ListAction.ToBulletList]: Action;\n [ListAction.ToOrderedList]: Action;\n [ListAction.SinkListItem]: Action;\n [ListAction.LiftListItem]: Action;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["extensions/markdown/Lists/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAC;AAGpE,OAAO,EAAC,aAAa,EAAC,iCAA8B;AAEpD,OAAO,EAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAC,8BAAqB;AAChE,OAAO,EAAC,OAAO,EAAC,qBAAkB;AAClC,OAAO,EAAC,YAAY,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,EAAC,sBAAmB;AAC7F,OAAO,EAAC,UAAU,EAAC,mBAAgB;AACnC,OAAO,EAAC,wBAAwB,EAA8B,wBAAqB;AACnF,OAAO,EAAC,mBAAmB,EAAC,yCAAsC;AAClE,OAAO,EAAC,gBAAgB,EAAC,sCAAmC;AAE5D,OAAO,EAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAC,8BAAqB;AAQzE,MAAM,CAAC,MAAM,KAAK,GAAgC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;IAChE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAExB,OAAO,CAAC,SAAS,CAAC,CAAC,EAAC,MAAM,EAAC,EAAE,EAAE;QAC3B,MAAM,EAAC,KAAK,EAAE,KAAK,EAAC,GAAG,IAAI,IAAI,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAW,EAAE,CAAC;QAC5B,IAAI,KAAK;YAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACjF,IAAI,KAAK;YAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAElF,OAAO;YACH,GAAG,EAAE,wBAAwB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC7C,WAAW,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACzC,SAAS,EAAE,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAE5C,OAAO,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACrC,OAAO,EAAE,wBAAwB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAEjD,GAAG,QAAQ;SACd,CAAC;IACN,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAE1B,OAAO,CAAC,SAAS,CACb,CAAC,EAAC,MAAM,EAAC,EAAE,EAAE,CAAC,CAAC;QACX,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpC,SAAS,EAAE,YAAY;KAC1B,CAAC,EACF,OAAO,CAAC,QAAQ,CAAC,GAAG,CACvB,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,EAAC,mBAAmB,EAAE,IAAI,EAAE,YAAY,EAAC,CAAC,CAAC;IAEjF,OAAO,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAEpC,OAAO,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAEvC,OAAO;SACF,SAAS,CAAC,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC;SACxD,SAAS,CAAC,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,aAAa,CAAC;SAC1D,SAAS,CAAC,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC;SACxD,SAAS,CAAC,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;AAClE,CAAC,CAAC","sourcesContent":["import {liftListItem, splitListItem} from 'prosemirror-schema-list';\n\nimport type {Action, ExtensionAuto, Keymap} from '../../../core';\nimport {withLogAction} from '../../../utils/keymap';\n\nimport {ListsSpecs, blType, liType, olType} from './ListsSpecs';\nimport {actions} from './actions';\nimport {joinPrevList, liftEmptyListItem, sinkOnlySelectedListItem, toList} from './commands';\nimport {ListAction} from './const';\nimport {ListsInputRulesExtension, type ListsInputRulesOptions} from './inputrules';\nimport {collapseListsPlugin} from './plugins/CollapseListsPlugin';\nimport {mergeListsPlugin} from './plugins/MergeListsPlugin';\n\nexport {ListNode, ListsAttr, blType, liType, olType} from './ListsSpecs';\n\nexport type ListsOptions = {\n ulKey?: string | null;\n olKey?: string | null;\n ulInputRules?: ListsInputRulesOptions['bulletListInputRule'];\n};\n\nexport const Lists: ExtensionAuto<ListsOptions> = (builder, opts) => {\n builder.use(ListsSpecs);\n\n builder.addKeymap(({schema}) => {\n const {ulKey, olKey} = opts ?? {};\n const bindings: Keymap = {};\n if (ulKey) bindings[ulKey] = withLogAction('bulletList', toList(blType(schema)));\n if (olKey) bindings[olKey] = withLogAction('orderedList', toList(olType(schema)));\n\n return {\n Tab: sinkOnlySelectedListItem(liType(schema)),\n 'Shift-Tab': liftListItem(liType(schema)),\n Backspace: liftEmptyListItem(liType(schema)),\n\n 'Mod-[': liftListItem(liType(schema)),\n 'Mod-]': sinkOnlySelectedListItem(liType(schema)),\n\n ...bindings,\n };\n }, builder.Priority.High);\n\n builder.addKeymap(\n ({schema}) => ({\n Enter: splitListItem(liType(schema)),\n Backspace: joinPrevList,\n }),\n builder.Priority.Low,\n );\n\n builder.use(ListsInputRulesExtension, {bulletListInputRule: opts?.ulInputRules});\n\n builder.addPlugin(mergeListsPlugin);\n\n builder.addPlugin(collapseListsPlugin);\n\n builder\n .addAction(ListAction.ToBulletList, actions.toBulletList)\n .addAction(ListAction.ToOrderedList, actions.toOrderedList)\n .addAction(ListAction.SinkListItem, actions.sinkListItem)\n .addAction(ListAction.LiftListItem, actions.liftListItem);\n};\n\ndeclare global {\n namespace WysiwygEditor {\n interface Actions {\n [ListAction.ToBulletList]: Action;\n [ListAction.ToOrderedList]: Action;\n [ListAction.SinkListItem]: Action;\n [ListAction.LiftListItem]: Action;\n }\n }\n}\n"]}
@@ -0,0 +1,27 @@
1
+ type AutoScrollDirection = 'horizontal' | 'vertical' | 'both';
2
+ type AutoScrollOptions = {
3
+ edgeThreshold?: number;
4
+ maxSpeed?: number;
5
+ };
6
+ export declare class DnDAutoScroller {
7
+ private readonly _container;
8
+ private readonly _direction;
9
+ private readonly _edgeThreshold;
10
+ private readonly _maxSpeed;
11
+ private _clientX;
12
+ private _clientY;
13
+ private _rafId;
14
+ private _active;
15
+ constructor(scrollContainer: HTMLElement, direction: AutoScrollDirection, options?: AutoScrollOptions);
16
+ update(clientX: number, clientY: number): void;
17
+ destroy(): void;
18
+ private _startLoop;
19
+ private _getVisibleRect;
20
+ private _scrollStep;
21
+ /**
22
+ * Returns scroll delta for one axis.
23
+ * Negative when cursor is near the start edge, positive when near the end edge.
24
+ */
25
+ private _calcDelta;
26
+ }
27
+ export {};
@@ -0,0 +1,77 @@
1
+ const DEFAULT_EDGE_THRESHOLD = 40; // px
2
+ const DEFAULT_MAX_SPEED = 15; // px per frame
3
+ export class DnDAutoScroller {
4
+ _container;
5
+ _direction;
6
+ _edgeThreshold;
7
+ _maxSpeed;
8
+ _clientX = 0;
9
+ _clientY = 0;
10
+ _rafId = null;
11
+ _active = false;
12
+ constructor(scrollContainer, direction, options) {
13
+ this._container = scrollContainer;
14
+ this._direction = direction;
15
+ this._edgeThreshold = options?.edgeThreshold ?? DEFAULT_EDGE_THRESHOLD;
16
+ this._maxSpeed = options?.maxSpeed ?? DEFAULT_MAX_SPEED;
17
+ }
18
+ update(clientX, clientY) {
19
+ this._clientX = clientX;
20
+ this._clientY = clientY;
21
+ if (!this._active) {
22
+ this._active = true;
23
+ this._startLoop();
24
+ }
25
+ }
26
+ destroy() {
27
+ if (this._rafId !== null) {
28
+ cancelAnimationFrame(this._rafId);
29
+ this._rafId = null;
30
+ }
31
+ }
32
+ _startLoop() {
33
+ const tick = () => {
34
+ this._scrollStep();
35
+ this._rafId = requestAnimationFrame(tick);
36
+ };
37
+ this._rafId = requestAnimationFrame(tick);
38
+ }
39
+ _getVisibleRect() {
40
+ if (this._container === this._container.ownerDocument.documentElement) {
41
+ return { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight };
42
+ }
43
+ return this._container.getBoundingClientRect();
44
+ }
45
+ _scrollStep() {
46
+ const rect = this._getVisibleRect();
47
+ let dx = 0;
48
+ let dy = 0;
49
+ if (this._direction === 'horizontal' || this._direction === 'both') {
50
+ dx = this._calcDelta(this._clientX, rect.left, rect.right);
51
+ }
52
+ if (this._direction === 'vertical' || this._direction === 'both') {
53
+ dy = this._calcDelta(this._clientY, rect.top, rect.bottom);
54
+ }
55
+ if (dx !== 0 || dy !== 0) {
56
+ this._container.scrollBy(dx, dy);
57
+ }
58
+ }
59
+ /**
60
+ * Returns scroll delta for one axis.
61
+ * Negative when cursor is near the start edge, positive when near the end edge.
62
+ */
63
+ _calcDelta(cursor, edgeStart, edgeEnd) {
64
+ const distToStart = cursor - edgeStart;
65
+ const distToEnd = edgeEnd - cursor;
66
+ if (distToStart >= 0 && distToStart < this._edgeThreshold) {
67
+ const ratio = 1 - distToStart / this._edgeThreshold;
68
+ return -Math.round(this._maxSpeed * ratio);
69
+ }
70
+ if (distToEnd >= 0 && distToEnd < this._edgeThreshold) {
71
+ const ratio = 1 - distToEnd / this._edgeThreshold;
72
+ return Math.round(this._maxSpeed * ratio);
73
+ }
74
+ return 0;
75
+ }
76
+ }
77
+ //# sourceMappingURL=dnd-auto-scroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dnd-auto-scroll.js","sourceRoot":"../../../../../../../../src","sources":["extensions/yfm/YfmTable/plugins/YfmTableControls/dnd/dnd-auto-scroll.ts"],"names":[],"mappings":"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","sourcesContent":["type AutoScrollDirection = 'horizontal' | 'vertical' | 'both';\n\ntype AutoScrollOptions = {\n edgeThreshold?: number;\n maxSpeed?: number;\n};\n\nconst DEFAULT_EDGE_THRESHOLD = 40; // px\nconst DEFAULT_MAX_SPEED = 15; // px per frame\n\nexport class DnDAutoScroller {\n private readonly _container: HTMLElement;\n private readonly _direction: AutoScrollDirection;\n private readonly _edgeThreshold: number;\n private readonly _maxSpeed: number;\n\n private _clientX = 0;\n private _clientY = 0;\n private _rafId: number | null = null;\n private _active = false;\n\n constructor(\n scrollContainer: HTMLElement,\n direction: AutoScrollDirection,\n options?: AutoScrollOptions,\n ) {\n this._container = scrollContainer;\n this._direction = direction;\n this._edgeThreshold = options?.edgeThreshold ?? DEFAULT_EDGE_THRESHOLD;\n this._maxSpeed = options?.maxSpeed ?? DEFAULT_MAX_SPEED;\n }\n\n update(clientX: number, clientY: number): void {\n this._clientX = clientX;\n this._clientY = clientY;\n\n if (!this._active) {\n this._active = true;\n this._startLoop();\n }\n }\n\n destroy(): void {\n if (this._rafId !== null) {\n cancelAnimationFrame(this._rafId);\n this._rafId = null;\n }\n }\n\n private _startLoop(): void {\n const tick = () => {\n this._scrollStep();\n this._rafId = requestAnimationFrame(tick);\n };\n this._rafId = requestAnimationFrame(tick);\n }\n\n private _getVisibleRect(): {left: number; right: number; top: number; bottom: number} {\n if (this._container === this._container.ownerDocument.documentElement) {\n return {left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight};\n }\n return this._container.getBoundingClientRect();\n }\n\n private _scrollStep(): void {\n const rect = this._getVisibleRect();\n let dx = 0;\n let dy = 0;\n\n if (this._direction === 'horizontal' || this._direction === 'both') {\n dx = this._calcDelta(this._clientX, rect.left, rect.right);\n }\n\n if (this._direction === 'vertical' || this._direction === 'both') {\n dy = this._calcDelta(this._clientY, rect.top, rect.bottom);\n }\n\n if (dx !== 0 || dy !== 0) {\n this._container.scrollBy(dx, dy);\n }\n }\n\n /**\n * Returns scroll delta for one axis.\n * Negative when cursor is near the start edge, positive when near the end edge.\n */\n private _calcDelta(cursor: number, edgeStart: number, edgeEnd: number): number {\n const distToStart = cursor - edgeStart;\n const distToEnd = edgeEnd - cursor;\n\n if (distToStart >= 0 && distToStart < this._edgeThreshold) {\n const ratio = 1 - distToStart / this._edgeThreshold;\n return -Math.round(this._maxSpeed * ratio);\n }\n\n if (distToEnd >= 0 && distToEnd < this._edgeThreshold) {\n const ratio = 1 - distToEnd / this._edgeThreshold;\n return Math.round(this._maxSpeed * ratio);\n }\n\n return 0;\n }\n}\n"]}
@@ -8,6 +8,7 @@ import { YfmTableNode } from "../../../YfmTableSpecs/index.js";
8
8
  import { clearAllSelections, selectDraggedColumn, selectDraggedRow } from "../plugins/dnd-plugin.js";
9
9
  import { hideHoverDecos } from "../plugins/focus-plugin.js";
10
10
  import { getSelectedCellsForColumns, getSelectedCellsForRows } from "../utils.js";
11
+ import { DnDAutoScroller } from "./dnd-auto-scroll.js";
11
12
  import { TableColumnDropCursor, TableRowDropCursor, } from "./dnd-drop-cursor.js";
12
13
  import { YfmTableDnDGhost } from "./dnd-ghost.js";
13
14
  import "./dnd.css";
@@ -162,6 +163,10 @@ class YfmTableRowDnDHandler extends YfmTableDnDAbstractHandler {
162
163
  rangeIdx: draggedRangeIdx,
163
164
  tableDesc,
164
165
  });
166
+ const scrollContainer = findScrollableAncestor(this._editorView.dom, 'vertical');
167
+ const autoScroller = scrollContainer
168
+ ? new DnDAutoScroller(scrollContainer, 'vertical')
169
+ : null;
165
170
  const onMoveDebounced = debounce((event) => {
166
171
  this._moveDragging(event, {
167
172
  rangeIdx: draggedRangeIdx,
@@ -170,11 +175,13 @@ class YfmTableRowDnDHandler extends YfmTableDnDAbstractHandler {
170
175
  }, MOUSE_MOVE_DEBOUNCE, { maxWait: MOUSE_MOVE_DEBOUNCE });
171
176
  const onMove = (event) => {
172
177
  ghost.move(event);
178
+ autoScroller?.update(event.clientX, event.clientY);
173
179
  onMoveDebounced(event);
174
180
  };
175
181
  document.addEventListener('mousemove', onMove);
176
182
  document.addEventListener('mouseup', () => {
177
183
  onMoveDebounced.flush();
184
+ autoScroller?.destroy();
178
185
  ghost.destroy();
179
186
  document.removeEventListener('mousemove', onMove);
180
187
  this._endDragging(currRowRange, tableDesc);
@@ -323,6 +330,8 @@ class YfmTableColumnDnDHandler extends YfmTableDnDAbstractHandler {
323
330
  rangeIdx: draggedRangeIdx,
324
331
  tableDesc,
325
332
  });
333
+ const { node: tableElem } = this._editorView.domAtPos(tableDesc.pos + 1);
334
+ const autoScroller = tableElem instanceof HTMLElement ? new DnDAutoScroller(tableElem, 'horizontal') : null;
326
335
  const onMoveDebounced = debounce((event) => {
327
336
  this._moveDragging(event, {
328
337
  rangeIdx: draggedRangeIdx,
@@ -331,11 +340,13 @@ class YfmTableColumnDnDHandler extends YfmTableDnDAbstractHandler {
331
340
  }, MOUSE_MOVE_DEBOUNCE, { maxWait: MOUSE_MOVE_DEBOUNCE });
332
341
  const onMove = (event) => {
333
342
  ghost.move(event);
343
+ autoScroller?.update(event.clientX, event.clientY);
334
344
  onMoveDebounced(event);
335
345
  };
336
346
  document.addEventListener('mousemove', onMove);
337
347
  document.addEventListener('mouseup', () => {
338
348
  onMoveDebounced.flush();
349
+ autoScroller?.destroy();
339
350
  ghost.destroy();
340
351
  document.removeEventListener('mousemove', onMove);
341
352
  this._endDragging(currColumnRange, tableDesc);
@@ -475,4 +486,26 @@ function isDragThresholdPassed(init, curr) {
475
486
  return (Math.abs(init.pageX - curr.pageX) >= DRAG_START_THRESHOLD ||
476
487
  Math.abs(init.pageY - curr.pageY) >= DRAG_START_THRESHOLD);
477
488
  }
489
+ function findScrollableAncestor(element, axis) {
490
+ const prop = axis === 'vertical' ? 'overflowY' : 'overflowX';
491
+ let current = element.parentElement;
492
+ while (current && current !== document.documentElement) {
493
+ const overflow = getComputedStyle(current)[prop];
494
+ if (overflow === 'auto' || overflow === 'scroll') {
495
+ const hasScroll = axis === 'vertical'
496
+ ? current.scrollHeight > current.clientHeight
497
+ : current.scrollWidth > current.clientWidth;
498
+ if (hasScroll)
499
+ return current;
500
+ }
501
+ current = current.parentElement;
502
+ }
503
+ const docEl = element.ownerDocument.documentElement;
504
+ const hasDocScroll = axis === 'vertical'
505
+ ? docEl.scrollHeight > docEl.clientHeight
506
+ : docEl.scrollWidth > docEl.clientWidth;
507
+ if (hasDocScroll)
508
+ return docEl;
509
+ return null;
510
+ }
478
511
  //# sourceMappingURL=dnd.js.map