@formfeed/engine 0.3.5 → 0.3.7
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 +1 -1
- package/index.cjs +532 -175
- package/index.js +532 -175
- package/package.json +1 -1
- package/types/index.d.cts +3 -2
- package/types/index.d.ts +3 -2
- package/types/lib/codes/epc.d.cts +56 -0
- package/types/lib/codes/epc.d.ts +56 -0
- package/types/lib/codes/index.d.cts +7 -0
- package/types/lib/codes/index.d.ts +7 -0
- package/types/lib/codes/qr.d.cts +11 -0
- package/types/lib/codes/qr.d.ts +11 -0
- package/types/lib/helpers/codes.d.cts +0 -19
- package/types/lib/helpers/codes.d.ts +0 -19
- package/types/lib/helpers/format.d.cts +2 -1
- package/types/lib/helpers/format.d.ts +2 -1
- package/types/lib/helpers/index.d.cts +4 -6
- package/types/lib/helpers/index.d.ts +4 -6
- package/types/lib/preview.d.cts +13 -0
- package/types/lib/preview.d.ts +13 -0
- package/types/lib/types.d.cts +19 -1
- package/types/lib/types.d.ts +19 -1
- package/types/lib/values.d.cts +8 -0
- package/types/lib/values.d.ts +8 -0
package/index.js
CHANGED
|
@@ -77,8 +77,234 @@ function depthGuard(resolve2, includeDepth) {
|
|
|
77
77
|
|
|
78
78
|
// packages/engine/src/lib/helpers/codes.ts
|
|
79
79
|
import { toSVG } from "bwip-js";
|
|
80
|
+
|
|
81
|
+
// packages/engine/src/lib/values.ts
|
|
82
|
+
function toNumber(value) {
|
|
83
|
+
if (typeof value === "number") return value;
|
|
84
|
+
if (typeof value === "string") {
|
|
85
|
+
const n = Number(value.replace(/\s/g, "").replace(",", "."));
|
|
86
|
+
return Number.isFinite(n) ? n : NaN;
|
|
87
|
+
}
|
|
88
|
+
if (typeof value === "bigint") return Number(value);
|
|
89
|
+
return NaN;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// packages/engine/src/lib/codes/epc.ts
|
|
93
|
+
function epcPayload(input) {
|
|
94
|
+
const amount = input.amount === void 0 || input.amount === "" ? "" : `EUR${toNumber(input.amount).toFixed(2)}`;
|
|
95
|
+
return [
|
|
96
|
+
"BCD",
|
|
97
|
+
"002",
|
|
98
|
+
"1",
|
|
99
|
+
"SCT",
|
|
100
|
+
(input.bic ?? "").replace(/\s/g, ""),
|
|
101
|
+
String(input.name ?? "").slice(0, 70),
|
|
102
|
+
String(input.iban ?? "").replace(/\s/g, ""),
|
|
103
|
+
amount,
|
|
104
|
+
(input.purpose ?? "").slice(0, 4),
|
|
105
|
+
(input.reference ?? "").slice(0, 35),
|
|
106
|
+
input.reference ? "" : (input.text ?? "").slice(0, 140),
|
|
107
|
+
""
|
|
108
|
+
].join("\n");
|
|
109
|
+
}
|
|
110
|
+
var IBAN_LENGTHS = {
|
|
111
|
+
AD: 24,
|
|
112
|
+
AT: 20,
|
|
113
|
+
BE: 16,
|
|
114
|
+
BG: 22,
|
|
115
|
+
CH: 21,
|
|
116
|
+
CY: 28,
|
|
117
|
+
CZ: 24,
|
|
118
|
+
DE: 22,
|
|
119
|
+
DK: 18,
|
|
120
|
+
EE: 20,
|
|
121
|
+
ES: 24,
|
|
122
|
+
FI: 18,
|
|
123
|
+
FO: 18,
|
|
124
|
+
FR: 27,
|
|
125
|
+
GB: 22,
|
|
126
|
+
GI: 23,
|
|
127
|
+
GL: 18,
|
|
128
|
+
GR: 27,
|
|
129
|
+
HR: 21,
|
|
130
|
+
HU: 28,
|
|
131
|
+
IE: 22,
|
|
132
|
+
IS: 26,
|
|
133
|
+
IT: 27,
|
|
134
|
+
LI: 21,
|
|
135
|
+
LT: 20,
|
|
136
|
+
LU: 20,
|
|
137
|
+
LV: 21,
|
|
138
|
+
MC: 27,
|
|
139
|
+
MT: 31,
|
|
140
|
+
NL: 18,
|
|
141
|
+
NO: 15,
|
|
142
|
+
PL: 28,
|
|
143
|
+
PT: 25,
|
|
144
|
+
RO: 24,
|
|
145
|
+
SE: 24,
|
|
146
|
+
SI: 19,
|
|
147
|
+
SK: 24,
|
|
148
|
+
SM: 27,
|
|
149
|
+
VA: 22
|
|
150
|
+
};
|
|
151
|
+
var EPC_MAX_BYTES = 331;
|
|
152
|
+
var byteLength = (value) => new TextEncoder().encode(value).length;
|
|
153
|
+
var normaliseIban = (iban) => iban.replace(/\s/g, "").toUpperCase();
|
|
154
|
+
function ibanChecksum(iban) {
|
|
155
|
+
const rearranged = normaliseIban(iban).slice(4) + normaliseIban(iban).slice(0, 4);
|
|
156
|
+
const digits = [...rearranged].map((c) => c >= "A" && c <= "Z" ? String(c.charCodeAt(0) - 55) : c).join("");
|
|
157
|
+
let remainder = 0;
|
|
158
|
+
for (const digit of digits) remainder = (remainder * 10 + Number(digit)) % 97;
|
|
159
|
+
return remainder === 1;
|
|
160
|
+
}
|
|
161
|
+
function validateEpc(input) {
|
|
162
|
+
const problems = [];
|
|
163
|
+
const name = String(input.name ?? "").trim();
|
|
164
|
+
if (!name) problems.push({ field: "name", code: "required" });
|
|
165
|
+
else if (name.length > 70) problems.push({ field: "name", code: "too_long", max: 70, actual: name.length });
|
|
166
|
+
const iban = normaliseIban(String(input.iban ?? ""));
|
|
167
|
+
if (!iban) problems.push({ field: "iban", code: "required" });
|
|
168
|
+
else if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(iban)) problems.push({ field: "iban", code: "iban_characters" });
|
|
169
|
+
else {
|
|
170
|
+
const expected = IBAN_LENGTHS[iban.slice(0, 2)];
|
|
171
|
+
if (expected !== void 0 && iban.length !== expected)
|
|
172
|
+
problems.push({ field: "iban", code: "iban_country_length", max: expected, actual: iban.length });
|
|
173
|
+
else if (!ibanChecksum(iban)) problems.push({ field: "iban", code: "iban_checksum" });
|
|
174
|
+
}
|
|
175
|
+
const bic = String(input.bic ?? "").replace(/\s/g, "").toUpperCase();
|
|
176
|
+
if (bic && !/^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/.test(bic)) problems.push({ field: "bic", code: "bic_format" });
|
|
177
|
+
if (input.amount !== void 0 && input.amount !== "") {
|
|
178
|
+
const amount = toNumber(input.amount);
|
|
179
|
+
if (Number.isNaN(amount)) problems.push({ field: "amount", code: "not_a_number" });
|
|
180
|
+
else if (amount < 0.01 || amount > 99999999999e-2) problems.push({ field: "amount", code: "out_of_range" });
|
|
181
|
+
}
|
|
182
|
+
const purpose = String(input.purpose ?? "");
|
|
183
|
+
if (purpose.length > 4) problems.push({ field: "purpose", code: "too_long", max: 4, actual: purpose.length });
|
|
184
|
+
const reference = String(input.reference ?? "").trim();
|
|
185
|
+
const text = String(input.text ?? "").trim();
|
|
186
|
+
if (reference && text) problems.push({ field: "reference", code: "reference_or_text" });
|
|
187
|
+
if (reference.length > 35)
|
|
188
|
+
problems.push({ field: "reference", code: "too_long", max: 35, actual: reference.length });
|
|
189
|
+
if (text.length > 140) problems.push({ field: "text", code: "too_long", max: 140, actual: text.length });
|
|
190
|
+
const bytes = byteLength(epcPayload(input));
|
|
191
|
+
if (bytes > EPC_MAX_BYTES)
|
|
192
|
+
problems.push({ field: "payload", code: "too_long", max: EPC_MAX_BYTES, actual: bytes });
|
|
193
|
+
return problems;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// packages/engine/src/lib/codes/qr.ts
|
|
80
197
|
import { create as createQr } from "qrcode";
|
|
81
198
|
import { render as renderQrSvg } from "qrcode/lib/renderer/svg-tag.js";
|
|
199
|
+
function qrSvg(value, options = {}) {
|
|
200
|
+
const qr = createQr(value, { errorCorrectionLevel: options.ecc ?? "M" });
|
|
201
|
+
return renderQrSvg(qr, {
|
|
202
|
+
width: options.size ?? 160,
|
|
203
|
+
margin: options.margin ?? 1,
|
|
204
|
+
color: {
|
|
205
|
+
dark: (options.color ?? "#000000").replace(/^(#?)/, "#").slice(0, 9),
|
|
206
|
+
light: (options.background ?? "#ffffff").replace(/^(#?)/, "#")
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
var svgDataUri = (svg) => `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
|
|
211
|
+
|
|
212
|
+
// packages/engine/src/lib/helpers/codes.ts
|
|
213
|
+
var barcodeTypes = {
|
|
214
|
+
code128: "code128",
|
|
215
|
+
ean13: "ean13",
|
|
216
|
+
ean8: "ean8",
|
|
217
|
+
upc: "upca",
|
|
218
|
+
upca: "upca",
|
|
219
|
+
itf14: "itf14",
|
|
220
|
+
code39: "code39",
|
|
221
|
+
qrcode: "qrcode",
|
|
222
|
+
datamatrix: "datamatrix",
|
|
223
|
+
pdf417: "pdf417"
|
|
224
|
+
};
|
|
225
|
+
var codeHelpers = [
|
|
226
|
+
{
|
|
227
|
+
name: "qrcode",
|
|
228
|
+
aliases: ["qr"],
|
|
229
|
+
doc: {
|
|
230
|
+
signature: "qrcode(value, { size = 160, ecc = 'M', margin = 1, color, background })",
|
|
231
|
+
description: "QR code as an SVG data URI for an <img> src.",
|
|
232
|
+
examples: {
|
|
233
|
+
jinja2: '<img src="{{ qrcode(order.url, { size: 120 }) }}">',
|
|
234
|
+
liquid: '<img src="{{ order.url | qrcode: size: 120 }}">',
|
|
235
|
+
handlebars: '<img src="{{qrcode order.url size=120}}">'
|
|
236
|
+
},
|
|
237
|
+
category: "code"
|
|
238
|
+
},
|
|
239
|
+
fn: (ctx, value, options) => {
|
|
240
|
+
const o = options ?? {};
|
|
241
|
+
const svg = qrSvg(String(value ?? ""), o);
|
|
242
|
+
if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, width: o.size, height: o.size, alt: "QR code" });
|
|
243
|
+
return svgDataUri(svg);
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "barcode",
|
|
248
|
+
doc: {
|
|
249
|
+
signature: "barcode(value, { type = 'code128', height = 12, width, text = true, scale = 2 })",
|
|
250
|
+
description: "Barcode (code128, ean13, ean8, upc, itf14, code39, datamatrix, pdf417) as an SVG data URI.",
|
|
251
|
+
examples: {
|
|
252
|
+
jinja2: `<img src="{{ barcode(item.sku, { type: 'code128' }) }}">`,
|
|
253
|
+
liquid: `<img src="{{ item.sku | barcode: type: 'code128' }}">`,
|
|
254
|
+
handlebars: `<img src="{{barcode item.sku type='code128'}}">`
|
|
255
|
+
},
|
|
256
|
+
category: "code"
|
|
257
|
+
},
|
|
258
|
+
fn: (ctx, value, options) => {
|
|
259
|
+
const o = options ?? {};
|
|
260
|
+
const bcid = barcodeTypes[String(o.type ?? "code128").toLowerCase()] ?? "code128";
|
|
261
|
+
const svg = toSVG({
|
|
262
|
+
bcid,
|
|
263
|
+
text: String(value ?? ""),
|
|
264
|
+
height: o.height ?? 12,
|
|
265
|
+
...o.width === void 0 ? {} : { width: o.width },
|
|
266
|
+
scale: o.scale ?? 2,
|
|
267
|
+
includetext: o.text !== false,
|
|
268
|
+
textxalign: "center"
|
|
269
|
+
});
|
|
270
|
+
if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, alt: `Barcode ${String(value ?? "")}` });
|
|
271
|
+
return svgDataUri(svg);
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "epcQr",
|
|
276
|
+
aliases: ["epc_qr", "girocode"],
|
|
277
|
+
doc: {
|
|
278
|
+
signature: "epcQr({ name, iban, bic?, amount?, reference?, text? }, { size })",
|
|
279
|
+
description: "SEPA credit transfer QR code (EPC069-12 / GiroCode) that banking apps scan to prefill a payment.",
|
|
280
|
+
examples: {
|
|
281
|
+
jinja2: '<img src="{{ epcQr({ name: company.name, iban: company.iban, amount: invoice.total, reference: invoice.number }) }}">',
|
|
282
|
+
liquid: '<img src="{{ company.iban | epcQr: name: company.name, amount: invoice.total, reference: invoice.number }}">',
|
|
283
|
+
handlebars: '<img src="{{epcQr name=company.name iban=company.iban amount=invoice.total reference=invoice.number}}">'
|
|
284
|
+
},
|
|
285
|
+
category: "code"
|
|
286
|
+
},
|
|
287
|
+
// Payment fields and QR options may arrive together: Jinja2 passes two objects, Handlebars one
|
|
288
|
+
// hash (`{{epcQr name=… iban=… size=200}}`), and Liquid, which cannot build an object literal,
|
|
289
|
+
// keyword arguments (`{{ company.iban | epcQr: name: company.name, amount: invoice.total }}`,
|
|
290
|
+
// where a string input is the IBAN). They are merged and split by field name.
|
|
291
|
+
fn: (ctx, input, options) => {
|
|
292
|
+
const fields = {
|
|
293
|
+
...input && typeof input === "object" ? input : typeof input === "string" && input ? { iban: input } : {},
|
|
294
|
+
...options ?? {}
|
|
295
|
+
};
|
|
296
|
+
const payment = {};
|
|
297
|
+
const qr = {};
|
|
298
|
+
for (const [key, value] of Object.entries(fields))
|
|
299
|
+
(EPC_FIELDS.has(key) ? payment : qr)[key] = value;
|
|
300
|
+
const o = { ecc: "M", ...qr };
|
|
301
|
+
const svg = qrSvg(epcPayload(payment), o);
|
|
302
|
+
if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, width: o.size, height: o.size, alt: "GiroCode" });
|
|
303
|
+
return svgDataUri(svg);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
];
|
|
307
|
+
var EPC_FIELDS = /* @__PURE__ */ new Set(["name", "iban", "bic", "amount", "reference", "text", "purpose"]);
|
|
82
308
|
|
|
83
309
|
// packages/engine/src/lib/helpers/format.ts
|
|
84
310
|
import { TZDate } from "@date-fns/tz";
|
|
@@ -112,15 +338,6 @@ var dateLocales = {
|
|
|
112
338
|
function dateLocale(locale) {
|
|
113
339
|
return dateLocales[locale] ?? dateLocales[locale.split("-")[0] ?? ""] ?? enGB;
|
|
114
340
|
}
|
|
115
|
-
function toNumber(value) {
|
|
116
|
-
if (typeof value === "number") return value;
|
|
117
|
-
if (typeof value === "string") {
|
|
118
|
-
const n = Number(value.replace(/\s/g, "").replace(",", "."));
|
|
119
|
-
return Number.isFinite(n) ? n : NaN;
|
|
120
|
-
}
|
|
121
|
-
if (typeof value === "bigint") return Number(value);
|
|
122
|
-
return NaN;
|
|
123
|
-
}
|
|
124
341
|
function toDate(value, tz) {
|
|
125
342
|
if (value === "now" || value === void 0 || value === null || value === "")
|
|
126
343
|
return new TZDate(Date.now(), tz);
|
|
@@ -143,6 +360,10 @@ function escapeHtml(value) {
|
|
|
143
360
|
var isNamedArgs = (v) => !!v && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date);
|
|
144
361
|
var str = (v) => v === void 0 || v === null ? "" : String(v);
|
|
145
362
|
var opt = (v, fallback) => v === void 0 || v === null || v === "" ? fallback : v;
|
|
363
|
+
function requireFinite(helper, value) {
|
|
364
|
+
if (typeof value === "number" && !Number.isFinite(value))
|
|
365
|
+
throw new Error(`${helper}: got ${value}, the result of a calculation with a value that is missing or not a number`);
|
|
366
|
+
}
|
|
146
367
|
var wordsByLang = {
|
|
147
368
|
de: deWords,
|
|
148
369
|
en: enWords,
|
|
@@ -158,11 +379,16 @@ var formatHelpers = [
|
|
|
158
379
|
deprecatedAlias: { currency_format: "money" },
|
|
159
380
|
doc: {
|
|
160
381
|
signature: "money(value, currency?, locale?)",
|
|
161
|
-
description: "Formats a number as currency with Intl.NumberFormat. Defaults come from the template settings.",
|
|
162
|
-
|
|
382
|
+
description: "Formats a number as currency with Intl.NumberFormat. Defaults come from the template settings. Text that is not a number is printed as it is; a calculation that came out as NaN stops the render.",
|
|
383
|
+
examples: {
|
|
384
|
+
jinja2: "{{ line.total | money('EUR') }} \u2192 1.234,50 \u20AC",
|
|
385
|
+
liquid: "{{ line.total | money: 'EUR' }} \u2192 1.234,50 \u20AC",
|
|
386
|
+
handlebars: "{{money line.total 'EUR'}} \u2192 1.234,50 \u20AC"
|
|
387
|
+
},
|
|
163
388
|
category: "format"
|
|
164
389
|
},
|
|
165
390
|
fn: (ctx, value, currency, locale) => {
|
|
391
|
+
requireFinite("money", value);
|
|
166
392
|
const n = toNumber(value);
|
|
167
393
|
if (Number.isNaN(n)) return str(value);
|
|
168
394
|
return new Intl.NumberFormat(opt(locale, ctx.locale), {
|
|
@@ -176,10 +402,15 @@ var formatHelpers = [
|
|
|
176
402
|
doc: {
|
|
177
403
|
signature: "number(value, decimals?, locale?)",
|
|
178
404
|
description: "Formats a number with grouping. With decimals, exactly that many (rounded half away from zero); without, the decimals the number has, at most three, so 1234.5 \u2192 1.234,5 and 2.34567 \u2192 2,346. Use number(0) for whole numbers.",
|
|
179
|
-
|
|
405
|
+
examples: {
|
|
406
|
+
jinja2: "{{ qty | number(2) }} \u2192 1.234,00",
|
|
407
|
+
liquid: "{{ qty | number: 2 }} \u2192 1.234,00",
|
|
408
|
+
handlebars: "{{number qty 2}} \u2192 1.234,00"
|
|
409
|
+
},
|
|
180
410
|
category: "format"
|
|
181
411
|
},
|
|
182
412
|
fn: (ctx, value, decimals, locale) => {
|
|
413
|
+
requireFinite("number", value);
|
|
183
414
|
const n = toNumber(value);
|
|
184
415
|
if (Number.isNaN(n)) return str(value);
|
|
185
416
|
const d = decimals === void 0 || decimals === null ? void 0 : Number(decimals);
|
|
@@ -194,7 +425,11 @@ var formatHelpers = [
|
|
|
194
425
|
doc: {
|
|
195
426
|
signature: "date(value, format?, tz?, locale?)",
|
|
196
427
|
description: "Formats a date with date-fns tokens (e.g. 'dd.MM.yyyy', 'PPP'). Accepts ISO strings, epoch numbers and 'now'.",
|
|
197
|
-
|
|
428
|
+
examples: {
|
|
429
|
+
jinja2: "{{ invoice.date | date('dd.MM.yyyy') }}",
|
|
430
|
+
liquid: "{{ invoice.date | date: 'dd.MM.yyyy' }}",
|
|
431
|
+
handlebars: "{{date invoice.date 'dd.MM.yyyy'}}"
|
|
432
|
+
},
|
|
198
433
|
category: "format"
|
|
199
434
|
},
|
|
200
435
|
fn: (ctx, value, format, tz, locale) => {
|
|
@@ -211,7 +446,11 @@ var formatHelpers = [
|
|
|
211
446
|
doc: {
|
|
212
447
|
signature: "dateAdd(value, amount, unit)",
|
|
213
448
|
description: "Adds days, weeks, months or years to a date; returns an ISO string for further formatting.",
|
|
214
|
-
|
|
449
|
+
examples: {
|
|
450
|
+
jinja2: "{{ invoice.date | dateAdd(14, 'days') | date('PP') }}",
|
|
451
|
+
liquid: "{{ invoice.date | dateAdd: 14, 'days' | date: 'PP' }}",
|
|
452
|
+
handlebars: "{{date (dateAdd invoice.date 14 'days') 'PP'}}"
|
|
453
|
+
},
|
|
215
454
|
category: "format"
|
|
216
455
|
},
|
|
217
456
|
fn: (ctx, value, amount, unit) => {
|
|
@@ -228,7 +467,11 @@ var formatHelpers = [
|
|
|
228
467
|
doc: {
|
|
229
468
|
signature: "add(a, b)",
|
|
230
469
|
description: "a + b (Handlebars has no arithmetic of its own).",
|
|
231
|
-
|
|
470
|
+
examples: {
|
|
471
|
+
jinja2: "{{ line.qty | add(1) }}",
|
|
472
|
+
liquid: "{{ line.qty | add: 1 }}",
|
|
473
|
+
handlebars: "{{#each invoice.lines}}{{add @index 1}}{{/each}}"
|
|
474
|
+
},
|
|
232
475
|
category: "format"
|
|
233
476
|
},
|
|
234
477
|
fn: (_ctx, a, b) => toNumber(a) + toNumber(b)
|
|
@@ -239,7 +482,11 @@ var formatHelpers = [
|
|
|
239
482
|
doc: {
|
|
240
483
|
signature: "subtract(a, b)",
|
|
241
484
|
description: "a minus b.",
|
|
242
|
-
|
|
485
|
+
examples: {
|
|
486
|
+
jinja2: "{{ total | subtract(discount) }}",
|
|
487
|
+
liquid: "{{ total | subtract: discount }}",
|
|
488
|
+
handlebars: "{{subtract total discount}}"
|
|
489
|
+
},
|
|
243
490
|
category: "format"
|
|
244
491
|
},
|
|
245
492
|
fn: (_ctx, a, b) => toNumber(a) - toNumber(b)
|
|
@@ -250,7 +497,11 @@ var formatHelpers = [
|
|
|
250
497
|
doc: {
|
|
251
498
|
signature: "multiply(a, b)",
|
|
252
499
|
description: "a times b.",
|
|
253
|
-
|
|
500
|
+
examples: {
|
|
501
|
+
jinja2: "{{ line.qty | multiply(line.price) | money }}",
|
|
502
|
+
liquid: "{{ line.qty | multiply: line.price | money }}",
|
|
503
|
+
handlebars: "{{money (multiply line.qty line.price)}}"
|
|
504
|
+
},
|
|
254
505
|
category: "format"
|
|
255
506
|
},
|
|
256
507
|
fn: (_ctx, a, b) => toNumber(a) * toNumber(b)
|
|
@@ -261,7 +512,11 @@ var formatHelpers = [
|
|
|
261
512
|
doc: {
|
|
262
513
|
signature: "divide(a, b)",
|
|
263
514
|
description: "a divided by b; empty when b is 0.",
|
|
264
|
-
|
|
515
|
+
examples: {
|
|
516
|
+
jinja2: "{{ total | divide(count) }}",
|
|
517
|
+
liquid: "{{ total | divide: count }}",
|
|
518
|
+
handlebars: "{{divide total count}}"
|
|
519
|
+
},
|
|
265
520
|
category: "format"
|
|
266
521
|
},
|
|
267
522
|
fn: (_ctx, a, b) => toNumber(b) === 0 ? "" : toNumber(a) / toNumber(b)
|
|
@@ -271,7 +526,11 @@ var formatHelpers = [
|
|
|
271
526
|
doc: {
|
|
272
527
|
signature: "round(value, decimals = 0, method = 'common')",
|
|
273
528
|
description: "Rounds to the given decimals, as Jinja2's round does: 'common' rounds half away from zero, 'floor' always down and 'ceil' always up.",
|
|
274
|
-
|
|
529
|
+
examples: {
|
|
530
|
+
jinja2: "{{ 2.345 | round(2) }} gives 2.35, {{ 2.349 | round(2, 'floor') }} gives 2.34",
|
|
531
|
+
liquid: "{{ 2.345 | round: 2 }} gives 2.35, {{ 2.349 | round: 2, 'floor' }} gives 2.34",
|
|
532
|
+
handlebars: "{{round 2.345 2}} gives 2.35, {{round 2.349 2 'floor'}} gives 2.34"
|
|
533
|
+
},
|
|
275
534
|
category: "format"
|
|
276
535
|
},
|
|
277
536
|
// `round(2, method='floor')` (Jinja2) and `round: 2, method: 'floor'` (Liquid) arrive as a
|
|
@@ -297,7 +556,11 @@ var formatHelpers = [
|
|
|
297
556
|
doc: {
|
|
298
557
|
signature: "upper(value)",
|
|
299
558
|
description: "Upper-cases a string.",
|
|
300
|
-
|
|
559
|
+
examples: {
|
|
560
|
+
jinja2: "{{ name | upper }}",
|
|
561
|
+
liquid: "{{ name | upper }}",
|
|
562
|
+
handlebars: "{{upper name}}"
|
|
563
|
+
},
|
|
301
564
|
category: "text"
|
|
302
565
|
},
|
|
303
566
|
fn: (_ctx, value) => str(value).toUpperCase()
|
|
@@ -308,7 +571,11 @@ var formatHelpers = [
|
|
|
308
571
|
doc: {
|
|
309
572
|
signature: "lower(value)",
|
|
310
573
|
description: "Lower-cases a string.",
|
|
311
|
-
|
|
574
|
+
examples: {
|
|
575
|
+
jinja2: "{{ name | lower }}",
|
|
576
|
+
liquid: "{{ name | lower }}",
|
|
577
|
+
handlebars: "{{lower name}}"
|
|
578
|
+
},
|
|
312
579
|
category: "text"
|
|
313
580
|
},
|
|
314
581
|
fn: (_ctx, value) => str(value).toLowerCase()
|
|
@@ -319,7 +586,11 @@ var formatHelpers = [
|
|
|
319
586
|
doc: {
|
|
320
587
|
signature: "title(value)",
|
|
321
588
|
description: "Capitalises every word.",
|
|
322
|
-
|
|
589
|
+
examples: {
|
|
590
|
+
jinja2: "{{ 'hello world' | title }} \u2192 Hello World",
|
|
591
|
+
liquid: "{{ 'hello world' | title }} \u2192 Hello World",
|
|
592
|
+
handlebars: "{{title 'hello world'}} \u2192 Hello World"
|
|
593
|
+
},
|
|
323
594
|
category: "text"
|
|
324
595
|
},
|
|
325
596
|
fn: (_ctx, value) => str(value).replace(
|
|
@@ -332,7 +603,11 @@ var formatHelpers = [
|
|
|
332
603
|
doc: {
|
|
333
604
|
signature: "truncate(value, length = 255, suffix = '\u2026')",
|
|
334
605
|
description: "Cuts a string to `length` characters and appends the suffix when cut.",
|
|
335
|
-
|
|
606
|
+
examples: {
|
|
607
|
+
jinja2: "{{ description | truncate(40) }}",
|
|
608
|
+
liquid: "{{ description | truncate: 40 }}",
|
|
609
|
+
handlebars: "{{truncate description 40}}"
|
|
610
|
+
},
|
|
336
611
|
category: "text"
|
|
337
612
|
},
|
|
338
613
|
fn: (_ctx, value, length, suffix) => {
|
|
@@ -347,7 +622,11 @@ var formatHelpers = [
|
|
|
347
622
|
doc: {
|
|
348
623
|
signature: "replace(value, search, replacement)",
|
|
349
624
|
description: "Replaces every occurrence of `search`.",
|
|
350
|
-
|
|
625
|
+
examples: {
|
|
626
|
+
jinja2: "{{ iban | replace(' ', '') }}",
|
|
627
|
+
liquid: "{{ iban | replace: ' ', '' }}",
|
|
628
|
+
handlebars: "{{replace iban ' ' ''}}"
|
|
629
|
+
},
|
|
351
630
|
category: "text"
|
|
352
631
|
},
|
|
353
632
|
fn: (_ctx, value, search, replacement) => str(value).split(str(search)).join(str(replacement))
|
|
@@ -357,7 +636,11 @@ var formatHelpers = [
|
|
|
357
636
|
doc: {
|
|
358
637
|
signature: "split(value, separator = ',')",
|
|
359
638
|
description: "Splits a string into a list.",
|
|
360
|
-
|
|
639
|
+
examples: {
|
|
640
|
+
jinja2: "{% for tag in keywords | split(',') %}{{ tag }}{% endfor %}",
|
|
641
|
+
liquid: "{% assign tags = keywords | split: ',' %}{% for tag in tags %}{{ tag }}{% endfor %}",
|
|
642
|
+
handlebars: "{{#each (split keywords ',')}}{{this}}{{/each}}"
|
|
643
|
+
},
|
|
361
644
|
category: "text"
|
|
362
645
|
},
|
|
363
646
|
fn: (_ctx, value, separator) => str(value).split(str(opt(separator, ",")))
|
|
@@ -367,7 +650,11 @@ var formatHelpers = [
|
|
|
367
650
|
doc: {
|
|
368
651
|
signature: "join(list, separator = ', ')",
|
|
369
652
|
description: "Joins a list into a string.",
|
|
370
|
-
|
|
653
|
+
examples: {
|
|
654
|
+
jinja2: "{{ tags | join(' \xB7 ') }}",
|
|
655
|
+
liquid: "{{ tags | join: ' \xB7 ' }}",
|
|
656
|
+
handlebars: "{{join tags ' \xB7 '}}"
|
|
657
|
+
},
|
|
371
658
|
category: "collection"
|
|
372
659
|
},
|
|
373
660
|
fn: (_ctx, value, separator) => Array.isArray(value) ? value.map(str).join(str(opt(separator, ", "))) : str(value)
|
|
@@ -378,7 +665,11 @@ var formatHelpers = [
|
|
|
378
665
|
doc: {
|
|
379
666
|
signature: "default(value, fallback)",
|
|
380
667
|
description: "Returns the fallback when the value is empty, null or undefined.",
|
|
381
|
-
|
|
668
|
+
examples: {
|
|
669
|
+
jinja2: "{{ customer.vat | default('\u2014') }}",
|
|
670
|
+
liquid: "{{ customer.vat | default: '\u2014' }}",
|
|
671
|
+
handlebars: "{{default customer.vat '\u2014'}}"
|
|
672
|
+
},
|
|
382
673
|
category: "text"
|
|
383
674
|
},
|
|
384
675
|
fn: (_ctx, value, fallback) => value === void 0 || value === null || value === "" ? fallback : value
|
|
@@ -389,7 +680,11 @@ var formatHelpers = [
|
|
|
389
680
|
doc: {
|
|
390
681
|
signature: "nl2br(value)",
|
|
391
682
|
description: "Escapes the text and turns line breaks into <br>.",
|
|
392
|
-
|
|
683
|
+
examples: {
|
|
684
|
+
jinja2: "{{ address | nl2br }}",
|
|
685
|
+
liquid: "{{ address | nl2br }}",
|
|
686
|
+
handlebars: "{{nl2br address}}"
|
|
687
|
+
},
|
|
393
688
|
category: "text"
|
|
394
689
|
},
|
|
395
690
|
// office templates turn line breaks into breaks themselves, and escape the text once
|
|
@@ -401,7 +696,11 @@ var formatHelpers = [
|
|
|
401
696
|
doc: {
|
|
402
697
|
signature: "markdown(value)",
|
|
403
698
|
description: "Renders Markdown to HTML (no sanitiser: template data is trusted by the author).",
|
|
404
|
-
|
|
699
|
+
examples: {
|
|
700
|
+
jinja2: "{{ notes | markdown }}",
|
|
701
|
+
liquid: "{{ notes | markdown }}",
|
|
702
|
+
handlebars: "{{markdown notes}}"
|
|
703
|
+
},
|
|
405
704
|
category: "text"
|
|
406
705
|
},
|
|
407
706
|
fn: (ctx, value) => {
|
|
@@ -420,7 +719,11 @@ var formatHelpers = [
|
|
|
420
719
|
doc: {
|
|
421
720
|
signature: "safe(value)",
|
|
422
721
|
description: "Marks a string as HTML so the engine does not escape it.",
|
|
423
|
-
|
|
722
|
+
examples: {
|
|
723
|
+
jinja2: "{{ html_snippet | safe }}",
|
|
724
|
+
liquid: "{{ html_snippet | safe }}",
|
|
725
|
+
handlebars: "{{safe html_snippet}}"
|
|
726
|
+
},
|
|
424
727
|
category: "text"
|
|
425
728
|
},
|
|
426
729
|
fn: (_ctx, value) => str(value)
|
|
@@ -431,7 +734,11 @@ var formatHelpers = [
|
|
|
431
734
|
doc: {
|
|
432
735
|
signature: "json(value, indent = 2)",
|
|
433
736
|
description: "Serialises a value as JSON; handy while building a template.",
|
|
434
|
-
|
|
737
|
+
examples: {
|
|
738
|
+
jinja2: "<pre>{{ invoice | json }}</pre>",
|
|
739
|
+
liquid: "<pre>{{ invoice | json }}</pre>",
|
|
740
|
+
handlebars: "<pre>{{json invoice}}</pre>"
|
|
741
|
+
},
|
|
435
742
|
category: "debug"
|
|
436
743
|
},
|
|
437
744
|
fn: (_ctx, value, indent) => JSON.stringify(value, null, opt(Number(indent), 2))
|
|
@@ -442,10 +749,15 @@ var formatHelpers = [
|
|
|
442
749
|
doc: {
|
|
443
750
|
signature: "numToWords(value, locale?)",
|
|
444
751
|
description: "Spells a number out in words (de, en, fr, es, it, nl).",
|
|
445
|
-
|
|
752
|
+
examples: {
|
|
753
|
+
jinja2: "{{ total | numToWords('de') }} \u2192 zweihundertdreiundvierzig",
|
|
754
|
+
liquid: "{{ total | numToWords: 'de' }} \u2192 zweihundertdreiundvierzig",
|
|
755
|
+
handlebars: "{{numToWords total 'de'}} \u2192 zweihundertdreiundvierzig"
|
|
756
|
+
},
|
|
446
757
|
category: "format"
|
|
447
758
|
},
|
|
448
759
|
fn: (ctx, value, locale) => {
|
|
760
|
+
requireFinite("numToWords", value);
|
|
449
761
|
const n = toNumber(value);
|
|
450
762
|
if (Number.isNaN(n)) return str(value);
|
|
451
763
|
const lang = str(opt(locale, ctx.locale)).split("-")[0] ?? "en";
|
|
@@ -458,7 +770,11 @@ var formatHelpers = [
|
|
|
458
770
|
doc: {
|
|
459
771
|
signature: "t(key, params?)",
|
|
460
772
|
description: "Looks a key up in the template's i18n dictionary for the request locale; `{name}` placeholders come from params.",
|
|
461
|
-
|
|
773
|
+
examples: {
|
|
774
|
+
jinja2: "{{ t('invoice.title', { number: invoice.number }) }}",
|
|
775
|
+
liquid: "{{ 'invoice.title' | t: number: invoice.number }}",
|
|
776
|
+
handlebars: "{{t 'invoice.title' number=invoice.number}}"
|
|
777
|
+
},
|
|
462
778
|
category: "text"
|
|
463
779
|
},
|
|
464
780
|
fn: (ctx, key, params) => {
|
|
@@ -474,7 +790,11 @@ var formatHelpers = [
|
|
|
474
790
|
doc: {
|
|
475
791
|
signature: "asset(name)",
|
|
476
792
|
description: "URL of a file in the workspace library (logo, background) on the CDN. Upload it on the Files page or with `POST /files` and use the name it has there; relative image paths resolve against the same library.",
|
|
477
|
-
|
|
793
|
+
examples: {
|
|
794
|
+
jinja2: `<img src="{{ asset('logo.png') }}">`,
|
|
795
|
+
liquid: `<img src="{{ 'logo.png' | asset }}">`,
|
|
796
|
+
handlebars: `<img src="{{asset 'logo.png'}}">`
|
|
797
|
+
},
|
|
478
798
|
category: "document"
|
|
479
799
|
},
|
|
480
800
|
fn: (ctx, name) => `${(ctx.assetBaseUrl ?? "").replace(/\/$/, "")}/${str(name).replace(/^\//, "")}`
|
|
@@ -486,128 +806,17 @@ var formatHelpers = [
|
|
|
486
806
|
doc: {
|
|
487
807
|
signature: "pageBreak()",
|
|
488
808
|
description: "Starts a new page in PDF output.",
|
|
489
|
-
|
|
809
|
+
examples: {
|
|
810
|
+
jinja2: "{{ pageBreak() }}",
|
|
811
|
+
liquid: "{{ '' | pageBreak }}",
|
|
812
|
+
handlebars: "{{pageBreak}}"
|
|
813
|
+
},
|
|
490
814
|
category: "document"
|
|
491
815
|
},
|
|
492
816
|
fn: (ctx) => ctx.drawing ? ctx.drawing({ kind: "page-break" }) : '<div class="page-break" style="break-after:page"></div>'
|
|
493
817
|
}
|
|
494
818
|
];
|
|
495
819
|
|
|
496
|
-
// packages/engine/src/lib/helpers/codes.ts
|
|
497
|
-
var svgDataUri = (svg) => `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
|
|
498
|
-
var barcodeTypes = {
|
|
499
|
-
code128: "code128",
|
|
500
|
-
ean13: "ean13",
|
|
501
|
-
ean8: "ean8",
|
|
502
|
-
upc: "upca",
|
|
503
|
-
upca: "upca",
|
|
504
|
-
itf14: "itf14",
|
|
505
|
-
code39: "code39",
|
|
506
|
-
qrcode: "qrcode",
|
|
507
|
-
datamatrix: "datamatrix",
|
|
508
|
-
pdf417: "pdf417"
|
|
509
|
-
};
|
|
510
|
-
function qrSvg(value, options = {}) {
|
|
511
|
-
const qr = createQr(value, { errorCorrectionLevel: options.ecc ?? "M" });
|
|
512
|
-
const svg = renderQrSvg(qr, {
|
|
513
|
-
width: options.size ?? 160,
|
|
514
|
-
margin: options.margin ?? 1,
|
|
515
|
-
color: {
|
|
516
|
-
dark: (options.color ?? "#000000").replace(/^(#?)/, "#").slice(0, 9),
|
|
517
|
-
light: (options.background ?? "#ffffff").replace(/^(#?)/, "#")
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
return svg;
|
|
521
|
-
}
|
|
522
|
-
function epcPayload(input) {
|
|
523
|
-
const amount = input.amount === void 0 || input.amount === "" ? "" : `EUR${toNumber(input.amount).toFixed(2)}`;
|
|
524
|
-
return [
|
|
525
|
-
"BCD",
|
|
526
|
-
"002",
|
|
527
|
-
"1",
|
|
528
|
-
"SCT",
|
|
529
|
-
(input.bic ?? "").replace(/\s/g, ""),
|
|
530
|
-
String(input.name ?? "").slice(0, 70),
|
|
531
|
-
String(input.iban ?? "").replace(/\s/g, ""),
|
|
532
|
-
amount,
|
|
533
|
-
(input.purpose ?? "").slice(0, 4),
|
|
534
|
-
(input.reference ?? "").slice(0, 35),
|
|
535
|
-
input.reference ? "" : (input.text ?? "").slice(0, 140),
|
|
536
|
-
""
|
|
537
|
-
].join("\n");
|
|
538
|
-
}
|
|
539
|
-
var codeHelpers = [
|
|
540
|
-
{
|
|
541
|
-
name: "qrcode",
|
|
542
|
-
aliases: ["qr"],
|
|
543
|
-
doc: {
|
|
544
|
-
signature: "qrcode(value, { size = 160, ecc = 'M', margin = 1, color, background })",
|
|
545
|
-
description: "QR code as an SVG data URI for an <img> src.",
|
|
546
|
-
example: '<img src="{{ qrcode(order.url, { size: 120 }) }}">',
|
|
547
|
-
category: "code"
|
|
548
|
-
},
|
|
549
|
-
fn: (ctx, value, options) => {
|
|
550
|
-
const o = options ?? {};
|
|
551
|
-
const svg = qrSvg(String(value ?? ""), o);
|
|
552
|
-
if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, width: o.size, height: o.size, alt: "QR code" });
|
|
553
|
-
return svgDataUri(svg);
|
|
554
|
-
}
|
|
555
|
-
},
|
|
556
|
-
{
|
|
557
|
-
name: "barcode",
|
|
558
|
-
doc: {
|
|
559
|
-
signature: "barcode(value, { type = 'code128', height = 12, width, text = true, scale = 2 })",
|
|
560
|
-
description: "Barcode (code128, ean13, ean8, upc, itf14, code39, datamatrix, pdf417) as an SVG data URI.",
|
|
561
|
-
example: `<img src="{{ barcode(item.sku, { type: 'code128' }) }}">`,
|
|
562
|
-
category: "code"
|
|
563
|
-
},
|
|
564
|
-
fn: (ctx, value, options) => {
|
|
565
|
-
const o = options ?? {};
|
|
566
|
-
const bcid = barcodeTypes[String(o.type ?? "code128").toLowerCase()] ?? "code128";
|
|
567
|
-
const svg = toSVG({
|
|
568
|
-
bcid,
|
|
569
|
-
text: String(value ?? ""),
|
|
570
|
-
height: o.height ?? 12,
|
|
571
|
-
...o.width === void 0 ? {} : { width: o.width },
|
|
572
|
-
scale: o.scale ?? 2,
|
|
573
|
-
includetext: o.text !== false,
|
|
574
|
-
textxalign: "center"
|
|
575
|
-
});
|
|
576
|
-
if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, alt: `Barcode ${String(value ?? "")}` });
|
|
577
|
-
return svgDataUri(svg);
|
|
578
|
-
}
|
|
579
|
-
},
|
|
580
|
-
{
|
|
581
|
-
name: "epcQr",
|
|
582
|
-
aliases: ["epc_qr", "girocode"],
|
|
583
|
-
doc: {
|
|
584
|
-
signature: "epcQr({ name, iban, bic?, amount?, reference?, text? }, { size })",
|
|
585
|
-
description: "SEPA credit transfer QR code (EPC069-12 / GiroCode) that banking apps scan to prefill a payment.",
|
|
586
|
-
example: '<img src="{{ epcQr({ name: company.name, iban: company.iban, amount: invoice.total, reference: invoice.number }) }}">',
|
|
587
|
-
category: "code"
|
|
588
|
-
},
|
|
589
|
-
// Payment fields and QR options may arrive together: Jinja2 passes two objects, Handlebars one
|
|
590
|
-
// hash (`{{epcQr name=… iban=… size=200}}`), and Liquid, which cannot build an object literal,
|
|
591
|
-
// keyword arguments (`{{ company.iban | epcQr: name: company.name, amount: invoice.total }}`,
|
|
592
|
-
// where a string input is the IBAN). They are merged and split by field name.
|
|
593
|
-
fn: (ctx, input, options) => {
|
|
594
|
-
const fields = {
|
|
595
|
-
...input && typeof input === "object" ? input : typeof input === "string" && input ? { iban: input } : {},
|
|
596
|
-
...options ?? {}
|
|
597
|
-
};
|
|
598
|
-
const payment = {};
|
|
599
|
-
const qr = {};
|
|
600
|
-
for (const [key, value] of Object.entries(fields))
|
|
601
|
-
(EPC_FIELDS.has(key) ? payment : qr)[key] = value;
|
|
602
|
-
const o = { ecc: "M", ...qr };
|
|
603
|
-
const svg = qrSvg(epcPayload(payment), o);
|
|
604
|
-
if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, width: o.size, height: o.size, alt: "GiroCode" });
|
|
605
|
-
return svgDataUri(svg);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
];
|
|
609
|
-
var EPC_FIELDS = /* @__PURE__ */ new Set(["name", "iban", "bic", "amount", "reference", "text", "purpose"]);
|
|
610
|
-
|
|
611
820
|
// packages/engine/src/lib/helpers/collections.ts
|
|
612
821
|
var list = (v) => Array.isArray(v) ? v : v === void 0 || v === null ? [] : [v];
|
|
613
822
|
var get = (item, path) => String(path ?? "").split(".").filter(Boolean).reduce(
|
|
@@ -621,7 +830,11 @@ var collectionHelpers = [
|
|
|
621
830
|
doc: {
|
|
622
831
|
signature: "sum(list, path?)",
|
|
623
832
|
description: "Sum of the items, or of `path` inside each item.",
|
|
624
|
-
|
|
833
|
+
examples: {
|
|
834
|
+
jinja2: "{{ invoice.lines | sum('total') | money }}",
|
|
835
|
+
liquid: "{{ invoice.lines | sum: 'total' | money }}",
|
|
836
|
+
handlebars: "{{money (sum invoice.lines 'total')}}"
|
|
837
|
+
},
|
|
625
838
|
category: "collection"
|
|
626
839
|
},
|
|
627
840
|
fn: (_ctx, value, path) => numbers(value, path).reduce((a, b) => a + b, 0)
|
|
@@ -631,7 +844,11 @@ var collectionHelpers = [
|
|
|
631
844
|
doc: {
|
|
632
845
|
signature: "avg(list, path?)",
|
|
633
846
|
description: "Average of the items or of `path` inside each item.",
|
|
634
|
-
|
|
847
|
+
examples: {
|
|
848
|
+
jinja2: "{{ scores | avg | number(1) }}",
|
|
849
|
+
liquid: "{{ scores | avg | number: 1 }}",
|
|
850
|
+
handlebars: "{{number (avg scores) 1}}"
|
|
851
|
+
},
|
|
635
852
|
category: "collection"
|
|
636
853
|
},
|
|
637
854
|
fn: (_ctx, value, path) => {
|
|
@@ -644,7 +861,11 @@ var collectionHelpers = [
|
|
|
644
861
|
doc: {
|
|
645
862
|
signature: "min(list, path?)",
|
|
646
863
|
description: "Smallest number.",
|
|
647
|
-
|
|
864
|
+
examples: {
|
|
865
|
+
jinja2: "{{ prices | min }}",
|
|
866
|
+
liquid: "{{ prices | min }}",
|
|
867
|
+
handlebars: "{{min prices}}"
|
|
868
|
+
},
|
|
648
869
|
category: "collection"
|
|
649
870
|
},
|
|
650
871
|
fn: (_ctx, value, path) => Math.min(...numbers(value, path))
|
|
@@ -654,7 +875,11 @@ var collectionHelpers = [
|
|
|
654
875
|
doc: {
|
|
655
876
|
signature: "max(list, path?)",
|
|
656
877
|
description: "Largest number.",
|
|
657
|
-
|
|
878
|
+
examples: {
|
|
879
|
+
jinja2: "{{ prices | max }}",
|
|
880
|
+
liquid: "{{ prices | max }}",
|
|
881
|
+
handlebars: "{{max prices}}"
|
|
882
|
+
},
|
|
658
883
|
category: "collection"
|
|
659
884
|
},
|
|
660
885
|
fn: (_ctx, value, path) => Math.max(...numbers(value, path))
|
|
@@ -665,7 +890,11 @@ var collectionHelpers = [
|
|
|
665
890
|
doc: {
|
|
666
891
|
signature: "groupBy(list, path)",
|
|
667
892
|
description: "Groups items by a field; returns a list of `{ key, items }`.",
|
|
668
|
-
|
|
893
|
+
examples: {
|
|
894
|
+
jinja2: "{% for group in lines | groupBy('category') %}{{ group.key }}{% endfor %}",
|
|
895
|
+
liquid: "{% assign groups = lines | groupBy: 'category' %}{% for group in groups %}{{ group.key }}{% endfor %}",
|
|
896
|
+
handlebars: "{{#each (groupBy lines 'category')}}{{key}}{{/each}}"
|
|
897
|
+
},
|
|
669
898
|
category: "collection"
|
|
670
899
|
},
|
|
671
900
|
fn: (_ctx, value, path) => {
|
|
@@ -683,7 +912,11 @@ var collectionHelpers = [
|
|
|
683
912
|
doc: {
|
|
684
913
|
signature: "sortBy(list, path?, direction = 'asc')",
|
|
685
914
|
description: "Sorts items by a field (numbers numerically, strings by locale).",
|
|
686
|
-
|
|
915
|
+
examples: {
|
|
916
|
+
jinja2: "{% for l in lines | sortBy('position') %}{{ l.sku }}{% endfor %}",
|
|
917
|
+
liquid: "{% assign sorted = lines | sortBy: 'position' %}{% for l in sorted %}{{ l.sku }}{% endfor %}",
|
|
918
|
+
handlebars: "{{#each (sortBy lines 'position')}}{{sku}}{{/each}}"
|
|
919
|
+
},
|
|
687
920
|
category: "collection"
|
|
688
921
|
},
|
|
689
922
|
fn: (ctx, value, path, direction) => {
|
|
@@ -704,7 +937,11 @@ var collectionHelpers = [
|
|
|
704
937
|
doc: {
|
|
705
938
|
signature: "where(list, path, value?)",
|
|
706
939
|
description: "Keeps items whose field equals `value` (or is truthy when omitted).",
|
|
707
|
-
|
|
940
|
+
examples: {
|
|
941
|
+
jinja2: "{% for l in lines | where('taxable', true) %}{{ l.sku }}{% endfor %}",
|
|
942
|
+
liquid: "{% assign taxable = lines | where: 'taxable', true %}{% for l in taxable %}{{ l.sku }}{% endfor %}",
|
|
943
|
+
handlebars: "{{#each (where lines 'taxable' true)}}{{sku}}{{/each}}"
|
|
944
|
+
},
|
|
708
945
|
category: "collection"
|
|
709
946
|
},
|
|
710
947
|
fn: (_ctx, value, path, expected) => list(value).filter((item) => {
|
|
@@ -718,7 +955,11 @@ var collectionHelpers = [
|
|
|
718
955
|
doc: {
|
|
719
956
|
signature: "pluck(list, path)",
|
|
720
957
|
description: "Extracts one field from every item.",
|
|
721
|
-
|
|
958
|
+
examples: {
|
|
959
|
+
jinja2: "{{ lines | pluck('sku') | join(', ') }}",
|
|
960
|
+
liquid: "{{ lines | pluck: 'sku' | join: ', ' }}",
|
|
961
|
+
handlebars: "{{join (pluck lines 'sku') ', '}}"
|
|
962
|
+
},
|
|
722
963
|
category: "collection"
|
|
723
964
|
},
|
|
724
965
|
fn: (_ctx, value, path) => list(value).map((item) => get(item, path))
|
|
@@ -729,7 +970,11 @@ var collectionHelpers = [
|
|
|
729
970
|
doc: {
|
|
730
971
|
signature: "chunk(list, size)",
|
|
731
972
|
description: "Splits a list into lists of `size` items (rows of labels, columns).",
|
|
732
|
-
|
|
973
|
+
examples: {
|
|
974
|
+
jinja2: "{% for row in labels | chunk(3) %}{{ row | join(', ') }}{% endfor %}",
|
|
975
|
+
liquid: "{% assign rows = labels | chunk: 3 %}{% for row in rows %}{{ row | join: ', ' }}{% endfor %}",
|
|
976
|
+
handlebars: "{{#each (chunk labels 3)}}{{join this ', '}}{{/each}}"
|
|
977
|
+
},
|
|
733
978
|
category: "collection"
|
|
734
979
|
},
|
|
735
980
|
fn: (_ctx, value, size) => {
|
|
@@ -745,7 +990,11 @@ var collectionHelpers = [
|
|
|
745
990
|
doc: {
|
|
746
991
|
signature: "range(start, end?, step = 1)",
|
|
747
992
|
description: "List of numbers from start to end (exclusive).",
|
|
748
|
-
|
|
993
|
+
examples: {
|
|
994
|
+
jinja2: "{% for i in range(1, 4) %}{{ i }}{% endfor %}",
|
|
995
|
+
liquid: "{% assign nums = 1 | range: 4 %}{% for i in nums %}{{ i }}{% endfor %}",
|
|
996
|
+
handlebars: "{{#each (range 1 4)}}{{this}}{{/each}}"
|
|
997
|
+
},
|
|
749
998
|
category: "collection"
|
|
750
999
|
},
|
|
751
1000
|
fn: (_ctx, start, end, step) => {
|
|
@@ -763,7 +1012,11 @@ var collectionHelpers = [
|
|
|
763
1012
|
doc: {
|
|
764
1013
|
signature: "first(list)",
|
|
765
1014
|
description: "First item.",
|
|
766
|
-
|
|
1015
|
+
examples: {
|
|
1016
|
+
jinja2: "{{ tags | first }}",
|
|
1017
|
+
liquid: "{{ tags | first }}",
|
|
1018
|
+
handlebars: "{{first tags}}"
|
|
1019
|
+
},
|
|
767
1020
|
category: "collection"
|
|
768
1021
|
},
|
|
769
1022
|
fn: (_ctx, value) => list(value)[0]
|
|
@@ -773,7 +1026,11 @@ var collectionHelpers = [
|
|
|
773
1026
|
doc: {
|
|
774
1027
|
signature: "last(list)",
|
|
775
1028
|
description: "Last item.",
|
|
776
|
-
|
|
1029
|
+
examples: {
|
|
1030
|
+
jinja2: "{{ tags | last }}",
|
|
1031
|
+
liquid: "{{ tags | last }}",
|
|
1032
|
+
handlebars: "{{last tags}}"
|
|
1033
|
+
},
|
|
777
1034
|
category: "collection"
|
|
778
1035
|
},
|
|
779
1036
|
fn: (_ctx, value) => list(value).at(-1)
|
|
@@ -784,7 +1041,11 @@ var collectionHelpers = [
|
|
|
784
1041
|
doc: {
|
|
785
1042
|
signature: "length(value)",
|
|
786
1043
|
description: "Number of items in a list, keys in an object or characters in a string.",
|
|
787
|
-
|
|
1044
|
+
examples: {
|
|
1045
|
+
jinja2: "{{ lines | length }}",
|
|
1046
|
+
liquid: "{{ lines | length }}",
|
|
1047
|
+
handlebars: "{{length lines}}"
|
|
1048
|
+
},
|
|
788
1049
|
category: "collection"
|
|
789
1050
|
},
|
|
790
1051
|
fn: (_ctx, value) => Array.isArray(value) || typeof value === "string" ? value.length : value && typeof value === "object" ? Object.keys(value).length : 0
|
|
@@ -860,7 +1121,11 @@ var documentHelpers = [
|
|
|
860
1121
|
doc: {
|
|
861
1122
|
signature: "chart({ type, labels, values, label, color } | { type, labels, series } | { type, data, options }, { width, height })",
|
|
862
1123
|
description: "Draws a Chart.js chart (bar, line, pie, doughnut, radar, \u2026) from plain data, `labels` with `values` or several `series` of `{ label, values, color }`, or from a full Chart.js `data` and `options`; animations are disabled so PDF and preview match.",
|
|
863
|
-
|
|
1124
|
+
examples: {
|
|
1125
|
+
jinja2: "{{ chart({ type: 'line', labels: sales.labels, values: sales.quarters, width: 480 }) }}",
|
|
1126
|
+
liquid: "{{ '' | chart: type: 'line', labels: sales.labels, values: sales.quarters, width: 480 }}",
|
|
1127
|
+
handlebars: "{{chart type='line' labels=sales.labels values=sales.quarters width=480}}"
|
|
1128
|
+
},
|
|
864
1129
|
category: "document"
|
|
865
1130
|
},
|
|
866
1131
|
fn: (ctx, spec, extra) => {
|
|
@@ -874,7 +1139,11 @@ var documentHelpers = [
|
|
|
874
1139
|
doc: {
|
|
875
1140
|
signature: "image(url, { width, height, fit, alt })",
|
|
876
1141
|
description: "Places an image with fixed size and crop mode (`cover`, `contain`, `fill`); the render-worker fetches it through its cache and blocks private hosts.",
|
|
877
|
-
|
|
1142
|
+
examples: {
|
|
1143
|
+
jinja2: "{{ image(product.photo, { width: 120, height: 80, fit: 'cover' }) }}",
|
|
1144
|
+
liquid: "{{ product.photo | image: width: 120, height: 80, fit: 'cover' }}",
|
|
1145
|
+
handlebars: "{{image product.photo width=120 height=80 fit='cover'}}"
|
|
1146
|
+
},
|
|
878
1147
|
category: "document"
|
|
879
1148
|
},
|
|
880
1149
|
fn: (ctx, url, options) => {
|
|
@@ -900,7 +1169,12 @@ var comparison = (name, test, description) => ({
|
|
|
900
1169
|
doc: {
|
|
901
1170
|
signature: `${name}(a, b)`,
|
|
902
1171
|
description,
|
|
903
|
-
|
|
1172
|
+
examples: {
|
|
1173
|
+
jinja2: `{% if ${name}(invoice.total, 1000) %}\u2026{% endif %}`,
|
|
1174
|
+
// Liquid takes no filter in a condition, so the comparison is assigned first
|
|
1175
|
+
liquid: `{% assign hit = invoice.total | ${name}: 1000 %}{% if hit %}\u2026{% endif %}`,
|
|
1176
|
+
handlebars: `{{#if (${name} invoice.total 1000)}}\u2026{{/if}}`
|
|
1177
|
+
},
|
|
904
1178
|
category: "logic"
|
|
905
1179
|
},
|
|
906
1180
|
fn: (_ctx, a, b) => test(a, b)
|
|
@@ -917,7 +1191,12 @@ var logicHelpers = [
|
|
|
917
1191
|
doc: {
|
|
918
1192
|
signature: "and(a, b, \u2026)",
|
|
919
1193
|
description: "True when every value is true; an empty list counts as false.",
|
|
920
|
-
|
|
1194
|
+
examples: {
|
|
1195
|
+
jinja2: "{% if and(customer.vatId, eq(country, 'DE')) %}\u2026{% endif %}",
|
|
1196
|
+
// nesting one comparison in another needs its own assignment in Liquid
|
|
1197
|
+
liquid: "{% assign german = country | eq: 'DE' %}{% assign both = customer.vatId | and: german %}{% if both %}\u2026{% endif %}",
|
|
1198
|
+
handlebars: "{{#if (and customer.vatId (eq country 'DE'))}}\u2026{{/if}}"
|
|
1199
|
+
},
|
|
921
1200
|
category: "logic"
|
|
922
1201
|
},
|
|
923
1202
|
fn: (_ctx, ...values) => values.length > 0 && values.every(truthy)
|
|
@@ -927,7 +1206,11 @@ var logicHelpers = [
|
|
|
927
1206
|
doc: {
|
|
928
1207
|
signature: "or(a, b, \u2026)",
|
|
929
1208
|
description: "True when any value is true.",
|
|
930
|
-
|
|
1209
|
+
examples: {
|
|
1210
|
+
jinja2: "{% if or(invoice.paid, invoice.credited) %}\u2026{% endif %}",
|
|
1211
|
+
liquid: "{% assign any = invoice.paid | or: invoice.credited %}{% if any %}\u2026{% endif %}",
|
|
1212
|
+
handlebars: "{{#if (or invoice.paid invoice.credited)}}\u2026{{/if}}"
|
|
1213
|
+
},
|
|
931
1214
|
category: "logic"
|
|
932
1215
|
},
|
|
933
1216
|
fn: (_ctx, ...values) => values.some(truthy)
|
|
@@ -937,7 +1220,11 @@ var logicHelpers = [
|
|
|
937
1220
|
doc: {
|
|
938
1221
|
signature: "not(value)",
|
|
939
1222
|
description: "The opposite truth value.",
|
|
940
|
-
|
|
1223
|
+
examples: {
|
|
1224
|
+
jinja2: "{% if invoice.paid | not %}\u2026{% endif %}",
|
|
1225
|
+
liquid: "{% assign unpaid = invoice.paid | not %}{% if unpaid %}\u2026{% endif %}",
|
|
1226
|
+
handlebars: "{{#if (not invoice.paid)}}\u2026{{/if}}"
|
|
1227
|
+
},
|
|
941
1228
|
category: "logic"
|
|
942
1229
|
},
|
|
943
1230
|
fn: (_ctx, value) => !truthy(value)
|
|
@@ -1010,7 +1297,9 @@ function helperDocs(registry = defaultHelpers()) {
|
|
|
1010
1297
|
return [...registry.definitions.values()].map((d) => ({
|
|
1011
1298
|
name: d.name,
|
|
1012
1299
|
aliases: d.aliases ?? [],
|
|
1013
|
-
...d.doc
|
|
1300
|
+
...d.doc,
|
|
1301
|
+
// derived rather than authored, so the old field cannot drift from `examples`
|
|
1302
|
+
example: d.doc.examples.jinja2
|
|
1014
1303
|
}));
|
|
1015
1304
|
}
|
|
1016
1305
|
|
|
@@ -1132,6 +1421,7 @@ function scanBlocks(source, spec) {
|
|
|
1132
1421
|
severity: "error",
|
|
1133
1422
|
code: "unexpected-end-tag",
|
|
1134
1423
|
message: `Unexpected {% ${keyword} %} without an open block`,
|
|
1424
|
+
args: { keyword },
|
|
1135
1425
|
range
|
|
1136
1426
|
});
|
|
1137
1427
|
} else if (top.keyword !== expected) {
|
|
@@ -1139,6 +1429,7 @@ function scanBlocks(source, spec) {
|
|
|
1139
1429
|
severity: "error",
|
|
1140
1430
|
code: "mismatched-end-tag",
|
|
1141
1431
|
message: `Expected {% end${top.keyword} %} to close the {% ${top.keyword} %} at line ${top.block.open.start.line}, found {% ${keyword} %}`,
|
|
1432
|
+
args: { open: top.keyword, line: top.block.open.start.line, found: keyword },
|
|
1142
1433
|
range
|
|
1143
1434
|
});
|
|
1144
1435
|
const idx = stack.map((s) => s.keyword).lastIndexOf(expected);
|
|
@@ -1155,6 +1446,7 @@ function scanBlocks(source, spec) {
|
|
|
1155
1446
|
severity: "error",
|
|
1156
1447
|
code: "unexpected-tag",
|
|
1157
1448
|
message: `{% ${keyword} %} outside of a block`,
|
|
1449
|
+
args: { keyword },
|
|
1158
1450
|
range
|
|
1159
1451
|
});
|
|
1160
1452
|
}
|
|
@@ -1164,6 +1456,7 @@ function scanBlocks(source, spec) {
|
|
|
1164
1456
|
severity: "error",
|
|
1165
1457
|
code: "unclosed-block",
|
|
1166
1458
|
message: `{% ${open.keyword} %} is never closed; add {% end${open.keyword} %}`,
|
|
1459
|
+
args: { keyword: open.keyword },
|
|
1167
1460
|
range: open.block.open
|
|
1168
1461
|
});
|
|
1169
1462
|
}
|
|
@@ -1218,6 +1511,7 @@ function missingPathDiagnostics(variables, sampleData, loopSources, builtinRoots
|
|
|
1218
1511
|
severity: "warning",
|
|
1219
1512
|
code: "unknown-variable",
|
|
1220
1513
|
message: `"${v.path.join(".")}" is not present in the sample data`,
|
|
1514
|
+
args: { name: v.path.join(".") },
|
|
1221
1515
|
range: v.range
|
|
1222
1516
|
});
|
|
1223
1517
|
}
|
|
@@ -1468,6 +1762,7 @@ function analyzeHandlebars(source, opts = {}) {
|
|
|
1468
1762
|
severity: "warning",
|
|
1469
1763
|
code: "deprecated-filter",
|
|
1470
1764
|
message: `"${f.name}" is a compatibility alias; use "${preferred}"`,
|
|
1765
|
+
args: { name: f.name, preferred },
|
|
1471
1766
|
range: f.range,
|
|
1472
1767
|
fix: { title: `Rename to ${preferred}`, replacement: preferred }
|
|
1473
1768
|
});
|
|
@@ -1483,6 +1778,7 @@ function analyzeHandlebars(source, opts = {}) {
|
|
|
1483
1778
|
severity: "error",
|
|
1484
1779
|
code: "unknown-filter",
|
|
1485
1780
|
message: `Unknown helper "${f.name}"`,
|
|
1781
|
+
args: { name: f.name, kind: "helper" },
|
|
1486
1782
|
range: f.range
|
|
1487
1783
|
});
|
|
1488
1784
|
}
|
|
@@ -2277,6 +2573,7 @@ function analyzeJinja2(source, opts = {}) {
|
|
|
2277
2573
|
severity: "warning",
|
|
2278
2574
|
code: "deprecated-filter",
|
|
2279
2575
|
message: `"${f.name}" is an apitemplate.io compatibility alias; use "${preferred}"`,
|
|
2576
|
+
args: { name: f.name, preferred },
|
|
2280
2577
|
range: f.range,
|
|
2281
2578
|
fix: { title: `Rename to ${preferred}`, replacement: preferred }
|
|
2282
2579
|
});
|
|
@@ -2292,6 +2589,7 @@ function analyzeJinja2(source, opts = {}) {
|
|
|
2292
2589
|
severity: "error",
|
|
2293
2590
|
code: "unknown-filter",
|
|
2294
2591
|
message: `Unknown filter or helper "${f.name}"`,
|
|
2592
|
+
args: { name: f.name, kind: "filter-or-helper" },
|
|
2295
2593
|
range: f.range
|
|
2296
2594
|
});
|
|
2297
2595
|
}
|
|
@@ -2692,6 +2990,7 @@ function analyzeLiquid(source, opts = {}) {
|
|
|
2692
2990
|
severity: "warning",
|
|
2693
2991
|
code: "deprecated-filter",
|
|
2694
2992
|
message: `"${f.name}" is a compatibility alias; use "${preferred}"`,
|
|
2993
|
+
args: { name: f.name, preferred },
|
|
2695
2994
|
range: f.range,
|
|
2696
2995
|
fix: { title: `Rename to ${preferred}`, replacement: preferred }
|
|
2697
2996
|
});
|
|
@@ -2707,6 +3006,7 @@ function analyzeLiquid(source, opts = {}) {
|
|
|
2707
3006
|
severity: "error",
|
|
2708
3007
|
code: "unknown-filter",
|
|
2709
3008
|
message: `Unknown filter "${f.name}"`,
|
|
3009
|
+
args: { name: f.name, kind: "filter" },
|
|
2710
3010
|
range: f.range
|
|
2711
3011
|
});
|
|
2712
3012
|
}
|
|
@@ -3442,7 +3742,7 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
3442
3742
|
invoice: { due_date: "2026-10-13" },
|
|
3443
3743
|
payment: {
|
|
3444
3744
|
name: "Fennlor Studio GmbH",
|
|
3445
|
-
iban: "
|
|
3745
|
+
iban: "DE36000000000000000000",
|
|
3446
3746
|
bic: "XXXXDEXXXXX",
|
|
3447
3747
|
amount: 3760.4,
|
|
3448
3748
|
reference: "2026-0042"
|
|
@@ -3629,7 +3929,7 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
3629
3929
|
sampleData: {
|
|
3630
3930
|
payment: {
|
|
3631
3931
|
name: "Fennlor Studio GmbH",
|
|
3632
|
-
iban: "
|
|
3932
|
+
iban: "DE36000000000000000000",
|
|
3633
3933
|
bic: "XXXXDEXXXXX",
|
|
3634
3934
|
amount: 3760.4,
|
|
3635
3935
|
reference: "2026-0042"
|
|
@@ -5239,6 +5539,58 @@ var pageNavScript = `<script>
|
|
|
5239
5539
|
}, { passive: true });
|
|
5240
5540
|
})();
|
|
5241
5541
|
</script>`;
|
|
5542
|
+
function overflowScript(mode) {
|
|
5543
|
+
return `<script>
|
|
5544
|
+
(function () {
|
|
5545
|
+
var clipped = function (el, stop) {
|
|
5546
|
+
for (var p = el.parentElement; p && p !== stop; p = p.parentElement)
|
|
5547
|
+
if (getComputedStyle(p).overflowX !== 'visible') return true;
|
|
5548
|
+
return false;
|
|
5549
|
+
};
|
|
5550
|
+
var measure = function () {
|
|
5551
|
+
try {
|
|
5552
|
+
var boxes = ${mode === "paged" ? "document.querySelectorAll('.pagedjs_page_content')" : "[document.body]"};
|
|
5553
|
+
var worst = null;
|
|
5554
|
+
for (var i = 0; i < boxes.length; i++) {
|
|
5555
|
+
var box = boxes[i], rect = box.getBoundingClientRect(), style = getComputedStyle(box);
|
|
5556
|
+
var left = rect.left + parseFloat(style.paddingLeft || '0') + parseFloat(style.borderLeftWidth || '0');
|
|
5557
|
+
var right = rect.right - parseFloat(style.paddingRight || '0') - parseFloat(style.borderRightWidth || '0');
|
|
5558
|
+
if (right - left <= 0) continue;
|
|
5559
|
+
var all = box.querySelectorAll('*');
|
|
5560
|
+
for (var j = 0; j < all.length; j++) {
|
|
5561
|
+
var el = all[j];
|
|
5562
|
+
// Paged.js wraps each page's content in a div of its own that spans its layout columns
|
|
5563
|
+
if (${mode === "paged"} && el.parentElement === box && !el.className) continue;
|
|
5564
|
+
if (el.closest('.formfeed-chrome, .ff-running-header, .ff-running-footer, script, style, head')) continue;
|
|
5565
|
+
var over = el.getBoundingClientRect().right - right;
|
|
5566
|
+
if (over <= 1 || (worst && over <= worst.over)) continue;
|
|
5567
|
+
if (getComputedStyle(el).position === 'fixed' || clipped(el, box)) continue;
|
|
5568
|
+
worst = { over: over, width: right - left, el: el };
|
|
5569
|
+
}
|
|
5570
|
+
}
|
|
5571
|
+
var overflow = null;
|
|
5572
|
+
if (worst) {
|
|
5573
|
+
var ref = document.createElement('div');
|
|
5574
|
+
ref.style.cssText = 'position:absolute;visibility:hidden;width:100mm;height:0';
|
|
5575
|
+
document.body.appendChild(ref);
|
|
5576
|
+
var pxPerMm = ref.getBoundingClientRect().width / 100 || 1;
|
|
5577
|
+
ref.remove();
|
|
5578
|
+
var annotated = worst.el.closest('[data-ff-src]');
|
|
5579
|
+
overflow = {
|
|
5580
|
+
overflowMm: Math.round((worst.over / pxPerMm) * 10) / 10,
|
|
5581
|
+
scale: Math.round((worst.width / (worst.width + worst.over)) * 1000) / 1000,
|
|
5582
|
+
tag: worst.el.tagName.toLowerCase(),
|
|
5583
|
+
src: annotated ? annotated.getAttribute('data-ff-src') : null,
|
|
5584
|
+
};
|
|
5585
|
+
}
|
|
5586
|
+
parent.postMessage({ type: 'formfeed:overflow', overflow: overflow }, '*');
|
|
5587
|
+
} catch (e) {}
|
|
5588
|
+
};
|
|
5589
|
+
window.formfeedCheckOverflow = measure;
|
|
5590
|
+
${mode === "flow" ? "window.addEventListener('load', function () { (document.fonts ? document.fonts.ready : Promise.resolve()).then(measure); });" : ""}
|
|
5591
|
+
})();
|
|
5592
|
+
</script>`;
|
|
5593
|
+
}
|
|
5242
5594
|
var zoomGestureScript = `<script>
|
|
5243
5595
|
(function () {
|
|
5244
5596
|
var say = function (message) { try { parent.postMessage(message, '*'); } catch (e) {} };
|
|
@@ -5285,7 +5637,7 @@ body.formfeed-preview { position: relative; box-sizing: border-box; width: ${pap
|
|
|
5285
5637
|
</style>` : `<style data-formfeed="preview">html { background: #e5e7eb; } body.formfeed-preview { margin: 24px auto; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.2); }</style>`;
|
|
5286
5638
|
const header = headerHtml ? `<div class="formfeed-chrome" style="${pdf ? flowChromeStyle(settings, "header") : `${chromeBoxStyle(settings, "header")}margin-bottom:8px`}">${headerHtml}</div>` : "";
|
|
5287
5639
|
const footer = footerHtml ? `<div class="formfeed-chrome" style="${pdf ? flowChromeStyle(settings, "footer") : `${chromeBoxStyle(settings, "footer")}margin-top:8px`}">${pageNumberSpans(footerHtml, "1", "1")}</div>` : "";
|
|
5288
|
-
return intoHead(draft.document, `${chrome}${inspectScript}${zoomGestureScript}`).replace(/(<body[^>]*>)/, `$1${header}`).replace("</body>", `${footer}</body>`);
|
|
5640
|
+
return intoHead(draft.document, `${chrome}${inspectScript}${zoomGestureScript}${pdf ? overflowScript("flow") : ""}`).replace(/(<body[^>]*>)/, `$1${header}`).replace("</body>", `${footer}</body>`);
|
|
5289
5641
|
}
|
|
5290
5642
|
function pagedDocument(draft, options) {
|
|
5291
5643
|
const { headerHtml, footerHtml, settings } = draft;
|
|
@@ -5307,13 +5659,13 @@ html { background: #e5e7eb; }
|
|
|
5307
5659
|
`;
|
|
5308
5660
|
const selectorGuard = `<script>(function(){var q=DocumentFragment.prototype.querySelectorAll;DocumentFragment.prototype.querySelectorAll=function(s){try{return q.call(this,s);}catch(e){console.warn('formfeed paged preview: skipped a selector Paged.js could not use:',s);return q.call(this,':not(*)');}};})();</script>`;
|
|
5309
5661
|
const boxSizingGuard = `<script>(function(){var rule='.pagedjs_pagebox *';var o=new MutationObserver(function(ms){ms.forEach(function(m){m.addedNodes.forEach(function(n){if(n.nodeName==='STYLE'&&n.textContent&&n.textContent.indexOf(rule)>=0){n.textContent=n.textContent.split(rule).join('.pagedjs_pagebox [class*="pagedjs_"]');o.disconnect();}});});});o.observe(document.documentElement,{childList:true,subtree:true});})();</script>`;
|
|
5310
|
-
const config = `<script>window.PagedConfig = { auto: true, after: function (flow) { try { if (window.formfeedDrawCharts) window.formfeedDrawCharts(); } catch (e) {} try { parent.postMessage({ type: 'formfeed:pages', pages: flow.total }, '*'); } catch (e) {} } };</script>`;
|
|
5662
|
+
const config = `<script>window.PagedConfig = { auto: true, after: function (flow) { try { if (window.formfeedDrawCharts) window.formfeedDrawCharts(); } catch (e) {} try { parent.postMessage({ type: 'formfeed:pages', pages: flow.total }, '*'); } catch (e) {} if (window.formfeedCheckOverflow) window.formfeedCheckOverflow(); } };</script>`;
|
|
5311
5663
|
const script = `<script src="${options.pagedScriptUrl}"></script>`;
|
|
5312
5664
|
const header = headerHtml ? `<div class="ff-running-header">${pageNumberSpans(headerHtml, '<span class="ff-page-no"></span>', '<span class="ff-page-total"></span>')}</div>` : "";
|
|
5313
5665
|
const footer = footerHtml ? `<div class="ff-running-footer">${pageNumberSpans(footerHtml, '<span class="ff-page-no"></span>', '<span class="ff-page-total"></span>')}</div>` : "";
|
|
5314
5666
|
return intoHead(
|
|
5315
5667
|
draft.document,
|
|
5316
|
-
`<style data-formfeed="paged">${runningCss}</style>${selectorGuard}${boxSizingGuard}${config}${script}${inspectScript}${pageNavScript}${zoomGestureScript}`
|
|
5668
|
+
`<style data-formfeed="paged">${runningCss}</style>${selectorGuard}${boxSizingGuard}${overflowScript("paged")}${config}${script}${inspectScript}${pageNavScript}${zoomGestureScript}`
|
|
5317
5669
|
).replace(/(<body[^>]*>)/, `$1${header}${footer}`);
|
|
5318
5670
|
}
|
|
5319
5671
|
|
|
@@ -8464,6 +8816,7 @@ function starterPresentation(engine) {
|
|
|
8464
8816
|
export {
|
|
8465
8817
|
DEFAULT_CHROME_PADDING,
|
|
8466
8818
|
DefaultHelperRegistry,
|
|
8819
|
+
EPC_MAX_BYTES,
|
|
8467
8820
|
EngineSyntaxError,
|
|
8468
8821
|
OFFICE_FILLED_MAX_BYTES,
|
|
8469
8822
|
OfficeTemplateError,
|
|
@@ -8500,6 +8853,7 @@ export {
|
|
|
8500
8853
|
getEngine,
|
|
8501
8854
|
handlebarsEngine,
|
|
8502
8855
|
helperDocs,
|
|
8856
|
+
ibanChecksum,
|
|
8503
8857
|
imageMarkup,
|
|
8504
8858
|
imageOutput,
|
|
8505
8859
|
importApitemplate,
|
|
@@ -8524,6 +8878,7 @@ export {
|
|
|
8524
8878
|
mergeSettings,
|
|
8525
8879
|
mergeSnippetCss,
|
|
8526
8880
|
mergeSnippetI18n,
|
|
8881
|
+
normaliseIban,
|
|
8527
8882
|
normalisePartialName,
|
|
8528
8883
|
nunjucksBuiltinFilters,
|
|
8529
8884
|
officeDrawingLimits,
|
|
@@ -8554,7 +8909,9 @@ export {
|
|
|
8554
8909
|
starterDocument,
|
|
8555
8910
|
starterPresentation,
|
|
8556
8911
|
strftimeToDateFns,
|
|
8912
|
+
svgDataUri,
|
|
8557
8913
|
toDate,
|
|
8558
8914
|
toNumber,
|
|
8915
|
+
validateEpc,
|
|
8559
8916
|
zoomGestureScript
|
|
8560
8917
|
};
|