@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.
- package/LICENSE +661 -0
- package/README.md +24 -0
- package/dist/exporter/bits/index.d.ts +42 -0
- package/dist/exporter/bits/index.d.ts.map +1 -0
- package/dist/exporter/bits/index.js +98 -0
- package/dist/exporter/bits/index.js.map +1 -0
- package/dist/exporter/bits/templates.d.ts +7 -0
- package/dist/exporter/bits/templates.d.ts.map +1 -0
- package/dist/exporter/bits/templates.js +78 -0
- package/dist/exporter/bits/templates.js.map +1 -0
- package/dist/exporter/docx/index.d.ts +27 -0
- package/dist/exporter/docx/index.d.ts.map +1 -0
- package/dist/exporter/docx/index.js +129 -0
- package/dist/exporter/docx/index.js.map +1 -0
- package/dist/exporter/docx/render.d.ts +14 -0
- package/dist/exporter/docx/render.d.ts.map +1 -0
- package/dist/exporter/docx/render.js +116 -0
- package/dist/exporter/docx/render.js.map +1 -0
- package/dist/exporter/epub/index.d.ts +65 -0
- package/dist/exporter/epub/index.d.ts.map +1 -0
- package/dist/exporter/epub/index.js +268 -0
- package/dist/exporter/epub/index.js.map +1 -0
- package/dist/exporter/epub/templates.d.ts +65 -0
- package/dist/exporter/epub/templates.d.ts.map +1 -0
- package/dist/exporter/epub/templates.js +223 -0
- package/dist/exporter/epub/templates.js.map +1 -0
- package/dist/exporter/epub/tools.d.ts +5 -0
- package/dist/exporter/epub/tools.d.ts.map +1 -0
- package/dist/exporter/epub/tools.js +59 -0
- package/dist/exporter/epub/tools.js.map +1 -0
- package/dist/exporter/html/index.d.ts +67 -0
- package/dist/exporter/html/index.d.ts.map +1 -0
- package/dist/exporter/html/index.js +300 -0
- package/dist/exporter/html/index.js.map +1 -0
- package/dist/exporter/html/templates.d.ts +40 -0
- package/dist/exporter/html/templates.d.ts.map +1 -0
- package/dist/exporter/html/templates.js +226 -0
- package/dist/exporter/html/templates.js.map +1 -0
- package/dist/exporter/html/tools.d.ts +2 -0
- package/dist/exporter/html/tools.d.ts.map +1 -0
- package/dist/exporter/html/tools.js +23 -0
- package/dist/exporter/html/tools.js.map +1 -0
- package/dist/exporter/latex/index.d.ts +32 -0
- package/dist/exporter/latex/index.d.ts.map +1 -0
- package/dist/exporter/latex/index.js +102 -0
- package/dist/exporter/latex/index.js.map +1 -0
- package/dist/exporter/latex/templates.d.ts +4 -0
- package/dist/exporter/latex/templates.d.ts.map +1 -0
- package/dist/exporter/latex/templates.js +26 -0
- package/dist/exporter/latex/templates.js.map +1 -0
- package/dist/exporter/native/index.d.ts +20 -0
- package/dist/exporter/native/index.d.ts.map +1 -0
- package/dist/exporter/native/index.js +109 -0
- package/dist/exporter/native/index.js.map +1 -0
- package/dist/exporter/odt/index.d.ts +27 -0
- package/dist/exporter/odt/index.d.ts.map +1 -0
- package/dist/exporter/odt/index.js +123 -0
- package/dist/exporter/odt/index.js.map +1 -0
- package/dist/exporter/odt/render.d.ts +15 -0
- package/dist/exporter/odt/render.d.ts.map +1 -0
- package/dist/exporter/odt/render.js +119 -0
- package/dist/exporter/odt/render.js.map +1 -0
- package/dist/exporter/print/index.d.ts +24 -0
- package/dist/exporter/print/index.d.ts.map +1 -0
- package/dist/exporter/print/index.js +60 -0
- package/dist/exporter/print/index.js.map +1 -0
- package/dist/exporter/print/templates.d.ts +2 -0
- package/dist/exporter/print/templates.d.ts.map +1 -0
- package/dist/exporter/print/templates.js +5 -0
- package/dist/exporter/print/templates.js.map +1 -0
- package/dist/exporter/tools.d.ts +29 -0
- package/dist/exporter/tools.d.ts.map +1 -0
- package/dist/exporter/tools.js +151 -0
- package/dist/exporter/tools.js.map +1 -0
- package/dist/i18n.d.ts +7 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +918 -0
- package/dist/i18n.js.map +1 -0
- package/dist/importer/native/index.d.ts +34 -0
- package/dist/importer/native/index.d.ts.map +1 -0
- package/dist/importer/native/index.js +152 -0
- package/dist/importer/native/index.js.map +1 -0
- package/dist/importer/native/reader.d.ts +26 -0
- package/dist/importer/native/reader.d.ts.map +1 -0
- package/dist/importer/native/reader.js +105 -0
- package/dist/importer/native/reader.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/types.d.ts +151 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +101 -0
- package/src/exporter/bits/index.ts +163 -0
- package/src/exporter/bits/templates.ts +107 -0
- package/src/exporter/docx/index.ts +239 -0
- package/src/exporter/docx/render.ts +142 -0
- package/src/exporter/epub/index.ts +426 -0
- package/src/exporter/epub/templates.ts +401 -0
- package/src/exporter/epub/tools.ts +68 -0
- package/src/exporter/html/index.ts +443 -0
- package/src/exporter/html/templates.ts +363 -0
- package/src/exporter/html/tools.ts +26 -0
- package/src/exporter/latex/index.ts +161 -0
- package/src/exporter/latex/templates.ts +35 -0
- package/src/exporter/native/index.ts +187 -0
- package/src/exporter/odt/index.ts +211 -0
- package/src/exporter/odt/render.ts +146 -0
- package/src/exporter/print/index.ts +92 -0
- package/src/exporter/print/templates.ts +8 -0
- package/src/exporter/tools.ts +233 -0
- package/src/global.d.ts +2 -0
- package/src/i18n.ts +936 -0
- package/src/importer/native/index.ts +233 -0
- package/src/importer/native/reader.ts +165 -0
- package/src/index.ts +19 -0
- package/src/modules.d.ts +11 -0
- package/src/schema/index.ts +4 -0
- 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
|
+
}
|
package/src/global.d.ts
ADDED