@podlite/schema 0.0.67 → 0.0.69
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/CHANGELOG.podlite +32 -0
- package/esm/ast-helpers.d.ts +25 -1
- package/esm/ast-helpers.js +101 -2
- package/esm/ast-helpers.js.map +1 -1
- package/esm/attribute-names.d.ts +5 -0
- package/esm/attribute-names.js +36 -0
- package/esm/attribute-names.js.map +1 -0
- package/esm/exportAny.js +2 -2
- package/esm/exportAny.js.map +1 -1
- package/esm/exportHtml.js +50 -17
- package/esm/exportHtml.js.map +1 -1
- package/esm/exportMarkdown.js +28 -16
- package/esm/exportMarkdown.js.map +1 -1
- package/esm/folded-sections.d.ts +1 -0
- package/esm/folded-sections.js +80 -0
- package/esm/folded-sections.js.map +1 -0
- package/esm/grammarfc.js +6099 -15834
- package/esm/grammarfc.js.map +1 -1
- package/esm/helpers/corePlugins.js +9 -3
- package/esm/helpers/corePlugins.js.map +1 -1
- package/esm/helpers/html-attr.d.ts +1 -0
- package/esm/helpers/html-attr.js +6 -0
- package/esm/helpers/html-attr.js.map +1 -0
- package/esm/index.d.ts +4 -1
- package/esm/index.js +4 -1
- package/esm/index.js.map +1 -1
- package/esm/pluggableParser.d.ts +1 -1
- package/esm/pluggableParser.js +4 -1
- package/esm/pluggableParser.js.map +1 -1
- package/esm/types.d.ts +27 -16
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- package/lib/ast-helpers.d.ts +25 -1
- package/lib/ast-helpers.js +106 -3
- package/lib/attribute-names.d.ts +5 -0
- package/lib/attribute-names.js +41 -0
- package/lib/exportAny.js +1 -1
- package/lib/exportHtml.js +52 -19
- package/lib/exportMarkdown.js +27 -15
- package/lib/folded-sections.d.ts +1 -0
- package/lib/folded-sections.js +84 -0
- package/lib/grammarfc.js +6099 -15834
- package/lib/helpers/corePlugins.js +9 -3
- package/lib/helpers/html-attr.d.ts +1 -0
- package/lib/helpers/html-attr.js +10 -0
- package/lib/index.d.ts +4 -1
- package/lib/index.js +7 -1
- package/lib/pluggableParser.d.ts +1 -1
- package/lib/pluggableParser.js +5 -1
- package/lib/types.d.ts +27 -16
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/schema/AstTree.json +588 -436
- package/schema/PodliteDocument.json +588 -436
package/lib/exportHtml.js
CHANGED
|
@@ -47,24 +47,45 @@ const plugin_clean_location_1 = __importDefault(require("./plugin-clean-location
|
|
|
47
47
|
const ast_helpers_1 = require("./ast-helpers");
|
|
48
48
|
const link_config_1 = require("./helpers/link-config");
|
|
49
49
|
const entities_1 = require("entities");
|
|
50
|
-
const
|
|
50
|
+
const html_attr_1 = require("./helpers/html-attr");
|
|
51
|
+
// HTML gives an anchor without href to a link whose target the author never
|
|
52
|
+
// wrote; an empty href would claim the current document instead. The address is
|
|
53
|
+
// quoted like every other attribute value: a quote inside it would otherwise
|
|
54
|
+
// close the attribute and let the document write markup of its own.
|
|
55
|
+
const hrefAttr = (node, ctx) => {
|
|
56
|
+
const target = (0, ast_helpers_1.linkTarget)(node);
|
|
57
|
+
if (target === undefined)
|
|
58
|
+
return '';
|
|
59
|
+
const address = (0, ast_helpers_1.sameDocTarget)(target, ctx);
|
|
60
|
+
// A link level one refused points at nothing, and an anchor with no href is what
|
|
61
|
+
// html gives that case. An address shaped from a target that was never there only
|
|
62
|
+
// looks like a working link.
|
|
63
|
+
return address === undefined ? '' : ` href="${(0, html_attr_1.quoteAttribute)(String(address))}"`;
|
|
64
|
+
};
|
|
51
65
|
const linkConfigAttrs = config => {
|
|
52
66
|
const { newContext, title, lang, download } = (0, link_config_1.readLinkConfig)(config);
|
|
53
67
|
const attrs = [];
|
|
54
68
|
if (newContext)
|
|
55
69
|
attrs.push(' target="_blank"');
|
|
56
70
|
if (title !== undefined)
|
|
57
|
-
attrs.push(` title="${
|
|
71
|
+
attrs.push(` title="${(0, html_attr_1.quoteAttribute)(title)}"`);
|
|
58
72
|
if (lang !== undefined)
|
|
59
|
-
attrs.push(` hreflang="${
|
|
73
|
+
attrs.push(` hreflang="${(0, html_attr_1.quoteAttribute)(lang)}"`);
|
|
60
74
|
if (download === true)
|
|
61
75
|
attrs.push(' download');
|
|
62
76
|
else if (typeof download === 'string')
|
|
63
|
-
attrs.push(` download="${
|
|
77
|
+
attrs.push(` download="${(0, html_attr_1.quoteAttribute)(download)}"`);
|
|
64
78
|
return attrs.join('');
|
|
65
79
|
};
|
|
80
|
+
// Shaped by the same rule the address is: an anchor keeping a dot the address drops
|
|
81
|
+
// is how :id<v1.2> came to render an unreachable target. Every place that writes an
|
|
82
|
+
// anchor goes through here, or the two sides part again.
|
|
83
|
+
const anchorOf = (node, ctx) => {
|
|
84
|
+
const written = (0, ast_helpers_1.getExplicitNodeId)(node, ctx);
|
|
85
|
+
return written === null ? null : (ctx?.__anchors?.shape || ast_helpers_1.toFragment)(written);
|
|
86
|
+
};
|
|
66
87
|
const openTag = (tag, node, ctx, attrs = '') => {
|
|
67
|
-
const id = (
|
|
88
|
+
const id = anchorOf(node, ctx);
|
|
68
89
|
return `<${tag}${id ? ` id="${id}"` : ''}${attrs}>`;
|
|
69
90
|
};
|
|
70
91
|
const rules = {
|
|
@@ -147,18 +168,12 @@ const rules = {
|
|
|
147
168
|
'H<>': (0, handlers_1.wrapContent)('<sup>', '</sup>'),
|
|
148
169
|
'J<>': (0, handlers_1.wrapContent)('<sub>', '</sub>'),
|
|
149
170
|
'L<>': (0, handlers_1.setFn)((node, ctx) => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
meta = node.content;
|
|
153
|
-
}
|
|
154
|
-
return (0, handlers_1.wrapContent)(`<a href="${(0, ast_helpers_1.sameDocTarget)(meta, ctx)}"${linkConfigAttrs((0, config_1.codeConfigWithDefaults)(node, ctx))}>`, `</a>`);
|
|
171
|
+
const attrs = linkConfigAttrs((0, config_1.codeConfigWithDefaults)(node, ctx));
|
|
172
|
+
return (0, handlers_1.wrapContent)(`<a${hrefAttr(node, ctx)}${attrs}>`, `</a>`);
|
|
155
173
|
}),
|
|
156
174
|
'W<>': (0, handlers_1.setFn)((node, ctx) => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
meta = node.content;
|
|
160
|
-
}
|
|
161
|
-
return (0, handlers_1.wrapContent)(`<a href="${(0, ast_helpers_1.sameDocTarget)(meta, ctx)}"${linkConfigAttrs((0, config_1.codeConfigWithDefaults)(node, ctx))} class="backlink">`, `</a>`);
|
|
175
|
+
const attrs = linkConfigAttrs((0, config_1.codeConfigWithDefaults)(node, ctx));
|
|
176
|
+
return (0, handlers_1.wrapContent)(`<a${hrefAttr(node, ctx)}${attrs} class="backlink">`, `</a>`);
|
|
162
177
|
}),
|
|
163
178
|
/**
|
|
164
179
|
* CSS rules for footnotes
|
|
@@ -244,6 +259,19 @@ const rules = {
|
|
|
244
259
|
pod: handlers_1.content,
|
|
245
260
|
':code': (0, handlers_1.wrapContent)('<pre><code>', '</code></pre>'),
|
|
246
261
|
code: (0, handlers_1.handleNested)((0, handlers_1.setFn)((node, ctx) => (0, handlers_1.wrapContent)(`${openTag('pre', node, ctx)}<code>`, '</code></pre>'))),
|
|
262
|
+
// a folded section is a heading plus the nodes under it; the disclosure is
|
|
263
|
+
// native so the reader needs no script to open it
|
|
264
|
+
_folded_section: (writer, processor) => (node, ctx, interator) => {
|
|
265
|
+
const [heading, ...rest] = (node.content || []);
|
|
266
|
+
const open = node.foldedState === false || node.foldedState === 0 || node.foldedState === '0';
|
|
267
|
+
writer.writeRaw(`<details class="folded-section"${open ? ' open' : ''}>`);
|
|
268
|
+
writer.writeRaw('<summary class="folded-section-summary">');
|
|
269
|
+
if (heading)
|
|
270
|
+
interator([heading], ctx);
|
|
271
|
+
writer.writeRaw('</summary><div class="folded-section-content">');
|
|
272
|
+
interator(rest, ctx);
|
|
273
|
+
writer.writeRaw('</div></details>');
|
|
274
|
+
},
|
|
247
275
|
data: handlers_1.emptyContent,
|
|
248
276
|
':verbatim': (writer, processor) => (node, ctx, interator) => {
|
|
249
277
|
if (node.error) {
|
|
@@ -277,7 +305,7 @@ const rules = {
|
|
|
277
305
|
// block =para
|
|
278
306
|
// With an :id the block owns the <p> so the anchor lands on it; without one
|
|
279
307
|
// the inner paragraph renders as before.
|
|
280
|
-
para: (0, handlers_1.handleNested)((0, handlers_1.setFn)((node, ctx) => (
|
|
308
|
+
para: (0, handlers_1.handleNested)((0, handlers_1.setFn)((node, ctx) => anchorOf(node, ctx)
|
|
281
309
|
? (0, handlers_1.subUse)({ ':para': handlers_1.content }, (0, handlers_1.wrapContent)(openTag('p', node, ctx), '</p>'))
|
|
282
310
|
: handlers_1.content)),
|
|
283
311
|
':para': (0, handlers_1.setFn)((node, ctx) => (0, handlers_1.wrapContent)(openTag('p', node, ctx), '</p>')),
|
|
@@ -316,7 +344,7 @@ const rules = {
|
|
|
316
344
|
'comment:block': handlers_1.emptyContent,
|
|
317
345
|
'boundary:block': (writer, processor) => (node, ctx) => {
|
|
318
346
|
const conf = (0, config_1.default)(node, ctx);
|
|
319
|
-
const id = (
|
|
347
|
+
const id = anchorOf(node, ctx);
|
|
320
348
|
const idAttr = id ? ` id="${id}"` : '';
|
|
321
349
|
if (conf.exists('caption')) {
|
|
322
350
|
writer.writeRaw(`<hr${idAttr} title="`);
|
|
@@ -329,7 +357,7 @@ const rules = {
|
|
|
329
357
|
},
|
|
330
358
|
// The term opens the pair, so an :id on the definition lands on its <dt>.
|
|
331
359
|
defn: (0, handlers_1.setFn)((node, ctx) => {
|
|
332
|
-
const id = (
|
|
360
|
+
const id = anchorOf(node, ctx);
|
|
333
361
|
return id
|
|
334
362
|
? (0, handlers_1.subUse)({ 'term:para': (0, handlers_1.wrapContent)(`<dt id="${id}">`, '</dt><dd>') }, (0, handlers_1.wrapContent)('', '</dd>'))
|
|
335
363
|
: (0, handlers_1.wrapContent)('', '</dd>');
|
|
@@ -421,7 +449,12 @@ const rules = {
|
|
|
421
449
|
':toc-list': (0, handlers_1.setFn)((node, ctx) => (0, handlers_1.wrapContent)(`<ul class="toc-list listlevel${node.level}">`, '</ul>')),
|
|
422
450
|
':toc-item': (0, handlers_1.setFn)((node, ctx) => (0, handlers_1.wrapContent)('<li class="toc-item">', '</li>')),
|
|
423
451
|
':image': (writer, processor) => (node, ctx, interator) => {
|
|
424
|
-
|
|
452
|
+
const src = (0, html_attr_1.quoteAttribute)(String((0, image_base_1.applyImageBase)(node.src, ctx?.base) ?? ''));
|
|
453
|
+
// html reads a missing alt as "this image is part of the content" and an empty
|
|
454
|
+
// one as "decorative", so an alternative text the author never wrote is left out
|
|
455
|
+
const altText = (0, ast_helpers_1.writtenValue)(node.alt);
|
|
456
|
+
const alt = altText === undefined ? '' : ` alt="${(0, html_attr_1.quoteAttribute)(altText)}"`;
|
|
457
|
+
writer.writeRaw(`<img src="${src}"${alt}/>`);
|
|
425
458
|
},
|
|
426
459
|
};
|
|
427
460
|
const toHtml = opt => (0, exportAny_1.default)({
|
package/lib/exportMarkdown.js
CHANGED
|
@@ -52,6 +52,26 @@ const linkTitle = config => {
|
|
|
52
52
|
const { title } = (0, link_config_1.readLinkConfig)(config);
|
|
53
53
|
return title === undefined ? '' : ` "${title.replace(/"/g, '\\"')}"`;
|
|
54
54
|
};
|
|
55
|
+
// Markdown has no form for a link whose target the author never wrote — an empty
|
|
56
|
+
// address there claims the current document — so the text is written on its own.
|
|
57
|
+
// A bracket or a space inside the address would end it where markdown looks for
|
|
58
|
+
// the closing one, so such an address is written in angle brackets, the form
|
|
59
|
+
// markdown keeps for exactly that. An angle of its own is percent-encoded rather
|
|
60
|
+
// than dropped, so the address survives the round trip — no source text reaches
|
|
61
|
+
// here holding one today, since the parser ends the code on it.
|
|
62
|
+
const markdownAddress = (address) => /[()\s]/.test(address) ? `<${address.replace(/</g, '%3C').replace(/>/g, '%3E')}>` : address;
|
|
63
|
+
const linkWrap = (node, ctx) => {
|
|
64
|
+
const target = (0, ast_helpers_1.linkTarget)(node);
|
|
65
|
+
if (target === undefined)
|
|
66
|
+
return (0, handlers_1.wrapContent)('', '');
|
|
67
|
+
const resolved = (0, ast_helpers_1.sameDocTarget)(target, ctx, markdownAnchors(ctx));
|
|
68
|
+
// Markdown has no anchor without an address either, so a refused link keeps its
|
|
69
|
+
// text and loses the brackets.
|
|
70
|
+
if (resolved === undefined)
|
|
71
|
+
return (0, handlers_1.wrapContent)('', '');
|
|
72
|
+
const address = String(resolved);
|
|
73
|
+
return (0, handlers_1.wrapContent)(`[`, `](${markdownAddress(address)}${linkTitle((0, config_1.codeConfigWithDefaults)(node, ctx))})`);
|
|
74
|
+
};
|
|
55
75
|
// A cell's own text is written whole. Trimming each fragment on its own eats the
|
|
56
76
|
// spaces that sit between them — between a word and a markup code, or around a
|
|
57
77
|
// sign the parser split the text on — so only the cell edges are stripped.
|
|
@@ -122,20 +142,8 @@ const rules = {
|
|
|
122
142
|
},
|
|
123
143
|
'H<>': (0, handlers_1.wrapContent)('<sup>', '</sup>'),
|
|
124
144
|
'J<>': (0, handlers_1.wrapContent)('<sub>', '</sub>'),
|
|
125
|
-
'L<>': (0, handlers_1.setFn)((node, ctx) =>
|
|
126
|
-
|
|
127
|
-
if (meta === null) {
|
|
128
|
-
meta = node.content;
|
|
129
|
-
}
|
|
130
|
-
return (0, handlers_1.wrapContent)(`[`, `](${(0, ast_helpers_1.sameDocTarget)(meta, ctx, markdownAnchors(ctx))}${linkTitle((0, config_1.codeConfigWithDefaults)(node, ctx))})`);
|
|
131
|
-
}),
|
|
132
|
-
'W<>': (0, handlers_1.setFn)((node, ctx) => {
|
|
133
|
-
let { meta } = node;
|
|
134
|
-
if (meta === null) {
|
|
135
|
-
meta = node.content;
|
|
136
|
-
}
|
|
137
|
-
return (0, handlers_1.wrapContent)(`[`, `](${(0, ast_helpers_1.sameDocTarget)(meta, ctx, markdownAnchors(ctx))}${linkTitle((0, config_1.codeConfigWithDefaults)(node, ctx))})`);
|
|
138
|
-
}),
|
|
145
|
+
'L<>': (0, handlers_1.setFn)((node, ctx) => linkWrap(node, ctx)),
|
|
146
|
+
'W<>': (0, handlers_1.setFn)((node, ctx) => linkWrap(node, ctx)),
|
|
139
147
|
'N<>': (writer, processor) => {
|
|
140
148
|
writer.addListener('end', () => {
|
|
141
149
|
if (!writer.hasOwnProperty('FOOTNOTES')) {
|
|
@@ -457,7 +465,11 @@ const rules = {
|
|
|
457
465
|
':toc-list': handlers_1.emptyContent,
|
|
458
466
|
':toc-item': handlers_1.emptyContent,
|
|
459
467
|
':image': (writer, processor) => (node, ctx, interator) => {
|
|
460
|
-
|
|
468
|
+
// the alternative text sits between square brackets, where a `]` of its own
|
|
469
|
+
// would end it, and the address between round ones — the same shape a link has
|
|
470
|
+
const alt = ((0, ast_helpers_1.writtenValue)(node.alt) ?? '').replace(/([[\]\\])/g, '\\$1');
|
|
471
|
+
const src = markdownAddress(String((0, image_base_1.applyImageBase)(node.src, ctx?.base) ?? ''));
|
|
472
|
+
writer.writeRaw(``);
|
|
461
473
|
},
|
|
462
474
|
};
|
|
463
475
|
const toMarkdown = opt => (0, exportAny_1.default)({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const applyFoldedSections: (node: any) => any;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyFoldedSections = void 0;
|
|
4
|
+
// Folding of a section belongs to the tree, not to one renderer: the command
|
|
5
|
+
// line and every build-time producer read the same document as the editor does.
|
|
6
|
+
// Moved here from @podlite/to-jsx, where it lived and left exportHtml and
|
|
7
|
+
// exportMarkdown unable to fold anything.
|
|
8
|
+
// `:folded` on a heading folds the whole section — the heading plus every
|
|
9
|
+
// following node up to the next same-or-higher-level heading. Detect the
|
|
10
|
+
// attribute on =head nodes in a container's content array and wrap that
|
|
11
|
+
// range in a synthetic `_folded_section` block so the JSX renderer can emit
|
|
12
|
+
// <details> around it.
|
|
13
|
+
const getFoldedAttr = (node) => {
|
|
14
|
+
const config = node && node.config;
|
|
15
|
+
if (!Array.isArray(config))
|
|
16
|
+
return null;
|
|
17
|
+
const entry = config.find((c) => c && c.name === 'folded');
|
|
18
|
+
return entry ? entry.value : null;
|
|
19
|
+
};
|
|
20
|
+
const isHeadBlock = (node) => node && node.type === 'block' && node.name === 'head' && node.level !== undefined && node.level !== null;
|
|
21
|
+
const headLevel = (node) => Number(node.level);
|
|
22
|
+
const groupFoldedSections = (content) => {
|
|
23
|
+
if (!Array.isArray(content))
|
|
24
|
+
return content;
|
|
25
|
+
const result = [];
|
|
26
|
+
let i = 0;
|
|
27
|
+
while (i < content.length) {
|
|
28
|
+
const node = content[i];
|
|
29
|
+
if (isHeadBlock(node)) {
|
|
30
|
+
const folded = getFoldedAttr(node);
|
|
31
|
+
if (folded !== null) {
|
|
32
|
+
const level = headLevel(node);
|
|
33
|
+
const sectionNodes = [node];
|
|
34
|
+
let j = i + 1;
|
|
35
|
+
while (j < content.length) {
|
|
36
|
+
const next = content[j];
|
|
37
|
+
if (isHeadBlock(next) && headLevel(next) <= level)
|
|
38
|
+
break;
|
|
39
|
+
sectionNodes.push(next);
|
|
40
|
+
j++;
|
|
41
|
+
}
|
|
42
|
+
result.push({
|
|
43
|
+
type: 'block',
|
|
44
|
+
name: '_folded_section',
|
|
45
|
+
content: sectionNodes,
|
|
46
|
+
foldedState: folded,
|
|
47
|
+
location: node.location,
|
|
48
|
+
});
|
|
49
|
+
i = j;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
result.push(node);
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
// Walk the AST and apply `groupFoldedSections` to every block's `content`
|
|
59
|
+
// array, so :folded heads inside named blocks, defn, nested etc. fold the
|
|
60
|
+
// same way they do at the pod-level. Inside an already-built
|
|
61
|
+
// `_folded_section`, the first element is the heading the wrapper belongs
|
|
62
|
+
// to — skip it when grouping the rest, otherwise the same head would be
|
|
63
|
+
// re-wrapped on every recursion. Nested folds (a folded heading inside a
|
|
64
|
+
// folded section's body) are still picked up by grouping the remainder.
|
|
65
|
+
const applyFoldedSections = (node) => {
|
|
66
|
+
if (!node || typeof node !== 'object')
|
|
67
|
+
return node;
|
|
68
|
+
if (Array.isArray(node))
|
|
69
|
+
return node.map(exports.applyFoldedSections);
|
|
70
|
+
if (!Array.isArray(node.content))
|
|
71
|
+
return node;
|
|
72
|
+
if (node.name === '_folded_section') {
|
|
73
|
+
const [head, ...rest] = node.content;
|
|
74
|
+
const grouped = groupFoldedSections(rest);
|
|
75
|
+
return {
|
|
76
|
+
...node,
|
|
77
|
+
content: [(0, exports.applyFoldedSections)(head), ...grouped.map(exports.applyFoldedSections)],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const grouped = groupFoldedSections(node.content);
|
|
81
|
+
return { ...node, content: grouped.map(exports.applyFoldedSections) };
|
|
82
|
+
};
|
|
83
|
+
exports.applyFoldedSections = applyFoldedSections;
|
|
84
|
+
//# sourceMappingURL=folded-sections.js.map
|