@kerebron/extension-basic-editor 0.5.2 → 0.5.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.
Files changed (88) hide show
  1. package/assets/image-resize.css +76 -0
  2. package/esm/BasicEditorKit.d.ts.map +1 -1
  3. package/esm/BasicEditorKit.js +7 -1
  4. package/esm/BasicEditorKit.js.map +1 -1
  5. package/esm/ExtensionBasicCodeEditor.d.ts.map +1 -1
  6. package/esm/ExtensionBasicCodeEditor.js +4 -2
  7. package/esm/ExtensionBasicCodeEditor.js.map +1 -1
  8. package/esm/ExtensionMediaUpload.d.ts +1 -1
  9. package/esm/ExtensionMediaUpload.d.ts.map +1 -1
  10. package/esm/ExtensionMediaUpload.js.map +1 -1
  11. package/esm/MarkLink.d.ts.map +1 -1
  12. package/esm/MarkLink.js +3 -1
  13. package/esm/MarkLink.js.map +1 -1
  14. package/esm/MarkUnderline.d.ts.map +1 -1
  15. package/esm/MarkUnderline.js +0 -5
  16. package/esm/MarkUnderline.js.map +1 -1
  17. package/esm/NodeBookmark.d.ts.map +1 -1
  18. package/esm/NodeBookmark.js +6 -2
  19. package/esm/NodeBookmark.js.map +1 -1
  20. package/esm/NodeBulletList.d.ts +8 -0
  21. package/esm/NodeBulletList.d.ts.map +1 -1
  22. package/esm/NodeBulletList.js +8 -0
  23. package/esm/NodeBulletList.js.map +1 -1
  24. package/esm/NodeCodeBlock.js +1 -1
  25. package/esm/NodeCodeBlock.js.map +1 -1
  26. package/esm/NodeCommentAnchor.d.ts +15 -0
  27. package/esm/NodeCommentAnchor.d.ts.map +1 -0
  28. package/esm/NodeCommentAnchor.js +133 -0
  29. package/esm/NodeCommentAnchor.js.map +1 -0
  30. package/esm/NodeHorizontalRule.d.ts.map +1 -1
  31. package/esm/NodeHorizontalRule.js +2 -1
  32. package/esm/NodeHorizontalRule.js.map +1 -1
  33. package/esm/NodeImage.d.ts +1 -1
  34. package/esm/NodeImage.d.ts.map +1 -1
  35. package/esm/NodeImage.js +4 -1
  36. package/esm/NodeImage.js.map +1 -1
  37. package/esm/NodeInlineShortCode.d.ts +1 -3
  38. package/esm/NodeInlineShortCode.d.ts.map +1 -1
  39. package/esm/NodeInlineShortCode.js +11 -3
  40. package/esm/NodeInlineShortCode.js.map +1 -1
  41. package/esm/NodeListItem.d.ts +12 -2
  42. package/esm/NodeListItem.d.ts.map +1 -1
  43. package/esm/NodeListItem.js +24 -4
  44. package/esm/NodeListItem.js.map +1 -1
  45. package/esm/NodeOrderedList.d.ts +11 -2
  46. package/esm/NodeOrderedList.d.ts.map +1 -1
  47. package/esm/NodeOrderedList.js +11 -2
  48. package/esm/NodeOrderedList.js.map +1 -1
  49. package/esm/NodeParagraph.d.ts +1 -0
  50. package/esm/NodeParagraph.d.ts.map +1 -1
  51. package/esm/NodeParagraph.js +4 -4
  52. package/esm/NodeParagraph.js.map +1 -1
  53. package/esm/NodeSoftBreak.d.ts +8 -0
  54. package/esm/NodeSoftBreak.d.ts.map +1 -0
  55. package/esm/NodeSoftBreak.js +17 -0
  56. package/esm/NodeSoftBreak.js.map +1 -0
  57. package/esm/pairing/ExtensionPairing.d.ts +7 -0
  58. package/esm/pairing/ExtensionPairing.d.ts.map +1 -0
  59. package/esm/pairing/ExtensionPairing.js +11 -0
  60. package/esm/pairing/ExtensionPairing.js.map +1 -0
  61. package/esm/pairing/PairNodesPlugin.d.ts +3 -0
  62. package/esm/pairing/PairNodesPlugin.d.ts.map +1 -0
  63. package/esm/pairing/PairNodesPlugin.js +45 -0
  64. package/esm/pairing/PairNodesPlugin.js.map +1 -0
  65. package/esm/pairing/pairNodes.d.ts +5 -0
  66. package/esm/pairing/pairNodes.d.ts.map +1 -0
  67. package/esm/pairing/pairNodes.js +81 -0
  68. package/esm/pairing/pairNodes.js.map +1 -0
  69. package/package.json +7 -3
  70. package/src/BasicEditorKit.ts +7 -1
  71. package/src/ExtensionBasicCodeEditor.ts +4 -2
  72. package/src/ExtensionMediaUpload.ts +1 -1
  73. package/src/MarkLink.ts +3 -1
  74. package/src/MarkUnderline.ts +0 -7
  75. package/src/NodeBookmark.ts +6 -2
  76. package/src/NodeBulletList.ts +9 -0
  77. package/src/NodeCodeBlock.ts +1 -1
  78. package/src/NodeCommentAnchor.ts +170 -0
  79. package/src/NodeHorizontalRule.ts +2 -1
  80. package/src/NodeImage.ts +6 -3
  81. package/src/NodeInlineShortCode.ts +17 -7
  82. package/src/NodeListItem.ts +35 -9
  83. package/src/NodeOrderedList.ts +11 -2
  84. package/src/NodeParagraph.ts +5 -5
  85. package/src/NodeSoftBreak.ts +19 -0
  86. package/src/pairing/ExtensionPairing.ts +14 -0
  87. package/src/pairing/PairNodesPlugin.ts +56 -0
  88. package/src/pairing/pairNodes.ts +108 -0
@@ -6,8 +6,9 @@ import { ExtensionDropcursor } from './ExtensionDropcursor.js';
6
6
  import { ExtensionGapcursor } from './ExtensionGapcursor.js';
7
7
  import { ExtensionHtml } from './ExtensionHtml.js';
8
8
  import { ExtensionMediaUpload } from './ExtensionMediaUpload.js';
9
- import { ExtensionRemoteSelection } from './remote-selection/ExtensionRemoteSelection.js';
10
9
  import { ExtensionTextAlign } from './ExtensionTextAlign.js';
10
+ import { ExtensionRemoteSelection } from './remote-selection/ExtensionRemoteSelection.js';
11
+ import { ExtensionPairing } from './pairing/ExtensionPairing.js';
11
12
 
12
13
  import { MarkLink } from './MarkLink.js';
13
14
  import { MarkStrong } from './MarkStrong.js';
@@ -28,6 +29,7 @@ import { NodeCodeBlock } from './NodeCodeBlock.js';
28
29
  import { NodeBookmark } from './NodeBookmark.js';
29
30
  import { NodeParagraph } from './NodeParagraph.js';
30
31
  import { NodeHardBreak } from './NodeHardBreak.js';
32
+ import { NodeSoftBreak } from './NodeSoftBreak.js';
31
33
  import { NodeHorizontalRule } from './NodeHorizontalRule.js';
32
34
  import { NodeOrderedList } from './NodeOrderedList.js';
33
35
  import { NodeBulletList } from './NodeBulletList.js';
@@ -46,6 +48,7 @@ import { NodeTaskList } from './NodeTaskList.js';
46
48
  import { NodeTaskItem } from './NodeTaskItem.js';
47
49
  import { NodeInlineShortCode } from './NodeInlineShortCode.js';
48
50
  import { NodeIframe } from './NodeIframe.js';
51
+ import { NodeCommentAnchor } from './NodeCommentAnchor.js';
49
52
 
50
53
  export class BasicEditorKit implements EditorKit {
51
54
  getExtensions(): AnyExtensionOrReq[] {
@@ -58,10 +61,12 @@ export class BasicEditorKit implements EditorKit {
58
61
  new ExtensionRemoteSelection(),
59
62
  new ExtensionSelection(),
60
63
  new ExtensionTextAlign(),
64
+ new ExtensionPairing(),
61
65
  new NodeDocument(),
62
66
  new NodeText(),
63
67
  new NodeParagraph(),
64
68
  new NodeHardBreak(),
69
+ new NodeSoftBreak(),
65
70
  new NodeCodeBlock(),
66
71
  new NodeBookmark(),
67
72
  new NodeHorizontalRule(),
@@ -84,6 +89,7 @@ export class BasicEditorKit implements EditorKit {
84
89
  new NodeMath(),
85
90
  new NodeInlineShortCode(),
86
91
  new NodeIframe(),
92
+ new NodeCommentAnchor(),
87
93
  new MarkLink(),
88
94
  new MarkItalic(),
89
95
  new MarkStrong(),
@@ -6,15 +6,16 @@ import {
6
6
  RawTextMapEntry,
7
7
  RawTextResult,
8
8
  } from '@kerebron/editor';
9
- import { NodeDocumentCode } from './NodeDocumentCode.js';
10
9
 
11
10
  import { ExtensionSelection } from './ExtensionSelection.js';
12
11
  import { ExtensionBaseKeymap } from './ExtensionBaseKeymap.js';
13
12
  import { ExtensionDropcursor } from './ExtensionDropcursor.js';
14
13
  import { ExtensionGapcursor } from './ExtensionGapcursor.js';
15
14
  import { ExtensionHtml } from './ExtensionHtml.js';
16
- import { NodeText } from './NodeText.js';
17
15
  import { ExtensionRemoteSelection } from './remote-selection/ExtensionRemoteSelection.js';
16
+ import { NodeText } from './NodeText.js';
17
+ import { NodeDocumentCode } from './NodeDocumentCode.js';
18
+ import { NodeCodeBlock } from './NodeCodeBlock.js';
18
19
 
19
20
  export class ExtensionBasicCodeEditor extends Extension {
20
21
  name = 'basic-code-editor';
@@ -31,6 +32,7 @@ export class ExtensionBasicCodeEditor extends Extension {
31
32
  new ExtensionRemoteSelection(),
32
33
  new ExtensionSelection(),
33
34
  new NodeDocumentCode({ lang }),
35
+ new NodeCodeBlock(),
34
36
  new NodeText(),
35
37
  ];
36
38
  }
@@ -248,7 +248,7 @@ function createMediaUploadPlugin(options: MediaUploadOptions = {}): Plugin {
248
248
  export class ExtensionMediaUpload extends Extension {
249
249
  name = 'mediaUpload';
250
250
 
251
- constructor(protected override config: Partial<MediaUploadOptions> = {}) {
251
+ constructor(public override config: Partial<MediaUploadOptions> = {}) {
252
252
  super(config);
253
253
  }
254
254
 
package/src/MarkLink.ts CHANGED
@@ -9,7 +9,9 @@ export class MarkLink extends Mark {
9
9
  return {
10
10
  attrs: {
11
11
  href: {},
12
- title: { default: null },
12
+ title: { default: undefined },
13
+ origUrl: { default: undefined },
14
+ mdTemplate: { default: undefined },
13
15
  },
14
16
  inclusive: false,
15
17
  parseDOM: [
@@ -15,13 +15,6 @@ export class MarkUnderline extends Mark {
15
15
  {
16
16
  tag: 'u',
17
17
  },
18
- {
19
- style: 'text-decoration',
20
- consuming: false,
21
- getAttrs: (
22
- style,
23
- ) => ((style as string).includes('underline') ? {} : false),
24
- },
25
18
  ],
26
19
  toDOM() {
27
20
  return ['u', 0];
@@ -1,9 +1,9 @@
1
1
  import { NodeSpec } from 'prosemirror-model';
2
2
 
3
- import { Node } from '@kerebron/editor';
3
+ import { NESTING_SELF_CLOSING, Node } from '@kerebron/editor';
4
4
 
5
5
  export class NodeBookmark extends Node {
6
- override name = 'bookmark-node';
6
+ override name = 'node_bookmark';
7
7
  requires = ['doc'];
8
8
 
9
9
  override getNodeSpec(): NodeSpec {
@@ -11,8 +11,12 @@ export class NodeBookmark extends Node {
11
11
  inline: true,
12
12
  group: 'inline',
13
13
  selectable: false,
14
+ atom: true,
14
15
  attrs: {
15
16
  id: {},
17
+ nesting: {
18
+ default: NESTING_SELF_CLOSING,
19
+ },
16
20
  },
17
21
  parseDOM: [],
18
22
  toDOM(mark) {
@@ -14,6 +14,15 @@ export class NodeBulletList extends Node {
14
14
  override name = 'bullet_list';
15
15
  requires = ['doc'];
16
16
 
17
+ override attributes = {
18
+ toc: {
19
+ default: undefined,
20
+ },
21
+ odtMarginLeft: {
22
+ default: undefined,
23
+ },
24
+ };
25
+
17
26
  override getNodeSpec(): NodeSpec {
18
27
  return {
19
28
  content: 'list_item+',
@@ -17,6 +17,7 @@ export class NodeCodeBlock extends Node {
17
17
  group: 'block',
18
18
  code: true,
19
19
  defining: true,
20
+ attrs: { lang: { default: undefined } },
20
21
  parseDOM: [
21
22
  {
22
23
  tag: 'pre',
@@ -42,7 +43,6 @@ export class NodeCodeBlock extends Node {
42
43
  },
43
44
  },
44
45
  ],
45
- attrs: { lang: { default: null } },
46
46
  toDOM(node) {
47
47
  const { lang } = node.attrs;
48
48
  return ['pre', { lang }, ['code', 0]];
@@ -0,0 +1,170 @@
1
+ import type { NodeSpec, NodeType } from 'prosemirror-model';
2
+ import { Node as PmNode } from 'prosemirror-model';
3
+ import { Plugin } from 'prosemirror-state';
4
+
5
+ import {
6
+ type CoreEditor,
7
+ NESTING_CLOSING,
8
+ NESTING_OPENING,
9
+ Node,
10
+ } from '@kerebron/editor';
11
+ import { type CommandFactories } from '@kerebron/editor/commands';
12
+ import { Decoration, DecorationSet } from 'prosemirror-view';
13
+ import { CommandFactory } from '@kerebron/editor/commands';
14
+
15
+ function collectCommentAnchors(doc: PmNode) {
16
+ const starts = new Map();
17
+ const ends = new Map();
18
+
19
+ doc.descendants((node, pos) => {
20
+ if (node.type.name === 'comment') {
21
+ if (node.attrs.nesting === NESTING_OPENING) {
22
+ starts.set(node.attrs.id, pos);
23
+ }
24
+ if (node.attrs.nesting === NESTING_CLOSING) {
25
+ ends.set(node.attrs.id, pos);
26
+ }
27
+ }
28
+ });
29
+
30
+ return { starts, ends };
31
+ }
32
+
33
+ function buildCommentRanges(doc: PmNode) {
34
+ const { starts, ends } = collectCommentAnchors(doc);
35
+ const ranges = [];
36
+
37
+ for (const [id, from] of starts) {
38
+ const to = ends.get(id);
39
+ if (!to || to <= from) continue;
40
+
41
+ ranges.push({
42
+ id,
43
+ from: from + 1, // skip start atom
44
+ to, // end atom position
45
+ });
46
+ }
47
+
48
+ return ranges;
49
+ }
50
+
51
+ function buildDecorations(doc: PmNode) {
52
+ const ranges = buildCommentRanges(doc);
53
+
54
+ return DecorationSet.create(
55
+ doc,
56
+ ranges.map(({ id, from, to }) =>
57
+ Decoration.inline(from, to, {
58
+ class: 'comment-highlight',
59
+ 'data-comment-id': id,
60
+ })
61
+ ),
62
+ );
63
+ }
64
+
65
+ function commentsPlugin(): Plugin {
66
+ return new Plugin({
67
+ state: {
68
+ init(_, { doc }) {
69
+ return buildDecorations(doc);
70
+ },
71
+ apply(tr, oldDecos, oldState, newState) {
72
+ if (!tr.docChanged) return oldDecos;
73
+ return buildDecorations(newState.doc);
74
+ },
75
+ },
76
+ props: {
77
+ decorations(state) {
78
+ return this.getState(state);
79
+ },
80
+ },
81
+ });
82
+ }
83
+
84
+ const addCommentBoundary: CommandFactory = (id) => {
85
+ return (state, dispatch) => {
86
+ const { schema, selection } = state;
87
+ const { from, to } = selection;
88
+
89
+ const commentType = schema.nodes.comment;
90
+ if (!commentType) return false;
91
+
92
+ const startNode = commentType.create({ id, nesting: NESTING_OPENING });
93
+ const endNode = commentType.create({ id, nesting: NESTING_CLOSING });
94
+
95
+ const tr = state.tr;
96
+
97
+ tr.insert(to, endNode);
98
+ tr.insert(from, startNode);
99
+
100
+ // Optional: restore selection
101
+ // tr = tr.setSelection(
102
+ // TextSelection.create(tr.doc, from, to + startNode.nodeSize)
103
+ // );
104
+
105
+ if (dispatch) dispatch(tr);
106
+ return true;
107
+ };
108
+ };
109
+
110
+ export class NodeCommentAnchor extends Node {
111
+ override name = 'comment';
112
+ requires = ['doc'];
113
+
114
+ options = {
115
+ keepMarks: true,
116
+ };
117
+
118
+ override getNodeSpec(): NodeSpec {
119
+ return {
120
+ inline: true,
121
+ group: 'inline',
122
+ selectable: false,
123
+ atom: true,
124
+ attrs: {
125
+ id: {},
126
+ active: {
127
+ default: false,
128
+ },
129
+ nesting: {
130
+ default: NESTING_OPENING,
131
+ },
132
+ },
133
+ parseDOM: [
134
+ {
135
+ tag: 'span[data-comment-id]',
136
+ getAttrs(dom: HTMLElement) {
137
+ return {
138
+ id: dom.getAttribute('data-comment-id'),
139
+ nesting: parseInt(dom.getAttribute('data-nesting') || '1'),
140
+ active: dom.hasAttribute('data-active'),
141
+ };
142
+ },
143
+ },
144
+ ],
145
+ toDOM: (
146
+ node,
147
+ ) => ['span', {
148
+ 'data-comment-id': node.attrs.id,
149
+ 'data-active': node.attrs.active ? 'true' : undefined,
150
+ 'data-nesting': node.attrs.nesting,
151
+ }],
152
+ };
153
+ }
154
+
155
+ override getCommandFactories(
156
+ editor: CoreEditor,
157
+ type: NodeType,
158
+ ): Partial<CommandFactories> {
159
+ return {
160
+ 'addComment': (id, active) => addCommentBoundary(id, active),
161
+ };
162
+ }
163
+
164
+ override getProseMirrorPlugins(): Plugin[] {
165
+ return [
166
+ commentsPlugin(),
167
+ // dropCursor(this.options),
168
+ ];
169
+ }
170
+ }
@@ -11,7 +11,8 @@ export class NodeHorizontalRule extends Node {
11
11
 
12
12
  override getNodeSpec(): NodeSpec {
13
13
  return {
14
- group: 'block',
14
+ inline: true,
15
+ group: 'inline',
15
16
  parseDOM: [{ tag: 'hr' }],
16
17
  toDOM() {
17
18
  return ['hr'];
package/src/NodeImage.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { type NodeSpec } from 'prosemirror-model';
2
- import type { Node as PmNode } from 'prosemirror-model';
1
+ import type { Node as PmNode, NodeSpec } from 'prosemirror-model';
2
+ import { Selection } from 'prosemirror-state';
3
3
  import type { EditorView, NodeViewConstructor } from 'prosemirror-view';
4
+
4
5
  import { Node } from '@kerebron/editor';
5
6
  import type { CoreEditor } from '@kerebron/editor';
6
7
 
@@ -17,6 +18,8 @@ export class NodeImage extends Node {
17
18
  title: { default: null },
18
19
  width: { default: null },
19
20
  height: { default: null },
21
+ origUrl: { default: undefined },
22
+ mdTemplate: { default: undefined },
20
23
  },
21
24
  group: 'inline',
22
25
  draggable: true,
@@ -194,7 +197,7 @@ export class NodeImage extends Node {
194
197
  const pos = getPos();
195
198
  if (typeof pos === 'number') {
196
199
  const $pos = view.state.doc.resolve(pos);
197
- const selection = view.state.selection.constructor.near($pos);
200
+ const selection = Selection.near($pos);
198
201
  view.dispatch(view.state.tr.setSelection(selection));
199
202
  }
200
203
  });
@@ -1,14 +1,17 @@
1
1
  import { Node as PmNode, NodeSpec, NodeType, Schema } from 'prosemirror-model';
2
2
  import { EditorState, Transaction } from 'prosemirror-state';
3
3
 
4
- import { Node } from '@kerebron/editor';
4
+ import {
5
+ CommandFactories,
6
+ CoreEditor,
7
+ NESTING_SELF_CLOSING,
8
+ Node,
9
+ } from '@kerebron/editor';
10
+ import { Command } from '@kerebron/editor/commands';
5
11
  import {
6
12
  type InputRule,
7
13
  replaceInlineNode,
8
14
  } from '@kerebron/editor/plugins/input-rules';
9
- import { CoreEditor } from '@kerebron/editor';
10
- import { CommandFactories } from '@kerebron/editor';
11
- import { Command } from '@kerebron/editor/commands';
12
15
 
13
16
  export function fixCharacters(text: string) {
14
17
  return text
@@ -37,8 +40,6 @@ function replaceAllNodesOfType(
37
40
  }
38
41
  });
39
42
 
40
- console.log('replaceAllNodesOfType', replacements.length);
41
-
42
43
  for (let i = replacements.length - 1; i >= 0; i--) {
43
44
  const { node, pos } = replacements[i];
44
45
  const newNode = factory(node, tr.doc.type.schema);
@@ -57,12 +58,21 @@ export class NodeInlineShortCode extends Node {
57
58
  inline: true,
58
59
  group: 'inline',
59
60
  selectable: true,
61
+ atom: true,
60
62
  attrs: {
63
+ id: {
64
+ default: undefined,
65
+ },
61
66
  content: {
62
67
  default: '',
63
68
  },
69
+ nesting: {
70
+ default: NESTING_SELF_CLOSING,
71
+ },
72
+ error: {
73
+ default: '',
74
+ },
64
75
  },
65
- atom: true,
66
76
  parseDOM: [{
67
77
  tag: 'span.kb-shortcode-inline',
68
78
  getAttrs: (dom) => ({ content: dom.textContent || null }),
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type Attrs,
3
3
  Fragment,
4
+ Node as PMNode,
4
5
  NodeRange,
5
6
  NodeSpec,
6
7
  type NodeType,
@@ -12,12 +13,6 @@ import type {
12
13
  Transaction,
13
14
  } from 'prosemirror-state';
14
15
  import { Selection } from 'prosemirror-state';
15
- import { type CoreEditor, Node } from '@kerebron/editor';
16
- import {
17
- type Command,
18
- type CommandFactories,
19
- type CommandShortcuts,
20
- } from '@kerebron/editor/commands';
21
16
  import {
22
17
  canJoin,
23
18
  canSplit,
@@ -25,6 +20,17 @@ import {
25
20
  ReplaceAroundStep,
26
21
  } from 'prosemirror-transform';
27
22
 
23
+ import { type CoreEditor, Node } from '@kerebron/editor';
24
+ import type {
25
+ Command,
26
+ CommandFactories,
27
+ CommandShortcuts,
28
+ } from '@kerebron/editor/commands';
29
+ import {
30
+ getHtmlAttributes,
31
+ setHtmlAttributes,
32
+ } from '@kerebron/editor/utilities';
33
+
28
34
  /// Build a command that splits a non-empty textblock at the top level
29
35
  /// of a list item by also splitting that list item.
30
36
  export function splitListItem(itemType: NodeType, itemAttrs?: Attrs): Command {
@@ -288,13 +294,33 @@ export class NodeListItem extends Node {
288
294
  override name = 'list_item';
289
295
  requires = ['doc'];
290
296
 
297
+ override attributes = {
298
+ value: {
299
+ default: undefined,
300
+ },
301
+ type: {
302
+ default: undefined,
303
+ fromDom(element: HTMLElement) {
304
+ return element.hasAttribute('type')
305
+ ? element.getAttribute('type')
306
+ : undefined;
307
+ },
308
+ toDom(node: PMNode) {
309
+ return node.attrs.type;
310
+ },
311
+ },
312
+ };
313
+
291
314
  override getNodeSpec(): NodeSpec {
292
315
  return {
293
316
  content: 'paragraph block*',
294
- parseDOM: [{ tag: 'li' }],
317
+ parseDOM: [{
318
+ tag: 'li',
319
+ getAttrs: (element) => setHtmlAttributes(this, element),
320
+ }],
295
321
  defining: true,
296
- toDOM() {
297
- return ['li', 0];
322
+ toDOM: (node) => {
323
+ return ['li', getHtmlAttributes(this, node), 0];
298
324
  },
299
325
  };
300
326
  }
@@ -29,13 +29,22 @@ export class NodeOrderedList extends Node {
29
29
  },
30
30
  },
31
31
  start: {
32
- default: 1,
32
+ default: undefined,
33
33
  fromDom(element: HTMLElement) {
34
34
  return element.hasAttribute('start')
35
35
  ? +element.getAttribute('start')!
36
- : 1;
36
+ : undefined;
37
37
  },
38
38
  },
39
+ id: {
40
+ default: undefined,
41
+ },
42
+ continue: {
43
+ default: undefined,
44
+ },
45
+ odtMarginLeft: {
46
+ default: undefined,
47
+ },
39
48
  };
40
49
 
41
50
  override getNodeSpec(): NodeSpec {
@@ -5,7 +5,7 @@ import {
5
5
  type CommandShortcuts,
6
6
  } from '@kerebron/editor/commands';
7
7
 
8
- type TextAlign = 'left' | 'center' | 'right' | 'justify';
8
+ export type TextAlign = 'left' | 'center' | 'right' | 'justify' | undefined;
9
9
 
10
10
  export class NodeParagraph extends Node {
11
11
  override name = 'paragraph';
@@ -16,7 +16,7 @@ export class NodeParagraph extends Node {
16
16
  content: 'inline*',
17
17
  group: 'block',
18
18
  attrs: {
19
- textAlign: { default: null },
19
+ textAlign: { default: undefined },
20
20
  },
21
21
  parseDOM: [
22
22
  {
@@ -29,13 +29,13 @@ export class NodeParagraph extends Node {
29
29
  ) {
30
30
  return { textAlign: style };
31
31
  }
32
- return { textAlign: null };
32
+ return false;
33
33
  },
34
34
  },
35
35
  ],
36
36
  toDOM(node) {
37
- const align = node.attrs.textAlign as TextAlign | null;
38
- if (align && align !== 'left') {
37
+ const align = node.attrs.textAlign || 'left';
38
+ if (['center', 'right', 'justify'].includes(align)) {
39
39
  return ['p', { style: `text-align: ${align}` }, 0];
40
40
  }
41
41
  return ['p', 0];
@@ -0,0 +1,19 @@
1
+ import type { NodeSpec } from 'prosemirror-model';
2
+ import { Node } from '@kerebron/editor';
3
+
4
+ export class NodeSoftBreak extends Node {
5
+ override name = 'softbreak';
6
+ requires = ['doc'];
7
+
8
+ override getNodeSpec(): NodeSpec {
9
+ return {
10
+ inline: true,
11
+ group: 'inline',
12
+ selectable: false,
13
+ parseDOM: [{ tag: 'wbr' }],
14
+ toDOM() {
15
+ return ['wbr'];
16
+ },
17
+ };
18
+ }
19
+ }
@@ -0,0 +1,14 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+
3
+ import { Extension } from '@kerebron/editor';
4
+ import { createPairingPlugin } from './PairNodesPlugin.js';
5
+
6
+ export class ExtensionPairing extends Extension {
7
+ name = 'pairing-nodes';
8
+
9
+ override getProseMirrorPlugins(): Plugin[] {
10
+ return [
11
+ createPairingPlugin(['shortcode_inline']),
12
+ ];
13
+ }
14
+ }
@@ -0,0 +1,56 @@
1
+ import { Plugin, Transaction } from 'prosemirror-state';
2
+ import { ReplaceAroundStep, ReplaceStep } from 'prosemirror-transform';
3
+ import { buildPairingTransaction } from './pairNodes.js';
4
+
5
+ export const createPairingPlugin = (types: string[]) => {
6
+ return new Plugin({
7
+ appendTransaction(
8
+ transactions: readonly Transaction[],
9
+ oldState,
10
+ newState,
11
+ ) {
12
+ const typesToRebuild: Set<string> = new Set();
13
+
14
+ for (const tr of transactions) {
15
+ if (!tr.docChanged) continue;
16
+
17
+ for (const step of tr.steps) {
18
+ if (
19
+ !(step instanceof ReplaceStep || step instanceof ReplaceAroundStep)
20
+ ) {
21
+ continue;
22
+ }
23
+
24
+ if (step.slice) {
25
+ step.slice.content.descendants((node) => {
26
+ if (types.includes(node.type.name)) {
27
+ typesToRebuild.add(node.type.name);
28
+ }
29
+ });
30
+ }
31
+
32
+ if (step.from != null && step.to != null && step.from !== step.to) {
33
+ oldState.doc.nodesBetween(step.from, step.to, (node) => {
34
+ if (types.includes(node.type.name)) {
35
+ typesToRebuild.add(node.type.name);
36
+ }
37
+ });
38
+ }
39
+
40
+ if (typesToRebuild.size === types.length) break;
41
+ }
42
+
43
+ if (typesToRebuild.size === types.length) break;
44
+ }
45
+
46
+ if (typesToRebuild.size === 0) return null;
47
+
48
+ const tr = newState.tr;
49
+ for (const type of typesToRebuild) {
50
+ buildPairingTransaction(newState, type, tr);
51
+ }
52
+
53
+ return tr;
54
+ },
55
+ });
56
+ };