@openuji/speculator 0.1.0 → 0.2.0
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/README.md +15 -0
- package/dist/browser.cjs +908 -0
- package/dist/browser.d.cts +238 -0
- package/dist/browser.d.ts +238 -0
- package/dist/browser.js +26 -0
- package/dist/chunk-JAR5PGCK.js +862 -0
- package/dist/node.cjs +930 -0
- package/dist/node.d.cts +17 -0
- package/dist/node.d.ts +17 -0
- package/dist/node.js +47 -0
- package/package.json +28 -8
- package/dist/index.d.ts +0 -6
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -17
- package/dist/index.js.map +0 -1
- package/dist/markdown/index.d.ts +0 -5
- package/dist/markdown/index.d.ts.map +0 -1
- package/dist/markdown/index.js +0 -70
- package/dist/markdown/index.js.map +0 -1
- package/dist/markdown/plugins/cite.d.ts +0 -3
- package/dist/markdown/plugins/cite.d.ts.map +0 -1
- package/dist/markdown/plugins/cite.js +0 -36
- package/dist/markdown/plugins/cite.js.map +0 -1
- package/dist/markdown/plugins/concept.d.ts +0 -3
- package/dist/markdown/plugins/concept.d.ts.map +0 -1
- package/dist/markdown/plugins/concept.js +0 -30
- package/dist/markdown/plugins/concept.js.map +0 -1
- package/dist/markdown/plugins/idl.d.ts +0 -3
- package/dist/markdown/plugins/idl.d.ts.map +0 -1
- package/dist/markdown/plugins/idl.js +0 -28
- package/dist/markdown/plugins/idl.js.map +0 -1
- package/dist/pipeline/passes/boilerplate.d.ts +0 -3
- package/dist/pipeline/passes/boilerplate.d.ts.map +0 -1
- package/dist/pipeline/passes/boilerplate.js +0 -59
- package/dist/pipeline/passes/boilerplate.js.map +0 -1
- package/dist/pipeline/passes/diagnostics.d.ts +0 -3
- package/dist/pipeline/passes/diagnostics.d.ts.map +0 -1
- package/dist/pipeline/passes/diagnostics.js +0 -43
- package/dist/pipeline/passes/diagnostics.js.map +0 -1
- package/dist/pipeline/passes/idl.d.ts +0 -4
- package/dist/pipeline/passes/idl.d.ts.map +0 -1
- package/dist/pipeline/passes/idl.js +0 -188
- package/dist/pipeline/passes/idl.js.map +0 -1
- package/dist/pipeline/passes/references.d.ts +0 -3
- package/dist/pipeline/passes/references.d.ts.map +0 -1
- package/dist/pipeline/passes/references.js +0 -99
- package/dist/pipeline/passes/references.js.map +0 -1
- package/dist/pipeline/passes/toc.d.ts +0 -3
- package/dist/pipeline/passes/toc.d.ts.map +0 -1
- package/dist/pipeline/passes/toc.js +0 -30
- package/dist/pipeline/passes/toc.js.map +0 -1
- package/dist/pipeline/passes/xref.d.ts +0 -3
- package/dist/pipeline/passes/xref.d.ts.map +0 -1
- package/dist/pipeline/passes/xref.js +0 -132
- package/dist/pipeline/passes/xref.js.map +0 -1
- package/dist/pipeline/postprocess.d.ts +0 -6
- package/dist/pipeline/postprocess.d.ts.map +0 -1
- package/dist/pipeline/postprocess.js +0 -26
- package/dist/pipeline/postprocess.js.map +0 -1
- package/dist/speculator.d.ts +0 -40
- package/dist/speculator.d.ts.map +0 -1
- package/dist/speculator.js +0 -188
- package/dist/speculator.js.map +0 -1
- package/dist/types.d.ts +0 -146
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -16
- package/dist/types.js.map +0 -1
- package/dist/utils/file-loader.d.ts +0 -18
- package/dist/utils/file-loader.d.ts.map +0 -1
- package/dist/utils/file-loader.js +0 -100
- package/dist/utils/file-loader.js.map +0 -1
- package/dist/utils/strip-ident.d.ts +0 -2
- package/dist/utils/strip-ident.d.ts.map +0 -1
- package/dist/utils/strip-ident.js +0 -15
- package/dist/utils/strip-ident.js.map +0 -1
package/dist/node.cjs
ADDED
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/node.ts
|
|
31
|
+
var node_exports = {};
|
|
32
|
+
__export(node_exports, {
|
|
33
|
+
DOMHtmlRenderer: () => DOMHtmlRenderer,
|
|
34
|
+
FormatProcessor: () => FormatProcessor,
|
|
35
|
+
IncludeProcessor: () => IncludeProcessor,
|
|
36
|
+
LinkedomHtmlRenderer: () => LinkedomHtmlRenderer,
|
|
37
|
+
Speculator: () => Speculator2,
|
|
38
|
+
SpeculatorError: () => SpeculatorError,
|
|
39
|
+
browserFileLoader: () => browserFileLoader,
|
|
40
|
+
createFallbackFileLoader: () => createFallbackFileLoader,
|
|
41
|
+
createMarkdownRenderer: () => createMarkdownRenderer,
|
|
42
|
+
getDefaultFileLoader: () => getDefaultFileLoader,
|
|
43
|
+
nodeFileLoader: () => nodeFileLoader,
|
|
44
|
+
parseMarkdown: () => parseMarkdown
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(node_exports);
|
|
47
|
+
|
|
48
|
+
// src/utils/file-loader.ts
|
|
49
|
+
var nodeFileLoader = async (path) => {
|
|
50
|
+
const fs = await import("fs/promises");
|
|
51
|
+
const { fileURLToPath } = await import("url");
|
|
52
|
+
const urlPath = fileURLToPath(path);
|
|
53
|
+
try {
|
|
54
|
+
return await fs.readFile(urlPath, "utf-8");
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new Error(`Failed to load file: ${path}. ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var browserFileLoader = async (path) => {
|
|
60
|
+
try {
|
|
61
|
+
const response = await fetch(path);
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
return await response.text();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw new Error(`Failed to fetch file: ${path}. ${error instanceof Error ? error.message : "Network error"}`);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function createFallbackFileLoader(loaders) {
|
|
71
|
+
return async (path) => {
|
|
72
|
+
let lastError;
|
|
73
|
+
for (const loader of loaders) {
|
|
74
|
+
try {
|
|
75
|
+
return await loader(path);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
throw lastError || new Error(`All file loaders failed for: ${path}`);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function getDefaultFileLoader() {
|
|
85
|
+
if (typeof window !== "undefined" && typeof fetch !== "undefined") {
|
|
86
|
+
return browserFileLoader;
|
|
87
|
+
} else if (typeof process !== "undefined" && process.versions?.node) {
|
|
88
|
+
return nodeFileLoader;
|
|
89
|
+
} else {
|
|
90
|
+
throw new Error("Unable to detect environment for file loading. Please provide a custom fileLoader.");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/types.ts
|
|
95
|
+
var SpeculatorError = class extends Error {
|
|
96
|
+
constructor(message, element, path) {
|
|
97
|
+
super(message);
|
|
98
|
+
this.element = element;
|
|
99
|
+
this.path = path;
|
|
100
|
+
this.name = "SpeculatorError";
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// src/pipeline/postprocess.ts
|
|
105
|
+
async function postprocess(root, passes, options = {}) {
|
|
106
|
+
const warnings = [];
|
|
107
|
+
for (const pass of passes) {
|
|
108
|
+
const result = await pass.run(root, options);
|
|
109
|
+
if (result && result.length) {
|
|
110
|
+
warnings.push(...result);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { warnings };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/processors/include-processor.ts
|
|
117
|
+
var IncludeProcessor = class {
|
|
118
|
+
constructor(baseUrl, fileLoader, formatProcessor) {
|
|
119
|
+
this.baseUrl = baseUrl;
|
|
120
|
+
this.fileLoader = fileLoader;
|
|
121
|
+
this.formatProcessor = formatProcessor;
|
|
122
|
+
}
|
|
123
|
+
async process(element, stats, warnings) {
|
|
124
|
+
const includePath = element.getAttribute("data-include");
|
|
125
|
+
const includeFormat = element.getAttribute("data-include-format") || "text";
|
|
126
|
+
if (!includePath) {
|
|
127
|
+
warnings.push("data-include attribute is empty");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const fullPath = this.resolveFilePath(includePath);
|
|
132
|
+
const content = await this.fileLoader(fullPath);
|
|
133
|
+
const processedContent = this.formatProcessor.processContent(content, includeFormat);
|
|
134
|
+
element.innerHTML = processedContent;
|
|
135
|
+
stats.filesIncluded++;
|
|
136
|
+
if (includeFormat === "markdown") {
|
|
137
|
+
stats.markdownBlocks++;
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const errorMsg = `Failed to load: ${includePath}`;
|
|
141
|
+
warnings.push(errorMsg);
|
|
142
|
+
element.innerHTML = `<p class="error">${errorMsg}</p>`;
|
|
143
|
+
throw new SpeculatorError(errorMsg, element, includePath);
|
|
144
|
+
}
|
|
145
|
+
element.removeAttribute("data-include");
|
|
146
|
+
element.removeAttribute("data-include-format");
|
|
147
|
+
}
|
|
148
|
+
resolveFilePath(path) {
|
|
149
|
+
const filePath = new URL(path, this.baseUrl || "file:///").toString();
|
|
150
|
+
console.log(`Resolved file path: ${filePath}`);
|
|
151
|
+
return filePath;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/markdown/index.ts
|
|
156
|
+
var import_markdown_it = __toESM(require("markdown-it"), 1);
|
|
157
|
+
|
|
158
|
+
// src/markdown/plugins/concept.ts
|
|
159
|
+
function respecConceptPlugin(md) {
|
|
160
|
+
const NAME = "respec-concept";
|
|
161
|
+
function tokenize(state, silent) {
|
|
162
|
+
const pos = state.pos;
|
|
163
|
+
const src = state.src;
|
|
164
|
+
if (src.charCodeAt(pos) !== 91 || src.charCodeAt(pos + 1) !== 61) return false;
|
|
165
|
+
const end = src.indexOf("=]", pos + 2);
|
|
166
|
+
if (end < 0) return false;
|
|
167
|
+
if (!silent) {
|
|
168
|
+
const content = src.slice(pos + 2, end).trim();
|
|
169
|
+
const tokenOpen = state.push("respec_concept_open", "a", 1);
|
|
170
|
+
tokenOpen.attrSet("data-xref", content);
|
|
171
|
+
const text = state.push("text", "", 0);
|
|
172
|
+
text.content = content;
|
|
173
|
+
state.push("respec_concept_close", "a", -1);
|
|
174
|
+
}
|
|
175
|
+
state.pos = end + 2;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
md.inline.ruler.before("link", NAME, tokenize);
|
|
179
|
+
md.renderer.rules.respec_concept_open = (tokens, idx) => `<a data-xref="${md.utils.escapeHtml(tokens[idx].attrGet("data-xref") || "")}">`;
|
|
180
|
+
md.renderer.rules.respec_concept_close = () => `</a>`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/markdown/plugins/idl.ts
|
|
184
|
+
function respecIdlPlugin(md) {
|
|
185
|
+
const NAME = "respec-idl";
|
|
186
|
+
function tokenize(state, silent) {
|
|
187
|
+
const pos = state.pos;
|
|
188
|
+
const src = state.src;
|
|
189
|
+
if (src.charCodeAt(pos) !== 123 || src.charCodeAt(pos + 1) !== 123) return false;
|
|
190
|
+
const end = src.indexOf("}}", pos + 2);
|
|
191
|
+
if (end < 0) return false;
|
|
192
|
+
if (!silent) {
|
|
193
|
+
const content = src.slice(pos + 2, end).trim();
|
|
194
|
+
const open = state.push("link_open", "a", 1);
|
|
195
|
+
open.attrSet("data-idl", content);
|
|
196
|
+
const text = state.push("text", "", 0);
|
|
197
|
+
text.content = content;
|
|
198
|
+
state.push("link_close", "a", -1);
|
|
199
|
+
}
|
|
200
|
+
state.pos = end + 2;
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
md.inline.ruler.before("emphasis", NAME, tokenize);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/markdown/plugins/cite.ts
|
|
207
|
+
function respecCitePlugin(md) {
|
|
208
|
+
const NAME = "respec-cite";
|
|
209
|
+
function tokenize(state, silent) {
|
|
210
|
+
const pos = state.pos;
|
|
211
|
+
const src = state.src;
|
|
212
|
+
if (src.charCodeAt(pos) !== 91 || src.charCodeAt(pos + 1) !== 91) return false;
|
|
213
|
+
const end = src.indexOf("]]", pos + 2);
|
|
214
|
+
if (end < 0) return false;
|
|
215
|
+
if (!silent) {
|
|
216
|
+
const raw = src.slice(pos + 2, end).trim();
|
|
217
|
+
const normative = raw.startsWith("!");
|
|
218
|
+
const id = normative ? raw.slice(1) : raw;
|
|
219
|
+
const env = state.env;
|
|
220
|
+
const col = env.__citations || (env.__citations = []);
|
|
221
|
+
col.push({ id, normative });
|
|
222
|
+
const open = state.push("link_open", "a", 1);
|
|
223
|
+
open.attrSet("data-spec", id);
|
|
224
|
+
open.attrSet("data-normative", String(normative));
|
|
225
|
+
const text = state.push("text", "", 0);
|
|
226
|
+
text.content = `[${id}]`;
|
|
227
|
+
state.push("link_close", "a", -1);
|
|
228
|
+
}
|
|
229
|
+
state.pos = end + 2;
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
md.inline.ruler.before("link", NAME, tokenize);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/markdown/index.ts
|
|
236
|
+
function createMarkdownRenderer(options = {}) {
|
|
237
|
+
const md = new import_markdown_it.default({
|
|
238
|
+
html: false,
|
|
239
|
+
linkify: options.gfm ?? true,
|
|
240
|
+
breaks: options.breaks ?? true,
|
|
241
|
+
typographer: options.smartypants ?? true,
|
|
242
|
+
xhtmlOut: false
|
|
243
|
+
});
|
|
244
|
+
const headerIds = options.headerIds ?? true;
|
|
245
|
+
md.renderer.rules.heading_open = (tokens, idx, _opts, env, self) => {
|
|
246
|
+
void env;
|
|
247
|
+
const open = tokens[idx];
|
|
248
|
+
const inline = tokens[idx + 1];
|
|
249
|
+
const close = tokens[idx + 2];
|
|
250
|
+
if (open.tag === "h1") {
|
|
251
|
+
open.tag = "h2";
|
|
252
|
+
if (close && close.type === "heading_close") close.tag = "h2";
|
|
253
|
+
}
|
|
254
|
+
if (headerIds && inline && inline.type === "inline") {
|
|
255
|
+
const id = slugify(inline.content);
|
|
256
|
+
open.attrSet("id", id);
|
|
257
|
+
}
|
|
258
|
+
return self.renderToken(tokens, idx, _opts);
|
|
259
|
+
};
|
|
260
|
+
md.renderer.rules.fence = (tokens, idx) => {
|
|
261
|
+
const token = tokens[idx];
|
|
262
|
+
const info = (token.info || "").trim();
|
|
263
|
+
const lang = info ? info.split(/\s+/)[0] : "";
|
|
264
|
+
const classAttr = lang ? ` class="${md.utils.escapeHtml(lang)}"` : "";
|
|
265
|
+
const code = md.utils.escapeHtml(token.content);
|
|
266
|
+
return `<pre${classAttr}><code>${code}</code></pre>
|
|
267
|
+
`;
|
|
268
|
+
};
|
|
269
|
+
md.use(respecConceptPlugin);
|
|
270
|
+
md.use(respecIdlPlugin);
|
|
271
|
+
md.use(respecCitePlugin);
|
|
272
|
+
for (const extension of options.extensions ?? []) {
|
|
273
|
+
if (Array.isArray(extension)) {
|
|
274
|
+
const [plugin, pluginOptions] = extension;
|
|
275
|
+
md.use(plugin, pluginOptions);
|
|
276
|
+
} else {
|
|
277
|
+
md.use(extension);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return md;
|
|
281
|
+
}
|
|
282
|
+
function parseMarkdown(markdown, options = {}, env) {
|
|
283
|
+
try {
|
|
284
|
+
const md = createMarkdownRenderer(options);
|
|
285
|
+
return md.render(markdown, env);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error("Markdown parsing error:", error);
|
|
288
|
+
return `<p class="error">Error parsing markdown: ${error instanceof Error ? error.message : "Unknown error"}</p>`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function slugify(content) {
|
|
292
|
+
return content.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/utils/strip-ident.ts
|
|
296
|
+
function stripIndent(content) {
|
|
297
|
+
const lines = content.replace(/\r\n?/g, "\n").split("\n");
|
|
298
|
+
const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.match(/^\s*/)?.[0].length || 0);
|
|
299
|
+
const minIndent = indents.length > 0 ? Math.min(...indents) : 0;
|
|
300
|
+
return lines.map((line) => line.slice(minIndent)).join("\n");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/processors/format-processor.ts
|
|
304
|
+
var FormatProcessor = class {
|
|
305
|
+
constructor(markdownOptions = {}) {
|
|
306
|
+
this.markdownOptions = markdownOptions;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Convert content based on the specified format.
|
|
310
|
+
*/
|
|
311
|
+
processContent(content, format) {
|
|
312
|
+
switch (format) {
|
|
313
|
+
case "markdown":
|
|
314
|
+
return parseMarkdown(content, this.markdownOptions);
|
|
315
|
+
case "text":
|
|
316
|
+
case "html":
|
|
317
|
+
default:
|
|
318
|
+
return content;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Process an element with a data-format attribute.
|
|
323
|
+
*/
|
|
324
|
+
process(element, stats, warnings) {
|
|
325
|
+
const format = element.getAttribute("data-format");
|
|
326
|
+
if (format === "markdown" && element.innerHTML.trim()) {
|
|
327
|
+
try {
|
|
328
|
+
const markdownContent = stripIndent(element.innerHTML).trim();
|
|
329
|
+
element.innerHTML = this.processContent(markdownContent, format);
|
|
330
|
+
stats.markdownBlocks++;
|
|
331
|
+
} catch (error) {
|
|
332
|
+
const errorMsg = `Failed to process markdown: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
333
|
+
warnings.push(errorMsg);
|
|
334
|
+
element.innerHTML = `<p class="error">${errorMsg}</p>`;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
element.removeAttribute("data-format");
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// src/html-renderer.ts
|
|
342
|
+
var DOMHtmlRenderer = class {
|
|
343
|
+
parse(html) {
|
|
344
|
+
if (typeof DOMParser === "undefined") {
|
|
345
|
+
throw new SpeculatorError("DOMParser not available. This method requires a browser environment or jsdom.");
|
|
346
|
+
}
|
|
347
|
+
const parser = new DOMParser();
|
|
348
|
+
const doc = parser.parseFromString(`<div>${html}</div>`, "text/html");
|
|
349
|
+
return doc.body.firstElementChild;
|
|
350
|
+
}
|
|
351
|
+
serialize(element) {
|
|
352
|
+
return element.innerHTML;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// src/pipeline/passes/idl.ts
|
|
357
|
+
var WebIDL2 = __toESM(require("webidl2"), 1);
|
|
358
|
+
function norm(s) {
|
|
359
|
+
return s.toLowerCase().replace(/\s+/g, " ").trim();
|
|
360
|
+
}
|
|
361
|
+
function slug(s) {
|
|
362
|
+
return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
363
|
+
}
|
|
364
|
+
function uniqueId(doc, base) {
|
|
365
|
+
let id = base;
|
|
366
|
+
let i = 2;
|
|
367
|
+
while (doc.getElementById(id)) {
|
|
368
|
+
id = `${base}-${i++}`;
|
|
369
|
+
}
|
|
370
|
+
return id;
|
|
371
|
+
}
|
|
372
|
+
function collectTargetsFromAst(ast) {
|
|
373
|
+
const defs = [];
|
|
374
|
+
const addTop = (name) => {
|
|
375
|
+
const key = norm(name);
|
|
376
|
+
defs.push({ id: `idl-${slug(name)}`, key, text: name });
|
|
377
|
+
};
|
|
378
|
+
const addMember = (ifc, mem) => {
|
|
379
|
+
const key = `${norm(ifc)}.${norm(mem)}`;
|
|
380
|
+
defs.push({ id: `idl-${slug(ifc)}-${slug(mem)}`, key, text: `${ifc}.${mem}` });
|
|
381
|
+
};
|
|
382
|
+
for (const d of ast) {
|
|
383
|
+
switch (d.type) {
|
|
384
|
+
case "interface":
|
|
385
|
+
case "interface mixin":
|
|
386
|
+
case "namespace":
|
|
387
|
+
case "dictionary":
|
|
388
|
+
case "callback interface":
|
|
389
|
+
case "callback":
|
|
390
|
+
case "typedef":
|
|
391
|
+
case "enum": {
|
|
392
|
+
if (!("name" in d) || !d.name) break;
|
|
393
|
+
addTop(d.name);
|
|
394
|
+
if ("members" in d && Array.isArray(d.members)) {
|
|
395
|
+
for (const m of d.members) {
|
|
396
|
+
if (m.type === "attribute" && m.name) addMember(d.name, m.name);
|
|
397
|
+
else if (m.type === "operation" && m.name) addMember(d.name, m.name);
|
|
398
|
+
else if (m.type === "const" && m.name) addMember(d.name, m.name);
|
|
399
|
+
else if (m.type === "field" && m.name) addMember(d.name, m.name);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return { defs };
|
|
407
|
+
}
|
|
408
|
+
function insertAnchorsBefore(pre, targets) {
|
|
409
|
+
if (!targets.length) return;
|
|
410
|
+
const doc = pre.ownerDocument;
|
|
411
|
+
const wrapper = doc.createElement("div");
|
|
412
|
+
wrapper.className = "idl-anchors";
|
|
413
|
+
wrapper.hidden = true;
|
|
414
|
+
for (const t of targets) {
|
|
415
|
+
const a = doc.createElement("a");
|
|
416
|
+
a.id = uniqueId(doc, t.id);
|
|
417
|
+
a.textContent = t.text;
|
|
418
|
+
wrapper.appendChild(a);
|
|
419
|
+
}
|
|
420
|
+
pre.parentNode?.insertBefore(wrapper, pre);
|
|
421
|
+
}
|
|
422
|
+
function parseIdlToTargets(idl) {
|
|
423
|
+
const ast = WebIDL2.parse(idl);
|
|
424
|
+
return collectTargetsFromAst(ast).defs;
|
|
425
|
+
}
|
|
426
|
+
function findIdlBlocks(root) {
|
|
427
|
+
const blocks = [];
|
|
428
|
+
const pres = root.querySelectorAll("pre");
|
|
429
|
+
pres.forEach((pre) => {
|
|
430
|
+
const cls = (pre.getAttribute("class") || "").toLowerCase();
|
|
431
|
+
const looksIdlClass = /\b(idl|language-idl)\b/.test(cls);
|
|
432
|
+
const code = pre.querySelector("code");
|
|
433
|
+
const text = (code ? code.textContent : pre.textContent) || "";
|
|
434
|
+
const maybeIdl = looksIdlClass || /^\s*(interface|dictionary|enum|namespace|callback|typedef)\b/.test(text);
|
|
435
|
+
if (maybeIdl && text.trim()) blocks.push(pre);
|
|
436
|
+
});
|
|
437
|
+
return blocks;
|
|
438
|
+
}
|
|
439
|
+
function buildIdlIndex(root, warnings) {
|
|
440
|
+
const map = /* @__PURE__ */ new Map();
|
|
441
|
+
const blocks = findIdlBlocks(root);
|
|
442
|
+
for (const pre of blocks) {
|
|
443
|
+
const code = pre.querySelector("code");
|
|
444
|
+
const idl = (code ? code.textContent : pre.textContent) || "";
|
|
445
|
+
try {
|
|
446
|
+
const targets = parseIdlToTargets(idl);
|
|
447
|
+
insertAnchorsBefore(pre, targets);
|
|
448
|
+
for (const t of targets) {
|
|
449
|
+
const href = `#${t.id}`;
|
|
450
|
+
if (!map.has(t.key)) map.set(t.key, href);
|
|
451
|
+
}
|
|
452
|
+
} catch (e) {
|
|
453
|
+
const msg = e && e.message ? e.message : String(e);
|
|
454
|
+
warnings.push(`IDL parse error: ${msg}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return map;
|
|
458
|
+
}
|
|
459
|
+
function resolveIdlLinks(root, index, warnings, suppressClass) {
|
|
460
|
+
const anchors = root.querySelectorAll("a[data-idl]");
|
|
461
|
+
Array.from(anchors).forEach((a) => {
|
|
462
|
+
if (a.closest(`.${suppressClass}`)) return;
|
|
463
|
+
const raw = a.getAttribute("data-idl") || "";
|
|
464
|
+
const term = raw.trim();
|
|
465
|
+
const hasMember = term.includes(".");
|
|
466
|
+
let key = "";
|
|
467
|
+
if (hasMember) {
|
|
468
|
+
const [iface, member] = term.split(".", 2);
|
|
469
|
+
key = `${norm(iface)}.${norm(member)}`;
|
|
470
|
+
} else {
|
|
471
|
+
key = norm(term);
|
|
472
|
+
}
|
|
473
|
+
const href = index.get(key);
|
|
474
|
+
if (href) {
|
|
475
|
+
a.setAttribute("href", href);
|
|
476
|
+
} else {
|
|
477
|
+
warnings.push(`Unresolved IDL link: "${term}"`);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
var idlPass = {
|
|
482
|
+
async run(root, options) {
|
|
483
|
+
const warnings = [];
|
|
484
|
+
const suppressClass = options.diagnostics?.suppressClass ?? "no-link-warnings";
|
|
485
|
+
const index = buildIdlIndex(root, warnings);
|
|
486
|
+
resolveIdlLinks(root, index, warnings, suppressClass);
|
|
487
|
+
return warnings;
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/pipeline/passes/xref.ts
|
|
492
|
+
function uniqueId2(doc, base) {
|
|
493
|
+
let id = base;
|
|
494
|
+
let i = 2;
|
|
495
|
+
while (doc.getElementById(id)) id = `${base}-${i++}`;
|
|
496
|
+
return id;
|
|
497
|
+
}
|
|
498
|
+
function norm2(term) {
|
|
499
|
+
return term.toLowerCase().replace(/\s+/g, " ").trim();
|
|
500
|
+
}
|
|
501
|
+
function slugify2(text) {
|
|
502
|
+
return text.trim().toLowerCase().replace(/[^\w]+/g, "-").replace(/^-+|-+$/g, "");
|
|
503
|
+
}
|
|
504
|
+
function isSuppressed(node, suppressClass) {
|
|
505
|
+
return !!node.closest(`.${suppressClass}`);
|
|
506
|
+
}
|
|
507
|
+
function buildLocalMap(root) {
|
|
508
|
+
const map = /* @__PURE__ */ new Map();
|
|
509
|
+
const doc = root.ownerDocument;
|
|
510
|
+
const dfns = root.querySelectorAll("dfn");
|
|
511
|
+
dfns.forEach((dfn) => {
|
|
512
|
+
const text = (dfn.getAttribute("data-lt") || dfn.textContent || "").trim();
|
|
513
|
+
if (!text) return;
|
|
514
|
+
if (!dfn.id) {
|
|
515
|
+
const first = (text.split(/[|,]/)[0] || dfn.textContent || "").trim();
|
|
516
|
+
dfn.id = slugify2(first);
|
|
517
|
+
}
|
|
518
|
+
const href = `#${dfn.id}`;
|
|
519
|
+
const variants = text.split(/[|,]/).map((s) => s.trim()).filter(Boolean);
|
|
520
|
+
for (const v of variants) {
|
|
521
|
+
const key = norm2(v);
|
|
522
|
+
if (!map.has(key)) {
|
|
523
|
+
map.set(key, { href, text: v, source: "dfn" });
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
const headings = root.querySelectorAll("h2, h3, h4, h5, h6");
|
|
528
|
+
headings.forEach((h) => {
|
|
529
|
+
const label = (h.textContent || "").trim();
|
|
530
|
+
if (!label) return;
|
|
531
|
+
const parentSection = h.closest("section[id]");
|
|
532
|
+
let targetId = null;
|
|
533
|
+
if (parentSection) {
|
|
534
|
+
targetId = parentSection.id;
|
|
535
|
+
} else if (h.id) {
|
|
536
|
+
targetId = h.id;
|
|
537
|
+
} else {
|
|
538
|
+
const slug2 = slugify2(label);
|
|
539
|
+
const uid = uniqueId2(doc, slug2);
|
|
540
|
+
h.id = uid;
|
|
541
|
+
targetId = uid;
|
|
542
|
+
}
|
|
543
|
+
if (!targetId) return;
|
|
544
|
+
const key = norm2(label);
|
|
545
|
+
if (!map.has(key)) {
|
|
546
|
+
map.set(key, { href: `#${targetId}`, text: label, source: "heading" });
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
return map;
|
|
550
|
+
}
|
|
551
|
+
var xrefPass = {
|
|
552
|
+
async run(root, options) {
|
|
553
|
+
const suppressClass = options.diagnostics?.suppressClass ?? "no-link-warnings";
|
|
554
|
+
const warnings = [];
|
|
555
|
+
const localMap = buildLocalMap(root);
|
|
556
|
+
const xrefAnchors = Array.from(root.querySelectorAll("a[data-xref]"));
|
|
557
|
+
const unresolved = /* @__PURE__ */ new Map();
|
|
558
|
+
for (const a of xrefAnchors) {
|
|
559
|
+
if (isSuppressed(a, suppressClass)) continue;
|
|
560
|
+
const term = a.getAttribute("data-xref") || "";
|
|
561
|
+
const key = norm2(term);
|
|
562
|
+
const hit = localMap.get(key);
|
|
563
|
+
if (hit) {
|
|
564
|
+
a.setAttribute("href", hit.href);
|
|
565
|
+
} else {
|
|
566
|
+
const bucket = unresolved.get(key) || [];
|
|
567
|
+
bucket.push(a);
|
|
568
|
+
unresolved.set(key, bucket);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const resolver = options.xref?.resolver;
|
|
572
|
+
if (resolver && unresolved.size) {
|
|
573
|
+
const queries = Array.from(unresolved.keys()).map((term) => ({ term }));
|
|
574
|
+
resolver.resolveBatch(queries, options.xref?.specs).then((results) => {
|
|
575
|
+
for (const [key, anchors] of unresolved.entries()) {
|
|
576
|
+
const res = results.get(key);
|
|
577
|
+
if (!res) continue;
|
|
578
|
+
for (const a of anchors) {
|
|
579
|
+
a.setAttribute("href", res.href);
|
|
580
|
+
if (res.cite) a.setAttribute("data-cite", res.cite);
|
|
581
|
+
}
|
|
582
|
+
unresolved.delete(key);
|
|
583
|
+
}
|
|
584
|
+
}).catch((err) => {
|
|
585
|
+
warnings.push(`Xref resolver failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
for (const [key, anchors] of unresolved.entries()) {
|
|
589
|
+
const original = anchors[0].getAttribute("data-xref") || key;
|
|
590
|
+
warnings.push(`Unresolved xref: "${original}"`);
|
|
591
|
+
}
|
|
592
|
+
return warnings;
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
// src/pipeline/passes/references.ts
|
|
597
|
+
function ensureSection(root, id, title) {
|
|
598
|
+
let section = root.querySelector(`#${id}`);
|
|
599
|
+
if (!section) {
|
|
600
|
+
section = root.ownerDocument.createElement("section");
|
|
601
|
+
section.id = id;
|
|
602
|
+
section.innerHTML = `<h2>${title}</h2>`;
|
|
603
|
+
root.appendChild(section);
|
|
604
|
+
}
|
|
605
|
+
return section;
|
|
606
|
+
}
|
|
607
|
+
function ensureSubsection(parent, id, title) {
|
|
608
|
+
let sec = parent.querySelector(`#${id}`);
|
|
609
|
+
if (!sec) {
|
|
610
|
+
sec = parent.ownerDocument.createElement("section");
|
|
611
|
+
sec.id = id;
|
|
612
|
+
sec.innerHTML = `<h3>${title}</h3><ul></ul>`;
|
|
613
|
+
parent.appendChild(sec);
|
|
614
|
+
}
|
|
615
|
+
return sec;
|
|
616
|
+
}
|
|
617
|
+
function formatEntry(id, e) {
|
|
618
|
+
const parts = [];
|
|
619
|
+
parts.push(`<span class="ref-id">[${id}]</span>`);
|
|
620
|
+
if (e.href) parts.push(`<a href="${e.href}">${e.title || id}</a>`);
|
|
621
|
+
else parts.push(`<span class="ref-title">${e.title || id}</span>`);
|
|
622
|
+
const meta = [];
|
|
623
|
+
if (e.publisher) meta.push(e.publisher);
|
|
624
|
+
if (e.status) meta.push(e.status);
|
|
625
|
+
if (e.date) meta.push(e.date);
|
|
626
|
+
if (meta.length) parts.push(`<span class="ref-meta"> \u2014 ${meta.join(", ")}</span>`);
|
|
627
|
+
return parts.join(" ");
|
|
628
|
+
}
|
|
629
|
+
function idForRef(id) {
|
|
630
|
+
return `bib-${id.toLowerCase()}`;
|
|
631
|
+
}
|
|
632
|
+
var referencesPass = {
|
|
633
|
+
async run(root, options) {
|
|
634
|
+
const warnings = [];
|
|
635
|
+
const biblio = options.biblio?.entries ?? {};
|
|
636
|
+
const cites = Array.from(root.querySelectorAll("a[data-spec]"));
|
|
637
|
+
if (!cites.length) return warnings;
|
|
638
|
+
const normative = /* @__PURE__ */ new Set();
|
|
639
|
+
const informative = /* @__PURE__ */ new Set();
|
|
640
|
+
for (const a of cites) {
|
|
641
|
+
const id = (a.getAttribute("data-spec") || "").trim();
|
|
642
|
+
const norm3 = (a.getAttribute("data-normative") || "false") === "true";
|
|
643
|
+
(norm3 ? normative : informative).add(id);
|
|
644
|
+
}
|
|
645
|
+
for (const id of normative) informative.delete(id);
|
|
646
|
+
const refs = ensureSection(root, "references", "References");
|
|
647
|
+
const normSec = ensureSubsection(refs, "normative-references", "Normative references");
|
|
648
|
+
const infoSec = ensureSubsection(refs, "informative-references", "Informative references");
|
|
649
|
+
const renderList = (sec, ids) => {
|
|
650
|
+
const ul = sec.querySelector("ul");
|
|
651
|
+
ul.innerHTML = "";
|
|
652
|
+
Array.from(ids).sort((a, b) => a.localeCompare(b)).forEach((id) => {
|
|
653
|
+
const li = root.ownerDocument.createElement("li");
|
|
654
|
+
li.id = idForRef(id);
|
|
655
|
+
const entry = biblio[id];
|
|
656
|
+
if (entry) {
|
|
657
|
+
li.innerHTML = formatEntry(id, entry);
|
|
658
|
+
} else {
|
|
659
|
+
li.setAttribute("data-spec", id);
|
|
660
|
+
li.innerHTML = `<span class="ref-id">[${id}]</span> <span class="ref-missing">\u2014 unresolved reference</span>`;
|
|
661
|
+
warnings.push(`Unresolved reference: "${id}"`);
|
|
662
|
+
}
|
|
663
|
+
ul.appendChild(li);
|
|
664
|
+
});
|
|
665
|
+
};
|
|
666
|
+
renderList(normSec, normative);
|
|
667
|
+
renderList(infoSec, informative);
|
|
668
|
+
for (const a of cites) {
|
|
669
|
+
const id = (a.getAttribute("data-spec") || "").trim();
|
|
670
|
+
const targetId = idForRef(id);
|
|
671
|
+
a.setAttribute("href", `#${targetId}`);
|
|
672
|
+
a.setAttribute("data-cite-ref", targetId);
|
|
673
|
+
}
|
|
674
|
+
return warnings;
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
// src/pipeline/passes/boilerplate.ts
|
|
679
|
+
function createSection(doc, id, title, content) {
|
|
680
|
+
const sec = doc.createElement("section");
|
|
681
|
+
sec.id = id;
|
|
682
|
+
const h2 = doc.createElement("h2");
|
|
683
|
+
h2.textContent = title;
|
|
684
|
+
sec.appendChild(h2);
|
|
685
|
+
if (content) {
|
|
686
|
+
const p = doc.createElement("p");
|
|
687
|
+
p.textContent = content;
|
|
688
|
+
sec.appendChild(p);
|
|
689
|
+
}
|
|
690
|
+
return sec;
|
|
691
|
+
}
|
|
692
|
+
var boilerplatePass = {
|
|
693
|
+
async run(root, options) {
|
|
694
|
+
const bp = options.boilerplate;
|
|
695
|
+
if (!bp) return [];
|
|
696
|
+
const doc = root.ownerDocument;
|
|
697
|
+
const mountMode = bp.mount || "end";
|
|
698
|
+
let ref = null;
|
|
699
|
+
if (mountMode === "before-references") {
|
|
700
|
+
ref = root.querySelector("#references");
|
|
701
|
+
} else if (mountMode === "after-toc") {
|
|
702
|
+
const toc = root.querySelector("#toc");
|
|
703
|
+
ref = toc ? toc.nextSibling : null;
|
|
704
|
+
}
|
|
705
|
+
const insert = (section) => {
|
|
706
|
+
if (ref) {
|
|
707
|
+
root.insertBefore(section, ref);
|
|
708
|
+
} else {
|
|
709
|
+
root.appendChild(section);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
const defs = [
|
|
713
|
+
{ key: "conformance", title: "Conformance" },
|
|
714
|
+
{ key: "security", title: "Security" },
|
|
715
|
+
{ key: "privacy", title: "Privacy" }
|
|
716
|
+
];
|
|
717
|
+
for (const { key, title: defaultTitle } of defs) {
|
|
718
|
+
const opt = bp[key];
|
|
719
|
+
if (!opt) continue;
|
|
720
|
+
const cfg = typeof opt === "object" ? opt : {};
|
|
721
|
+
const id = cfg.id || key;
|
|
722
|
+
if (root.querySelector(`#${id}`)) continue;
|
|
723
|
+
const title = cfg.title || defaultTitle;
|
|
724
|
+
const content = cfg.content;
|
|
725
|
+
const sec = createSection(doc, id, title, content);
|
|
726
|
+
insert(sec);
|
|
727
|
+
}
|
|
728
|
+
return [];
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// src/pipeline/passes/toc.ts
|
|
733
|
+
var tocPass = {
|
|
734
|
+
async run(root, options) {
|
|
735
|
+
const { toc } = options;
|
|
736
|
+
if (toc?.enabled === false) return [];
|
|
737
|
+
const selector = toc?.selector ?? "#toc";
|
|
738
|
+
const mount = root.querySelector(selector);
|
|
739
|
+
if (!mount) return [];
|
|
740
|
+
const headings = Array.from(root.querySelectorAll("h2, h3"));
|
|
741
|
+
if (!headings.length) return [];
|
|
742
|
+
const doc = root.ownerDocument;
|
|
743
|
+
const ol = doc.createElement("ol");
|
|
744
|
+
ol.setAttribute("role", "list");
|
|
745
|
+
for (const h of headings) {
|
|
746
|
+
if (!h.id) continue;
|
|
747
|
+
const li = doc.createElement("li");
|
|
748
|
+
const depth = h.tagName.toLowerCase() === "h3" ? 2 : 1;
|
|
749
|
+
li.setAttribute("data-depth", String(depth));
|
|
750
|
+
li.innerHTML = `<a href="#${h.id}">${h.textContent || ""}</a>`;
|
|
751
|
+
ol.appendChild(li);
|
|
752
|
+
}
|
|
753
|
+
mount.innerHTML = "";
|
|
754
|
+
mount.appendChild(ol);
|
|
755
|
+
return [];
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
// src/pipeline/passes/diagnostics.ts
|
|
760
|
+
var diagnosticsPass = {
|
|
761
|
+
async run(root, options) {
|
|
762
|
+
const warnings = [];
|
|
763
|
+
const suppressClass = options.diagnostics?.suppressClass ?? "no-link-warnings";
|
|
764
|
+
const idsAndLinks = options.diagnostics?.idsAndLinks ?? true;
|
|
765
|
+
if (idsAndLinks) {
|
|
766
|
+
const seen = /* @__PURE__ */ new Map();
|
|
767
|
+
root.querySelectorAll("[id]").forEach((el) => {
|
|
768
|
+
const id = el.id;
|
|
769
|
+
if (!id) return;
|
|
770
|
+
if (seen.has(id)) {
|
|
771
|
+
if (!el.closest(`.${suppressClass}`)) {
|
|
772
|
+
warnings.push(`Duplicate id: "${id}"`);
|
|
773
|
+
}
|
|
774
|
+
} else {
|
|
775
|
+
seen.set(id, el);
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
const anchors = root.querySelectorAll("a");
|
|
779
|
+
anchors.forEach((a) => {
|
|
780
|
+
if (a.closest(`.${suppressClass}`)) return;
|
|
781
|
+
const hasHref = a.hasAttribute("href") && a.getAttribute("href") !== "";
|
|
782
|
+
const isPlaceholder = a.hasAttribute("data-xref") || a.hasAttribute("data-idl") || a.hasAttribute("data-spec");
|
|
783
|
+
if (!hasHref && isPlaceholder) {
|
|
784
|
+
const label = a.getAttribute("data-xref") || a.getAttribute("data-idl") || a.getAttribute("data-spec") || a.textContent || "";
|
|
785
|
+
warnings.push(`Unresolved link placeholder: "${label.trim()}"`);
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
return warnings;
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
// src/speculator.ts
|
|
794
|
+
var Speculator = class {
|
|
795
|
+
constructor(options = {}) {
|
|
796
|
+
const baseUrl = options.baseUrl;
|
|
797
|
+
const fileLoader = options.fileLoader || getDefaultFileLoader();
|
|
798
|
+
const markdownOptions = options.markdownOptions || {};
|
|
799
|
+
this.postprocessOptions = options.postprocess || {};
|
|
800
|
+
this.formatProcessor = options.formatProcessor || new FormatProcessor(markdownOptions);
|
|
801
|
+
this.includeProcessor = options.includeProcessor || new IncludeProcessor(baseUrl, fileLoader, this.formatProcessor);
|
|
802
|
+
this.htmlRenderer = options.htmlRenderer || new DOMHtmlRenderer();
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Process a single DOM element
|
|
806
|
+
*/
|
|
807
|
+
async processElement(element) {
|
|
808
|
+
const startTime = performance.now();
|
|
809
|
+
const stats = {
|
|
810
|
+
elementsProcessed: 0,
|
|
811
|
+
filesIncluded: 0,
|
|
812
|
+
markdownBlocks: 0,
|
|
813
|
+
processingTime: 0
|
|
814
|
+
};
|
|
815
|
+
const warnings = [];
|
|
816
|
+
const clonedElement = element.cloneNode(true);
|
|
817
|
+
try {
|
|
818
|
+
if (clonedElement.hasAttribute("data-include")) {
|
|
819
|
+
await this.includeProcessor.process(clonedElement, stats, warnings);
|
|
820
|
+
}
|
|
821
|
+
if (clonedElement.hasAttribute("data-format")) {
|
|
822
|
+
this.formatProcessor.process(clonedElement, stats, warnings);
|
|
823
|
+
}
|
|
824
|
+
stats.elementsProcessed = 1;
|
|
825
|
+
stats.processingTime = performance.now() - startTime;
|
|
826
|
+
return { element: clonedElement, warnings, stats };
|
|
827
|
+
} catch (error) {
|
|
828
|
+
throw new SpeculatorError(
|
|
829
|
+
`Failed to process element: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
830
|
+
element
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Process an entire document
|
|
836
|
+
*/
|
|
837
|
+
async renderDocument(container) {
|
|
838
|
+
const startTime = performance.now();
|
|
839
|
+
const sections = container.querySelectorAll(
|
|
840
|
+
"section[data-include], section[data-format], *[data-include], *[data-format]"
|
|
841
|
+
);
|
|
842
|
+
const allStats = {
|
|
843
|
+
elementsProcessed: 0,
|
|
844
|
+
filesIncluded: 0,
|
|
845
|
+
markdownBlocks: 0,
|
|
846
|
+
processingTime: 0
|
|
847
|
+
};
|
|
848
|
+
const allWarnings = [];
|
|
849
|
+
const processedElements = [];
|
|
850
|
+
for (const section of Array.from(sections)) {
|
|
851
|
+
try {
|
|
852
|
+
const result = await this.processElement(section);
|
|
853
|
+
processedElements.push(result.element);
|
|
854
|
+
allStats.elementsProcessed += result.stats.elementsProcessed;
|
|
855
|
+
allStats.filesIncluded += result.stats.filesIncluded;
|
|
856
|
+
allStats.markdownBlocks += result.stats.markdownBlocks;
|
|
857
|
+
allWarnings.push(...result.warnings);
|
|
858
|
+
} catch (error) {
|
|
859
|
+
allWarnings.push(
|
|
860
|
+
`Failed to process element: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
861
|
+
);
|
|
862
|
+
processedElements.push(section);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
sections.forEach((section, index) => {
|
|
866
|
+
if (processedElements[index] && section.parentNode) {
|
|
867
|
+
section.parentNode.replaceChild(processedElements[index], section);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
const passes = [
|
|
871
|
+
idlPass,
|
|
872
|
+
xrefPass,
|
|
873
|
+
referencesPass,
|
|
874
|
+
boilerplatePass,
|
|
875
|
+
tocPass,
|
|
876
|
+
diagnosticsPass
|
|
877
|
+
];
|
|
878
|
+
try {
|
|
879
|
+
const { warnings } = await postprocess(container, passes, this.postprocessOptions || {});
|
|
880
|
+
allWarnings.push(...warnings);
|
|
881
|
+
} catch (e) {
|
|
882
|
+
allWarnings.push(`Postprocess failed: ${e instanceof Error ? e.message : "Unknown error"}`);
|
|
883
|
+
}
|
|
884
|
+
allStats.processingTime = performance.now() - startTime;
|
|
885
|
+
return { element: container, warnings: allWarnings, stats: allStats };
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Process HTML string and return processed HTML
|
|
889
|
+
*/
|
|
890
|
+
async renderHTML(html) {
|
|
891
|
+
const container = this.htmlRenderer.parse(html);
|
|
892
|
+
await this.renderDocument(container);
|
|
893
|
+
return this.htmlRenderer.serialize(container);
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// src/linkedom-html-renderer.ts
|
|
898
|
+
var import_linkedom = require("linkedom");
|
|
899
|
+
var LinkedomHtmlRenderer = class {
|
|
900
|
+
parse(html) {
|
|
901
|
+
const parser = new import_linkedom.DOMParser();
|
|
902
|
+
const doc = parser.parseFromString(`<div>${html}</div>`, "text/html");
|
|
903
|
+
return doc.querySelector("div");
|
|
904
|
+
}
|
|
905
|
+
serialize(element) {
|
|
906
|
+
return element.innerHTML;
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
// src/node.ts
|
|
911
|
+
var Speculator2 = class extends Speculator {
|
|
912
|
+
constructor(options = {}) {
|
|
913
|
+
super({ ...options, htmlRenderer: options.htmlRenderer || new LinkedomHtmlRenderer() });
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
917
|
+
0 && (module.exports = {
|
|
918
|
+
DOMHtmlRenderer,
|
|
919
|
+
FormatProcessor,
|
|
920
|
+
IncludeProcessor,
|
|
921
|
+
LinkedomHtmlRenderer,
|
|
922
|
+
Speculator,
|
|
923
|
+
SpeculatorError,
|
|
924
|
+
browserFileLoader,
|
|
925
|
+
createFallbackFileLoader,
|
|
926
|
+
createMarkdownRenderer,
|
|
927
|
+
getDefaultFileLoader,
|
|
928
|
+
nodeFileLoader,
|
|
929
|
+
parseMarkdown
|
|
930
|
+
});
|