@texuli/blocks 0.0.0-stage → 0.1.0-next.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/LICENSE +21 -0
- package/README.md +77 -2
- package/dist/code/CodeBlockView.svelte +135 -0
- package/dist/code/CodeBlockView.svelte.d.ts +5 -0
- package/dist/code/check.d.ts +15 -0
- package/dist/code/check.js +14 -0
- package/dist/code/code.css +201 -0
- package/dist/code/code.css.d.ts +1 -0
- package/dist/code/engine-entry.d.ts +10 -0
- package/dist/code/engine-entry.js +10 -0
- package/dist/code/engine.d.ts +18 -0
- package/dist/code/engine.js +18 -0
- package/dist/code/family-engine.d.ts +23 -0
- package/dist/code/family-engine.js +4 -0
- package/dist/code/family.d.ts +7 -0
- package/dist/code/family.js +5 -0
- package/dist/code/grammars.d.ts +12 -0
- package/dist/code/grammars.js +83 -0
- package/dist/code/highlight.d.ts +15 -0
- package/dist/code/highlight.js +53 -0
- package/dist/code/index.d.ts +13 -0
- package/dist/code/index.js +12 -0
- package/dist/code/labels.d.ts +27 -0
- package/dist/code/labels.js +24 -0
- package/dist/code/languages/bash.d.ts +3 -0
- package/dist/code/languages/bash.js +48 -0
- package/dist/code/languages/css.d.ts +10 -0
- package/dist/code/languages/css.js +67 -0
- package/dist/code/languages/html.d.ts +6 -0
- package/dist/code/languages/html.js +35 -0
- package/dist/code/languages/javascript.d.ts +3 -0
- package/dist/code/languages/javascript.js +113 -0
- package/dist/code/languages/json.d.ts +3 -0
- package/dist/code/languages/json.js +24 -0
- package/dist/code/languages/python.d.ts +3 -0
- package/dist/code/languages/python.js +35 -0
- package/dist/code/languages/shared.d.ts +22 -0
- package/dist/code/languages/shared.js +37 -0
- package/dist/code/languages/sql.d.ts +3 -0
- package/dist/code/languages/sql.js +32 -0
- package/dist/code/languages/yaml.d.ts +7 -0
- package/dist/code/languages/yaml.js +36 -0
- package/dist/code/renderers.d.ts +5 -0
- package/dist/code/renderers.js +5 -0
- package/dist/code/token-styles.d.ts +25 -0
- package/dist/code/token-styles.js +51 -0
- package/dist/code/tokenize-code.d.ts +17 -0
- package/dist/code/tokenize-code.js +34 -0
- package/dist/code/tokenizer.d.ts +76 -0
- package/dist/code/tokenizer.js +171 -0
- package/dist/core/catalog/entries.d.ts +10 -0
- package/dist/core/catalog/entries.js +589 -0
- package/dist/core/catalog/index.d.ts +4 -0
- package/dist/core/catalog/index.js +2 -0
- package/dist/core/catalog/types.d.ts +36 -0
- package/dist/core/catalog/types.js +1 -0
- package/dist/core/catalog/validate.d.ts +15 -0
- package/dist/core/catalog/validate.js +46 -0
- package/dist/core/formula/evaluate.d.ts +14 -0
- package/dist/core/formula/evaluate.js +181 -0
- package/dist/core/formula/index.d.ts +8 -0
- package/dist/core/formula/index.js +6 -0
- package/dist/core/formula/parse.d.ts +19 -0
- package/dist/core/formula/parse.js +452 -0
- package/dist/core/formula/types.d.ts +76 -0
- package/dist/core/formula/types.js +7 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.js +13 -0
- package/dist/core/limits.d.ts +27 -0
- package/dist/core/limits.js +26 -0
- package/dist/core/safety.d.ts +30 -0
- package/dist/core/safety.js +110 -0
- package/dist/core/schema/blocks.d.ts +138 -0
- package/dist/core/schema/blocks.js +181 -0
- package/dist/core/schema/code-family.d.ts +4 -0
- package/dist/core/schema/code-family.js +75 -0
- package/dist/core/schema/line-spec.d.ts +7 -0
- package/dist/core/schema/line-spec.js +19 -0
- package/dist/core/schema/media-family.d.ts +4 -0
- package/dist/core/schema/media-family.js +260 -0
- package/dist/core/schema/parse-family.d.ts +14 -0
- package/dist/core/schema/parse-family.js +37 -0
- package/dist/core/schema/primitives.d.ts +73 -0
- package/dist/core/schema/primitives.js +140 -0
- package/dist/core/schema/text-family.d.ts +4 -0
- package/dist/core/schema/text-family.js +121 -0
- package/dist/core/schema/vocabulary.d.ts +20 -0
- package/dist/core/schema/vocabulary.js +33 -0
- package/dist/core/text.d.ts +31 -0
- package/dist/core/text.js +139 -0
- package/dist/core/untrusted.d.ts +20 -0
- package/dist/core/untrusted.js +377 -0
- package/dist/css-modules.d.ts +1 -0
- package/dist/css.d.ts +1 -0
- package/dist/data/ChartBlockView.svelte +77 -0
- package/dist/data/ChartBlockView.svelte.d.ts +10 -0
- package/dist/data/DataBlockView.svelte +109 -0
- package/dist/data/DataBlockView.svelte.d.ts +5 -0
- package/dist/data/check.d.ts +10 -0
- package/dist/data/check.js +9 -0
- package/dist/data/engine.d.ts +18 -0
- package/dist/data/engine.js +10 -0
- package/dist/data/family.d.ts +6 -0
- package/dist/data/family.js +4 -0
- package/dist/data/index.d.ts +9 -0
- package/dist/data/index.js +8 -0
- package/dist/data/labels.d.ts +8 -0
- package/dist/data/labels.js +8 -0
- package/dist/data/renderers.d.ts +2 -0
- package/dist/data/renderers.js +2 -0
- package/dist/data/schema.d.ts +84 -0
- package/dist/data/schema.js +385 -0
- package/dist/diagram/BoardDiagram.svelte +48 -0
- package/dist/diagram/BoardDiagram.svelte.d.ts +8 -0
- package/dist/diagram/DiagramFamily.svelte +46 -0
- package/dist/diagram/DiagramFamily.svelte.d.ts +10 -0
- package/dist/diagram/FlowDiagram.svelte +108 -0
- package/dist/diagram/FlowDiagram.svelte.d.ts +8 -0
- package/dist/diagram/TerminalDiagram.svelte +38 -0
- package/dist/diagram/TerminalDiagram.svelte.d.ts +8 -0
- package/dist/diagram/TreeDiagram.svelte +149 -0
- package/dist/diagram/TreeDiagram.svelte.d.ts +8 -0
- package/dist/diagram/diagram.css +287 -0
- package/dist/diagram/diagram.css.d.ts +1 -0
- package/dist/diagram/engine.d.ts +16 -0
- package/dist/diagram/engine.js +14 -0
- package/dist/diagram/family.d.ts +7 -0
- package/dist/diagram/family.js +5 -0
- package/dist/diagram/index.d.ts +10 -0
- package/dist/diagram/index.js +9 -0
- package/dist/diagram/layout.d.ts +26 -0
- package/dist/diagram/layout.js +165 -0
- package/dist/diagram/schema.d.ts +74 -0
- package/dist/diagram/schema.js +358 -0
- package/dist/diagram/tones.d.ts +6 -0
- package/dist/diagram/tones.js +16 -0
- package/dist/embed/EmbedApp.svelte +138 -0
- package/dist/embed/EmbedApp.svelte.d.ts +17 -0
- package/dist/embed/EmbedBlockView.svelte +32 -0
- package/dist/embed/EmbedBlockView.svelte.d.ts +5 -0
- package/dist/embed/LinkPreview.svelte +48 -0
- package/dist/embed/LinkPreview.svelte.d.ts +10 -0
- package/dist/embed/engine.d.ts +30 -0
- package/dist/embed/engine.js +17 -0
- package/dist/embed/family.d.ts +7 -0
- package/dist/embed/family.js +5 -0
- package/dist/embed/index.d.ts +5 -0
- package/dist/embed/index.js +3 -0
- package/dist/embed/renderers.d.ts +3 -0
- package/dist/embed/renderers.js +3 -0
- package/dist/embed/sandbox.d.ts +86 -0
- package/dist/embed/sandbox.js +114 -0
- package/dist/embed/schema.d.ts +30 -0
- package/dist/embed/schema.js +217 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +15 -0
- package/dist/interactive/CalculatorBlock.svelte +61 -0
- package/dist/interactive/CalculatorBlock.svelte.d.ts +10 -0
- package/dist/interactive/ChecklistBlock.svelte +44 -0
- package/dist/interactive/ChecklistBlock.svelte.d.ts +10 -0
- package/dist/interactive/InteractiveBlockView.svelte +31 -0
- package/dist/interactive/InteractiveBlockView.svelte.d.ts +5 -0
- package/dist/interactive/PollBlock.svelte +111 -0
- package/dist/interactive/PollBlock.svelte.d.ts +10 -0
- package/dist/interactive/QuizBlock.svelte +75 -0
- package/dist/interactive/QuizBlock.svelte.d.ts +10 -0
- package/dist/interactive/check.d.ts +10 -0
- package/dist/interactive/check.js +9 -0
- package/dist/interactive/engine.d.ts +19 -0
- package/dist/interactive/engine.js +12 -0
- package/dist/interactive/family.d.ts +6 -0
- package/dist/interactive/family.js +4 -0
- package/dist/interactive/index.d.ts +13 -0
- package/dist/interactive/index.js +13 -0
- package/dist/interactive/interactive.css +97 -0
- package/dist/interactive/interactive.css.d.ts +1 -0
- package/dist/interactive/labels.d.ts +16 -0
- package/dist/interactive/labels.js +16 -0
- package/dist/interactive/renderers.d.ts +9 -0
- package/dist/interactive/renderers.js +9 -0
- package/dist/interactive/schema.d.ts +76 -0
- package/dist/interactive/schema.js +324 -0
- package/dist/map/MapBlockView.svelte +465 -0
- package/dist/map/MapBlockView.svelte.d.ts +5 -0
- package/dist/map/check.d.ts +11 -0
- package/dist/map/check.js +6 -0
- package/dist/map/engine.d.ts +19 -0
- package/dist/map/engine.js +8 -0
- package/dist/map/family.d.ts +7 -0
- package/dist/map/family.js +5 -0
- package/dist/map/index.d.ts +11 -0
- package/dist/map/index.js +10 -0
- package/dist/map/labels.d.ts +27 -0
- package/dist/map/labels.js +21 -0
- package/dist/map/renderers.d.ts +2 -0
- package/dist/map/renderers.js +2 -0
- package/dist/map/schema.d.ts +41 -0
- package/dist/map/schema.js +176 -0
- package/dist/map/view-model.d.ts +55 -0
- package/dist/map/view-model.js +168 -0
- package/dist/media/MediaBlockView.svelte +258 -0
- package/dist/media/MediaBlockView.svelte.d.ts +5 -0
- package/dist/media/check.d.ts +14 -0
- package/dist/media/check.js +13 -0
- package/dist/media/engine.d.ts +18 -0
- package/dist/media/engine.js +10 -0
- package/dist/media/family.d.ts +7 -0
- package/dist/media/family.js +5 -0
- package/dist/media/index.d.ts +11 -0
- package/dist/media/index.js +10 -0
- package/dist/media/labels.d.ts +7 -0
- package/dist/media/labels.js +12 -0
- package/dist/media/renderers.d.ts +5 -0
- package/dist/media/renderers.js +5 -0
- package/dist/media/resolve.d.ts +6 -0
- package/dist/media/resolve.js +15 -0
- package/dist/structure/NestedBlocks.svelte +65 -0
- package/dist/structure/NestedBlocks.svelte.d.ts +11 -0
- package/dist/structure/SafeBody.svelte +13 -0
- package/dist/structure/SafeBody.svelte.d.ts +7 -0
- package/dist/structure/StructureFamily.svelte +157 -0
- package/dist/structure/StructureFamily.svelte.d.ts +5 -0
- package/dist/structure/check.d.ts +14 -0
- package/dist/structure/check.js +27 -0
- package/dist/structure/engine.d.ts +18 -0
- package/dist/structure/engine.js +18 -0
- package/dist/structure/family.d.ts +7 -0
- package/dist/structure/family.js +5 -0
- package/dist/structure/index.d.ts +11 -0
- package/dist/structure/index.js +10 -0
- package/dist/structure/labels.d.ts +11 -0
- package/dist/structure/labels.js +11 -0
- package/dist/structure/renderers.d.ts +5 -0
- package/dist/structure/renderers.js +5 -0
- package/dist/structure/schema.d.ts +71 -0
- package/dist/structure/schema.js +327 -0
- package/dist/structure/structure.css +182 -0
- package/dist/structure/structure.css.d.ts +1 -0
- package/dist/text/RichText.svelte +169 -0
- package/dist/text/RichText.svelte.d.ts +13 -0
- package/dist/text/TextBlockView.svelte +177 -0
- package/dist/text/TextBlockView.svelte.d.ts +5 -0
- package/dist/text/check.d.ts +14 -0
- package/dist/text/check.js +13 -0
- package/dist/text/engine.d.ts +17 -0
- package/dist/text/engine.js +10 -0
- package/dist/text/family.d.ts +7 -0
- package/dist/text/family.js +5 -0
- package/dist/text/index.d.ts +12 -0
- package/dist/text/index.js +11 -0
- package/dist/text/labels.d.ts +22 -0
- package/dist/text/labels.js +29 -0
- package/dist/text/markdown.d.ts +80 -0
- package/dist/text/markdown.js +361 -0
- package/dist/text/renderers.d.ts +6 -0
- package/dist/text/renderers.js +6 -0
- package/package.json +167 -4
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/** Caps that keep parsing and evaluation small and linear. */
|
|
2
|
+
export const FORMULA_LIMITS = Object.freeze({
|
|
3
|
+
nodes: 64,
|
|
4
|
+
depth: 12,
|
|
5
|
+
args: 8,
|
|
6
|
+
exponent: 64,
|
|
7
|
+
roundDigits: 6,
|
|
8
|
+
literal: 1e15,
|
|
9
|
+
});
|
|
10
|
+
const NUMBER_OPS = [
|
|
11
|
+
'num',
|
|
12
|
+
'var',
|
|
13
|
+
'add',
|
|
14
|
+
'sub',
|
|
15
|
+
'mul',
|
|
16
|
+
'div',
|
|
17
|
+
'pow',
|
|
18
|
+
'min',
|
|
19
|
+
'max',
|
|
20
|
+
'round',
|
|
21
|
+
'clamp',
|
|
22
|
+
'if',
|
|
23
|
+
];
|
|
24
|
+
const CONDITION_OPS = ['lt', 'lte', 'gt', 'gte', 'eq', 'neq', 'and', 'or', 'not'];
|
|
25
|
+
const RESERVED_VARIABLE_NAMES = new Set(['constructor', 'prototype']);
|
|
26
|
+
const VARIABLE_NAME = /^[a-z][a-zA-Z0-9]{0,31}$/;
|
|
27
|
+
export function formulaJsonPath(path) {
|
|
28
|
+
let result = '$';
|
|
29
|
+
for (const segment of path) {
|
|
30
|
+
result += typeof segment === 'number' ? `[${segment}]` : `[${JSON.stringify(segment)}]`;
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
function diagnostic(code, path, message, hint) {
|
|
35
|
+
return { code, path, jsonPath: formulaJsonPath(path), message, hint };
|
|
36
|
+
}
|
|
37
|
+
function preflight(input) {
|
|
38
|
+
const seen = new WeakSet();
|
|
39
|
+
const stack = [
|
|
40
|
+
{ value: input, depth: 0, path: [] },
|
|
41
|
+
];
|
|
42
|
+
let nodes = 0;
|
|
43
|
+
let containers = 0;
|
|
44
|
+
const tooLarge = (path) => diagnostic('formula-too-large', path, `A formula has at most ${FORMULA_LIMITS.nodes} nodes`, 'Split the calculation into several outputs, or simplify it.');
|
|
45
|
+
while (stack.length > 0) {
|
|
46
|
+
const current = stack.pop();
|
|
47
|
+
if (!current)
|
|
48
|
+
continue;
|
|
49
|
+
const { value, path } = current;
|
|
50
|
+
if (value === null || typeof value !== 'object')
|
|
51
|
+
continue;
|
|
52
|
+
if (seen.has(value)) {
|
|
53
|
+
return diagnostic('cyclic-formula', path, 'A formula must not contain cycles', 'Use plain JSON.');
|
|
54
|
+
}
|
|
55
|
+
seen.add(value);
|
|
56
|
+
containers += 1;
|
|
57
|
+
if (containers > FORMULA_LIMITS.nodes * 2)
|
|
58
|
+
return tooLarge(path);
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
if (value.length > FORMULA_LIMITS.nodes)
|
|
61
|
+
return tooLarge(path);
|
|
62
|
+
for (let i = value.length - 1; i >= 0; i -= 1) {
|
|
63
|
+
stack.push({ value: value[i], depth: current.depth, path: [...path, i] });
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const prototype = Object.getPrototypeOf(value);
|
|
68
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
69
|
+
return diagnostic('invalid-container', path, 'Formula nodes must be plain JSON objects', 'Use plain JSON.');
|
|
70
|
+
}
|
|
71
|
+
nodes += 1;
|
|
72
|
+
if (nodes > FORMULA_LIMITS.nodes)
|
|
73
|
+
return tooLarge(path);
|
|
74
|
+
const depth = current.depth + 1;
|
|
75
|
+
if (depth > FORMULA_LIMITS.depth) {
|
|
76
|
+
return diagnostic('formula-too-deep', path, `A formula nests at most ${FORMULA_LIMITS.depth} levels`, 'Flatten the calculation, for example with min/max over several operands.');
|
|
77
|
+
}
|
|
78
|
+
const keys = Object.keys(value);
|
|
79
|
+
if (keys.length > 8) {
|
|
80
|
+
return diagnostic('invalid-formula', path, 'A formula node has too many keys', 'Use only the keys of its op.');
|
|
81
|
+
}
|
|
82
|
+
for (const key of keys.reverse()) {
|
|
83
|
+
stack.push({ value: value[key], depth, path: [...path, key] });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
function parseVarName(value, path) {
|
|
89
|
+
if (typeof value !== 'string' || !VARIABLE_NAME.test(value)) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
diagnostic: diagnostic('invalid-formula', path, 'Use a lowerCamelCase name of at most 32 characters', 'Rename the variable.'),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (RESERVED_VARIABLE_NAMES.has(value)) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
diagnostic: diagnostic('invalid-formula', path, '"constructor" and "prototype" are reserved; choose another name', 'Rename the variable.'),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return { ok: true, name: value };
|
|
102
|
+
}
|
|
103
|
+
function parseNode(input, path, kind) {
|
|
104
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
diagnostics: [
|
|
108
|
+
diagnostic('invalid-formula', path, 'Expected a formula node object', 'Use plain JSON.'),
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const record = input;
|
|
113
|
+
const op = record.op;
|
|
114
|
+
if (typeof op !== 'string') {
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
diagnostics: [diagnostic('invalid-formula', [...path, 'op'], 'op is required', 'Set op.')],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const isNumberOp = NUMBER_OPS.includes(op);
|
|
121
|
+
const isConditionOp = CONDITION_OPS.includes(op);
|
|
122
|
+
if (kind === 'number' && isConditionOp) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
diagnostics: [
|
|
126
|
+
diagnostic('type-mismatch', path, `Expected a number here, got the condition "${op}"`, 'Turn a condition into a number with { "op": "if", "test": …, "ifTrue": …, "ifFalse": … }.'),
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (kind === 'condition' && isNumberOp) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
diagnostics: [
|
|
134
|
+
diagnostic('type-mismatch', path, `Expected a condition here, got the number expression "${op}"`, 'Compare two numbers with lt, lte, gt, gte, eq or neq.'),
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (!isNumberOp && !isConditionOp) {
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
diagnostics: [
|
|
142
|
+
diagnostic('unknown-op', path, `Unknown formula op ${JSON.stringify(op)}`, `Use one of ${(kind === 'condition' ? CONDITION_OPS : NUMBER_OPS).join(', ')}.`),
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const child = (key, expected) => parseNode(record[key], [...path, key], expected);
|
|
147
|
+
switch (op) {
|
|
148
|
+
case 'num': {
|
|
149
|
+
if (typeof record.value !== 'number' || !Number.isFinite(record.value)) {
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
diagnostics: [
|
|
153
|
+
diagnostic('invalid-formula', [...path, 'value'], 'num.value must be a finite number', 'Use a finite literal.'),
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (Math.abs(record.value) > FORMULA_LIMITS.literal) {
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
diagnostics: [
|
|
161
|
+
diagnostic('invalid-formula', [...path, 'value'], `Literal magnitude is at most ${FORMULA_LIMITS.literal}`, 'Use a smaller number.'),
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return { ok: true, node: { op: 'num', value: record.value } };
|
|
166
|
+
}
|
|
167
|
+
case 'var': {
|
|
168
|
+
const name = parseVarName(record.name, [...path, 'name']);
|
|
169
|
+
if (!name.ok)
|
|
170
|
+
return { ok: false, diagnostics: [name.diagnostic] };
|
|
171
|
+
return { ok: true, node: { op: 'var', name: name.name } };
|
|
172
|
+
}
|
|
173
|
+
case 'add':
|
|
174
|
+
case 'sub':
|
|
175
|
+
case 'mul':
|
|
176
|
+
case 'div': {
|
|
177
|
+
const left = child('left', 'number');
|
|
178
|
+
if (!left.ok)
|
|
179
|
+
return left;
|
|
180
|
+
const right = child('right', 'number');
|
|
181
|
+
if (!right.ok)
|
|
182
|
+
return right;
|
|
183
|
+
return {
|
|
184
|
+
ok: true,
|
|
185
|
+
node: {
|
|
186
|
+
op,
|
|
187
|
+
left: left.node,
|
|
188
|
+
right: right.node,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
case 'pow': {
|
|
193
|
+
const base = child('base', 'number');
|
|
194
|
+
if (!base.ok)
|
|
195
|
+
return base;
|
|
196
|
+
const exponent = child('exponent', 'number');
|
|
197
|
+
if (!exponent.ok)
|
|
198
|
+
return exponent;
|
|
199
|
+
return {
|
|
200
|
+
ok: true,
|
|
201
|
+
node: {
|
|
202
|
+
op: 'pow',
|
|
203
|
+
base: base.node,
|
|
204
|
+
exponent: exponent.node,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
case 'min':
|
|
209
|
+
case 'max': {
|
|
210
|
+
if (!Array.isArray(record.args) ||
|
|
211
|
+
record.args.length < 2 ||
|
|
212
|
+
record.args.length > FORMULA_LIMITS.args) {
|
|
213
|
+
return {
|
|
214
|
+
ok: false,
|
|
215
|
+
diagnostics: [
|
|
216
|
+
diagnostic('invalid-formula', [...path, 'args'], `args must have 2–${FORMULA_LIMITS.args} number formulas`, 'Pass a short args array.'),
|
|
217
|
+
],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const args = [];
|
|
221
|
+
for (let i = 0; i < record.args.length; i += 1) {
|
|
222
|
+
const parsed = parseNode(record.args[i], [...path, 'args', i], 'number');
|
|
223
|
+
if (!parsed.ok)
|
|
224
|
+
return parsed;
|
|
225
|
+
args.push(parsed.node);
|
|
226
|
+
}
|
|
227
|
+
return { ok: true, node: { op, args } };
|
|
228
|
+
}
|
|
229
|
+
case 'round': {
|
|
230
|
+
const value = child('value', 'number');
|
|
231
|
+
if (!value.ok)
|
|
232
|
+
return value;
|
|
233
|
+
let digits;
|
|
234
|
+
if (record.digits !== undefined) {
|
|
235
|
+
if (typeof record.digits !== 'number' ||
|
|
236
|
+
!Number.isInteger(record.digits) ||
|
|
237
|
+
Math.abs(record.digits) > FORMULA_LIMITS.roundDigits) {
|
|
238
|
+
return {
|
|
239
|
+
ok: false,
|
|
240
|
+
diagnostics: [
|
|
241
|
+
diagnostic('invalid-formula', [...path, 'digits'], `digits must be an integer from −${FORMULA_LIMITS.roundDigits} to ${FORMULA_LIMITS.roundDigits}`, 'Adjust digits.'),
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
digits = record.digits;
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
ok: true,
|
|
249
|
+
node: digits === undefined
|
|
250
|
+
? { op: 'round', value: value.node }
|
|
251
|
+
: { op: 'round', value: value.node, digits },
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
case 'clamp': {
|
|
255
|
+
const value = child('value', 'number');
|
|
256
|
+
if (!value.ok)
|
|
257
|
+
return value;
|
|
258
|
+
const min = child('min', 'number');
|
|
259
|
+
if (!min.ok)
|
|
260
|
+
return min;
|
|
261
|
+
const max = child('max', 'number');
|
|
262
|
+
if (!max.ok)
|
|
263
|
+
return max;
|
|
264
|
+
return {
|
|
265
|
+
ok: true,
|
|
266
|
+
node: {
|
|
267
|
+
op: 'clamp',
|
|
268
|
+
value: value.node,
|
|
269
|
+
min: min.node,
|
|
270
|
+
max: max.node,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
case 'if': {
|
|
275
|
+
const test = child('test', 'condition');
|
|
276
|
+
if (!test.ok)
|
|
277
|
+
return test;
|
|
278
|
+
const ifTrue = child('ifTrue', 'number');
|
|
279
|
+
if (!ifTrue.ok)
|
|
280
|
+
return ifTrue;
|
|
281
|
+
const ifFalse = child('ifFalse', 'number');
|
|
282
|
+
if (!ifFalse.ok)
|
|
283
|
+
return ifFalse;
|
|
284
|
+
return {
|
|
285
|
+
ok: true,
|
|
286
|
+
node: {
|
|
287
|
+
op: 'if',
|
|
288
|
+
test: test.node,
|
|
289
|
+
ifTrue: ifTrue.node,
|
|
290
|
+
ifFalse: ifFalse.node,
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
case 'lt':
|
|
295
|
+
case 'lte':
|
|
296
|
+
case 'gt':
|
|
297
|
+
case 'gte':
|
|
298
|
+
case 'eq':
|
|
299
|
+
case 'neq': {
|
|
300
|
+
const left = child('left', 'number');
|
|
301
|
+
if (!left.ok)
|
|
302
|
+
return left;
|
|
303
|
+
const right = child('right', 'number');
|
|
304
|
+
if (!right.ok)
|
|
305
|
+
return right;
|
|
306
|
+
return {
|
|
307
|
+
ok: true,
|
|
308
|
+
node: {
|
|
309
|
+
op,
|
|
310
|
+
left: left.node,
|
|
311
|
+
right: right.node,
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
case 'and':
|
|
316
|
+
case 'or': {
|
|
317
|
+
if (!Array.isArray(record.args) ||
|
|
318
|
+
record.args.length < 2 ||
|
|
319
|
+
record.args.length > FORMULA_LIMITS.args) {
|
|
320
|
+
return {
|
|
321
|
+
ok: false,
|
|
322
|
+
diagnostics: [
|
|
323
|
+
diagnostic('invalid-formula', [...path, 'args'], `args must have 2–${FORMULA_LIMITS.args} conditions`, 'Pass a short args array.'),
|
|
324
|
+
],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const args = [];
|
|
328
|
+
for (let i = 0; i < record.args.length; i += 1) {
|
|
329
|
+
const parsed = parseNode(record.args[i], [...path, 'args', i], 'condition');
|
|
330
|
+
if (!parsed.ok)
|
|
331
|
+
return parsed;
|
|
332
|
+
args.push(parsed.node);
|
|
333
|
+
}
|
|
334
|
+
return { ok: true, node: { op, args } };
|
|
335
|
+
}
|
|
336
|
+
case 'not': {
|
|
337
|
+
const value = child('value', 'condition');
|
|
338
|
+
if (!value.ok)
|
|
339
|
+
return value;
|
|
340
|
+
return { ok: true, node: { op: 'not', value: value.node } };
|
|
341
|
+
}
|
|
342
|
+
default:
|
|
343
|
+
return {
|
|
344
|
+
ok: false,
|
|
345
|
+
diagnostics: [
|
|
346
|
+
diagnostic('unknown-op', path, `Unknown formula op ${JSON.stringify(op)}`, 'Use a known op.'),
|
|
347
|
+
],
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/** Variable names a formula reads, sorted and unique. */
|
|
352
|
+
export function formulaVariables(formula) {
|
|
353
|
+
const names = new Set();
|
|
354
|
+
const visit = (node) => {
|
|
355
|
+
switch (node.op) {
|
|
356
|
+
case 'num':
|
|
357
|
+
return;
|
|
358
|
+
case 'var':
|
|
359
|
+
names.add(node.name);
|
|
360
|
+
return;
|
|
361
|
+
case 'add':
|
|
362
|
+
case 'sub':
|
|
363
|
+
case 'mul':
|
|
364
|
+
case 'div':
|
|
365
|
+
case 'lt':
|
|
366
|
+
case 'lte':
|
|
367
|
+
case 'gt':
|
|
368
|
+
case 'gte':
|
|
369
|
+
case 'eq':
|
|
370
|
+
case 'neq':
|
|
371
|
+
visit(node.left);
|
|
372
|
+
visit(node.right);
|
|
373
|
+
return;
|
|
374
|
+
case 'pow':
|
|
375
|
+
visit(node.base);
|
|
376
|
+
visit(node.exponent);
|
|
377
|
+
return;
|
|
378
|
+
case 'min':
|
|
379
|
+
case 'max':
|
|
380
|
+
case 'and':
|
|
381
|
+
case 'or':
|
|
382
|
+
for (const arg of node.args)
|
|
383
|
+
visit(arg);
|
|
384
|
+
return;
|
|
385
|
+
case 'round':
|
|
386
|
+
case 'not':
|
|
387
|
+
visit(node.value);
|
|
388
|
+
return;
|
|
389
|
+
case 'clamp':
|
|
390
|
+
visit(node.value);
|
|
391
|
+
visit(node.min);
|
|
392
|
+
visit(node.max);
|
|
393
|
+
return;
|
|
394
|
+
case 'if':
|
|
395
|
+
visit(node.test);
|
|
396
|
+
visit(node.ifTrue);
|
|
397
|
+
visit(node.ifFalse);
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
visit(formula);
|
|
401
|
+
return [...names].sort();
|
|
402
|
+
}
|
|
403
|
+
function variablePaths(node, path, out) {
|
|
404
|
+
if (node === null || typeof node !== 'object')
|
|
405
|
+
return;
|
|
406
|
+
if (Array.isArray(node)) {
|
|
407
|
+
node.forEach((child, i) => {
|
|
408
|
+
variablePaths(child, [...path, i], out);
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const record = node;
|
|
413
|
+
if (record.op === 'var' && typeof record.name === 'string') {
|
|
414
|
+
out.push({ name: record.name, path: [...path, 'name'] });
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
for (const [key, child] of Object.entries(record))
|
|
418
|
+
variablePaths(child, [...path, key], out);
|
|
419
|
+
}
|
|
420
|
+
/** Validates untrusted formula JSON. Never throws and never executes anything. */
|
|
421
|
+
export function parseFormula(input, options = {}) {
|
|
422
|
+
const failed = (diagnostics) => ({
|
|
423
|
+
success: false,
|
|
424
|
+
formula: null,
|
|
425
|
+
variables: [],
|
|
426
|
+
diagnostics,
|
|
427
|
+
});
|
|
428
|
+
const size = preflight(input);
|
|
429
|
+
if (size)
|
|
430
|
+
return failed([size]);
|
|
431
|
+
const parsed = parseNode(input, [], 'number');
|
|
432
|
+
if (!parsed.ok)
|
|
433
|
+
return failed(parsed.diagnostics);
|
|
434
|
+
const formula = parsed.node;
|
|
435
|
+
if (options.variables) {
|
|
436
|
+
const known = new Set(options.variables);
|
|
437
|
+
const found = [];
|
|
438
|
+
variablePaths(formula, [], found);
|
|
439
|
+
const unknown = found.filter((ref) => !known.has(ref.name));
|
|
440
|
+
if (unknown.length > 0) {
|
|
441
|
+
return failed(unknown.map((ref) => diagnostic('unknown-variable', ref.path, `Unknown variable "${ref.name}"`, known.size > 0
|
|
442
|
+
? `Use one of the calculator inputs: ${[...known].sort().join(', ')}.`
|
|
443
|
+
: 'Add a calculator input with this name.')));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
success: true,
|
|
448
|
+
formula,
|
|
449
|
+
variables: formulaVariables(formula),
|
|
450
|
+
diagnostics: [],
|
|
451
|
+
};
|
|
452
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed formula grammar for calculator blocks. A formula is plain JSON:
|
|
3
|
+
* every node names its operation in `op`, and there is no string syntax, no
|
|
4
|
+
* function call by name and no way to reach host objects. Numbers and
|
|
5
|
+
* conditions are separate node kinds.
|
|
6
|
+
*/
|
|
7
|
+
export type ArithmeticOp = 'add' | 'sub' | 'mul' | 'div';
|
|
8
|
+
export type ComparisonOp = 'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'neq';
|
|
9
|
+
/** An expression that evaluates to a number, or `null` when undefined. */
|
|
10
|
+
export type NumberFormula = {
|
|
11
|
+
op: 'num';
|
|
12
|
+
value: number;
|
|
13
|
+
} | {
|
|
14
|
+
op: 'var';
|
|
15
|
+
name: string;
|
|
16
|
+
} | {
|
|
17
|
+
op: ArithmeticOp;
|
|
18
|
+
left: NumberFormula;
|
|
19
|
+
right: NumberFormula;
|
|
20
|
+
} | {
|
|
21
|
+
op: 'pow';
|
|
22
|
+
base: NumberFormula;
|
|
23
|
+
exponent: NumberFormula;
|
|
24
|
+
} | {
|
|
25
|
+
op: 'min' | 'max';
|
|
26
|
+
args: NumberFormula[];
|
|
27
|
+
} | {
|
|
28
|
+
op: 'round';
|
|
29
|
+
value: NumberFormula;
|
|
30
|
+
digits?: number | undefined;
|
|
31
|
+
} | {
|
|
32
|
+
op: 'clamp';
|
|
33
|
+
value: NumberFormula;
|
|
34
|
+
min: NumberFormula;
|
|
35
|
+
max: NumberFormula;
|
|
36
|
+
} | {
|
|
37
|
+
op: 'if';
|
|
38
|
+
test: Condition;
|
|
39
|
+
ifTrue: NumberFormula;
|
|
40
|
+
ifFalse: NumberFormula;
|
|
41
|
+
};
|
|
42
|
+
/** An expression that evaluates to true, false, or `null` when an operand is undefined. */
|
|
43
|
+
export type Condition = {
|
|
44
|
+
op: ComparisonOp;
|
|
45
|
+
left: NumberFormula;
|
|
46
|
+
right: NumberFormula;
|
|
47
|
+
} | {
|
|
48
|
+
op: 'and' | 'or';
|
|
49
|
+
args: Condition[];
|
|
50
|
+
} | {
|
|
51
|
+
op: 'not';
|
|
52
|
+
value: Condition;
|
|
53
|
+
};
|
|
54
|
+
/** Input values by variable name. Missing, `null` and non-finite inputs are undefined. */
|
|
55
|
+
export type FormulaInputs = Readonly<Record<string, number | null | undefined>>;
|
|
56
|
+
/** One problem with a formula, addressed like authoring diagnostics. */
|
|
57
|
+
export interface FormulaDiagnostic {
|
|
58
|
+
code: string;
|
|
59
|
+
path: (string | number)[];
|
|
60
|
+
jsonPath: string;
|
|
61
|
+
message: string;
|
|
62
|
+
hint: string;
|
|
63
|
+
}
|
|
64
|
+
export type FormulaParseResult = {
|
|
65
|
+
success: true;
|
|
66
|
+
formula: NumberFormula;
|
|
67
|
+
variables: string[];
|
|
68
|
+
diagnostics: [];
|
|
69
|
+
} | {
|
|
70
|
+
success: false;
|
|
71
|
+
formula: null;
|
|
72
|
+
variables: [];
|
|
73
|
+
diagnostics: FormulaDiagnostic[];
|
|
74
|
+
};
|
|
75
|
+
/** How a calculator output is shown. */
|
|
76
|
+
export type FormulaUnit = 'number' | 'compact' | 'cents' | 'percent';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed formula grammar for calculator blocks. A formula is plain JSON:
|
|
3
|
+
* every node names its operation in `op`, and there is no string syntax, no
|
|
4
|
+
* function call by name and no way to reach host objects. Numbers and
|
|
5
|
+
* conditions are separate node kinds.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// <reference path="../css-modules.d.ts" />
|
|
2
|
+
/**
|
|
3
|
+
* `@texuli/blocks` core — schemas, limits, untrusted-input guard, formula AST,
|
|
4
|
+
* text helpers and catalog metadata. Framework-free TypeScript (no Svelte).
|
|
5
|
+
*/
|
|
6
|
+
export { BLOCK_CATALOG, type BlockAuthoringDiagnostic, type BlockAuthoringValidation, type BlockCatalogEntry, getCatalogEntry, listCatalogTypes, parseBlocksForAuthoring, validateBlocksForAuthoring, } from './catalog/index.js';
|
|
7
|
+
export { type Condition, evaluateCondition, evaluateFormula, FORMULA_LIMITS, type FormatFormulaOptions, type FormulaDiagnostic, type FormulaInputs, type FormulaParseResult, type FormulaUnit, formatFormulaValue, formulaJsonPath, formulaVariables, type NumberFormula, type ParseFormulaOptions, parseFormula, roundTo, } from './formula/index.js';
|
|
8
|
+
export { BLOCK_LIMITS, type BlockLimits, } from './limits.js';
|
|
9
|
+
export { base64DecodedBytes, classifyMediaSource, hasUnsafeText, isUnsafeTextCharacter, MEDIA_DATA_URI_MAX_BYTES, type MediaKind, type MediaSourceKind, normalizeLineSeparators, } from './safety.js';
|
|
10
|
+
export { type AccordionBlock, BLOCK_TYPES, type Block, type BlockParseResult, type BlockType, type CalloutBlock, type CardsBlock, type ChartBlock, type ChartKind, type ChartSeries, type CodeBlock, type ColumnsBlock, type CtaBlock, type DataBlock, type DividerBlock, type FigureBlock, forEachBlock, type GalleryBlock, type GalleryItem, type GridBlock, type HeadingBlock, type ImageBlock, type ImageFit, type KpiBlock, type ListBlock, type ListItem, type QuoteBlock, type StatBlock, safeParseBlock, safeParseBlocks, type TableBlock, type TableCellValue, type TableColumn, type TableRow, type TabsBlock, type TextBlock, toJsonPath, type VideoBlock, } from './schema/blocks.js';
|
|
11
|
+
export { BLOCK_CONTENT_LIMITS, type BlockBase, type FieldIssue, type ParseResult, parseBlockBase, parseBlockId, parseMultilineText, parsePlainText, } from './schema/primitives.js';
|
|
12
|
+
export { BLOCK_FAMILIES, BLOCK_REVEALS, BLOCK_TONES, type BlockFamily, type BlockReveal, type BlockTone, isBlockFamily, isBlockReveal, isBlockTone, } from './schema/vocabulary.js';
|
|
13
|
+
export { blockText, documentText, headingOutline, slugifyHeading, stripLightMarkdown, uniqueSlug, type WalkedBlock, walkBlocks, } from './text.js';
|
|
14
|
+
export { parseUntrustedBlocks, type UntrustedBlockIssue, type UntrustedBlocksResult, } from './untrusted.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@texuli/blocks` core — schemas, limits, untrusted-input guard, formula AST,
|
|
3
|
+
* text helpers and catalog metadata. Framework-free TypeScript (no Svelte).
|
|
4
|
+
*/
|
|
5
|
+
export { BLOCK_CATALOG, getCatalogEntry, listCatalogTypes, parseBlocksForAuthoring, validateBlocksForAuthoring, } from "./catalog/index.js";
|
|
6
|
+
export { evaluateCondition, evaluateFormula, FORMULA_LIMITS, formatFormulaValue, formulaJsonPath, formulaVariables, parseFormula, roundTo, } from "./formula/index.js";
|
|
7
|
+
export { BLOCK_LIMITS, } from "./limits.js";
|
|
8
|
+
export { base64DecodedBytes, classifyMediaSource, hasUnsafeText, isUnsafeTextCharacter, MEDIA_DATA_URI_MAX_BYTES, normalizeLineSeparators, } from "./safety.js";
|
|
9
|
+
export { BLOCK_TYPES, forEachBlock, safeParseBlock, safeParseBlocks, toJsonPath, } from "./schema/blocks.js";
|
|
10
|
+
export { BLOCK_CONTENT_LIMITS, parseBlockBase, parseBlockId, parseMultilineText, parsePlainText, } from "./schema/primitives.js";
|
|
11
|
+
export { BLOCK_FAMILIES, BLOCK_REVEALS, BLOCK_TONES, isBlockFamily, isBlockReveal, isBlockTone, } from "./schema/vocabulary.js";
|
|
12
|
+
export { blockText, documentText, headingOutline, slugifyHeading, stripLightMarkdown, uniqueSlug, walkBlocks, } from "./text.js";
|
|
13
|
+
export { parseUntrustedBlocks, } from "./untrusted.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hard upper bounds for block documents accepted from files, links or paste.
|
|
3
|
+
* Individual schemas impose smaller field caps; these are the outer guards.
|
|
4
|
+
*/
|
|
5
|
+
export declare const BLOCK_LIMITS: Readonly<{
|
|
6
|
+
/** Serialized UTF-8 JSON bytes, including embedded media data URIs. */
|
|
7
|
+
maxBytes: number;
|
|
8
|
+
/** JSON object/array nesting in one block document. */
|
|
9
|
+
maxDepth: 12;
|
|
10
|
+
maxBlocks: 2000;
|
|
11
|
+
maxContainerDepth: 3;
|
|
12
|
+
maxDataUriBytes: number;
|
|
13
|
+
maxDocumentDataUriBytes: number;
|
|
14
|
+
/** General guard ceiling; field schemas are tighter. */
|
|
15
|
+
maxTextLength: 20000;
|
|
16
|
+
maxTableCells: number;
|
|
17
|
+
/** Diagram model caps (flow / tree / board / terminal). */
|
|
18
|
+
flowNodes: 12;
|
|
19
|
+
treeNodes: 64;
|
|
20
|
+
treeDepth: 6;
|
|
21
|
+
boardColumns: 6;
|
|
22
|
+
boardCardsPerColumn: 8;
|
|
23
|
+
terminalLines: 40;
|
|
24
|
+
}>;
|
|
25
|
+
export type BlockLimits = {
|
|
26
|
+
[Key in keyof typeof BLOCK_LIMITS]: number;
|
|
27
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { MEDIA_DATA_URI_MAX_BYTES } from "./safety.js";
|
|
2
|
+
import { BLOCK_CONTENT_LIMITS } from "./schema/primitives.js";
|
|
3
|
+
/**
|
|
4
|
+
* Hard upper bounds for block documents accepted from files, links or paste.
|
|
5
|
+
* Individual schemas impose smaller field caps; these are the outer guards.
|
|
6
|
+
*/
|
|
7
|
+
export const BLOCK_LIMITS = Object.freeze({
|
|
8
|
+
/** Serialized UTF-8 JSON bytes, including embedded media data URIs. */
|
|
9
|
+
maxBytes: 1024 * 1024,
|
|
10
|
+
/** JSON object/array nesting in one block document. */
|
|
11
|
+
maxDepth: 12,
|
|
12
|
+
maxBlocks: 2_000,
|
|
13
|
+
maxContainerDepth: BLOCK_CONTENT_LIMITS.containerDepth,
|
|
14
|
+
maxDataUriBytes: MEDIA_DATA_URI_MAX_BYTES,
|
|
15
|
+
maxDocumentDataUriBytes: 512 * 1024,
|
|
16
|
+
/** General guard ceiling; field schemas are tighter. */
|
|
17
|
+
maxTextLength: BLOCK_CONTENT_LIMITS.code,
|
|
18
|
+
maxTableCells: BLOCK_CONTENT_LIMITS.tableColumns * BLOCK_CONTENT_LIMITS.tableRows,
|
|
19
|
+
/** Diagram model caps (flow / tree / board / terminal). */
|
|
20
|
+
flowNodes: 12,
|
|
21
|
+
treeNodes: 64,
|
|
22
|
+
treeDepth: 6,
|
|
23
|
+
boardColumns: 6,
|
|
24
|
+
boardCardsPerColumn: 8,
|
|
25
|
+
terminalLines: 40,
|
|
26
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-light safety predicates shared by block schemas and the
|
|
3
|
+
* untrusted-input loader. No Svelte, no schema graph.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Turns U+2028/U+2029 into a line break (multiline) or a space so loaders keep
|
|
7
|
+
* the word boundary before the schema refuses them.
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizeLineSeparators(value: string, keepNewlines: boolean): string;
|
|
10
|
+
export declare function isUnsafeTextCharacter(character: string, allowNewlines?: boolean, allowTabs?: boolean): boolean;
|
|
11
|
+
/** True when any code point in `value` is unsafe under the given rules. */
|
|
12
|
+
export declare function hasUnsafeText(value: string, allowNewlines?: boolean, allowTabs?: boolean): boolean;
|
|
13
|
+
/** Decoded bytes allowed in one `data:` source. */
|
|
14
|
+
export declare const MEDIA_DATA_URI_MAX_BYTES: number;
|
|
15
|
+
export type MediaKind = 'image' | 'video';
|
|
16
|
+
export type MediaSourceKind = 'avatar' | 'data' | 'relative';
|
|
17
|
+
/** Decoded size of a base64 payload. */
|
|
18
|
+
export declare function base64DecodedBytes(payload: string): number;
|
|
19
|
+
/**
|
|
20
|
+
* Classifies a media source, or explains why it is refused. Hosts use the kind
|
|
21
|
+
* to apply level rules and per-document byte caps. No remote URLs.
|
|
22
|
+
*/
|
|
23
|
+
export declare function classifyMediaSource(src: string, media: MediaKind): {
|
|
24
|
+
ok: true;
|
|
25
|
+
kind: MediaSourceKind;
|
|
26
|
+
bytes: number;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
message: string;
|
|
30
|
+
};
|