@fiduswriter/document 0.1.0-alpha.1

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 (110) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +16 -0
  3. package/jest.config.js +23 -0
  4. package/package.json +59 -0
  5. package/schema.json +1 -0
  6. package/scripts/export-schema.js +16 -0
  7. package/src/bibliography/common.js +92 -0
  8. package/src/bibliography/csl_bib.js +139 -0
  9. package/src/citations/citeproc_sys.js +42 -0
  10. package/src/citations/format.js +194 -0
  11. package/src/common/blob.js +10 -0
  12. package/src/common/file.js +25 -0
  13. package/src/common/index.js +12 -0
  14. package/src/common/network.js +79 -0
  15. package/src/common/text.js +44 -0
  16. package/src/editor/e2ee/encryptor.js +228 -0
  17. package/src/exporter/docx/citations.js +177 -0
  18. package/src/exporter/docx/comments.js +165 -0
  19. package/src/exporter/docx/footnotes.js +240 -0
  20. package/src/exporter/docx/images.js +101 -0
  21. package/src/exporter/docx/index.js +185 -0
  22. package/src/exporter/docx/lists.js +260 -0
  23. package/src/exporter/docx/math.js +46 -0
  24. package/src/exporter/docx/metadata.js +289 -0
  25. package/src/exporter/docx/rels.js +193 -0
  26. package/src/exporter/docx/render.js +941 -0
  27. package/src/exporter/docx/richtext.js +1182 -0
  28. package/src/exporter/docx/tables.js +112 -0
  29. package/src/exporter/docx/tools.js +50 -0
  30. package/src/exporter/epub/index.js +142 -0
  31. package/src/exporter/epub/templates.js +140 -0
  32. package/src/exporter/epub/tools.js +96 -0
  33. package/src/exporter/html/citations.js +121 -0
  34. package/src/exporter/html/convert.js +813 -0
  35. package/src/exporter/html/index.js +192 -0
  36. package/src/exporter/html/templates.js +34 -0
  37. package/src/exporter/html/tools.js +50 -0
  38. package/src/exporter/jats/bibliography.js +183 -0
  39. package/src/exporter/jats/citations.js +109 -0
  40. package/src/exporter/jats/convert.js +871 -0
  41. package/src/exporter/jats/index.js +92 -0
  42. package/src/exporter/jats/templates.js +35 -0
  43. package/src/exporter/jats/text.js +72 -0
  44. package/src/exporter/latex/convert.js +934 -0
  45. package/src/exporter/latex/escape_latex.js +21 -0
  46. package/src/exporter/latex/index.js +74 -0
  47. package/src/exporter/latex/readme.js +22 -0
  48. package/src/exporter/native/shrink.js +132 -0
  49. package/src/exporter/odt/citations.js +101 -0
  50. package/src/exporter/odt/footnotes.js +147 -0
  51. package/src/exporter/odt/images.js +115 -0
  52. package/src/exporter/odt/index.js +156 -0
  53. package/src/exporter/odt/math.js +57 -0
  54. package/src/exporter/odt/metadata.js +251 -0
  55. package/src/exporter/odt/render.js +806 -0
  56. package/src/exporter/odt/richtext.js +865 -0
  57. package/src/exporter/odt/styles.js +387 -0
  58. package/src/exporter/odt/track.js +68 -0
  59. package/src/exporter/pandoc/citations.js +98 -0
  60. package/src/exporter/pandoc/convert.js +1017 -0
  61. package/src/exporter/pandoc/index.js +92 -0
  62. package/src/exporter/pandoc/readme.js +8 -0
  63. package/src/exporter/pandoc/tools.js +51 -0
  64. package/src/exporter/print/index.js +177 -0
  65. package/src/exporter/tools/doc_content.js +144 -0
  66. package/src/exporter/tools/file.js +9 -0
  67. package/src/exporter/tools/json.js +73 -0
  68. package/src/exporter/tools/svg.js +29 -0
  69. package/src/exporter/tools/xml.js +531 -0
  70. package/src/exporter/tools/xml_zip.js +95 -0
  71. package/src/exporter/tools/zip.js +90 -0
  72. package/src/exporter/tools/zotero_csl.js +93 -0
  73. package/src/importer/citations.js +129 -0
  74. package/src/importer/docx/citations.js +123 -0
  75. package/src/importer/docx/convert.js +1427 -0
  76. package/src/importer/docx/helpers.js +9 -0
  77. package/src/importer/docx/omml2mathml.js +1448 -0
  78. package/src/importer/docx/parse.js +735 -0
  79. package/src/importer/native/get_images.js +76 -0
  80. package/src/importer/native/update.js +29 -0
  81. package/src/importer/odt/citations.js +87 -0
  82. package/src/importer/odt/convert.js +1855 -0
  83. package/src/importer/pandoc/convert.js +884 -0
  84. package/src/importer/pandoc/helpers.js +84 -0
  85. package/src/importer/zip_analyzer.js +102 -0
  86. package/src/index.js +1 -0
  87. package/src/mathlive/opf_includes.js +24 -0
  88. package/src/schema/common/annotate.js +76 -0
  89. package/src/schema/common/base.js +118 -0
  90. package/src/schema/common/citation.js +62 -0
  91. package/src/schema/common/equation.js +31 -0
  92. package/src/schema/common/figure.js +190 -0
  93. package/src/schema/common/heading.js +43 -0
  94. package/src/schema/common/index.js +40 -0
  95. package/src/schema/common/list.js +95 -0
  96. package/src/schema/common/reference.js +100 -0
  97. package/src/schema/common/table.js +103 -0
  98. package/src/schema/common/track.js +190 -0
  99. package/src/schema/const.js +58 -0
  100. package/src/schema/convert.js +1272 -0
  101. package/src/schema/document/content.js +187 -0
  102. package/src/schema/document/index.js +117 -0
  103. package/src/schema/document/structure.js +452 -0
  104. package/src/schema/export.js +21 -0
  105. package/src/schema/footnotes.js +126 -0
  106. package/src/schema/footnotes_convert.js +31 -0
  107. package/src/schema/i18n.js +595 -0
  108. package/src/schema/index.js +5 -0
  109. package/src/schema/mini_json.js +61 -0
  110. package/src/schema/text.js +22 -0
@@ -0,0 +1,84 @@
1
+ export const applyMarkToNodes = (nodes, markType, attrs = null) => {
2
+ return nodes.map(node => {
3
+ if (node.type === "text") {
4
+ const mark = attrs ? {type: markType, attrs} : {type: markType}
5
+ return {
6
+ ...node,
7
+ marks: [...(node.marks || []), mark]
8
+ }
9
+ }
10
+ return node
11
+ })
12
+ }
13
+
14
+ export const mergeTextNodes = nodes => {
15
+ const mergedNodes = []
16
+ let currentNode = null
17
+
18
+ const areSameMarks = (marks1 = [], marks2 = []) => {
19
+ if (marks1.length !== marks2.length) {
20
+ return false
21
+ }
22
+ // Sort marks by type to ensure consistent comparison
23
+ const sortedMarks1 = [...marks1].sort((a, b) =>
24
+ a.type.localeCompare(b.type)
25
+ )
26
+ const sortedMarks2 = [...marks2].sort((a, b) =>
27
+ a.type.localeCompare(b.type)
28
+ )
29
+ return sortedMarks1.every((mark, index) => {
30
+ const mark2 = sortedMarks2[index]
31
+ if (mark.type !== mark2.type) {
32
+ return false
33
+ }
34
+ if (!mark.attrs && !mark2.attrs) {
35
+ return true
36
+ }
37
+ return JSON.stringify(mark.attrs) === JSON.stringify(mark2.attrs)
38
+ })
39
+ }
40
+
41
+ nodes.forEach(node => {
42
+ if (node.type === "text") {
43
+ if (
44
+ currentNode &&
45
+ currentNode.type === "text" &&
46
+ areSameMarks(currentNode.marks, node.marks)
47
+ ) {
48
+ // Merge with previous node
49
+ currentNode.text += node.text
50
+ } else {
51
+ // Start new node
52
+ if (currentNode) {
53
+ mergedNodes.push(currentNode)
54
+ }
55
+ currentNode = {...node}
56
+ }
57
+ } else {
58
+ if (currentNode) {
59
+ mergedNodes.push(currentNode)
60
+ }
61
+ mergedNodes.push(node)
62
+ currentNode = null
63
+ }
64
+ })
65
+
66
+ if (currentNode) {
67
+ mergedNodes.push(currentNode)
68
+ }
69
+
70
+ return mergedNodes
71
+ }
72
+
73
+ export const applyAnnotation = (nodes, type) => {
74
+ return nodes.map(node => ({
75
+ ...node,
76
+ marks: [
77
+ ...(node.marks || []),
78
+ {
79
+ type: "annotation_tag",
80
+ attrs: {type, key: "", value: ""}
81
+ }
82
+ ]
83
+ }))
84
+ }
@@ -0,0 +1,102 @@
1
+ export class ZipAnalyzer {
2
+ constructor(zip, formats = []) {
3
+ this.zip = zip
4
+ this.formats = formats
5
+
6
+ this.analysis = null
7
+ }
8
+
9
+ analyze() {
10
+ if (this.analysis) {
11
+ return this.analysis
12
+ }
13
+
14
+ let convertibleFile = null
15
+ const imageFiles = []
16
+ let bibFile = null
17
+
18
+ // Analyze all files in the ZIP
19
+ this.zip.forEach((relativePath, zipEntry) => {
20
+ if (!zipEntry.dir) {
21
+ const fileName = relativePath.split("/").pop()
22
+ const extension = fileName.split(".").pop().toLowerCase()
23
+
24
+ if (extension === "bib") {
25
+ bibFile = zipEntry
26
+ } else if (
27
+ [
28
+ "avif",
29
+ "avifs",
30
+ "png",
31
+ "jpg",
32
+ "jpeg",
33
+ "gif",
34
+ "svg",
35
+ "webp"
36
+ ].includes(extension)
37
+ ) {
38
+ imageFiles.push({path: relativePath, entry: zipEntry})
39
+ } else if (this.formats.includes(extension)) {
40
+ // Store the first convertible file found
41
+ if (!convertibleFile) {
42
+ convertibleFile = {
43
+ path: relativePath,
44
+ entry: zipEntry,
45
+ fileName,
46
+ format: extension
47
+ }
48
+ }
49
+ }
50
+ }
51
+ })
52
+
53
+ this.analysis = {
54
+ hasConvertible: Boolean(convertibleFile),
55
+ format: convertibleFile ? convertibleFile.format : null,
56
+ convertibleFile,
57
+ imageFiles,
58
+ bibFile
59
+ }
60
+
61
+ return this.analysis
62
+ }
63
+
64
+ async getContents() {
65
+ if (!this.analysis) {
66
+ await this.analyze()
67
+ }
68
+
69
+ const contents = {
70
+ images: {},
71
+ bibliography: null,
72
+ mainContent: null
73
+ }
74
+
75
+ // Load main content file
76
+ if (this.analysis.hasConvertible) {
77
+ const mainBlob =
78
+ await this.analysis.convertibleFile.entry.async("blob")
79
+ contents.mainContent = new File(
80
+ [mainBlob],
81
+ this.analysis.convertibleFile.fileName
82
+ )
83
+ }
84
+
85
+ // Load images
86
+ const imagePromises = this.analysis.imageFiles.map(
87
+ async ({path, entry}) => {
88
+ const blob = await entry.async("blob")
89
+ contents.images[path] = blob
90
+ return {filename: path, blob}
91
+ }
92
+ )
93
+ await Promise.all(imagePromises)
94
+
95
+ // Load bibliography if present
96
+ if (this.analysis.bibFile) {
97
+ contents.bibliography = await this.analysis.bibFile.async("text")
98
+ }
99
+
100
+ return contents
101
+ }
102
+ }
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ export {FW_DOCUMENT_VERSION} from "./schema/index.js"
@@ -0,0 +1,24 @@
1
+ // This file is auto-generated. CHANGES WILL BE OVERWRITTEN! Re-generate by running ./manage.py bundle_mathlive.
2
+ export const mathliveOpfIncludes = `
3
+ <item id="mathlive-0" href="css/mathlive.css" media-type="text/css" />
4
+ <item id="mathlive-1" href="css/media/KaTeX_Caligraphic-Bold.woff2" media-type="font/woff2" />
5
+ <item id="mathlive-2" href="css/media/KaTeX_Fraktur-Bold.woff2" media-type="font/woff2" />
6
+ <item id="mathlive-3" href="css/media/KaTeX_SansSerif-Bold.woff2" media-type="font/woff2" />
7
+ <item id="mathlive-4" href="css/media/KaTeX_Size1-Regular.woff2" media-type="font/woff2" />
8
+ <item id="mathlive-5" href="css/media/KaTeX_Fraktur-Regular.woff2" media-type="font/woff2" />
9
+ <item id="mathlive-6" href="css/media/KaTeX_SansSerif-Regular.woff2" media-type="font/woff2" />
10
+ <item id="mathlive-7" href="css/media/KaTeX_Main-Italic.woff2" media-type="font/woff2" />
11
+ <item id="mathlive-8" href="css/media/KaTeX_Size4-Regular.woff2" media-type="font/woff2" />
12
+ <item id="mathlive-9" href="css/media/KaTeX_Caligraphic-Regular.woff2" media-type="font/woff2" />
13
+ <item id="mathlive-10" href="css/media/KaTeX_AMS-Regular.woff2" media-type="font/woff2" />
14
+ <item id="mathlive-11" href="css/media/KaTeX_SansSerif-Italic.woff2" media-type="font/woff2" />
15
+ <item id="mathlive-12" href="css/media/KaTeX_Size3-Regular.woff2" media-type="font/woff2" />
16
+ <item id="mathlive-13" href="css/media/KaTeX_Size2-Regular.woff2" media-type="font/woff2" />
17
+ <item id="mathlive-14" href="css/media/KaTeX_Script-Regular.woff2" media-type="font/woff2" />
18
+ <item id="mathlive-15" href="css/media/KaTeX_Main-Bold.woff2" media-type="font/woff2" />
19
+ <item id="mathlive-16" href="css/media/KaTeX_Math-BoldItalic.woff2" media-type="font/woff2" />
20
+ <item id="mathlive-17" href="css/media/KaTeX_Main-BoldItalic.woff2" media-type="font/woff2" />
21
+ <item id="mathlive-18" href="css/media/KaTeX_Main-Regular.woff2" media-type="font/woff2" />
22
+ <item id="mathlive-19" href="css/media/KaTeX_Math-Italic.woff2" media-type="font/woff2" />
23
+ <item id="mathlive-20" href="css/media/KaTeX_Typewriter-Regular.woff2" media-type="font/woff2" />
24
+ `
@@ -0,0 +1,76 @@
1
+ // Annotation tag is not used by the core Fidus Writer editor, but can be used by plugins that need to add annotation capability.
2
+ export const annotation_tag = {
3
+ attrs: {
4
+ type: {
5
+ default: "" // Make this a string unique to your plugin so that you avoid handling tags of other plugins. For example 'rdfa' for an rdfa-tagging plugin.
6
+ },
7
+ key: {
8
+ default: "" // key or variable/tag name
9
+ },
10
+ value: {
11
+ default: "" // value of variable/tag
12
+ }
13
+ },
14
+ inclusive: false,
15
+ excludes: "", // allows several tags on the same content.
16
+ group: "annotation",
17
+ parseDOM: [
18
+ {
19
+ tag: "span.annotation-tag[data-type]",
20
+ getAttrs(dom) {
21
+ return {
22
+ type: dom.dataset.type,
23
+ key: dom.dataset.key ? dom.dataset.key : "",
24
+ value: dom.dataset.value ? dom.dataset.value : ""
25
+ }
26
+ }
27
+ }
28
+ ],
29
+ toDOM(node) {
30
+ const attrs = {
31
+ class: "annotation-tag",
32
+ "data-type": node.attrs.type
33
+ }
34
+ if (node.attrs.key?.length) {
35
+ attrs["data-key"] = node.attrs.key
36
+ }
37
+ if (node.attrs.value?.length) {
38
+ attrs["data-value"] = node.attrs.value
39
+ }
40
+ return ["span", attrs]
41
+ }
42
+ }
43
+
44
+ export const comment = {
45
+ attrs: {
46
+ id: {
47
+ default: false
48
+ }
49
+ },
50
+ inclusive: false,
51
+ excludes: "",
52
+ group: "annotation",
53
+ parseDOM: [
54
+ {
55
+ tag: "span.comment[data-id]",
56
+ getAttrs(dom) {
57
+ return {
58
+ id: Number.parseInt(dom.dataset.id)
59
+ }
60
+ }
61
+ }
62
+ ],
63
+ toDOM(node) {
64
+ return [
65
+ "span",
66
+ {
67
+ class: "comment",
68
+ "data-id": node.attrs.id
69
+ }
70
+ ]
71
+ }
72
+ }
73
+
74
+ export function randomCommentId() {
75
+ return String(Math.floor(Math.random() * 0xffffffff))
76
+ }
@@ -0,0 +1,118 @@
1
+ import {parseTracks} from "./track.js"
2
+
3
+ // :: NodeSpec A plain paragraph textblock. Represented in the DOM
4
+ // as a `<p>` element.
5
+ export const paragraph = {
6
+ group: "block",
7
+ content: "inline*",
8
+ attrs: {
9
+ track: {
10
+ default: []
11
+ }
12
+ },
13
+ parseDOM: [
14
+ {
15
+ tag: "p",
16
+ getAttrs(dom) {
17
+ return {
18
+ track: parseTracks(dom.dataset.track)
19
+ }
20
+ }
21
+ }
22
+ ],
23
+ toDOM(node) {
24
+ const attrs =
25
+ node.attrs.track && node.attrs.track.length
26
+ ? {"data-track": JSON.stringify(node.attrs.track)}
27
+ : {}
28
+ return ["p", attrs, 0]
29
+ }
30
+ }
31
+
32
+ // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
33
+ export const blockquote = {
34
+ content: "block+",
35
+ group: "block",
36
+ attrs: {
37
+ track: {
38
+ default: []
39
+ }
40
+ },
41
+ marks: "annotation",
42
+ defining: true,
43
+ parseDOM: [
44
+ {
45
+ tag: "blockquote",
46
+ getAttrs(dom) {
47
+ return {
48
+ track: parseTracks(dom.dataset.track)
49
+ }
50
+ }
51
+ }
52
+ ],
53
+ toDOM(node) {
54
+ const attrs =
55
+ node.attrs.track && node.attrs.track.length
56
+ ? {"data-track": JSON.stringify(node.attrs.track)}
57
+ : {}
58
+ return ["blockquote", attrs, 0]
59
+ }
60
+ }
61
+
62
+ // :: NodeSpec A horizontal rule (`<hr>`).
63
+ export const horizontal_rule = {
64
+ group: "block",
65
+ attrs: {
66
+ track: {
67
+ default: []
68
+ }
69
+ },
70
+ parseDOM: [
71
+ {
72
+ tag: "hr",
73
+ getAttrs(dom) {
74
+ return {
75
+ track: parseTracks(dom.dataset.track)
76
+ }
77
+ }
78
+ }
79
+ ],
80
+ toDOM(node) {
81
+ const attrs =
82
+ node.attrs.track && node.attrs.track.length
83
+ ? {"data-track": JSON.stringify(node.attrs.track)}
84
+ : {}
85
+ return ["hr", attrs]
86
+ }
87
+ }
88
+
89
+ export const underline = {
90
+ parseDOM: [{tag: "span.underline"}],
91
+ toDOM() {
92
+ return ["span", {class: "underline"}, 0]
93
+ }
94
+ }
95
+
96
+ export const sup = {
97
+ parseDOM: [{tag: "sup"}],
98
+ toDOM() {
99
+ return ["sup", 0]
100
+ },
101
+ excludes: "sub"
102
+ }
103
+
104
+ export const sub = {
105
+ parseDOM: [{tag: "sub"}],
106
+ toDOM() {
107
+ return ["sub", 0]
108
+ },
109
+ excludes: "sup"
110
+ }
111
+
112
+ export const code = {
113
+ parseDOM: [{tag: "code"}],
114
+ toDOM() {
115
+ return ["code", 0]
116
+ },
117
+ excludes: "strong em underline link sup sub"
118
+ }
@@ -0,0 +1,62 @@
1
+ function parseReferences(str) {
2
+ if (!str) {
3
+ return []
4
+ }
5
+ let references
6
+ try {
7
+ references = JSON.parse(str)
8
+ } catch (_error) {
9
+ return []
10
+ }
11
+ if (!Array.isArray(references)) {
12
+ return []
13
+ }
14
+ return references
15
+ .filter(
16
+ ref => ref.hasOwnProperty("id") // ensure there is an id.
17
+ )
18
+ .map(ref => {
19
+ const mRef = {id: ref.id}
20
+ if (ref.locator) {
21
+ mRef.locator = ref.locator
22
+ }
23
+ if (ref.prefix) {
24
+ mRef.prefix = ref.prefix
25
+ }
26
+ return mRef
27
+ })
28
+ }
29
+
30
+ export const citation = {
31
+ inline: true,
32
+ group: "inline",
33
+ attrs: {
34
+ format: {
35
+ default: "autocite" // "autocite" or "textcite"
36
+ },
37
+ references: {
38
+ default: [] // array of {id[, locator][, prefix]}
39
+ }
40
+ },
41
+ parseDOM: [
42
+ {
43
+ tag: "span.citation",
44
+ getAttrs(dom) {
45
+ return {
46
+ format: dom.dataset.format || "",
47
+ references: parseReferences(dom.dataset.references)
48
+ }
49
+ }
50
+ }
51
+ ],
52
+ toDOM(node) {
53
+ return [
54
+ "span",
55
+ {
56
+ class: "citation",
57
+ "data-format": node.attrs.format,
58
+ "data-references": JSON.stringify(node.attrs.references)
59
+ }
60
+ ]
61
+ }
62
+ }
@@ -0,0 +1,31 @@
1
+ export const equation = {
2
+ inline: true,
3
+ group: "inline",
4
+ attrs: {
5
+ equation: {
6
+ default: ""
7
+ }
8
+ },
9
+ parseDOM: [
10
+ {
11
+ tag: "span.equation",
12
+ getAttrs(dom) {
13
+ return {
14
+ equation: dom.dataset.equation
15
+ }
16
+ }
17
+ }
18
+ ],
19
+ toDOM(node) {
20
+ const dom = document.createElement("span")
21
+ dom.dataset.equation = node.attrs.equation
22
+ dom.classList.add("equation")
23
+ import("mathlive").then(MathLive => {
24
+ dom.innerHTML = MathLive.convertLatexToMarkup(node.attrs.equation, {
25
+ mathstyle: "textstyle"
26
+ })
27
+ })
28
+ dom.setAttribute("contenteditable", "false")
29
+ return dom
30
+ }
31
+ }
@@ -0,0 +1,190 @@
1
+ import {parseTracks} from "./track.js"
2
+
3
+ export function randomFigureId() {
4
+ return "F" + Math.round(Math.random() * 10000000) + 1
5
+ }
6
+
7
+ let imageDBBroken = false
8
+
9
+ export const figure = {
10
+ inline: false,
11
+ allowGapCursor: false,
12
+ selectable: true,
13
+ group: "block",
14
+ attrs: {
15
+ category: {default: "none"},
16
+ caption: {default: false},
17
+ id: {default: false},
18
+ track: {default: []},
19
+ aligned: {default: "center"},
20
+ width: {default: "100"}
21
+ },
22
+ content:
23
+ "figure_caption image|figure_caption figure_equation|image figure_caption|figure_equation figure_caption",
24
+ parseDOM: [
25
+ {
26
+ tag: "figure",
27
+ getAttrs(dom) {
28
+ return {
29
+ category: dom.dataset.category,
30
+ caption: !!dom.dataset.captionHidden,
31
+ id: dom.id,
32
+ track: parseTracks(dom.dataset.track),
33
+ aligned: dom.dataset.aligned,
34
+ width: dom.dataset.width,
35
+ diff: dom.dataset.diff
36
+ }
37
+ }
38
+ }
39
+ ],
40
+ toDOM(node) {
41
+ const attrs = {
42
+ id: node.attrs.id,
43
+ class: `aligned-${node.attrs.aligned} image-width-${node.attrs.width}`,
44
+ "data-aligned": node.attrs.aligned,
45
+ "data-width": node.attrs.width,
46
+ "data-category": node.attrs.category
47
+ }
48
+ if (!node.attrs.caption) {
49
+ attrs["data-caption-hidden"] = true
50
+ }
51
+ if (node.attrs.track?.length) {
52
+ attrs["data-track"] = JSON.stringify(node.attrs.track)
53
+ }
54
+ return ["figure", attrs, 0]
55
+ }
56
+ }
57
+
58
+ export const image = {
59
+ selectable: false,
60
+ draggable: false,
61
+ attrs: {
62
+ image: {default: false}
63
+ },
64
+ parseDOM: [
65
+ {
66
+ tag: "img",
67
+ getAttrs(dom) {
68
+ const image = Number.parseInt(dom.dataset.image)
69
+ return {
70
+ image: isNaN(image) ? false : image
71
+ }
72
+ }
73
+ }
74
+ ],
75
+ toDOM(node) {
76
+ const dom = document.createElement("img")
77
+ if (node.attrs.image !== false) {
78
+ dom.dataset.image = node.attrs.image
79
+ const imageDB = node.type.schema.cached.imageDB
80
+ if (imageDB) {
81
+ const imageEntry = imageDB.db[node.attrs.image]
82
+ if (imageEntry?.image) {
83
+ const isEncrypted =
84
+ imageEntry.file_type === "application/octet-stream"
85
+ if (isEncrypted) {
86
+ const editor = imageDB.mod?.editor
87
+ const key = editor?.e2ee?.key
88
+ if (key) {
89
+ import("../../editor/e2ee/encryptor.js").then(
90
+ ({E2EEEncryptor}) => {
91
+ E2EEEncryptor.decryptImageToUrl(
92
+ imageEntry.image,
93
+ key,
94
+ imageEntry.original_file_type ||
95
+ "image/png"
96
+ )
97
+ .then(url => {
98
+ dom.setAttribute("src", url)
99
+ dom.dataset.imageSrc = url
100
+ })
101
+ .catch(() => {
102
+ dom.setAttribute(
103
+ "src",
104
+ (typeof staticUrl !== "undefined" ? staticUrl("img/error.avif") : "/static/img/error.avif")
105
+ )
106
+ })
107
+ }
108
+ )
109
+ } else {
110
+ dom.setAttribute("src", (typeof staticUrl !== "undefined" ? staticUrl("img/error.avif") : "/static/img/error.avif"))
111
+ }
112
+ } else {
113
+ const imgSrc = imageEntry.image
114
+ dom.setAttribute("src", imgSrc)
115
+ dom.dataset.imageSrc = imgSrc
116
+ }
117
+ } else {
118
+ /* The image was not present in the imageDB -- possibly because a collaborator just added it.
119
+ Try to reload the imageDB, but only once. If the image cannot be found in the updated
120
+ imageDB, do not attempt at reloading the imageDB if an image cannot be
121
+ found. */
122
+ if (imageDBBroken) {
123
+ dom.setAttribute("src", (typeof staticUrl !== "undefined" ? staticUrl("img/error.avif") : "/static/img/error.avif"))
124
+ } else {
125
+ imageDB.getDB().then(() => {
126
+ const refreshedEntry = imageDB.db[node.attrs.image]
127
+ if (refreshedEntry?.image) {
128
+ dom.setAttribute("src", refreshedEntry.image)
129
+ dom.dataset.imageSrc = refreshedEntry.image
130
+ } else {
131
+ imageDBBroken = true
132
+ }
133
+ })
134
+ }
135
+ }
136
+ }
137
+ }
138
+ return dom
139
+ }
140
+ }
141
+
142
+ export const figure_equation = {
143
+ selectable: false,
144
+ draggable: false,
145
+ attrs: {
146
+ equation: {
147
+ default: false
148
+ }
149
+ },
150
+ parseDOM: [
151
+ {
152
+ tag: "div.figure-equation[data-equation]",
153
+ getAttrs(dom) {
154
+ return {
155
+ equation: dom.dataset.equation
156
+ }
157
+ }
158
+ }
159
+ ],
160
+ toDOM(node) {
161
+ const dom = document.createElement("div")
162
+ dom.dataset.equation = node.attrs.equation
163
+ dom.classList.add("figure-equation")
164
+ if (node.attrs.equation !== false) {
165
+ import("mathlive").then(MathLive => {
166
+ dom.innerHTML = MathLive.convertLatexToMarkup(
167
+ node.attrs.equation,
168
+ {
169
+ mathstyle: "displaystyle"
170
+ }
171
+ )
172
+ })
173
+ }
174
+ return dom
175
+ }
176
+ }
177
+
178
+ export const figure_caption = {
179
+ isolating: true,
180
+ defining: true,
181
+ content: "inline*",
182
+ parseDOM: [{tag: "figcaption span.text"}],
183
+ toDOM() {
184
+ return [
185
+ "figcaption",
186
+ ["span", {class: "label"}],
187
+ ["span", {class: "text"}, 0]
188
+ ]
189
+ }
190
+ }