@kerebron/extension-odt 0.5.2 → 0.5.3

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 (54) hide show
  1. package/README.md +2 -2
  2. package/esm/ExtensionOdt.d.ts.map +1 -1
  3. package/esm/ExtensionOdt.js +16 -70
  4. package/esm/ExtensionOdt.js.map +1 -1
  5. package/esm/OdtParser.d.ts +5 -5
  6. package/esm/OdtParser.d.ts.map +1 -1
  7. package/esm/OdtParser.js +20 -13
  8. package/esm/OdtParser.js.map +1 -1
  9. package/esm/lists.d.ts +9 -9
  10. package/esm/lists.d.ts.map +1 -1
  11. package/esm/lists.js +21 -7
  12. package/esm/lists.js.map +1 -1
  13. package/esm/node_handlers/basic_node_handlers.d.ts.map +1 -1
  14. package/esm/node_handlers/basic_node_handlers.js +1 -3
  15. package/esm/node_handlers/basic_node_handlers.js.map +1 -1
  16. package/esm/node_handlers/list_node_handlers.d.ts.map +1 -1
  17. package/esm/node_handlers/list_node_handlers.js +50 -64
  18. package/esm/node_handlers/list_node_handlers.js.map +1 -1
  19. package/esm/postprocess/convertCodeParagraphsToCodeBlocks.d.ts.map +1 -1
  20. package/esm/postprocess/convertCodeParagraphsToCodeBlocks.js +30 -67
  21. package/esm/postprocess/convertCodeParagraphsToCodeBlocks.js.map +1 -1
  22. package/esm/postprocess/convertMathMl.d.ts.map +1 -1
  23. package/esm/postprocess/convertMathMl.js.map +1 -1
  24. package/esm/postprocess/fixContinuedLists.d.ts.map +1 -1
  25. package/esm/postprocess/fixContinuedLists.js +80 -67
  26. package/esm/postprocess/fixContinuedLists.js.map +1 -1
  27. package/esm/postprocess/mergeCodeBlocks.d.ts +3 -0
  28. package/esm/postprocess/mergeCodeBlocks.d.ts.map +1 -0
  29. package/esm/postprocess/mergeCodeBlocks.js +72 -0
  30. package/esm/postprocess/mergeCodeBlocks.js.map +1 -0
  31. package/esm/postprocess/postProcess.d.ts.map +1 -1
  32. package/esm/postprocess/postProcess.js +3 -1
  33. package/esm/postprocess/postProcess.js.map +1 -1
  34. package/esm/postprocess/removeUnusedBookmarks.d.ts +1 -1
  35. package/esm/postprocess/removeUnusedBookmarks.d.ts.map +1 -1
  36. package/esm/postprocess/removeUnusedBookmarks.js +17 -19
  37. package/esm/postprocess/removeUnusedBookmarks.js.map +1 -1
  38. package/esm/postprocess/urlRewrite.d.ts +4 -0
  39. package/esm/postprocess/urlRewrite.d.ts.map +1 -0
  40. package/esm/postprocess/urlRewrite.js +60 -0
  41. package/esm/postprocess/urlRewrite.js.map +1 -0
  42. package/package.json +3 -3
  43. package/src/ExtensionOdt.ts +17 -102
  44. package/src/OdtParser.ts +15 -8
  45. package/src/lists.ts +24 -10
  46. package/src/node_handlers/basic_node_handlers.ts +2 -4
  47. package/src/node_handlers/list_node_handlers.ts +64 -87
  48. package/src/postprocess/convertCodeParagraphsToCodeBlocks.ts +33 -88
  49. package/src/postprocess/convertMathMl.ts +0 -1
  50. package/src/postprocess/fixContinuedLists.ts +95 -78
  51. package/src/postprocess/mergeCodeBlocks.ts +98 -0
  52. package/src/postprocess/postProcess.ts +3 -1
  53. package/src/postprocess/removeUnusedBookmarks.ts +34 -21
  54. package/src/postprocess/urlRewrite.ts +95 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerebron/extension-odt",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "license": "MIT",
5
5
  "module": "./esm/ExtensionOdt.js",
6
6
  "exports": {
@@ -14,8 +14,8 @@
14
14
  "src"
15
15
  ],
16
16
  "dependencies": {
17
- "@kerebron/editor": "0.5.2",
18
- "@kerebron/odt-wasm": "0.5.2",
17
+ "@kerebron/editor": "0.5.3",
18
+ "@kerebron/odt-wasm": "0.5.3",
19
19
  "prosemirror-model": "1.25.3",
20
20
  "prosemirror-state": "1.4.3"
21
21
  },
@@ -16,7 +16,8 @@ import {
16
16
  import { OdtParser, OdtParserConfig } from './OdtParser.js';
17
17
  import { getDefaultsPostProcessFilters } from './postprocess/postProcess.js';
18
18
  import { Command } from '@kerebron/editor/commands';
19
- import { InputRulesPlugin } from '@kerebron/editor/plugins/input-rules';
19
+ import { EditorState, Transaction } from 'prosemirror-state';
20
+ import { urlRewrite } from './postprocess/urlRewrite.js';
20
21
 
21
22
  export interface OdtConfig extends OdtParserConfig {
22
23
  debug?: boolean;
@@ -37,8 +38,6 @@ export class ExtensionOdt extends Extension {
37
38
  editor: CoreEditor,
38
39
  schema: Schema,
39
40
  ): Record<string, Converter> {
40
- const config = this.config;
41
-
42
41
  const odtConverter = {
43
42
  fromDoc: async (document: Node): Promise<Uint8Array> => {
44
43
  throw new Error('Not implemented');
@@ -53,118 +52,34 @@ export class ExtensionOdt extends Extension {
53
52
  this.config.postProcessCommands || [],
54
53
  );
55
54
 
56
- const plugin = editor.state.plugins.find((plugin) =>
57
- plugin instanceof InputRulesPlugin
58
- );
59
- if (plugin) {
60
- // plugin.props.
61
- }
55
+ let state = EditorState.create({ doc });
56
+ const dispatch = (tr: Transaction) => {
57
+ state = state.apply(tr);
58
+ };
62
59
 
63
- const subEditor = editor.clone();
64
- subEditor.setDocument(doc.toJSON());
65
-
66
- let modified = false;
67
60
  if (this.urlFromRewriter) {
68
- const imageNodes: Array<{ node: Node; pos: number }> = [];
69
- subEditor.getDocument().descendants((node, pos) => {
70
- if (node.type.name === 'image') {
71
- imageNodes.push({ node, pos });
72
- }
73
- });
74
-
75
- const linkNodes: Array<{ node: Node; pos: number }> = [];
76
- subEditor.getDocument().descendants((node, pos) => {
77
- if (node.marks.find((mark) => mark.type.name === 'link')) {
78
- linkNodes.push({ node, pos });
79
- }
80
- });
81
-
82
- const tr = subEditor.state.tr;
83
-
84
- for (const { node, pos } of linkNodes) {
85
- const linkMark = node.marks.find((mark) =>
86
- mark.type.name === 'link'
87
- );
88
- if (!linkMark) {
89
- continue;
90
- }
91
- let href = linkMark.attrs.href || '';
92
- href = await this.urlFromRewriter(href, {
93
- type: 'A',
94
- dest: 'kerebron',
95
- });
96
- if (href !== linkMark.attrs.href) {
97
- const newMarks = node.marks.map((mark) => {
98
- if (mark.type.name === 'link') {
99
- const markType = this.editor.schema.marks['link'];
100
- return markType.create({ ...mark.attrs, href });
101
- }
102
- return mark;
103
- });
104
-
105
- const nodeType = this.editor.schema.nodes[node.type.name];
106
- let replaceNode;
107
- if (nodeType.isText) {
108
- replaceNode = this.editor.schema.text(
109
- node.text || '',
110
- newMarks,
111
- );
112
- } else {
113
- replaceNode = nodeType.create(
114
- node.attrs,
115
- node.content,
116
- newMarks,
117
- );
118
- }
119
- tr.replaceWith(
120
- tr.mapping.map(pos),
121
- tr.mapping.map(pos + node.nodeSize),
122
- replaceNode,
123
- );
124
- }
125
- }
126
-
127
- for (const { node, pos } of imageNodes) {
128
- let src = node.attrs.src || '';
129
-
130
- src = await this.urlFromRewriter(src, {
131
- type: 'IMG',
132
- dest: 'kerebron',
133
- filesMap,
134
- });
135
-
136
- if (src !== node.attrs.src) {
137
- const nodeType = this.editor.schema.nodes[node.type.name];
138
- const replaceNode = nodeType.create(
139
- { ...node.attrs, src },
140
- node.content,
141
- node.marks,
142
- );
143
- tr.replaceWith(
144
- tr.mapping.map(pos),
145
- tr.mapping.map(pos + node.nodeSize),
146
- replaceNode,
147
- );
148
- }
149
- }
150
- subEditor.dispatchTransaction(tr), modified = true;
61
+ await urlRewrite(this.urlFromRewriter, filesMap, state, dispatch);
151
62
  }
152
63
 
153
64
  if (filterCommands.length > 0) {
154
65
  for (const filter of filterCommands) {
155
66
  filter(
156
- subEditor.state,
157
- (tr) => subEditor.dispatchTransaction(tr),
67
+ state,
68
+ (tr) => dispatch(tr),
158
69
  );
159
70
  }
160
- modified = true;
161
71
  }
162
72
 
163
- if (modified) {
164
- return subEditor.getDocument();
73
+ if (this.config.debug) {
74
+ const event = new CustomEvent('odt:pmdoc:filtered', {
75
+ detail: {
76
+ doc: state.doc,
77
+ },
78
+ });
79
+ this.editor.dispatchEvent(event);
165
80
  }
166
81
 
167
- return doc;
82
+ return state.doc;
168
83
  },
169
84
  odtToJson: (buffer: Uint8Array) => {
170
85
  const files = unzip(buffer);
package/src/OdtParser.ts CHANGED
@@ -6,7 +6,6 @@ import {
6
6
  import { getListNodesHandlers } from './node_handlers/list_node_handlers.js';
7
7
  import { getTableNodesHandlers } from './node_handlers/table_node_handlers.js';
8
8
  import { ListTracker } from './lists.js';
9
- import { type UrlRewriter } from '@kerebron/editor';
10
9
 
11
10
  const COURIER_FONTS = ['Courier New', 'Courier', 'Roboto Mono'];
12
11
 
@@ -16,24 +15,24 @@ export interface OdtElement {
16
15
 
17
16
  export type NodeHandler = (ctx: OdtStashContext, value: any) => void;
18
17
 
19
- interface ListStyle {
18
+ export interface ListStyle {
20
19
  '@name': string;
21
20
  }
22
21
 
23
- interface Style {
22
+ export interface Style {
24
23
  '@name': string;
25
24
  '@parent-style-name'?: string;
26
25
  styles: string[];
27
26
  }
28
27
 
29
- interface StylesTree {
28
+ export interface StylesTree {
30
29
  styles: {
31
30
  'list-style': Array<ListStyle>;
32
31
  'style': Array<Style>;
33
32
  };
34
33
  }
35
34
 
36
- interface AutomaticStyles {
35
+ export interface AutomaticStyles {
37
36
  'style': Array<Style>;
38
37
  }
39
38
 
@@ -42,7 +41,7 @@ export function resolveStyle(
42
41
  automaticStyles: AutomaticStyles,
43
42
  name: string,
44
43
  ): Style {
45
- let style: Style;
44
+ let style: Style | undefined;
46
45
 
47
46
  if (!style) {
48
47
  style = stylesTree.styles['list-style'].find((item) =>
@@ -59,6 +58,7 @@ export function resolveStyle(
59
58
  if (!style) {
60
59
  style = {
61
60
  '@name': name,
61
+ styles: [],
62
62
  };
63
63
  }
64
64
 
@@ -198,6 +198,10 @@ export class OdtStashContext {
198
198
  this.current.content = [];
199
199
  }
200
200
 
201
+ public dropNode() {
202
+ this.unstash();
203
+ }
204
+
201
205
  public closeNode(type: string, attrs = {}, marks = Mark.none) {
202
206
  const node = this.createNode(type, attrs, marks);
203
207
  this.unstash();
@@ -292,7 +296,7 @@ export class OdtParser {
292
296
  ...getListNodesHandlers(),
293
297
  ...getTableNodesHandlers(),
294
298
 
295
- 'change-start': {
299
+ 'change-start': () => {
296
300
  // custom(state) {
297
301
  // state.textMarks.add({
298
302
  // markName: 'change',
@@ -300,13 +304,16 @@ export class OdtParser {
300
304
  // });
301
305
  // },
302
306
  },
303
- 'change-end': {
307
+ 'change-end': () => {
304
308
  // custom(state) {
305
309
  // state.textMarks.forEach((x) =>
306
310
  // x.markName === 'change' ? state.textMarks.delete(x) : x
307
311
  // );
308
312
  // },
309
313
  },
314
+ 'g': () => { // Test is: embedded-diagram-example.odt
315
+ // DrawG draw:g
316
+ },
310
317
  'frame': (ctx: OdtStashContext, odtElement: any) => {
311
318
  if (odtElement.object && odtElement.object['@href']) {
312
319
  const fullPath = odtElement.object['@href'].replace(/^\.\//, '') +
package/src/lists.ts CHANGED
@@ -1,10 +1,8 @@
1
- import { OdtElement } from './OdtParser.js';
2
-
3
1
  export class ListNumbering {
4
2
  levels: { [level: number]: number } = {};
5
- levelNodes: { [level: number]: Node } = {};
3
+ forceStart: { [level: number]: boolean } = {};
6
4
 
7
- constructor() {
5
+ constructor(public readonly id: string) {
8
6
  for (let i = 0; i < 20; i++) {
9
7
  this.levels[i] = 1;
10
8
  }
@@ -16,20 +14,36 @@ export class ListNumbering {
16
14
  }
17
15
  }
18
16
 
19
- setLevelNode(level: number, node: Node) {
20
- this.levelNodes[level] = node;
17
+ clone(id: string): ListNumbering {
18
+ const retVal = new ListNumbering(id);
19
+
20
+ retVal.levels = structuredClone(retVal.levels);
21
+ retVal.forceStart = structuredClone(retVal.forceStart);
22
+
23
+ return retVal;
21
24
  }
22
25
  }
23
26
 
24
27
  export interface List {
25
28
  level: number;
26
- odtElement: OdtElement;
29
+ id?: string;
30
+ styleName: string;
27
31
  }
28
32
 
29
33
  export class ListTracker {
30
34
  listStack: List[] = [];
31
35
 
32
- listNumberings: Map<string, ListNumbering> = new Map<string, ListNumbering>();
33
- lastNumbering?: ListNumbering;
34
- preserveMinLevel = 999;
36
+ pushList(id?: string, styleName = ''): List {
37
+ const list: List = {
38
+ id,
39
+ styleName,
40
+ level: this.listStack.length + 1,
41
+ };
42
+ this.listStack.push(list);
43
+ return list;
44
+ }
45
+
46
+ getCurrentList(): List {
47
+ return this.listStack[this.listStack.length - 1];
48
+ }
35
49
  }
@@ -33,13 +33,11 @@ export function getInlineNodesHandlers(): Record<string, NodeHandler> {
33
33
  ctx.closeNode('br');
34
34
  },
35
35
  'soft-page-break': (ctx: OdtStashContext, odtElement: any) => {
36
- ctx.openNode();
37
- ctx.closeNode('br');
38
36
  },
39
37
 
40
38
  'bookmark': (ctx: OdtStashContext, element: any) => { // bookmark for parent para
41
39
  ctx.openNode();
42
- ctx.closeNode('bookmark-node', {
40
+ ctx.closeNode('node_bookmark', {
43
41
  id: element['@name'],
44
42
  });
45
43
  },
@@ -74,7 +72,7 @@ export function getBasicNodesHandlers(): Record<string, NodeHandler> {
74
72
  );
75
73
  },
76
74
  'p': (ctx: OdtStashContext, value: any) => {
77
- const attrs = {};
75
+ const attrs: Record<string, any> = {};
78
76
 
79
77
  const style = ctx.getElementStyle(value);
80
78
  const heading = style.styles.find((item) =>
@@ -1,4 +1,3 @@
1
- import { ListNumbering } from '../lists.js';
2
1
  import {
3
2
  iterateChildren,
4
3
  NodeHandler,
@@ -6,91 +5,73 @@ import {
6
5
  resolveStyle,
7
6
  } from '../OdtParser.js';
8
7
 
8
+ // https://docs.oasis-open.org/office/OpenDocument/v1.4/OpenDocument-v1.4-part3-schema.html#a_19_880_22__text_list_
9
+ // The text:style-name attribute specifies the name of a list style that is applied to a list.
10
+ // If this attribute is not included and therefore no list style is specified, one of the following actions is taken:
11
+ // •If a list is contained within another list, the list style defaults to the style of the surrounding list.
12
+ // •If there is no list style specified for the surrounding list, but the list's list items contain paragraphs that have paragraph styles attached specifying a list style, that list style is used.
13
+ // •An implementation-dependent default list style is used.
14
+ // To determine which formatting properties are applied to a list, the list level and list style name are taken into account.
15
+ function processListStyle(ctx: OdtStashContext, level: number) {
16
+ const listTracker = ctx.listTracker;
17
+ const attrs: Record<string, string> = {};
18
+
19
+ let style = {};
20
+ for (let i = listTracker.listStack.length - 1; i >= 0; i--) {
21
+ const list = listTracker.listStack[i];
22
+ if (!style['@style-name']) {
23
+ style = (list.styleName)
24
+ ? resolveStyle(
25
+ ctx.stylesTree,
26
+ ctx.automaticStyles,
27
+ list.styleName,
28
+ )
29
+ : {};
30
+ }
31
+ }
32
+
33
+ let nodeTypeName = 'bullet_list';
34
+ if (style) {
35
+ const numLevelStyle = style['list-level-style-number'].find(
36
+ (levelStyle) => parseInt(levelStyle['@level']) === level,
37
+ );
38
+ if (numLevelStyle) {
39
+ attrs['type'] = numLevelStyle['@num-format'] || '1';
40
+ if (numLevelStyle['@start-value']) {
41
+ attrs['start'] = numLevelStyle['@start-value'];
42
+ }
43
+ nodeTypeName = 'ordered_list';
44
+ }
45
+ }
46
+
47
+ return {
48
+ attrs,
49
+ nodeTypeName,
50
+ };
51
+ }
52
+
53
+ // https://docs.oasis-open.org/office/OpenDocument/v1.4/OpenDocument-v1.4-part3-schema.html#element-text_list
9
54
  export function getListNodesHandlers(): Record<string, NodeHandler> {
10
55
  return {
11
56
  'list': (ctx: OdtStashContext, odtElement: any) => {
12
57
  const listTracker = ctx.listTracker;
13
- const list = {
14
- level: listTracker.listStack.length + 1,
15
- odtElement,
16
- };
17
- listTracker.listStack.push(list);
58
+ listTracker.pushList(odtElement['@id'], odtElement['@style-name']);
18
59
 
19
- let style = {};
20
- let listId = null;
21
- for (let i = listTracker.listStack.length - 1; i >= 0; i--) {
22
- const element = listTracker.listStack[i].odtElement;
23
- if (!listId) {
24
- if (element['@id']) {
25
- listId = element['@id'];
26
- }
27
- }
28
- if (!style['@style-name']) {
29
- style = ('object' === typeof element && element['@style-name'])
30
- ? resolveStyle(
31
- ctx.stylesTree,
32
- ctx.automaticStyles,
33
- element['@style-name'],
34
- )
35
- : {};
36
- }
37
- }
38
-
39
- let nodeTypeName = 'bullet_list';
40
- const attrs = {};
41
- if (style) {
42
- const numLevelStyle = style['list-level-style-number'].find(
43
- (levelStyle) => parseInt(levelStyle['@level']) === list.level,
44
- );
45
- if (numLevelStyle) {
46
- attrs['type'] = numLevelStyle['@num-format'] || '1';
47
- nodeTypeName = 'ordered_list';
48
- }
49
- }
60
+ const { nodeTypeName, attrs } = processListStyle(
61
+ ctx,
62
+ listTracker.getCurrentList().level,
63
+ );
50
64
 
51
65
  ctx.current.meta['list_type'] = nodeTypeName;
52
66
 
53
- let listNumbering = null;
54
-
55
- if (listId && listTracker.listNumberings.has(listId)) {
56
- listNumbering = listTracker.listNumberings.get(listId);
67
+ if (odtElement['@id']) {
68
+ attrs['id'] = odtElement['@id'];
57
69
  }
58
-
59
- let isContinue = false;
60
- if (
61
- odtElement['@continue-list'] &&
62
- listTracker.listNumberings.has(odtElement['@continue-list'])
63
- ) {
64
- listNumbering = listTracker.listNumberings.get(
65
- odtElement['@continue-list'],
66
- );
67
- isContinue = true;
70
+ if (odtElement['@continue-list']) {
71
+ attrs['continue'] = odtElement['@continue-list'];
68
72
  }
69
73
  if (odtElement['@continue-numbering']) {
70
- listNumbering = listTracker.lastNumbering;
71
- isContinue = true;
72
- }
73
-
74
- if (!listNumbering) {
75
- listNumbering = new ListNumbering();
76
- }
77
-
78
- if (isContinue) {
79
- listTracker.preserveMinLevel = 999;
80
- }
81
-
82
- if (listId) {
83
- listTracker.listNumberings.set(listId, listNumbering);
84
- }
85
-
86
- listTracker.lastNumbering = listNumbering;
87
-
88
- if (listTracker.preserveMinLevel <= list.level) {
89
- listNumbering.clearAbove(list.level - 1);
90
- }
91
-
92
- if (nodeTypeName === 'ordered_list') {
93
- attrs['start'] = listNumbering.levels[list.level] || 1;
74
+ attrs['continue'] = '_last';
94
75
  }
95
76
 
96
77
  ctx.openNode();
@@ -99,18 +80,14 @@ export function getListNodesHandlers(): Record<string, NodeHandler> {
99
80
  'list-item': item,
100
81
  }));
101
82
 
102
- if (children) {
103
- iterateChildren(children, (child) => {
104
- ctx.handle(child.tag, child.value);
105
- });
106
-
107
- listNumbering.levels[list.level] += children.length;
108
- }
109
-
110
- ctx.closeNode(nodeTypeName, attrs);
83
+ iterateChildren(children, (child) => {
84
+ ctx.handle(child.tag, child.value);
85
+ });
111
86
 
112
- if (listTracker.preserveMinLevel >= list.level) {
113
- listTracker.preserveMinLevel = list.level;
87
+ if (ctx.current.content.length === 0) {
88
+ ctx.dropNode();
89
+ } else {
90
+ ctx.closeNode(nodeTypeName, attrs);
114
91
  }
115
92
 
116
93
  listTracker.listStack.pop();
@@ -122,7 +99,7 @@ export function getListNodesHandlers(): Record<string, NodeHandler> {
122
99
  (child) => ctx.handle(child.tag, child.value),
123
100
  );
124
101
 
125
- const attrs = {};
102
+ const attrs: Record<string, string> = {};
126
103
  attrs.markup = '* ';
127
104
 
128
105
  ctx.closeNode('list_item', attrs);
@@ -1,36 +1,8 @@
1
- import { Fragment, MarkType, Node } from 'prosemirror-model';
1
+ import { Node } from 'prosemirror-model';
2
2
  import { Transaction } from 'prosemirror-state';
3
3
 
4
4
  import type { Command } from '@kerebron/editor/commands';
5
5
 
6
- function onlyHasCodeMarkedText(
7
- paragraph: Node,
8
- codeMarkType: MarkType,
9
- ): boolean {
10
- if (paragraph.content.size === 0) {
11
- return paragraph.marks.some((mark) => mark.type.name === codeMarkType.name);
12
- }
13
-
14
- let allAreCodeMarked = true;
15
-
16
- paragraph.content.forEach((child) => {
17
- if (
18
- !child.isText ||
19
- !child.marks.some((mark) => mark.type.name === codeMarkType.name)
20
- ) {
21
- allAreCodeMarked = false;
22
- }
23
- });
24
-
25
- return allAreCodeMarked;
26
- }
27
-
28
- interface ParagraphsToMerge {
29
- startPos: number;
30
- endPos: number;
31
- innerText: string;
32
- }
33
-
34
6
  export const convertCodeParagraphsToCodeBlocks: Command = (
35
7
  state,
36
8
  dispatch,
@@ -39,80 +11,53 @@ export const convertCodeParagraphsToCodeBlocks: Command = (
39
11
  const schema = state.schema;
40
12
  let tr: Transaction = state.tr;
41
13
 
42
- let paragraphsToMerge: ParagraphsToMerge | null = null;
43
-
44
- function flushCodeBlock() {
45
- if (paragraphsToMerge === null) {
46
- return;
47
- }
48
-
49
- const textNode = schema.text(paragraphsToMerge.innerText);
50
- const codeBlock = schema.nodes.code_block.createAndFill(null, [textNode]);
51
-
52
- const startPos = tr.mapping.map(paragraphsToMerge.startPos);
53
- const endPos = tr.mapping.map(paragraphsToMerge.endPos);
54
-
55
- if (codeBlock) {
56
- tr.replaceRangeWith(startPos, endPos, codeBlock);
57
- }
58
-
59
- paragraphsToMerge = null;
60
- }
14
+ const markCodeType = schema.marks.code;
61
15
 
62
- function nodesToText(fragment: Fragment) {
63
- if (fragment.content.length === 0) {
64
- return '';
65
- }
16
+ doc.descendants((node, pos) => {
17
+ if (node.type.name === 'paragraph') {
18
+ let codeText = '';
19
+ let codeSize = 0;
20
+ for (let childNo = 0; childNo < node.childCount; childNo++) {
21
+ const child = node.child(childNo);
22
+
23
+ if (child.type.name === 'br') {
24
+ codeText += '\n';
25
+ codeSize += child.nodeSize;
26
+ continue;
27
+ }
66
28
 
67
- let retVal = '';
29
+ if (child.marks.some((mark) => mark.type === markCodeType)) {
30
+ codeText += child.text || child.textBetween(0, child.content.size);
31
+ codeSize += child.nodeSize;
32
+ continue;
33
+ }
68
34
 
69
- fragment.content.forEach((node) => {
70
- if (node.isText) {
71
- retVal += node.text;
72
- } else {
73
- retVal = '@TODO: node.type ' + node.type;
35
+ break;
74
36
  }
75
- });
76
37
 
77
- return retVal;
78
- }
38
+ if (codeSize > 0) {
39
+ const startPos = tr.mapping.map(pos);
40
+ const endPos = tr.mapping.map(pos + 1 + codeSize);
79
41
 
80
- doc.forEach((node, pos) => {
81
- if (node.type.name === 'paragraph') {
82
- const isCodeOnly = onlyHasCodeMarkedText(node, schema.marks.code);
83
- const isEmpty = node.content.size === 0;
42
+ const textNode = schema.text(codeText);
43
+ const codeBlock = schema.nodes.code_block.createAndFill(null, [
44
+ textNode,
45
+ ]);
84
46
 
85
- if (isCodeOnly) {
86
- if (paragraphsToMerge === null) {
87
- paragraphsToMerge = {
88
- startPos: pos,
89
- endPos: pos + node.nodeSize,
90
- innerText: nodesToText(node.content),
91
- };
92
- } else {
93
- paragraphsToMerge = {
94
- startPos: paragraphsToMerge.startPos,
95
- endPos: pos + node.nodeSize,
96
- innerText: paragraphsToMerge.innerText + '\n' +
97
- nodesToText(node.content),
98
- };
47
+ if (codeBlock) {
48
+ tr = tr.replaceRangeWith(startPos, endPos, codeBlock);
99
49
  }
100
- return;
101
50
  }
102
- }
103
51
 
104
- if (paragraphsToMerge !== null) {
105
- flushCodeBlock();
52
+ if (codeSize > 0 && codeSize + 2 === node.nodeSize) {
53
+ // tr = tr.deleteRange(tr.mapping.map(pos), tr.mapping.map(pos))
54
+ }
106
55
  }
107
56
  });
108
57
 
109
- if (paragraphsToMerge !== null) {
110
- flushCodeBlock();
111
- }
112
-
113
58
  if (dispatch) {
114
59
  dispatch(tr);
115
60
  }
116
61
 
117
- return tr.steps.length > 0;
62
+ return tr.docChanged;
118
63
  };
@@ -1,4 +1,3 @@
1
- import { off } from 'node:process';
2
1
  import { Node } from 'prosemirror-model';
3
2
  import { Command } from 'prosemirror-state';
4
3