@fiduswriter/books-document 0.1.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 (125) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +24 -0
  3. package/dist/exporter/bits/index.d.ts +42 -0
  4. package/dist/exporter/bits/index.d.ts.map +1 -0
  5. package/dist/exporter/bits/index.js +98 -0
  6. package/dist/exporter/bits/index.js.map +1 -0
  7. package/dist/exporter/bits/templates.d.ts +7 -0
  8. package/dist/exporter/bits/templates.d.ts.map +1 -0
  9. package/dist/exporter/bits/templates.js +78 -0
  10. package/dist/exporter/bits/templates.js.map +1 -0
  11. package/dist/exporter/docx/index.d.ts +27 -0
  12. package/dist/exporter/docx/index.d.ts.map +1 -0
  13. package/dist/exporter/docx/index.js +129 -0
  14. package/dist/exporter/docx/index.js.map +1 -0
  15. package/dist/exporter/docx/render.d.ts +14 -0
  16. package/dist/exporter/docx/render.d.ts.map +1 -0
  17. package/dist/exporter/docx/render.js +116 -0
  18. package/dist/exporter/docx/render.js.map +1 -0
  19. package/dist/exporter/epub/index.d.ts +65 -0
  20. package/dist/exporter/epub/index.d.ts.map +1 -0
  21. package/dist/exporter/epub/index.js +268 -0
  22. package/dist/exporter/epub/index.js.map +1 -0
  23. package/dist/exporter/epub/templates.d.ts +65 -0
  24. package/dist/exporter/epub/templates.d.ts.map +1 -0
  25. package/dist/exporter/epub/templates.js +223 -0
  26. package/dist/exporter/epub/templates.js.map +1 -0
  27. package/dist/exporter/epub/tools.d.ts +5 -0
  28. package/dist/exporter/epub/tools.d.ts.map +1 -0
  29. package/dist/exporter/epub/tools.js +59 -0
  30. package/dist/exporter/epub/tools.js.map +1 -0
  31. package/dist/exporter/html/index.d.ts +67 -0
  32. package/dist/exporter/html/index.d.ts.map +1 -0
  33. package/dist/exporter/html/index.js +300 -0
  34. package/dist/exporter/html/index.js.map +1 -0
  35. package/dist/exporter/html/templates.d.ts +40 -0
  36. package/dist/exporter/html/templates.d.ts.map +1 -0
  37. package/dist/exporter/html/templates.js +226 -0
  38. package/dist/exporter/html/templates.js.map +1 -0
  39. package/dist/exporter/html/tools.d.ts +2 -0
  40. package/dist/exporter/html/tools.d.ts.map +1 -0
  41. package/dist/exporter/html/tools.js +23 -0
  42. package/dist/exporter/html/tools.js.map +1 -0
  43. package/dist/exporter/latex/index.d.ts +32 -0
  44. package/dist/exporter/latex/index.d.ts.map +1 -0
  45. package/dist/exporter/latex/index.js +102 -0
  46. package/dist/exporter/latex/index.js.map +1 -0
  47. package/dist/exporter/latex/templates.d.ts +4 -0
  48. package/dist/exporter/latex/templates.d.ts.map +1 -0
  49. package/dist/exporter/latex/templates.js +26 -0
  50. package/dist/exporter/latex/templates.js.map +1 -0
  51. package/dist/exporter/native/index.d.ts +20 -0
  52. package/dist/exporter/native/index.d.ts.map +1 -0
  53. package/dist/exporter/native/index.js +109 -0
  54. package/dist/exporter/native/index.js.map +1 -0
  55. package/dist/exporter/odt/index.d.ts +27 -0
  56. package/dist/exporter/odt/index.d.ts.map +1 -0
  57. package/dist/exporter/odt/index.js +123 -0
  58. package/dist/exporter/odt/index.js.map +1 -0
  59. package/dist/exporter/odt/render.d.ts +15 -0
  60. package/dist/exporter/odt/render.d.ts.map +1 -0
  61. package/dist/exporter/odt/render.js +119 -0
  62. package/dist/exporter/odt/render.js.map +1 -0
  63. package/dist/exporter/print/index.d.ts +24 -0
  64. package/dist/exporter/print/index.d.ts.map +1 -0
  65. package/dist/exporter/print/index.js +60 -0
  66. package/dist/exporter/print/index.js.map +1 -0
  67. package/dist/exporter/print/templates.d.ts +2 -0
  68. package/dist/exporter/print/templates.d.ts.map +1 -0
  69. package/dist/exporter/print/templates.js +5 -0
  70. package/dist/exporter/print/templates.js.map +1 -0
  71. package/dist/exporter/tools.d.ts +29 -0
  72. package/dist/exporter/tools.d.ts.map +1 -0
  73. package/dist/exporter/tools.js +151 -0
  74. package/dist/exporter/tools.js.map +1 -0
  75. package/dist/i18n.d.ts +7 -0
  76. package/dist/i18n.d.ts.map +1 -0
  77. package/dist/i18n.js +918 -0
  78. package/dist/i18n.js.map +1 -0
  79. package/dist/importer/native/index.d.ts +34 -0
  80. package/dist/importer/native/index.d.ts.map +1 -0
  81. package/dist/importer/native/index.js +152 -0
  82. package/dist/importer/native/index.js.map +1 -0
  83. package/dist/importer/native/reader.d.ts +26 -0
  84. package/dist/importer/native/reader.d.ts.map +1 -0
  85. package/dist/importer/native/reader.js +105 -0
  86. package/dist/importer/native/reader.js.map +1 -0
  87. package/dist/index.d.ts +3 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +2 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/schema/index.d.ts +4 -0
  92. package/dist/schema/index.d.ts.map +1 -0
  93. package/dist/schema/index.js +4 -0
  94. package/dist/schema/index.js.map +1 -0
  95. package/dist/types.d.ts +151 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +5 -0
  98. package/dist/types.js.map +1 -0
  99. package/package.json +101 -0
  100. package/src/exporter/bits/index.ts +163 -0
  101. package/src/exporter/bits/templates.ts +107 -0
  102. package/src/exporter/docx/index.ts +239 -0
  103. package/src/exporter/docx/render.ts +142 -0
  104. package/src/exporter/epub/index.ts +426 -0
  105. package/src/exporter/epub/templates.ts +401 -0
  106. package/src/exporter/epub/tools.ts +68 -0
  107. package/src/exporter/html/index.ts +443 -0
  108. package/src/exporter/html/templates.ts +363 -0
  109. package/src/exporter/html/tools.ts +26 -0
  110. package/src/exporter/latex/index.ts +161 -0
  111. package/src/exporter/latex/templates.ts +35 -0
  112. package/src/exporter/native/index.ts +187 -0
  113. package/src/exporter/odt/index.ts +211 -0
  114. package/src/exporter/odt/render.ts +146 -0
  115. package/src/exporter/print/index.ts +92 -0
  116. package/src/exporter/print/templates.ts +8 -0
  117. package/src/exporter/tools.ts +233 -0
  118. package/src/global.d.ts +2 -0
  119. package/src/i18n.ts +936 -0
  120. package/src/importer/native/index.ts +233 -0
  121. package/src/importer/native/reader.ts +165 -0
  122. package/src/index.ts +19 -0
  123. package/src/modules.d.ts +11 -0
  124. package/src/schema/index.ts +4 -0
  125. package/src/types.ts +188 -0
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Shared tools for book exporters.
3
+ *
4
+ * `getMissingChapterData` is fully dependency-injected: it no longer imports
5
+ * browser/server-specific helpers from the Fidus Writer core. Callers supply
6
+ * a `ChapterLoader` to fetch missing chapter data and an `E2EEStrategy` to
7
+ * decrypt end-to-end encrypted chapters.
8
+ */
9
+
10
+ import type {Schema} from "prosemirror-model"
11
+ import {getSettings} from "@fiduswriter/document/schema/convert"
12
+ import {acceptAllNoInsertions} from "@fiduswriter/document/transform"
13
+ import {addAlert} from "fwtoolkit"
14
+
15
+ import type {
16
+ ChapterLoader,
17
+ DocumentListEntry,
18
+ E2EEStrategy,
19
+ Book
20
+ } from "../types.js"
21
+
22
+ /** Default no-op chapter loader. */
23
+ export const noopChapterLoader: ChapterLoader = {
24
+ loadChapters: async () => {
25
+ // noop
26
+ }
27
+ }
28
+
29
+ /** Default no-op E2EE strategy. */
30
+ export const noopE2EEStrategy: E2EEStrategy = {
31
+ ensurePassphraseUnlocked: async () => false,
32
+ getDocumentPassword: async () => null,
33
+ resolvePasswordToKey: async () => {
34
+ throw new Error("No E2EE strategy provided")
35
+ },
36
+ decryptObject: async () => {
37
+ throw new Error("No E2EE strategy provided")
38
+ },
39
+ decryptImageToUrl: async () => {
40
+ throw new Error("No E2EE strategy provided")
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Ensure all chapter data required for export is present.
46
+ *
47
+ * @param book - The book being exported.
48
+ * @param documentList - Document entries for the book's chapters.
49
+ * @param schema - ProseMirror schema used when parsing decrypted content.
50
+ * @param options - Optional loader and E2EE strategy.
51
+ * @returns Promise that resolves when data is ready.
52
+ */
53
+ export async function getMissingChapterData(
54
+ book: Book,
55
+ documentList: DocumentListEntry[],
56
+ schema: Schema,
57
+ options: {
58
+ rawContent?: boolean
59
+ loader?: ChapterLoader
60
+ e2ee?: E2EEStrategy
61
+ } = {}
62
+ ): Promise<void> {
63
+ const {rawContent = false, loader = noopChapterLoader, e2ee = noopE2EEStrategy} =
64
+ options
65
+
66
+ const bookDocuments = book.chapters.map(chapter =>
67
+ documentList.find(doc => doc.id === chapter.text)
68
+ )
69
+
70
+ if (bookDocuments.some(doc => doc === undefined)) {
71
+ addAlert(
72
+ "error",
73
+ gettext(
74
+ "Cannot produce book as you lack access rights to its chapters."
75
+ )
76
+ )
77
+ throw new Error(
78
+ "Cannot produce book as you lack access rights to its chapters."
79
+ )
80
+ }
81
+
82
+ const docIds = book.chapters.map(chapter => chapter.text)
83
+ await loader.loadChapters(docIds, documentList, schema, rawContent)
84
+ await decryptE2EEChapters(book, documentList, schema, rawContent, e2ee)
85
+ }
86
+
87
+ /**
88
+ * Decrypt all E2EE chapters in the book whose content is still an encrypted
89
+ * string.
90
+ */
91
+ async function decryptE2EEChapters(
92
+ book: Book,
93
+ documentList: DocumentListEntry[],
94
+ schema: Schema,
95
+ rawContent: boolean,
96
+ e2ee: E2EEStrategy
97
+ ): Promise<void> {
98
+ const e2eeChapters = book.chapters.filter(chapter => {
99
+ const doc = documentList.find(doc => doc.id === chapter.text)
100
+ return doc && doc.e2ee && typeof doc.content === "string"
101
+ })
102
+
103
+ if (!e2eeChapters.length) {
104
+ return
105
+ }
106
+
107
+ const unlocked = await e2ee.ensurePassphraseUnlocked()
108
+ if (!unlocked) {
109
+ addAlert(
110
+ "error",
111
+ gettext(
112
+ "A personal passphrase is required to work with books that contain encrypted chapters. Please set up or unlock your personal passphrase in your profile settings."
113
+ )
114
+ )
115
+ throw new Error("Passphrase required for encrypted chapters")
116
+ }
117
+
118
+ await Promise.all(
119
+ e2eeChapters.map(async chapter => {
120
+ const doc = documentList.find(d => d.id === chapter.text)
121
+ if (!doc) {
122
+ return
123
+ }
124
+ const chapterLabel = doc.title
125
+ ? `"${doc.title}"`
126
+ : gettext("Untitled")
127
+
128
+ const password = await e2ee.getDocumentPassword(Number(doc.id))
129
+ if (!password) {
130
+ addAlert(
131
+ "error",
132
+ `${gettext("No encryption key found for chapter:")} ${chapterLabel}. ${gettext("The key may not have been shared with you.")}`
133
+ )
134
+ throw new Error(`No encryption key for document ${String(doc.id)}`)
135
+ }
136
+
137
+ if (!doc.e2ee_salt) {
138
+ // No salt means the document has no encrypted snapshot yet.
139
+ return
140
+ }
141
+
142
+ const salt = base64ToUint8Array(doc.e2ee_salt)
143
+ const iterations = doc.e2ee_iterations || 600000
144
+
145
+ const key = await e2ee.resolvePasswordToKey(password, salt, iterations)
146
+
147
+ try {
148
+ const decryptedContent = (await e2ee.decryptObject(
149
+ doc.content as unknown as string,
150
+ key
151
+ )) as Record<string, unknown>
152
+
153
+ // Update the plaintext title.
154
+ const titleNode = (decryptedContent.content as Record<string, unknown>[] | undefined)?.[0]
155
+ const titleContent = (titleNode?.content as Record<string, unknown>[] | undefined) || []
156
+ let title = ""
157
+ titleContent.forEach(child => {
158
+ const marks = (child.marks as Record<string, unknown>[] | undefined) || []
159
+ if (!marks.some(m => (m.type as string) === "deletion")) {
160
+ title += (child.text as string) || ""
161
+ }
162
+ })
163
+ if (title) {
164
+ doc.title = title.substring(0, 255)
165
+ if (typeof sessionStorage !== "undefined") {
166
+ sessionStorage.setItem(`e2ee_title_${String(doc.id)}`, doc.title)
167
+ }
168
+ }
169
+
170
+ // Parse ProseMirror content.
171
+ if (rawContent) {
172
+ doc.rawContent = JSON.parse(
173
+ JSON.stringify(schema.nodeFromJSON(decryptedContent as Record<string, unknown>).toJSON())
174
+ )
175
+ }
176
+ doc.content = acceptAllNoInsertions(
177
+ schema.nodeFromJSON(decryptedContent as Record<string, unknown>)
178
+ ).toJSON()
179
+ doc.settings = getSettings(doc.content)
180
+
181
+ // Decrypt encrypted images.
182
+ const encryptedImageEntries = Object.entries(doc.images || {}).filter(
183
+ ([, entry]) =>
184
+ entry.file_type === "application/octet-stream" &&
185
+ typeof entry.image === "string"
186
+ )
187
+
188
+ if (!encryptedImageEntries.length) {
189
+ return
190
+ }
191
+
192
+ await Promise.all(
193
+ encryptedImageEntries.map(async ([id, entry]) => {
194
+ try {
195
+ const blobUrl = await e2ee.decryptImageToUrl(
196
+ entry.image as string,
197
+ key
198
+ )
199
+ doc.images![id] = {
200
+ ...entry,
201
+ image: blobUrl,
202
+ file_type: "image/png"
203
+ }
204
+ } catch {
205
+ delete doc.images![id]
206
+ }
207
+ })
208
+ )
209
+ } catch (err) {
210
+ if (
211
+ err instanceof Error &&
212
+ err.message.startsWith("No encryption key")
213
+ ) {
214
+ throw err
215
+ }
216
+ addAlert(
217
+ "error",
218
+ `${gettext("Could not decrypt chapter:")} ${chapterLabel}. ${gettext("The document may have been re-encrypted with a different password.")}`
219
+ )
220
+ throw err
221
+ }
222
+ })
223
+ )
224
+ }
225
+
226
+ function base64ToUint8Array(base64: string): Uint8Array {
227
+ const binary = atob(base64)
228
+ const bytes = new Uint8Array(binary.length)
229
+ for (let i = 0; i < binary.length; i++) {
230
+ bytes[i] = binary.charCodeAt(i)
231
+ }
232
+ return bytes
233
+ }
@@ -0,0 +1,2 @@
1
+ declare function gettext(text: string): string
2
+ declare function interpolate(fmt: string, ...args: unknown[]): string