@longform/longform 0.0.11 → 0.0.12
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 +67 -30
- package/dist/longform.cjs.map +1 -1
- package/dist/longform.js +67 -30
- 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 +67 -30
- 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/types.d.ts +66 -12
- package/lib/longform.ts +90 -45
- package/lib/types.ts +82 -13
- package/package.json +2 -2
package/lib/longform.ts
CHANGED
|
@@ -69,7 +69,7 @@ function makeElement(indent: number = 0): WorkingElement {
|
|
|
69
69
|
html: '',
|
|
70
70
|
mount: undefined,
|
|
71
71
|
serializerConfig: undefined,
|
|
72
|
-
chain: undefined,
|
|
72
|
+
chain: undefined as unknown as WorkingElement[],
|
|
73
73
|
beforeRender: undefined,
|
|
74
74
|
};
|
|
75
75
|
}
|
|
@@ -90,13 +90,14 @@ class Doc {
|
|
|
90
90
|
id?: string;
|
|
91
91
|
lang?: string;
|
|
92
92
|
dir?: string;
|
|
93
|
-
meta: Record<string, unknown
|
|
93
|
+
meta: Readonly<Record<string, unknown>>;
|
|
94
94
|
#serializerConfig: SerializerConfig;
|
|
95
95
|
|
|
96
96
|
constructor(
|
|
97
|
-
id
|
|
98
|
-
lang
|
|
99
|
-
dir
|
|
97
|
+
id: string | undefined,
|
|
98
|
+
lang: string | undefined,
|
|
99
|
+
dir: string | undefined,
|
|
100
|
+
meta: Record<string, unknown>,
|
|
100
101
|
allowAll?: boolean | undefined,
|
|
101
102
|
allowedAttributes?: string[] | undefined,
|
|
102
103
|
allowedElements?: Array<string | SerializationElement>,
|
|
@@ -104,6 +105,7 @@ class Doc {
|
|
|
104
105
|
this.id = id;
|
|
105
106
|
this.lang = lang;
|
|
106
107
|
this.dir = dir;
|
|
108
|
+
this.meta = Object.freeze(meta);
|
|
107
109
|
this.#serializerConfig = {
|
|
108
110
|
allowAll: allowAll ?? false,
|
|
109
111
|
allowedAttributes: allowedAttributes ?? [],
|
|
@@ -162,9 +164,10 @@ export function longform(
|
|
|
162
164
|
, fragment: WorkingFragment = makeFragment()
|
|
163
165
|
// the root fragment
|
|
164
166
|
, root: WorkingFragment | null = null
|
|
165
|
-
, id: string | undefined
|
|
166
|
-
, lang: string | undefined
|
|
167
|
-
, dir: string | undefined
|
|
167
|
+
, id: string | undefined = args?.id
|
|
168
|
+
, lang: string | undefined = args?.lang
|
|
169
|
+
, dir: string | undefined = args?.dir
|
|
170
|
+
, meta: Record<string, unknown> = {}
|
|
168
171
|
, doc: Doc | undefined
|
|
169
172
|
// ids of claimed fragments
|
|
170
173
|
const claimed: Set<string> = new Set()
|
|
@@ -175,6 +178,17 @@ export function longform(
|
|
|
175
178
|
, output: ParsedResult = Object.create(null)
|
|
176
179
|
, directives: Record<string, DirectiveDef> = {}
|
|
177
180
|
|
|
181
|
+
let key: string = args?.key as string;
|
|
182
|
+
|
|
183
|
+
if (!args?.predictable && key == null) {
|
|
184
|
+
const arr = new Uint8Array(10);
|
|
185
|
+
|
|
186
|
+
crypto.getRandomValues(arr)
|
|
187
|
+
|
|
188
|
+
key = arr.toHex();
|
|
189
|
+
} else if (key == null) {
|
|
190
|
+
key = 'lf';
|
|
191
|
+
}
|
|
178
192
|
|
|
179
193
|
output.fragments = {};
|
|
180
194
|
output.templates = {};
|
|
@@ -201,7 +215,7 @@ export function longform(
|
|
|
201
215
|
if (Array.isArray(element.beforeRender)) {
|
|
202
216
|
const el: SimplifiedElement = {
|
|
203
217
|
id: element.id,
|
|
204
|
-
tag: element.tag,
|
|
218
|
+
tag: element.tag as string,
|
|
205
219
|
class: element.class,
|
|
206
220
|
attrs: element.attrs,
|
|
207
221
|
};
|
|
@@ -210,7 +224,7 @@ export function longform(
|
|
|
210
224
|
for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
|
|
211
225
|
chain.push({
|
|
212
226
|
id: el.id,
|
|
213
|
-
tag: el.tag,
|
|
227
|
+
tag: el.tag as string,
|
|
214
228
|
class: el.class,
|
|
215
229
|
attrs: el.attrs,
|
|
216
230
|
});
|
|
@@ -220,7 +234,7 @@ export function longform(
|
|
|
220
234
|
def.beforeRender({
|
|
221
235
|
el,
|
|
222
236
|
chain,
|
|
223
|
-
doc,
|
|
237
|
+
doc: doc as Doc,
|
|
224
238
|
inlineArg: def.inlineArg,
|
|
225
239
|
blockArg: def.blockArg,
|
|
226
240
|
});
|
|
@@ -235,18 +249,6 @@ export function longform(
|
|
|
235
249
|
|
|
236
250
|
fragment.html += `<${element.tag}`
|
|
237
251
|
|
|
238
|
-
if (root) {
|
|
239
|
-
if (fragment.type === 'root') {
|
|
240
|
-
fragment.html += ` data-lf-root`;
|
|
241
|
-
} else if (fragment.type === 'bare' || fragment.type === 'range') {
|
|
242
|
-
fragment.html += ` data-lf="${fragment.id}"`;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (element.mount !== undefined) {
|
|
247
|
-
fragment.html += ` data-lf-mount="${element.mount}"`;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
252
|
if (element.id !== undefined) {
|
|
251
253
|
fragment.html += ' id="' + element.id + '"';
|
|
252
254
|
}
|
|
@@ -256,6 +258,7 @@ export function longform(
|
|
|
256
258
|
}
|
|
257
259
|
|
|
258
260
|
for (const attr of Object.entries(element.attrs)) {
|
|
261
|
+
if (attr[0] === 'id' || attr[0] === 'class') continue;
|
|
259
262
|
if (attr[1] == null) {
|
|
260
263
|
fragment.html += ' ' + attr[0]
|
|
261
264
|
} else {
|
|
@@ -263,6 +266,20 @@ export function longform(
|
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
268
|
|
|
269
|
+
if (root) {
|
|
270
|
+
if (fragment.type === 'root') {
|
|
271
|
+
fragment.html += ` data-${key}-root`;
|
|
272
|
+
} else if (fragment.type === 'bare' || fragment.type === 'range') {
|
|
273
|
+
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
274
|
+
} else if (fragment.type === 'embed' && !args?.predictable) {
|
|
275
|
+
fragment.html += ` data-${key}="${fragment.id}"`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (element.mount !== undefined) {
|
|
280
|
+
fragment.html += ` data-${key}-mount="${element.mount}"`;
|
|
281
|
+
}
|
|
282
|
+
|
|
266
283
|
fragment.html += '>';
|
|
267
284
|
|
|
268
285
|
if (Array.isArray(element.chain)) {
|
|
@@ -313,7 +330,7 @@ export function longform(
|
|
|
313
330
|
fragment.els[fragment.els.length - 1].indent !== targetIndent - 1
|
|
314
331
|
)
|
|
315
332
|
) {
|
|
316
|
-
const element = fragment.els.pop();
|
|
333
|
+
const element = fragment.els.pop() as WorkingElement;
|
|
317
334
|
|
|
318
335
|
if (Array.isArray(element.chain)) {
|
|
319
336
|
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
@@ -349,7 +366,7 @@ export function longform(
|
|
|
349
366
|
|
|
350
367
|
// If this is a script tag or preformatted block
|
|
351
368
|
// we want to retain the intended formatting less
|
|
352
|
-
// the indent.
|
|
369
|
+
// the indent. Pre-formatting can apply to any element
|
|
353
370
|
// by ending the declaration with `:: {`.
|
|
354
371
|
if (verbatimIndent != null) {
|
|
355
372
|
// inside a script or preformatted block
|
|
@@ -404,21 +421,45 @@ export function longform(
|
|
|
404
421
|
|
|
405
422
|
switch (m1[DIRECTIVE]) {
|
|
406
423
|
case 'id': {
|
|
407
|
-
|
|
408
|
-
id = inlineArgs.trim();
|
|
424
|
+
id = id ?? new URL(inlineArgs.trim(), args?.base).toString();
|
|
409
425
|
continue main;
|
|
410
426
|
}
|
|
411
427
|
case 'lang': {
|
|
412
|
-
lang = inlineArgs.trim();
|
|
428
|
+
lang = lang ?? inlineArgs.trim();
|
|
413
429
|
continue main;
|
|
414
430
|
}
|
|
415
431
|
case 'dir': {
|
|
416
|
-
dir = inlineArgs.trim();
|
|
432
|
+
dir = dir ?? inlineArgs.trim();
|
|
417
433
|
continue main;
|
|
418
434
|
}
|
|
435
|
+
default: {
|
|
436
|
+
const def = directives[m1[DIRECTIVE]];
|
|
437
|
+
|
|
438
|
+
if (typeof def?.meta === 'function') {
|
|
439
|
+
if (Object.keys(def).length > 1) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
`A custom directive performing the meta role cannot be used for other purposes. ` +
|
|
442
|
+
`See @${m1[DIRECTIVE]}`,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
meta[m1[DIRECTIVE]] = def.meta({ inlineArgs });
|
|
447
|
+
continue main;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
419
450
|
}
|
|
420
451
|
|
|
421
|
-
doc = new Doc(id, lang, dir, args?.allowAll, args?.allowedAttributes, args?.allowedElements);
|
|
452
|
+
doc = new Doc(id, lang, dir, meta, args?.allowAll, args?.allowedAttributes, args?.allowedElements);
|
|
453
|
+
|
|
454
|
+
if (args?.outputMode === 'meta') {
|
|
455
|
+
return {
|
|
456
|
+
key,
|
|
457
|
+
id,
|
|
458
|
+
lang,
|
|
459
|
+
dir,
|
|
460
|
+
meta: doc.meta,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
422
463
|
}
|
|
423
464
|
|
|
424
465
|
switch (m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) {
|
|
@@ -751,7 +792,7 @@ export function longform(
|
|
|
751
792
|
if (i === 0 && root == null) {
|
|
752
793
|
continue;
|
|
753
794
|
} else if (i === 0) {
|
|
754
|
-
fragment = root;
|
|
795
|
+
fragment = root as WorkingFragment;
|
|
755
796
|
} else {
|
|
756
797
|
fragment = arr[i - 1];
|
|
757
798
|
}
|
|
@@ -765,41 +806,44 @@ export function longform(
|
|
|
765
806
|
|
|
766
807
|
if (root?.html != null) {
|
|
767
808
|
output.root = root.html;
|
|
768
|
-
output.selector = `[data-
|
|
809
|
+
output.selector = `[data-${key}-root]`;
|
|
769
810
|
}
|
|
770
811
|
|
|
771
812
|
for (let i = 0; i < arr.length; i++) {
|
|
772
|
-
let selector
|
|
773
|
-
const fragment = arr[i];
|
|
813
|
+
let selector!: string;
|
|
814
|
+
const fragment: WorkingFragment = arr[i];
|
|
774
815
|
|
|
775
|
-
if (fragment == null || claimed.has(fragment.id)) {
|
|
816
|
+
if (fragment == null || claimed.has(fragment.id as string)) {
|
|
776
817
|
continue;
|
|
777
818
|
}
|
|
778
819
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
820
|
+
switch (fragment.type) {
|
|
821
|
+
case 'embed': {
|
|
822
|
+
if (args?.predictable) {
|
|
823
|
+
selector = `[id=${fragment.id}]`;
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
case 'bare':
|
|
828
|
+
case 'range': selector = `[data-${key}=${fragment.id}]`;
|
|
785
829
|
}
|
|
786
830
|
|
|
787
|
-
output.fragments[fragment.id] = {
|
|
788
|
-
id: fragment.id,
|
|
831
|
+
output.fragments[fragment.id as string] = {
|
|
832
|
+
id: fragment.id as string,
|
|
789
833
|
selector,
|
|
790
834
|
type: fragment.type as 'embed' | 'bare' | 'range',
|
|
791
835
|
html: fragment.html,
|
|
792
836
|
};
|
|
793
837
|
}
|
|
794
838
|
|
|
839
|
+
output.key = key;
|
|
795
840
|
output.id = doc?.id;
|
|
796
841
|
output.lang = doc?.lang;
|
|
842
|
+
output.dir = doc?.dir;
|
|
797
843
|
|
|
798
844
|
return output;
|
|
799
845
|
}
|
|
800
846
|
|
|
801
|
-
|
|
802
|
-
|
|
803
847
|
/**
|
|
804
848
|
* Processes a client side Longform template to HTML fragment string.
|
|
805
849
|
*
|
|
@@ -827,3 +871,4 @@ export function processTemplate(
|
|
|
827
871
|
|
|
828
872
|
return Object.values(longform(lf).fragments)[0]?.html ?? null;
|
|
829
873
|
}
|
|
874
|
+
|
package/lib/types.ts
CHANGED
|
@@ -72,18 +72,6 @@ export type MountPoint = {
|
|
|
72
72
|
part: string;
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
export type ParsedResult = {
|
|
76
|
-
id?: string;
|
|
77
|
-
lang?: string;
|
|
78
|
-
mountable?: boolean;
|
|
79
|
-
root?: string;
|
|
80
|
-
selector?: string;
|
|
81
|
-
mountPoints: MountPoint[];
|
|
82
|
-
tail?: string;
|
|
83
|
-
fragments: Record<string, Fragment>;
|
|
84
|
-
templates: Record<string, string>;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
75
|
export type Doc = {
|
|
88
76
|
id?: string;
|
|
89
77
|
lang?: string;
|
|
@@ -105,6 +93,10 @@ export type GlobalCtx = {
|
|
|
105
93
|
doc: Doc;
|
|
106
94
|
};
|
|
107
95
|
|
|
96
|
+
export type MetaCtx = {
|
|
97
|
+
inlineArgs?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
108
100
|
export type BeforeRenderCtx = {
|
|
109
101
|
inlineArg?: string;
|
|
110
102
|
blockArg?: string;
|
|
@@ -132,7 +124,13 @@ export type AppliesTo =
|
|
|
132
124
|
;
|
|
133
125
|
|
|
134
126
|
export type DirectiveDef = {
|
|
135
|
-
|
|
127
|
+
/**
|
|
128
|
+
* A directive that implements the meta hook cannot be used for other purposes.
|
|
129
|
+
* This is enforced by the parser through checking the amount of keys on the
|
|
130
|
+
* definition object.
|
|
131
|
+
*/
|
|
132
|
+
meta?: (args: MetaCtx) => unknown;
|
|
133
|
+
global?: (ctx: GlobalCtx) => void;
|
|
136
134
|
beforeFragment?: () => void;
|
|
137
135
|
beforeElement?: () => void;
|
|
138
136
|
beforeRender?: (ctx: BeforeRenderCtx) => void;
|
|
@@ -151,11 +149,82 @@ export type AppliedDirective = {
|
|
|
151
149
|
renderAttr?: (ctx: AttrValueCtx) => string;
|
|
152
150
|
}
|
|
153
151
|
|
|
152
|
+
export type OutputMode =
|
|
153
|
+
| 'doc'
|
|
154
|
+
| 'meta'
|
|
155
|
+
| 'mountable'
|
|
156
|
+
;
|
|
157
|
+
|
|
154
158
|
export type LongformArgs = {
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The key used for creating data attributes.
|
|
162
|
+
* Defaults to a random string or 'lf' if predictable mode is enabled.
|
|
163
|
+
*/
|
|
155
164
|
key?: string;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* If true the parser will default to using the 'lf' key for data attributes
|
|
168
|
+
* and fragments with embedded ids will not used data attributes for selectors.
|
|
169
|
+
*
|
|
170
|
+
* This is less safe in environments where Longform is used for SSR.
|
|
171
|
+
*/
|
|
172
|
+
predictable?: boolean;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The id of the document. If set the @id is ignored.
|
|
176
|
+
* This can be used when creating partial re-usable documents.
|
|
177
|
+
*/
|
|
178
|
+
id?: string;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The language tag of the document. If set the @lang
|
|
182
|
+
* directive is ignored.
|
|
183
|
+
* This can be used when creating partial re-usable documents.
|
|
184
|
+
*/
|
|
185
|
+
lang?: string;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The text direction of the document. If set the @dir
|
|
189
|
+
* directive is ignored.
|
|
190
|
+
* This can be used when creating partial re-usable documents.
|
|
191
|
+
*/
|
|
192
|
+
dir?: string;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* A base URL that can be used for resolving relative URLs
|
|
196
|
+
* declared using the @id directive.
|
|
197
|
+
*/
|
|
198
|
+
base?: string;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Metadata set on the document.
|
|
202
|
+
*/
|
|
203
|
+
meta?: Record<string, unknown>;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The output mode of the document.
|
|
207
|
+
*/
|
|
208
|
+
outputMode?: OutputMode;
|
|
209
|
+
|
|
156
210
|
allowAll?: boolean;
|
|
157
211
|
allowedAttributes?: string[];
|
|
158
212
|
allowedElements?: Array<string | SerializationElement>;
|
|
159
213
|
directives?: Record<string, DirectiveDef>;
|
|
160
214
|
fragments?: Record<string, Fragment>;
|
|
161
215
|
};
|
|
216
|
+
|
|
217
|
+
export type ParsedResult = {
|
|
218
|
+
key: string;
|
|
219
|
+
id?: string;
|
|
220
|
+
lang?: string;
|
|
221
|
+
dir?: string;
|
|
222
|
+
meta: Record<string, unknown>;
|
|
223
|
+
mountable?: boolean;
|
|
224
|
+
root?: string;
|
|
225
|
+
selector?: string;
|
|
226
|
+
mountPoints: MountPoint[];
|
|
227
|
+
tail?: string;
|
|
228
|
+
fragments: Record<string, Fragment>;
|
|
229
|
+
templates: Record<string, string>;
|
|
230
|
+
};
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"author": "Matthew Quinn",
|
|
5
5
|
"homepage": "https://github.com/occultist-dev/longform",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"version": "0.0.
|
|
7
|
+
"version": "0.0.12",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"keywords": [
|
|
10
10
|
"HTML",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"prettier": "^3.6.2",
|
|
50
50
|
"rollup": "^4.60.2",
|
|
51
51
|
"tslib": "^2.8.1",
|
|
52
|
-
"typescript": "^
|
|
52
|
+
"typescript": "^6.0.3",
|
|
53
53
|
"vnu-jar": "^26.4.16"
|
|
54
54
|
},
|
|
55
55
|
"files": [
|