@formfeed/mcp 0.1.0 → 0.3.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/bin/formfeed-mcp.mjs +1045 -147
- package/package.json +1 -1
package/bin/formfeed-mcp.mjs
CHANGED
|
@@ -1155,16 +1155,16 @@ var require_numeric_data = __commonJS({
|
|
|
1155
1155
|
return NumericData.getBitsLength(this.data.length);
|
|
1156
1156
|
};
|
|
1157
1157
|
NumericData.prototype.write = function write(bitBuffer) {
|
|
1158
|
-
let i,
|
|
1158
|
+
let i, group2, value;
|
|
1159
1159
|
for (i = 0; i + 3 <= this.data.length; i += 3) {
|
|
1160
|
-
|
|
1161
|
-
value = parseInt(
|
|
1160
|
+
group2 = this.data.substr(i, 3);
|
|
1161
|
+
value = parseInt(group2, 10);
|
|
1162
1162
|
bitBuffer.put(value, 10);
|
|
1163
1163
|
}
|
|
1164
1164
|
const remainingNum = this.data.length - i;
|
|
1165
1165
|
if (remainingNum > 0) {
|
|
1166
|
-
|
|
1167
|
-
value = parseInt(
|
|
1166
|
+
group2 = this.data.substr(i);
|
|
1167
|
+
value = parseInt(group2, 10);
|
|
1168
1168
|
bitBuffer.put(value, remainingNum * 3 + 1);
|
|
1169
1169
|
}
|
|
1170
1170
|
};
|
|
@@ -17445,16 +17445,16 @@ var require_liquid_node = __commonJS({
|
|
|
17445
17445
|
this.trimLeft = false;
|
|
17446
17446
|
this.trimRight = false;
|
|
17447
17447
|
const tl = input[contentBegin] === "-";
|
|
17448
|
-
const
|
|
17448
|
+
const tr2 = input[contentEnd - 1] === "-";
|
|
17449
17449
|
let l3 = tl ? contentBegin + 1 : contentBegin;
|
|
17450
|
-
let r =
|
|
17450
|
+
let r = tr2 ? contentEnd - 1 : contentEnd;
|
|
17451
17451
|
while (l3 < r && TYPES[input.charCodeAt(l3)] & BLANK)
|
|
17452
17452
|
l3++;
|
|
17453
17453
|
while (r > l3 && TYPES[input.charCodeAt(r - 1)] & BLANK)
|
|
17454
17454
|
r--;
|
|
17455
17455
|
this.contentRange = [l3, r];
|
|
17456
17456
|
this.trimLeft = tl || trimLeft2;
|
|
17457
|
-
this.trimRight =
|
|
17457
|
+
this.trimRight = tr2 || trimRight2;
|
|
17458
17458
|
}
|
|
17459
17459
|
get content() {
|
|
17460
17460
|
return this.input.slice(this.contentRange[0], this.contentRange[1]);
|
|
@@ -20735,14 +20735,14 @@ var require_liquid_node = __commonJS({
|
|
|
20735
20735
|
constructor(token, remainTokens, liquid) {
|
|
20736
20736
|
super(token, remainTokens, liquid);
|
|
20737
20737
|
this.candidates = [];
|
|
20738
|
-
const
|
|
20738
|
+
const group2 = this.tokenizer.readValue();
|
|
20739
20739
|
this.tokenizer.skipBlank();
|
|
20740
|
-
if (
|
|
20740
|
+
if (group2) {
|
|
20741
20741
|
if (this.tokenizer.peek() === ":") {
|
|
20742
|
-
this.group =
|
|
20742
|
+
this.group = group2;
|
|
20743
20743
|
this.tokenizer.advance();
|
|
20744
20744
|
} else
|
|
20745
|
-
this.candidates.push(
|
|
20745
|
+
this.candidates.push(group2);
|
|
20746
20746
|
}
|
|
20747
20747
|
while (!this.tokenizer.end()) {
|
|
20748
20748
|
const value = this.tokenizer.readValue();
|
|
@@ -20753,8 +20753,8 @@ var require_liquid_node = __commonJS({
|
|
|
20753
20753
|
this.tokenizer.assert(this.candidates.length, () => `empty candidates: "${token.getText()}"`);
|
|
20754
20754
|
}
|
|
20755
20755
|
*render(ctx, emitter) {
|
|
20756
|
-
const
|
|
20757
|
-
const fingerprint = `cycle:${
|
|
20756
|
+
const group2 = yield evalToken(this.group, ctx);
|
|
20757
|
+
const fingerprint = `cycle:${group2}:` + this.candidates.join(",");
|
|
20758
20758
|
const groups = ctx.getRegister("cycle", {});
|
|
20759
20759
|
let idx = groups[fingerprint];
|
|
20760
20760
|
if (idx === void 0) {
|
|
@@ -77456,6 +77456,11 @@ var currencyValues6 = { currency: (
|
|
|
77456
77456
|
Object.keys(nl2)
|
|
77457
77457
|
) };
|
|
77458
77458
|
|
|
77459
|
+
// packages/engine/src/lib/helpers/office.ts
|
|
77460
|
+
function officeUnsupported(name) {
|
|
77461
|
+
return `${name}() is not supported in office templates`;
|
|
77462
|
+
}
|
|
77463
|
+
|
|
77459
77464
|
// packages/engine/src/lib/helpers/format.ts
|
|
77460
77465
|
var dateLocales = {
|
|
77461
77466
|
de,
|
|
@@ -77738,7 +77743,8 @@ var formatHelpers = [
|
|
|
77738
77743
|
example: "{{ address | nl2br }}",
|
|
77739
77744
|
category: "text"
|
|
77740
77745
|
},
|
|
77741
|
-
|
|
77746
|
+
// office templates turn line breaks into breaks themselves, and escape the text once
|
|
77747
|
+
fn: (ctx, value) => ctx.mode === "office" ? str(value) : escapeHtml(str(value)).replace(/\r?\n/g, "<br>\n")
|
|
77742
77748
|
},
|
|
77743
77749
|
{
|
|
77744
77750
|
name: "markdown",
|
|
@@ -77749,11 +77755,14 @@ var formatHelpers = [
|
|
|
77749
77755
|
example: "{{ notes | markdown }}",
|
|
77750
77756
|
category: "text"
|
|
77751
77757
|
},
|
|
77752
|
-
fn: (
|
|
77753
|
-
|
|
77754
|
-
|
|
77755
|
-
|
|
77756
|
-
|
|
77758
|
+
fn: (ctx, value) => {
|
|
77759
|
+
if (ctx.mode === "office") throw new Error(officeUnsupported("markdown"));
|
|
77760
|
+
return f.parse(str(value), {
|
|
77761
|
+
async: false,
|
|
77762
|
+
gfm: true,
|
|
77763
|
+
breaks: false
|
|
77764
|
+
});
|
|
77765
|
+
}
|
|
77757
77766
|
},
|
|
77758
77767
|
{
|
|
77759
77768
|
name: "safe",
|
|
@@ -77815,7 +77824,7 @@ var formatHelpers = [
|
|
|
77815
77824
|
name: "asset",
|
|
77816
77825
|
doc: {
|
|
77817
77826
|
signature: "asset(name)",
|
|
77818
|
-
description: "URL of a workspace
|
|
77827
|
+
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.",
|
|
77819
77828
|
example: `<img src="{{ asset('logo.png') }}">`,
|
|
77820
77829
|
category: "document"
|
|
77821
77830
|
},
|
|
@@ -77831,7 +77840,7 @@ var formatHelpers = [
|
|
|
77831
77840
|
example: "{{ pageBreak() }}",
|
|
77832
77841
|
category: "document"
|
|
77833
77842
|
},
|
|
77834
|
-
fn: () => '<div class="page-break" style="break-after:page"></div>'
|
|
77843
|
+
fn: (ctx) => ctx.drawing ? ctx.drawing({ kind: "page-break" }) : '<div class="page-break" style="break-after:page"></div>'
|
|
77835
77844
|
}
|
|
77836
77845
|
];
|
|
77837
77846
|
|
|
@@ -77888,7 +77897,12 @@ var codeHelpers = [
|
|
|
77888
77897
|
example: '<img src="{{ qrcode(order.url, { size: 120 }) }}">',
|
|
77889
77898
|
category: "code"
|
|
77890
77899
|
},
|
|
77891
|
-
fn: (
|
|
77900
|
+
fn: (ctx, value, options) => {
|
|
77901
|
+
const o = options ?? {};
|
|
77902
|
+
const svg = qrSvg(String(value ?? ""), o);
|
|
77903
|
+
if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, width: o.size, height: o.size, alt: "QR code" });
|
|
77904
|
+
return svgDataUri(svg);
|
|
77905
|
+
}
|
|
77892
77906
|
},
|
|
77893
77907
|
{
|
|
77894
77908
|
name: "barcode",
|
|
@@ -77898,7 +77912,7 @@ var codeHelpers = [
|
|
|
77898
77912
|
example: `<img src="{{ barcode(item.sku, { type: 'code128' }) }}">`,
|
|
77899
77913
|
category: "code"
|
|
77900
77914
|
},
|
|
77901
|
-
fn: (
|
|
77915
|
+
fn: (ctx, value, options) => {
|
|
77902
77916
|
const o = options ?? {};
|
|
77903
77917
|
const bcid = barcodeTypes[String(o.type ?? "code128").toLowerCase()] ?? "code128";
|
|
77904
77918
|
const svg = ToSVG({
|
|
@@ -77910,6 +77924,7 @@ var codeHelpers = [
|
|
|
77910
77924
|
includetext: o.text !== false,
|
|
77911
77925
|
textxalign: "center"
|
|
77912
77926
|
});
|
|
77927
|
+
if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, alt: `Barcode ${String(value ?? "")}` });
|
|
77913
77928
|
return svgDataUri(svg);
|
|
77914
77929
|
}
|
|
77915
77930
|
},
|
|
@@ -77926,7 +77941,7 @@ var codeHelpers = [
|
|
|
77926
77941
|
// hash (`{{epcQr name=… iban=… size=200}}`), and Liquid, which cannot build an object literal,
|
|
77927
77942
|
// keyword arguments (`{{ company.iban | epcQr: name: company.name, amount: invoice.total }}`,
|
|
77928
77943
|
// where a string input is the IBAN). They are merged and split by field name.
|
|
77929
|
-
fn: (
|
|
77944
|
+
fn: (ctx, input, options) => {
|
|
77930
77945
|
const fields = {
|
|
77931
77946
|
...input && typeof input === "object" ? input : typeof input === "string" && input ? { iban: input } : {},
|
|
77932
77947
|
...options ?? {}
|
|
@@ -77935,12 +77950,10 @@ var codeHelpers = [
|
|
|
77935
77950
|
const qr = {};
|
|
77936
77951
|
for (const [key, value] of Object.entries(fields))
|
|
77937
77952
|
(EPC_FIELDS.has(key) ? payment : qr)[key] = value;
|
|
77938
|
-
|
|
77939
|
-
|
|
77940
|
-
|
|
77941
|
-
|
|
77942
|
-
})
|
|
77943
|
-
);
|
|
77953
|
+
const o = { ecc: "M", ...qr };
|
|
77954
|
+
const svg = qrSvg(epcPayload(payment), o);
|
|
77955
|
+
if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, width: o.size, height: o.size, alt: "GiroCode" });
|
|
77956
|
+
return svgDataUri(svg);
|
|
77944
77957
|
}
|
|
77945
77958
|
}
|
|
77946
77959
|
];
|
|
@@ -78130,6 +78143,29 @@ var collectionHelpers = [
|
|
|
78130
78143
|
];
|
|
78131
78144
|
|
|
78132
78145
|
// packages/engine/src/lib/helpers/document.ts
|
|
78146
|
+
var CHART_PALETTE = ["#3E63DD", "#12A594", "#E5484D", "#F76B15", "#8E4EC6", "#FFC53D", "#0090FF", "#978365"];
|
|
78147
|
+
var SLICED = /* @__PURE__ */ new Set(["pie", "doughnut", "polarArea"]);
|
|
78148
|
+
var colour = (value) => typeof value === "string" && value ? value : Array.isArray(value) && value.length ? value.map(String) : void 0;
|
|
78149
|
+
function chartDataFromPlain(spec) {
|
|
78150
|
+
const type = spec.type ?? "bar";
|
|
78151
|
+
const series = Array.isArray(spec.series) ? spec.series.map((s) => asObject(s)) : [{ label: spec.label, values: spec.values, color: spec.color }];
|
|
78152
|
+
const labels = Array.isArray(spec.labels) ? spec.labels : [];
|
|
78153
|
+
const datasets = series.map((s, index) => {
|
|
78154
|
+
const data = (Array.isArray(s.values) ? s.values : []).map((v2) => {
|
|
78155
|
+
const n = toNumber(v2);
|
|
78156
|
+
return Number.isFinite(n) ? n : null;
|
|
78157
|
+
});
|
|
78158
|
+
const own = colour(s.color);
|
|
78159
|
+
const fill = SLICED.has(type) ? own ?? labels.map((_2, i) => CHART_PALETTE[i % CHART_PALETTE.length]) : own ?? CHART_PALETTE[index % CHART_PALETTE.length];
|
|
78160
|
+
const dataset = { data, backgroundColor: fill, borderColor: fill };
|
|
78161
|
+
if (s.label !== void 0 && s.label !== null && s.label !== "") dataset["label"] = String(s.label);
|
|
78162
|
+
if (type === "line") dataset["fill"] = false;
|
|
78163
|
+
return dataset;
|
|
78164
|
+
});
|
|
78165
|
+
return { labels, datasets };
|
|
78166
|
+
}
|
|
78167
|
+
var isPlain = (spec) => spec.data === void 0 && (Array.isArray(spec.values) || Array.isArray(spec.series));
|
|
78168
|
+
var asObject = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
78133
78169
|
var css = (value) => {
|
|
78134
78170
|
if (value === void 0 || value === null || value === "") return void 0;
|
|
78135
78171
|
const n = toNumber(value);
|
|
@@ -78139,7 +78175,13 @@ function chartPayload(spec) {
|
|
|
78139
78175
|
const options = { ...spec.options ?? {} };
|
|
78140
78176
|
options["animation"] = false;
|
|
78141
78177
|
options["responsive"] = false;
|
|
78142
|
-
return { type: spec.type ?? "bar", data: spec.data ?? {}, options };
|
|
78178
|
+
if (!isPlain(spec)) return { type: spec.type ?? "bar", data: spec.data ?? {}, options };
|
|
78179
|
+
const data = chartDataFromPlain(spec);
|
|
78180
|
+
const unnamed = data.datasets.length === 1 && data.datasets[0]?.["label"] === void 0;
|
|
78181
|
+
const plugins = asObject(options["plugins"]);
|
|
78182
|
+
if (unnamed && !SLICED.has(spec.type ?? "bar") && plugins["legend"] === void 0)
|
|
78183
|
+
options["plugins"] = { ...plugins, legend: { display: false } };
|
|
78184
|
+
return { type: spec.type ?? "bar", data, options };
|
|
78143
78185
|
}
|
|
78144
78186
|
function chartMarkup(spec, id) {
|
|
78145
78187
|
const width = toNumber(spec.width ?? 480) || 480;
|
|
@@ -78158,18 +78200,24 @@ function imageMarkup(url, options = {}) {
|
|
|
78158
78200
|
const style = styles ? ` style="${styles}"` : "";
|
|
78159
78201
|
return `<img src="${escapeHtml(url)}" alt="${escapeHtml(options.alt ?? "")}"${cls}${style}>`;
|
|
78160
78202
|
}
|
|
78161
|
-
|
|
78203
|
+
function resolveAsset(base, url) {
|
|
78204
|
+
if (!base || /^[a-z][a-z0-9+.-]*:/i.test(url)) return url;
|
|
78205
|
+
return `${base.replace(/\/$/, "")}/${url.replace(/^\//, "")}`;
|
|
78206
|
+
}
|
|
78162
78207
|
var documentHelpers = [
|
|
78163
78208
|
{
|
|
78164
78209
|
name: "chart",
|
|
78165
78210
|
html: true,
|
|
78166
78211
|
doc: {
|
|
78167
|
-
signature: "chart({ type, data, options, width, height })",
|
|
78168
|
-
description: "Draws a Chart.js chart (bar, line, pie, doughnut, radar, \u2026) from a
|
|
78169
|
-
example: "{{ chart({ type: '
|
|
78212
|
+
signature: "chart({ type, labels, values, label, color } | { type, labels, series } | { type, data, options }, { width, height })",
|
|
78213
|
+
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.",
|
|
78214
|
+
example: "{{ chart({ type: 'line', labels: ['Q1', 'Q2', 'Q3'], values: sales.quarters, width: 480 }) }}",
|
|
78170
78215
|
category: "document"
|
|
78171
78216
|
},
|
|
78172
|
-
fn: (
|
|
78217
|
+
fn: (ctx, spec, extra) => {
|
|
78218
|
+
if (ctx.mode === "office") throw new Error(officeUnsupported("chart"));
|
|
78219
|
+
return chartMarkup({ ...asObject(spec), ...asObject(extra) });
|
|
78220
|
+
}
|
|
78173
78221
|
},
|
|
78174
78222
|
{
|
|
78175
78223
|
name: "image",
|
|
@@ -78180,7 +78228,12 @@ var documentHelpers = [
|
|
|
78180
78228
|
example: "{{ image(product.photo, { width: 120, height: 80, fit: 'cover' }) }}",
|
|
78181
78229
|
category: "document"
|
|
78182
78230
|
},
|
|
78183
|
-
fn: (
|
|
78231
|
+
fn: (ctx, url, options) => {
|
|
78232
|
+
const o = asObject(options);
|
|
78233
|
+
if (ctx.drawing)
|
|
78234
|
+
return ctx.drawing({ kind: "url", url: resolveAsset(ctx.assetBaseUrl, String(url ?? "")), width: o.width, height: o.height, alt: o.alt });
|
|
78235
|
+
return imageMarkup(String(url ?? ""), o);
|
|
78236
|
+
}
|
|
78184
78237
|
}
|
|
78185
78238
|
];
|
|
78186
78239
|
|
|
@@ -78308,6 +78361,12 @@ function defaultHelpers() {
|
|
|
78308
78361
|
// packages/engine/src/lib/engines/handlebars.ts
|
|
78309
78362
|
var import_handlebars = __toESM(require_lib2());
|
|
78310
78363
|
|
|
78364
|
+
// packages/engine/src/lib/unavailable.ts
|
|
78365
|
+
var UNAVAILABLE = {};
|
|
78366
|
+
function unavailableHelper(name) {
|
|
78367
|
+
return UNAVAILABLE[name];
|
|
78368
|
+
}
|
|
78369
|
+
|
|
78311
78370
|
// packages/engine/src/lib/analysis/tags.ts
|
|
78312
78371
|
function rangeAt(line, column, length) {
|
|
78313
78372
|
return {
|
|
@@ -78487,7 +78546,10 @@ function missingPathDiagnostics(variables, sampleData, loopSources, builtinRoots
|
|
|
78487
78546
|
const root = v2.path[0];
|
|
78488
78547
|
if (!root || builtinRoots4.has(root)) continue;
|
|
78489
78548
|
let path = v2.path;
|
|
78490
|
-
if (
|
|
78549
|
+
if (v2.source !== void 0) {
|
|
78550
|
+
if (!v2.source) continue;
|
|
78551
|
+
path = [...v2.source, ...v2.path.slice(1)];
|
|
78552
|
+
} else if (loopSources.has(root)) {
|
|
78491
78553
|
const source = loopSources.get(root);
|
|
78492
78554
|
if (!source) continue;
|
|
78493
78555
|
path = [...source, ...v2.path.slice(1)];
|
|
@@ -78519,6 +78581,8 @@ var builtinHelpers2 = /* @__PURE__ */ new Set([
|
|
|
78519
78581
|
"helperMissing"
|
|
78520
78582
|
]);
|
|
78521
78583
|
var builtinRoots = /* @__PURE__ */ new Set([
|
|
78584
|
+
// the organisation's brand kit (spec 18), merged under the data
|
|
78585
|
+
"brand",
|
|
78522
78586
|
"this",
|
|
78523
78587
|
"@index",
|
|
78524
78588
|
"@key",
|
|
@@ -78527,6 +78591,11 @@ var builtinRoots = /* @__PURE__ */ new Set([
|
|
|
78527
78591
|
"@root",
|
|
78528
78592
|
"lookup"
|
|
78529
78593
|
]);
|
|
78594
|
+
function withBrand(data, ctx) {
|
|
78595
|
+
const root = data ?? {};
|
|
78596
|
+
if (!ctx.brand || typeof root !== "object" || Array.isArray(root)) return root;
|
|
78597
|
+
return { brand: ctx.brand, ...root };
|
|
78598
|
+
}
|
|
78530
78599
|
function createInstance(ctx) {
|
|
78531
78600
|
const hb = import_handlebars.default.create();
|
|
78532
78601
|
for (const [name, { fn, html }] of ctx.helpers.bind(ctx)) {
|
|
@@ -78746,6 +78815,13 @@ function analyzeHandlebars(source, opts = {}) {
|
|
|
78746
78815
|
range: f2.range,
|
|
78747
78816
|
fix: { title: `Rename to ${preferred}`, replacement: preferred }
|
|
78748
78817
|
});
|
|
78818
|
+
} else if (unavailableHelper(f2.name)) {
|
|
78819
|
+
diagnostics.push({
|
|
78820
|
+
severity: "warning",
|
|
78821
|
+
code: "unavailable-helper",
|
|
78822
|
+
message: unavailableHelper(f2.name),
|
|
78823
|
+
range: f2.range
|
|
78824
|
+
});
|
|
78749
78825
|
} else if (!f2.known) {
|
|
78750
78826
|
diagnostics.push({
|
|
78751
78827
|
severity: "error",
|
|
@@ -78790,7 +78866,8 @@ var HandlebarsCompiled = class {
|
|
|
78790
78866
|
hb,
|
|
78791
78867
|
template: hb.compile(this.source, {
|
|
78792
78868
|
strict: false,
|
|
78793
|
-
|
|
78869
|
+
// office text is escaped when its markup is restored (office/template-text.ts)
|
|
78870
|
+
noEscape: ctx.mode === "office",
|
|
78794
78871
|
preventIndent: true
|
|
78795
78872
|
})
|
|
78796
78873
|
};
|
|
@@ -78819,7 +78896,7 @@ var HandlebarsCompiled = class {
|
|
|
78819
78896
|
};
|
|
78820
78897
|
collect(this.source, 0);
|
|
78821
78898
|
try {
|
|
78822
|
-
const out = template(data
|
|
78899
|
+
const out = template(withBrand(data, ctx), {
|
|
78823
78900
|
partials,
|
|
78824
78901
|
allowProtoPropertiesByDefault: false,
|
|
78825
78902
|
allowProtoMethodsByDefault: false
|
|
@@ -78854,6 +78931,151 @@ var handlebarsEngine = {
|
|
|
78854
78931
|
|
|
78855
78932
|
// packages/engine/src/lib/engines/jinja2.ts
|
|
78856
78933
|
var import_nunjucks = __toESM(require_nunjucks());
|
|
78934
|
+
|
|
78935
|
+
// packages/engine/src/lib/engines/python-format.ts
|
|
78936
|
+
var SPEC = /^(?:([\s\S])?([<>=^]))?([+\- ])?(#)?(0)?(\d+)?([,_])?(?:\.(\d+))?([bcdeEfFgGnosxX%])?$/;
|
|
78937
|
+
function group(digits, separator) {
|
|
78938
|
+
return digits.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
|
|
78939
|
+
}
|
|
78940
|
+
function pythonExponent(text2) {
|
|
78941
|
+
return text2.replace(/e([+-])(\d)$/i, (m2, sign, digit) => `${m2[0]}${sign}0${digit}`);
|
|
78942
|
+
}
|
|
78943
|
+
function formatNumber(value, type, precision, alternate) {
|
|
78944
|
+
if (!Number.isFinite(value)) return Number.isNaN(value) ? "nan" : "inf";
|
|
78945
|
+
const abs = Math.abs(value);
|
|
78946
|
+
switch (type) {
|
|
78947
|
+
case "f":
|
|
78948
|
+
case "F":
|
|
78949
|
+
return abs.toFixed(precision ?? 6);
|
|
78950
|
+
case "%":
|
|
78951
|
+
return `${(abs * 100).toFixed(precision ?? 6)}%`;
|
|
78952
|
+
case "e":
|
|
78953
|
+
case "E": {
|
|
78954
|
+
const text2 = pythonExponent(abs.toExponential(precision ?? 6));
|
|
78955
|
+
return type === "E" ? text2.toUpperCase() : text2;
|
|
78956
|
+
}
|
|
78957
|
+
case "g":
|
|
78958
|
+
case "G": {
|
|
78959
|
+
const p = precision === 0 ? 1 : precision ?? 6;
|
|
78960
|
+
const exp = abs === 0 ? 0 : Math.floor(Math.log10(abs));
|
|
78961
|
+
let text2 = exp < -4 || exp >= p ? pythonExponent(abs.toExponential(p - 1)) : abs.toFixed(Math.max(0, p - 1 - exp));
|
|
78962
|
+
if (!alternate) {
|
|
78963
|
+
const [mantissa = "", exponent] = text2.split("e");
|
|
78964
|
+
const trimmed = mantissa.includes(".") ? mantissa.replace(/0+$/, "").replace(/\.$/, "") : mantissa;
|
|
78965
|
+
text2 = exponent === void 0 ? trimmed : `${trimmed}e${exponent}`;
|
|
78966
|
+
}
|
|
78967
|
+
return type === "G" ? text2.toUpperCase() : text2;
|
|
78968
|
+
}
|
|
78969
|
+
case "d":
|
|
78970
|
+
case "n":
|
|
78971
|
+
return String(Math.trunc(abs));
|
|
78972
|
+
case "b":
|
|
78973
|
+
return (alternate ? "0b" : "") + Math.trunc(abs).toString(2);
|
|
78974
|
+
case "o":
|
|
78975
|
+
return (alternate ? "0o" : "") + Math.trunc(abs).toString(8);
|
|
78976
|
+
case "x":
|
|
78977
|
+
return (alternate ? "0x" : "") + Math.trunc(abs).toString(16);
|
|
78978
|
+
case "X":
|
|
78979
|
+
return (alternate ? "0X" : "") + Math.trunc(abs).toString(16).toUpperCase();
|
|
78980
|
+
default:
|
|
78981
|
+
return precision === void 0 ? String(abs) : formatNumber(abs, "g", precision, alternate);
|
|
78982
|
+
}
|
|
78983
|
+
}
|
|
78984
|
+
function formatValue(value, spec) {
|
|
78985
|
+
const m2 = SPEC.exec(spec);
|
|
78986
|
+
if (!m2) return String(value ?? "");
|
|
78987
|
+
const [, fillChar, alignChar, sign, alt, zero, widthText, grouping, precisionText, type = ""] = m2;
|
|
78988
|
+
const precision = precisionText === void 0 ? void 0 : Number(precisionText);
|
|
78989
|
+
const width = widthText === void 0 ? 0 : Number(widthText);
|
|
78990
|
+
const numericType = /[bdeEfFgGnoxX%]/.test(type);
|
|
78991
|
+
const asNumber = typeof value === "number" ? value : typeof value === "boolean" ? Number(value) : numericType && value !== null && value !== void 0 && String(value).trim() !== "" && Number.isFinite(Number(value)) ? Number(value) : void 0;
|
|
78992
|
+
let body;
|
|
78993
|
+
let prefix = "";
|
|
78994
|
+
if (asNumber !== void 0 && type !== "s" && type !== "c") {
|
|
78995
|
+
body = formatNumber(asNumber, type, precision, Boolean(alt));
|
|
78996
|
+
if (grouping) {
|
|
78997
|
+
const [int = "", ...rest] = body.split(".");
|
|
78998
|
+
const radix = /^0[box]/i.exec(int)?.[0] ?? "";
|
|
78999
|
+
body = radix + group(int.slice(radix.length), grouping) + (rest.length ? `.${rest.join(".")}` : "");
|
|
79000
|
+
}
|
|
79001
|
+
const negative = asNumber < 0 || Object.is(asNumber, -0);
|
|
79002
|
+
prefix = negative ? "-" : sign === "+" ? "+" : sign === " " ? " " : "";
|
|
79003
|
+
} else if (type === "c" && asNumber !== void 0) {
|
|
79004
|
+
body = String.fromCodePoint(asNumber);
|
|
79005
|
+
} else {
|
|
79006
|
+
body = String(value ?? "None");
|
|
79007
|
+
if (precision !== void 0 && !numericType) body = body.slice(0, precision);
|
|
79008
|
+
}
|
|
79009
|
+
const isNumber = asNumber !== void 0 && type !== "s";
|
|
79010
|
+
const align = alignChar ?? (zero && isNumber ? "=" : isNumber ? ">" : "<");
|
|
79011
|
+
const fill = fillChar ?? (zero && !alignChar ? "0" : " ");
|
|
79012
|
+
const pad = Math.max(0, width - prefix.length - body.length);
|
|
79013
|
+
switch (align) {
|
|
79014
|
+
case "<":
|
|
79015
|
+
return prefix + body + fill.repeat(pad);
|
|
79016
|
+
case "^":
|
|
79017
|
+
return fill.repeat(Math.floor(pad / 2)) + prefix + body + fill.repeat(Math.ceil(pad / 2));
|
|
79018
|
+
case "=":
|
|
79019
|
+
return prefix + fill.repeat(pad) + body;
|
|
79020
|
+
default:
|
|
79021
|
+
return fill.repeat(pad) + prefix + body;
|
|
79022
|
+
}
|
|
79023
|
+
}
|
|
79024
|
+
function lookupField(name, positional, named, next) {
|
|
79025
|
+
const [head = "", ...rest] = name.split(/(?=[.[])/);
|
|
79026
|
+
let value = head === "" ? positional[next()] : /^\d+$/.test(head) ? positional[Number(head)] : named[head];
|
|
79027
|
+
for (const part of rest) {
|
|
79028
|
+
const key = part.startsWith("[") ? part.slice(1, -1) : part.slice(1);
|
|
79029
|
+
value = value !== null && typeof value === "object" ? value[Array.isArray(value) && /^\d+$/.test(key) ? Number(key) : key] : void 0;
|
|
79030
|
+
}
|
|
79031
|
+
return value;
|
|
79032
|
+
}
|
|
79033
|
+
function pythonFormat(template, positional, named = {}) {
|
|
79034
|
+
let auto = 0;
|
|
79035
|
+
const next = () => auto++;
|
|
79036
|
+
const render = (text2) => {
|
|
79037
|
+
let out = "";
|
|
79038
|
+
for (let i = 0; i < text2.length; i++) {
|
|
79039
|
+
const ch = text2[i];
|
|
79040
|
+
if (ch === "}") {
|
|
79041
|
+
out += "}";
|
|
79042
|
+
if (text2[i + 1] === "}") i++;
|
|
79043
|
+
continue;
|
|
79044
|
+
}
|
|
79045
|
+
if (ch !== "{") {
|
|
79046
|
+
out += ch;
|
|
79047
|
+
continue;
|
|
79048
|
+
}
|
|
79049
|
+
if (text2[i + 1] === "{") {
|
|
79050
|
+
out += "{";
|
|
79051
|
+
i++;
|
|
79052
|
+
continue;
|
|
79053
|
+
}
|
|
79054
|
+
let depth = 1;
|
|
79055
|
+
let j2 = i + 1;
|
|
79056
|
+
for (; j2 < text2.length && depth > 0; j2++) {
|
|
79057
|
+
if (text2[j2] === "{") depth++;
|
|
79058
|
+
else if (text2[j2] === "}") depth--;
|
|
79059
|
+
}
|
|
79060
|
+
if (depth > 0) {
|
|
79061
|
+
out += text2.slice(i);
|
|
79062
|
+
break;
|
|
79063
|
+
}
|
|
79064
|
+
const field = text2.slice(i + 1, j2 - 1);
|
|
79065
|
+
const colon = field.indexOf(":");
|
|
79066
|
+
const head = colon < 0 ? field : field.slice(0, colon);
|
|
79067
|
+
const name = head.replace(/![rsa]$/, "");
|
|
79068
|
+
const value = lookupField(name, positional, named, next);
|
|
79069
|
+
const spec = colon < 0 ? "" : render(field.slice(colon + 1));
|
|
79070
|
+
out += value === void 0 ? "" : formatValue(value, spec);
|
|
79071
|
+
i = j2 - 1;
|
|
79072
|
+
}
|
|
79073
|
+
return out;
|
|
79074
|
+
};
|
|
79075
|
+
return render(template);
|
|
79076
|
+
}
|
|
79077
|
+
|
|
79078
|
+
// packages/engine/src/lib/engines/jinja2.ts
|
|
78857
79079
|
var nunjucksBuiltinFilters = /* @__PURE__ */ new Set([
|
|
78858
79080
|
"abs",
|
|
78859
79081
|
"batch",
|
|
@@ -78899,6 +79121,7 @@ var nunjucksBuiltinFilters = /* @__PURE__ */ new Set([
|
|
|
78899
79121
|
"urlize",
|
|
78900
79122
|
"wordcount"
|
|
78901
79123
|
]);
|
|
79124
|
+
var jinjaLiterals = /* @__PURE__ */ new Set(["True", "False", "None"]);
|
|
78902
79125
|
var builtinRoots2 = /* @__PURE__ */ new Set([
|
|
78903
79126
|
"loop",
|
|
78904
79127
|
"caller",
|
|
@@ -78908,7 +79131,14 @@ var builtinRoots2 = /* @__PURE__ */ new Set([
|
|
|
78908
79131
|
"joiner",
|
|
78909
79132
|
"lipsum",
|
|
78910
79133
|
"varargs",
|
|
78911
|
-
"kwargs"
|
|
79134
|
+
"kwargs",
|
|
79135
|
+
"namespace",
|
|
79136
|
+
// the organisation's brand kit (spec 18), a global beside the data
|
|
79137
|
+
"brand",
|
|
79138
|
+
// Jinja2's spelling of the literals, which the compat layer resolves at runtime
|
|
79139
|
+
"True",
|
|
79140
|
+
"False",
|
|
79141
|
+
"None"
|
|
78912
79142
|
]);
|
|
78913
79143
|
var stringMethods = {
|
|
78914
79144
|
upper: (s) => s.toUpperCase(),
|
|
@@ -78937,12 +79167,8 @@ var stringMethods = {
|
|
|
78937
79167
|
split: (s, sep, max) => sep === void 0 || sep === null ? s.trim().split(/\s+/) : s.split(String(sep), max === void 0 ? void 0 : Number(max) + 1),
|
|
78938
79168
|
join: (s, list2) => Array.isArray(list2) ? list2.join(s) : String(list2),
|
|
78939
79169
|
format: (s, ...args2) => {
|
|
78940
|
-
|
|
78941
|
-
|
|
78942
|
-
return s.replace(
|
|
78943
|
-
/\{(\w*)\}/g,
|
|
78944
|
-
(_2, key) => String(key ? named[key] ?? "" : args2[i++] ?? "")
|
|
78945
|
-
);
|
|
79170
|
+
const named = keywordArgs(args2);
|
|
79171
|
+
return pythonFormat(s, named ? args2.slice(0, -1) : args2, named ?? {});
|
|
78946
79172
|
},
|
|
78947
79173
|
zfill: (s, width) => s.padStart(Number(width), "0"),
|
|
78948
79174
|
count: (s, sub) => s.split(String(sub)).length - 1,
|
|
@@ -78957,6 +79183,92 @@ var stringMethods = {
|
|
|
78957
79183
|
return f2.repeat(left) + s + f2.repeat(total - left);
|
|
78958
79184
|
}
|
|
78959
79185
|
};
|
|
79186
|
+
function keywordArgs(args2) {
|
|
79187
|
+
const last = args2.at(-1);
|
|
79188
|
+
if (!last || typeof last !== "object" || !Object.prototype.hasOwnProperty.call(last, "__keywords")) return void 0;
|
|
79189
|
+
const { __keywords: _marker, ...named } = last;
|
|
79190
|
+
return named;
|
|
79191
|
+
}
|
|
79192
|
+
var JinjaNamespace = class {
|
|
79193
|
+
};
|
|
79194
|
+
function namespace(...args2) {
|
|
79195
|
+
const ns = new JinjaNamespace();
|
|
79196
|
+
const named = keywordArgs(args2);
|
|
79197
|
+
for (const arg of named ? [...args2.slice(0, -1), named] : args2)
|
|
79198
|
+
if (arg && typeof arg === "object") Object.assign(ns, arg);
|
|
79199
|
+
return ns;
|
|
79200
|
+
}
|
|
79201
|
+
function setNamespaceAttribute(target, key, value) {
|
|
79202
|
+
if (!(target instanceof JinjaNamespace)) throw new Error("Cannot assign attribute on non-namespace object");
|
|
79203
|
+
target[String(key)] = value;
|
|
79204
|
+
}
|
|
79205
|
+
var toList = (v2) => Array.isArray(v2) ? v2 : v2 === void 0 || v2 === null ? [] : typeof v2 === "string" ? [...v2] : [v2];
|
|
79206
|
+
var attributeOf = (item, path) => String(path ?? "").split(".").reduce((acc, key) => acc !== null && typeof acc === "object" ? acc[key] : void 0, item);
|
|
79207
|
+
var jinjaTests = {
|
|
79208
|
+
none: (v2) => v2 === null,
|
|
79209
|
+
true: (v2) => v2 === true,
|
|
79210
|
+
false: (v2) => v2 === false,
|
|
79211
|
+
in: (v2, container) => typeof container === "string" ? container.includes(String(v2)) : Array.isArray(container) ? container.includes(v2) : !!container && typeof container === "object" && String(v2) in container,
|
|
79212
|
+
"==": (v2, a) => v2 === a,
|
|
79213
|
+
"!=": (v2, a) => v2 !== a,
|
|
79214
|
+
">": (v2, a) => v2 > a,
|
|
79215
|
+
">=": (v2, a) => v2 >= a,
|
|
79216
|
+
"<": (v2, a) => v2 < a,
|
|
79217
|
+
"<=": (v2, a) => v2 <= a
|
|
79218
|
+
};
|
|
79219
|
+
function runTest(self2, name, value, arg) {
|
|
79220
|
+
const own = jinjaTests[name];
|
|
79221
|
+
if (own) return own(value, arg);
|
|
79222
|
+
return self2.env.getTest(name).call(self2, value, arg) === true;
|
|
79223
|
+
}
|
|
79224
|
+
function installJinjaFilters(env) {
|
|
79225
|
+
const lookup = env;
|
|
79226
|
+
const helperTruncate = lookup.getFilter("truncate");
|
|
79227
|
+
lookup.addFilter("truncate", function(value, ...args2) {
|
|
79228
|
+
const named = keywordArgs(args2);
|
|
79229
|
+
const p = named ? args2.slice(0, -1) : args2;
|
|
79230
|
+
if (!named && typeof p[1] !== "boolean" && p.length < 3) return helperTruncate.call(this, value, ...args2);
|
|
79231
|
+
const s = String(value ?? "");
|
|
79232
|
+
const length = Number(p[0] ?? named?.["length"] ?? 255);
|
|
79233
|
+
const killwords = Boolean(p[1] ?? named?.["killwords"] ?? false);
|
|
79234
|
+
const end = String(p[2] ?? named?.["end"] ?? "...");
|
|
79235
|
+
const leeway = Number(p[3] ?? named?.["leeway"] ?? 5);
|
|
79236
|
+
if (s.length <= length + leeway) return s;
|
|
79237
|
+
const cut = s.slice(0, Math.max(0, length - end.length));
|
|
79238
|
+
if (killwords) return cut + end;
|
|
79239
|
+
const space = cut.lastIndexOf(" ");
|
|
79240
|
+
return (space >= 0 ? cut.slice(0, space) : cut) + end;
|
|
79241
|
+
});
|
|
79242
|
+
const helperMap = lookup.getFilter("map");
|
|
79243
|
+
lookup.addFilter("map", function(value, ...args2) {
|
|
79244
|
+
const named = keywordArgs(args2);
|
|
79245
|
+
const p = named ? args2.slice(0, -1) : args2;
|
|
79246
|
+
if (named && "attribute" in named)
|
|
79247
|
+
return toList(value).map((item) => {
|
|
79248
|
+
const v2 = attributeOf(item, named["attribute"]);
|
|
79249
|
+
return v2 === void 0 && "default" in named ? named["default"] : v2;
|
|
79250
|
+
});
|
|
79251
|
+
const items = toList(value);
|
|
79252
|
+
if (typeof p[0] === "string" && !items.some((item) => item !== null && typeof item === "object")) {
|
|
79253
|
+
let filter;
|
|
79254
|
+
try {
|
|
79255
|
+
filter = lookup.getFilter(p[0]);
|
|
79256
|
+
} catch {
|
|
79257
|
+
filter = void 0;
|
|
79258
|
+
}
|
|
79259
|
+
if (filter) return items.map((item) => filter.call(this, item, ...p.slice(1)));
|
|
79260
|
+
}
|
|
79261
|
+
return helperMap.call(this, value, ...args2);
|
|
79262
|
+
});
|
|
79263
|
+
const byAttribute = (keep) => function(value, attribute, test, arg) {
|
|
79264
|
+
return toList(value).filter((item) => {
|
|
79265
|
+
const v2 = attributeOf(item, attribute);
|
|
79266
|
+
return (test === void 0 ? Boolean(v2) : runTest(this, String(test), v2, arg)) === keep;
|
|
79267
|
+
});
|
|
79268
|
+
};
|
|
79269
|
+
lookup.addFilter("selectattr", byAttribute(true));
|
|
79270
|
+
lookup.addFilter("rejectattr", byAttribute(false));
|
|
79271
|
+
}
|
|
78960
79272
|
function trimChars(s, chars, side) {
|
|
78961
79273
|
if (chars === void 0 || chars === null)
|
|
78962
79274
|
return side === "both" ? s.trim() : side === "left" ? s.trimStart() : s.trimEnd();
|
|
@@ -78977,6 +79289,20 @@ function installCompat() {
|
|
|
78977
79289
|
this.compile(node.target, frame);
|
|
78978
79290
|
this._emit(")");
|
|
78979
79291
|
};
|
|
79292
|
+
const compileSet = import_nunjucks.default.compiler.Compiler.prototype.compileSet;
|
|
79293
|
+
import_nunjucks.default.runtime["formfeedSetAttribute"] = setNamespaceAttribute;
|
|
79294
|
+
import_nunjucks.default.compiler.Compiler.prototype.compileSet = function(node, frame) {
|
|
79295
|
+
const [target, ...more] = node.targets;
|
|
79296
|
+
if (!target || more.length || target.typename !== "LookupVal") return compileSet.call(this, node, frame);
|
|
79297
|
+
this._emit("runtime.formfeedSetAttribute(");
|
|
79298
|
+
this._compileExpression(target["target"], frame);
|
|
79299
|
+
this._emit(", ");
|
|
79300
|
+
this._compileExpression(target["val"], frame);
|
|
79301
|
+
this._emit(", ");
|
|
79302
|
+
if (node.value) this._compileExpression(node.value, frame);
|
|
79303
|
+
else this.compile(node.body, frame);
|
|
79304
|
+
this._emitLine(");");
|
|
79305
|
+
};
|
|
78980
79306
|
const original = import_nunjucks.default.runtime.memberLookup;
|
|
78981
79307
|
import_nunjucks.default.runtime.memberLookup = (obj, val2, ...rest) => {
|
|
78982
79308
|
if (typeof obj === "string" && typeof val2 === "string" && Object.prototype.hasOwnProperty.call(stringMethods, val2)) {
|
|
@@ -79009,7 +79335,8 @@ function createEnvironment(ctx) {
|
|
|
79009
79335
|
const env = new import_nunjucks.default.Environment(
|
|
79010
79336
|
new PartialLoader(ctx.partials),
|
|
79011
79337
|
{
|
|
79012
|
-
|
|
79338
|
+
// office text is escaped when its markup is restored, so escaping here would double it
|
|
79339
|
+
autoescape: ctx.mode !== "office",
|
|
79013
79340
|
throwOnUndefined: false,
|
|
79014
79341
|
trimBlocks: false,
|
|
79015
79342
|
lstripBlocks: false
|
|
@@ -79023,6 +79350,9 @@ function createEnvironment(ctx) {
|
|
|
79023
79350
|
env.addFilter(name, wrapped);
|
|
79024
79351
|
env.addGlobal(name, wrapped);
|
|
79025
79352
|
}
|
|
79353
|
+
env.addGlobal("namespace", namespace);
|
|
79354
|
+
if (ctx.brand) env.addGlobal("brand", ctx.brand);
|
|
79355
|
+
installJinjaFilters(env);
|
|
79026
79356
|
return env;
|
|
79027
79357
|
}
|
|
79028
79358
|
var validationContext2 = {
|
|
@@ -79063,18 +79393,28 @@ function pathOf(node) {
|
|
|
79063
79393
|
function inScope(w, name) {
|
|
79064
79394
|
return w.scopes.some((s) => s.has(name));
|
|
79065
79395
|
}
|
|
79396
|
+
function sourceOf(w, name) {
|
|
79397
|
+
for (let i = w.scopes.length - 1; i >= 0; i--) {
|
|
79398
|
+
const scope = w.scopes[i];
|
|
79399
|
+
if (scope?.has(name)) return scope.get(name) ?? null;
|
|
79400
|
+
}
|
|
79401
|
+
return null;
|
|
79402
|
+
}
|
|
79066
79403
|
function walk2(node, w) {
|
|
79067
79404
|
if (!node || typeof node !== "object") return;
|
|
79068
79405
|
switch (node.typename) {
|
|
79069
79406
|
case "Symbol":
|
|
79070
79407
|
case "LookupVal": {
|
|
79071
79408
|
const p = pathOf(node);
|
|
79409
|
+
if (node.typename === "Symbol" && jinjaLiterals.has(String(node.value))) return;
|
|
79072
79410
|
if (p) {
|
|
79073
79411
|
const root = p.path[0] ?? "";
|
|
79412
|
+
const bound = inScope(w, root);
|
|
79074
79413
|
w.variables.push({
|
|
79075
79414
|
path: p.path,
|
|
79076
79415
|
range: nodeRange(p.root, p.path.join(".").length),
|
|
79077
|
-
kind:
|
|
79416
|
+
kind: bound ? "loop-var" : "read",
|
|
79417
|
+
...bound ? { source: sourceOf(w, root) } : {}
|
|
79078
79418
|
});
|
|
79079
79419
|
}
|
|
79080
79420
|
if (node.typename === "LookupVal" && !p) {
|
|
@@ -79120,18 +79460,18 @@ function walk2(node, w) {
|
|
|
79120
79460
|
const source = pathOf(arr);
|
|
79121
79461
|
const nameNode = node["name"];
|
|
79122
79462
|
const names = nameNode.typename === "Array" ? nameNode.children ?? [] : [nameNode];
|
|
79123
|
-
const
|
|
79463
|
+
const root = source?.path[0] ?? "";
|
|
79464
|
+
const items = !source ? null : inScope(w, root) ? ((s) => s ? [...s, ...source.path.slice(1)] : null)(sourceOf(w, root)) : source.path;
|
|
79465
|
+
const scope = /* @__PURE__ */ new Map();
|
|
79124
79466
|
for (const n of names) {
|
|
79125
79467
|
const name = String(n.value);
|
|
79126
|
-
|
|
79127
|
-
|
|
79128
|
-
name,
|
|
79129
|
-
names.length === 1 && source && !inScope(w, source.path[0] ?? "") ? source.path : names.length === 1 && source ? resolveLoopSource(w, source.path) : null
|
|
79130
|
-
);
|
|
79468
|
+
const bound = names.length === 1 ? items : null;
|
|
79469
|
+
scope.set(name, bound);
|
|
79131
79470
|
w.variables.push({
|
|
79132
79471
|
path: [name],
|
|
79133
79472
|
range: nodeRange(n, name.length),
|
|
79134
|
-
kind: "loop-var"
|
|
79473
|
+
kind: "loop-var",
|
|
79474
|
+
source: bound
|
|
79135
79475
|
});
|
|
79136
79476
|
}
|
|
79137
79477
|
w.scopes.push(scope);
|
|
@@ -79149,8 +79489,7 @@ function walk2(node, w) {
|
|
|
79149
79489
|
range: nodeRange(p.root, p.path.join(".").length),
|
|
79150
79490
|
kind: "assigned"
|
|
79151
79491
|
});
|
|
79152
|
-
w.scopes[0]?.
|
|
79153
|
-
w.loopSources.set(p.path[0] ?? "", null);
|
|
79492
|
+
if (p.path.length === 1 || !inScope(w, p.path[0] ?? "")) w.scopes[0]?.set(p.path[0] ?? "", null);
|
|
79154
79493
|
}
|
|
79155
79494
|
}
|
|
79156
79495
|
walk2(node["value"], w);
|
|
@@ -79159,17 +79498,27 @@ function walk2(node, w) {
|
|
|
79159
79498
|
}
|
|
79160
79499
|
case "Macro": {
|
|
79161
79500
|
const args2 = node["args"];
|
|
79162
|
-
const scope = /* @__PURE__ */ new
|
|
79163
|
-
for (const child of args2.children ?? [])
|
|
79164
|
-
if (child.typename === "Symbol") scope.
|
|
79501
|
+
const scope = /* @__PURE__ */ new Map();
|
|
79502
|
+
for (const child of args2.children ?? []) {
|
|
79503
|
+
if (child.typename === "Symbol") scope.set(String(child.value), null);
|
|
79504
|
+
if (child.typename === "KeywordArgs")
|
|
79505
|
+
for (const pair of child.children ?? []) {
|
|
79506
|
+
const key = pair["key"];
|
|
79507
|
+
if (key) scope.set(String(key.value), null);
|
|
79508
|
+
walk2(pair["value"], w);
|
|
79509
|
+
}
|
|
79510
|
+
}
|
|
79165
79511
|
const nameNode = node["name"];
|
|
79166
|
-
w.scopes[0]?.
|
|
79167
|
-
w.loopSources.set(String(nameNode.value), null);
|
|
79512
|
+
w.scopes[0]?.set(String(nameNode.value), null);
|
|
79168
79513
|
w.scopes.push(scope);
|
|
79169
79514
|
walk2(node["body"], w);
|
|
79170
79515
|
w.scopes.pop();
|
|
79171
79516
|
return;
|
|
79172
79517
|
}
|
|
79518
|
+
case "Pair": {
|
|
79519
|
+
walk2(node["value"], w);
|
|
79520
|
+
return;
|
|
79521
|
+
}
|
|
79173
79522
|
case "Include":
|
|
79174
79523
|
case "Extends":
|
|
79175
79524
|
case "Import":
|
|
@@ -79182,15 +79531,17 @@ function walk2(node, w) {
|
|
|
79182
79531
|
});
|
|
79183
79532
|
else walk2(tpl, w);
|
|
79184
79533
|
if (node.typename === "Import")
|
|
79185
|
-
w.scopes[0]?.
|
|
79186
|
-
String(node["target"]?.value ?? "")
|
|
79534
|
+
w.scopes[0]?.set(
|
|
79535
|
+
String(node["target"]?.value ?? ""),
|
|
79536
|
+
null
|
|
79187
79537
|
);
|
|
79188
79538
|
if (node.typename === "FromImport")
|
|
79189
79539
|
for (const n of node["names"].children ?? [])
|
|
79190
|
-
w.scopes[0]?.
|
|
79540
|
+
w.scopes[0]?.set(
|
|
79191
79541
|
String(
|
|
79192
79542
|
n.typename === "Pair" ? n["value"].value : n.value
|
|
79193
|
-
)
|
|
79543
|
+
),
|
|
79544
|
+
null
|
|
79194
79545
|
);
|
|
79195
79546
|
return;
|
|
79196
79547
|
}
|
|
@@ -79208,12 +79559,6 @@ function walk2(node, w) {
|
|
|
79208
79559
|
walk2(value, w);
|
|
79209
79560
|
}
|
|
79210
79561
|
}
|
|
79211
|
-
function resolveLoopSource(w, path) {
|
|
79212
|
-
const root = path[0] ?? "";
|
|
79213
|
-
if (!w.loopSources.has(root)) return path;
|
|
79214
|
-
const source = w.loopSources.get(root);
|
|
79215
|
-
return source ? [...source, ...path.slice(1)] : null;
|
|
79216
|
-
}
|
|
79217
79562
|
function analyzeJinja2(source, opts = {}) {
|
|
79218
79563
|
const helpers = opts.helpers ?? defaultHelpers();
|
|
79219
79564
|
const { blocks, diagnostics } = scanBlocks(source, jinjaTags);
|
|
@@ -79221,8 +79566,7 @@ function analyzeJinja2(source, opts = {}) {
|
|
|
79221
79566
|
variables: [],
|
|
79222
79567
|
filters: [],
|
|
79223
79568
|
includes: [],
|
|
79224
|
-
|
|
79225
|
-
scopes: [/* @__PURE__ */ new Set()],
|
|
79569
|
+
scopes: [/* @__PURE__ */ new Map()],
|
|
79226
79570
|
helpers
|
|
79227
79571
|
};
|
|
79228
79572
|
try {
|
|
@@ -79251,6 +79595,13 @@ function analyzeJinja2(source, opts = {}) {
|
|
|
79251
79595
|
range: f2.range,
|
|
79252
79596
|
fix: { title: `Rename to ${preferred}`, replacement: preferred }
|
|
79253
79597
|
});
|
|
79598
|
+
} else if (unavailableHelper(f2.name)) {
|
|
79599
|
+
diagnostics.push({
|
|
79600
|
+
severity: "warning",
|
|
79601
|
+
code: "unavailable-helper",
|
|
79602
|
+
message: unavailableHelper(f2.name),
|
|
79603
|
+
range: f2.range
|
|
79604
|
+
});
|
|
79254
79605
|
} else if (!f2.known) {
|
|
79255
79606
|
diagnostics.push({
|
|
79256
79607
|
severity: "error",
|
|
@@ -79264,7 +79615,7 @@ function analyzeJinja2(source, opts = {}) {
|
|
|
79264
79615
|
const dataDiagnostics = missingPathDiagnostics(
|
|
79265
79616
|
w.variables,
|
|
79266
79617
|
opts.sampleData,
|
|
79267
|
-
|
|
79618
|
+
/* @__PURE__ */ new Map(),
|
|
79268
79619
|
builtin
|
|
79269
79620
|
);
|
|
79270
79621
|
return {
|
|
@@ -79397,6 +79748,10 @@ var liquidBuiltinFilters = /* @__PURE__ */ new Set([
|
|
|
79397
79748
|
"url_decode",
|
|
79398
79749
|
"url_encode",
|
|
79399
79750
|
"where",
|
|
79751
|
+
"where_exp",
|
|
79752
|
+
"find_exp",
|
|
79753
|
+
"reject_exp",
|
|
79754
|
+
"group_by_exp",
|
|
79400
79755
|
"raw",
|
|
79401
79756
|
"sum",
|
|
79402
79757
|
"group_by",
|
|
@@ -79418,6 +79773,8 @@ var liquidBuiltinFilters = /* @__PURE__ */ new Set([
|
|
|
79418
79773
|
"time_zone"
|
|
79419
79774
|
]);
|
|
79420
79775
|
var builtinRoots3 = /* @__PURE__ */ new Set([
|
|
79776
|
+
// the organisation's brand kit (spec 18), a global beside the data
|
|
79777
|
+
"brand",
|
|
79421
79778
|
"forloop",
|
|
79422
79779
|
"tablerowloop",
|
|
79423
79780
|
"true",
|
|
@@ -79483,6 +79840,8 @@ function createLiquid(ctx) {
|
|
|
79483
79840
|
extname: "",
|
|
79484
79841
|
root: [""],
|
|
79485
79842
|
fs: partialFs(ctx.partials),
|
|
79843
|
+
// scope data shadows globals, so `data.brand` still wins
|
|
79844
|
+
globals: ctx.brand ? { brand: ctx.brand } : {},
|
|
79486
79845
|
renderLimit: ctx.limits.ms,
|
|
79487
79846
|
memoryLimit: ctx.limits.outputBytes * 4,
|
|
79488
79847
|
parseLimit: 2e6
|
|
@@ -79651,6 +80010,13 @@ function analyzeLiquid(source, opts = {}) {
|
|
|
79651
80010
|
range: f2.range,
|
|
79652
80011
|
fix: { title: `Rename to ${preferred}`, replacement: preferred }
|
|
79653
80012
|
});
|
|
80013
|
+
} else if (unavailableHelper(f2.name)) {
|
|
80014
|
+
diagnostics.push({
|
|
80015
|
+
severity: "warning",
|
|
80016
|
+
code: "unavailable-helper",
|
|
80017
|
+
message: unavailableHelper(f2.name),
|
|
80018
|
+
range: f2.range
|
|
80019
|
+
});
|
|
79654
80020
|
} else if (!f2.known) {
|
|
79655
80021
|
diagnostics.push({
|
|
79656
80022
|
severity: "error",
|
|
@@ -79746,6 +80112,26 @@ function getEngine(id) {
|
|
|
79746
80112
|
return engine;
|
|
79747
80113
|
}
|
|
79748
80114
|
|
|
80115
|
+
// packages/engine/src/lib/schema-diff.ts
|
|
80116
|
+
var LOWER_BOUNDS = ["minimum", "exclusiveMinimum", "minLength", "minItems", "minProperties"];
|
|
80117
|
+
var UPPER_BOUNDS = ["maximum", "exclusiveMaximum", "maxLength", "maxItems", "maxProperties"];
|
|
80118
|
+
var ANALYSED = /* @__PURE__ */ new Set([
|
|
80119
|
+
"type",
|
|
80120
|
+
"properties",
|
|
80121
|
+
"required",
|
|
80122
|
+
"additionalProperties",
|
|
80123
|
+
"items",
|
|
80124
|
+
"enum",
|
|
80125
|
+
"const",
|
|
80126
|
+
"format",
|
|
80127
|
+
"pattern",
|
|
80128
|
+
"$ref",
|
|
80129
|
+
"$defs",
|
|
80130
|
+
"definitions",
|
|
80131
|
+
...LOWER_BOUNDS,
|
|
80132
|
+
...UPPER_BOUNDS
|
|
80133
|
+
]);
|
|
80134
|
+
|
|
79749
80135
|
// packages/engine/src/lib/schema.ts
|
|
79750
80136
|
var isoDateTime = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
79751
80137
|
var isoDate = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -79836,9 +80222,12 @@ function inferSchema(sample) {
|
|
|
79836
80222
|
};
|
|
79837
80223
|
}
|
|
79838
80224
|
|
|
80225
|
+
// packages/engine/src/lib/output.ts
|
|
80226
|
+
var outputFormats = ["pdf", "png", "jpg", "webp", "docx", "pptx"];
|
|
80227
|
+
|
|
79839
80228
|
// packages/engine/src/lib/snippets.ts
|
|
79840
80229
|
var loop = (engine, item, list2, body) => engine === "handlebars" ? `{{#each ${list2}}}
|
|
79841
|
-
${body.replace(
|
|
80230
|
+
${body.replace(new RegExp(`(?<![\\w.])${item}\\.`, "g"), "")}
|
|
79842
80231
|
{{/each}}` : `{% for ${item} in ${list2} %}
|
|
79843
80232
|
${body}
|
|
79844
80233
|
{% endfor %}`;
|
|
@@ -79848,6 +80237,15 @@ var val = (engine, expr, filter) => {
|
|
|
79848
80237
|
return `{{${filter.split("(")[0]} ${expr}${filter.includes("(") ? " " + filter.slice(filter.indexOf("(") + 1, -1) : ""}}}`;
|
|
79849
80238
|
return engine === "liquid" ? `{{ ${expr} | ${filter.replace("(", ": ").replace(")", "")} }}` : `{{ ${expr} | ${filter} }}`;
|
|
79850
80239
|
};
|
|
80240
|
+
var ifElse = (engine, cond, then, otherwise) => engine === "handlebars" ? `{{#if ${cond}}}${then}{{else}}${otherwise}{{/if}}` : `{% if ${cond} %}${then}{% else %}${otherwise}{% endif %}`;
|
|
80241
|
+
var tr = (engine, key, params = {}) => {
|
|
80242
|
+
const entries = Object.entries(params);
|
|
80243
|
+
if (engine === "handlebars")
|
|
80244
|
+
return `{{t '${key}'${entries.map(([k2, v2]) => ` ${k2}=${v2}`).join("")}}}`;
|
|
80245
|
+
if (engine === "liquid")
|
|
80246
|
+
return `{{ '${key}' | t${entries.length ? ": " + entries.map(([k2, v2]) => `${k2}: ${v2}`).join(", ") : ""} }}`;
|
|
80247
|
+
return `{{ t('${key}'${entries.length ? `, { ${entries.map(([k2, v2]) => `${k2}: ${v2}`).join(", ")} }` : ""}) }}`;
|
|
80248
|
+
};
|
|
79851
80249
|
function forAll(build) {
|
|
79852
80250
|
return {
|
|
79853
80251
|
jinja2: build("jinja2"),
|
|
@@ -79855,14 +80253,101 @@ function forAll(build) {
|
|
|
79855
80253
|
handlebars: build("handlebars")
|
|
79856
80254
|
};
|
|
79857
80255
|
}
|
|
80256
|
+
var rule = "var(--brand-color-primary, #222)";
|
|
79858
80257
|
var snippets = [
|
|
80258
|
+
{
|
|
80259
|
+
id: "letterhead",
|
|
80260
|
+
group: "letter",
|
|
80261
|
+
title: "Letterhead",
|
|
80262
|
+
description: "Logo from the brand kit, or the organisation name when no logo is set.",
|
|
80263
|
+
source: forAll(
|
|
80264
|
+
(e) => `<header class="ff-letterhead">
|
|
80265
|
+
${ifElse(e, "brand.logo.primary", `<img class="logo" src="${val(e, "brand.logo.primary")}" alt="${val(e, "brand.name")}">`, `<strong class="name">${val(e, "brand.name")}</strong>`)}
|
|
80266
|
+
</header>$0`
|
|
80267
|
+
),
|
|
80268
|
+
css: `.ff-letterhead { display: flex; justify-content: flex-end; align-items: center; min-height: 48px; margin-bottom: 24px; padding-bottom: 8px; border-bottom: 2px solid ${rule}; }
|
|
80269
|
+
.ff-letterhead .logo { max-height: 56px; max-width: 200px; }
|
|
80270
|
+
.ff-letterhead .name { font-family: var(--brand-font-heading, inherit); font-size: 18pt; color: var(--brand-color-primary, inherit); }`
|
|
80271
|
+
},
|
|
80272
|
+
{
|
|
80273
|
+
id: "window-address",
|
|
80274
|
+
group: "letter",
|
|
80275
|
+
kinds: ["pdf"],
|
|
80276
|
+
title: "Window envelope address",
|
|
80277
|
+
description: "Return address and recipient in the 85 \xD7 45 mm field of a DIN 5008 window envelope.",
|
|
80278
|
+
source: forAll(
|
|
80279
|
+
(e) => `<div class="ff-window">
|
|
80280
|
+
<div class="sender">${val(e, "company.name")} \xB7 ${val(e, "company.street")} \xB7 ${val(e, "company.zip")} ${val(e, "company.city")}</div>
|
|
80281
|
+
<address>
|
|
80282
|
+
<strong>${val(e, "customer.name")}</strong><br>
|
|
80283
|
+
${val(e, "customer.street")}<br>
|
|
80284
|
+
${val(e, "customer.zip")} ${val(e, "customer.city")}
|
|
80285
|
+
</address>
|
|
80286
|
+
</div>$0`
|
|
80287
|
+
),
|
|
80288
|
+
sampleData: {
|
|
80289
|
+
company: { name: "Fennlor Studio GmbH", street: "Musterstra\xDFe 1", zip: "12345", city: "Musterstadt" },
|
|
80290
|
+
customer: { name: "Olvarest GmbH", street: "Beispielweg 2", zip: "54321", city: "Beispielstadt" }
|
|
80291
|
+
},
|
|
80292
|
+
// Form B puts the field 45 mm below the top edge and 20 mm from the left; the page margins decide where it lands
|
|
80293
|
+
css: `.ff-window { width: 85mm; height: 45mm; box-sizing: border-box; padding: 0 5mm; overflow: hidden; }
|
|
80294
|
+
.ff-window .sender { height: 5mm; line-height: 5mm; margin-bottom: 3mm; font-size: 7pt; color: #555; border-bottom: 0.5pt solid #999; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
80295
|
+
.ff-window address { font-style: normal; font-size: 10pt; line-height: 1.35; }`
|
|
80296
|
+
},
|
|
80297
|
+
{
|
|
80298
|
+
id: "reference-block",
|
|
80299
|
+
group: "letter",
|
|
80300
|
+
title: "Reference block",
|
|
80301
|
+
description: "Customer number, invoice number and date as a right-aligned table; put it in a two-column layout to sit beside the address.",
|
|
80302
|
+
source: forAll(
|
|
80303
|
+
(e) => `<table class="ff-reference">
|
|
80304
|
+
<tr><th>${tr(e, "reference.customer")}</th><td>${val(e, "customer.number")}</td></tr>
|
|
80305
|
+
<tr><th>${tr(e, "reference.invoice")}</th><td>${val(e, "invoice.number")}</td></tr>
|
|
80306
|
+
<tr><th>${tr(e, "reference.date")}</th><td>${val(e, "invoice.date", "date('P')")}</td></tr>
|
|
80307
|
+
</table>$0`
|
|
80308
|
+
),
|
|
80309
|
+
sampleData: {
|
|
80310
|
+
customer: { number: "K-1042" },
|
|
80311
|
+
invoice: { number: "2026-0042", date: "2026-09-13" }
|
|
80312
|
+
},
|
|
80313
|
+
css: `.ff-reference { margin-left: auto; border-collapse: collapse; font-size: 9pt; }
|
|
80314
|
+
.ff-reference th { text-align: left; font-weight: normal; color: #555; padding: 1px 12px 1px 0; }
|
|
80315
|
+
.ff-reference td { text-align: right; padding: 1px 0; }`,
|
|
80316
|
+
i18n: {
|
|
80317
|
+
en: { "reference.customer": "Customer no.", "reference.invoice": "Invoice no.", "reference.date": "Date" },
|
|
80318
|
+
de: { "reference.customer": "Kundennummer", "reference.invoice": "Rechnungsnummer", "reference.date": "Datum" }
|
|
80319
|
+
}
|
|
80320
|
+
},
|
|
80321
|
+
{
|
|
80322
|
+
id: "invoice-heading",
|
|
80323
|
+
group: "invoice",
|
|
80324
|
+
title: "Invoice heading",
|
|
80325
|
+
description: "Invoice number as the title and the service period below it.",
|
|
80326
|
+
source: forAll(
|
|
80327
|
+
(e) => `<div class="ff-heading">
|
|
80328
|
+
<h1>${tr(e, "heading.title", { number: "invoice.number" })}</h1>
|
|
80329
|
+
<p>${tr(e, "heading.period")} ${val(e, "invoice.period_start", "date('P')")} \u2013 ${val(e, "invoice.period_end", "date('P')")}</p>
|
|
80330
|
+
</div>$0`
|
|
80331
|
+
),
|
|
80332
|
+
sampleData: {
|
|
80333
|
+
invoice: { number: "2026-0042", period_start: "2026-09-01", period_end: "2026-09-30" }
|
|
80334
|
+
},
|
|
80335
|
+
css: `.ff-heading { margin: 24px 0 16px; }
|
|
80336
|
+
.ff-heading h1 { font-family: var(--brand-font-heading, inherit); font-size: 18pt; color: var(--brand-color-primary, inherit); margin: 0 0 4px; }
|
|
80337
|
+
.ff-heading p { margin: 0; color: #555; }`,
|
|
80338
|
+
i18n: {
|
|
80339
|
+
en: { "heading.title": "Invoice {number}", "heading.period": "Service period:" },
|
|
80340
|
+
de: { "heading.title": "Rechnung {number}", "heading.period": "Leistungszeitraum:" }
|
|
80341
|
+
}
|
|
80342
|
+
},
|
|
79859
80343
|
{
|
|
79860
80344
|
id: "invoice-table",
|
|
80345
|
+
group: "invoice",
|
|
79861
80346
|
title: "Invoice table",
|
|
79862
80347
|
description: "Line items with description, quantity, unit price and line total.",
|
|
79863
80348
|
source: forAll(
|
|
79864
80349
|
(e) => `<table class="ff-lines">
|
|
79865
|
-
<thead><tr><th
|
|
80350
|
+
<thead><tr><th>${tr(e, "lines.description")}</th><th class="num">${tr(e, "lines.qty")}</th><th class="num">${tr(e, "lines.price")}</th><th class="num">${tr(e, "lines.total")}</th></tr></thead>
|
|
79866
80351
|
<tbody>
|
|
79867
80352
|
${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</td><td class="num">${val(e, "line.qty", "number(0)")}</td><td class="num">${val(e, "line.price", "money")}</td><td class="num">${val(e, "line.total", "money")}</td></tr>`)}
|
|
79868
80353
|
</tbody>
|
|
@@ -79879,17 +80364,32 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
79879
80364
|
css: `.ff-lines { width: 100%; border-collapse: collapse; font-size: 10pt; }
|
|
79880
80365
|
.ff-lines th, .ff-lines td { padding: 6px 8px; border-bottom: 1px solid #ddd; text-align: left; }
|
|
79881
80366
|
.ff-lines .num { text-align: right; white-space: nowrap; }
|
|
79882
|
-
.ff-lines thead th { border-bottom: 2px solid
|
|
80367
|
+
.ff-lines thead th { border-bottom: 2px solid ${rule}; }`,
|
|
80368
|
+
i18n: {
|
|
80369
|
+
en: {
|
|
80370
|
+
"lines.description": "Description",
|
|
80371
|
+
"lines.qty": "Qty",
|
|
80372
|
+
"lines.price": "Unit price",
|
|
80373
|
+
"lines.total": "Total"
|
|
80374
|
+
},
|
|
80375
|
+
de: {
|
|
80376
|
+
"lines.description": "Beschreibung",
|
|
80377
|
+
"lines.qty": "Menge",
|
|
80378
|
+
"lines.price": "Einzelpreis",
|
|
80379
|
+
"lines.total": "Gesamt"
|
|
80380
|
+
}
|
|
80381
|
+
}
|
|
79883
80382
|
},
|
|
79884
80383
|
{
|
|
79885
80384
|
id: "totals",
|
|
80385
|
+
group: "invoice",
|
|
79886
80386
|
title: "Totals block",
|
|
79887
80387
|
description: "Net, VAT and gross totals right-aligned under a table.",
|
|
79888
80388
|
source: forAll(
|
|
79889
80389
|
(e) => `<table class="ff-totals">
|
|
79890
|
-
<tr><td
|
|
79891
|
-
<tr><td
|
|
79892
|
-
<tr class="grand"><td
|
|
80390
|
+
<tr><td>${tr(e, "totals.net")}</td><td class="num">${val(e, "invoice.net", "money")}</td></tr>
|
|
80391
|
+
<tr><td>${tr(e, "totals.vat", { rate: "invoice.vat_rate" })}</td><td class="num">${val(e, "invoice.vat", "money")}</td></tr>
|
|
80392
|
+
<tr class="grand"><td>${tr(e, "totals.total")}</td><td class="num">${val(e, "invoice.total", "money")}</td></tr>
|
|
79893
80393
|
</table>$0`
|
|
79894
80394
|
),
|
|
79895
80395
|
sampleData: {
|
|
@@ -79898,10 +80398,103 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
79898
80398
|
css: `.ff-totals { margin-left: auto; margin-top: 12px; border-collapse: collapse; font-size: 10pt; }
|
|
79899
80399
|
.ff-totals td { padding: 4px 8px; }
|
|
79900
80400
|
.ff-totals .num { text-align: right; min-width: 90px; }
|
|
79901
|
-
.ff-totals .grand td { border-top: 2px solid
|
|
80401
|
+
.ff-totals .grand td { border-top: 2px solid ${rule}; font-weight: 600; }`,
|
|
80402
|
+
i18n: {
|
|
80403
|
+
en: {
|
|
80404
|
+
"totals.net": "Net",
|
|
80405
|
+
"totals.vat": "VAT {rate} %",
|
|
80406
|
+
"totals.total": "Total"
|
|
80407
|
+
},
|
|
80408
|
+
de: {
|
|
80409
|
+
"totals.net": "Netto",
|
|
80410
|
+
"totals.vat": "USt. {rate} %",
|
|
80411
|
+
"totals.total": "Gesamtbetrag"
|
|
80412
|
+
}
|
|
80413
|
+
}
|
|
80414
|
+
},
|
|
80415
|
+
{
|
|
80416
|
+
id: "payment-terms",
|
|
80417
|
+
group: "invoice",
|
|
80418
|
+
title: "Payment terms and bank details",
|
|
80419
|
+
description: "Amount, due date and the account to transfer it to.",
|
|
80420
|
+
source: forAll(
|
|
80421
|
+
(e) => `<div class="ff-payment">
|
|
80422
|
+
<p>${tr(e, "payment.transfer")} <strong>${val(e, "payment.amount", "money")}</strong> ${tr(e, "payment.until")} <strong>${val(e, "invoice.due_date", "date('P')")}</strong> ${tr(e, "payment.account")}</p>
|
|
80423
|
+
<table>
|
|
80424
|
+
<tr><th>${tr(e, "payment.holder")}</th><td>${val(e, "payment.name")}</td></tr>
|
|
80425
|
+
<tr><th>IBAN</th><td>${val(e, "payment.iban")}</td></tr>
|
|
80426
|
+
<tr><th>BIC</th><td>${val(e, "payment.bic")}</td></tr>
|
|
80427
|
+
<tr><th>${tr(e, "payment.reference")}</th><td>${val(e, "payment.reference")}</td></tr>
|
|
80428
|
+
</table>
|
|
80429
|
+
</div>$0`
|
|
80430
|
+
),
|
|
80431
|
+
sampleData: {
|
|
80432
|
+
invoice: { due_date: "2026-10-13" },
|
|
80433
|
+
payment: {
|
|
80434
|
+
name: "Fennlor Studio GmbH",
|
|
80435
|
+
iban: "DE00000000000000000000",
|
|
80436
|
+
bic: "XXXXDEXXXXX",
|
|
80437
|
+
amount: 3760.4,
|
|
80438
|
+
reference: "2026-0042"
|
|
80439
|
+
}
|
|
80440
|
+
},
|
|
80441
|
+
css: `.ff-payment { margin-top: 16px; font-size: 9.5pt; break-inside: avoid; }
|
|
80442
|
+
.ff-payment p { margin: 0 0 6px; }
|
|
80443
|
+
.ff-payment table { border-collapse: collapse; }
|
|
80444
|
+
.ff-payment th { text-align: left; font-weight: normal; color: #555; padding: 1px 16px 1px 0; }`,
|
|
80445
|
+
i18n: {
|
|
80446
|
+
en: {
|
|
80447
|
+
"payment.transfer": "Please transfer",
|
|
80448
|
+
"payment.until": "by",
|
|
80449
|
+
"payment.account": "to the following account:",
|
|
80450
|
+
"payment.holder": "Account holder",
|
|
80451
|
+
"payment.reference": "Reference"
|
|
80452
|
+
},
|
|
80453
|
+
de: {
|
|
80454
|
+
"payment.transfer": "Bitte \xFCberweisen Sie",
|
|
80455
|
+
"payment.until": "bis zum",
|
|
80456
|
+
"payment.account": "auf folgendes Konto:",
|
|
80457
|
+
"payment.holder": "Kontoinhaber",
|
|
80458
|
+
"payment.reference": "Verwendungszweck"
|
|
80459
|
+
}
|
|
80460
|
+
}
|
|
80461
|
+
},
|
|
80462
|
+
{
|
|
80463
|
+
id: "small-business-note",
|
|
80464
|
+
group: "invoice",
|
|
80465
|
+
title: "Small business VAT note",
|
|
80466
|
+
description: "States that no VAT is charged under \xA7 19 UStG (Kleinunternehmerregelung).",
|
|
80467
|
+
source: forAll((e) => `<p class="ff-note">${tr(e, "vat.small_business")}</p>$0`),
|
|
80468
|
+
css: `.ff-note { margin-top: 12px; font-size: 9pt; color: #444; }`,
|
|
80469
|
+
i18n: {
|
|
80470
|
+
en: { "vat.small_business": "No VAT is charged under the small business scheme of \xA7 19 UStG." },
|
|
80471
|
+
de: { "vat.small_business": "Gem\xE4\xDF \xA7 19 UStG wird keine Umsatzsteuer berechnet." }
|
|
80472
|
+
}
|
|
80473
|
+
},
|
|
80474
|
+
{
|
|
80475
|
+
id: "reverse-charge-note",
|
|
80476
|
+
group: "invoice",
|
|
80477
|
+
title: "Reverse charge note",
|
|
80478
|
+
description: "EU business customers: the recipient owes the VAT, with their VAT ID.",
|
|
80479
|
+
source: forAll(
|
|
80480
|
+
(e) => `<p class="ff-note">${tr(e, "vat.reverse_charge")} ${tr(e, "vat.customer_id")} ${val(e, "customer.vat_id")}</p>$0`
|
|
80481
|
+
),
|
|
80482
|
+
sampleData: { customer: { vat_id: "ATU00000000" } },
|
|
80483
|
+
css: `.ff-note { margin-top: 12px; font-size: 9pt; color: #444; }`,
|
|
80484
|
+
i18n: {
|
|
80485
|
+
en: {
|
|
80486
|
+
"vat.reverse_charge": "Reverse charge: the recipient of the service is liable for VAT.",
|
|
80487
|
+
"vat.customer_id": "Customer VAT ID:"
|
|
80488
|
+
},
|
|
80489
|
+
de: {
|
|
80490
|
+
"vat.reverse_charge": "Steuerschuldnerschaft des Leistungsempf\xE4ngers.",
|
|
80491
|
+
"vat.customer_id": "USt-IdNr. des Leistungsempf\xE4ngers:"
|
|
80492
|
+
}
|
|
80493
|
+
}
|
|
79902
80494
|
},
|
|
79903
80495
|
{
|
|
79904
80496
|
id: "address-block",
|
|
80497
|
+
group: "letter",
|
|
79905
80498
|
title: "Address block",
|
|
79906
80499
|
description: "Recipient name and postal address lines.",
|
|
79907
80500
|
source: forAll(
|
|
@@ -79914,10 +80507,10 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
79914
80507
|
),
|
|
79915
80508
|
sampleData: {
|
|
79916
80509
|
customer: {
|
|
79917
|
-
name: "
|
|
79918
|
-
street: "
|
|
79919
|
-
zip: "
|
|
79920
|
-
city: "
|
|
80510
|
+
name: "Olvarest GmbH",
|
|
80511
|
+
street: "Beispielweg 2",
|
|
80512
|
+
zip: "54321",
|
|
80513
|
+
city: "Beispielstadt",
|
|
79921
80514
|
country: "Deutschland"
|
|
79922
80515
|
}
|
|
79923
80516
|
},
|
|
@@ -79925,18 +80518,38 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
79925
80518
|
},
|
|
79926
80519
|
{
|
|
79927
80520
|
id: "page-footer",
|
|
80521
|
+
group: "page",
|
|
80522
|
+
kinds: ["pdf"],
|
|
79928
80523
|
title: "Footer with page numbers",
|
|
79929
|
-
description: "
|
|
80524
|
+
description: "Company line and \u201CPage x of y\u201D in the page footer.",
|
|
80525
|
+
target: "footer",
|
|
79930
80526
|
source: forAll(
|
|
79931
80527
|
(e) => `<div style="width:100%;display:flex;justify-content:space-between;font-size:9px;color:#666">
|
|
79932
80528
|
<span>${val(e, "company.name")} \xB7 ${val(e, "company.email")}</span>
|
|
79933
|
-
<span
|
|
80529
|
+
<span>${tr(e, "footer.page")} <span class="pageNumber"></span> ${tr(e, "footer.of")} <span class="totalPages"></span></span>
|
|
79934
80530
|
</div>$0`
|
|
79935
80531
|
),
|
|
79936
|
-
sampleData: { company: { name: "
|
|
80532
|
+
sampleData: { company: { name: "Fennlor Studio GmbH", email: "hello@fennlor.example" } },
|
|
80533
|
+
i18n: {
|
|
80534
|
+
en: { "footer.page": "Page", "footer.of": "of" },
|
|
80535
|
+
de: { "footer.page": "Seite", "footer.of": "von" }
|
|
80536
|
+
}
|
|
80537
|
+
},
|
|
80538
|
+
{
|
|
80539
|
+
id: "legal-footer",
|
|
80540
|
+
group: "page",
|
|
80541
|
+
kinds: ["pdf"],
|
|
80542
|
+
title: "Legal footer",
|
|
80543
|
+
description: "The legal footer from the brand kit, centred in the page footer.",
|
|
80544
|
+
target: "footer",
|
|
80545
|
+
source: forAll(
|
|
80546
|
+
(e) => `<div style="width:100%;text-align:center;font-size:8px;color:#666">${val(e, "brand.legal_footer")}</div>$0`
|
|
80547
|
+
)
|
|
79937
80548
|
},
|
|
79938
80549
|
{
|
|
79939
80550
|
id: "cover-page",
|
|
80551
|
+
group: "report",
|
|
80552
|
+
kinds: ["pdf"],
|
|
79940
80553
|
title: "Cover page",
|
|
79941
80554
|
description: "Title, subtitle and date on a page of its own.",
|
|
79942
80555
|
source: forAll(
|
|
@@ -79955,12 +80568,13 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
79955
80568
|
}
|
|
79956
80569
|
},
|
|
79957
80570
|
css: `.ff-cover { min-height: 60vh; display: flex; flex-direction: column; justify-content: center; }
|
|
79958
|
-
.ff-cover h1 { font-size: 32pt; margin: 0 0 8px; }
|
|
80571
|
+
.ff-cover h1 { font-family: var(--brand-font-heading, inherit); font-size: 32pt; color: var(--brand-color-primary, inherit); margin: 0 0 8px; }
|
|
79959
80572
|
.ff-cover .subtitle { font-size: 16pt; color: #444; margin: 0; }
|
|
79960
80573
|
.ff-cover .date { margin-top: 24px; color: #666; }`
|
|
79961
80574
|
},
|
|
79962
80575
|
{
|
|
79963
80576
|
id: "signature",
|
|
80577
|
+
group: "letter",
|
|
79964
80578
|
title: "Signature line",
|
|
79965
80579
|
description: "Place, date and a signature line.",
|
|
79966
80580
|
source: forAll(
|
|
@@ -79973,7 +80587,7 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
79973
80587
|
signature: {
|
|
79974
80588
|
place: "Berlin",
|
|
79975
80589
|
date: "2026-09-07",
|
|
79976
|
-
name: "
|
|
80590
|
+
name: "Erika Mustermann"
|
|
79977
80591
|
}
|
|
79978
80592
|
},
|
|
79979
80593
|
css: `.ff-signature { margin-top: 48px; width: 260px; font-size: 9pt; color: #444; }
|
|
@@ -79981,33 +80595,32 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
79981
80595
|
},
|
|
79982
80596
|
{
|
|
79983
80597
|
id: "epc-qr",
|
|
80598
|
+
group: "invoice",
|
|
79984
80599
|
title: "SEPA payment QR code",
|
|
79985
80600
|
description: "GiroCode that banking apps scan to prefill the transfer.",
|
|
79986
80601
|
source: {
|
|
79987
80602
|
jinja2: `<figure class="ff-qr">
|
|
79988
|
-
<img src="{{ epcQr(
|
|
79989
|
-
<figcaption
|
|
80603
|
+
<img src="{{ epcQr(payment, { size: 120 }) }}" alt="${tr("jinja2", "qr.alt")}">
|
|
80604
|
+
<figcaption>${tr("jinja2", "qr.caption")}</figcaption>
|
|
79990
80605
|
</figure>$0`,
|
|
79991
80606
|
liquid: `<figure class="ff-qr">
|
|
79992
|
-
<img src="{{ payment | epcQr }}" alt="
|
|
79993
|
-
<figcaption
|
|
80607
|
+
<img src="{{ payment | epcQr: size: 120 }}" alt="${tr("liquid", "qr.alt")}">
|
|
80608
|
+
<figcaption>${tr("liquid", "qr.caption")}</figcaption>
|
|
79994
80609
|
</figure>$0`,
|
|
79995
80610
|
handlebars: `<figure class="ff-qr">
|
|
79996
|
-
<img src="{{epcQr payment size=120}}" alt="
|
|
79997
|
-
<figcaption
|
|
80611
|
+
<img src="{{epcQr payment size=120}}" alt="${tr("handlebars", "qr.alt")}">
|
|
80612
|
+
<figcaption>${tr("handlebars", "qr.caption")}</figcaption>
|
|
79998
80613
|
</figure>$0`
|
|
79999
80614
|
},
|
|
80615
|
+
i18n: {
|
|
80616
|
+
en: { "qr.alt": "Payment QR code", "qr.caption": "Scan to pay" },
|
|
80617
|
+
de: { "qr.alt": "QR-Code zur Zahlung", "qr.caption": "Zum Bezahlen scannen" }
|
|
80618
|
+
},
|
|
80000
80619
|
sampleData: {
|
|
80001
|
-
company: {
|
|
80002
|
-
name: "Acme GmbH",
|
|
80003
|
-
iban: "DE02120300000000202051",
|
|
80004
|
-
bic: "BYLADEM1001"
|
|
80005
|
-
},
|
|
80006
|
-
invoice: { number: "2026-0042", total: 3760.4 },
|
|
80007
80620
|
payment: {
|
|
80008
|
-
name: "
|
|
80009
|
-
iban: "
|
|
80010
|
-
bic: "
|
|
80621
|
+
name: "Fennlor Studio GmbH",
|
|
80622
|
+
iban: "DE00000000000000000000",
|
|
80623
|
+
bic: "XXXXDEXXXXX",
|
|
80011
80624
|
amount: 3760.4,
|
|
80012
80625
|
reference: "2026-0042"
|
|
80013
80626
|
}
|
|
@@ -80017,38 +80630,29 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
80017
80630
|
},
|
|
80018
80631
|
{
|
|
80019
80632
|
id: "chart",
|
|
80633
|
+
group: "report",
|
|
80020
80634
|
title: "Chart",
|
|
80021
|
-
description: "Bar chart
|
|
80635
|
+
description: "Bar chart from labels and values in the data, in the brand colour.",
|
|
80636
|
+
// the colour comes from the brand kit; without one the helper takes its palette
|
|
80022
80637
|
source: {
|
|
80023
80638
|
jinja2: `<figure class="ff-chart-box">
|
|
80024
|
-
{{ chart(
|
|
80025
|
-
<figcaption>{{
|
|
80639
|
+
{{ chart(sales, { type: '\${1:bar}', width: 480, height: 240, color: brand.colors.primary }) }}
|
|
80640
|
+
<figcaption>{{ sales.caption }}</figcaption>
|
|
80026
80641
|
</figure>$0`,
|
|
80027
80642
|
liquid: `<figure class="ff-chart-box">
|
|
80028
|
-
{{
|
|
80029
|
-
<figcaption>{{
|
|
80643
|
+
{{ sales | chart: type: '\${1:bar}', width: 480, height: 240, color: brand.colors.primary }}
|
|
80644
|
+
<figcaption>{{ sales.caption }}</figcaption>
|
|
80030
80645
|
</figure>$0`,
|
|
80031
80646
|
handlebars: `<figure class="ff-chart-box">
|
|
80032
|
-
{{chart
|
|
80033
|
-
<figcaption>{{
|
|
80647
|
+
{{chart sales type='\${1:bar}' width=480 height=240 color=brand.colors.primary}}
|
|
80648
|
+
<figcaption>{{sales.caption}}</figcaption>
|
|
80034
80649
|
</figure>$0`
|
|
80035
80650
|
},
|
|
80036
80651
|
sampleData: {
|
|
80037
|
-
|
|
80038
|
-
type: "bar",
|
|
80652
|
+
sales: {
|
|
80039
80653
|
caption: "Revenue per quarter",
|
|
80040
|
-
|
|
80041
|
-
|
|
80042
|
-
data: {
|
|
80043
|
-
labels: ["Q1", "Q2", "Q3", "Q4"],
|
|
80044
|
-
datasets: [
|
|
80045
|
-
{
|
|
80046
|
-
label: "Revenue",
|
|
80047
|
-
data: [12e3, 15500, 14200, 18900],
|
|
80048
|
-
backgroundColor: "#3E63DD"
|
|
80049
|
-
}
|
|
80050
|
-
]
|
|
80051
|
-
}
|
|
80654
|
+
labels: ["Q1", "Q2", "Q3", "Q4"],
|
|
80655
|
+
values: [12e3, 15500, 14200, 18900]
|
|
80052
80656
|
}
|
|
80053
80657
|
},
|
|
80054
80658
|
css: `.ff-chart-box { margin: 16px 0; break-inside: avoid; }
|
|
@@ -80056,6 +80660,7 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
80056
80660
|
},
|
|
80057
80661
|
{
|
|
80058
80662
|
id: "two-columns",
|
|
80663
|
+
group: "page",
|
|
80059
80664
|
title: "Two-column layout",
|
|
80060
80665
|
description: "Two equal columns that stay side by side in print.",
|
|
80061
80666
|
source: forAll(
|
|
@@ -80066,10 +80671,102 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
|
|
|
80066
80671
|
),
|
|
80067
80672
|
css: `.ff-columns { display: flex; gap: 24px; }
|
|
80068
80673
|
.ff-columns > div { flex: 1 1 0; min-width: 0; }`
|
|
80674
|
+
},
|
|
80675
|
+
{
|
|
80676
|
+
id: "keep-together",
|
|
80677
|
+
group: "page",
|
|
80678
|
+
kinds: ["pdf"],
|
|
80679
|
+
title: "Keep together",
|
|
80680
|
+
description: "Content that is never split across two pages.",
|
|
80681
|
+
source: forAll(() => `<div class="avoid-break">
|
|
80682
|
+
$0
|
|
80683
|
+
</div>`)
|
|
80684
|
+
},
|
|
80685
|
+
{
|
|
80686
|
+
id: "page-break",
|
|
80687
|
+
group: "page",
|
|
80688
|
+
kinds: ["pdf"],
|
|
80689
|
+
title: "Page break",
|
|
80690
|
+
description: "Starts a new page in the PDF.",
|
|
80691
|
+
source: forAll(() => `<div class="page-break"></div>
|
|
80692
|
+
$0`)
|
|
80693
|
+
},
|
|
80694
|
+
{
|
|
80695
|
+
id: "kpi-cards",
|
|
80696
|
+
group: "report",
|
|
80697
|
+
title: "Key figures",
|
|
80698
|
+
description: "A row of cards with label, value and change for each figure.",
|
|
80699
|
+
source: forAll(
|
|
80700
|
+
(e) => `<div class="ff-kpis">
|
|
80701
|
+
${loop(e, "kpi", "report.kpis", ` <div class="kpi"><span class="label">${val(e, "kpi.label")}</span><strong>${val(e, "kpi.value")}</strong><span class="change">${val(e, "kpi.change")}</span></div>`)}
|
|
80702
|
+
</div>$0`
|
|
80703
|
+
),
|
|
80704
|
+
sampleData: {
|
|
80705
|
+
report: {
|
|
80706
|
+
kpis: [
|
|
80707
|
+
{ label: "Revenue", value: "\u20AC 61,600", change: "+12 % on Q2" },
|
|
80708
|
+
{ label: "New customers", value: "48", change: "+9 on Q2" },
|
|
80709
|
+
{ label: "Churn", value: "1.8 %", change: "\u22120.4 pt on Q2" }
|
|
80710
|
+
]
|
|
80711
|
+
}
|
|
80712
|
+
},
|
|
80713
|
+
css: `.ff-kpis { display: flex; gap: 12px; margin: 16px 0; break-inside: avoid; }
|
|
80714
|
+
.ff-kpis .kpi { flex: 1 1 0; padding: 10px 12px; border: 1px solid #e5e5e5; border-top: 3px solid ${rule}; border-radius: 4px; }
|
|
80715
|
+
.ff-kpis .label { display: block; font-size: 8pt; color: #666; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
80716
|
+
.ff-kpis strong { display: block; margin: 2px 0; font-size: 18pt; }
|
|
80717
|
+
.ff-kpis .change { font-size: 8pt; color: #555; }`
|
|
80069
80718
|
}
|
|
80070
80719
|
];
|
|
80071
80720
|
|
|
80721
|
+
// packages/api-types/src/lib/assets.ts
|
|
80722
|
+
var SEGMENT = "[A-Za-z0-9][A-Za-z0-9._-]*";
|
|
80723
|
+
var ASSET_NAME_PATTERN = new RegExp(`^${SEGMENT}(?:/${SEGMENT})*$`);
|
|
80724
|
+
var ASSET_MAX_BYTES = 10 * 1024 * 1024;
|
|
80725
|
+
|
|
80726
|
+
// packages/engine/src/lib/office/errors.ts
|
|
80727
|
+
var officeLimits = {
|
|
80728
|
+
/** Entries in one container. */
|
|
80729
|
+
maxEntries: 2e3,
|
|
80730
|
+
/** Sum of the uncompressed sizes the central directory declares. */
|
|
80731
|
+
maxDeclaredBytes: 200 * 1024 * 1024,
|
|
80732
|
+
/** One part we inflate; enforced while inflating, because the declared size can lie. */
|
|
80733
|
+
maxPartBytes: 20 * 1024 * 1024,
|
|
80734
|
+
/** A conversion upload or a template file. */
|
|
80735
|
+
maxFileBytes: 20 * 1024 * 1024
|
|
80736
|
+
};
|
|
80737
|
+
|
|
80738
|
+
// packages/engine/src/lib/office/drawings.ts
|
|
80739
|
+
var OPEN = String.fromCharCode(57346);
|
|
80740
|
+
var CLOSE = String.fromCharCode(57347);
|
|
80741
|
+
var officeDrawingLimits = {
|
|
80742
|
+
/** Drawings per render. */
|
|
80743
|
+
maxDrawings: 50,
|
|
80744
|
+
/** Image bytes placed per render. */
|
|
80745
|
+
maxImageBytes: 20 * 1024 * 1024,
|
|
80746
|
+
/** Largest raster edge in pixels. */
|
|
80747
|
+
maxPixels: 2400
|
|
80748
|
+
};
|
|
80749
|
+
|
|
80750
|
+
// packages/engine/src/lib/office/render.ts
|
|
80751
|
+
var OFFICE_FILLED_MAX_BYTES = 50 * 1024 * 1024;
|
|
80752
|
+
|
|
80072
80753
|
// packages/sdk-ts/src/lib/client.ts
|
|
80754
|
+
var isOfficeInput = (input) => "file" in input;
|
|
80755
|
+
function blobOf(file) {
|
|
80756
|
+
const blob = file.data instanceof Blob ? file.data : new Blob([file.data]);
|
|
80757
|
+
const own = typeof File !== "undefined" && file.data instanceof File ? file.data.name : void 0;
|
|
80758
|
+
return { blob, name: file.name ?? own ?? "document" };
|
|
80759
|
+
}
|
|
80760
|
+
function multipart(fields, file) {
|
|
80761
|
+
const form = new FormData();
|
|
80762
|
+
const { blob, name } = blobOf(file);
|
|
80763
|
+
form.set("file", blob, name);
|
|
80764
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
80765
|
+
if (value === void 0) continue;
|
|
80766
|
+
form.set(key, typeof value === "string" ? value : typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
80767
|
+
}
|
|
80768
|
+
return form;
|
|
80769
|
+
}
|
|
80073
80770
|
var FormfeedError = class extends Error {
|
|
80074
80771
|
constructor(code, message2, status, problem = null, requestId = null) {
|
|
80075
80772
|
super(message2);
|
|
@@ -80096,6 +80793,8 @@ function randomKey() {
|
|
|
80096
80793
|
if (c?.randomUUID) return c.randomUUID();
|
|
80097
80794
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
80098
80795
|
}
|
|
80796
|
+
var withKey = (options) => ({ ...options, idempotencyKey: options.idempotencyKey ?? randomKey() });
|
|
80797
|
+
var idOf = (source) => typeof source === "string" ? source : source.id;
|
|
80099
80798
|
var Formfeed = class {
|
|
80100
80799
|
baseUrl;
|
|
80101
80800
|
apiKey;
|
|
@@ -80159,11 +80858,96 @@ var Formfeed = class {
|
|
|
80159
80858
|
},
|
|
80160
80859
|
/** Removes the stored files of a render before they expire. Needs the `file:delete` scope. */
|
|
80161
80860
|
deleteOutputs: (id, options = {}) => this.request("DELETE", `/renders/${encodeURIComponent(id)}/outputs`, void 0, options),
|
|
80861
|
+
/**
|
|
80862
|
+
* The stored request of a render, to render it again. Needs `render:input`; throws
|
|
80863
|
+
* `render_input_expired` (410) when the workspace keeps no requests or the period ended.
|
|
80864
|
+
*/
|
|
80865
|
+
input: (id, options = {}) => this.request("GET", `/renders/${encodeURIComponent(id)}/input`, void 0, options),
|
|
80162
80866
|
batch: (request, options = {}) => this.request("POST", "/renders/batch", request, {
|
|
80163
80867
|
...options,
|
|
80164
80868
|
idempotencyKey: options.idempotencyKey ?? randomKey()
|
|
80165
80869
|
})
|
|
80166
80870
|
};
|
|
80871
|
+
/**
|
|
80872
|
+
* PDF tools on the outputs of earlier renders (a `Render` or its id) and on PDFs of the file library
|
|
80873
|
+
* (a `LibraryFile`, its id or its name). merge, protect and watermark return a new render and cost
|
|
80874
|
+
* 0.5 units each on live keys; info is free.
|
|
80875
|
+
*/
|
|
80876
|
+
pdf = {
|
|
80877
|
+
merge: (sources, output = {}, options = {}) => this.request("POST", "/pdf/merge", { ...output, sources: sources.map(idOf) }, withKey(options)),
|
|
80878
|
+
protect: (source, protect, options = {}) => this.request("POST", "/pdf/protect", { ...protect, source: idOf(source) }, withKey(options)),
|
|
80879
|
+
watermark: (source, watermark, options = {}) => this.request("POST", "/pdf/watermark", { ...watermark, source: idOf(source) }, withKey(options)),
|
|
80880
|
+
info: (source, options = {}) => this.request("POST", "/pdf/info", { source: idOf(source) }, options),
|
|
80881
|
+
/**
|
|
80882
|
+
* Converts an office document to a PDF render (Starter plan and above): an uploaded file (Word,
|
|
80883
|
+
* Excel, PowerPoint, OpenDocument, RTF or HTML, up to 20 MB, not kept), or the output of a Word or
|
|
80884
|
+
* PowerPoint template render. Costs follow the PDF rule. A busy converter answers 503, which is
|
|
80885
|
+
* retried like any other.
|
|
80886
|
+
*/
|
|
80887
|
+
convert: (source, convert = {}, options = {}) => {
|
|
80888
|
+
if (typeof source === "object" && "file" in source)
|
|
80889
|
+
return this.request("POST", "/pdf/convert", multipart({ ...convert }, source.file), withKey(options));
|
|
80890
|
+
return this.request("POST", "/pdf/convert", { ...convert, source: idOf(source) }, withKey(options));
|
|
80891
|
+
}
|
|
80892
|
+
};
|
|
80893
|
+
/**
|
|
80894
|
+
* The workspace file library (`/files`): images and PDFs a template references by name with
|
|
80895
|
+
* `asset('logo.png')`, and the sources of image watermarks and merges. Needs `file:read`,
|
|
80896
|
+
* `file:write` or `file:delete`. Files are served without a signature, so keep confidential
|
|
80897
|
+
* documents out of it.
|
|
80898
|
+
*/
|
|
80899
|
+
files = {
|
|
80900
|
+
/** Uploads a file; the same name replaces the existing file in place and keeps its URL. */
|
|
80901
|
+
upload: (file, options = {}) => {
|
|
80902
|
+
const blob = file.data instanceof Blob ? file.data : new Blob([file.data], file.contentType ? { type: file.contentType } : {});
|
|
80903
|
+
const form = new FormData();
|
|
80904
|
+
form.set("file", blob, file.name);
|
|
80905
|
+
form.set("name", file.name);
|
|
80906
|
+
return this.request("POST", "/files", form, options);
|
|
80907
|
+
},
|
|
80908
|
+
/** Page of files, newest first. */
|
|
80909
|
+
list: (query = {}, options = {}) => {
|
|
80910
|
+
const params = new URLSearchParams();
|
|
80911
|
+
for (const [k2, v2] of Object.entries(query)) if (v2 !== void 0 && v2 !== null && v2 !== "") params.set(k2, String(v2));
|
|
80912
|
+
const qs = params.toString();
|
|
80913
|
+
return this.request("GET", `/files${qs ? `?${qs}` : ""}`, void 0, options);
|
|
80914
|
+
},
|
|
80915
|
+
/** Every file matching the query, following the cursor. */
|
|
80916
|
+
all: async (query = {}, options = {}) => {
|
|
80917
|
+
const out = [];
|
|
80918
|
+
let cursor = null;
|
|
80919
|
+
do {
|
|
80920
|
+
const page = await this.files.list({ ...query, cursor }, options);
|
|
80921
|
+
out.push(...page.data);
|
|
80922
|
+
cursor = page.next_cursor;
|
|
80923
|
+
} while (cursor);
|
|
80924
|
+
return out;
|
|
80925
|
+
},
|
|
80926
|
+
get: (id, options = {}) => this.request("GET", `/files/${encodeURIComponent(id)}`, void 0, options),
|
|
80927
|
+
/** Removes the file and its bytes; templates that name it render a missing image afterwards. */
|
|
80928
|
+
delete: (file, options = {}) => this.request("DELETE", `/files/${encodeURIComponent(idOf(file))}`, void 0, options)
|
|
80929
|
+
};
|
|
80930
|
+
/** The organisation's brand kit, read-only through the API (it is edited in the app). Needs `template:read`. */
|
|
80931
|
+
brand = {
|
|
80932
|
+
get: (options = {}) => this.request("GET", "/brand", void 0, options)
|
|
80933
|
+
};
|
|
80934
|
+
/**
|
|
80935
|
+
* Shared partials of the organisation. Renders use the current source, so a change reaches every
|
|
80936
|
+
* template that includes the partial without a new version. Needs `template:read` or `template:write`.
|
|
80937
|
+
*/
|
|
80938
|
+
partials = {
|
|
80939
|
+
/** Every shared partial, without its source. */
|
|
80940
|
+
list: async (options = {}) => (await this.request("GET", "/partials", void 0, options)).data,
|
|
80941
|
+
/** One partial with its source. */
|
|
80942
|
+
get: (name, options = {}) => this.request("GET", `/partials/${encodeURIComponent(name)}`, void 0, options),
|
|
80943
|
+
/** Creates or replaces a partial; pass `base_version` to fail with 409 when it changed since you read it. */
|
|
80944
|
+
put: async (name, input, options = {}) => {
|
|
80945
|
+
const { status, body } = await this.send("PUT", `/partials/${encodeURIComponent(name)}`, input, options);
|
|
80946
|
+
return { partial: body, created: status === 201 };
|
|
80947
|
+
},
|
|
80948
|
+
/** Removes a partial; templates that still include it fail to render afterwards. */
|
|
80949
|
+
delete: (name, options = {}) => this.request("DELETE", `/partials/${encodeURIComponent(name)}`, void 0, options)
|
|
80950
|
+
};
|
|
80167
80951
|
jobs = {
|
|
80168
80952
|
get: (id, options = {}) => this.request("GET", `/jobs/${encodeURIComponent(id)}`, void 0, options),
|
|
80169
80953
|
waitFor: async (id, options = {}) => {
|
|
@@ -80183,7 +80967,20 @@ var Formfeed = class {
|
|
|
80183
80967
|
create: (input, options = {}) => this.request("POST", "/webhooks", input, options),
|
|
80184
80968
|
update: (id, patch, options = {}) => this.request("PUT", `/webhooks/${encodeURIComponent(id)}`, patch, options),
|
|
80185
80969
|
delete: (id, options = {}) => this.request("DELETE", `/webhooks/${encodeURIComponent(id)}`, void 0, options),
|
|
80186
|
-
test: (id, options = {}) => this.request("POST", `/webhooks/${encodeURIComponent(id)}/test`, void 0, options)
|
|
80970
|
+
test: (id, options = {}) => this.request("POST", `/webhooks/${encodeURIComponent(id)}/test`, void 0, options),
|
|
80971
|
+
/**
|
|
80972
|
+
* Sends a stored event (`evt_…`) again as a new delivery to a registered endpoint (`webhook:manage`)
|
|
80973
|
+
* or a running listen session (`webhook:listen`).
|
|
80974
|
+
*/
|
|
80975
|
+
resend: (eventId, endpointId, options = {}) => this.request("POST", `/webhooks/events/${encodeURIComponent(eventId)}/resend`, { endpoint_id: endpointId }, options),
|
|
80976
|
+
/**
|
|
80977
|
+
* Listen sessions, what `formfeed listen` uses. Needs `webhook:listen`. Starting a session ends the
|
|
80978
|
+
* previous one of the same key; the WebSocket itself is up to the caller.
|
|
80979
|
+
*/
|
|
80980
|
+
listen: {
|
|
80981
|
+
start: (input = {}, options = {}) => this.request("POST", "/webhooks/listen", input, options),
|
|
80982
|
+
end: (id, options = {}) => this.request("DELETE", `/webhooks/listen/${encodeURIComponent(id)}`, void 0, options)
|
|
80983
|
+
}
|
|
80187
80984
|
};
|
|
80188
80985
|
templates = {
|
|
80189
80986
|
list: (query = {}, options = {}) => {
|
|
@@ -80204,32 +81001,127 @@ var Formfeed = class {
|
|
|
80204
81001
|
return out;
|
|
80205
81002
|
},
|
|
80206
81003
|
get: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}`, void 0, options),
|
|
80207
|
-
|
|
81004
|
+
/** A Word or PowerPoint template (`kind` `docx` or `pptx`) is created from its `file`, sent as multipart. */
|
|
81005
|
+
create: (input, options = {}) => {
|
|
81006
|
+
if (isOfficeInput(input) && input.file) {
|
|
81007
|
+
const { file, ...fields } = input;
|
|
81008
|
+
return this.request("POST", "/templates", multipart(fields, file), options);
|
|
81009
|
+
}
|
|
81010
|
+
return this.request("POST", "/templates", input, options);
|
|
81011
|
+
},
|
|
80208
81012
|
update: (idOrSlug, patch, options = {}) => this.request("PUT", `/templates/${encodeURIComponent(idOrSlug)}`, patch, options),
|
|
80209
81013
|
archive: (idOrSlug, options = {}) => this.request("DELETE", `/templates/${encodeURIComponent(idOrSlug)}`, void 0, options),
|
|
80210
81014
|
versions: {
|
|
80211
81015
|
list: async (idOrSlug, options = {}) => (await this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions`, void 0, options)).data,
|
|
80212
|
-
/** `which`: 'published', 'latest' or a version
|
|
80213
|
-
get: (idOrSlug, which = "published", options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions/${which}`, void 0, options),
|
|
80214
|
-
|
|
80215
|
-
|
|
81016
|
+
/** `which`: 'published', 'latest', a version number or a channel name (its main version). Carries the files. */
|
|
81017
|
+
get: (idOrSlug, which = "published", options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions/${encodeURIComponent(String(which))}`, void 0, options),
|
|
81018
|
+
/** For a Word or PowerPoint template, pass `file` to replace the document; without it the latest file is kept. */
|
|
81019
|
+
create: (idOrSlug, input, options = {}) => {
|
|
81020
|
+
const path = `/templates/${encodeURIComponent(idOrSlug)}/versions`;
|
|
81021
|
+
if (isOfficeInput(input)) {
|
|
81022
|
+
const { file, ...fields } = input;
|
|
81023
|
+
if (file) return this.request("POST", path, multipart(fields, file), options);
|
|
81024
|
+
return this.request("POST", path, fields, options);
|
|
81025
|
+
}
|
|
81026
|
+
return this.request("POST", path, input, options);
|
|
81027
|
+
},
|
|
81028
|
+
/** The Word or PowerPoint file of a version (`which` as for `get`); needs `template:read`. */
|
|
81029
|
+
file: async (idOrSlug, which = "published", options = {}) => {
|
|
81030
|
+
const path = `/templates/${encodeURIComponent(idOrSlug)}/versions/${encodeURIComponent(String(which))}/file`;
|
|
81031
|
+
return (await this.send("GET", path, void 0, options, "bytes")).body;
|
|
81032
|
+
},
|
|
81033
|
+
/**
|
|
81034
|
+
* Publishes a version. When both versions store a data schema and the new one breaks callers of the
|
|
81035
|
+
* published one, the API refuses with `schema_breaking_change` unless `allowBreaking` is set.
|
|
81036
|
+
*/
|
|
81037
|
+
publish: (idOrSlug, number, options = {}) => {
|
|
81038
|
+
const { allowBreaking, ...rest } = options;
|
|
81039
|
+
return this.request(
|
|
81040
|
+
"POST",
|
|
81041
|
+
`/templates/${encodeURIComponent(idOrSlug)}/versions/${number}/publish`,
|
|
81042
|
+
allowBreaking ? { allow_breaking: true } : void 0,
|
|
81043
|
+
rest
|
|
81044
|
+
);
|
|
81045
|
+
}
|
|
81046
|
+
},
|
|
81047
|
+
/**
|
|
81048
|
+
* Release channels (`published` and named ones such as `staging`): a render with `version: 'staging'`
|
|
81049
|
+
* uses the version the channel points at. Moves reach renders within a minute.
|
|
81050
|
+
*/
|
|
81051
|
+
channels: {
|
|
81052
|
+
list: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/channels`, void 0, options),
|
|
81053
|
+
/** Creates or moves a channel; on `published` this publishes. */
|
|
81054
|
+
set: (idOrSlug, name, move, options = {}) => {
|
|
81055
|
+
const { allowBreaking, ...rest } = options;
|
|
81056
|
+
return this.request(
|
|
81057
|
+
"PUT",
|
|
81058
|
+
`/templates/${encodeURIComponent(idOrSlug)}/channels/${encodeURIComponent(name)}`,
|
|
81059
|
+
{ ...move, ...allowBreaking ? { allow_breaking: true } : {} },
|
|
81060
|
+
rest
|
|
81061
|
+
);
|
|
81062
|
+
},
|
|
81063
|
+
/** The canary becomes the main version. */
|
|
81064
|
+
promote: (idOrSlug, name, options = {}) => this.channelAction(idOrSlug, name, "promote", options),
|
|
81065
|
+
/** Back to the version before the last move. */
|
|
81066
|
+
rollback: (idOrSlug, name, options = {}) => this.channelAction(idOrSlug, name, "rollback", options),
|
|
81067
|
+
/** Refused with `channel_in_use` while renders of the last hour used the channel, unless `force`. */
|
|
81068
|
+
delete: (idOrSlug, name, options = {}) => {
|
|
81069
|
+
const { force, ...rest } = options;
|
|
81070
|
+
return this.request(
|
|
81071
|
+
"DELETE",
|
|
81072
|
+
`/templates/${encodeURIComponent(idOrSlug)}/channels/${encodeURIComponent(name)}${force ? "?force=true" : ""}`,
|
|
81073
|
+
void 0,
|
|
81074
|
+
rest
|
|
81075
|
+
);
|
|
81076
|
+
}
|
|
80216
81077
|
},
|
|
80217
|
-
schema: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/schema`, void 0, options)
|
|
81078
|
+
schema: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/schema`, void 0, options),
|
|
81079
|
+
/**
|
|
81080
|
+
* Checks a version with data without rendering: syntax, header and footer, missing partials,
|
|
81081
|
+
* runtime errors and the data against the stored schema. Free. Without `data`, the version's
|
|
81082
|
+
* sample data is checked.
|
|
81083
|
+
*/
|
|
81084
|
+
validate: (idOrSlug, input = {}, options = {}) => this.request("POST", `/templates/${encodeURIComponent(idOrSlug)}/validate`, input, options)
|
|
80218
81085
|
};
|
|
80219
81086
|
account = {
|
|
80220
81087
|
get: (options = {}) => this.request("GET", "/account", void 0, options),
|
|
80221
81088
|
/** Units of the current period, or of `YYYY-MM`, with a daily series and a breakdown per template. */
|
|
80222
81089
|
usage: (period = "current", options = {}) => this.request("GET", `/usage?period=${encodeURIComponent(period)}`, void 0, options)
|
|
80223
81090
|
};
|
|
81091
|
+
/** Workspaces of the key's organisation. Needs `workspace:delete`, which no key has by default. */
|
|
81092
|
+
workspaces = {
|
|
81093
|
+
/**
|
|
81094
|
+
* Deletes a workspace: it leaves the app and the API at once, its keys are revoked and its content
|
|
81095
|
+
* is removed after 30 days. The organisation's last workspace is refused with `last_workspace`.
|
|
81096
|
+
*/
|
|
81097
|
+
delete: (id, options = {}) => this.request("DELETE", `/workspaces/${encodeURIComponent(id)}`, void 0, options)
|
|
81098
|
+
};
|
|
81099
|
+
channelAction(idOrSlug, name, action, options) {
|
|
81100
|
+
const { allowBreaking, ...rest } = options;
|
|
81101
|
+
return this.request(
|
|
81102
|
+
"POST",
|
|
81103
|
+
`/templates/${encodeURIComponent(idOrSlug)}/channels/${encodeURIComponent(name)}/${action}`,
|
|
81104
|
+
allowBreaking ? { allow_breaking: true } : void 0,
|
|
81105
|
+
rest
|
|
81106
|
+
);
|
|
81107
|
+
}
|
|
80224
81108
|
/** Raw request with auth, retries and problem mapping; use it for endpoints without a helper. */
|
|
80225
81109
|
async request(method, path, body, options = {}) {
|
|
81110
|
+
return (await this.send(method, path, body, options)).body;
|
|
81111
|
+
}
|
|
81112
|
+
/**
|
|
81113
|
+
* `request` with the HTTP status, for endpoints whose answer depends on it (201 created, 200 updated).
|
|
81114
|
+
* `bytes` returns a successful body as bytes (file downloads); problems are JSON either way.
|
|
81115
|
+
*/
|
|
81116
|
+
async send(method, path, body, options = {}, as = "json") {
|
|
80226
81117
|
const url = `${this.baseUrl}${path}`;
|
|
80227
81118
|
const headers = {
|
|
80228
81119
|
authorization: `Bearer ${this.apiKey}`,
|
|
80229
|
-
accept: "application/json",
|
|
80230
|
-
"user-agent": "formfeed-sdk-ts/0.
|
|
81120
|
+
accept: as === "bytes" ? "*/*" : "application/json",
|
|
81121
|
+
"user-agent": "formfeed-sdk-ts/0.3"
|
|
80231
81122
|
};
|
|
80232
|
-
|
|
81123
|
+
const isForm = typeof FormData !== "undefined" && body instanceof FormData;
|
|
81124
|
+
if (body !== void 0 && !isForm) headers["content-type"] = "application/json";
|
|
80233
81125
|
if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
|
|
80234
81126
|
let attempt = 0;
|
|
80235
81127
|
for (; ; ) {
|
|
@@ -80241,7 +81133,7 @@ var Formfeed = class {
|
|
|
80241
81133
|
res = await this.fetchImpl(url, {
|
|
80242
81134
|
method,
|
|
80243
81135
|
headers,
|
|
80244
|
-
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
81136
|
+
body: body === void 0 ? void 0 : isForm ? body : JSON.stringify(body),
|
|
80245
81137
|
signal: controller.signal
|
|
80246
81138
|
});
|
|
80247
81139
|
} catch (e) {
|
|
@@ -80259,9 +81151,15 @@ var Formfeed = class {
|
|
|
80259
81151
|
await sleep(this.backoff(attempt, res.headers.get("retry-after")), options.signal);
|
|
80260
81152
|
continue;
|
|
80261
81153
|
}
|
|
80262
|
-
if (res.status === 204) return void 0;
|
|
81154
|
+
if (res.status === 204) return { status: 204, body: void 0 };
|
|
81155
|
+
if (as === "bytes" && res.ok) return { status: res.status, body: new Uint8Array(await res.arrayBuffer()) };
|
|
80263
81156
|
const text2 = await res.text();
|
|
80264
|
-
|
|
81157
|
+
let json = null;
|
|
81158
|
+
try {
|
|
81159
|
+
json = text2 ? JSON.parse(text2) : null;
|
|
81160
|
+
} catch {
|
|
81161
|
+
if (res.ok) throw new FormfeedError("invalid_response", `the API answered HTTP ${res.status} with a body that is not JSON`, res.status);
|
|
81162
|
+
}
|
|
80265
81163
|
if (!res.ok) {
|
|
80266
81164
|
const problem = json ?? {};
|
|
80267
81165
|
throw new FormfeedError(
|
|
@@ -80272,7 +81170,7 @@ var Formfeed = class {
|
|
|
80272
81170
|
res.headers.get("x-request-id")
|
|
80273
81171
|
);
|
|
80274
81172
|
}
|
|
80275
|
-
return json;
|
|
81173
|
+
return { status: res.status, body: json };
|
|
80276
81174
|
}
|
|
80277
81175
|
}
|
|
80278
81176
|
backoff(attempt, retryAfter) {
|
|
@@ -80287,9 +81185,9 @@ var encoder2 = new TextEncoder();
|
|
|
80287
81185
|
|
|
80288
81186
|
// packages/mcp/src/lib/server.ts
|
|
80289
81187
|
import { z as z2 } from "zod";
|
|
80290
|
-
var SERVER_INFO = { name: "formfeed", version: "0.
|
|
81188
|
+
var SERVER_INFO = { name: "formfeed", version: "0.3.0" };
|
|
80291
81189
|
var engineSchema = z2.enum(["jinja2", "liquid", "handlebars"]);
|
|
80292
|
-
var outputSchema = z2.enum(
|
|
81190
|
+
var outputSchema = z2.enum(outputFormats);
|
|
80293
81191
|
var templateSummary = (t) => ({
|
|
80294
81192
|
id: t.id,
|
|
80295
81193
|
slug: t.slug,
|
|
@@ -80425,7 +81323,7 @@ function createFormfeedServer(options) {
|
|
|
80425
81323
|
html: z2.string().optional().describe("Raw HTML instead of a template"),
|
|
80426
81324
|
engine: engineSchema.optional().describe("Engine for html that contains template syntax"),
|
|
80427
81325
|
data: z2.record(z2.string(), z2.unknown()).optional(),
|
|
80428
|
-
output: outputSchema.
|
|
81326
|
+
output: outputSchema.optional().describe("Left out: PDF for PDF templates and html, the template's image format for image templates"),
|
|
80429
81327
|
filename: z2.string().optional(),
|
|
80430
81328
|
locale: z2.string().optional().describe("BCP 47 tag for helpers and translations, e.g. de-DE"),
|
|
80431
81329
|
wait: z2.boolean().default(true).describe("Wait for the render to finish (sync renders finish immediately)")
|
|
@@ -80440,7 +81338,7 @@ function createFormfeedServer(options) {
|
|
|
80440
81338
|
...html ? { html } : {},
|
|
80441
81339
|
...engine ? { engine } : {},
|
|
80442
81340
|
data: data ?? {},
|
|
80443
|
-
output,
|
|
81341
|
+
...output ? { output } : {},
|
|
80444
81342
|
...filename ? { filename } : {},
|
|
80445
81343
|
...locale ? { locale } : {},
|
|
80446
81344
|
meta: { source: "mcp" }
|