Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/website/scripts/generateAwaitedDOM.ts
1028 views
1
#!/usr/bin/env ts-node
2
3
import * as Fs from 'fs';
4
import * as Path from 'path';
5
import json2md from 'json2md';
6
import decamelize from 'decamelize';
7
import rawDocs from 'awaited-dom/docs.json';
8
import { DOMParser } from 'noderdom-detached';
9
import { IElement, IHTMLLinkElement } from 'noderdom-detached/base/interfaces';
10
11
const docs = (rawDocs as unknown) as IDoc[];
12
13
const domParser = new DOMParser();
14
15
interface IDoc {
16
name: string;
17
variableName: string;
18
category: string;
19
tags: string;
20
overview: string;
21
dependencies: any[];
22
properties: any[];
23
methods: any[];
24
events: any[];
25
}
26
27
const docUrls = new Map<string, string>();
28
for (const doc of docs) {
29
docUrls.set(doc.name, decamelize(doc.name, '-'));
30
}
31
32
json2md.converters.html = input => input;
33
34
const awaitedDomDir = Path.resolve(__dirname, '../awaited-dom');
35
const docsDir = Path.resolve(__dirname, '../docs');
36
const awaitedDOMBasePath = Path.join(docsDir, 'BasicInterfaces', 'AwaitedDOM.base.md');
37
const awaitedDOMIndexPath1 = Path.join(docsDir, 'BasicInterfaces', 'AwaitedDOM.md');
38
const awaitedDOMIndexPath2 = Path.join(awaitedDomDir, 'index.md');
39
40
const docsByTag: { [tag: string]: IDoc[] } = {};
41
42
docs.forEach((doc: IDoc) => {
43
const filepath = Path.join(awaitedDomDir, `${doc.name}.md`);
44
const tags = doc.tags.split(',').filter(t => t);
45
tags.forEach(tag => {
46
docsByTag[tag] = docsByTag[tag] || [];
47
docsByTag[tag].push(doc);
48
});
49
saveDoc(doc, filepath);
50
});
51
52
let awaitedDOMBase = Fs.readFileSync(awaitedDOMBasePath, 'utf-8');
53
54
Object.keys(docsByTag).forEach(tag => {
55
const placementToken = `[INTERFACES:${tag}]`;
56
if (!awaitedDOMBase.includes(placementToken)) return;
57
58
const linksTable = extractLinksTable(docsByTag[tag], (doc: IDoc) => {
59
return [doc.name, `/docs/awaited-dom/${decamelize(doc.name, '-')}`];
60
});
61
62
const markup = [{ table: linksTable }];
63
const markdown = json2md(markup);
64
awaitedDOMBase = awaitedDOMBase.replace(placementToken, markdown);
65
});
66
67
Fs.writeFileSync(awaitedDOMIndexPath1, awaitedDOMBase);
68
Fs.writeFileSync(awaitedDOMIndexPath2, awaitedDOMBase);
69
70
// SAVE DOC
71
72
function saveDoc(doc: IDoc, filePath: string) {
73
const markup: any[] = [
74
{ h1: `[AwaitedDOM](/docs/basic-interfaces/awaited-dom) <span>/</span> ${doc.name}` },
75
];
76
const variableName = doc.variableName || '';
77
78
JSON.parse(doc.overview || '[]').forEach((overview: any) => {
79
markup.push({ html: `<div class='overview'>${cleanupHTML(overview)}</div>` });
80
});
81
82
if (doc.tags.includes('Super') && doc.dependencies.length) {
83
markup.push({ h2: 'Dependencies' });
84
markup.push({
85
p: `${doc.name} implements all the properties and methods of the following classes:`,
86
});
87
const linksTable = extractLinksTable(doc.dependencies, (dep: any) => {
88
return [dep.name, `./${decamelize(dep.name, '-')}`];
89
});
90
markup.push({ table: linksTable });
91
}
92
93
if (doc.name === 'XPathResult') {
94
for (const p of doc.properties) {
95
if (p.name === 'singleNodeValue') {
96
const example =
97
'\n\n ```js' +
98
'\n await result.singleNodeResult === null; // null if not present' +
99
'\n await result.singleNodeResult.textContent; // gets text' +
100
'\n ```';
101
p.overview += `\n\n\nNOTE: The returned SuperNode will behave like all AwaitedDom SuperNodes: nothing will be retrieved until you await the node or child property.
102
${example}
103
`;
104
}
105
}
106
for (const m of doc.methods) {
107
if (m.name === 'iterateNext') {
108
const example =
109
'\n\n ```js' +
110
'\n await result.iterateNext() === null; // null if not present' +
111
'\n await result.iterateNext().textContent; // gets text' +
112
'\n ```';
113
m.overview += `\n\n\nNOTE: The iterated SuperNodes will behave like all AwaitedDom SuperNodes: nothing will be retrieved until you await the node or child property.
114
${example}
115
`;
116
}
117
}
118
}
119
120
const implementedProperties = doc.properties.filter(x => x.isImplemented);
121
if (implementedProperties.length) {
122
markup.push({ h2: 'Properties' });
123
for (const p of implementedProperties) {
124
markup.push({
125
h3: `${variableName}.${p.name} <div class="specs"><i>W3C</i></div> {#${p.name}}`,
126
});
127
markup.push({ html: cleanupHTML(p.overview || 'Needs content.') });
128
markup.push({ h4: `**Type**: ${urlify(p.returnType)}` });
129
}
130
}
131
132
const implementedMethods = doc.methods.filter(x => x.isImplemented);
133
if (implementedMethods.length) {
134
markup.push({ h2: 'Methods' });
135
for (const m of implementedMethods) {
136
const args = m.parameters
137
.map((x: any) => {
138
let name = x.name;
139
if (x.isVariadic) name = `...${name}`;
140
else if (x.isOptional) name += '?';
141
return name;
142
})
143
.join(', ');
144
markup.push({
145
h3: `${variableName}.${m.name}*(${args})* <div class="specs"><i>W3C</i></div> {#${m.name}}`,
146
});
147
markup.push({ html: cleanupHTML(m.overview || 'Needs content.') });
148
if (m.parameters.length) {
149
markup.push({ h4: `**Arguments**:` });
150
151
const paramList: any[] = [];
152
markup.push({ ul: paramList });
153
for (const param of m.parameters) {
154
const name = `${param.name} ${urlify(param.type)}`;
155
if (param.overview?.startsWith('<ul')) {
156
const details = getParameterList(param.overview || 'Needs content.');
157
paramList.push(name, { ul: details });
158
} else {
159
const details = cleanupHTML(param.overview || 'Needs content.');
160
paramList.push(`${name}. ${details}`);
161
}
162
}
163
}
164
markup.push({ h4: `**Returns**: ${urlify(m.returnType)}` });
165
}
166
}
167
168
if (doc.events) {
169
markup.push({ h2: 'Events' });
170
}
171
172
const unimplementedProperties = doc.properties.filter(x => !x.isImplemented);
173
const unimplementedMethods = doc.methods.filter(x => !x.isImplemented);
174
const hasUnimplementedSpecs = unimplementedProperties.length || unimplementedMethods.length;
175
if (!doc.tags.includes('Super') && hasUnimplementedSpecs) {
176
markup.push({ h2: 'Unimplemented Specs' });
177
if (unimplementedProperties.length) {
178
markup.push({ h4: 'Properties' });
179
const rows: [string, string][] = [];
180
for (let i = 0; i < unimplementedProperties.length; i += 2) {
181
rows.push([
182
asCode(unimplementedProperties[i]?.name),
183
asCode(unimplementedProperties[i + 1]?.name),
184
]);
185
}
186
187
markup.push({
188
table: { headers: [' ', ' '], rows },
189
});
190
}
191
192
if (unimplementedMethods.length) {
193
markup.push({ h4: 'Methods' });
194
const rows: [string, string][] = [];
195
for (let i = 0; i < unimplementedMethods.length; i += 2) {
196
rows.push([
197
asCode(unimplementedMethods[i]?.name, true),
198
asCode(unimplementedMethods[i + 1]?.name, true),
199
]);
200
}
201
202
markup.push({
203
table: { headers: [' ', ' '], rows },
204
});
205
}
206
}
207
208
const markdown = json2md(markup);
209
Fs.writeFileSync(filePath, markdown);
210
console.log(`Saved ${filePath}`);
211
}
212
213
function asCode(code: string, appendMethodSignature = false) {
214
if (code) {
215
if (appendMethodSignature) return `\`${code}()\``;
216
return `\`${code}\``;
217
}
218
return '';
219
}
220
221
function urlify(type: string) {
222
// we don't list "isolates" in our docs
223
type = type.replace('Isolate', '');
224
225
const url =
226
docUrls.get(type) ??
227
docUrls.get(`Promise<${type}>`) ??
228
docUrls.get(`Promise<${type}[]>`) ??
229
docUrls.get(`${type}[]`);
230
if (url) {
231
return `[\`${type}\`](/docs/awaited-dom/${url})`;
232
}
233
return `\`${type}\``;
234
}
235
236
function getParameterList(html: string) {
237
html = cleanupHTML(html);
238
const document = domParser.parseFromString(html, 'text/html');
239
const children = [];
240
const firstList = document.querySelector('ul');
241
if (!firstList) return html;
242
for (const child of firstList.children) {
243
children.push(child.innerHTML.trim());
244
}
245
return children;
246
}
247
248
function cleanupHTML(html: string) {
249
const document = domParser.parseFromString(html, 'text/html');
250
for (const a of document.querySelectorAll('a')) {
251
const link = a as IHTMLLinkElement;
252
const outer = link.outerHTML;
253
const href = link.getAttribute('href');
254
const body = link.innerHTML;
255
const text = link.textContent;
256
if (text === 'DOMString' || text === 'USVString') html = html.replace(outer, '`string`');
257
else if (text === 'Boolean') html = html.replace(outer, '`boolean`');
258
else if (text === 'Number') html = html.replace(outer, '`number`');
259
else if (href?.startsWith('/')) {
260
if (href.includes('Document_object_model/Locating_DOM_elements_using_selectors')) {
261
link.setAttribute('target', 'mdnrel');
262
link.setAttribute('href', `https://developer.mozilla.org${href}`);
263
html = html.replace(outer, link.outerHTML);
264
} else {
265
html = html.replace(outer, body);
266
}
267
}
268
}
269
for (const tb of document.querySelectorAll('table')) {
270
const table = tb as IElement;
271
const outer = table.outerHTML;
272
html = html.replace(
273
outer,
274
`
275
<code class="language-html">
276
${outer}
277
</code>
278
279
`,
280
);
281
}
282
283
html = html.replace(/[\n\t]`+/g, '`');
284
return html.replace(/\n\n/g, '\n');
285
}
286
287
function extractLinksTable(records: any[], extractLinkFn: (record: any) => [string, string]) {
288
const cells: string[] = [];
289
290
records.forEach(record => {
291
const [linkName, linkHref] = extractLinkFn(record);
292
cells.push(`[${linkName}](${linkHref})`);
293
});
294
295
const rows: string[][] = [];
296
while (cells.length) {
297
const row = cells.splice(0, 2);
298
if (row.length < 2) row.push('');
299
rows.push(row);
300
}
301
302
return { headers: [' ', ' '], rows };
303
}
304
305