@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
|
+
* Native `.fidusbook` importer.
|
|
3
|
+
*
|
|
4
|
+
* Uses `NativeImporter` from `@fiduswriter/document/importer/native` for each
|
|
5
|
+
* chapter and delegates creation of the book record to a `BookImporterBackend`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {NativeImporterBackend} from "@fiduswriter/document"
|
|
9
|
+
import {NativeImporter} from "@fiduswriter/document/importer/native"
|
|
10
|
+
import {addAlert} from "fwtoolkit"
|
|
11
|
+
|
|
12
|
+
import type {BookImporterBackend, Chapter, User} from "../../types.js"
|
|
13
|
+
import {readFidusBookFile} from "./reader.js"
|
|
14
|
+
|
|
15
|
+
export {FIDUSBOOK_VERSION} from "./reader.js"
|
|
16
|
+
export {readFidusBookFile, FidusBookReader} from "./reader.js"
|
|
17
|
+
|
|
18
|
+
export class NativeBookImporter {
|
|
19
|
+
file: Blob
|
|
20
|
+
user: User
|
|
21
|
+
path: string
|
|
22
|
+
nativeBackend: NativeImporterBackend
|
|
23
|
+
bookBackend: BookImporterBackend
|
|
24
|
+
|
|
25
|
+
ok: boolean
|
|
26
|
+
statusText: string
|
|
27
|
+
bookId: number | null
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
file: Blob,
|
|
31
|
+
user: User,
|
|
32
|
+
nativeBackend: NativeImporterBackend,
|
|
33
|
+
bookBackend: BookImporterBackend,
|
|
34
|
+
path = "/"
|
|
35
|
+
) {
|
|
36
|
+
this.file = file
|
|
37
|
+
this.user = user
|
|
38
|
+
this.path = path.endsWith("/") ? path : path + "/"
|
|
39
|
+
this.nativeBackend = nativeBackend
|
|
40
|
+
this.bookBackend = bookBackend
|
|
41
|
+
|
|
42
|
+
this.ok = false
|
|
43
|
+
this.statusText = ""
|
|
44
|
+
this.bookId = null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Entry point. Validates the file is a ZIP, then delegates to the reader.
|
|
49
|
+
*/
|
|
50
|
+
init(): Promise<NativeBookImporter> {
|
|
51
|
+
return new Promise(resolve => {
|
|
52
|
+
const reader = new FileReader()
|
|
53
|
+
reader.onloadend = () => {
|
|
54
|
+
if (
|
|
55
|
+
reader.result &&
|
|
56
|
+
(reader.result as string).length > 60 &&
|
|
57
|
+
(reader.result as string).substring(0, 2) === "PK"
|
|
58
|
+
) {
|
|
59
|
+
this.readZip().then(() => resolve(this))
|
|
60
|
+
} else {
|
|
61
|
+
this.statusText = gettext(
|
|
62
|
+
"The uploaded file does not appear to be a Fidusbook file."
|
|
63
|
+
)
|
|
64
|
+
resolve(this)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
reader.readAsText(this.file.slice(0, 64))
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async readZip(): Promise<void> {
|
|
72
|
+
let textFiles: Array<{filename: string; content: string}> = []
|
|
73
|
+
let binaryFiles: Array<{filename: string; content: Blob}> = []
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const JSZip = (await import("jszip")).default
|
|
77
|
+
const zipfs = await JSZip.loadAsync(this.file)
|
|
78
|
+
|
|
79
|
+
const filenames: string[] = []
|
|
80
|
+
zipfs.forEach(filename => filenames.push(filename))
|
|
81
|
+
|
|
82
|
+
await Promise.all(
|
|
83
|
+
filenames
|
|
84
|
+
.filter(f => !f.endsWith("/"))
|
|
85
|
+
.map(async filename => {
|
|
86
|
+
const isText =
|
|
87
|
+
filename.endsWith(".json") ||
|
|
88
|
+
filename === "filetype-version" ||
|
|
89
|
+
filename === "mimetype"
|
|
90
|
+
const content = await zipfs.files[filename].async(
|
|
91
|
+
isText ? "string" : "blob"
|
|
92
|
+
)
|
|
93
|
+
if (isText) {
|
|
94
|
+
textFiles.push({
|
|
95
|
+
filename,
|
|
96
|
+
content: content as string
|
|
97
|
+
})
|
|
98
|
+
} else {
|
|
99
|
+
binaryFiles.push({
|
|
100
|
+
filename,
|
|
101
|
+
content: content as Blob
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
)
|
|
106
|
+
} catch (error) {
|
|
107
|
+
this.statusText = gettext(
|
|
108
|
+
"The uploaded file does not appear to be a Fidusbook file."
|
|
109
|
+
)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return this.processFidusbookFile(textFiles, binaryFiles)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async processFidusbookFile(
|
|
117
|
+
textFiles: Array<{filename: string; content: string}>,
|
|
118
|
+
binaryFiles: Array<{filename: string; content: Blob}>
|
|
119
|
+
): Promise<void> {
|
|
120
|
+
const versionEntry = textFiles.find(
|
|
121
|
+
f => f.filename === "filetype-version"
|
|
122
|
+
)
|
|
123
|
+
const filetypeVersion = Number.parseFloat(versionEntry?.content || "")
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
filetypeVersion < 1.0 ||
|
|
127
|
+
filetypeVersion > 1.0
|
|
128
|
+
) {
|
|
129
|
+
this.statusText =
|
|
130
|
+
gettext(
|
|
131
|
+
"The Fidusbook file version is not supported by this server: "
|
|
132
|
+
) + (versionEntry?.content || "")
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const mimetypeEntry = textFiles.find(f => f.filename === "mimetype")
|
|
137
|
+
if (
|
|
138
|
+
mimetypeEntry &&
|
|
139
|
+
mimetypeEntry.content !== "application/fidusbook+zip"
|
|
140
|
+
) {
|
|
141
|
+
this.statusText = gettext(
|
|
142
|
+
"The uploaded file does not appear to be a Fidusbook file."
|
|
143
|
+
)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const bookData = JSON.parse(
|
|
148
|
+
textFiles.find(f => f.filename === "book.json")?.content || "{}"
|
|
149
|
+
) as Record<string, any>
|
|
150
|
+
|
|
151
|
+
const sortedChapters = [...(bookData.chapters as Array<{chapter_index: number; number: number; part?: string}>)].sort(
|
|
152
|
+
(a, b) => a.chapter_index - b.chapter_index
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const importedDocIds: Record<number, number> = {}
|
|
156
|
+
|
|
157
|
+
for (const chapter of sortedChapters) {
|
|
158
|
+
const ci = chapter.chapter_index
|
|
159
|
+
|
|
160
|
+
const docFile = textFiles.find(
|
|
161
|
+
f => f.filename === `chapters/${ci}/document.json`
|
|
162
|
+
)
|
|
163
|
+
const imagesFile = textFiles.find(
|
|
164
|
+
f => f.filename === `chapters/${ci}/images.json`
|
|
165
|
+
)
|
|
166
|
+
const bibFile = textFiles.find(
|
|
167
|
+
f => f.filename === `chapters/${ci}/bibliography.json`
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if (!docFile || !imagesFile || !bibFile) {
|
|
171
|
+
addAlert(
|
|
172
|
+
"error",
|
|
173
|
+
gettext("Fidusbook file is missing data for chapter ") +
|
|
174
|
+
(sortedChapters.indexOf(chapter) + 1)
|
|
175
|
+
)
|
|
176
|
+
throw new Error(`Missing chapter data for index ${ci}`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const docJson = JSON.parse(docFile.content) as Record<string, any>
|
|
180
|
+
const imagesJson = JSON.parse(imagesFile.content) as Record<string, any>
|
|
181
|
+
const bibJson = JSON.parse(bibFile.content) as Record<string, any>
|
|
182
|
+
|
|
183
|
+
const chapterPrefix = `chapters/${ci}/images/`
|
|
184
|
+
const chapterOtherFiles = binaryFiles
|
|
185
|
+
.filter(f => f.filename.startsWith(chapterPrefix))
|
|
186
|
+
.map(f => ({
|
|
187
|
+
filename: `images/${f.filename.slice(chapterPrefix.length)}`,
|
|
188
|
+
content: f.content
|
|
189
|
+
}))
|
|
190
|
+
|
|
191
|
+
const safeBookTitle = (bookData.title as string) || "Untitled"
|
|
192
|
+
const chapterPath = `${this.path}${safeBookTitle}/${(docJson.title as string) || "Untitled"}`
|
|
193
|
+
|
|
194
|
+
const importer = new NativeImporter(
|
|
195
|
+
docJson,
|
|
196
|
+
bibJson,
|
|
197
|
+
{db: imagesJson},
|
|
198
|
+
chapterOtherFiles,
|
|
199
|
+
this.user,
|
|
200
|
+
this.nativeBackend,
|
|
201
|
+
{
|
|
202
|
+
requestedPath: chapterPath
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
let doc
|
|
207
|
+
try {
|
|
208
|
+
;({doc} = await importer.init())
|
|
209
|
+
} catch (error) {
|
|
210
|
+
addAlert(
|
|
211
|
+
"error",
|
|
212
|
+
gettext("Could not import chapter ") +
|
|
213
|
+
((docJson.title as string) ||
|
|
214
|
+
sortedChapters.indexOf(chapter) + 1)
|
|
215
|
+
)
|
|
216
|
+
throw error
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
importedDocIds[ci] = doc.id as number
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const chapters: Chapter[] = sortedChapters.map(chapter => ({
|
|
223
|
+
text: importedDocIds[chapter.chapter_index],
|
|
224
|
+
number: chapter.number,
|
|
225
|
+
part: chapter.part || ""
|
|
226
|
+
}))
|
|
227
|
+
|
|
228
|
+
const book = await this.bookBackend.createBook(bookData as any, chapters, false)
|
|
229
|
+
this.bookId = book.id || null
|
|
230
|
+
this.ok = true
|
|
231
|
+
this.statusText = `"${bookData.title}" ${gettext("successfully imported.")}`
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure reader for `.fidusbook` archives.
|
|
3
|
+
*
|
|
4
|
+
* Reads the ZIP file, validates the mimetype/version, and returns the raw
|
|
5
|
+
* book metadata plus a `documentList` shaped like the one used by the
|
|
6
|
+
* exporters. No server interaction.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {DocumentListEntry} from "../../types.js"
|
|
10
|
+
|
|
11
|
+
export const FIDUSBOOK_VERSION = "1.0"
|
|
12
|
+
export const MIN_FIDUSBOOK_VERSION = 1.0
|
|
13
|
+
export const MAX_FIDUSBOOK_VERSION = 1.0
|
|
14
|
+
|
|
15
|
+
interface ArchiveFile {
|
|
16
|
+
filename: string
|
|
17
|
+
content: string | Blob
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FidusBookReaderResult {
|
|
21
|
+
book: Record<string, unknown>
|
|
22
|
+
documentList: DocumentListEntry[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Read a `.fidusbook` archive and return its contents.
|
|
27
|
+
*
|
|
28
|
+
* @param file - A File/Blob/ArrayBuffer containing the archive.
|
|
29
|
+
* @returns The parsed book and document list.
|
|
30
|
+
*/
|
|
31
|
+
export async function readFidusBookFile(
|
|
32
|
+
file: Blob | ArrayBuffer
|
|
33
|
+
): Promise<FidusBookReaderResult> {
|
|
34
|
+
const JSZip = (await import("jszip")).default
|
|
35
|
+
const zipfs = await JSZip.loadAsync(file)
|
|
36
|
+
|
|
37
|
+
const filenames: string[] = []
|
|
38
|
+
zipfs.forEach(filename => filenames.push(filename))
|
|
39
|
+
|
|
40
|
+
if (
|
|
41
|
+
!filenames.includes("book.json") ||
|
|
42
|
+
!filenames.includes("filetype-version")
|
|
43
|
+
) {
|
|
44
|
+
throw new Error("The file does not appear to be a Fidusbook file.")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const textFiles: ArchiveFile[] = []
|
|
48
|
+
const binaryFiles: ArchiveFile[] = []
|
|
49
|
+
|
|
50
|
+
await Promise.all(
|
|
51
|
+
filenames
|
|
52
|
+
.filter(f => !f.endsWith("/"))
|
|
53
|
+
.map(async filename => {
|
|
54
|
+
const isText =
|
|
55
|
+
filename.endsWith(".json") ||
|
|
56
|
+
filename === "filetype-version" ||
|
|
57
|
+
filename === "mimetype"
|
|
58
|
+
const content = await zipfs.files[filename].async(
|
|
59
|
+
isText ? "string" : "blob"
|
|
60
|
+
)
|
|
61
|
+
const entry = {filename, content}
|
|
62
|
+
if (isText) {
|
|
63
|
+
textFiles.push(entry as ArchiveFile)
|
|
64
|
+
} else {
|
|
65
|
+
binaryFiles.push(entry as ArchiveFile)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const versionEntry = textFiles.find(f => f.filename === "filetype-version")
|
|
71
|
+
const filetypeVersion = Number.parseFloat(versionEntry?.content as string)
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
filetypeVersion < MIN_FIDUSBOOK_VERSION ||
|
|
75
|
+
filetypeVersion > MAX_FIDUSBOOK_VERSION
|
|
76
|
+
) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`The Fidusbook file version is not supported by this reader: ${String(
|
|
79
|
+
versionEntry?.content
|
|
80
|
+
)}`
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const mimetypeEntry = textFiles.find(f => f.filename === "mimetype")
|
|
85
|
+
if (
|
|
86
|
+
mimetypeEntry &&
|
|
87
|
+
mimetypeEntry.content !== "application/fidusbook+zip"
|
|
88
|
+
) {
|
|
89
|
+
throw new Error("The file does not appear to be a Fidusbook file.")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const bookData = JSON.parse(
|
|
93
|
+
textFiles.find(f => f.filename === "book.json")?.content as string
|
|
94
|
+
) as Record<string, unknown>
|
|
95
|
+
|
|
96
|
+
const sortedChapters = [...(bookData.chapters as Array<{chapter_index: number}>)].sort(
|
|
97
|
+
(a, b) => a.chapter_index - b.chapter_index
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const documentList: DocumentListEntry[] = []
|
|
101
|
+
|
|
102
|
+
for (const chapter of sortedChapters) {
|
|
103
|
+
const ci = chapter.chapter_index
|
|
104
|
+
|
|
105
|
+
const docFile = textFiles.find(
|
|
106
|
+
f => f.filename === `chapters/${ci}/document.json`
|
|
107
|
+
)
|
|
108
|
+
const imagesFile = textFiles.find(
|
|
109
|
+
f => f.filename === `chapters/${ci}/images.json`
|
|
110
|
+
)
|
|
111
|
+
const bibFile = textFiles.find(
|
|
112
|
+
f => f.filename === `chapters/${ci}/bibliography.json`
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if (!docFile || !imagesFile || !bibFile) {
|
|
116
|
+
throw new Error(`Missing chapter data for index ${ci}`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const docJson = JSON.parse(docFile.content as string) as Record<string, unknown>
|
|
120
|
+
const imagesJson = JSON.parse(imagesFile.content as string) as Record<string, {image: string; file?: Blob}>
|
|
121
|
+
const bibJson = JSON.parse(bibFile.content as string) as Record<string, unknown>
|
|
122
|
+
|
|
123
|
+
const chapterPrefix = `chapters/${ci}/images/`
|
|
124
|
+
const chapterImages: Record<string, Blob> = {}
|
|
125
|
+
binaryFiles
|
|
126
|
+
.filter(f => f.filename.startsWith(chapterPrefix))
|
|
127
|
+
.forEach(f => {
|
|
128
|
+
chapterImages[
|
|
129
|
+
`images/${f.filename.slice(chapterPrefix.length)}`
|
|
130
|
+
] = f.content as Blob
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
Object.entries(imagesJson).forEach(([, image]) => {
|
|
134
|
+
if (chapterImages[image.image]) {
|
|
135
|
+
image.file = chapterImages[image.image]
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
documentList.push({
|
|
140
|
+
id: (docJson.id as number) || 0,
|
|
141
|
+
title: (docJson.title as string) || "",
|
|
142
|
+
path: (docJson.path as string) || "",
|
|
143
|
+
content: (docJson.content || {
|
|
144
|
+
type: "doc",
|
|
145
|
+
content: []
|
|
146
|
+
}) as DocumentListEntry["content"],
|
|
147
|
+
settings: (docJson.settings || {}) as DocumentListEntry["settings"],
|
|
148
|
+
comments: (docJson.comments || {}) as DocumentListEntry["comments"],
|
|
149
|
+
images: imagesJson as DocumentListEntry["images"],
|
|
150
|
+
bibliography: bibJson as DocumentListEntry["bibliography"],
|
|
151
|
+
rawContent: docJson.rawContent as DocumentListEntry["rawContent"],
|
|
152
|
+
e2ee: docJson.e2ee as boolean | undefined,
|
|
153
|
+
e2ee_salt: docJson.e2ee_salt as string | undefined,
|
|
154
|
+
e2ee_iterations: docJson.e2ee_iterations as number | undefined
|
|
155
|
+
} as DocumentListEntry)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {book: bookData, documentList}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export class FidusBookReader {
|
|
162
|
+
async read(file: Blob | ArrayBuffer): Promise<FidusBookReaderResult> {
|
|
163
|
+
return readFidusBookFile(file)
|
|
164
|
+
}
|
|
165
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {FIDUSBOOK_VERSION} from "./schema/index.js"
|
|
2
|
+
export type {
|
|
3
|
+
Book,
|
|
4
|
+
BookBaseMetadata,
|
|
5
|
+
BookCoverImage,
|
|
6
|
+
BookExporterOptions,
|
|
7
|
+
BookImporterBackend,
|
|
8
|
+
BookMetadata,
|
|
9
|
+
BookSettings,
|
|
10
|
+
BookStyle,
|
|
11
|
+
BookStyles,
|
|
12
|
+
BookTexTemplateParams,
|
|
13
|
+
BitsTemplateParams,
|
|
14
|
+
Chapter,
|
|
15
|
+
DocumentListEntry,
|
|
16
|
+
E2EEStrategy,
|
|
17
|
+
ChapterLoader,
|
|
18
|
+
FidusBookArchive
|
|
19
|
+
} from "./types.js"
|
package/src/modules.d.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the @fiduswriter/books-document package.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {Schema} from "prosemirror-model"
|
|
6
|
+
import type {
|
|
7
|
+
BibDB,
|
|
8
|
+
CSL,
|
|
9
|
+
ExportDoc,
|
|
10
|
+
ExportMetadata,
|
|
11
|
+
ImageDB,
|
|
12
|
+
User
|
|
13
|
+
} from "@fiduswriter/document"
|
|
14
|
+
|
|
15
|
+
export type {CSL, User} from "@fiduswriter/document"
|
|
16
|
+
|
|
17
|
+
/** A single chapter entry inside a book. */
|
|
18
|
+
export interface Chapter {
|
|
19
|
+
/** Document ID of the chapter. */
|
|
20
|
+
text: number
|
|
21
|
+
/** Display number / order of the chapter. */
|
|
22
|
+
number: number
|
|
23
|
+
/** Optional part title. */
|
|
24
|
+
part?: string
|
|
25
|
+
/** Archive storage index (used by the native importer). */
|
|
26
|
+
chapter_index?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Book metadata block. */
|
|
30
|
+
export interface BookMetadata {
|
|
31
|
+
subtitle?: string
|
|
32
|
+
author?: string
|
|
33
|
+
version?: string
|
|
34
|
+
publisher?: string
|
|
35
|
+
copyright?: string
|
|
36
|
+
description?: string
|
|
37
|
+
isbn?: string
|
|
38
|
+
publication_date?: string
|
|
39
|
+
series_title?: string
|
|
40
|
+
series_position?: string
|
|
41
|
+
keywords?: string
|
|
42
|
+
[key: string]: unknown
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Book settings block. */
|
|
46
|
+
export interface BookSettings {
|
|
47
|
+
language: string
|
|
48
|
+
book_style?: string
|
|
49
|
+
papersize?: string
|
|
50
|
+
[key: string]: unknown
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A book record. */
|
|
54
|
+
export interface Book {
|
|
55
|
+
id?: number
|
|
56
|
+
title: string
|
|
57
|
+
path?: string
|
|
58
|
+
metadata: BookMetadata
|
|
59
|
+
settings: BookSettings
|
|
60
|
+
chapters: Chapter[]
|
|
61
|
+
cover_image?: number
|
|
62
|
+
cover_image_data?: BookCoverImage
|
|
63
|
+
updated?: number
|
|
64
|
+
added?: number
|
|
65
|
+
docx_template?: string
|
|
66
|
+
odt_template?: string
|
|
67
|
+
rights?: string
|
|
68
|
+
[key: string]: unknown
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Cover image description stored in a book. */
|
|
72
|
+
export interface BookCoverImage {
|
|
73
|
+
title?: string
|
|
74
|
+
checksum?: string
|
|
75
|
+
file_type?: string
|
|
76
|
+
image?: string
|
|
77
|
+
[key: string]: unknown
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A single book style with its associated media files. */
|
|
81
|
+
export interface BookStyle {
|
|
82
|
+
slug: string
|
|
83
|
+
contents: string
|
|
84
|
+
bookstylefile_set: Array<[string, string]>
|
|
85
|
+
[key: string]: unknown
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type BookStyles = BookStyle[]
|
|
89
|
+
|
|
90
|
+
/** A document entry as held in the book's document list. */
|
|
91
|
+
export interface DocumentListEntry extends ExportDoc {
|
|
92
|
+
/** Raw ProseMirror content (used by DOCX/ODT exporters). */
|
|
93
|
+
rawContent?: Record<string, unknown>
|
|
94
|
+
/** Whether the document is end-to-end encrypted. */
|
|
95
|
+
e2ee?: boolean
|
|
96
|
+
/** Base64-encoded salt for E2EE key derivation. */
|
|
97
|
+
e2ee_salt?: string
|
|
98
|
+
/** PBKDF2 iterations for E2EE key derivation. */
|
|
99
|
+
e2ee_iterations?: number
|
|
100
|
+
images?: ImageDB["db"]
|
|
101
|
+
bibliography?: BibDB["db"]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Options shared by book exporters. */
|
|
105
|
+
export interface BookExporterOptions {
|
|
106
|
+
/** ProseMirror schema used for E2EE decryption. */
|
|
107
|
+
schema?: Schema
|
|
108
|
+
/** CSL engine provider. */
|
|
109
|
+
csl?: CSL
|
|
110
|
+
/** Book styles data. */
|
|
111
|
+
bookStyles?: BookStyles
|
|
112
|
+
/** Logged-in user. */
|
|
113
|
+
user?: User
|
|
114
|
+
/** Document list entries for the book's chapters. */
|
|
115
|
+
documentList?: DocumentListEntry[]
|
|
116
|
+
/** Last-modified timestamp (seconds since epoch). */
|
|
117
|
+
updated?: number
|
|
118
|
+
/** Whether to produce multiple HTML files (HTML exporter only). */
|
|
119
|
+
multiDoc?: boolean
|
|
120
|
+
/** Use relative URLs for linked assets (HTML exporter only). */
|
|
121
|
+
relativeUrls?: boolean
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Strategy for loading missing chapter data before export. */
|
|
125
|
+
export interface ChapterLoader {
|
|
126
|
+
loadChapters(
|
|
127
|
+
chapterIds: number[],
|
|
128
|
+
documentList: DocumentListEntry[],
|
|
129
|
+
schema?: Schema,
|
|
130
|
+
rawContent?: boolean
|
|
131
|
+
): Promise<void>
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Strategy for handling end-to-end encrypted chapters. */
|
|
135
|
+
export interface E2EEStrategy {
|
|
136
|
+
ensurePassphraseUnlocked(): Promise<boolean>
|
|
137
|
+
getDocumentPassword(docId: number): Promise<string | null>
|
|
138
|
+
resolvePasswordToKey(
|
|
139
|
+
password: string,
|
|
140
|
+
salt: Uint8Array,
|
|
141
|
+
iterations: number
|
|
142
|
+
): Promise<CryptoKey>
|
|
143
|
+
decryptObject(encrypted: string, key: CryptoKey): Promise<unknown>
|
|
144
|
+
decryptImageToUrl(encrypted: string, key: CryptoKey): Promise<string>
|
|
145
|
+
storePasswordInSession?(docId: number, password: string): void
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Backend used by NativeBookImporter to create the book record. */
|
|
149
|
+
export interface BookImporterBackend {
|
|
150
|
+
/**
|
|
151
|
+
* Create a book record from the imported book data and chapter documents.
|
|
152
|
+
*
|
|
153
|
+
* @param bookData - The raw book object from book.json.
|
|
154
|
+
* @param chapters - The chapters with their final document IDs.
|
|
155
|
+
* @param coverImageId - ID of the imported cover image, or false.
|
|
156
|
+
* @returns A promise resolving to the created book record.
|
|
157
|
+
*/
|
|
158
|
+
createBook(
|
|
159
|
+
bookData: Record<string, unknown>,
|
|
160
|
+
chapters: Chapter[],
|
|
161
|
+
coverImageId: number | false
|
|
162
|
+
): Promise<Book>
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Raw archive content returned by FidusBookReader. */
|
|
166
|
+
export interface FidusBookArchive {
|
|
167
|
+
book: Record<string, unknown>
|
|
168
|
+
documentList: DocumentListEntry[]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Parameters passed to the book.tex template. */
|
|
172
|
+
export interface BookTexTemplateParams {
|
|
173
|
+
book: Book
|
|
174
|
+
preamble: string
|
|
175
|
+
epilogue: string
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Parameters passed to the BITS book template. */
|
|
179
|
+
export interface BitsTemplateParams {
|
|
180
|
+
front: string
|
|
181
|
+
body: string
|
|
182
|
+
back: string
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Base metadata used by DOCX/ODT book exporters. */
|
|
186
|
+
export interface BookBaseMetadata extends ExportMetadata {
|
|
187
|
+
language: string
|
|
188
|
+
}
|