@longform/longform 0.0.10 → 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 +72 -33
- package/dist/longform.cjs.map +1 -1
- package/dist/longform.js +72 -33
- 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 +72 -33
- 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 +97 -48
- 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,20 +164,34 @@ 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()
|
|
171
174
|
// parsed fragments
|
|
172
|
-
, parsed: Map<string, WorkingFragment> = new Map(
|
|
175
|
+
, parsed: Map<string, WorkingFragment> = new Map(
|
|
176
|
+
Object.entries(args?.fragments as unknown as Record<string, WorkingFragment> ?? {})
|
|
177
|
+
)
|
|
173
178
|
, output: ParsedResult = Object.create(null)
|
|
174
179
|
, directives: Record<string, DirectiveDef> = {}
|
|
175
180
|
|
|
181
|
+
let key: string = args?.key as string;
|
|
176
182
|
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
}
|
|
192
|
+
|
|
193
|
+
output.fragments = {};
|
|
194
|
+
output.templates = {};
|
|
179
195
|
|
|
180
196
|
if (args?.directives != null) {
|
|
181
197
|
const entries = Object.entries(args.directives);
|
|
@@ -199,7 +215,7 @@ export function longform(
|
|
|
199
215
|
if (Array.isArray(element.beforeRender)) {
|
|
200
216
|
const el: SimplifiedElement = {
|
|
201
217
|
id: element.id,
|
|
202
|
-
tag: element.tag,
|
|
218
|
+
tag: element.tag as string,
|
|
203
219
|
class: element.class,
|
|
204
220
|
attrs: element.attrs,
|
|
205
221
|
};
|
|
@@ -208,7 +224,7 @@ export function longform(
|
|
|
208
224
|
for (let i = 0, l = element.chain.length, el = element.chain[i]; i < l; i++) {
|
|
209
225
|
chain.push({
|
|
210
226
|
id: el.id,
|
|
211
|
-
tag: el.tag,
|
|
227
|
+
tag: el.tag as string,
|
|
212
228
|
class: el.class,
|
|
213
229
|
attrs: el.attrs,
|
|
214
230
|
});
|
|
@@ -218,7 +234,7 @@ export function longform(
|
|
|
218
234
|
def.beforeRender({
|
|
219
235
|
el,
|
|
220
236
|
chain,
|
|
221
|
-
doc,
|
|
237
|
+
doc: doc as Doc,
|
|
222
238
|
inlineArg: def.inlineArg,
|
|
223
239
|
blockArg: def.blockArg,
|
|
224
240
|
});
|
|
@@ -233,18 +249,6 @@ export function longform(
|
|
|
233
249
|
|
|
234
250
|
fragment.html += `<${element.tag}`
|
|
235
251
|
|
|
236
|
-
if (root) {
|
|
237
|
-
if (fragment.type === 'root') {
|
|
238
|
-
fragment.html += ` data-lf-root`;
|
|
239
|
-
} else if (fragment.type === 'bare' || fragment.type === 'range') {
|
|
240
|
-
fragment.html += ` data-lf="${fragment.id}"`;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (element.mount !== undefined) {
|
|
245
|
-
fragment.html += ` data-lf-mount="${element.mount}"`;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
252
|
if (element.id !== undefined) {
|
|
249
253
|
fragment.html += ' id="' + element.id + '"';
|
|
250
254
|
}
|
|
@@ -254,6 +258,7 @@ export function longform(
|
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
for (const attr of Object.entries(element.attrs)) {
|
|
261
|
+
if (attr[0] === 'id' || attr[0] === 'class') continue;
|
|
257
262
|
if (attr[1] == null) {
|
|
258
263
|
fragment.html += ' ' + attr[0]
|
|
259
264
|
} else {
|
|
@@ -261,6 +266,20 @@ export function longform(
|
|
|
261
266
|
}
|
|
262
267
|
}
|
|
263
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
|
+
|
|
264
283
|
fragment.html += '>';
|
|
265
284
|
|
|
266
285
|
if (Array.isArray(element.chain)) {
|
|
@@ -311,7 +330,7 @@ export function longform(
|
|
|
311
330
|
fragment.els[fragment.els.length - 1].indent !== targetIndent - 1
|
|
312
331
|
)
|
|
313
332
|
) {
|
|
314
|
-
const element = fragment.els.pop();
|
|
333
|
+
const element = fragment.els.pop() as WorkingElement;
|
|
315
334
|
|
|
316
335
|
if (Array.isArray(element.chain)) {
|
|
317
336
|
for (let i = 0, l = element.chain.length; i < l; i++) {
|
|
@@ -347,7 +366,7 @@ export function longform(
|
|
|
347
366
|
|
|
348
367
|
// If this is a script tag or preformatted block
|
|
349
368
|
// we want to retain the intended formatting less
|
|
350
|
-
// the indent.
|
|
369
|
+
// the indent. Pre-formatting can apply to any element
|
|
351
370
|
// by ending the declaration with `:: {`.
|
|
352
371
|
if (verbatimIndent != null) {
|
|
353
372
|
// inside a script or preformatted block
|
|
@@ -402,21 +421,45 @@ export function longform(
|
|
|
402
421
|
|
|
403
422
|
switch (m1[DIRECTIVE]) {
|
|
404
423
|
case 'id': {
|
|
405
|
-
|
|
406
|
-
id = inlineArgs.trim();
|
|
424
|
+
id = id ?? new URL(inlineArgs.trim(), args?.base).toString();
|
|
407
425
|
continue main;
|
|
408
426
|
}
|
|
409
427
|
case 'lang': {
|
|
410
|
-
lang = inlineArgs.trim();
|
|
428
|
+
lang = lang ?? inlineArgs.trim();
|
|
411
429
|
continue main;
|
|
412
430
|
}
|
|
413
431
|
case 'dir': {
|
|
414
|
-
dir = inlineArgs.trim();
|
|
432
|
+
dir = dir ?? inlineArgs.trim();
|
|
415
433
|
continue main;
|
|
416
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
|
+
}
|
|
417
450
|
}
|
|
418
451
|
|
|
419
|
-
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
|
+
}
|
|
420
463
|
}
|
|
421
464
|
|
|
422
465
|
switch (m1[DIRECTIVE_KEY] ?? m1[ID_TYPE] ?? m1[ELEMENT] ?? m1[ATTR]) {
|
|
@@ -707,6 +750,8 @@ export function longform(
|
|
|
707
750
|
const arr = Array.from(parsed.values());
|
|
708
751
|
|
|
709
752
|
function flatten(fragment: WorkingFragment): WorkingFragment {
|
|
753
|
+
if (fragment.refs == null) fragment.refs = [];
|
|
754
|
+
|
|
710
755
|
// work backwards so we don't change the html string length
|
|
711
756
|
// for the later replacements
|
|
712
757
|
for (let j = fragment.refs.length - 1; j >= 0; j--) {
|
|
@@ -747,7 +792,7 @@ export function longform(
|
|
|
747
792
|
if (i === 0 && root == null) {
|
|
748
793
|
continue;
|
|
749
794
|
} else if (i === 0) {
|
|
750
|
-
fragment = root;
|
|
795
|
+
fragment = root as WorkingFragment;
|
|
751
796
|
} else {
|
|
752
797
|
fragment = arr[i - 1];
|
|
753
798
|
}
|
|
@@ -761,41 +806,44 @@ export function longform(
|
|
|
761
806
|
|
|
762
807
|
if (root?.html != null) {
|
|
763
808
|
output.root = root.html;
|
|
764
|
-
output.selector = `[data-
|
|
809
|
+
output.selector = `[data-${key}-root]`;
|
|
765
810
|
}
|
|
766
811
|
|
|
767
812
|
for (let i = 0; i < arr.length; i++) {
|
|
768
|
-
let selector
|
|
769
|
-
const fragment = arr[i];
|
|
813
|
+
let selector!: string;
|
|
814
|
+
const fragment: WorkingFragment = arr[i];
|
|
770
815
|
|
|
771
|
-
if (fragment == null || claimed.has(fragment.id)) {
|
|
816
|
+
if (fragment == null || claimed.has(fragment.id as string)) {
|
|
772
817
|
continue;
|
|
773
818
|
}
|
|
774
819
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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}]`;
|
|
781
829
|
}
|
|
782
830
|
|
|
783
|
-
output.fragments[fragment.id] = {
|
|
784
|
-
id: fragment.id,
|
|
831
|
+
output.fragments[fragment.id as string] = {
|
|
832
|
+
id: fragment.id as string,
|
|
785
833
|
selector,
|
|
786
834
|
type: fragment.type as 'embed' | 'bare' | 'range',
|
|
787
835
|
html: fragment.html,
|
|
788
836
|
};
|
|
789
837
|
}
|
|
790
838
|
|
|
839
|
+
output.key = key;
|
|
791
840
|
output.id = doc?.id;
|
|
792
841
|
output.lang = doc?.lang;
|
|
842
|
+
output.dir = doc?.dir;
|
|
793
843
|
|
|
794
844
|
return output;
|
|
795
845
|
}
|
|
796
846
|
|
|
797
|
-
|
|
798
|
-
|
|
799
847
|
/**
|
|
800
848
|
* Processes a client side Longform template to HTML fragment string.
|
|
801
849
|
*
|
|
@@ -823,3 +871,4 @@ export function processTemplate(
|
|
|
823
871
|
|
|
824
872
|
return Object.values(longform(lf).fragments)[0]?.html ?? null;
|
|
825
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": [
|