@kerebron/extension-odt 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 (61) hide show
  1. package/README.md +2 -2
  2. package/esm/ExtensionOdt.d.ts.map +1 -1
  3. package/esm/ExtensionOdt.js +28 -77
  4. package/esm/ExtensionOdt.js.map +1 -1
  5. package/esm/OdtParser.d.ts +41 -7
  6. package/esm/OdtParser.d.ts.map +1 -1
  7. package/esm/OdtParser.js +26 -19
  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 +1 -0
  14. package/esm/node_handlers/basic_node_handlers.d.ts.map +1 -1
  15. package/esm/node_handlers/basic_node_handlers.js +77 -9
  16. package/esm/node_handlers/basic_node_handlers.js.map +1 -1
  17. package/esm/node_handlers/list_node_handlers.d.ts.map +1 -1
  18. package/esm/node_handlers/list_node_handlers.js +61 -65
  19. package/esm/node_handlers/list_node_handlers.js.map +1 -1
  20. package/esm/postprocess/convertCodeParagraphsToCodeBlocks.d.ts.map +1 -1
  21. package/esm/postprocess/convertCodeParagraphsToCodeBlocks.js +40 -66
  22. package/esm/postprocess/convertCodeParagraphsToCodeBlocks.js.map +1 -1
  23. package/esm/postprocess/convertMathMl.d.ts.map +1 -1
  24. package/esm/postprocess/convertMathMl.js +1 -1
  25. package/esm/postprocess/convertMathMl.js.map +1 -1
  26. package/esm/postprocess/fixContinuedLists.d.ts.map +1 -1
  27. package/esm/postprocess/fixContinuedLists.js +80 -67
  28. package/esm/postprocess/fixContinuedLists.js.map +1 -1
  29. package/esm/postprocess/fixListsLevels.d.ts +3 -0
  30. package/esm/postprocess/fixListsLevels.d.ts.map +1 -0
  31. package/esm/postprocess/fixListsLevels.js +62 -0
  32. package/esm/postprocess/fixListsLevels.js.map +1 -0
  33. package/esm/postprocess/mergeCodeBlocks.d.ts +3 -0
  34. package/esm/postprocess/mergeCodeBlocks.d.ts.map +1 -0
  35. package/esm/postprocess/mergeCodeBlocks.js +83 -0
  36. package/esm/postprocess/mergeCodeBlocks.js.map +1 -0
  37. package/esm/postprocess/postProcess.d.ts.map +1 -1
  38. package/esm/postprocess/postProcess.js +5 -2
  39. package/esm/postprocess/postProcess.js.map +1 -1
  40. package/esm/postprocess/removeUnusedBookmarks.d.ts +1 -1
  41. package/esm/postprocess/removeUnusedBookmarks.d.ts.map +1 -1
  42. package/esm/postprocess/removeUnusedBookmarks.js +16 -19
  43. package/esm/postprocess/removeUnusedBookmarks.js.map +1 -1
  44. package/esm/postprocess/urlRewrite.d.ts +4 -0
  45. package/esm/postprocess/urlRewrite.d.ts.map +1 -0
  46. package/esm/postprocess/urlRewrite.js +60 -0
  47. package/esm/postprocess/urlRewrite.js.map +1 -0
  48. package/package.json +3 -3
  49. package/src/ExtensionOdt.ts +30 -114
  50. package/src/OdtParser.ts +86 -31
  51. package/src/lists.ts +24 -10
  52. package/src/node_handlers/basic_node_handlers.ts +82 -10
  53. package/src/node_handlers/list_node_handlers.ts +91 -90
  54. package/src/postprocess/convertCodeParagraphsToCodeBlocks.ts +44 -86
  55. package/src/postprocess/convertMathMl.ts +1 -2
  56. package/src/postprocess/fixContinuedLists.ts +95 -78
  57. package/src/postprocess/fixListsLevels.ts +93 -0
  58. package/src/postprocess/mergeCodeBlocks.ts +114 -0
  59. package/src/postprocess/postProcess.ts +5 -1
  60. package/src/postprocess/removeUnusedBookmarks.ts +33 -21
  61. package/src/postprocess/urlRewrite.ts +95 -0
@@ -1,116 +1,117 @@
1
- import { ListNumbering } from '../lists.js';
2
1
  import {
3
2
  iterateChildren,
3
+ ListStyle,
4
4
  NodeHandler,
5
5
  OdtStashContext,
6
- resolveStyle,
6
+ resolveListStyle,
7
7
  } from '../OdtParser.js';
8
+ import { inchesToMm } from './basic_node_handlers.js';
9
+
10
+ // https://docs.oasis-open.org/office/OpenDocument/v1.4/OpenDocument-v1.4-part3-schema.html#a_19_880_22__text_list_
11
+ // The text:style-name attribute specifies the name of a list style that is applied to a list.
12
+ // If this attribute is not included and therefore no list style is specified, one of the following actions is taken:
13
+ // •If a list is contained within another list, the list style defaults to the style of the surrounding list.
14
+ // •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.
15
+ // •An implementation-dependent default list style is used.
16
+ // To determine which formatting properties are applied to a list, the list level and list style name are taken into account.
17
+ function processListStyle(ctx: OdtStashContext, level: number) {
18
+ const listTracker = ctx.listTracker;
19
+ const attrs: Record<string, string | number> = {};
20
+
21
+ let style: ListStyle = resolveListStyle(
22
+ ctx.stylesTree,
23
+ ctx.automaticStyles,
24
+ '',
25
+ );
26
+
27
+ for (let i = listTracker.listStack.length - 1; i >= 0; i--) {
28
+ const list = listTracker.listStack[i];
29
+ if (!style['@name']) {
30
+ style = resolveListStyle(
31
+ ctx.stylesTree,
32
+ ctx.automaticStyles,
33
+ list.styleName,
34
+ );
35
+ }
36
+ }
37
+
38
+ let nodeTypeName = 'bullet_list';
39
+ if (style) {
40
+ const numLevelStyle = style['list-level-style-number'].find(
41
+ (levelStyle) => parseInt(String(levelStyle['@level'])) === level,
42
+ );
43
+ if (numLevelStyle) {
44
+ attrs['type'] = numLevelStyle['@num-format'] || '1';
45
+ if (numLevelStyle['@start-value']) {
46
+ attrs['start'] = String(numLevelStyle['@start-value']);
47
+ }
48
+ nodeTypeName = 'ordered_list';
49
+ const marginLeft =
50
+ numLevelStyle['list-level-properties']['list-level-label-alignment'][
51
+ '@margin-left'
52
+ ];
53
+ if (marginLeft) {
54
+ attrs['odtMarginLeft'] = inchesToMm(marginLeft);
55
+ }
56
+ }
57
+ const bulletLevelStyle = style['list-level-style-bullet'].find(
58
+ (levelStyle) => parseInt(String(levelStyle['@level'])) === level,
59
+ );
60
+ if (bulletLevelStyle) {
61
+ const marginLeft =
62
+ bulletLevelStyle['list-level-properties']['list-level-label-alignment'][
63
+ '@margin-left'
64
+ ];
65
+ if (marginLeft) {
66
+ attrs['odtMarginLeft'] = inchesToMm(marginLeft);
67
+ }
68
+ }
69
+ }
70
+
71
+ return {
72
+ attrs,
73
+ nodeTypeName,
74
+ };
75
+ }
8
76
 
77
+ // https://docs.oasis-open.org/office/OpenDocument/v1.4/OpenDocument-v1.4-part3-schema.html#element-text_list
9
78
  export function getListNodesHandlers(): Record<string, NodeHandler> {
10
79
  return {
11
- 'list': (ctx: OdtStashContext, odtElement: any) => {
80
+ 'list': (ctx: OdtStashContext, odtElement) => {
12
81
  const listTracker = ctx.listTracker;
13
- const list = {
14
- level: listTracker.listStack.length + 1,
15
- odtElement,
16
- };
17
- listTracker.listStack.push(list);
18
-
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
- }
82
+ listTracker.pushList(odtElement['@xml:id'], odtElement['@style-name']);
38
83
 
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
- }
84
+ const { nodeTypeName, attrs } = processListStyle(
85
+ ctx,
86
+ listTracker.getCurrentList().level,
87
+ );
50
88
 
51
89
  ctx.current.meta['list_type'] = nodeTypeName;
52
90
 
53
- let listNumbering = null;
54
-
55
- if (listId && listTracker.listNumberings.has(listId)) {
56
- listNumbering = listTracker.listNumberings.get(listId);
91
+ if (odtElement['@xml:id']) {
92
+ attrs['id'] = odtElement['@xml:id'];
57
93
  }
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;
94
+ if (odtElement['@continue-list']) {
95
+ attrs['continue'] = odtElement['@continue-list'];
68
96
  }
69
97
  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;
98
+ attrs['continue'] = '_last';
94
99
  }
95
100
 
96
101
  ctx.openNode();
97
102
 
98
- const children = odtElement['list-item'].map((item) => ({
103
+ const children = odtElement['list-item'].map((item: any) => ({
99
104
  'list-item': item,
100
105
  }));
101
106
 
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);
107
+ iterateChildren(children, (child) => {
108
+ ctx.handle(child.tag, child.value);
109
+ });
111
110
 
112
- if (listTracker.preserveMinLevel >= list.level) {
113
- listTracker.preserveMinLevel = list.level;
111
+ if (ctx.current.content.length === 0) {
112
+ ctx.dropNode();
113
+ } else {
114
+ ctx.closeNode(nodeTypeName, attrs);
114
115
  }
115
116
 
116
117
  listTracker.listStack.pop();
@@ -122,7 +123,7 @@ export function getListNodesHandlers(): Record<string, NodeHandler> {
122
123
  (child) => ctx.handle(child.tag, child.value),
123
124
  );
124
125
 
125
- const attrs = {};
126
+ const attrs: Record<string, string> = {};
126
127
  attrs.markup = '* ';
127
128
 
128
129
  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,66 @@ 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
- }
14
+ const markCodeType = schema.marks.code;
58
15
 
59
- paragraphsToMerge = null;
60
- }
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
+ const monospaced = child.marks.some((mark) =>
23
+ mark.type === markCodeType
24
+ );
25
+
26
+ if (child.type.name === 'node_bookmark') {
27
+ codeText += '\n';
28
+ codeSize += child.nodeSize;
29
+ continue;
30
+ }
61
31
 
62
- function nodesToText(fragment: Fragment) {
63
- if (fragment.content.length === 0) {
64
- return '';
65
- }
32
+ if (child.type.name === 'br') {
33
+ codeText += '\n';
34
+ codeSize += child.nodeSize;
35
+ continue;
36
+ }
66
37
 
67
- let retVal = '';
38
+ if (monospaced) {
39
+ codeText += child.text || child.textBetween(0, child.content.size);
40
+ codeSize += child.nodeSize;
41
+ continue;
42
+ }
68
43
 
69
- fragment.content.forEach((node) => {
70
- if (node.isText) {
71
- retVal += node.text;
72
- } else {
73
- retVal = '@TODO: node.type ' + node.type;
44
+ break;
74
45
  }
75
- });
76
46
 
77
- return retVal;
78
- }
47
+ if (codeSize > 0) {
48
+ const startPos = tr.mapping.map(pos);
49
+ const endPos = tr.mapping.map(pos + 1 + codeSize);
79
50
 
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;
51
+ if (codeText) {
52
+ const textNode = schema.text(codeText);
53
+ const codeBlock = schema.nodes.code_block.createAndFill(null, [
54
+ textNode,
55
+ ]);
84
56
 
85
- if (isCodeOnly) {
86
- if (paragraphsToMerge === null) {
87
- paragraphsToMerge = {
88
- startPos: pos,
89
- endPos: pos + node.nodeSize,
90
- innerText: nodesToText(node.content),
91
- };
57
+ if (codeBlock) {
58
+ tr.replaceRangeWith(startPos, endPos, codeBlock);
59
+ }
92
60
  } else {
93
- paragraphsToMerge = {
94
- startPos: paragraphsToMerge.startPos,
95
- endPos: pos + node.nodeSize,
96
- innerText: paragraphsToMerge.innerText + '\n' +
97
- nodesToText(node.content),
98
- };
61
+ tr.replace(startPos, endPos);
99
62
  }
100
- return;
101
63
  }
102
- }
103
64
 
104
- if (paragraphsToMerge !== null) {
105
- flushCodeBlock();
65
+ if (codeSize > 0 && codeSize + 2 === node.nodeSize) {
66
+ // tr.deleteRange(tr.mapping.map(pos), tr.mapping.map(pos))
67
+ }
106
68
  }
107
69
  });
108
70
 
109
- if (paragraphsToMerge !== null) {
110
- flushCodeBlock();
111
- }
112
-
113
71
  if (dispatch) {
114
72
  dispatch(tr);
115
73
  }
116
74
 
117
- return tr.steps.length > 0;
75
+ return tr.docChanged;
118
76
  };
@@ -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
 
@@ -52,5 +51,5 @@ export const convertMathMl: Command = (state, dispatch): boolean => {
52
51
  dispatch(tr);
53
52
  }
54
53
 
55
- return true;
54
+ return tr.docChanged;
56
55
  };
@@ -1,109 +1,126 @@
1
- import { Fragment, Node, Slice } from 'prosemirror-model';
1
+ import { Node } from 'prosemirror-model';
2
2
  import { Command } from 'prosemirror-state';
3
3
 
4
- import { nodeToTreeString } from '@kerebron/editor';
4
+ import { ListNumbering } from '../lists.js';
5
5
 
6
6
  const ANY_LIST = ['ordered_list', 'bullet_list'];
7
7
 
8
- interface ListPos {
9
- start: number;
10
- end: number;
11
- stitchingDepth: number;
12
- }
13
-
14
- function getListEndingMaxDepth(list: Node | null): number {
15
- if (!list) {
16
- return 0;
17
- }
18
-
19
- if (ANY_LIST.indexOf(list.type.name) === -1) {
20
- return 0;
21
- }
22
-
23
- if (list.lastChild?.type.name === 'list_item') {
24
- return 1 + getListEndingMaxDepth(list.lastChild?.lastChild);
25
- }
8
+ export const fixContinuedLists: Command = (state, dispatch): boolean => {
9
+ const doc: Node = state.doc;
10
+ let tr = state.tr;
26
11
 
27
- return 0;
28
- }
12
+ const listNumberings: Map<string, ListNumbering> = new Map<
13
+ string,
14
+ ListNumbering
15
+ >();
29
16
 
30
- function convertToStitchingLevels(list: Node, array: Array<Slice> = []) {
31
- if (ANY_LIST.indexOf(list.type.name) === -1) {
32
- throw new Error(`Incorrect list type: ${list.type.name}`);
33
- }
17
+ let topListId = '';
34
18
 
35
- if (!list?.firstChild) {
36
- return array;
37
- }
19
+ function processContinuation(node: Node) {
20
+ let listNumbering = null;
38
21
 
39
- const firstChildParagraph = list.firstChild.firstChild;
22
+ if (topListId && listNumberings.has(topListId)) {
23
+ listNumbering = listNumberings.get(topListId);
24
+ }
40
25
 
41
- if (firstChildParagraph?.content.size === 0) {
42
- if (list?.firstChild.children.length < 2) {
43
- array.push(new Slice(Fragment.from([]), 0, 0));
44
- // array.push(list.slice(list.firstChild.nodeSize, list.firstChild.nodeSize)) // empty slice
45
- return array;
26
+ const cont = node.attrs['continue'];
27
+ if (cont) {
28
+ listNumbering = listNumberings.get(cont);
46
29
  }
47
30
 
48
- const subList = list?.firstChild.child(1);
31
+ if (!listNumbering) {
32
+ listNumbering = new ListNumbering(topListId);
33
+ }
49
34
 
50
- array.push(list.slice(list.firstChild.nodeSize, list.content.size));
35
+ listNumberings.set(topListId, listNumbering);
51
36
 
52
- if (ANY_LIST.indexOf(subList?.type.name) > -1) {
53
- convertToStitchingLevels(subList, array);
54
- }
55
- } else {
56
- array.push(list.slice(0, list.content.size));
37
+ return { listNumbering };
57
38
  }
58
39
 
59
- return array;
60
- }
61
-
62
- export const fixContinuedLists: Command = (state, dispatch): boolean => {
63
- const doc: Node = state.doc;
64
- let tr = state.tr;
40
+ let possibleContinue = true;
41
+ let level = 0;
42
+
43
+ function walk(
44
+ node: Node,
45
+ pos = 0,
46
+ depth = 0,
47
+ ) {
48
+ if ('list_item' === node.type.name) {
49
+ const listNumbering = listNumberings.get('_current');
50
+ const firstChild = node.content.firstChild;
51
+ if (firstChild && listNumbering) {
52
+ if (
53
+ firstChild.type.name === 'paragraph' &&
54
+ firstChild.content.childCount > 0
55
+ ) {
56
+ if (listNumbering.forceStart[level]) {
57
+ delete listNumbering.forceStart[level];
58
+
59
+ tr = tr.setNodeMarkup(tr.mapping.map(pos - 1), node.type, {
60
+ ...node.attrs,
61
+ value: listNumbering.levels[level],
62
+ });
63
+ }
65
64
 
66
- let previousList: ListPos | null = null;
65
+ listNumbering.levels[level]++;
66
+ } else {
67
+ tr = tr.setNodeMarkup(tr.mapping.map(pos - 1), node.type, {
68
+ ...node.attrs,
69
+ type: 'none',
70
+ });
71
+ listNumbering.forceStart[level] = true;
72
+ }
73
+ }
74
+ }
67
75
 
68
- // console.debug(nodeToTreeString(doc));
76
+ if (!ANY_LIST.includes(node.type.name)) {
77
+ node.forEach((child, offset) => {
78
+ walk(child, pos + offset + 1, depth + 1);
79
+ });
80
+ } else {
81
+ level++;
69
82
 
70
- doc.forEach((child, childPos) => {
71
- if (child.type.name === 'ordered_list') {
72
- const start = childPos;
73
- const end = start + child.nodeSize;
83
+ if (level === 1) {
84
+ topListId = node.attrs.id;
85
+ possibleContinue = true;
86
+ }
74
87
 
75
- if (previousList) {
76
- const stitchingLevels = convertToStitchingLevels(child);
88
+ const { listNumbering } = processContinuation(node);
77
89
 
78
- if (stitchingLevels.length <= previousList.stitchingDepth) {
79
- const betweenStart = previousList.end;
80
- const betweenNodes = doc.slice(betweenStart, start);
90
+ listNumberings.set('_current', listNumbering);
81
91
 
82
- tr = tr.delete(tr.mapping.map(betweenStart), tr.mapping.map(end));
92
+ if (!possibleContinue) {
93
+ listNumbering.levels[level] = 1;
94
+ }
83
95
 
84
- let posToInsert = tr.mapping.map(previousList.end);
85
- for (const level of stitchingLevels) {
86
- posToInsert -= 1; // Before /OL token
87
- tr = tr.replace(posToInsert, posToInsert, level);
88
- posToInsert -= 1; // Before /LI token
89
- }
90
- if (betweenNodes.size > 0) {
91
- tr = tr.replace(posToInsert, posToInsert, betweenNodes);
92
- }
96
+ if (node.attrs.start) {
97
+ listNumbering.levels[level] = node.attrs.start;
98
+ }
93
99
 
94
- previousList.stitchingDepth = getListEndingMaxDepth(child);
95
- previousList.end = end;
96
- return;
100
+ if ('ordered_list' === node.type.name) {
101
+ if (1 < listNumbering.levels[level]) {
102
+ tr = tr.setNodeMarkup(tr.mapping.map(pos - 1), node.type, {
103
+ ...node.attrs,
104
+ start: listNumbering.levels[level],
105
+ continue: undefined,
106
+ });
97
107
  }
98
108
  }
99
109
 
100
- previousList = {
101
- stitchingDepth: getListEndingMaxDepth(child),
102
- start,
103
- end,
104
- };
110
+ node.forEach((child, offset) => {
111
+ walk(child, pos + offset + 1, depth + 1);
112
+ });
113
+
114
+ if (level === 1) {
115
+ listNumberings.set('_last', listNumbering);
116
+ }
117
+
118
+ level--;
119
+ possibleContinue = false;
105
120
  }
106
- });
121
+ }
122
+
123
+ walk(doc);
107
124
 
108
125
  if (dispatch) {
109
126
  dispatch(tr);
@@ -0,0 +1,93 @@
1
+ import { Fragment, Node } from 'prosemirror-model';
2
+ import { Command } from 'prosemirror-state';
3
+
4
+ const ANY_LIST = ['ordered_list', 'bullet_list'];
5
+
6
+ type LevelMargins = Array<number>;
7
+
8
+ // Related tests:
9
+ // test ./example-document.md
10
+ export const fixListsLevels: Command = (state, dispatch): boolean => {
11
+ const doc: Node = state.doc;
12
+ const schema = state.schema;
13
+ const bulletListType = schema.nodes.bullet_list;
14
+ const listItemType = schema.nodes.list_item;
15
+ const paragraphType = schema.nodes.paragraph;
16
+
17
+ const tr = state.tr;
18
+
19
+ let level = 0;
20
+
21
+ let prevList: LevelMargins = [];
22
+ let curList: LevelMargins = [];
23
+ let fakeLevelGenerated = 0;
24
+
25
+ function walk(
26
+ node: Node,
27
+ pos = 0,
28
+ depth = 0,
29
+ ) {
30
+ if (!ANY_LIST.includes(node.type.name)) {
31
+ node.forEach((child, offset) => {
32
+ walk(child, pos + offset + 1, depth + 1);
33
+ });
34
+ } else {
35
+ const marginLeft = node.attrs['odtMarginLeft'] || 0;
36
+ if (level === 0) {
37
+ curList = [];
38
+ fakeLevelGenerated = 0;
39
+ if (prevList.length >= level) {
40
+ for (let i = 0; i < prevList.length; i++) {
41
+ if (
42
+ fakeLevelGenerated < prevList.length &&
43
+ marginLeft > prevList[fakeLevelGenerated]
44
+ ) {
45
+ curList[level + fakeLevelGenerated] =
46
+ prevList[fakeLevelGenerated];
47
+ fakeLevelGenerated++;
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ curList[level + fakeLevelGenerated] = marginLeft;
54
+
55
+ level++;
56
+
57
+ node.forEach((child, offset) => {
58
+ walk(child, pos + offset + 1, depth + 1);
59
+ });
60
+
61
+ level--;
62
+
63
+ if (level === 0 && !node.attrs.toc) {
64
+ prevList = curList;
65
+ for (let i = 0; i < fakeLevelGenerated; i++) {
66
+ const wrapper = bulletListType.create(
67
+ { type: 'none' },
68
+ listItemType.create(
69
+ null,
70
+ Fragment.from([
71
+ paragraphType.create(),
72
+ node,
73
+ ]),
74
+ ),
75
+ );
76
+ tr.replaceWith(
77
+ tr.mapping.map(pos),
78
+ tr.mapping.map(pos + node.nodeSize),
79
+ wrapper,
80
+ );
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ walk(doc);
87
+
88
+ if (dispatch) {
89
+ dispatch(tr);
90
+ }
91
+
92
+ return tr.docChanged;
93
+ };