@fiduswriter/editor 0.1.67 → 0.1.68
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/README.md +141 -5
- package/dist/clipboard/copy/serializers.js +1 -1
- package/dist/clipboard/copy/serializers.js.map +1 -1
- package/dist/comments/store.d.ts.map +1 -1
- package/dist/comments/store.js +1 -2
- package/dist/comments/store.js.map +1 -1
- package/dist/dialogs/contributor.d.ts.map +1 -1
- package/dist/dialogs/contributor.js +2 -2
- package/dist/dialogs/contributor.js.map +1 -1
- package/dist/document_template/exporter.js +2 -2
- package/dist/document_template/exporter.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/editor/index.d.ts +1 -1
- package/dist/plugins/editor/index.d.ts.map +1 -1
- package/dist/plugins/editor/index.js.map +1 -1
- package/dist/static_app.d.ts +72 -0
- package/dist/static_app.d.ts.map +1 -0
- package/dist/static_app.js +234 -0
- package/dist/static_app.js.map +1 -0
- package/dist/static_editor.d.ts +45 -0
- package/dist/static_editor.d.ts.map +1 -0
- package/dist/static_editor.js +118 -0
- package/dist/static_editor.js.map +1 -0
- package/package.json +3 -3
- package/src/clipboard/copy/serializers.ts +1 -1
- package/src/comments/store.ts +1 -2
- package/src/dialogs/contributor.ts +2 -3
- package/src/document_template/exporter.ts +2 -2
- package/src/index.ts +9 -0
- package/src/plugins/editor/index.ts +1 -1
- package/src/static_app.ts +360 -0
- package/src/static_editor.ts +186 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import type {CSL, EditorImageDB} from "./types.js"
|
|
2
|
+
import {BibliographyDB} from "@fiduswriter/bibliography-manager/database"
|
|
3
|
+
import type {BibliographyApi} from "@fiduswriter/bibliography-manager"
|
|
4
|
+
import {ImageDB} from "@fiduswriter/image-manager/database"
|
|
5
|
+
import type {ImageApi} from "@fiduswriter/image-manager"
|
|
6
|
+
import type {Image, SaveImageResponse} from "@fiduswriter/image-manager/types"
|
|
7
|
+
import {FW_DOCUMENT_VERSION} from "@fiduswriter/document/schema"
|
|
8
|
+
import {extractTemplate} from "@fiduswriter/document/importer/native/extract_template"
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
EditorApp,
|
|
12
|
+
EditorContactsApi,
|
|
13
|
+
EditorDocumentApi,
|
|
14
|
+
EditorDocumentImportApi
|
|
15
|
+
} from "./types.js"
|
|
16
|
+
|
|
17
|
+
export interface StaticDocumentStyle {
|
|
18
|
+
title: string
|
|
19
|
+
slug: string
|
|
20
|
+
contents: string
|
|
21
|
+
documentstylefile_set: Array<[string, string]>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface StaticExportTemplate {
|
|
25
|
+
title: string
|
|
26
|
+
file_type: string
|
|
27
|
+
template_file: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface StaticDocumentTemplate {
|
|
31
|
+
title: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface StaticAppConfig {
|
|
35
|
+
/** Locale code used by the editor, e.g. "en". */
|
|
36
|
+
locale: string
|
|
37
|
+
/** gettext function for UI strings. */
|
|
38
|
+
gettext: (msgid: string) => string
|
|
39
|
+
/** CSL engine instance. */
|
|
40
|
+
csl: CSL
|
|
41
|
+
/**
|
|
42
|
+
* Function returning the document payload the editor should load.
|
|
43
|
+
* Called whenever the editor refreshes document data from the server.
|
|
44
|
+
*/
|
|
45
|
+
documentData: () => Promise<{
|
|
46
|
+
doc: Record<string, unknown>
|
|
47
|
+
doc_info: Record<string, unknown>
|
|
48
|
+
time: number
|
|
49
|
+
}>
|
|
50
|
+
/**
|
|
51
|
+
* Optional callback returning the current document content node.
|
|
52
|
+
* Used by File > Download to extract a template definition from the document.
|
|
53
|
+
*/
|
|
54
|
+
getDocContent?: () => Record<string, unknown> | undefined
|
|
55
|
+
/** Initial image entries keyed by image id. */
|
|
56
|
+
initialImages?: Record<number, Image>
|
|
57
|
+
/** Document styles available for the document template. */
|
|
58
|
+
documentStyles?: StaticDocumentStyle[]
|
|
59
|
+
/** Export templates available for the document template. */
|
|
60
|
+
exportTemplates?: StaticExportTemplate[]
|
|
61
|
+
/** Document templates keyed by import id. */
|
|
62
|
+
documentTemplates?: Record<string, StaticDocumentTemplate>
|
|
63
|
+
/** Optional override for the template API response. */
|
|
64
|
+
getTemplateForDoc?: (
|
|
65
|
+
docId: string | number,
|
|
66
|
+
token: string | false
|
|
67
|
+
) => Promise<Record<string, unknown>>
|
|
68
|
+
/** Application name. */
|
|
69
|
+
appName?: string
|
|
70
|
+
/** Routes table used by the app router. */
|
|
71
|
+
routes?: Record<string, {app: string}>
|
|
72
|
+
/**
|
|
73
|
+
* Optional handler called when the editor tries to save the document.
|
|
74
|
+
* Defaults to a no-op that returns version 0.
|
|
75
|
+
*/
|
|
76
|
+
onSaveDocument?: (data: Record<string, unknown>) => Promise<{
|
|
77
|
+
json: Record<string, unknown>
|
|
78
|
+
status: number
|
|
79
|
+
}>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface StoredImage {
|
|
83
|
+
id: number
|
|
84
|
+
title: string
|
|
85
|
+
file_type: string
|
|
86
|
+
image: string
|
|
87
|
+
thumbnail?: string
|
|
88
|
+
width: number
|
|
89
|
+
height: number
|
|
90
|
+
added: number
|
|
91
|
+
cats: number[]
|
|
92
|
+
copyright: Image["copyright"]
|
|
93
|
+
[key: string]: unknown
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function fileToDataUrl(file: Blob): Promise<string> {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const reader = new FileReader()
|
|
99
|
+
reader.onload = () => resolve(String(reader.result))
|
|
100
|
+
reader.onerror = reject
|
|
101
|
+
reader.readAsDataURL(file)
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getImageDimensions(
|
|
106
|
+
dataUrl: string
|
|
107
|
+
): Promise<{width: number; height: number}> {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const img = new Image()
|
|
110
|
+
img.onload = () => resolve({width: img.width, height: img.height})
|
|
111
|
+
img.onerror = reject
|
|
112
|
+
img.src = dataUrl
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create an {@link EditorApp} for a statically served Fidus Writer editor.
|
|
118
|
+
*
|
|
119
|
+
* This wires up in-memory API connectors so that the editor can run without a
|
|
120
|
+
* backend server. It is used by the standalone demo and can be used by any
|
|
121
|
+
* static deployment of `@fiduswriter/editor`.
|
|
122
|
+
*/
|
|
123
|
+
export async function createStaticApp(
|
|
124
|
+
config: StaticAppConfig
|
|
125
|
+
): Promise<EditorApp> {
|
|
126
|
+
const documentStyles = config.documentStyles || []
|
|
127
|
+
const exportTemplates = config.exportTemplates || []
|
|
128
|
+
const documentTemplates = config.documentTemplates || {}
|
|
129
|
+
|
|
130
|
+
// In-memory store for images uploaded during this session.
|
|
131
|
+
const sessionImages: Record<number, StoredImage> = {}
|
|
132
|
+
let nextImageId = 1
|
|
133
|
+
|
|
134
|
+
if (config.initialImages) {
|
|
135
|
+
Object.entries(config.initialImages).forEach(([id, image]) => {
|
|
136
|
+
sessionImages[Number(id)] = image as StoredImage
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function storeImage(
|
|
141
|
+
data: Record<string, unknown>,
|
|
142
|
+
files: Record<string, unknown>
|
|
143
|
+
): Promise<StoredImage> {
|
|
144
|
+
const id = data.id ? Number(data.id) : nextImageId++
|
|
145
|
+
const existing = sessionImages[id]
|
|
146
|
+
const file =
|
|
147
|
+
(files?.image as {file?: Blob})?.file ??
|
|
148
|
+
(files?.image as Blob) ??
|
|
149
|
+
(data.image as Blob)
|
|
150
|
+
|
|
151
|
+
let imageUrl = existing?.image ?? ""
|
|
152
|
+
let fileType = existing?.file_type ?? "png"
|
|
153
|
+
let width = existing?.width ?? 0
|
|
154
|
+
let height = existing?.height ?? 0
|
|
155
|
+
|
|
156
|
+
if (file) {
|
|
157
|
+
imageUrl = await fileToDataUrl(file)
|
|
158
|
+
fileType =
|
|
159
|
+
(data.original_file_type as string) ||
|
|
160
|
+
(file as File).type ||
|
|
161
|
+
"image/png"
|
|
162
|
+
const dimensions = await getImageDimensions(imageUrl)
|
|
163
|
+
width = dimensions.width
|
|
164
|
+
height = dimensions.height
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const image: StoredImage = {
|
|
168
|
+
id,
|
|
169
|
+
title: (data.title as string) || existing?.title || "",
|
|
170
|
+
file_type: fileType,
|
|
171
|
+
image: imageUrl,
|
|
172
|
+
width,
|
|
173
|
+
height,
|
|
174
|
+
added: existing?.added || Date.now(),
|
|
175
|
+
cats: (data.cats as number[]) || existing?.cats || [],
|
|
176
|
+
copyright:
|
|
177
|
+
(data.copyright as Image["copyright"]) ||
|
|
178
|
+
existing?.copyright || {freeToRead: true, licenses: []}
|
|
179
|
+
}
|
|
180
|
+
sessionImages[id] = image
|
|
181
|
+
return image
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const defaultGetTemplateForDoc = async () => {
|
|
185
|
+
const docContent = config.getDocContent?.() as Record<string, unknown> | undefined
|
|
186
|
+
const template = docContent
|
|
187
|
+
? extractTemplate(docContent as unknown as any)
|
|
188
|
+
: null
|
|
189
|
+
const title =
|
|
190
|
+
((docContent as any)?.attrs?.template as string) ||
|
|
191
|
+
((template as any)?.content?.attrs?.template as string) ||
|
|
192
|
+
""
|
|
193
|
+
return {
|
|
194
|
+
json: {
|
|
195
|
+
id: 1,
|
|
196
|
+
title,
|
|
197
|
+
content: template?.content ?? {},
|
|
198
|
+
doc_version: FW_DOCUMENT_VERSION,
|
|
199
|
+
export_templates: exportTemplates.map(template => ({
|
|
200
|
+
fields: {
|
|
201
|
+
template_file: template.template_file,
|
|
202
|
+
file_type: template.file_type,
|
|
203
|
+
title: template.title
|
|
204
|
+
}
|
|
205
|
+
})),
|
|
206
|
+
document_styles: documentStyles.map(style => ({
|
|
207
|
+
fields: {
|
|
208
|
+
contents: style.contents,
|
|
209
|
+
slug: style.slug,
|
|
210
|
+
title: style.title,
|
|
211
|
+
documentstylefile_set: style.documentstylefile_set
|
|
212
|
+
}
|
|
213
|
+
}))
|
|
214
|
+
},
|
|
215
|
+
status: 200
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const documentApi: EditorDocumentApi = {
|
|
220
|
+
createDocument: async () => ({json: {id: 1}, status: 200}),
|
|
221
|
+
getWebSocketBase: async () => ({json: {ws_base: ""}, status: 200}),
|
|
222
|
+
getDocumentStyles: async () => ({
|
|
223
|
+
json: {
|
|
224
|
+
export_templates: exportTemplates,
|
|
225
|
+
document_styles: documentStyles,
|
|
226
|
+
document_templates: documentTemplates
|
|
227
|
+
},
|
|
228
|
+
status: 200
|
|
229
|
+
}),
|
|
230
|
+
getDocumentData: async () => {
|
|
231
|
+
const data = await config.documentData()
|
|
232
|
+
return {json: data, status: 200}
|
|
233
|
+
},
|
|
234
|
+
saveDocument: async data => {
|
|
235
|
+
if (config.onSaveDocument) {
|
|
236
|
+
return config.onSaveDocument(data)
|
|
237
|
+
}
|
|
238
|
+
return {json: {version: 0}, status: 200}
|
|
239
|
+
},
|
|
240
|
+
commentNotify: async () => Promise.resolve(),
|
|
241
|
+
requestAccess: async () => ({json: {}, status: 200}),
|
|
242
|
+
validateShareToken: async () => ({json: {}, status: 404}),
|
|
243
|
+
listShareTokens: async () => ({json: [], status: 200}),
|
|
244
|
+
createShareToken: async () => ({json: {}, status: 200}),
|
|
245
|
+
revokeShareToken: async () => ({json: {}, status: 200}),
|
|
246
|
+
getAccessRights: async () => ({json: {}, status: 200}),
|
|
247
|
+
saveAccessRights: async () => Promise.resolve(),
|
|
248
|
+
saveE2EEImage: async () => ({json: {}, status: 200}),
|
|
249
|
+
deleteE2EEImage: async () => Promise.resolve(),
|
|
250
|
+
uploadRevision: async () => Promise.resolve(),
|
|
251
|
+
getTemplateForDoc: async (id, token) => {
|
|
252
|
+
if (config.getTemplateForDoc) {
|
|
253
|
+
return {
|
|
254
|
+
json: await config.getTemplateForDoc(id, token),
|
|
255
|
+
status: 200
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return defaultGetTemplateForDoc()
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const documentImportApi: EditorDocumentImportApi = {
|
|
263
|
+
createDoc: async () => ({json: {id: 1}, status: 200}),
|
|
264
|
+
saveImage: async (data, files) => {
|
|
265
|
+
const image = await storeImage(
|
|
266
|
+
data as Record<string, unknown>,
|
|
267
|
+
(files as Record<string, unknown>) || {}
|
|
268
|
+
)
|
|
269
|
+
return {json: {id: image.id}, status: 200}
|
|
270
|
+
},
|
|
271
|
+
saveE2EEImage: async () => ({json: {}, status: 200}),
|
|
272
|
+
saveDocument: async data => {
|
|
273
|
+
if (config.onSaveDocument) {
|
|
274
|
+
return config.onSaveDocument(data)
|
|
275
|
+
}
|
|
276
|
+
return {json: {version: 0}, status: 200}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const imageApi: ImageApi = {
|
|
281
|
+
getImages: async () => ({
|
|
282
|
+
imageCategories: [],
|
|
283
|
+
images: Object.values(sessionImages) as Image[]
|
|
284
|
+
}),
|
|
285
|
+
saveImage: async (data, files = {}) => {
|
|
286
|
+
const image = await storeImage(
|
|
287
|
+
data as Record<string, unknown>,
|
|
288
|
+
files as Record<string, unknown>
|
|
289
|
+
)
|
|
290
|
+
return {
|
|
291
|
+
errormsg: {},
|
|
292
|
+
values: image
|
|
293
|
+
} as SaveImageResponse
|
|
294
|
+
},
|
|
295
|
+
saveCategories: async () => ({entries: []}),
|
|
296
|
+
deleteImages: async ids => {
|
|
297
|
+
ids.forEach(id => delete sessionImages[id])
|
|
298
|
+
return Promise.resolve()
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const bibliographyApi: BibliographyApi = {
|
|
303
|
+
getDB: async () => ({
|
|
304
|
+
bib_categories: [],
|
|
305
|
+
bib_list: [],
|
|
306
|
+
last_modified: -1,
|
|
307
|
+
number_of_entries: 0,
|
|
308
|
+
user_id: 1
|
|
309
|
+
}),
|
|
310
|
+
saveBibEntries: async tmpDB => ({
|
|
311
|
+
id_translations: Object.keys(tmpDB).map(tmpId => [
|
|
312
|
+
Number.parseInt(tmpId),
|
|
313
|
+
Number.parseInt(tmpId)
|
|
314
|
+
])
|
|
315
|
+
}),
|
|
316
|
+
saveCategories: async () => ({entries: []}),
|
|
317
|
+
deleteCategory: async () =>
|
|
318
|
+
new Response(JSON.stringify({}), {status: 200}),
|
|
319
|
+
deleteBibEntries: async () =>
|
|
320
|
+
new Response(JSON.stringify({}), {status: 200})
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const contactsApi: EditorContactsApi = {
|
|
324
|
+
add: async () => ({json: {}, status: 200})
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const app = {
|
|
328
|
+
name: config.appName || "fiduswriter-static-editor",
|
|
329
|
+
routes: config.routes || {
|
|
330
|
+
"": {app: "document"},
|
|
331
|
+
document: {app: "document"}
|
|
332
|
+
},
|
|
333
|
+
goTo: () => {},
|
|
334
|
+
isOffline: () => false,
|
|
335
|
+
settings: {
|
|
336
|
+
APPS: [config.appName || "static"],
|
|
337
|
+
EDITOR_SAVE_MODE: "external",
|
|
338
|
+
EDITOR_ONLY_MODE: true,
|
|
339
|
+
E2EE_MODE: "disabled",
|
|
340
|
+
LANGUAGE: config.locale
|
|
341
|
+
},
|
|
342
|
+
csl: config.csl,
|
|
343
|
+
apiConnectors: {
|
|
344
|
+
document: documentApi,
|
|
345
|
+
documentImport: documentImportApi,
|
|
346
|
+
image: imageApi,
|
|
347
|
+
bibliography: bibliographyApi,
|
|
348
|
+
contacts: contactsApi
|
|
349
|
+
}
|
|
350
|
+
} as unknown as EditorApp
|
|
351
|
+
|
|
352
|
+
const bibDB = new BibliographyDB(app as any)
|
|
353
|
+
const imageDB = new ImageDB(app as any) as unknown as EditorImageDB
|
|
354
|
+
imageDB.setImage = (_id: number, _data: Record<string, unknown>) => {}
|
|
355
|
+
|
|
356
|
+
;(app as any).bibDB = bibDB
|
|
357
|
+
;(app as any).imageDB = imageDB
|
|
358
|
+
|
|
359
|
+
return app
|
|
360
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import {gettext, initSettings, interpolate, staticUrl} from "fwtoolkit"
|
|
2
|
+
|
|
3
|
+
import type {CSL, EditorUser} from "./types.js"
|
|
4
|
+
import type {StaticAppConfig} from "./static_app.js"
|
|
5
|
+
|
|
6
|
+
import type {Editor} from "./index.js"
|
|
7
|
+
|
|
8
|
+
export type {StaticAppConfig} from "./static_app.js"
|
|
9
|
+
|
|
10
|
+
export interface StaticEditorConfig
|
|
11
|
+
extends Omit<StaticAppConfig, "gettext" | "csl"> {
|
|
12
|
+
/** gettext function for UI strings. When omitted, a function backed by `localeCatalog` is used. */
|
|
13
|
+
gettext?: (msgid: string) => string
|
|
14
|
+
/** CSL engine instance. When omitted, a default engine is created. */
|
|
15
|
+
csl?: CSL
|
|
16
|
+
/** Display name for the user. Used to build the user object when `user` is not given. */
|
|
17
|
+
username?: string
|
|
18
|
+
/** Pre-built user object. Takes precedence over `username`. */
|
|
19
|
+
user?: EditorUser
|
|
20
|
+
/**
|
|
21
|
+
* Base path for resolving static assets.
|
|
22
|
+
* Defaults to the directory containing the current page.
|
|
23
|
+
*/
|
|
24
|
+
staticBasePath?: string
|
|
25
|
+
/**
|
|
26
|
+
* Optional locale catalog. When omitted, the catalog is fetched from
|
|
27
|
+
* `../locale/{locale}/messages.json` relative to the current page.
|
|
28
|
+
*/
|
|
29
|
+
localeCatalog?: Record<string, string>
|
|
30
|
+
/**
|
|
31
|
+
* Optional callback for save attempts. Receives the document payload.
|
|
32
|
+
*/
|
|
33
|
+
onSaveDocument?: (data: Record<string, unknown>) => Promise<{
|
|
34
|
+
json: Record<string, unknown>
|
|
35
|
+
status: number
|
|
36
|
+
}>
|
|
37
|
+
/** Optional extra editor plugins. */
|
|
38
|
+
plugins?: Array<[string, Record<string, unknown>]>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function loadLocaleCatalog(
|
|
42
|
+
locale: string
|
|
43
|
+
): Promise<Record<string, string>> {
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(`../locale/${locale}/messages.json`)
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
return {}
|
|
48
|
+
}
|
|
49
|
+
return (await response.json()) as Record<string, string>
|
|
50
|
+
} catch {
|
|
51
|
+
return {}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createGettext(catalog: Record<string, string>) {
|
|
56
|
+
return function gettextImpl(msgid: string): string {
|
|
57
|
+
return catalog[msgid] || msgid
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function defaultStaticUrl(basePath: string): (path: string) => string {
|
|
62
|
+
return (path: string) => {
|
|
63
|
+
if (path.startsWith("css/editor/")) {
|
|
64
|
+
return `${basePath}css/${path.slice("css/editor/".length)}`
|
|
65
|
+
}
|
|
66
|
+
if (path === "css/bibliography/bibliography.css") {
|
|
67
|
+
return `${basePath}css/bibliography.css`
|
|
68
|
+
}
|
|
69
|
+
if (path.startsWith("css/")) {
|
|
70
|
+
return `${basePath}${path}`
|
|
71
|
+
}
|
|
72
|
+
return `${basePath}static/${path}`
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create and initialize a statically served Fidus Writer editor.
|
|
78
|
+
*
|
|
79
|
+
* This is the high-level entry point for running `@fiduswriter/editor` without
|
|
80
|
+
* a backend server. It sets up the runtime globals, loads locale strings,
|
|
81
|
+
* creates the static app shell, and initializes the editor.
|
|
82
|
+
*
|
|
83
|
+
* The lower-level {@link Editor} constructor and {@link createStaticApp} are
|
|
84
|
+
* still available for server-backed deployments or advanced customization.
|
|
85
|
+
*/
|
|
86
|
+
export async function createStaticEditor(
|
|
87
|
+
config: StaticEditorConfig
|
|
88
|
+
): Promise<Editor> {
|
|
89
|
+
// The editor source expects these Fidus Writer runtime helpers as globals.
|
|
90
|
+
// They must be present before any editor module is evaluated, because some
|
|
91
|
+
// editor modules call them at the top level.
|
|
92
|
+
;(window as any).gettext = gettext
|
|
93
|
+
;(window as any).interpolate = interpolate
|
|
94
|
+
;(window as any).staticUrl = staticUrl
|
|
95
|
+
|
|
96
|
+
const locale = config.locale || "en"
|
|
97
|
+
const catalog =
|
|
98
|
+
config.localeCatalog ?? (await loadLocaleCatalog(locale))
|
|
99
|
+
const localeGettext = config.gettext ?? createGettext(catalog)
|
|
100
|
+
|
|
101
|
+
const basePath =
|
|
102
|
+
config.staticBasePath ??
|
|
103
|
+
window.location.pathname.replace(
|
|
104
|
+
/\/(?:editor\/(?:index\.html)?|index\.html)$/,
|
|
105
|
+
"/"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
initSettings({
|
|
109
|
+
apiUrl: url => url,
|
|
110
|
+
apiUrlMap: {},
|
|
111
|
+
getCsrfToken: () => "",
|
|
112
|
+
gettext: localeGettext,
|
|
113
|
+
interpolate: (fmt, args, named) => {
|
|
114
|
+
if (named) {
|
|
115
|
+
return fmt.replace(/%\(([^)]+)\)s?/g, (_match, key) => {
|
|
116
|
+
const value = (args as unknown as Record<string, unknown>)[key]
|
|
117
|
+
return value !== undefined ? String(value) : ""
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
let index = 0
|
|
121
|
+
return fmt.replace(/%s/g, () => {
|
|
122
|
+
const value = (args as unknown[])[index++]
|
|
123
|
+
return value !== undefined ? String(value) : ""
|
|
124
|
+
})
|
|
125
|
+
},
|
|
126
|
+
staticUrl: defaultStaticUrl(basePath)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const username = config.username || "User"
|
|
130
|
+
const user: EditorUser =
|
|
131
|
+
config.user ??
|
|
132
|
+
({
|
|
133
|
+
id: 1,
|
|
134
|
+
username: username.toLowerCase().replace(/\s+/g, "_") || "user",
|
|
135
|
+
emails: [{address: "user@example.com", primary: true}],
|
|
136
|
+
name: username,
|
|
137
|
+
is_authenticated: true
|
|
138
|
+
} as EditorUser)
|
|
139
|
+
|
|
140
|
+
let csl = config.csl
|
|
141
|
+
if (!csl) {
|
|
142
|
+
const {createCSL} = await import(
|
|
143
|
+
"@fiduswriter/document/citations/create_csl"
|
|
144
|
+
)
|
|
145
|
+
csl = await createCSL()
|
|
146
|
+
// createCSL replaces getStyle/getLocale with versions that only look at
|
|
147
|
+
// pre-registered styles. Restore the prototype methods so the bundled
|
|
148
|
+
// style/locale chunks are loaded dynamically.
|
|
149
|
+
const cslProto = Object.getPrototypeOf(csl)
|
|
150
|
+
;(csl as any).getStyle = cslProto.getStyle
|
|
151
|
+
;(csl as any).getLocale = cslProto.getLocale
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const {createStaticApp} = await import("./static_app.js")
|
|
155
|
+
|
|
156
|
+
const app = await createStaticApp({
|
|
157
|
+
...config,
|
|
158
|
+
locale,
|
|
159
|
+
gettext: localeGettext,
|
|
160
|
+
csl,
|
|
161
|
+
onSaveDocument: config.onSaveDocument
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Prime the user bibliography and image databases so dialogs that read
|
|
165
|
+
// from them see empty but valid stores.
|
|
166
|
+
await Promise.all([
|
|
167
|
+
(app.bibDB as any).getDB(),
|
|
168
|
+
(app.imageDB as any).getDB()
|
|
169
|
+
])
|
|
170
|
+
|
|
171
|
+
const docInfo = (await config.documentData()).doc_info
|
|
172
|
+
const docId = (docInfo?.id as string | number) ?? 1
|
|
173
|
+
const docPath = (docInfo?.path as string) ?? ""
|
|
174
|
+
|
|
175
|
+
const {Editor} = await import("./index.js")
|
|
176
|
+
|
|
177
|
+
const editor = new Editor(
|
|
178
|
+
{app, user},
|
|
179
|
+
docPath,
|
|
180
|
+
String(docId),
|
|
181
|
+
config.plugins ?? ([] as Array<[string, Record<string, unknown>]>)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
await editor.init()
|
|
185
|
+
return editor
|
|
186
|
+
}
|