@longform/longform 0.0.6 → 0.0.8

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/lib/types.ts CHANGED
@@ -1,4 +1,16 @@
1
1
 
2
+
3
+ export type SerializationElement = {
4
+ tag: string;
5
+ attrs: string[];
6
+ };
7
+
8
+ export type SerializerConfig = {
9
+ allowAll: boolean;
10
+ allowedAttributes: string[];
11
+ allowedElements: Record<string, SerializationElement>;
12
+ };
13
+
2
14
  export type WorkingElement = {
3
15
  indent: number;
4
16
  key?: string;
@@ -9,6 +21,9 @@ export type WorkingElement = {
9
21
  text?: string;
10
22
  html: string;
11
23
  mount?: string;
24
+ serializerConfig?: SerializerConfig;
25
+ chain: WorkingElement[];
26
+ beforeRender?: AppliedDirective[];
12
27
  };
13
28
 
14
29
  export type WorkingFragmentType =
@@ -42,7 +57,7 @@ export type WorkingFragment = {
42
57
  html: string;
43
58
  refs: FragmentRef[];
44
59
  els: WorkingElement[];
45
- mountPoints: MountPoint[];
60
+ mountPoints?: MountPoint[];
46
61
  };
47
62
 
48
63
  export type Fragment = {
@@ -58,6 +73,8 @@ export type MountPoint = {
58
73
  };
59
74
 
60
75
  export type ParsedResult = {
76
+ id?: string;
77
+ lang?: string;
61
78
  mountable?: boolean;
62
79
  root?: string;
63
80
  selector?: string;
@@ -67,3 +84,77 @@ export type ParsedResult = {
67
84
  templates: Record<string, string>;
68
85
  };
69
86
 
87
+ export type Doc = {
88
+ id?: string;
89
+ lang?: string;
90
+ meta: Readonly<Record<string, unknown>>;
91
+ serializerConfig?: SerializerConfig;
92
+ allowAll(): void;
93
+ allowAttributes(attributes: string[]): void;
94
+ allowElements(elements: Record<string, SerializationElement>): void;
95
+ };
96
+
97
+ export type SimplifiedElement = {
98
+ id?: string;
99
+ tag: string;
100
+ class?: string;
101
+ attrs: Record<string, string | boolean | null>;
102
+ };
103
+
104
+ export type GlobalCtx = {
105
+ doc: Doc;
106
+ };
107
+
108
+ export type BeforeRenderCtx = {
109
+ inlineArg?: string;
110
+ blockArg?: string;
111
+ el: SimplifiedElement;
112
+ chain: SimplifiedElement[];
113
+ doc: Readonly<Doc>;
114
+ //serializerConfig?: SerializerConfig;
115
+ //setMeta(key: string, value: unknown): void;
116
+ //allowAll(): void;
117
+ //allowAttributes(attributes: string[]): void;
118
+ //allowElements(elements: Record<string, SerializationElement>): void;
119
+ };
120
+
121
+ export type AttrValueCtx = {
122
+ tag: string;
123
+ doc: Readonly<Doc>;
124
+ meta: Readonly<Record<string, string>>
125
+ };
126
+
127
+ export type AppliesTo =
128
+ | 'self'
129
+ | 'direct-descendants'
130
+ | 'children'
131
+ | 'all'
132
+ ;
133
+
134
+ export type DirectiveDef = {
135
+ onGlobal?: (ctx: GlobalCtx) => void;
136
+ beforeFragment?: () => void;
137
+ beforeElement?: () => void;
138
+ beforeRender?: (ctx: BeforeRenderCtx) => void;
139
+ render?: (inlineArgs?: string, blockArgs?: string) => string;
140
+ renderAttr?: (ctx: AttrValueCtx) => string;
141
+ };
142
+
143
+ export type AppliedDirective = {
144
+ inlineArg?: string;
145
+ blockArg?: string;
146
+ onGlobal?: (ctx: GlobalCtx) => void;
147
+ beforeFragment?: () => void;
148
+ beforeElement?: () => void;
149
+ beforeRender?: (ctx: BeforeRenderCtx) => void;
150
+ render?: (inlineArgs?: string, blockArgs?: string) => string;
151
+ renderAttr?: (ctx: AttrValueCtx) => string;
152
+ }
153
+
154
+ export type LongformArgs = {
155
+ key?: string;
156
+ allowAll?: boolean;
157
+ allowedAttributes?: string[];
158
+ allowedElements?: Array<string | SerializationElement>;
159
+ directives?: Record<string, DirectiveDef>;
160
+ };
package/package.json CHANGED
@@ -4,10 +4,23 @@
4
4
  "author": "Matthew Quinn",
5
5
  "homepage": "https://github.com/occultist-dev/longform",
6
6
  "license": "MIT",
7
- "version": "0.0.6",
7
+ "version": "0.0.8",
8
8
  "type": "module",
9
- "main": "dist/longform.js",
9
+ "keywords": [
10
+ "HTML",
11
+ "Fragments",
12
+ "Templating",
13
+ "Longform"
14
+ ],
15
+ "main": "./dist/longform.js",
10
16
  "types": "dist/mod.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "node": "./lib/longform.ts",
20
+ "import": "./dist/longform.js",
21
+ "require": "./dist/longform.cjs"
22
+ }
23
+ },
11
24
  "repository": {
12
25
  "type": "git",
13
26
  "url": "https://github.com/occultist-dev/longform"
@@ -24,26 +37,21 @@
24
37
  "url": "https://github.com/occultist-dev/longform/blob/main/LICENSE"
25
38
  }
26
39
  ],
27
- "keywords": [
28
- "HTML",
29
- "Fragments",
30
- "Templating",
31
- "Longform"
32
- ],
33
40
  "devDependencies": {
34
41
  "@rollup/plugin-typescript": "^12.3.0",
35
42
  "@types/commonmark": "^0.27.10",
43
+ "@types/deno": "^2.5.0",
36
44
  "@types/jsdom": "^27.0.0",
37
45
  "@types/node": "^24.8.1",
38
46
  "commonmark": "^0.31.2",
39
- "dompurify": "^3.3.0",
47
+ "dompurify": "^3.4.1",
40
48
  "jsdom": "^27.0.1",
41
49
  "marked": "^16.4.1",
42
50
  "prettier": "^3.6.2",
43
- "rollup": "^4.53.3",
51
+ "rollup": "^4.60.2",
44
52
  "tslib": "^2.8.1",
45
53
  "typescript": "^5.9.3",
46
- "vnu-jar": "^24.10.17"
54
+ "vnu-jar": "^26.4.16"
47
55
  },
48
56
  "files": [
49
57
  "package.json",
@@ -54,7 +62,8 @@
54
62
  ],
55
63
  "scripts": {
56
64
  "build": "node build.ts",
57
- "test": "node --test lib/longform.test.ts",
65
+ "bench": "deno bench --no-check --allow-read ./bench/longform.bench.ts",
66
+ "test": "node --test test/**/*.test.ts",
58
67
  "serve": "node ./serve.ts"
59
68
  }
60
69
  }
@@ -1,347 +0,0 @@
1
- import { longform, processTemplate } from './longform.ts';
2
- import type { ParsedResult } from './types.ts';
3
- import test from 'node:test';
4
- import assert from 'node:assert/strict';
5
- import vnu from 'vnu-jar';
6
- import { execFile } from "node:child_process";
7
- import { tmpdir } from "node:os";
8
- import { parse, resolve } from "node:path";
9
- import { randomUUID } from "node:crypto";
10
- import { writeFile, unlink } from "node:fs/promises";
11
- import * as prettier from 'prettier';
12
- import {execPath} from 'node:process';
13
-
14
- async function validate(html: string, type: 'html' | 'xml' = 'html'): Promise<boolean> {
15
- return true;
16
-
17
- const tmpfile = resolve(tmpdir(), randomUUID() + '.html');
18
- const format = type === 'xml'
19
- ? '--xml'
20
- : '--html';
21
-
22
- await writeFile(tmpfile, html, 'utf-8');
23
-
24
- return new Promise<boolean>((resolve, reject) => {
25
- execFile('java', [
26
- '-jar',
27
- `"${vnu}"`,
28
- format,
29
- '--text',
30
- 'json',
31
- tmpfile,
32
- ], { shell: true }, async (err) => {
33
- await unlink(tmpfile);
34
-
35
- if (err) {
36
- console.log(`HTML Validation error: ${err}`);
37
- console.log(await prettier.format(html, { parser: 'html' }));
38
- return reject(false);
39
- }
40
-
41
- resolve(true);
42
- });
43
- });
44
- }
45
-
46
- function wrapHead(html: string): string {
47
- return `<!doctype html><html lang=en><head>${html}</head><body><h1>Test</h1></body></html>`;
48
- }
49
-
50
- function wrapBody(html: string): string {
51
- return `<!doctype html><html lang=en><head><title>Test</title></head><body>${html}</body></html>`;
52
- }
53
-
54
- const lf1 = `
55
- #ignored-in-test
56
- p:: Ignore me.
57
-
58
- @doctype:: html
59
- html[lang=en]::
60
- head::
61
- title:: Longform title
62
- body::
63
- h1:: Longform h1
64
- `;
65
-
66
- const html1 = `\
67
- <!doctype html>\
68
- <html lang="en"><head><title>Longform title</title></head>\
69
- <body><h1>Longform h1</h1></body></html>`;
70
-
71
- test('It creates a root element with doctype', async () => {
72
- const res = longform(lf1);
73
- const html = res.root as string;
74
-
75
- assert(await validate(html));
76
- assert.equal(html, html1);
77
- });
78
-
79
- const lf2 = `\
80
- #page-info
81
- div.card.card--info::
82
- h4.card-header::
83
- The card's title goes here
84
- p.card-description::
85
- This is the body of the card. You
86
- can use&nbsp;<b>html</b> to inline
87
- elements which are hard to use in longform
88
- syntax, <strong>but they have to be allowed
89
- using longform directives</strong>.
90
- `;
91
-
92
- const html2 = `\
93
- <div id="page-info" class="card card--info"><h4 class="card-header">The card's \
94
- title goes here</h4><p class="card-description">\
95
- This is the body of the card. You can use&nbsp;<b>html</b> \
96
- to inline elements which are hard to use in \
97
- longform syntax, <strong>but they have to be \
98
- allowed using longform directives</strong>.</p>\
99
- </div>`;
100
- test('It creates an ided element with inline html copy', async () => {
101
- const res = longform(lf2);
102
- const html = res.fragments['page-info'].html;
103
-
104
- assert(await validate(wrapBody(html)));
105
- assert.equal(html, html2);
106
- });
107
-
108
- const lf3 = `
109
- #head [
110
- title:: My Longform Test
111
- meta::
112
- [name=description]
113
- [content=This tests the validity and correctness of the longform output]
114
- ]
115
- `;
116
- const html3 = `\
117
- <title>My Longform Test</title>\
118
- <meta name="description" content="This tests the validity and correctness of the longform output">\
119
- `;
120
- test('It creates a range of elements', async () => {
121
- const res = longform(lf3);
122
- const html = res.fragments['head'].html as string;
123
-
124
- assert.equal(html, html3);
125
- });
126
-
127
- const lf4 = `
128
- @xml:: version="1.0" encoding="UTF-8"
129
- h:html::
130
- [xmlns:xdc=http://www.xml.com/books]
131
- [xmlns:h=http://www.w3.org/HTML/1998/html4]
132
- h:head::
133
- h:title:: Book Review
134
- h:body::
135
- xdc:bookreview::
136
- xdc:title:: XML: A Primer
137
- h:table::
138
- h:tr[align=center]::
139
- h:td:: Author
140
- h:td:: Price
141
- h:td:: Pages
142
- h:td:: Date
143
- h:tr[align=left]::
144
- h:td::
145
- xdc:author:: Simon St. Laurent
146
- h:td::
147
- xdc:price:: 31.98
148
- h:td::
149
- xdc:pages:: 352
150
- h:td::
151
- xdc:date:: 1998/01
152
- `;
153
-
154
- const xml4 = `\
155
- <?xml version="1.0" encoding="UTF-8"?>\
156
- <h:html xmlns:xdc="http://www.xml.com/books" xmlns:h="http://www.w3.org/HTML/1998/html4">\
157
- <h:head><h:title>Book Review</h:title></h:head>\
158
- <h:body>\
159
- <xdc:bookreview>\
160
- <xdc:title>XML: A Primer</xdc:title>\
161
- <h:table>\
162
- <h:tr align="center">\
163
- <h:td>Author</h:td><h:td>Price</h:td>\
164
- <h:td>Pages</h:td><h:td>Date</h:td></h:tr>\
165
- <h:tr align="left">\
166
- <h:td><xdc:author>Simon St. Laurent</xdc:author></h:td>\
167
- <h:td><xdc:price>31.98</xdc:price></h:td>\
168
- <h:td><xdc:pages>352</xdc:pages></h:td>\
169
- <h:td><xdc:date>1998/01</xdc:date></h:td>\
170
- </h:tr>\
171
- </h:table>\
172
- </xdc:bookreview>\
173
- </h:body>\
174
- </h:html>\
175
- `;
176
- test('It parses an XML string', () => {
177
- const res = longform(lf4);
178
- const xml = res.root as string;
179
-
180
- assert.equal(xml, xml4);
181
- })
182
-
183
- const lf5 = `
184
- pre::
185
- code:: {
186
- div::
187
- Example longform
188
- <em>with preformatted html</em>
189
- }
190
- `;
191
- const html5 = `<pre><code>div::
192
- Example longform
193
- &lt;em&gt;with preformatted html&lt;/em&gt;
194
- </code></pre>`;
195
- test('It parses preformatted content', () => {
196
- const res = longform(lf5);
197
- const html = res.root as string;
198
-
199
- assert.equal(html, html5);
200
- })
201
-
202
- const lf6 = `
203
- head::
204
- script:: {{
205
- const foo = 'bar';
206
-
207
- console.log(foo);
208
- }}
209
- `;
210
- const html6 = `\
211
- <head><script>const foo = 'bar';
212
- console.log(foo);
213
- </script></head>\
214
- `;
215
- test('It parses preformatted content', () => {
216
- const res = longform(lf6);
217
- const html = res.root as string;
218
-
219
- assert.equal(html, html6);
220
- })
221
-
222
- const lf7 = `
223
- #meta [
224
- title:: Ref test
225
- meta::
226
- [name=description]
227
- [content=Tests referencing other frags]
228
- ]
229
-
230
- #footer::
231
- footer::
232
- This is the footer
233
-
234
- @doctype:: html
235
- html[lang=en]::
236
- head::
237
- #[meta]
238
- body::
239
- Test #[header]
240
- #[main]
241
- #[footer]
242
-
243
- #header
244
- header::
245
- hgroup::
246
- h1:: Ref test
247
- p:: Tests referenceing other frags
248
-
249
- #p2
250
- p::
251
- The second p tag.
252
-
253
- #main
254
- main::
255
- #[p1]
256
- #[p2]
257
-
258
- #p1
259
- p::
260
- The first p tag.
261
- `;
262
-
263
- test('It embeds referenced fragments', { skip: true }, async () => {
264
- const res = longform(lf7);
265
- });
266
-
267
- const lf8 = `
268
- @root
269
- html::
270
- This is ignored
271
-
272
- @template
273
- #label "
274
- This is my #{position} label text #[ref]
275
- with a new line in it
276
- "
277
-
278
- ##ref
279
- p::
280
- This is referenced.
281
- "
282
- `;
283
-
284
- test('It renders plan text fragments', { skip: true }, () => {
285
- const parsed = longform(lf8);
286
- const res = processTemplate('label', { position: 4 }, parsed);
287
- })
288
-
289
- const lf9 = `
290
- @template
291
- #my-template
292
- div[aria-label=Something something #{position}]::
293
- Hello world!
294
-
295
- #other-fragment
296
- div::
297
- p:: Test
298
- `;
299
- const html9 = `\
300
- <div id="other-fragment"><p>Test</p></div>\
301
- `;
302
- const template9 = `\
303
- <div id="my-template" aria-label="Something something 4">Hello world!</div>\
304
- `;
305
- test('It renders templated element fragments', { only: true }, () => {
306
- const parsed = longform(lf9);
307
- console.log(parsed)
308
- const res = processTemplate(parsed.templates['my-template'], { position: 4 }, x => {
309
- return parsed.fragments[x]?.html;
310
- });
311
-
312
- assert.equal(parsed.fragments['other-fragment'].html, html9);
313
- assert.equal(res, template9);
314
- });
315
-
316
- const lf10 = `
317
- @root
318
- @doctype:: html
319
- html::
320
- @mount:: head
321
- head::
322
- body::
323
- @mount:: header
324
- header.header::
325
- @mount:: main
326
- main::
327
- @mount:: footer
328
- `;
329
- test('It breaks mount points up into individual dom slices', () => {
330
- const parsed = longform(lf10, console.log);
331
-
332
- console.log(parsed);
333
-
334
- assert(parsed.mountable);
335
- assert.equal(parsed.mountPoints[0].id, 'head');
336
- assert.equal(parsed.mountPoints[0].part, '<!doctype html><html><head data-lf-mount="head">');
337
- assert.equal(parsed.mountPoints[1].id, 'header');
338
- assert.equal(parsed.mountPoints[1].part, '</head><body><header data-lf-mount="header" class="header">');
339
- assert.equal(parsed.mountPoints[2].id, 'main');
340
- assert.equal(parsed.mountPoints[2].part, '</header><main data-lf-mount="main">');
341
- assert.equal(parsed.tail, '</main></body></html>');
342
- });
343
-
344
-
345
-
346
-
347
-