Path: blob/main/website/scripts/generateAwaitedDOM.ts
1028 views
#!/usr/bin/env ts-node12import * as Fs from 'fs';3import * as Path from 'path';4import json2md from 'json2md';5import decamelize from 'decamelize';6import rawDocs from 'awaited-dom/docs.json';7import { DOMParser } from 'noderdom-detached';8import { IElement, IHTMLLinkElement } from 'noderdom-detached/base/interfaces';910const docs = (rawDocs as unknown) as IDoc[];1112const domParser = new DOMParser();1314interface IDoc {15name: string;16variableName: string;17category: string;18tags: string;19overview: string;20dependencies: any[];21properties: any[];22methods: any[];23events: any[];24}2526const docUrls = new Map<string, string>();27for (const doc of docs) {28docUrls.set(doc.name, decamelize(doc.name, '-'));29}3031json2md.converters.html = input => input;3233const awaitedDomDir = Path.resolve(__dirname, '../awaited-dom');34const docsDir = Path.resolve(__dirname, '../docs');35const awaitedDOMBasePath = Path.join(docsDir, 'BasicInterfaces', 'AwaitedDOM.base.md');36const awaitedDOMIndexPath1 = Path.join(docsDir, 'BasicInterfaces', 'AwaitedDOM.md');37const awaitedDOMIndexPath2 = Path.join(awaitedDomDir, 'index.md');3839const docsByTag: { [tag: string]: IDoc[] } = {};4041docs.forEach((doc: IDoc) => {42const filepath = Path.join(awaitedDomDir, `${doc.name}.md`);43const tags = doc.tags.split(',').filter(t => t);44tags.forEach(tag => {45docsByTag[tag] = docsByTag[tag] || [];46docsByTag[tag].push(doc);47});48saveDoc(doc, filepath);49});5051let awaitedDOMBase = Fs.readFileSync(awaitedDOMBasePath, 'utf-8');5253Object.keys(docsByTag).forEach(tag => {54const placementToken = `[INTERFACES:${tag}]`;55if (!awaitedDOMBase.includes(placementToken)) return;5657const linksTable = extractLinksTable(docsByTag[tag], (doc: IDoc) => {58return [doc.name, `/docs/awaited-dom/${decamelize(doc.name, '-')}`];59});6061const markup = [{ table: linksTable }];62const markdown = json2md(markup);63awaitedDOMBase = awaitedDOMBase.replace(placementToken, markdown);64});6566Fs.writeFileSync(awaitedDOMIndexPath1, awaitedDOMBase);67Fs.writeFileSync(awaitedDOMIndexPath2, awaitedDOMBase);6869// SAVE DOC7071function saveDoc(doc: IDoc, filePath: string) {72const markup: any[] = [73{ h1: `[AwaitedDOM](/docs/basic-interfaces/awaited-dom) <span>/</span> ${doc.name}` },74];75const variableName = doc.variableName || '';7677JSON.parse(doc.overview || '[]').forEach((overview: any) => {78markup.push({ html: `<div class='overview'>${cleanupHTML(overview)}</div>` });79});8081if (doc.tags.includes('Super') && doc.dependencies.length) {82markup.push({ h2: 'Dependencies' });83markup.push({84p: `${doc.name} implements all the properties and methods of the following classes:`,85});86const linksTable = extractLinksTable(doc.dependencies, (dep: any) => {87return [dep.name, `./${decamelize(dep.name, '-')}`];88});89markup.push({ table: linksTable });90}9192if (doc.name === 'XPathResult') {93for (const p of doc.properties) {94if (p.name === 'singleNodeValue') {95const example =96'\n\n ```js' +97'\n await result.singleNodeResult === null; // null if not present' +98'\n await result.singleNodeResult.textContent; // gets text' +99'\n ```';100p.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.101${example}102`;103}104}105for (const m of doc.methods) {106if (m.name === 'iterateNext') {107const example =108'\n\n ```js' +109'\n await result.iterateNext() === null; // null if not present' +110'\n await result.iterateNext().textContent; // gets text' +111'\n ```';112m.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.113${example}114`;115}116}117}118119const implementedProperties = doc.properties.filter(x => x.isImplemented);120if (implementedProperties.length) {121markup.push({ h2: 'Properties' });122for (const p of implementedProperties) {123markup.push({124h3: `${variableName}.${p.name} <div class="specs"><i>W3C</i></div> {#${p.name}}`,125});126markup.push({ html: cleanupHTML(p.overview || 'Needs content.') });127markup.push({ h4: `**Type**: ${urlify(p.returnType)}` });128}129}130131const implementedMethods = doc.methods.filter(x => x.isImplemented);132if (implementedMethods.length) {133markup.push({ h2: 'Methods' });134for (const m of implementedMethods) {135const args = m.parameters136.map((x: any) => {137let name = x.name;138if (x.isVariadic) name = `...${name}`;139else if (x.isOptional) name += '?';140return name;141})142.join(', ');143markup.push({144h3: `${variableName}.${m.name}*(${args})* <div class="specs"><i>W3C</i></div> {#${m.name}}`,145});146markup.push({ html: cleanupHTML(m.overview || 'Needs content.') });147if (m.parameters.length) {148markup.push({ h4: `**Arguments**:` });149150const paramList: any[] = [];151markup.push({ ul: paramList });152for (const param of m.parameters) {153const name = `${param.name} ${urlify(param.type)}`;154if (param.overview?.startsWith('<ul')) {155const details = getParameterList(param.overview || 'Needs content.');156paramList.push(name, { ul: details });157} else {158const details = cleanupHTML(param.overview || 'Needs content.');159paramList.push(`${name}. ${details}`);160}161}162}163markup.push({ h4: `**Returns**: ${urlify(m.returnType)}` });164}165}166167if (doc.events) {168markup.push({ h2: 'Events' });169}170171const unimplementedProperties = doc.properties.filter(x => !x.isImplemented);172const unimplementedMethods = doc.methods.filter(x => !x.isImplemented);173const hasUnimplementedSpecs = unimplementedProperties.length || unimplementedMethods.length;174if (!doc.tags.includes('Super') && hasUnimplementedSpecs) {175markup.push({ h2: 'Unimplemented Specs' });176if (unimplementedProperties.length) {177markup.push({ h4: 'Properties' });178const rows: [string, string][] = [];179for (let i = 0; i < unimplementedProperties.length; i += 2) {180rows.push([181asCode(unimplementedProperties[i]?.name),182asCode(unimplementedProperties[i + 1]?.name),183]);184}185186markup.push({187table: { headers: [' ', ' '], rows },188});189}190191if (unimplementedMethods.length) {192markup.push({ h4: 'Methods' });193const rows: [string, string][] = [];194for (let i = 0; i < unimplementedMethods.length; i += 2) {195rows.push([196asCode(unimplementedMethods[i]?.name, true),197asCode(unimplementedMethods[i + 1]?.name, true),198]);199}200201markup.push({202table: { headers: [' ', ' '], rows },203});204}205}206207const markdown = json2md(markup);208Fs.writeFileSync(filePath, markdown);209console.log(`Saved ${filePath}`);210}211212function asCode(code: string, appendMethodSignature = false) {213if (code) {214if (appendMethodSignature) return `\`${code}()\``;215return `\`${code}\``;216}217return '';218}219220function urlify(type: string) {221// we don't list "isolates" in our docs222type = type.replace('Isolate', '');223224const url =225docUrls.get(type) ??226docUrls.get(`Promise<${type}>`) ??227docUrls.get(`Promise<${type}[]>`) ??228docUrls.get(`${type}[]`);229if (url) {230return `[\`${type}\`](/docs/awaited-dom/${url})`;231}232return `\`${type}\``;233}234235function getParameterList(html: string) {236html = cleanupHTML(html);237const document = domParser.parseFromString(html, 'text/html');238const children = [];239const firstList = document.querySelector('ul');240if (!firstList) return html;241for (const child of firstList.children) {242children.push(child.innerHTML.trim());243}244return children;245}246247function cleanupHTML(html: string) {248const document = domParser.parseFromString(html, 'text/html');249for (const a of document.querySelectorAll('a')) {250const link = a as IHTMLLinkElement;251const outer = link.outerHTML;252const href = link.getAttribute('href');253const body = link.innerHTML;254const text = link.textContent;255if (text === 'DOMString' || text === 'USVString') html = html.replace(outer, '`string`');256else if (text === 'Boolean') html = html.replace(outer, '`boolean`');257else if (text === 'Number') html = html.replace(outer, '`number`');258else if (href?.startsWith('/')) {259if (href.includes('Document_object_model/Locating_DOM_elements_using_selectors')) {260link.setAttribute('target', 'mdnrel');261link.setAttribute('href', `https://developer.mozilla.org${href}`);262html = html.replace(outer, link.outerHTML);263} else {264html = html.replace(outer, body);265}266}267}268for (const tb of document.querySelectorAll('table')) {269const table = tb as IElement;270const outer = table.outerHTML;271html = html.replace(272outer,273`274<code class="language-html">275${outer}276</code>277278`,279);280}281282html = html.replace(/[\n\t]`+/g, '`');283return html.replace(/\n\n/g, '\n');284}285286function extractLinksTable(records: any[], extractLinkFn: (record: any) => [string, string]) {287const cells: string[] = [];288289records.forEach(record => {290const [linkName, linkHref] = extractLinkFn(record);291cells.push(`[${linkName}](${linkHref})`);292});293294const rows: string[][] = [];295while (cells.length) {296const row = cells.splice(0, 2);297if (row.length < 2) row.push('');298rows.push(row);299}300301return { headers: [' ', ' '], rows };302}303304305