@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,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structure-family block shapes: tabs, accordion, cards and grid.
|
|
3
|
+
* Columns, divider and cta live in core and join the family at render time.
|
|
4
|
+
* Nested child lists (tabs panels) are parsed through a core-provided
|
|
5
|
+
* `parseChildren` callback so this module stays free of a cycle with
|
|
6
|
+
* `safeParseBlock`.
|
|
7
|
+
*/
|
|
8
|
+
import { safeHref } from '@texuli/core/url-policy';
|
|
9
|
+
import { BLOCK_CONTENT_LIMITS, fail, ok, parseBlockId, parseMultilineText, parsePlainText, } from "../core/schema/primitives.js";
|
|
10
|
+
import { isBlockTone } from "../core/schema/vocabulary.js";
|
|
11
|
+
export const STRUCTURE_OWNED_TYPES = [
|
|
12
|
+
'tabs',
|
|
13
|
+
'accordion',
|
|
14
|
+
'cards',
|
|
15
|
+
'grid',
|
|
16
|
+
];
|
|
17
|
+
function plainObject(value, path) {
|
|
18
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
19
|
+
return fail(path, 'Expected a plain object');
|
|
20
|
+
}
|
|
21
|
+
const prototype = Object.getPrototypeOf(value);
|
|
22
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
23
|
+
return fail(path, 'Objects must be plain JSON data');
|
|
24
|
+
}
|
|
25
|
+
return ok(value);
|
|
26
|
+
}
|
|
27
|
+
function parseTone(value, path) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return ok(undefined);
|
|
30
|
+
if (!isBlockTone(value))
|
|
31
|
+
return fail(path, 'tone must be a known block tone');
|
|
32
|
+
return ok(value);
|
|
33
|
+
}
|
|
34
|
+
function parseColumnsPref(value, path) {
|
|
35
|
+
if (value === undefined)
|
|
36
|
+
return ok(undefined);
|
|
37
|
+
if (value !== 2 && value !== 3 && value !== 4) {
|
|
38
|
+
return fail(path, 'columns must be 2, 3 or 4');
|
|
39
|
+
}
|
|
40
|
+
return ok(value);
|
|
41
|
+
}
|
|
42
|
+
function parsePanelId(value, path) {
|
|
43
|
+
const id = parseBlockId(value, path);
|
|
44
|
+
if (!id.success)
|
|
45
|
+
return id;
|
|
46
|
+
if (id.data === undefined)
|
|
47
|
+
return fail(path, 'id is required');
|
|
48
|
+
return ok(id.data);
|
|
49
|
+
}
|
|
50
|
+
export function parseTabsBlock(record, path, base, containerDepth, parseChildren) {
|
|
51
|
+
if (containerDepth >= BLOCK_CONTENT_LIMITS.containerDepth) {
|
|
52
|
+
return fail(path, `containers nest at most ${BLOCK_CONTENT_LIMITS.containerDepth} deep`);
|
|
53
|
+
}
|
|
54
|
+
const id = parsePanelId(record.id, [...path, 'id']);
|
|
55
|
+
if (!id.success)
|
|
56
|
+
return id;
|
|
57
|
+
const label = parsePlainText(record.label, [...path, 'label'], 80);
|
|
58
|
+
if (!label.success)
|
|
59
|
+
return label;
|
|
60
|
+
if (!Array.isArray(record.panels) || record.panels.length < 2) {
|
|
61
|
+
return fail([...path, 'panels'], 'tabs need at least two panels');
|
|
62
|
+
}
|
|
63
|
+
if (record.panels.length > BLOCK_CONTENT_LIMITS.tabPanels) {
|
|
64
|
+
return fail([...path, 'panels'], `tabs may have at most ${BLOCK_CONTENT_LIMITS.tabPanels} panels`);
|
|
65
|
+
}
|
|
66
|
+
const panels = [];
|
|
67
|
+
const ids = new Set();
|
|
68
|
+
for (let i = 0; i < record.panels.length; i += 1) {
|
|
69
|
+
const panelPath = [...path, 'panels', i];
|
|
70
|
+
const panel = plainObject(record.panels[i], panelPath);
|
|
71
|
+
if (!panel.success)
|
|
72
|
+
return panel;
|
|
73
|
+
const panelId = parsePanelId(panel.data.id, [...panelPath, 'id']);
|
|
74
|
+
if (!panelId.success)
|
|
75
|
+
return panelId;
|
|
76
|
+
if (ids.has(panelId.data)) {
|
|
77
|
+
return fail([...panelPath, 'id'], `duplicate panel id "${panelId.data}"`);
|
|
78
|
+
}
|
|
79
|
+
ids.add(panelId.data);
|
|
80
|
+
const panelLabel = parsePlainText(panel.data.label, [...panelPath, 'label'], 40);
|
|
81
|
+
if (!panelLabel.success)
|
|
82
|
+
return panelLabel;
|
|
83
|
+
if (!Array.isArray(panel.data.blocks)) {
|
|
84
|
+
return fail([...panelPath, 'blocks'], 'panel blocks must be an array');
|
|
85
|
+
}
|
|
86
|
+
if (panel.data.blocks.length === 0) {
|
|
87
|
+
return fail([...panelPath, 'blocks'], 'panel needs at least one block');
|
|
88
|
+
}
|
|
89
|
+
if (panel.data.blocks.length > BLOCK_CONTENT_LIMITS.blocksPerColumn) {
|
|
90
|
+
return fail([...panelPath, 'blocks'], `at most ${BLOCK_CONTENT_LIMITS.blocksPerColumn} blocks per panel`);
|
|
91
|
+
}
|
|
92
|
+
const children = parseChildren(panel.data.blocks, [...panelPath, 'blocks'], containerDepth + 1);
|
|
93
|
+
if (!children.success)
|
|
94
|
+
return children;
|
|
95
|
+
panels.push({ id: panelId.data, label: panelLabel.data, blocks: children.data });
|
|
96
|
+
}
|
|
97
|
+
let defaultPanel;
|
|
98
|
+
if (record.defaultPanel !== undefined) {
|
|
99
|
+
if (typeof record.defaultPanel !== 'string' || !ids.has(record.defaultPanel)) {
|
|
100
|
+
return fail([...path, 'defaultPanel'], 'defaultPanel must name one of the panel ids');
|
|
101
|
+
}
|
|
102
|
+
defaultPanel = record.defaultPanel;
|
|
103
|
+
}
|
|
104
|
+
return ok({
|
|
105
|
+
...base,
|
|
106
|
+
type: 'tabs',
|
|
107
|
+
id: id.data,
|
|
108
|
+
label: label.data,
|
|
109
|
+
panels,
|
|
110
|
+
...(defaultPanel !== undefined ? { defaultPanel } : {}),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
export function parseAccordionBlock(record, path, base) {
|
|
114
|
+
let id;
|
|
115
|
+
if (record.id !== undefined) {
|
|
116
|
+
const parsed = parsePanelId(record.id, [...path, 'id']);
|
|
117
|
+
if (!parsed.success)
|
|
118
|
+
return parsed;
|
|
119
|
+
id = parsed.data;
|
|
120
|
+
}
|
|
121
|
+
let label;
|
|
122
|
+
if (record.label !== undefined) {
|
|
123
|
+
const parsed = parsePlainText(record.label, [...path, 'label'], 80);
|
|
124
|
+
if (!parsed.success)
|
|
125
|
+
return parsed;
|
|
126
|
+
label = parsed.data;
|
|
127
|
+
}
|
|
128
|
+
let mode;
|
|
129
|
+
if (record.mode !== undefined) {
|
|
130
|
+
if (record.mode !== 'single' && record.mode !== 'multiple') {
|
|
131
|
+
return fail([...path, 'mode'], 'mode must be single or multiple');
|
|
132
|
+
}
|
|
133
|
+
mode = record.mode;
|
|
134
|
+
}
|
|
135
|
+
if (!Array.isArray(record.items) || record.items.length < 1) {
|
|
136
|
+
return fail([...path, 'items'], 'accordion needs at least one item');
|
|
137
|
+
}
|
|
138
|
+
if (record.items.length > BLOCK_CONTENT_LIMITS.tabPanels) {
|
|
139
|
+
return fail([...path, 'items'], `accordion may have at most ${BLOCK_CONTENT_LIMITS.tabPanels} items`);
|
|
140
|
+
}
|
|
141
|
+
const items = [];
|
|
142
|
+
const ids = new Set();
|
|
143
|
+
for (let i = 0; i < record.items.length; i += 1) {
|
|
144
|
+
const itemPath = [...path, 'items', i];
|
|
145
|
+
const item = plainObject(record.items[i], itemPath);
|
|
146
|
+
if (!item.success)
|
|
147
|
+
return item;
|
|
148
|
+
const itemId = parsePanelId(item.data.id, [...itemPath, 'id']);
|
|
149
|
+
if (!itemId.success)
|
|
150
|
+
return itemId;
|
|
151
|
+
if (ids.has(itemId.data)) {
|
|
152
|
+
return fail([...itemPath, 'id'], `duplicate item id "${itemId.data}"`);
|
|
153
|
+
}
|
|
154
|
+
ids.add(itemId.data);
|
|
155
|
+
const title = parsePlainText(item.data.title, [...itemPath, 'title'], 80);
|
|
156
|
+
if (!title.success)
|
|
157
|
+
return title;
|
|
158
|
+
let body;
|
|
159
|
+
if (item.data.body !== undefined) {
|
|
160
|
+
const parsed = parseMultilineText(item.data.body, [...itemPath, 'body'], BLOCK_CONTENT_LIMITS.shortMarkdown);
|
|
161
|
+
if (!parsed.success)
|
|
162
|
+
return parsed;
|
|
163
|
+
body = parsed.data;
|
|
164
|
+
}
|
|
165
|
+
items.push({
|
|
166
|
+
id: itemId.data,
|
|
167
|
+
title: title.data,
|
|
168
|
+
...(body !== undefined ? { body } : {}),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return ok({
|
|
172
|
+
...base,
|
|
173
|
+
type: 'accordion',
|
|
174
|
+
items,
|
|
175
|
+
...(id !== undefined ? { id } : {}),
|
|
176
|
+
...(label !== undefined ? { label } : {}),
|
|
177
|
+
...(mode !== undefined ? { mode } : {}),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
export function parseCardsBlock(record, path, base) {
|
|
181
|
+
let title;
|
|
182
|
+
if (record.title !== undefined) {
|
|
183
|
+
const parsed = parsePlainText(record.title, [...path, 'title'], 120);
|
|
184
|
+
if (!parsed.success)
|
|
185
|
+
return parsed;
|
|
186
|
+
title = parsed.data;
|
|
187
|
+
}
|
|
188
|
+
const columns = parseColumnsPref(record.columns, [...path, 'columns']);
|
|
189
|
+
if (!columns.success)
|
|
190
|
+
return columns;
|
|
191
|
+
if (!Array.isArray(record.items) || record.items.length < 1) {
|
|
192
|
+
return fail([...path, 'items'], 'cards need at least one item');
|
|
193
|
+
}
|
|
194
|
+
if (record.items.length > BLOCK_CONTENT_LIMITS.cards) {
|
|
195
|
+
return fail([...path, 'items'], `cards may have at most ${BLOCK_CONTENT_LIMITS.cards} items`);
|
|
196
|
+
}
|
|
197
|
+
const items = [];
|
|
198
|
+
for (let i = 0; i < record.items.length; i += 1) {
|
|
199
|
+
const itemPath = [...path, 'items', i];
|
|
200
|
+
const item = plainObject(record.items[i], itemPath);
|
|
201
|
+
if (!item.success)
|
|
202
|
+
return item;
|
|
203
|
+
const cardTitle = parsePlainText(item.data.title, [...itemPath, 'title'], 80);
|
|
204
|
+
if (!cardTitle.success)
|
|
205
|
+
return cardTitle;
|
|
206
|
+
let eyebrow;
|
|
207
|
+
if (item.data.eyebrow !== undefined) {
|
|
208
|
+
const parsed = parsePlainText(item.data.eyebrow, [...itemPath, 'eyebrow'], 40);
|
|
209
|
+
if (!parsed.success)
|
|
210
|
+
return parsed;
|
|
211
|
+
eyebrow = parsed.data;
|
|
212
|
+
}
|
|
213
|
+
let body;
|
|
214
|
+
if (item.data.body !== undefined) {
|
|
215
|
+
const parsed = parseMultilineText(item.data.body, [...itemPath, 'body'], BLOCK_CONTENT_LIMITS.shortMarkdown);
|
|
216
|
+
if (!parsed.success)
|
|
217
|
+
return parsed;
|
|
218
|
+
body = parsed.data;
|
|
219
|
+
}
|
|
220
|
+
let href;
|
|
221
|
+
if (item.data.href !== undefined) {
|
|
222
|
+
if (typeof item.data.href !== 'string') {
|
|
223
|
+
return fail([...itemPath, 'href'], 'href must be a string');
|
|
224
|
+
}
|
|
225
|
+
if (item.data.href.length > BLOCK_CONTENT_LIMITS.href) {
|
|
226
|
+
return fail([...itemPath, 'href'], `href must be at most ${BLOCK_CONTENT_LIMITS.href} characters`);
|
|
227
|
+
}
|
|
228
|
+
const safe = safeHref(item.data.href, 'untrusted');
|
|
229
|
+
if (!safe) {
|
|
230
|
+
return fail([...itemPath, 'href'], 'href must be an https, mailto or same-document link (untrusted tier)');
|
|
231
|
+
}
|
|
232
|
+
href = safe;
|
|
233
|
+
}
|
|
234
|
+
const tone = parseTone(item.data.tone, [...itemPath, 'tone']);
|
|
235
|
+
if (!tone.success)
|
|
236
|
+
return tone;
|
|
237
|
+
items.push({
|
|
238
|
+
title: cardTitle.data,
|
|
239
|
+
...(eyebrow !== undefined ? { eyebrow } : {}),
|
|
240
|
+
...(body !== undefined ? { body } : {}),
|
|
241
|
+
...(href !== undefined ? { href } : {}),
|
|
242
|
+
...(tone.data !== undefined ? { tone: tone.data } : {}),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return ok({
|
|
246
|
+
...base,
|
|
247
|
+
type: 'cards',
|
|
248
|
+
items,
|
|
249
|
+
...(title !== undefined ? { title } : {}),
|
|
250
|
+
...(columns.data !== undefined ? { columns: columns.data } : {}),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
export function parseGridBlock(record, path, base) {
|
|
254
|
+
let title;
|
|
255
|
+
if (record.title !== undefined) {
|
|
256
|
+
const parsed = parsePlainText(record.title, [...path, 'title'], 120);
|
|
257
|
+
if (!parsed.success)
|
|
258
|
+
return parsed;
|
|
259
|
+
title = parsed.data;
|
|
260
|
+
}
|
|
261
|
+
const columns = parseColumnsPref(record.columns, [...path, 'columns']);
|
|
262
|
+
if (!columns.success)
|
|
263
|
+
return columns;
|
|
264
|
+
if (!Array.isArray(record.cells) || record.cells.length < 1) {
|
|
265
|
+
return fail([...path, 'cells'], 'grid needs at least one cell');
|
|
266
|
+
}
|
|
267
|
+
if (record.cells.length > BLOCK_CONTENT_LIMITS.cards) {
|
|
268
|
+
return fail([...path, 'cells'], `grid may have at most ${BLOCK_CONTENT_LIMITS.cards} cells`);
|
|
269
|
+
}
|
|
270
|
+
const cells = [];
|
|
271
|
+
for (let i = 0; i < record.cells.length; i += 1) {
|
|
272
|
+
const cellPath = [...path, 'cells', i];
|
|
273
|
+
const cell = plainObject(record.cells[i], cellPath);
|
|
274
|
+
if (!cell.success)
|
|
275
|
+
return cell;
|
|
276
|
+
let cellTitle;
|
|
277
|
+
if (cell.data.title !== undefined) {
|
|
278
|
+
const parsed = parsePlainText(cell.data.title, [...cellPath, 'title'], 80);
|
|
279
|
+
if (!parsed.success)
|
|
280
|
+
return parsed;
|
|
281
|
+
cellTitle = parsed.data;
|
|
282
|
+
}
|
|
283
|
+
let body;
|
|
284
|
+
if (cell.data.body !== undefined) {
|
|
285
|
+
const parsed = parseMultilineText(cell.data.body, [...cellPath, 'body'], BLOCK_CONTENT_LIMITS.shortMarkdown);
|
|
286
|
+
if (!parsed.success)
|
|
287
|
+
return parsed;
|
|
288
|
+
body = parsed.data;
|
|
289
|
+
}
|
|
290
|
+
if (cellTitle === undefined && body === undefined) {
|
|
291
|
+
return fail(cellPath, 'grid cell needs a title or body');
|
|
292
|
+
}
|
|
293
|
+
const tone = parseTone(cell.data.tone, [...cellPath, 'tone']);
|
|
294
|
+
if (!tone.success)
|
|
295
|
+
return tone;
|
|
296
|
+
cells.push({
|
|
297
|
+
...(cellTitle !== undefined ? { title: cellTitle } : {}),
|
|
298
|
+
...(body !== undefined ? { body } : {}),
|
|
299
|
+
...(tone.data !== undefined ? { tone: tone.data } : {}),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return ok({
|
|
303
|
+
...base,
|
|
304
|
+
type: 'grid',
|
|
305
|
+
cells,
|
|
306
|
+
...(title !== undefined ? { title } : {}),
|
|
307
|
+
...(columns.data !== undefined ? { columns: columns.data } : {}),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Dispatch for structure-owned types. Returns null when `type` is not owned
|
|
312
|
+
* here (core handles columns/divider/cta).
|
|
313
|
+
*/
|
|
314
|
+
export function parseStructureBlock(type, record, path, base, containerDepth, parseChildren) {
|
|
315
|
+
switch (type) {
|
|
316
|
+
case 'tabs':
|
|
317
|
+
return parseTabsBlock(record, path, base, containerDepth, parseChildren);
|
|
318
|
+
case 'accordion':
|
|
319
|
+
return parseAccordionBlock(record, path, base);
|
|
320
|
+
case 'cards':
|
|
321
|
+
return parseCardsBlock(record, path, base);
|
|
322
|
+
case 'grid':
|
|
323
|
+
return parseGridBlock(record, path, base);
|
|
324
|
+
default:
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
@layer tx.components {
|
|
2
|
+
.tx-structure-nested {
|
|
3
|
+
display: grid;
|
|
4
|
+
gap: 0.75rem;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.tx-structure-empty,
|
|
8
|
+
.tx-structure-loading,
|
|
9
|
+
.tx-block-invalid {
|
|
10
|
+
margin: 0;
|
|
11
|
+
color: var(--tx-muted-foreground, GrayText);
|
|
12
|
+
font-size: 0.875rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.tx-block-invalid {
|
|
16
|
+
padding: 0.5em 0.75em;
|
|
17
|
+
border: 1px dashed currentColor;
|
|
18
|
+
border-radius: 0.375em;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.tx-structure-columns {
|
|
22
|
+
display: grid;
|
|
23
|
+
gap: 1rem;
|
|
24
|
+
grid-template-columns: 1fr;
|
|
25
|
+
align-items: start;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.tx-structure-columns[data-cols="2"] {
|
|
29
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
30
|
+
}
|
|
31
|
+
.tx-structure-columns[data-cols="3"] {
|
|
32
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
33
|
+
}
|
|
34
|
+
.tx-structure-columns[data-cols="4"] {
|
|
35
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.tx-structure-column {
|
|
39
|
+
display: grid;
|
|
40
|
+
gap: 0.75rem;
|
|
41
|
+
min-inline-size: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.tx-structure-section-title {
|
|
45
|
+
margin: 0 0 0.75rem;
|
|
46
|
+
font-size: 1.125rem;
|
|
47
|
+
font-weight: 650;
|
|
48
|
+
line-height: 1.3;
|
|
49
|
+
text-wrap: balance;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.tx-structure-grid,
|
|
53
|
+
.tx-structure-cards {
|
|
54
|
+
display: grid;
|
|
55
|
+
gap: 1rem;
|
|
56
|
+
grid-template-columns: 1fr;
|
|
57
|
+
margin: 0;
|
|
58
|
+
padding: 0;
|
|
59
|
+
list-style: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.tx-structure-grid[data-cols="2"],
|
|
63
|
+
.tx-structure-cards[data-cols="2"] {
|
|
64
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
65
|
+
}
|
|
66
|
+
.tx-structure-grid[data-cols="3"],
|
|
67
|
+
.tx-structure-cards[data-cols="3"] {
|
|
68
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
69
|
+
}
|
|
70
|
+
.tx-structure-grid[data-cols="4"],
|
|
71
|
+
.tx-structure-cards[data-cols="4"] {
|
|
72
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.tx-structure-cell {
|
|
76
|
+
margin: 0;
|
|
77
|
+
padding: 0.875rem 1rem;
|
|
78
|
+
border: 1px solid var(--tx-color-border, CanvasText);
|
|
79
|
+
border-radius: 0.5rem;
|
|
80
|
+
background: var(--tx-color-surface, Canvas);
|
|
81
|
+
min-inline-size: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.tx-structure-cell-title {
|
|
85
|
+
margin: 0 0 0.375rem;
|
|
86
|
+
font-weight: 650;
|
|
87
|
+
line-height: 1.3;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.tx-structure-cell-body {
|
|
91
|
+
margin: 0;
|
|
92
|
+
line-height: 1.5;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.tx-structure-card-eyebrow {
|
|
96
|
+
margin: 0 0 0.25rem;
|
|
97
|
+
font-size: 0.75rem;
|
|
98
|
+
font-weight: 600;
|
|
99
|
+
letter-spacing: 0.04em;
|
|
100
|
+
text-transform: uppercase;
|
|
101
|
+
color: var(--tx-muted-foreground, GrayText);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.tx-structure-card-title {
|
|
105
|
+
margin: 0;
|
|
106
|
+
font-weight: 650;
|
|
107
|
+
line-height: 1.3;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.tx-structure-card-title a {
|
|
111
|
+
color: inherit;
|
|
112
|
+
text-decoration: underline;
|
|
113
|
+
text-underline-offset: 0.15em;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.tx-structure-card-title a:focus-visible {
|
|
117
|
+
outline: 2px solid var(--tx-color-ring, Highlight);
|
|
118
|
+
outline-offset: 2px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.tx-structure-divider {
|
|
122
|
+
margin-block: 1rem;
|
|
123
|
+
margin-inline: 0;
|
|
124
|
+
border: 0;
|
|
125
|
+
border-block-start: 1px solid var(--tx-color-border, CanvasText);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.tx-structure-cta {
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-wrap: wrap;
|
|
131
|
+
gap: 0.75rem;
|
|
132
|
+
align-items: center;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.tx-structure-cta a {
|
|
136
|
+
display: inline-flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
min-block-size: 2.25rem;
|
|
140
|
+
padding-inline: 0.875rem;
|
|
141
|
+
border-radius: 0.375rem;
|
|
142
|
+
border: 1px solid var(--tx-color-border, CanvasText);
|
|
143
|
+
background: var(--tx-color-surface, Canvas);
|
|
144
|
+
color: var(--tx-color-foreground, CanvasText);
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
text-decoration: none;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.tx-structure-cta a:focus-visible {
|
|
150
|
+
outline: 2px solid var(--tx-color-ring, Highlight);
|
|
151
|
+
outline-offset: 2px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.tx-structure-tabs,
|
|
155
|
+
.tx-structure-accordion {
|
|
156
|
+
min-inline-size: 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.tx-structure-panel {
|
|
160
|
+
padding-block-start: 0.75rem;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.tx-structure-body {
|
|
164
|
+
line-height: 1.5;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@media (max-width: 39.99rem) {
|
|
168
|
+
.tx-structure-columns[data-cols],
|
|
169
|
+
.tx-structure-grid[data-cols],
|
|
170
|
+
.tx-structure-cards[data-cols] {
|
|
171
|
+
grid-template-columns: 1fr;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@media (forced-colors: active) {
|
|
176
|
+
.tx-structure-cell,
|
|
177
|
+
.tx-structure-cta a,
|
|
178
|
+
.tx-structure-divider {
|
|
179
|
+
border-color: CanvasText;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { safeHref } from '@texuli/core';
|
|
3
|
+
import { DEFAULT_TEXT_BLOCK_LABELS, type TextBlockLabels } from './labels.js';
|
|
4
|
+
import { headingLevels, type MdBlock, type MdInline, parseMarkdown } from './markdown.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders block Markdown through the owned closed subset (`markdown.ts`).
|
|
8
|
+
* Text interpolation only: no HTML sink, and every href passes `safeHref`
|
|
9
|
+
* again here. Markdown headings are relative: the top level in the source
|
|
10
|
+
* maps to `headingBase` (h3 by default) and deeper levels follow without
|
|
11
|
+
* gaps, so author text cannot outrank heading blocks or skip levels.
|
|
12
|
+
*/
|
|
13
|
+
let {
|
|
14
|
+
source,
|
|
15
|
+
compact = false,
|
|
16
|
+
headingBase = 3,
|
|
17
|
+
labels = DEFAULT_TEXT_BLOCK_LABELS,
|
|
18
|
+
}: {
|
|
19
|
+
source: string;
|
|
20
|
+
/** Unwraps a lone paragraph so list items and captions stay inline. */
|
|
21
|
+
compact?: boolean;
|
|
22
|
+
/** Heading level the source's top Markdown heading renders as (1–6). */
|
|
23
|
+
headingBase?: number;
|
|
24
|
+
labels?: TextBlockLabels;
|
|
25
|
+
} = $props();
|
|
26
|
+
|
|
27
|
+
const tree = $derived(parseMarkdown(source));
|
|
28
|
+
const lone = $derived(
|
|
29
|
+
compact && tree.blocks.length === 1 && tree.blocks[0]?.type === 'paragraph'
|
|
30
|
+
? tree.blocks[0].children
|
|
31
|
+
: null
|
|
32
|
+
);
|
|
33
|
+
const levels = $derived(headingLevels(tree.blocks));
|
|
34
|
+
const headingTag = (level: number) => {
|
|
35
|
+
const base = Math.min(6, Math.max(1, Math.trunc(headingBase) || 3));
|
|
36
|
+
return `h${Math.min(6, base + levels.indexOf(level))}`;
|
|
37
|
+
};
|
|
38
|
+
const absolute = (href: string) => /^(?:https?:|mailto:)/i.test(href);
|
|
39
|
+
const emphasisTag = (type: 'strong' | 'em' | 'del') => type;
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
{#snippet inline(nodes: MdInline[])}
|
|
43
|
+
{#each nodes as node, i (i)}
|
|
44
|
+
{#if node.type === 'text'}{node.text}{:else if node.type === 'break'}<br />{:else if node.type === 'code'}<code class="tx-md-code">{node.text}</code>{:else if node.type === 'link'}{@const href = safeHref(node.href)}{#if href}<a
|
|
45
|
+
{href}
|
|
46
|
+
class="tx-md-link"
|
|
47
|
+
rel={absolute(href) ? 'noopener noreferrer nofollow' : undefined}>{@render inline(node.children)}</a
|
|
48
|
+
>{:else}{@render inline(node.children)}{/if}{:else}<svelte:element this={emphasisTag(node.type)}>{@render inline(node.children)}</svelte:element>{/if}
|
|
49
|
+
{/each}
|
|
50
|
+
{/snippet}
|
|
51
|
+
|
|
52
|
+
{#snippet blocks(items: MdBlock[])}
|
|
53
|
+
{#each items as item, i (i)}
|
|
54
|
+
{#if item.type === 'paragraph'}
|
|
55
|
+
<p>{@render inline(item.children)}</p>
|
|
56
|
+
{:else if item.type === 'heading'}
|
|
57
|
+
<svelte:element this={headingTag(item.level)} class="tx-md-heading">{@render inline(item.children)}</svelte:element>
|
|
58
|
+
{:else if item.type === 'code'}
|
|
59
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -- a scrollable fence needs keyboard access. -->
|
|
60
|
+
<pre class="tx-md-pre" tabindex="0" aria-label={labels.codeBlock} data-lang={item.lang}><code>{item.text}</code></pre>
|
|
61
|
+
{:else if item.type === 'list'}
|
|
62
|
+
<svelte:element
|
|
63
|
+
this={item.ordered ? 'ol' : 'ul'}
|
|
64
|
+
class="tx-md-list"
|
|
65
|
+
start={item.ordered && item.start !== 1 ? item.start : undefined}
|
|
66
|
+
>
|
|
67
|
+
{#each item.items as entry, j (j)}
|
|
68
|
+
<li class:tx-md-task={entry.checked !== null}>
|
|
69
|
+
{#if entry.checked !== null}<input
|
|
70
|
+
type="checkbox"
|
|
71
|
+
class="tx-md-check"
|
|
72
|
+
checked={entry.checked}
|
|
73
|
+
disabled
|
|
74
|
+
aria-label={entry.checked ? labels.taskDone : labels.taskOpen}
|
|
75
|
+
/>{/if}
|
|
76
|
+
{#if entry.children.length === 1 && entry.children[0]?.type === 'paragraph'}{@render inline(
|
|
77
|
+
entry.children[0].children
|
|
78
|
+
)}{:else}{@render blocks(entry.children)}{/if}
|
|
79
|
+
</li>
|
|
80
|
+
{/each}
|
|
81
|
+
</svelte:element>
|
|
82
|
+
{:else if item.type === 'quote'}
|
|
83
|
+
<blockquote class="tx-md-quote">{@render blocks(item.children)}</blockquote>
|
|
84
|
+
{:else}
|
|
85
|
+
<hr class="tx-md-rule" />
|
|
86
|
+
{/if}
|
|
87
|
+
{/each}
|
|
88
|
+
{/snippet}
|
|
89
|
+
|
|
90
|
+
{#if lone}{@render inline(lone)}{:else}<div class="tx-md">{@render blocks(tree.blocks)}</div>{/if}
|
|
91
|
+
|
|
92
|
+
<style>
|
|
93
|
+
.tx-md {
|
|
94
|
+
overflow-wrap: anywhere;
|
|
95
|
+
}
|
|
96
|
+
.tx-md > :global(* + *),
|
|
97
|
+
.tx-md :global(li > * + *),
|
|
98
|
+
.tx-md :global(blockquote > * + *) {
|
|
99
|
+
margin-block-start: 0.75em;
|
|
100
|
+
}
|
|
101
|
+
.tx-md :global(p),
|
|
102
|
+
.tx-md :global(.tx-md-heading),
|
|
103
|
+
.tx-md :global(.tx-md-list),
|
|
104
|
+
.tx-md :global(.tx-md-quote),
|
|
105
|
+
.tx-md :global(.tx-md-pre) {
|
|
106
|
+
margin-block-end: 0;
|
|
107
|
+
}
|
|
108
|
+
.tx-md > :global(:first-child) {
|
|
109
|
+
margin-block-start: 0;
|
|
110
|
+
}
|
|
111
|
+
.tx-md-heading {
|
|
112
|
+
font-weight: 600;
|
|
113
|
+
line-height: 1.3;
|
|
114
|
+
}
|
|
115
|
+
.tx-md-list {
|
|
116
|
+
padding-inline-start: 1.5em;
|
|
117
|
+
}
|
|
118
|
+
.tx-md-task {
|
|
119
|
+
list-style: none;
|
|
120
|
+
margin-inline-start: -1.5em;
|
|
121
|
+
}
|
|
122
|
+
.tx-md-check {
|
|
123
|
+
margin-inline-end: 0.5em;
|
|
124
|
+
vertical-align: middle;
|
|
125
|
+
}
|
|
126
|
+
.tx-md-quote {
|
|
127
|
+
margin-inline: 0;
|
|
128
|
+
padding-inline-start: 1em;
|
|
129
|
+
border-inline-start: 3px solid var(--tx-color-border-strong, currentColor);
|
|
130
|
+
}
|
|
131
|
+
.tx-md-code,
|
|
132
|
+
.tx-md-pre {
|
|
133
|
+
font-family: var(--tx-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
134
|
+
font-size: 0.9em;
|
|
135
|
+
}
|
|
136
|
+
.tx-md-code {
|
|
137
|
+
padding-inline: 0.25em;
|
|
138
|
+
border-radius: 0.25em;
|
|
139
|
+
background: var(--tx-color-surface-muted, color-mix(in srgb, currentColor 10%, transparent));
|
|
140
|
+
}
|
|
141
|
+
.tx-md-pre {
|
|
142
|
+
overflow-x: auto;
|
|
143
|
+
padding: 0.75em 1em;
|
|
144
|
+
border: 1px solid var(--tx-color-border, color-mix(in srgb, currentColor 30%, transparent));
|
|
145
|
+
border-radius: 0.375em;
|
|
146
|
+
white-space: pre;
|
|
147
|
+
}
|
|
148
|
+
.tx-md-pre:focus-visible,
|
|
149
|
+
.tx-md-link:focus-visible {
|
|
150
|
+
outline: 2px solid var(--tx-color-focus, Highlight);
|
|
151
|
+
outline-offset: 2px;
|
|
152
|
+
}
|
|
153
|
+
/* Link color stays the host's (or the UA's); the underline marks links in every scheme. */
|
|
154
|
+
.tx-md-link {
|
|
155
|
+
text-decoration: underline;
|
|
156
|
+
text-underline-offset: 0.2em;
|
|
157
|
+
}
|
|
158
|
+
.tx-md-rule {
|
|
159
|
+
border: 0;
|
|
160
|
+
border-block-start: 1px solid var(--tx-color-border, color-mix(in srgb, currentColor 40%, transparent));
|
|
161
|
+
}
|
|
162
|
+
@media (forced-colors: active) {
|
|
163
|
+
.tx-md-quote,
|
|
164
|
+
.tx-md-pre,
|
|
165
|
+
.tx-md-rule {
|
|
166
|
+
border-color: CanvasText;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference path="../css-modules.d.ts" />
|
|
2
|
+
import { type TextBlockLabels } from './labels.js';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
source: string;
|
|
5
|
+
/** Unwraps a lone paragraph so list items and captions stay inline. */
|
|
6
|
+
compact?: boolean;
|
|
7
|
+
/** Heading level the source's top Markdown heading renders as (1–6). */
|
|
8
|
+
headingBase?: number;
|
|
9
|
+
labels?: TextBlockLabels;
|
|
10
|
+
};
|
|
11
|
+
declare const RichText: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type RichText = ReturnType<typeof RichText>;
|
|
13
|
+
export default RichText;
|