@longform/longform 0.0.18 → 0.0.20
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/dist/longform.cjs +426 -365
- package/dist/longform.cjs.map +1 -1
- package/dist/longform.d.ts +2 -2
- package/dist/longform.js +426 -365
- package/dist/longform.js.br +0 -0
- package/dist/longform.js.gz +0 -0
- package/dist/longform.js.map +1 -1
- package/dist/longform.min.js +1 -735
- package/dist/longform.min.js.br +0 -0
- package/dist/longform.min.js.gz +0 -0
- package/dist/longform.min.js.map +1 -1
- package/dist/mod.d.ts +2 -2
- package/dist/types.d.ts +31 -24
- package/lib/longform.ts +528 -447
- package/lib/mod.ts +17 -2
- package/lib/types.ts +33 -29
- package/package.json +4 -2
package/dist/longform.cjs
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const LINE = 0, INDENT = 1, DIRECTIVE_KEY = 2, DIRECTIVE = 3, DIRECTIVE_INLINE_ARGS = 4, ID_TYPE = 5, ID = 6, FRAGMENT_TYPE = 7, ELEMENT = 8, ATTR = 9
|
|
4
|
-
const sniffTestRe = /^((?: )*)(?:(@)([a-z][a-z\-]*(?::[a-z][a-z\-]*)?)(?:(?::: (.*))| *)?|(##?)([a-z][a-z\-]*)(?: ?(?: +([\["]))? *|(?: *))?|(?:[a-z][a-z\-]*(?::[a-z][a-z\-])?.*(::).*)|(?:(\[)[a-z][a-z\-]?.*(?:=.+)?\]\w*)|(.+))$/gmi
|
|
3
|
+
const LINE = 0, INDENT = 1, DIRECTIVE_KEY = 2, DIRECTIVE = 3, DIRECTIVE_INLINE_ARGS = 4, ID_TYPE = 5, ID = 6, FRAGMENT_TYPE = 7, ELEMENT = 8, ATTR = 9, COMMENT = 10, sniffTestRe = /^((?: )*)(?:(@)([a-z][a-z\-]*(?::[a-z][a-z\-]*)?)(?:(?:(?::: (.*)))|(?:::)? *)?|(##?)([a-z][a-z\-]*)(?: ?(?: +([\["]))? *|(?: *))?|(?:[a-z][a-z\-]*(?::[a-z][a-z\-])?.*(::).*)|(?:(\[)[a-z][a-z\-]?.*(?:=.+)?\]\w*)|(--).*|(.+))$/gmi
|
|
5
4
|
// captures a single element definition which could be in a chain.
|
|
6
5
|
// id, class and attributes are matched as a single block for a later loop
|
|
7
6
|
// if in chained situation the regexp will need to be looped over for each element
|
|
8
7
|
// we know the element is the last if the final capture group has a positive match
|
|
9
8
|
// or if the line is consumed by the regexp.
|
|
10
|
-
,
|
|
9
|
+
, outerRe = /([a-z][\w\-]*(?::[a-z][\w\-]*)?)((?:(?:[^:])|(?::(?!:)))*)::(?: (?:({{?)|(.*)))?/gi
|
|
11
10
|
// captures each id, class and attribute declaration in an element
|
|
12
|
-
,
|
|
13
|
-
let m1, m2, m3, m4;
|
|
11
|
+
, innerRe = /(?:\.([a-z][\w\-]+)|#([a-z][\w\-]+)|\[([a-z][a-z\-]+(?::[a-z][a-z|\-]*)?)(?:=(?:"([^"]+)"|'([^']+)'|([^\]]+)))?\])/gi, attributeRe = /((?:\ \ )+)\[(\w[\w-]*(?::\w[\w-]*)?)(?:=([^\n]+))?\]/, attributeDirectiveRe = /^@([a-z][\w\-]*(?::[a-z][\w\-]*)?)$/i, idRe = /((?:\ \ )+)?#(#)?([\w\-]+)(?: ([\["]))?/i, refRe = /#\[([\w\-]+)\]/g, escapeRe = /([&<>"'#\[\]{}])/g, templateLinesRe = /^(\ \ )?([^\n]+)$/gm, templateRe = /#(#)?{([a-z][\w\-]*(?::[a-z][\w\-]*)?)}|#\[([a-z][\w\-]*(?::[a-z][\w\-]*)?)\]/g, preformatClose = new Set(['}', '}}']), voids = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wrb']);
|
|
12
|
+
let m1 = null, m2 = null, m3 = null, m4 = null;
|
|
14
13
|
const entities = {
|
|
15
14
|
'&': '&',
|
|
16
15
|
'<': '<',
|
|
@@ -46,6 +45,7 @@ function makeElement(indent = 0) {
|
|
|
46
45
|
}
|
|
47
46
|
function makeFragment(type = 'bare') {
|
|
48
47
|
return {
|
|
48
|
+
id: undefined,
|
|
49
49
|
type,
|
|
50
50
|
html: '',
|
|
51
51
|
template: false,
|
|
@@ -101,10 +101,10 @@ const directiveValidator = /^[a-z][a-z\-]*\:[a-z][a-z\-]*$/i;
|
|
|
101
101
|
* @param input - The Longform document to parse.
|
|
102
102
|
* @param args - Arguments for the Longform parser.
|
|
103
103
|
*/
|
|
104
|
-
function longform(input, args) {
|
|
105
|
-
let
|
|
104
|
+
async function longform(input, args) {
|
|
105
|
+
let skipping = false, verbatimText = '', verbatimIndent = 0, verbatimType = 0, element = makeElement(), fragment = makeFragment(), directive
|
|
106
106
|
// the root fragment
|
|
107
|
-
, root = null, id = args?.id, lang = args?.lang, dir = args?.dir, meta = {}, doc;
|
|
107
|
+
, root = null, id = args?.id, lang = args?.lang, dir = args?.dir, meta = {}, data = {}, doc, asyncCount = 0;
|
|
108
108
|
// ids of claimed fragments
|
|
109
109
|
const claimed = new Set()
|
|
110
110
|
// parsed fragments
|
|
@@ -112,7 +112,7 @@ function longform(input, args) {
|
|
|
112
112
|
id: { attr: ctx => ctx.doc.id },
|
|
113
113
|
dir: { attr: ctx => ctx.doc.dir },
|
|
114
114
|
lang: { attr: ctx => ctx.doc.lang },
|
|
115
|
-
};
|
|
115
|
+
}, promises = [];
|
|
116
116
|
let key = args?.key;
|
|
117
117
|
if (!args?.predictable && key == null) {
|
|
118
118
|
const arr = new Uint8Array(10);
|
|
@@ -126,245 +126,209 @@ function longform(input, args) {
|
|
|
126
126
|
output.templates = {};
|
|
127
127
|
if (args?.directives != null) {
|
|
128
128
|
const entries = Object.entries(args.directives);
|
|
129
|
-
for (let i = 0, l = entries.length; i < l; i++) {
|
|
130
|
-
if (!directiveValidator.test(
|
|
131
|
-
console.warn(`Invalid custom directive name '${
|
|
129
|
+
for (let i = 0, l = entries.length, e = entries[i]; i < l; i++) {
|
|
130
|
+
if (!directiveValidator.test(e[0])) {
|
|
131
|
+
console.warn(`Invalid custom directive name '$e{[0]}'`);
|
|
132
132
|
continue;
|
|
133
133
|
}
|
|
134
|
-
directives[
|
|
134
|
+
directives[e[0]] = e[1];
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
id: element.id,
|
|
145
|
-
tag: element.tag,
|
|
146
|
-
class: element.class,
|
|
147
|
-
attrs: element.attrs,
|
|
148
|
-
};
|
|
149
|
-
const chain = [];
|
|
150
|
-
for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
|
|
151
|
-
chain.push({
|
|
152
|
-
id: el.id,
|
|
153
|
-
tag: el.tag,
|
|
154
|
-
class: el.class,
|
|
155
|
-
attrs: el.attrs,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
for (let i = 0, l = element.beforeRender.length, def = element.beforeRender[i]; i < l; i++) {
|
|
159
|
-
def.beforeRender({
|
|
160
|
-
el,
|
|
161
|
-
chain,
|
|
162
|
-
doc: doc,
|
|
163
|
-
inlineArg: def.inlineArg,
|
|
164
|
-
blockArg: def.blockArg,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
137
|
+
// This is a hack to allow open verbatim blocks to detect
|
|
138
|
+
// when they close via an extra step in the loop.
|
|
139
|
+
input += '\n ';
|
|
140
|
+
sniffTestRe.lastIndex = 0;
|
|
141
|
+
main: while ((m1 = sniffTestRe.exec(input))) {
|
|
142
|
+
if (m1[COMMENT] === '--') {
|
|
143
|
+
continue;
|
|
167
144
|
}
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
145
|
+
else if (fragment.template) {
|
|
146
|
+
fragment.html += m1[0];
|
|
147
|
+
}
|
|
148
|
+
const indent = m1[INDENT].length / 2;
|
|
149
|
+
// only one root fragment is allowed. Skip until beginning of next fragment / directive.
|
|
150
|
+
if (skipping && indent !== 0)
|
|
151
|
+
continue;
|
|
152
|
+
// I hear avoiding branching is faster than avoiding assignments...
|
|
153
|
+
skipping = false;
|
|
154
|
+
// verbatim blocks collect the string as is as a continuous block
|
|
155
|
+
// and do processing on it once the full block has been collected.
|
|
156
|
+
if (verbatimType !== 0) {
|
|
157
|
+
if (indent >= verbatimIndent && (
|
|
158
|
+
// text verbatim type should exit when other Longform constructs begin.
|
|
159
|
+
verbatimType !== 1 ||
|
|
160
|
+
(m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) === undefined)) {
|
|
161
|
+
if (verbatimType === 1) {
|
|
162
|
+
// Text verbatim blocks join with blank spaces
|
|
163
|
+
verbatimText += ' ' + m1[0].trim();
|
|
184
164
|
}
|
|
185
165
|
else {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (fragment.type === 'root') {
|
|
191
|
-
fragment.html += ` data-${key}-root`;
|
|
192
|
-
}
|
|
193
|
-
else if (fragment.type === 'bare' || fragment.type === 'range') {
|
|
194
|
-
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
195
|
-
}
|
|
196
|
-
else if (fragment.type === 'embed' && !args?.predictable) {
|
|
197
|
-
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
166
|
+
if (verbatimText !== '')
|
|
167
|
+
verbatimText += '\n';
|
|
168
|
+
// other verbatim blocks normalize the Longform indent of the block away
|
|
169
|
+
verbatimText += m1[0].replace(' '.repeat(verbatimIndent), '');
|
|
198
170
|
}
|
|
171
|
+
continue;
|
|
199
172
|
}
|
|
200
|
-
if (
|
|
201
|
-
|
|
173
|
+
else if (m1[0].trim() === '' &&
|
|
174
|
+
input.length !== m1.index + m1[0].length) {
|
|
175
|
+
// blank line in verbatim are ignored by text types
|
|
176
|
+
if (verbatimType !== 1)
|
|
177
|
+
verbatimText += '\n';
|
|
178
|
+
continue;
|
|
202
179
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
180
|
+
else {
|
|
181
|
+
// verbatim block is finished
|
|
182
|
+
switch (verbatimType) {
|
|
183
|
+
case 1:
|
|
184
|
+
// text
|
|
185
|
+
fragment.html += verbatimText;
|
|
186
|
+
// locate reference points in text
|
|
187
|
+
while ((m2 = refRe.exec(verbatimText))) {
|
|
188
|
+
const start = fragment.html.length + m2.index - verbatimText.length;
|
|
189
|
+
fragment.refs.push({
|
|
190
|
+
id: m2[1],
|
|
191
|
+
start,
|
|
192
|
+
end: start + m2[0].length,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
196
|
+
break;
|
|
197
|
+
case 2:
|
|
198
|
+
// escaped preformatted text
|
|
199
|
+
fragment.html += escape(verbatimText);
|
|
200
|
+
break;
|
|
201
|
+
case 3:
|
|
202
|
+
// preformatted
|
|
203
|
+
fragment.html += verbatimText + '\n';
|
|
204
|
+
break;
|
|
205
|
+
case 4:
|
|
206
|
+
// directive block args
|
|
207
|
+
if (directive.def == null)
|
|
208
|
+
break;
|
|
209
|
+
if (doc == null) {
|
|
210
|
+
if (typeof directive.def.meta === 'function') {
|
|
211
|
+
meta[m1[DIRECTIVE]] = directive.def.meta({
|
|
212
|
+
inlineArgs: directive.inlineArgs,
|
|
213
|
+
blockArgs: verbatimText,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (typeof directive.def.render === 'function') {
|
|
218
|
+
try {
|
|
219
|
+
element.html += directive.def.render({
|
|
220
|
+
doc,
|
|
221
|
+
inlineArgs: directive.inlineArgs,
|
|
222
|
+
blockArgs: verbatimText,
|
|
223
|
+
}) + ' ';
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
console.error(`Error in calling directive ${directive.name}`);
|
|
227
|
+
console.error(err);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else if (typeof directive.def.asyncRender === 'function') {
|
|
231
|
+
asyncCount++;
|
|
232
|
+
// async rendering uses the #[ref] feature to insert the
|
|
233
|
+
// eventual response.
|
|
234
|
+
const directiveFragment = makeFragment('embed');
|
|
235
|
+
directiveFragment.id = `@${asyncCount}`;
|
|
236
|
+
parsed.set(directiveFragment.id, directiveFragment);
|
|
237
|
+
fragment.refs.push({
|
|
238
|
+
id: directiveFragment.id,
|
|
239
|
+
start: fragment.html.length,
|
|
240
|
+
end: fragment.html.length,
|
|
241
|
+
});
|
|
242
|
+
// in case text follows we want a space separating the render output
|
|
243
|
+
fragment.html += ' ';
|
|
244
|
+
promises.push(directive.def.asyncRender({
|
|
245
|
+
doc,
|
|
246
|
+
inlineArgs: directive.inlineArgs,
|
|
247
|
+
blockArgs: verbatimText,
|
|
248
|
+
}).then(res => {
|
|
249
|
+
directiveFragment.html = res ?? '';
|
|
250
|
+
}).catch(err => {
|
|
251
|
+
console.error(`Error in calling directive ${directive.name}`);
|
|
252
|
+
console.error(err);
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
else if (typeof directive.def.element === 'function') {
|
|
256
|
+
if (element.beforeRender == null)
|
|
257
|
+
element.beforeRender = [];
|
|
258
|
+
element.beforeRender.push({
|
|
259
|
+
blockArgs: verbatimText,
|
|
260
|
+
inlineArgs: directive.inlineArgs,
|
|
261
|
+
element: directive.def.element,
|
|
262
|
+
});
|
|
218
263
|
}
|
|
219
264
|
else {
|
|
220
|
-
|
|
265
|
+
console.warn(`Directive used in incorrect context ${directive.name}`);
|
|
221
266
|
}
|
|
222
|
-
}
|
|
223
|
-
fragment.html += '>';
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
if (!voids.has(element.tag) && element.text != null) {
|
|
227
|
-
fragment.html += element.text;
|
|
228
|
-
}
|
|
229
|
-
if (!voids.has(element.tag)) {
|
|
230
|
-
fragment.els.push(element);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (targetIndent <= element.indent) {
|
|
234
|
-
element = makeElement(targetIndent);
|
|
235
|
-
while (fragment.els.length !== 0 && (targetIndent == null ||
|
|
236
|
-
fragment.els[fragment.els.length - 1].indent !== targetIndent - 1)) {
|
|
237
|
-
const element = fragment.els.pop();
|
|
238
|
-
if (Array.isArray(element.chain)) {
|
|
239
|
-
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
240
|
-
fragment.html += `</${element.chain[i].tag}>`;
|
|
241
|
-
}
|
|
242
267
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (fragment.template) {
|
|
247
|
-
output.templates[fragment.id] = fragment.html;
|
|
248
|
-
}
|
|
249
|
-
else if (fragment.type === 'root') {
|
|
250
|
-
root = fragment;
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
parsed.set(fragment.id, fragment);
|
|
254
|
-
}
|
|
255
|
-
fragment = makeFragment();
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
element = makeElement(targetIndent);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
main: while ((m1 = sniffTestRe.exec(input))) {
|
|
263
|
-
if (m1[1] === '--') {
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
else if (fragment.template) {
|
|
267
|
-
fragment.html += m1[0];
|
|
268
|
-
}
|
|
269
|
-
// If this is a script tag or preformatted block
|
|
270
|
-
// we want to retain the intended formatting less
|
|
271
|
-
// the indent. Pre-formatting can apply to any element
|
|
272
|
-
// by ending the declaration with `:: {`.
|
|
273
|
-
if (verbatimIndent != null) {
|
|
274
|
-
// inside a script or preformatted block
|
|
275
|
-
identRe.lastIndex = 0;
|
|
276
|
-
m2 = identRe.exec(m1[0]);
|
|
277
|
-
const indent = m2 == null
|
|
278
|
-
? null
|
|
279
|
-
: m2[0].length / 2;
|
|
280
|
-
if (m2 == null || indent <= verbatimIndent) {
|
|
281
|
-
fragment.html += '\n';
|
|
282
|
-
applyIndent(indent);
|
|
283
|
-
verbatimIndent = null;
|
|
284
|
-
verbatimFirst = false;
|
|
285
|
-
textIndent = indent;
|
|
286
|
-
if (preformattedClose.test(m1[0])) {
|
|
268
|
+
verbatimType = 0;
|
|
269
|
+
verbatimText = '';
|
|
270
|
+
if (preformatClose.has(m1[0].trim()))
|
|
287
271
|
continue;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
const line = m1[0].replace(' '.repeat(verbatimIndent + 1), '');
|
|
292
|
-
if (element.tag != null) {
|
|
293
|
-
applyIndent(indent);
|
|
294
|
-
}
|
|
295
|
-
if (verbatimFirst) {
|
|
296
|
-
verbatimFirst = false;
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
fragment.html += '\n';
|
|
300
|
-
}
|
|
301
|
-
if (verbatimSerialize) {
|
|
302
|
-
fragment.html += line;
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
fragment.html += escape(line);
|
|
306
|
-
}
|
|
307
|
-
continue;
|
|
308
272
|
}
|
|
309
273
|
}
|
|
310
274
|
if (m1[LINE].trim() === '') {
|
|
275
|
+
// empty lines have no effect from here on
|
|
311
276
|
continue;
|
|
312
277
|
}
|
|
313
|
-
// The id and lang directives should proceed all other directives and
|
|
314
|
-
// fragment declarations.
|
|
315
278
|
if (doc === undefined) {
|
|
279
|
+
// The meta directives get special treatment.
|
|
280
|
+
// Parsers can use the head output type to output
|
|
281
|
+
// the meta results only.
|
|
282
|
+
let parseBlock = false;
|
|
316
283
|
const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? '';
|
|
317
284
|
switch (m1[DIRECTIVE]) {
|
|
318
|
-
case 'id':
|
|
285
|
+
case 'id':
|
|
319
286
|
const url = inlineArgs.trim();
|
|
287
|
+
parseBlock = true;
|
|
320
288
|
try {
|
|
321
289
|
id = id ?? new URL(url, args?.base).toString();
|
|
322
290
|
}
|
|
323
291
|
catch (err) {
|
|
324
|
-
console.warn(`Invalid URL given to @id directive: ${url} base=${args
|
|
292
|
+
console.warn(`Invalid URL given to @id directive: ${url} base=${args?.base}`);
|
|
325
293
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
294
|
+
break;
|
|
295
|
+
case 'lang':
|
|
296
|
+
parseBlock = true;
|
|
329
297
|
lang = lang ?? inlineArgs.trim();
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
298
|
+
break;
|
|
299
|
+
case 'dir':
|
|
300
|
+
parseBlock = true;
|
|
333
301
|
dir = dir ?? inlineArgs.trim();
|
|
334
|
-
continue main;
|
|
335
|
-
}
|
|
336
|
-
default: {
|
|
337
|
-
const def = directives[m1[DIRECTIVE]];
|
|
338
|
-
if (typeof def?.meta === 'function') {
|
|
339
|
-
if (Object.keys(def).length > 1) {
|
|
340
|
-
throw new Error(`A custom directive performing the meta role cannot be used for other purposes. ` +
|
|
341
|
-
`See @${m1[DIRECTIVE]}`);
|
|
342
|
-
}
|
|
343
|
-
meta[m1[DIRECTIVE]] = def.meta({ inlineArgs });
|
|
344
|
-
continue main;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
302
|
}
|
|
348
|
-
|
|
349
|
-
|
|
303
|
+
// Even though the builtin meta directives do not support block args
|
|
304
|
+
// the args need to be parsed so they do not end up in the output.
|
|
305
|
+
const def = directives[m1[DIRECTIVE]];
|
|
306
|
+
if (typeof def?.meta === 'function' || parseBlock) {
|
|
307
|
+
verbatimIndent = indent + 1;
|
|
308
|
+
verbatimType = 4;
|
|
309
|
+
directive = {
|
|
310
|
+
name: m1[DIRECTIVE],
|
|
311
|
+
inlineArgs,
|
|
312
|
+
def,
|
|
313
|
+
};
|
|
314
|
+
continue main;
|
|
315
|
+
}
|
|
316
|
+
if (args?.outputMode === 'head') {
|
|
350
317
|
return {
|
|
351
318
|
key,
|
|
352
319
|
id,
|
|
353
320
|
lang,
|
|
354
321
|
dir,
|
|
355
|
-
meta
|
|
322
|
+
meta,
|
|
323
|
+
data,
|
|
356
324
|
};
|
|
357
325
|
}
|
|
326
|
+
doc = new Doc(id, lang, dir, meta, args?.allowAll, args?.allowedAttributes, args?.allowedElements);
|
|
358
327
|
}
|
|
359
328
|
switch (m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) {
|
|
360
329
|
case '#':
|
|
361
|
-
case '##':
|
|
362
|
-
|
|
363
|
-
const indent = (m1[INDENT].length ?? 0) / 2;
|
|
364
|
-
if (element.tag != null || textIndent != null) {
|
|
365
|
-
applyIndent(indent);
|
|
366
|
-
textIndent = null;
|
|
367
|
-
}
|
|
330
|
+
case '##':
|
|
331
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
368
332
|
fragment.id = m1[ID];
|
|
369
333
|
if (indent === 0) {
|
|
370
334
|
if (m1[FRAGMENT_TYPE] == '[') {
|
|
@@ -385,104 +349,93 @@ function longform(input, args) {
|
|
|
385
349
|
element.id = fragment.id;
|
|
386
350
|
}
|
|
387
351
|
break;
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (element.tag != null || textIndent != null) {
|
|
392
|
-
applyIndent(indent);
|
|
352
|
+
case '@':
|
|
353
|
+
if (element.tag !== undefined) {
|
|
354
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
393
355
|
}
|
|
356
|
+
const inlineArgs = m1[DIRECTIVE_INLINE_ARGS] ?? '';
|
|
394
357
|
switch (m1[DIRECTIVE]) {
|
|
395
|
-
case '
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
else if (m2[1] == null && indented) {
|
|
418
|
-
sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
|
|
419
|
-
break;
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
fragment.html += '\n' + m2[0];
|
|
423
|
-
if (m2[1] != null)
|
|
424
|
-
indented = true;
|
|
358
|
+
case 'template':
|
|
359
|
+
if (indent === 0) {
|
|
360
|
+
let indented = false;
|
|
361
|
+
fragment.template = true;
|
|
362
|
+
templateLinesRe.lastIndex = sniffTestRe.lastIndex;
|
|
363
|
+
while ((m2 = templateLinesRe.exec(input))) {
|
|
364
|
+
if (m2[1] == null && !indented && fragment.id == null) {
|
|
365
|
+
m3 = idRe.exec(m2[0]);
|
|
366
|
+
if (m3 != null)
|
|
367
|
+
fragment.id = m3[3];
|
|
368
|
+
fragment.html += m2[0];
|
|
369
|
+
}
|
|
370
|
+
else if (m2[1] == null && indented) {
|
|
371
|
+
sniffTestRe.lastIndex = templateLinesRe.lastIndex - m2[0].length;
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
fragment.html += '\n' + m2[0];
|
|
376
|
+
if (m2[1] != null)
|
|
377
|
+
indented = true;
|
|
378
|
+
}
|
|
425
379
|
}
|
|
380
|
+
[element, fragment] = applyIndent(0, key, element, fragment, doc, parsed, output, args);
|
|
426
381
|
}
|
|
427
|
-
|
|
382
|
+
continue main;
|
|
383
|
+
case 'doctype':
|
|
384
|
+
fragment.html += `<!doctype ${(inlineArgs.trim() || 'html').trim()}>`;
|
|
428
385
|
break;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
386
|
+
case 'xml':
|
|
387
|
+
fragment.html += `<?xml ${inlineArgs.trim() || 'version="1.0" encoding="UTF-8"'}?>`;
|
|
388
|
+
break;
|
|
389
|
+
case 'mount':
|
|
390
|
+
if (args?.outputMode !== 'mountable')
|
|
391
|
+
break;
|
|
392
|
+
if (inlineArgs === '') {
|
|
393
|
+
console.warn('Mount points must have a name');
|
|
433
394
|
}
|
|
434
395
|
else if (fragment.type !== 'root') {
|
|
435
|
-
|
|
396
|
+
console.warn('Mounting is only allowed on a root element');
|
|
436
397
|
}
|
|
437
398
|
fragment.mountable = true;
|
|
438
|
-
element.mount =
|
|
399
|
+
element.mount = inlineArgs.trim();
|
|
439
400
|
break;
|
|
440
|
-
}
|
|
441
|
-
default: {
|
|
442
|
-
const def = directives[m1[DIRECTIVE]];
|
|
443
|
-
if (def == null)
|
|
444
|
-
break;
|
|
445
|
-
if (typeof def.beforeRender === 'function') {
|
|
446
|
-
if (element.id != null) {
|
|
447
|
-
applyIndent(indent);
|
|
448
|
-
}
|
|
449
|
-
if (element.beforeRender == null)
|
|
450
|
-
element.beforeRender = [];
|
|
451
|
-
// TODO: Parse block args
|
|
452
|
-
const applied = {
|
|
453
|
-
...def,
|
|
454
|
-
inlineArg: m1[DIRECTIVE_INLINE_ARGS],
|
|
455
|
-
};
|
|
456
|
-
element.beforeRender.push(applied);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
401
|
}
|
|
402
|
+
const def = directives[m1[DIRECTIVE]];
|
|
403
|
+
// A directive may not be defined but we want to process
|
|
404
|
+
// any block args to keep the output valid. Builtin directives
|
|
405
|
+
// will be ignored unless they require block args.
|
|
406
|
+
verbatimIndent = indent + 1;
|
|
407
|
+
verbatimType = 4;
|
|
408
|
+
directive = {
|
|
409
|
+
name: m1[DIRECTIVE],
|
|
410
|
+
inlineArgs: m1[DIRECTIVE_INLINE_ARGS],
|
|
411
|
+
def,
|
|
412
|
+
};
|
|
460
413
|
break;
|
|
461
|
-
|
|
462
|
-
case '[':
|
|
463
|
-
case '::': {
|
|
414
|
+
case '::':
|
|
464
415
|
if (m1[ELEMENT] !== undefined) {
|
|
465
|
-
const indent = (m1[INDENT]?.length ?? 0) / 2;
|
|
466
416
|
let preformattedType;
|
|
467
417
|
let inlineText;
|
|
468
418
|
if (element.tag !== undefined ||
|
|
469
419
|
element.indent > indent) {
|
|
470
|
-
applyIndent(indent);
|
|
420
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
471
421
|
}
|
|
472
422
|
element.indent = indent;
|
|
473
|
-
textIndent = null;
|
|
474
423
|
if (indent === 0 && fragment.id == null) {
|
|
475
|
-
if (root != null)
|
|
424
|
+
if (root != null) {
|
|
425
|
+
// skip if root is found and this fragment
|
|
426
|
+
// has no id
|
|
427
|
+
skipping = true;
|
|
428
|
+
}
|
|
476
429
|
else {
|
|
477
430
|
fragment.type = 'root';
|
|
478
431
|
root = fragment;
|
|
479
432
|
}
|
|
480
433
|
}
|
|
481
434
|
const parent = element;
|
|
482
|
-
|
|
435
|
+
outerRe.lastIndex = 0;
|
|
483
436
|
// Looping through chained element declarations
|
|
484
437
|
// foo#x.y::bar::free::
|
|
485
|
-
while ((m2 =
|
|
438
|
+
while ((m2 = outerRe.exec(m1[LINE]))) {
|
|
486
439
|
let working;
|
|
487
440
|
preformattedType = m2[3];
|
|
488
441
|
inlineText = m2[4];
|
|
@@ -497,9 +450,9 @@ function longform(input, args) {
|
|
|
497
450
|
working.tag = m2[1];
|
|
498
451
|
parent.chain.push(working);
|
|
499
452
|
}
|
|
500
|
-
|
|
453
|
+
innerRe.lastIndex = 0;
|
|
501
454
|
// Looping through ids, classes and attrs
|
|
502
|
-
while ((m3 =
|
|
455
|
+
while ((m3 = innerRe.exec(m2[2]))) {
|
|
503
456
|
if (m3[2] !== undefined) {
|
|
504
457
|
working.id = m3[2];
|
|
505
458
|
}
|
|
@@ -516,8 +469,8 @@ function longform(input, args) {
|
|
|
516
469
|
let value = m3[4] ?? m3[5] ?? m3[6];
|
|
517
470
|
// attribute directives
|
|
518
471
|
if (value[0] === '@') {
|
|
519
|
-
|
|
520
|
-
m4 =
|
|
472
|
+
attributeDirectiveRe.lastIndex = 0;
|
|
473
|
+
m4 = attributeDirectiveRe.exec(value);
|
|
521
474
|
if (m4 != null) {
|
|
522
475
|
const def = directives[m4[1]];
|
|
523
476
|
if (def != null && typeof def.attr === 'function') {
|
|
@@ -532,20 +485,21 @@ function longform(input, args) {
|
|
|
532
485
|
}
|
|
533
486
|
switch (m3[3]) {
|
|
534
487
|
case 'id':
|
|
535
|
-
if (!working.id) {
|
|
488
|
+
if (!working.id && typeof value === 'string') {
|
|
536
489
|
working.id = value;
|
|
537
490
|
}
|
|
538
491
|
break;
|
|
539
492
|
case 'class':
|
|
540
|
-
if (!working.class) {
|
|
493
|
+
if (!working.class && typeof value === 'string') {
|
|
541
494
|
working.class = value;
|
|
542
495
|
}
|
|
543
|
-
else {
|
|
496
|
+
else if (typeof value === 'string') {
|
|
544
497
|
working.class += ' ' + value;
|
|
545
498
|
}
|
|
546
499
|
break;
|
|
547
500
|
default:
|
|
548
|
-
|
|
501
|
+
if (value !== false)
|
|
502
|
+
working.attrs[m3[3]] = value;
|
|
549
503
|
}
|
|
550
504
|
}
|
|
551
505
|
}
|
|
@@ -555,29 +509,34 @@ function longform(input, args) {
|
|
|
555
509
|
// server specific process.
|
|
556
510
|
if (element.mount != null) {
|
|
557
511
|
const id = element.mount;
|
|
558
|
-
applyIndent(indent + 1);
|
|
512
|
+
[element, fragment] = applyIndent(indent + 1, key, element, fragment, doc, parsed, output, args);
|
|
513
|
+
if (fragment.mountPoints == null)
|
|
514
|
+
fragment.mountPoints = [];
|
|
559
515
|
fragment.mountPoints.push({
|
|
560
516
|
id,
|
|
561
517
|
part: fragment.html,
|
|
562
518
|
});
|
|
563
519
|
fragment.html = '';
|
|
564
|
-
applyIndent(indent);
|
|
520
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
565
521
|
break;
|
|
566
522
|
}
|
|
567
523
|
if (preformattedType !== undefined) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
524
|
+
if (element.tag !== undefined)
|
|
525
|
+
[element, fragment] = applyIndent(indent + 1, key, element, fragment, doc, parsed, output, args);
|
|
526
|
+
verbatimIndent = indent + 1;
|
|
527
|
+
verbatimType = preformattedType === '{{' ? 3 : 2;
|
|
571
528
|
}
|
|
572
529
|
else if (inlineText !== undefined) {
|
|
573
530
|
element.text = inlineText;
|
|
574
531
|
}
|
|
575
532
|
break;
|
|
576
533
|
}
|
|
577
|
-
|
|
534
|
+
case '[':
|
|
535
|
+
// TODO: Add attr directive support.
|
|
536
|
+
attributeRe.lastIndex = 0;
|
|
578
537
|
m2 = m1[ATTR] !== undefined
|
|
579
|
-
?
|
|
580
|
-
:
|
|
538
|
+
? attributeRe.exec(m1[LINE])
|
|
539
|
+
: null;
|
|
581
540
|
if (m2 != null && element.tag != null) {
|
|
582
541
|
if (m2[2] === 'id') {
|
|
583
542
|
if (element.id == null) {
|
|
@@ -600,69 +559,24 @@ function longform(input, args) {
|
|
|
600
559
|
}
|
|
601
560
|
break;
|
|
602
561
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
const indent = m2[1].length / 2;
|
|
610
|
-
const tx = m2[2].trim();
|
|
611
|
-
if (element.tag != null) {
|
|
612
|
-
applyIndent(indent);
|
|
613
|
-
fragment.html += tx;
|
|
614
|
-
}
|
|
615
|
-
else if (fragment.type === 'text' && fragment.html === '') {
|
|
616
|
-
fragment.html += tx;
|
|
617
|
-
}
|
|
618
|
-
else {
|
|
619
|
-
fragment.html += ' ' + tx;
|
|
620
|
-
}
|
|
621
|
-
textIndent = indent;
|
|
622
|
-
while ((m2 = refRe.exec(tx))) {
|
|
623
|
-
const start = fragment.html.length + m2.index - tx.length;
|
|
624
|
-
fragment.refs.push({
|
|
625
|
-
id: m2[1],
|
|
626
|
-
start,
|
|
627
|
-
end: start + m2[0].length,
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
break;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
applyIndent(0);
|
|
635
|
-
const arr = Array.from(parsed.values());
|
|
636
|
-
function flatten(fragment) {
|
|
637
|
-
if (fragment.refs == null)
|
|
638
|
-
fragment.refs = [];
|
|
639
|
-
// work backwards so we don't change the html string length
|
|
640
|
-
// for the later replacements
|
|
641
|
-
for (let j = fragment.refs.length - 1; j >= 0; j--) {
|
|
642
|
-
const ref = fragment.refs[j];
|
|
643
|
-
if (claimed.has(ref.id) || !parsed.has(ref.id)) {
|
|
644
|
-
fragment.html = fragment.html.slice(0, ref.start)
|
|
645
|
-
+ fragment.html.slice(ref.end);
|
|
646
|
-
}
|
|
647
|
-
else {
|
|
648
|
-
const child = flatten(parsed.get(ref.id));
|
|
649
|
-
fragment.html = fragment.html.slice(0, ref.start)
|
|
650
|
-
+ child.html
|
|
651
|
-
+ fragment.html.slice(ref.end);
|
|
652
|
-
if (child.type === 'embed') {
|
|
653
|
-
claimed.add(child.id);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
562
|
+
default:
|
|
563
|
+
if (element.tag !== undefined)
|
|
564
|
+
[element, fragment] = applyIndent(indent, key, element, fragment, doc, parsed, output, args);
|
|
565
|
+
verbatimText = m1[0].trim();
|
|
566
|
+
verbatimIndent = indent;
|
|
567
|
+
verbatimType = 1;
|
|
656
568
|
}
|
|
657
|
-
fragment.refs = [];
|
|
658
|
-
return fragment;
|
|
659
569
|
}
|
|
570
|
+
applyIndent(0, key, element, fragment, doc, parsed, output, args);
|
|
571
|
+
if (promises.length > 0)
|
|
572
|
+
await Promise.all(promises);
|
|
660
573
|
if (root?.mountable) {
|
|
661
574
|
output.mountable = true;
|
|
662
575
|
output.tail = root.html;
|
|
663
|
-
output.mountPoints = root.mountPoints;
|
|
576
|
+
output.mountPoints = root.mountPoints ?? [];
|
|
664
577
|
return output;
|
|
665
578
|
}
|
|
579
|
+
const arr = Array.from(parsed.values());
|
|
666
580
|
for (let i = 0; i < parsed.size + 1; i++) {
|
|
667
581
|
let fragment;
|
|
668
582
|
if (i === 0 && root == null) {
|
|
@@ -677,33 +591,31 @@ function longform(input, args) {
|
|
|
677
591
|
if (fragment.refs == null || fragment.refs.length === 0) {
|
|
678
592
|
continue;
|
|
679
593
|
}
|
|
680
|
-
flatten(fragment);
|
|
594
|
+
flatten(fragment, claimed, parsed);
|
|
681
595
|
}
|
|
682
596
|
if (root?.html != null) {
|
|
683
597
|
output.root = root.html;
|
|
684
598
|
output.selector = `[data-${key}-root]`;
|
|
685
599
|
}
|
|
686
|
-
for (let i = 0
|
|
600
|
+
for (let i = 0, l = arr.length, f = arr[i]; i < l; i++) {
|
|
687
601
|
let selector;
|
|
688
|
-
|
|
689
|
-
if (fragment == null || claimed.has(fragment.id)) {
|
|
602
|
+
if (f == null || claimed.has(f.id)) {
|
|
690
603
|
continue;
|
|
691
604
|
}
|
|
692
|
-
switch (
|
|
693
|
-
case 'embed':
|
|
605
|
+
switch (f.type) {
|
|
606
|
+
case 'embed':
|
|
694
607
|
if (args?.predictable) {
|
|
695
|
-
selector = `[id=${
|
|
608
|
+
selector = `[id=${f.id}]`;
|
|
696
609
|
break;
|
|
697
610
|
}
|
|
698
|
-
}
|
|
699
611
|
case 'bare':
|
|
700
|
-
case 'range': selector = `[data-${key}=${
|
|
612
|
+
case 'range': selector = `[data-${key}=${f.id}]`;
|
|
701
613
|
}
|
|
702
|
-
output.fragments[
|
|
703
|
-
id:
|
|
614
|
+
output.fragments[f.id] = {
|
|
615
|
+
id: f.id,
|
|
704
616
|
selector,
|
|
705
|
-
type:
|
|
706
|
-
html:
|
|
617
|
+
type: f.type,
|
|
618
|
+
html: f.html,
|
|
707
619
|
};
|
|
708
620
|
}
|
|
709
621
|
output.key = key;
|
|
@@ -721,9 +633,9 @@ function longform(input, args) {
|
|
|
721
633
|
* @param getFragment - A function which returns an already processed fragment's HTML string.
|
|
722
634
|
* @returns The processed template.
|
|
723
635
|
*/
|
|
724
|
-
function processTemplate(template, args, getFragment) {
|
|
725
|
-
const lf = template.replace(templateRe, (
|
|
726
|
-
if (ref
|
|
636
|
+
async function processTemplate(template, args, longformArgs, getFragment) {
|
|
637
|
+
const lf = template.replace(templateRe, (_line, _structured, param, ref) => {
|
|
638
|
+
if (ref !== undefined && typeof getFragment === 'function') {
|
|
727
639
|
const fragment = getFragment(ref);
|
|
728
640
|
if (fragment == null)
|
|
729
641
|
return '';
|
|
@@ -731,7 +643,156 @@ function processTemplate(template, args, getFragment) {
|
|
|
731
643
|
}
|
|
732
644
|
return args[param] != null ? escape(args[param].toString()) : '';
|
|
733
645
|
});
|
|
734
|
-
return Object.values(longform(lf).fragments)[0]?.html ?? null;
|
|
646
|
+
return Object.values((await longform(lf, longformArgs)).fragments)[0]?.html ?? null;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Closes any current in progress element definition
|
|
650
|
+
* and creates a new working element.
|
|
651
|
+
*/
|
|
652
|
+
function applyIndent(targetIndent, key, element, fragment, doc, parsed, output, args) {
|
|
653
|
+
if (element.tag !== undefined) {
|
|
654
|
+
if (Array.isArray(element.beforeRender)) {
|
|
655
|
+
const el = {
|
|
656
|
+
id: element.id,
|
|
657
|
+
tag: element.tag,
|
|
658
|
+
class: element.class,
|
|
659
|
+
attrs: element.attrs,
|
|
660
|
+
};
|
|
661
|
+
const chain = [];
|
|
662
|
+
for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
|
|
663
|
+
chain.push({
|
|
664
|
+
id: el.id,
|
|
665
|
+
tag: el.tag,
|
|
666
|
+
class: el.class,
|
|
667
|
+
attrs: el.attrs,
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
for (let i = 0, l = element.beforeRender.length, def = element.beforeRender[i]; i < l; i++) {
|
|
671
|
+
def.element({
|
|
672
|
+
el,
|
|
673
|
+
chain,
|
|
674
|
+
doc,
|
|
675
|
+
inlineArgs: def.inlineArgs,
|
|
676
|
+
blockArgs: def.blockArgs,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const root = fragment.type === 'range'
|
|
681
|
+
? targetIndent < 2
|
|
682
|
+
: fragment.html === '';
|
|
683
|
+
fragment.html += `<${element.tag}`;
|
|
684
|
+
if (element.id !== undefined) {
|
|
685
|
+
fragment.html += ' id="' + element.id + '"';
|
|
686
|
+
}
|
|
687
|
+
if (element.class !== undefined) {
|
|
688
|
+
fragment.html += ' class="' + element.class + '"';
|
|
689
|
+
}
|
|
690
|
+
for (const attr of Object.entries(element.attrs)) {
|
|
691
|
+
if (attr[0] === 'id' || attr[0] === 'class')
|
|
692
|
+
continue;
|
|
693
|
+
if (attr[1] == null) {
|
|
694
|
+
fragment.html += ' ' + attr[0];
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
fragment.html += ` ${attr[0]}="${attr[1]}"`;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (root) {
|
|
701
|
+
if (fragment.type === 'root') {
|
|
702
|
+
fragment.html += ` data-${key}-root`;
|
|
703
|
+
}
|
|
704
|
+
else if (fragment.type === 'bare' || fragment.type === 'range') {
|
|
705
|
+
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
706
|
+
}
|
|
707
|
+
else if (fragment.type === 'embed' && !args?.predictable) {
|
|
708
|
+
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (element.mount !== undefined) {
|
|
712
|
+
fragment.html += ` data-${key}-mount="${element.mount}"`;
|
|
713
|
+
}
|
|
714
|
+
fragment.html += '>';
|
|
715
|
+
if (Array.isArray(element.chain)) {
|
|
716
|
+
let chained;
|
|
717
|
+
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
718
|
+
chained = element.chain[i];
|
|
719
|
+
fragment.html += '<' + chained.tag;
|
|
720
|
+
if (chained.id !== undefined) {
|
|
721
|
+
fragment.html += ' id="' + chained.id + '"';
|
|
722
|
+
}
|
|
723
|
+
if (chained.class != undefined) {
|
|
724
|
+
fragment.html += ' class="' + chained.class + '"';
|
|
725
|
+
}
|
|
726
|
+
for (const attr of Object.entries(chained.attrs)) {
|
|
727
|
+
if (attr[1] === undefined) {
|
|
728
|
+
fragment.html += ' ' + attr[0];
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
fragment.html += ` ${attr[0]}="${attr[1]}"`;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
fragment.html += '>';
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (!voids.has(element.tag) && element.text != null) {
|
|
738
|
+
fragment.html += element.text;
|
|
739
|
+
}
|
|
740
|
+
if (!voids.has(element.tag)) {
|
|
741
|
+
fragment.els.push(element);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (targetIndent <= element.indent) {
|
|
745
|
+
element = makeElement(targetIndent);
|
|
746
|
+
while (fragment.els.length !== 0 && (targetIndent == null ||
|
|
747
|
+
fragment.els[fragment.els.length - 1].indent !== targetIndent - 1)) {
|
|
748
|
+
const element = fragment.els.pop();
|
|
749
|
+
if (Array.isArray(element.chain)) {
|
|
750
|
+
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
751
|
+
fragment.html += `</${element.chain[i].tag}>`;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
fragment.html += `</${element?.tag}>`;
|
|
755
|
+
}
|
|
756
|
+
if (targetIndent === 0) {
|
|
757
|
+
if (fragment.template) {
|
|
758
|
+
output.templates[fragment.id] = fragment.html;
|
|
759
|
+
}
|
|
760
|
+
else if (fragment.type !== 'root') {
|
|
761
|
+
parsed.set(fragment.id, fragment);
|
|
762
|
+
}
|
|
763
|
+
fragment = makeFragment();
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
element = makeElement(targetIndent);
|
|
768
|
+
}
|
|
769
|
+
return [element, fragment];
|
|
770
|
+
}
|
|
771
|
+
function flatten(fragment, claimed, parsed, locals = new Set()) {
|
|
772
|
+
let r;
|
|
773
|
+
if (Array.isArray(fragment.refs)) {
|
|
774
|
+
for (let i = fragment.refs.length - 1; i > -1; i--) {
|
|
775
|
+
r = fragment.refs[i];
|
|
776
|
+
if (locals.has(r.id) || claimed.has(r.id) || !parsed.has(r.id)) {
|
|
777
|
+
// cannot use fragment here. Clear the reference marker
|
|
778
|
+
fragment.html = fragment.html.slice(0, r.start)
|
|
779
|
+
+ fragment.html.slice(r.end);
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
locals.add(fragment.id);
|
|
783
|
+
const child = flatten(parsed.get(r.id), claimed, parsed, locals);
|
|
784
|
+
fragment.html = fragment.html.slice(0, r.start)
|
|
785
|
+
+ child.html
|
|
786
|
+
+ fragment.html.slice(r.end);
|
|
787
|
+
if (child.type === 'embed') {
|
|
788
|
+
claimed.add(child.id);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
// don't re-process the fragment
|
|
794
|
+
fragment.refs = undefined;
|
|
795
|
+
return fragment;
|
|
735
796
|
}
|
|
736
797
|
|
|
737
798
|
exports.longform = longform;
|