@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
@@ -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,98 @@
1
+ import { Command } from 'prosemirror-state';
2
+
3
+ const CODEBLOCK_START = ''; // gdocs
4
+ const CODEBLOCK_END = '';
5
+
6
+ // Related tests:
7
+ // test ./code-blocks.md
8
+ export const mergeCodeBlocks: Command = (state, dispatch): boolean => {
9
+ const schema = state.schema;
10
+ let tr = state.tr;
11
+
12
+ const codeBlockType = schema.nodes.code_block;
13
+ const paraBlockType = schema.nodes.paragraph;
14
+
15
+ const parent = state.doc;
16
+ let offset = 0;
17
+ for (let nodeNo = 0; nodeNo < parent.childCount; nodeNo++) {
18
+ const node = parent.nodeAt(offset);
19
+ if (!node) {
20
+ continue;
21
+ }
22
+
23
+ if (node.type !== codeBlockType) {
24
+ offset += node.nodeSize;
25
+ continue;
26
+ }
27
+
28
+ let codeTexts = [];
29
+ let codeSize = 0;
30
+ {
31
+ codeSize += node.nodeSize;
32
+ const text = node.text || node.textBetween(0, node.content.size);
33
+ codeTexts.push(text.endsWith('\n') ? text : text + '\n');
34
+ }
35
+
36
+ let nextPos = offset + node.nodeSize;
37
+ while (true) {
38
+ const next = state.doc.nodeAt(nextPos);
39
+ if (!next) {
40
+ break;
41
+ }
42
+
43
+ const currentPos = nextPos;
44
+ nextPos += next.nodeSize;
45
+
46
+ if (next.type === paraBlockType && next.childCount === 0) {
47
+ codeTexts.push('\n');
48
+ codeSize += next.nodeSize;
49
+ continue;
50
+ }
51
+
52
+ if (next.type === codeBlockType && node.attrs.lang === next.attrs.lang) {
53
+ const text = next.text || next.textBetween(0, next.content.size);
54
+ codeTexts.push(text.endsWith('\n') ? text : text + '\n');
55
+
56
+ codeSize += next.nodeSize;
57
+ continue;
58
+ }
59
+
60
+ break;
61
+ }
62
+
63
+ if (codeTexts.length > 1) {
64
+ const startPos = tr.mapping.map(offset);
65
+ const endPos = tr.mapping.map(offset + codeSize);
66
+
67
+ const codeText = codeTexts.join('').replace(/\n+$/gm, '\n');
68
+ const textNode = schema.text(codeText);
69
+ const codeBlock = schema.nodes.code_block.createAndFill(null, [textNode]);
70
+
71
+ if (codeBlock) {
72
+ tr = tr.replaceRangeWith(startPos, endPos, codeBlock);
73
+ }
74
+ }
75
+
76
+ offset += codeSize;
77
+ }
78
+
79
+ state.doc.descendants((node, pos) => {
80
+ if (
81
+ node.type === paraBlockType &&
82
+ [CODEBLOCK_START, CODEBLOCK_END].includes(
83
+ node.textBetween(0, node.content.size),
84
+ )
85
+ ) {
86
+ tr = tr.deleteRange(
87
+ tr.mapping.map(pos),
88
+ tr.mapping.map(pos + node.nodeSize),
89
+ );
90
+ }
91
+ });
92
+
93
+ if (dispatch) {
94
+ dispatch(tr);
95
+ }
96
+
97
+ return tr.docChanged;
98
+ };
@@ -5,6 +5,7 @@ import { convertCodeParagraphsToCodeBlocks } from './convertCodeParagraphsToCode
5
5
  import { removeUnusedBookmarks } from './removeUnusedBookmarks.js';
6
6
  import { fixContinuedLists } from './fixContinuedLists.js';
7
7
  import { convertMathMl } from './convertMathMl.js';
8
+ import { mergeCodeBlocks } from './mergeCodeBlocks.js';
8
9
 
9
10
  export interface PostProcessConfig {
10
11
  doc: Node;
@@ -15,9 +16,10 @@ export function getDefaultsPostProcessFilters(
15
16
  { doc, filesMap }: PostProcessConfig,
16
17
  ): Array<Command> {
17
18
  return [
18
- convertCodeParagraphsToCodeBlocks,
19
19
  removeUnusedBookmarks,
20
+ convertCodeParagraphsToCodeBlocks,
20
21
  fixContinuedLists,
21
22
  convertMathMl,
23
+ mergeCodeBlocks,
22
24
  ];
23
25
  }
@@ -1,36 +1,49 @@
1
- import { Fragment, MarkType, Node, Schema } from 'prosemirror-model';
2
- import { Command } from 'prosemirror-state';
1
+ import type { Node } from 'prosemirror-model';
2
+ import type { Command } from 'prosemirror-state';
3
3
 
4
4
  export const removeUnusedBookmarks: Command = (state, dispatch): boolean => {
5
- return false;
6
- function condition(mark) {
7
- console.log('rrrr', mark.type.name);
8
- return mark.type.name === 'bookmark';
9
- }
5
+ const schema = state.schema;
6
+ let tr = state.tr;
7
+
8
+ const bookmarkNodeType = schema.nodes.node_bookmark;
9
+ const bookmarkMarkType = schema.marks.bookmark;
10
10
 
11
- if (node.marks) {
12
- // For text nodes, filter out the marks that match the condition
13
- const newMarks = node.marks.filter((mark) => !condition(mark));
11
+ function walk(
12
+ node: Node,
13
+ pos = 0,
14
+ depth = 0,
15
+ ) {
16
+ if (node.type === bookmarkNodeType) {
17
+ console.log('del');
18
+ tr = tr.delete(
19
+ tr.mapping.map(pos - 1),
20
+ tr.mapping.map(pos + node.nodeSize - 1),
21
+ );
22
+ return;
23
+ }
14
24
 
15
- // If marks were removed, return a new text node with the remaining marks
25
+ const newMarks = node.marks.filter((mark) =>
26
+ mark.type !== bookmarkMarkType
27
+ );
16
28
  if (newMarks.length !== node.marks.length) {
17
- return node.mark(newMarks);
29
+ tr = tr.setNodeMarkup(
30
+ tr.mapping.map(pos),
31
+ null,
32
+ null,
33
+ newMarks,
34
+ );
18
35
  }
19
36
 
20
- // Otherwise, return the original text node
21
- return node;
37
+ node.forEach((child, offset, index) => {
38
+ walk(child, pos + offset + 1, depth + 1);
39
+ });
22
40
  }
23
41
 
24
- // if (Array.isArray(node.content)) {
25
- // const content: Node[] = node.content.content.map(childNode => removeUnusedBookmarks(childNode));
26
- // return node.copy(content);
27
- // } else {
28
- // console.log('node.content', node.content);
29
- // }
42
+ walk(state.doc);
30
43
 
31
44
  if (dispatch) {
32
45
  dispatch(tr);
33
46
  }
34
47
 
35
- return tr.steps.length > 0;
48
+ return tr.docChanged;
36
49
  };
@@ -0,0 +1,95 @@
1
+ import { Node } from 'prosemirror-model';
2
+ import { EditorState, Transaction } from 'prosemirror-state';
3
+
4
+ import { UrlRewriter } from '@kerebron/editor';
5
+
6
+ export async function urlRewrite(
7
+ urlFromRewriter: UrlRewriter,
8
+ filesMap: Record<string, Uint8Array>,
9
+ state: EditorState,
10
+ dispatch: (tr: Transaction) => void,
11
+ ): Promise<boolean> {
12
+ const imageNodes: Array<{ node: Node; pos: number }> = [];
13
+ state.doc.descendants((node, pos) => {
14
+ if (node.type.name === 'image') {
15
+ imageNodes.push({ node, pos });
16
+ }
17
+ });
18
+
19
+ const linkNodes: Array<{ node: Node; pos: number }> = [];
20
+ state.doc.descendants((node, pos) => {
21
+ if (node.marks.find((mark) => mark.type.name === 'link')) {
22
+ linkNodes.push({ node, pos });
23
+ }
24
+ });
25
+
26
+ const tr = state.tr;
27
+
28
+ for (const { node, pos } of linkNodes) {
29
+ const linkMark = node.marks.find((mark) => mark.type.name === 'link');
30
+ if (!linkMark) {
31
+ continue;
32
+ }
33
+ let href = linkMark.attrs.href || '';
34
+ href = await urlFromRewriter(href, {
35
+ type: 'A',
36
+ dest: 'kerebron',
37
+ });
38
+ if (href !== linkMark.attrs.href) {
39
+ const newMarks = node.marks.map((mark) => {
40
+ if (mark.type.name === 'link') {
41
+ const markType = state.schema.marks['link'];
42
+ return markType.create({ ...mark.attrs, href });
43
+ }
44
+ return mark;
45
+ });
46
+
47
+ const nodeType = state.schema.nodes[node.type.name];
48
+ let replaceNode;
49
+ if (nodeType.isText) {
50
+ replaceNode = state.schema.text(
51
+ node.text || '',
52
+ newMarks,
53
+ );
54
+ } else {
55
+ replaceNode = nodeType.create(
56
+ node.attrs,
57
+ node.content,
58
+ newMarks,
59
+ );
60
+ }
61
+ tr.replaceWith(
62
+ tr.mapping.map(pos),
63
+ tr.mapping.map(pos + node.nodeSize),
64
+ replaceNode,
65
+ );
66
+ }
67
+ }
68
+
69
+ for (const { node, pos } of imageNodes) {
70
+ let src = node.attrs.src || '';
71
+
72
+ src = await urlFromRewriter(src, {
73
+ type: 'IMG',
74
+ dest: 'kerebron',
75
+ filesMap,
76
+ });
77
+
78
+ if (src !== node.attrs.src) {
79
+ const nodeType = state.schema.nodes[node.type.name];
80
+ const replaceNode = nodeType.create(
81
+ { ...node.attrs, src },
82
+ node.content,
83
+ node.marks,
84
+ );
85
+ tr.replaceWith(
86
+ tr.mapping.map(pos),
87
+ tr.mapping.map(pos + node.nodeSize),
88
+ replaceNode,
89
+ );
90
+ }
91
+ }
92
+ dispatch(tr);
93
+
94
+ return tr.docChanged;
95
+ }