@cosmicdrift/kumiko-renderer-puppeteer-client 0.374.0 → 0.378.0
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/dist/document-pdf.js +87 -5
- package/dist/entity.d.ts +12 -12
- package/package.json +6 -6
package/dist/document-pdf.js
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
import { sanitizePdfAttachmentFileName } from "@cosmicdrift/kumiko-bundled-features/delivery";
|
|
5
5
|
import { FileRefTooLargeError, readFileRefContent, } from "@cosmicdrift/kumiko-bundled-features/file-foundation";
|
|
6
6
|
import { RendererError, } from "@cosmicdrift/kumiko-bundled-features/renderer-foundation";
|
|
7
|
-
import { collectDocumentResourceKeys, compileDocumentBodyToHandlebars, createTemplateResolverApi, DEFAULT_PDF_LAYOUT_SLUG, PDF_DEFAULT_MARGIN_MM, PDF_FOOTER_MARGIN_MM, PDF_HEADER_MARGIN_MM, prepareDocumentForPrint, readLayoutRegions, readPdfTemplateMeta, readTemplateResourceMaxBytes, resolveTemplateVariables, sanitizeDocumentHtml, sanitizeLayoutHtml, TemplateNotFoundError, toResourceDataUri, } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
|
|
7
|
+
import { collectDocumentImageVariables, collectDocumentResourceKeys, compileDocumentBodyToHandlebars, createTemplateResolverApi, DEFAULT_PDF_LAYOUT_SLUG, isResourceImageContentType, PDF_DEFAULT_MARGIN_MM, PDF_FOOTER_MARGIN_MM, PDF_HEADER_MARGIN_MM, prepareDocumentForPrint, readLayoutRegions, readPdfTemplateMeta, readTemplateResourceMaxBytes, resolveTemplateVariables, sanitizeDocumentHtml, sanitizeLayoutHtml, TemplateNotFoundError, toResourceDataUri, } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
|
|
8
8
|
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
9
9
|
import { collectRegionResourceKeys, compileDocumentSource, createDocumentHandlebars, mergeResourceFileRefs, readBrandingAccentColor, readDocumentFormat, readTenantName, resolveResourceFileRefs, } from "@cosmicdrift/kumiko-renderer-mail-html";
|
|
10
10
|
const LINE_BREAKS = /[\r\n]+/g;
|
|
11
11
|
const IMAGE_CONTENT_TYPE_PREFIX = "image/";
|
|
12
|
+
const MAX_RECORD_IMAGES = 24;
|
|
13
|
+
const MAX_RECORD_IMAGES_TOTAL_BYTES = 20 * 1024 * 1024;
|
|
12
14
|
const PREVIEW_PAGE_NUMBER = "1";
|
|
13
15
|
const EMPTY_CHROMIUM_REGION = "<span></span>";
|
|
14
16
|
const PAGE_NUMBER_HTML = '<span class="pageNumber"></span>';
|
|
@@ -26,6 +28,9 @@ const PRINT_CSS = [
|
|
|
26
28
|
"[data-align=justify]{text-align:justify}",
|
|
27
29
|
"[data-size=small]{font-size:8pt;color:#555}",
|
|
28
30
|
"img{max-width:100%}",
|
|
31
|
+
".kumiko-doc-images{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin:8px 0}",
|
|
32
|
+
".kumiko-doc-images:empty{display:none}",
|
|
33
|
+
".kumiko-doc-images img{width:100%;break-inside:avoid;page-break-inside:avoid}",
|
|
29
34
|
].join("");
|
|
30
35
|
const EMPTY_REGIONS = { header: "", signature: "", footer: "" };
|
|
31
36
|
function describe(e) {
|
|
@@ -129,6 +134,67 @@ async function loadResourceDataUris(ctx, fileRefIds) {
|
|
|
129
134
|
}
|
|
130
135
|
return dataUris;
|
|
131
136
|
}
|
|
137
|
+
function fileRefIdsOf(value) {
|
|
138
|
+
if (typeof value === "string")
|
|
139
|
+
return [value];
|
|
140
|
+
return Array.isArray(value)
|
|
141
|
+
? value.filter((entry) => typeof entry === "string")
|
|
142
|
+
: [];
|
|
143
|
+
}
|
|
144
|
+
// Own tenant only (no system files): the ids come from the app's record, so a foreign or
|
|
145
|
+
// unreadable id drops silently like a missing image instead of failing the document.
|
|
146
|
+
async function readRecordImage(ctx, filesForTenant, fileRefId, maxBytes) {
|
|
147
|
+
try {
|
|
148
|
+
const found = await readFileRefContent({
|
|
149
|
+
db: ctx.db,
|
|
150
|
+
filesForTenant,
|
|
151
|
+
fileRefId,
|
|
152
|
+
allowedTenantIds: [ctx.tenantId],
|
|
153
|
+
maxBytes,
|
|
154
|
+
});
|
|
155
|
+
if (found === null || !isResourceImageContentType(found.contentType))
|
|
156
|
+
return null;
|
|
157
|
+
return { dataUri: toResourceDataUri(found.contentType, found.content), size: found.size };
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
if (e instanceof FileRefTooLargeError)
|
|
161
|
+
return null;
|
|
162
|
+
throw e;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function loadRecordImageDataUris(ctx, variables, imageVariableNames) {
|
|
166
|
+
const dataUrisByVariable = new Map();
|
|
167
|
+
const { filesForTenant } = ctx;
|
|
168
|
+
if (filesForTenant === undefined || imageVariableNames.length === 0)
|
|
169
|
+
return dataUrisByVariable;
|
|
170
|
+
const maxBytes = await readTemplateResourceMaxBytes(ctx.config, ctx.registry);
|
|
171
|
+
const seenIds = new Set();
|
|
172
|
+
let loadedCount = 0;
|
|
173
|
+
let loadedBytes = 0;
|
|
174
|
+
for (const name of imageVariableNames) {
|
|
175
|
+
const dataUris = [];
|
|
176
|
+
for (const fileRefId of fileRefIdsOf(variables[name])) {
|
|
177
|
+
if (seenIds.has(fileRefId))
|
|
178
|
+
continue;
|
|
179
|
+
seenIds.add(fileRefId);
|
|
180
|
+
if (loadedCount >= MAX_RECORD_IMAGES)
|
|
181
|
+
break;
|
|
182
|
+
const image = await readRecordImage(ctx, filesForTenant, fileRefId, Math.min(maxBytes, MAX_RECORD_IMAGES_TOTAL_BYTES - loadedBytes));
|
|
183
|
+
if (image === null)
|
|
184
|
+
continue;
|
|
185
|
+
loadedCount += 1;
|
|
186
|
+
loadedBytes += image.size;
|
|
187
|
+
dataUris.push(image.dataUri);
|
|
188
|
+
}
|
|
189
|
+
dataUrisByVariable.set(name, dataUris);
|
|
190
|
+
}
|
|
191
|
+
return dataUrisByVariable;
|
|
192
|
+
}
|
|
193
|
+
// Text context without images variables: their value is file ids, which no placeholder,
|
|
194
|
+
// title or file name pattern may print.
|
|
195
|
+
function withoutImageVariables(variables, definitions) {
|
|
196
|
+
return Object.fromEntries(Object.entries(variables).filter(([name]) => !(Object.hasOwn(definitions, name) && definitions[name]?.type === "images")));
|
|
197
|
+
}
|
|
132
198
|
function renderRegion(hbs, region, context, resourceSrc) {
|
|
133
199
|
if (region.trim() === "")
|
|
134
200
|
return "";
|
|
@@ -221,7 +287,7 @@ async function findOwnPdfTemplate(tplApi, ctx, slug, locale, scope) {
|
|
|
221
287
|
...(scope === "system" && { scope }),
|
|
222
288
|
});
|
|
223
289
|
}
|
|
224
|
-
async function assembleDocument(input, ctx, markers) {
|
|
290
|
+
async function assembleDocument(input, ctx, markers, recordImages = "placeholder") {
|
|
225
291
|
const { slug, locale, variables } = input;
|
|
226
292
|
const tplApi = createTemplateResolverApi(ctx.db);
|
|
227
293
|
const template = input.draft === undefined
|
|
@@ -237,7 +303,18 @@ async function assembleDocument(input, ctx, markers) {
|
|
|
237
303
|
readBrandingAccentColor(ctx),
|
|
238
304
|
]);
|
|
239
305
|
const hbs = createDocumentHandlebars(format);
|
|
240
|
-
const
|
|
306
|
+
const variableDefinitions = resolveTemplateVariables(ctx.registry, slug, template.variableSchema);
|
|
307
|
+
const context = {
|
|
308
|
+
...withoutImageVariables(variables, variableDefinitions),
|
|
309
|
+
tenantName,
|
|
310
|
+
signature: "",
|
|
311
|
+
imprint: "",
|
|
312
|
+
page: "",
|
|
313
|
+
pages: "",
|
|
314
|
+
};
|
|
315
|
+
const recordImageUris = recordImages === "resolve"
|
|
316
|
+
? await loadRecordImageDataUris(ctx, variables, collectDocumentImageVariables(template.content))
|
|
317
|
+
: new Map();
|
|
241
318
|
const bodyKeys = collectDocumentResourceKeys(template.content);
|
|
242
319
|
const layoutKeys = collectRegionResourceKeys(layout.regions);
|
|
243
320
|
const bodyRefs = await resolveResourceFileRefs(tplApi, ctx, template, bodyKeys);
|
|
@@ -247,7 +324,12 @@ async function assembleDocument(input, ctx, markers) {
|
|
|
247
324
|
const dataUris = await loadResourceDataUris(ctx, mergeResourceFileRefs(bodyRefs, layoutRefs));
|
|
248
325
|
const resourceSrc = (key) => dataUris.get(key);
|
|
249
326
|
const missingResourceKeys = [...new Set([...bodyKeys, ...layoutKeys])].filter((key) => !dataUris.has(key));
|
|
250
|
-
const bodySource = compileDocumentBodyToHandlebars(template.content,
|
|
327
|
+
const bodySource = compileDocumentBodyToHandlebars(template.content, variableDefinitions, {
|
|
328
|
+
resourceSrc,
|
|
329
|
+
recordImagesSrc: (variable) => recordImageUris.get(variable) ?? [],
|
|
330
|
+
recordImagesPlaceholder: recordImages === "placeholder",
|
|
331
|
+
...(accentColor !== undefined && { stepsColor: accentColor }),
|
|
332
|
+
});
|
|
251
333
|
const bodyHtml = prepareDocumentForPrint(compileDocumentSource(hbs, bodySource)(context));
|
|
252
334
|
const signatureHtml = renderRegion(hbs, layout.regions.signature, context, resourceSrc);
|
|
253
335
|
const pagedContext = markers === "chromium"
|
|
@@ -283,7 +365,7 @@ async function assembleDocument(input, ctx, markers) {
|
|
|
283
365
|
}
|
|
284
366
|
}
|
|
285
367
|
export async function buildDocumentPdfRequest(req, ctx) {
|
|
286
|
-
return toPdfRequest(await assembleDocument(req.payload, ctx, "chromium"), req.payload.locale);
|
|
368
|
+
return toPdfRequest(await assembleDocument(req.payload, ctx, "chromium", "resolve"), req.payload.locale);
|
|
287
369
|
}
|
|
288
370
|
// Prints an unsaved draft; the stored template of the slug only supplies images and the variable schema.
|
|
289
371
|
export async function buildDocumentDraftPdfRequest(req, ctx) {
|
package/dist/entity.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare const renderedPdfEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
14
14
|
readonly sensitive?: boolean;
|
|
15
15
|
readonly writeOnly?: boolean;
|
|
16
16
|
readonly readAsInstruction?: boolean;
|
|
17
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
17
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
18
18
|
readonly default?: string;
|
|
19
19
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
20
20
|
readonly multiline?: boolean | {
|
|
@@ -37,7 +37,7 @@ export declare const renderedPdfEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
37
37
|
readonly sensitive?: boolean;
|
|
38
38
|
readonly writeOnly?: boolean;
|
|
39
39
|
readonly readAsInstruction?: boolean;
|
|
40
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
40
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
41
41
|
readonly default?: string;
|
|
42
42
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
43
43
|
readonly multiline?: boolean | {
|
|
@@ -60,7 +60,7 @@ export declare const renderedPdfEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
60
60
|
readonly sensitive?: boolean;
|
|
61
61
|
readonly writeOnly?: boolean;
|
|
62
62
|
readonly readAsInstruction?: boolean;
|
|
63
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
63
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
64
64
|
readonly default?: string;
|
|
65
65
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
66
66
|
readonly multiline?: boolean | {
|
|
@@ -83,7 +83,7 @@ export declare const renderedPdfEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
83
83
|
readonly sensitive?: boolean;
|
|
84
84
|
readonly writeOnly?: boolean;
|
|
85
85
|
readonly readAsInstruction?: boolean;
|
|
86
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
86
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
87
87
|
readonly default?: string;
|
|
88
88
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
89
89
|
readonly multiline?: boolean | {
|
|
@@ -125,7 +125,7 @@ export declare const renderedPdfEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
125
125
|
readonly sensitive?: boolean;
|
|
126
126
|
readonly writeOnly?: boolean;
|
|
127
127
|
readonly readAsInstruction?: boolean;
|
|
128
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
128
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
129
129
|
readonly default?: string;
|
|
130
130
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
131
131
|
readonly multiline?: boolean | {
|
|
@@ -148,7 +148,7 @@ export declare const renderedPdfEntity: import("@cosmicdrift/kumiko-types/fields
|
|
|
148
148
|
readonly sensitive?: boolean;
|
|
149
149
|
readonly writeOnly?: boolean;
|
|
150
150
|
readonly readAsInstruction?: boolean;
|
|
151
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
151
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
152
152
|
readonly default?: string;
|
|
153
153
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
154
154
|
readonly multiline?: boolean | {
|
|
@@ -211,7 +211,7 @@ export declare const renderedPdfsTable: import("@cosmicdrift/kumiko-framework/db
|
|
|
211
211
|
readonly sensitive?: boolean;
|
|
212
212
|
readonly writeOnly?: boolean;
|
|
213
213
|
readonly readAsInstruction?: boolean;
|
|
214
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
214
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
215
215
|
readonly default?: string;
|
|
216
216
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
217
217
|
readonly multiline?: boolean | {
|
|
@@ -234,7 +234,7 @@ export declare const renderedPdfsTable: import("@cosmicdrift/kumiko-framework/db
|
|
|
234
234
|
readonly sensitive?: boolean;
|
|
235
235
|
readonly writeOnly?: boolean;
|
|
236
236
|
readonly readAsInstruction?: boolean;
|
|
237
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
237
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
238
238
|
readonly default?: string;
|
|
239
239
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
240
240
|
readonly multiline?: boolean | {
|
|
@@ -257,7 +257,7 @@ export declare const renderedPdfsTable: import("@cosmicdrift/kumiko-framework/db
|
|
|
257
257
|
readonly sensitive?: boolean;
|
|
258
258
|
readonly writeOnly?: boolean;
|
|
259
259
|
readonly readAsInstruction?: boolean;
|
|
260
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
260
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
261
261
|
readonly default?: string;
|
|
262
262
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
263
263
|
readonly multiline?: boolean | {
|
|
@@ -280,7 +280,7 @@ export declare const renderedPdfsTable: import("@cosmicdrift/kumiko-framework/db
|
|
|
280
280
|
readonly sensitive?: boolean;
|
|
281
281
|
readonly writeOnly?: boolean;
|
|
282
282
|
readonly readAsInstruction?: boolean;
|
|
283
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
283
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
284
284
|
readonly default?: string;
|
|
285
285
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
286
286
|
readonly multiline?: boolean | {
|
|
@@ -322,7 +322,7 @@ export declare const renderedPdfsTable: import("@cosmicdrift/kumiko-framework/db
|
|
|
322
322
|
readonly sensitive?: boolean;
|
|
323
323
|
readonly writeOnly?: boolean;
|
|
324
324
|
readonly readAsInstruction?: boolean;
|
|
325
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
325
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
326
326
|
readonly default?: string;
|
|
327
327
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
328
328
|
readonly multiline?: boolean | {
|
|
@@ -345,7 +345,7 @@ export declare const renderedPdfsTable: import("@cosmicdrift/kumiko-framework/db
|
|
|
345
345
|
readonly sensitive?: boolean;
|
|
346
346
|
readonly writeOnly?: boolean;
|
|
347
347
|
readonly readAsInstruction?: boolean;
|
|
348
|
-
readonly format?: "email" | "url" | "phone" | "password";
|
|
348
|
+
readonly format?: "email" | "url" | "phone" | "password" | "month";
|
|
349
349
|
readonly default?: string;
|
|
350
350
|
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
351
351
|
readonly multiline?: boolean | {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer-puppeteer-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.378.0",
|
|
4
4
|
"description": "Renderer-Foundation plugin: HTTP client for puppeteer-service (PDF + image snapshot via remote browser).",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
23
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
24
|
-
"@cosmicdrift/kumiko-renderer-mail-html": "0.
|
|
25
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
22
|
+
"@cosmicdrift/kumiko-bundled-features": "0.378.0",
|
|
23
|
+
"@cosmicdrift/kumiko-framework": "0.378.0",
|
|
24
|
+
"@cosmicdrift/kumiko-renderer-mail-html": "0.378.0",
|
|
25
|
+
"@cosmicdrift/kumiko-types": "0.378.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
28
|
+
"@cosmicdrift/kumiko-testing": "0.378.0"
|
|
29
29
|
},
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|