@mandolin/jsdoc-theme-hia 0.1.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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/RELEASE_CHECKLIST.md +43 -0
- package/THIRD_PARTY_NOTICES.md +23 -0
- package/examples/basic/README.md +29 -0
- package/examples/basic/jsdoc.conf.json +52 -0
- package/examples/basic/src/demo.js +65 -0
- package/package.json +58 -0
- package/publish.js +110 -0
- package/src/metadata-reader.cjs +262 -0
- package/src/render.cjs +1366 -0
- package/static/hia-theme.css +798 -0
- package/static/hia-theme.js +394 -0
package/src/render.cjs
ADDED
|
@@ -0,0 +1,1366 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
collectPageI18n,
|
|
5
|
+
getDocletI18n,
|
|
6
|
+
getLocalizedFieldEntry,
|
|
7
|
+
getLocalizedEntry,
|
|
8
|
+
summarizeHiaMetadata
|
|
9
|
+
} = require("./metadata-reader.cjs");
|
|
10
|
+
|
|
11
|
+
const KIND_LABELS = new Map([
|
|
12
|
+
["module", "Modules"],
|
|
13
|
+
["namespace", "Namespaces"],
|
|
14
|
+
["class", "Classes"],
|
|
15
|
+
["interface", "Interfaces"],
|
|
16
|
+
["mixin", "Mixins"],
|
|
17
|
+
["function", "Functions"],
|
|
18
|
+
["member", "Members"],
|
|
19
|
+
["constant", "Constants"],
|
|
20
|
+
["typedef", "Typedefs"],
|
|
21
|
+
["event", "Events"],
|
|
22
|
+
["external", "Externals"],
|
|
23
|
+
["unknown", "Other"]
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const KIND_ORDER = Array.from(KIND_LABELS.keys());
|
|
27
|
+
const JAVASCRIPT_KEYWORDS = new Set([
|
|
28
|
+
"async",
|
|
29
|
+
"await",
|
|
30
|
+
"break",
|
|
31
|
+
"case",
|
|
32
|
+
"catch",
|
|
33
|
+
"class",
|
|
34
|
+
"const",
|
|
35
|
+
"continue",
|
|
36
|
+
"default",
|
|
37
|
+
"do",
|
|
38
|
+
"else",
|
|
39
|
+
"export",
|
|
40
|
+
"extends",
|
|
41
|
+
"finally",
|
|
42
|
+
"for",
|
|
43
|
+
"function",
|
|
44
|
+
"if",
|
|
45
|
+
"import",
|
|
46
|
+
"let",
|
|
47
|
+
"module",
|
|
48
|
+
"new",
|
|
49
|
+
"of",
|
|
50
|
+
"return",
|
|
51
|
+
"switch",
|
|
52
|
+
"throw",
|
|
53
|
+
"try",
|
|
54
|
+
"typeof",
|
|
55
|
+
"var",
|
|
56
|
+
"while",
|
|
57
|
+
"yield"
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const BUILTIN_THEME_SKINS = new Set(["classic", "lumen", "graphite"]);
|
|
61
|
+
const CODE_FONT_FAMILIES = {
|
|
62
|
+
cascadia: "\"Cascadia Code\", \"Cascadia Mono\", Consolas, monospace",
|
|
63
|
+
consolas: "Consolas, \"Courier New\", monospace",
|
|
64
|
+
mono: "ui-monospace, \"SFMono-Regular\", Menlo, Monaco, Consolas, monospace",
|
|
65
|
+
system: "monospace"
|
|
66
|
+
};
|
|
67
|
+
const CODE_FONT_OPTIONS = [
|
|
68
|
+
["cascadia", "Cascadia"],
|
|
69
|
+
["consolas", "Consolas"],
|
|
70
|
+
["mono", "UI Mono"],
|
|
71
|
+
["system", "Monospace"]
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const UI_LABELS = {
|
|
75
|
+
en: {
|
|
76
|
+
"skip.content": "Skip to content",
|
|
77
|
+
"header.eyebrow": "API Documentation",
|
|
78
|
+
"summary.symbols": "{count} symbols",
|
|
79
|
+
"nav.symbols": "Symbols",
|
|
80
|
+
"search.label": "Search symbols",
|
|
81
|
+
"search.placeholder": "Filter by name, kind, or summary",
|
|
82
|
+
"empty.symbols": "No document symbols.",
|
|
83
|
+
"language.controls": "Languages",
|
|
84
|
+
"language.select": "Select language",
|
|
85
|
+
"language.runtime": "runtime",
|
|
86
|
+
"code.controls": "Code display",
|
|
87
|
+
"code.fontFamily": "Font",
|
|
88
|
+
"code.fontSize": "Size",
|
|
89
|
+
"code.lineHeight": "Line height",
|
|
90
|
+
"code.tabSize": "Tab",
|
|
91
|
+
"code.wrap": "Wrap",
|
|
92
|
+
"code.reset": "Reset",
|
|
93
|
+
"section.parameters": "Parameters",
|
|
94
|
+
"section.properties": "Properties",
|
|
95
|
+
"section.returns": "Returns",
|
|
96
|
+
"section.examples": "Examples",
|
|
97
|
+
"section.source": "Source",
|
|
98
|
+
"section.sourceReferences": "Source References",
|
|
99
|
+
"section.metadata": "Metadata",
|
|
100
|
+
"source.file": "File",
|
|
101
|
+
"source.lines": "Lines",
|
|
102
|
+
"source.lineRange": "Lines {start}-{end}",
|
|
103
|
+
"source.preview": "Source preview",
|
|
104
|
+
"source.unresolved": "Source fragment metadata is missing or unresolved.",
|
|
105
|
+
"source.previewDisabled": "Source preview is disabled for this fragment.",
|
|
106
|
+
"source.definedIn": "Defined in",
|
|
107
|
+
"source.reference": "Reference {index}",
|
|
108
|
+
"table.name": "Name",
|
|
109
|
+
"table.type": "Type",
|
|
110
|
+
"table.default": "Default",
|
|
111
|
+
"table.attributes": "Attributes",
|
|
112
|
+
"table.description": "Description",
|
|
113
|
+
"attr.optional": "optional",
|
|
114
|
+
"attr.nullable": "nullable",
|
|
115
|
+
"attr.repeatable": "repeatable",
|
|
116
|
+
"meta.kind": "Kind",
|
|
117
|
+
"meta.name": "Name",
|
|
118
|
+
"meta.longname": "Longname",
|
|
119
|
+
"meta.memberof": "Member of",
|
|
120
|
+
"meta.definedIn": "Defined in",
|
|
121
|
+
"meta.hiaMetadata": "HIA metadata",
|
|
122
|
+
"meta.microPlugins": "Micro plugins",
|
|
123
|
+
"meta.sourceRefs": "Source refs",
|
|
124
|
+
"meta.locales": "Locales",
|
|
125
|
+
"meta.defaultLocale": "Default locale",
|
|
126
|
+
"meta.fallbackLocale": "Fallback locale",
|
|
127
|
+
"meta.i18nMode": "I18N mode",
|
|
128
|
+
"meta.i18nFields": "I18N fields",
|
|
129
|
+
"meta.missingLocales": "Missing locales",
|
|
130
|
+
"value.yes": "yes",
|
|
131
|
+
"value.no": "no",
|
|
132
|
+
"value.empty": "-",
|
|
133
|
+
"example.default": "Example {index}",
|
|
134
|
+
"kind.module": "Modules",
|
|
135
|
+
"kind.namespace": "Namespaces",
|
|
136
|
+
"kind.class": "Classes",
|
|
137
|
+
"kind.interface": "Interfaces",
|
|
138
|
+
"kind.mixin": "Mixins",
|
|
139
|
+
"kind.function": "Functions",
|
|
140
|
+
"kind.member": "Members",
|
|
141
|
+
"kind.constant": "Constants",
|
|
142
|
+
"kind.typedef": "Typedefs",
|
|
143
|
+
"kind.event": "Events",
|
|
144
|
+
"kind.external": "Externals",
|
|
145
|
+
"kind.unknown": "Other"
|
|
146
|
+
},
|
|
147
|
+
"zh-CN": {
|
|
148
|
+
"skip.content": "跳到内容",
|
|
149
|
+
"header.eyebrow": "API 文档",
|
|
150
|
+
"summary.symbols": "{count} 个符号",
|
|
151
|
+
"nav.symbols": "符号",
|
|
152
|
+
"search.label": "搜索符号",
|
|
153
|
+
"search.placeholder": "按名称、类型或摘要筛选",
|
|
154
|
+
"empty.symbols": "没有文档符号。",
|
|
155
|
+
"language.controls": "语言",
|
|
156
|
+
"language.select": "选择语言",
|
|
157
|
+
"language.runtime": "运行时",
|
|
158
|
+
"code.controls": "代码显示",
|
|
159
|
+
"code.fontFamily": "字体",
|
|
160
|
+
"code.fontSize": "字号",
|
|
161
|
+
"code.lineHeight": "行高",
|
|
162
|
+
"code.tabSize": "缩进",
|
|
163
|
+
"code.wrap": "换行",
|
|
164
|
+
"code.reset": "重置",
|
|
165
|
+
"section.parameters": "参数",
|
|
166
|
+
"section.properties": "属性",
|
|
167
|
+
"section.returns": "返回值",
|
|
168
|
+
"section.examples": "示例",
|
|
169
|
+
"section.source": "源码",
|
|
170
|
+
"section.sourceReferences": "源码引用",
|
|
171
|
+
"section.metadata": "元数据",
|
|
172
|
+
"source.file": "文件",
|
|
173
|
+
"source.lines": "行",
|
|
174
|
+
"source.lineRange": "行 {start}-{end}",
|
|
175
|
+
"source.preview": "源码预览",
|
|
176
|
+
"source.unresolved": "源码片段元数据缺失或未解析。",
|
|
177
|
+
"source.previewDisabled": "此源码片段的预览已禁用。",
|
|
178
|
+
"source.definedIn": "定义于",
|
|
179
|
+
"source.reference": "引用 {index}",
|
|
180
|
+
"table.name": "名称",
|
|
181
|
+
"table.type": "类型",
|
|
182
|
+
"table.default": "默认值",
|
|
183
|
+
"table.attributes": "属性",
|
|
184
|
+
"table.description": "描述",
|
|
185
|
+
"attr.optional": "可选",
|
|
186
|
+
"attr.nullable": "可空",
|
|
187
|
+
"attr.repeatable": "可重复",
|
|
188
|
+
"meta.kind": "类型",
|
|
189
|
+
"meta.name": "名称",
|
|
190
|
+
"meta.longname": "完整名称",
|
|
191
|
+
"meta.memberof": "所属",
|
|
192
|
+
"meta.definedIn": "定义于",
|
|
193
|
+
"meta.hiaMetadata": "HIA metadata",
|
|
194
|
+
"meta.microPlugins": "微插件",
|
|
195
|
+
"meta.sourceRefs": "源码引用数",
|
|
196
|
+
"meta.locales": "语言",
|
|
197
|
+
"meta.defaultLocale": "默认语言",
|
|
198
|
+
"meta.fallbackLocale": "Fallback 语言",
|
|
199
|
+
"meta.i18nMode": "I18N 模式",
|
|
200
|
+
"meta.i18nFields": "I18N 字段数",
|
|
201
|
+
"meta.missingLocales": "缺失语言",
|
|
202
|
+
"value.yes": "是",
|
|
203
|
+
"value.no": "否",
|
|
204
|
+
"value.empty": "-",
|
|
205
|
+
"example.default": "示例 {index}",
|
|
206
|
+
"kind.module": "模块",
|
|
207
|
+
"kind.namespace": "命名空间",
|
|
208
|
+
"kind.class": "类",
|
|
209
|
+
"kind.interface": "接口",
|
|
210
|
+
"kind.mixin": "Mixin",
|
|
211
|
+
"kind.function": "函数",
|
|
212
|
+
"kind.member": "成员",
|
|
213
|
+
"kind.constant": "常量",
|
|
214
|
+
"kind.typedef": "类型定义",
|
|
215
|
+
"kind.event": "事件",
|
|
216
|
+
"kind.external": "外部项",
|
|
217
|
+
"kind.unknown": "其它"
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
function escapeHtml(value) {
|
|
222
|
+
return String(value ?? "")
|
|
223
|
+
.replace(/&/g, "&")
|
|
224
|
+
.replace(/</g, "<")
|
|
225
|
+
.replace(/>/g, ">")
|
|
226
|
+
.replace(/"/g, """);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function escapeJsonScript(value) {
|
|
230
|
+
return String(value ?? "")
|
|
231
|
+
.replace(/</g, "\\u003c")
|
|
232
|
+
.replace(/>/g, "\\u003e")
|
|
233
|
+
.replace(/&/g, "\\u0026");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function getUiLabels(locale = "") {
|
|
237
|
+
const normalized = String(locale || "").trim();
|
|
238
|
+
const baseLocale = normalized.split("-")[0];
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
...UI_LABELS.en,
|
|
242
|
+
...(UI_LABELS[baseLocale] || {}),
|
|
243
|
+
...(UI_LABELS[normalized] || {})
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function formatUiLabel(labels, key, values = {}) {
|
|
248
|
+
const template = labels[key] || UI_LABELS.en[key] || key;
|
|
249
|
+
|
|
250
|
+
return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, name) => {
|
|
251
|
+
return Object.prototype.hasOwnProperty.call(values, name) ? String(values[name]) : "";
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderLabelAttributes(key, values = {}) {
|
|
256
|
+
const attrs = [`data-hia-label="${escapeHtml(key)}"`];
|
|
257
|
+
|
|
258
|
+
for (const [name, value] of Object.entries(values)) {
|
|
259
|
+
attrs.push(`data-hia-label-${escapeHtml(name)}="${escapeHtml(value)}"`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return attrs.join(" ");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function renderAriaLabelAttributes(key) {
|
|
266
|
+
return `data-hia-label-aria="${escapeHtml(key)}"`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function renderPlaceholderLabelAttributes(key) {
|
|
270
|
+
return `data-hia-label-placeholder="${escapeHtml(key)}"`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function normalizeSkinName(value) {
|
|
274
|
+
const normalized = String(value || "classic").trim();
|
|
275
|
+
|
|
276
|
+
return BUILTIN_THEME_SKINS.has(normalized) ? normalized : "classic";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function clampNumber(value, fallback, min, max) {
|
|
280
|
+
const number = Number(value);
|
|
281
|
+
|
|
282
|
+
if (!Number.isFinite(number)) {
|
|
283
|
+
return fallback;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return Math.min(max, Math.max(min, number));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function normalizeCodeOptions(options = {}) {
|
|
290
|
+
const fontFamily = CODE_FONT_FAMILIES[options.fontFamily]
|
|
291
|
+
? options.fontFamily
|
|
292
|
+
: "cascadia";
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
controls: options.controls !== false,
|
|
296
|
+
fontFamily,
|
|
297
|
+
fontSize: clampNumber(options.fontSize, 12, 10, 20),
|
|
298
|
+
lineHeight: clampNumber(options.lineHeight, 1.55, 1.2, 2.2),
|
|
299
|
+
tabSize: Math.round(clampNumber(options.tabSize, 2, 2, 8)),
|
|
300
|
+
wrap: Boolean(options.wrap)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function normalizeThemeOptions(options = {}) {
|
|
305
|
+
const collapse = options.collapse && typeof options.collapse === "object"
|
|
306
|
+
? options.collapse
|
|
307
|
+
: {};
|
|
308
|
+
const languageControls = options.languageControls && typeof options.languageControls === "object"
|
|
309
|
+
? options.languageControls
|
|
310
|
+
: {};
|
|
311
|
+
const code = options.code && typeof options.code === "object"
|
|
312
|
+
? options.code
|
|
313
|
+
: {};
|
|
314
|
+
const skinOption = options.skin && typeof options.skin === "object"
|
|
315
|
+
? options.skin.name
|
|
316
|
+
: options.skin;
|
|
317
|
+
const dropdownThreshold = Number(languageControls.dropdownThreshold || 4);
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
skin: {
|
|
321
|
+
name: normalizeSkinName(skinOption)
|
|
322
|
+
},
|
|
323
|
+
collapse: {
|
|
324
|
+
docletsDefaultExpanded: collapse.docletsDefaultExpanded !== false,
|
|
325
|
+
sectionsDefaultExpanded: collapse.sectionsDefaultExpanded !== false,
|
|
326
|
+
metadataDefaultExpanded: collapse.metadataDefaultExpanded !== false
|
|
327
|
+
},
|
|
328
|
+
languageControls: {
|
|
329
|
+
mode: ["auto", "buttons", "select"].includes(languageControls.mode)
|
|
330
|
+
? languageControls.mode
|
|
331
|
+
: "auto",
|
|
332
|
+
dropdownThreshold: Number.isFinite(dropdownThreshold) && dropdownThreshold > 1
|
|
333
|
+
? dropdownThreshold
|
|
334
|
+
: 4
|
|
335
|
+
},
|
|
336
|
+
code: normalizeCodeOptions(code)
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function renderCodeStyle(codeOptions) {
|
|
341
|
+
const fontFamily = CODE_FONT_FAMILIES[codeOptions.fontFamily] || CODE_FONT_FAMILIES.cascadia;
|
|
342
|
+
|
|
343
|
+
return [
|
|
344
|
+
`--code-font-family: ${fontFamily}`,
|
|
345
|
+
`--code-font-size: ${codeOptions.fontSize}px`,
|
|
346
|
+
`--code-line-height: ${codeOptions.lineHeight}`,
|
|
347
|
+
`--code-tab-size: ${codeOptions.tabSize}`
|
|
348
|
+
].join("; ");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function renderCodeControls(renderOptions, labels) {
|
|
352
|
+
const code = renderOptions.theme.code;
|
|
353
|
+
|
|
354
|
+
if (!code.controls) {
|
|
355
|
+
return "";
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const fontOptions = CODE_FONT_OPTIONS.map(([value, label]) => {
|
|
359
|
+
const selected = value === code.fontFamily ? " selected" : "";
|
|
360
|
+
return `<option value="${escapeHtml(value)}"${selected}>${escapeHtml(label)}</option>`;
|
|
361
|
+
}).join("");
|
|
362
|
+
const wrapChecked = code.wrap ? " checked" : "";
|
|
363
|
+
|
|
364
|
+
return `<form class="code-controls" data-hia-code-controls aria-label="${escapeHtml(formatUiLabel(labels, "code.controls"))}" ${renderAriaLabelAttributes("code.controls")}>
|
|
365
|
+
<div class="code-controls-title" ${renderLabelAttributes("code.controls")}>${escapeHtml(formatUiLabel(labels, "code.controls"))}</div>
|
|
366
|
+
<label class="code-control">
|
|
367
|
+
<span ${renderLabelAttributes("code.fontFamily")}>${escapeHtml(formatUiLabel(labels, "code.fontFamily"))}</span>
|
|
368
|
+
<select class="code-select" data-hia-code-font-family>
|
|
369
|
+
${fontOptions}
|
|
370
|
+
</select>
|
|
371
|
+
</label>
|
|
372
|
+
<label class="code-control compact">
|
|
373
|
+
<span ${renderLabelAttributes("code.fontSize")}>${escapeHtml(formatUiLabel(labels, "code.fontSize"))}</span>
|
|
374
|
+
<input class="code-input" type="number" min="10" max="20" step="1" value="${escapeHtml(code.fontSize)}" data-hia-code-font-size>
|
|
375
|
+
</label>
|
|
376
|
+
<label class="code-control compact">
|
|
377
|
+
<span ${renderLabelAttributes("code.lineHeight")}>${escapeHtml(formatUiLabel(labels, "code.lineHeight"))}</span>
|
|
378
|
+
<input class="code-input" type="number" min="1.2" max="2.2" step="0.05" value="${escapeHtml(code.lineHeight)}" data-hia-code-line-height>
|
|
379
|
+
</label>
|
|
380
|
+
<label class="code-control compact">
|
|
381
|
+
<span ${renderLabelAttributes("code.tabSize")}>${escapeHtml(formatUiLabel(labels, "code.tabSize"))}</span>
|
|
382
|
+
<input class="code-input" type="number" min="2" max="8" step="1" value="${escapeHtml(code.tabSize)}" data-hia-code-tab-size>
|
|
383
|
+
</label>
|
|
384
|
+
<label class="code-toggle">
|
|
385
|
+
<input type="checkbox" data-hia-code-wrap${wrapChecked}>
|
|
386
|
+
<span ${renderLabelAttributes("code.wrap")}>${escapeHtml(formatUiLabel(labels, "code.wrap"))}</span>
|
|
387
|
+
</label>
|
|
388
|
+
<button class="code-reset" type="button" data-hia-code-reset ${renderLabelAttributes("code.reset")}>${escapeHtml(formatUiLabel(labels, "code.reset"))}</button>
|
|
389
|
+
</form>`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function getCollapseOpenAttribute(renderOptions, target) {
|
|
393
|
+
const collapse = renderOptions.theme && renderOptions.theme.collapse
|
|
394
|
+
? renderOptions.theme.collapse
|
|
395
|
+
: normalizeThemeOptions().collapse;
|
|
396
|
+
const key = `${target}DefaultExpanded`;
|
|
397
|
+
|
|
398
|
+
return collapse[key] === false ? "" : " open";
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function shouldUseLanguageSelect(pageI18n, renderOptions = {}) {
|
|
402
|
+
const languageControls = renderOptions.theme && renderOptions.theme.languageControls
|
|
403
|
+
? renderOptions.theme.languageControls
|
|
404
|
+
: normalizeThemeOptions().languageControls;
|
|
405
|
+
|
|
406
|
+
if (languageControls.mode === "select") {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (languageControls.mode === "buttons") {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return pageI18n.locales.length >= languageControls.dropdownThreshold;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function renderCollapsibleSection(titleKey, body, labels, renderOptions, options = {}) {
|
|
418
|
+
if (!body) {
|
|
419
|
+
return "";
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const className = options.className || "doc-section";
|
|
423
|
+
const target = options.target || "sections";
|
|
424
|
+
const open = getCollapseOpenAttribute(renderOptions, target);
|
|
425
|
+
|
|
426
|
+
return [
|
|
427
|
+
`<details class="${escapeHtml(className)} collapsible-section"${open}>`,
|
|
428
|
+
` <summary class="section-summary collapse-summary"><h3 ${renderLabelAttributes(titleKey)}>${escapeHtml(formatUiLabel(labels, titleKey))}</h3></summary>`,
|
|
429
|
+
' <div class="section-body">',
|
|
430
|
+
body,
|
|
431
|
+
" </div>",
|
|
432
|
+
"</details>"
|
|
433
|
+
].join("\n");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function stripHtml(value) {
|
|
437
|
+
return String(value ?? "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function toSlug(value) {
|
|
441
|
+
return String(value || "anonymous")
|
|
442
|
+
.trim()
|
|
443
|
+
.toLowerCase()
|
|
444
|
+
.replace(/[^a-z0-9_.:-]+/g, "-")
|
|
445
|
+
.replace(/^-+|-+$/g, "") || "anonymous";
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function getDocletTitle(doclet) {
|
|
449
|
+
return doclet.longname || doclet.name || "(anonymous)";
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function getDocletId(doclet) {
|
|
453
|
+
return `${toSlug(doclet.kind || "unknown")}-${toSlug(getDocletTitle(doclet))}`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function getKindLabel(kind) {
|
|
457
|
+
return KIND_LABELS.get(kind || "unknown") || KIND_LABELS.get("unknown");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function getTypeNames(typeLike) {
|
|
461
|
+
if (!typeLike) {
|
|
462
|
+
return [];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (Array.isArray(typeLike)) {
|
|
466
|
+
return typeLike.map(String).filter(Boolean);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (Array.isArray(typeLike.names)) {
|
|
470
|
+
return typeLike.names.map(String).filter(Boolean);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (typeof typeLike === "string") {
|
|
474
|
+
return [typeLike];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function renderType(typeLike) {
|
|
481
|
+
const names = getTypeNames(typeLike);
|
|
482
|
+
|
|
483
|
+
if (!names.length) {
|
|
484
|
+
return "-";
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return names.map(escapeHtml).join(" | ");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function normalizeKind(kind) {
|
|
491
|
+
return KIND_LABELS.has(kind) ? kind : "unknown";
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function sortDoclets(doclets) {
|
|
495
|
+
return [...doclets].sort((left, right) => {
|
|
496
|
+
const leftKind = KIND_ORDER.indexOf(normalizeKind(left.kind));
|
|
497
|
+
const rightKind = KIND_ORDER.indexOf(normalizeKind(right.kind));
|
|
498
|
+
|
|
499
|
+
if (leftKind !== rightKind) {
|
|
500
|
+
return leftKind - rightKind;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return getDocletTitle(left).localeCompare(getDocletTitle(right));
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function groupDoclets(doclets) {
|
|
508
|
+
const groups = new Map();
|
|
509
|
+
|
|
510
|
+
for (const doclet of sortDoclets(doclets)) {
|
|
511
|
+
const kind = normalizeKind(doclet.kind);
|
|
512
|
+
|
|
513
|
+
if (!groups.has(kind)) {
|
|
514
|
+
groups.set(kind, []);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
groups.get(kind).push(doclet);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return KIND_ORDER
|
|
521
|
+
.filter((kind) => groups.has(kind))
|
|
522
|
+
.map((kind) => ({
|
|
523
|
+
kind,
|
|
524
|
+
label: getKindLabel(kind),
|
|
525
|
+
doclets: groups.get(kind)
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function getParamDisplayName(param) {
|
|
530
|
+
const prefix = param.variable ? "..." : "";
|
|
531
|
+
const suffix = param.optional ? "?" : "";
|
|
532
|
+
return `${prefix}${param.name || "(anonymous)"}${suffix}`;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function renderSignature(doclet) {
|
|
536
|
+
const params = Array.isArray(doclet.params) ? doclet.params : [];
|
|
537
|
+
const returns = Array.isArray(doclet.returns) ? doclet.returns : [];
|
|
538
|
+
const showParams = ["function", "class", "typedef"].includes(doclet.kind) || params.length > 0;
|
|
539
|
+
const paramList = showParams
|
|
540
|
+
? `(${params.map((param) => getParamDisplayName(param)).join(", ")})`
|
|
541
|
+
: "";
|
|
542
|
+
const returnText = returns.length ? ` -> ${returns.map((item) => renderType(item.type)).join(" | ")}` : "";
|
|
543
|
+
|
|
544
|
+
return `${doclet.name || doclet.longname || "(anonymous)"}${paramList}${returnText}`;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function renderBadges(doclet, hiaSummary) {
|
|
548
|
+
const badges = [
|
|
549
|
+
`<span class="badge">${escapeHtml(doclet.kind || "unknown")}</span>`
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
if (doclet.scope) {
|
|
553
|
+
badges.push(`<span class="badge muted">${escapeHtml(doclet.scope)}</span>`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (hiaSummary.hasHia) {
|
|
557
|
+
badges.push('<span class="badge hia">HIA</span>');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (hiaSummary.sourceReferenceCount > 0) {
|
|
561
|
+
badges.push(`<span class="badge source">${hiaSummary.sourceReferenceCount} source</span>`);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return `<div class="badges">${badges.join("")}</div>`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function renderDescription(value) {
|
|
568
|
+
const text = String(value || "").trim();
|
|
569
|
+
|
|
570
|
+
if (!text) {
|
|
571
|
+
return "";
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return `<div class="description">${renderMarkdownish(text)}</div>`;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function getEntryText(localizedResult, doclet) {
|
|
578
|
+
const entry = localizedResult && localizedResult.entry ? localizedResult.entry : {};
|
|
579
|
+
const text = entry.block || entry.text || doclet.description || doclet.classdesc || "";
|
|
580
|
+
return String(text || "").trim();
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function renderLocalizedDescription(doclet, pageI18n, renderOptions = {}) {
|
|
584
|
+
const i18n = getDocletI18n(doclet);
|
|
585
|
+
|
|
586
|
+
if (!i18n || !pageI18n.enabled) {
|
|
587
|
+
return renderDescription(doclet.description || doclet.classdesc);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (renderOptions.locale) {
|
|
591
|
+
const localized = getLocalizedEntry(doclet, renderOptions.locale, pageI18n);
|
|
592
|
+
const text = getEntryText(localized, doclet);
|
|
593
|
+
const fallback = localized.usedFallback
|
|
594
|
+
? ` data-hia-fallback-from="${escapeHtml(localized.resolvedLocale)}"`
|
|
595
|
+
: "";
|
|
596
|
+
|
|
597
|
+
if (!text) {
|
|
598
|
+
return "";
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return [
|
|
602
|
+
`<div class="description i18n-description" data-hia-i18n="single" data-hia-locale="${escapeHtml(renderOptions.locale)}"${fallback}>`,
|
|
603
|
+
renderMarkdownish(text),
|
|
604
|
+
"</div>"
|
|
605
|
+
].join("\n");
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const locales = pageI18n.locales.length ? pageI18n.locales : i18n.locales || [];
|
|
609
|
+
const blocks = locales.map((locale) => {
|
|
610
|
+
const localized = getLocalizedEntry(doclet, locale, pageI18n);
|
|
611
|
+
const text = getEntryText(localized, doclet);
|
|
612
|
+
const hidden = locale === pageI18n.defaultLocale ? "" : " hidden";
|
|
613
|
+
const fallback = localized.usedFallback
|
|
614
|
+
? ` data-hia-fallback-from="${escapeHtml(localized.resolvedLocale)}"`
|
|
615
|
+
: "";
|
|
616
|
+
|
|
617
|
+
if (!text) {
|
|
618
|
+
return "";
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return [
|
|
622
|
+
`<div class="description i18n-description" data-hia-locale="${escapeHtml(locale)}"${fallback}${hidden}>`,
|
|
623
|
+
renderMarkdownish(text),
|
|
624
|
+
"</div>"
|
|
625
|
+
].join("\n");
|
|
626
|
+
}).filter(Boolean);
|
|
627
|
+
|
|
628
|
+
if (!blocks.length) {
|
|
629
|
+
return renderDescription(doclet.description || doclet.classdesc);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return `<div class="i18n-description-set">${blocks.join("\n")}</div>`;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function sanitizeFieldPathPart(value, fallback) {
|
|
636
|
+
const text = String(value || fallback || "").trim();
|
|
637
|
+
return text.replace(/\s+/g, "_").replace(/[.]/g, "_") || String(fallback || "");
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function getRuntimeLocale(pageI18n, renderOptions = {}) {
|
|
641
|
+
return renderOptions.locale || pageI18n.defaultLocale || "en";
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function getLocalizedText(localizedResult, defaultText = "") {
|
|
645
|
+
return getEntryText(localizedResult, {
|
|
646
|
+
description: defaultText,
|
|
647
|
+
classdesc: ""
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function renderLocalizedField(doclet, fieldPath, defaultText, pageI18n, renderOptions = {}, options = {}) {
|
|
652
|
+
const i18n = getDocletI18n(doclet);
|
|
653
|
+
const renderer = options.renderer || renderMarkdownish;
|
|
654
|
+
const empty = options.empty === undefined ? "" : options.empty;
|
|
655
|
+
const tag = options.tag || "div";
|
|
656
|
+
const className = options.className || "i18n-field";
|
|
657
|
+
|
|
658
|
+
if (!i18n || !pageI18n.enabled) {
|
|
659
|
+
return renderer(defaultText || "") || empty;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const renderOne = (locale, hidden) => {
|
|
663
|
+
const localized = getLocalizedFieldEntry(doclet, fieldPath, locale, {
|
|
664
|
+
...pageI18n,
|
|
665
|
+
defaultText
|
|
666
|
+
});
|
|
667
|
+
const text = getLocalizedText(localized, defaultText);
|
|
668
|
+
const fallback = localized.usedFallback
|
|
669
|
+
? ` data-hia-fallback-from="${escapeHtml(localized.resolvedLocale)}"`
|
|
670
|
+
: "";
|
|
671
|
+
const hiddenAttr = hidden ? " hidden" : "";
|
|
672
|
+
|
|
673
|
+
if (!text) {
|
|
674
|
+
return "";
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return `<${tag} class="${escapeHtml(className)}" data-hia-locale="${escapeHtml(locale)}"${fallback}${hiddenAttr}>${renderer(text)}</${tag}>`;
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
if (renderOptions.locale) {
|
|
681
|
+
const rendered = renderOne(renderOptions.locale, false);
|
|
682
|
+
return rendered || empty;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const locales = pageI18n.locales.length ? pageI18n.locales : i18n.locales || [];
|
|
686
|
+
const blocks = locales
|
|
687
|
+
.map((locale) => renderOne(locale, locale !== pageI18n.defaultLocale))
|
|
688
|
+
.filter(Boolean);
|
|
689
|
+
|
|
690
|
+
return blocks.length ? blocks.join("\n") : (renderer(defaultText || "") || empty);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function renderMarkdownish(text) {
|
|
694
|
+
const blocks = [];
|
|
695
|
+
const pattern = /```([a-zA-Z0-9_-]*)\r?\n([\s\S]*?)```/g;
|
|
696
|
+
let lastIndex = 0;
|
|
697
|
+
let match;
|
|
698
|
+
|
|
699
|
+
while ((match = pattern.exec(text))) {
|
|
700
|
+
if (match.index > lastIndex) {
|
|
701
|
+
blocks.push(renderTextParagraphs(text.slice(lastIndex, match.index)));
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
blocks.push(renderCodeBlock(match[2], {
|
|
705
|
+
language: match[1] || "text"
|
|
706
|
+
}));
|
|
707
|
+
lastIndex = pattern.lastIndex;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (lastIndex < text.length) {
|
|
711
|
+
blocks.push(renderTextParagraphs(text.slice(lastIndex)));
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return blocks.filter(Boolean).join("\n");
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function renderTextParagraphs(text) {
|
|
718
|
+
return String(text || "")
|
|
719
|
+
.trim()
|
|
720
|
+
.split(/\n{2,}/)
|
|
721
|
+
.map((paragraph) => paragraph.trim())
|
|
722
|
+
.filter(Boolean)
|
|
723
|
+
.map((paragraph) => `<p>${escapeHtml(paragraph).replace(/\n/g, "<br>")}</p>`)
|
|
724
|
+
.join("\n");
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function parseExample(example) {
|
|
728
|
+
const value = String(example || "");
|
|
729
|
+
const captionMatch = value.match(/^\s*<caption>([\s\S]*?)<\/caption>\s*/i);
|
|
730
|
+
|
|
731
|
+
if (!captionMatch) {
|
|
732
|
+
return {
|
|
733
|
+
caption: "",
|
|
734
|
+
code: value.trim()
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return {
|
|
739
|
+
caption: stripHtml(captionMatch[1]),
|
|
740
|
+
code: value.slice(captionMatch[0].length).trim()
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function renderExamples(doclet, pageI18n, renderOptions = {}, labels = UI_LABELS.en) {
|
|
745
|
+
const examples = Array.isArray(doclet.examples) ? doclet.examples : [];
|
|
746
|
+
|
|
747
|
+
if (!examples.length) {
|
|
748
|
+
return "";
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const body = examples
|
|
752
|
+
.map((example, index) => {
|
|
753
|
+
const parsed = parseExample(example);
|
|
754
|
+
const caption = parsed.caption || formatUiLabel(labels, "example.default", { index: index + 1 });
|
|
755
|
+
const language = doclet.meta && doclet.meta.filename ? inferLanguageFromName(doclet.meta.filename) : "javascript";
|
|
756
|
+
|
|
757
|
+
return [
|
|
758
|
+
' <figure class="example-block">',
|
|
759
|
+
` <figcaption>${renderLocalizedField(doclet, `examples.${index}.caption`, caption, pageI18n, renderOptions, {
|
|
760
|
+
renderer: escapeHtml,
|
|
761
|
+
tag: "span",
|
|
762
|
+
className: "i18n-field inline"
|
|
763
|
+
})}</figcaption>`,
|
|
764
|
+
renderLocalizedField(doclet, `examples.${index}.body`, parsed.code, pageI18n, renderOptions, {
|
|
765
|
+
renderer: (text) => renderCodeBlock(text, { language, indent: " " }),
|
|
766
|
+
tag: "div",
|
|
767
|
+
className: "i18n-example-code"
|
|
768
|
+
}),
|
|
769
|
+
" </figure>"
|
|
770
|
+
].join("\n");
|
|
771
|
+
})
|
|
772
|
+
.join("\n");
|
|
773
|
+
|
|
774
|
+
return renderCollapsibleSection("section.examples", body, labels, renderOptions, {
|
|
775
|
+
className: "doc-section examples"
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function renderParamTable(titleKey, items, doclet, pageI18n, renderOptions = {}, options = {}) {
|
|
780
|
+
if (!Array.isArray(items) || !items.length) {
|
|
781
|
+
return "";
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const labels = options.labels || getUiLabels(getRuntimeLocale(pageI18n, renderOptions));
|
|
785
|
+
const includeDefault = items.some((item) => item.defaultvalue !== undefined);
|
|
786
|
+
const fieldBase = options.fieldBase || "params";
|
|
787
|
+
const rows = items.map((item, index) => {
|
|
788
|
+
const detailKeys = [];
|
|
789
|
+
const fieldName = sanitizeFieldPathPart(item.name, index);
|
|
790
|
+
const fieldPath = `${fieldBase}.${fieldName}.description`;
|
|
791
|
+
|
|
792
|
+
if (item.optional) {
|
|
793
|
+
detailKeys.push("attr.optional");
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (item.nullable === true) {
|
|
797
|
+
detailKeys.push("attr.nullable");
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (item.variable) {
|
|
801
|
+
detailKeys.push("attr.repeatable");
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const details = detailKeys.length
|
|
805
|
+
? detailKeys
|
|
806
|
+
.map((key) => `<span ${renderLabelAttributes(key)}>${escapeHtml(formatUiLabel(labels, key))}</span>`)
|
|
807
|
+
.join(", ")
|
|
808
|
+
: escapeHtml(formatUiLabel(labels, "value.empty"));
|
|
809
|
+
|
|
810
|
+
return [
|
|
811
|
+
"<tr>",
|
|
812
|
+
` <th scope="row"><code>${escapeHtml(options.name ? options.name(item) : item.name || "-")}</code></th>`,
|
|
813
|
+
` <td><code>${renderType(item.type)}</code></td>`,
|
|
814
|
+
includeDefault ? ` <td>${item.defaultvalue === undefined ? formatUiLabel(labels, "value.empty") : `<code>${escapeHtml(item.defaultvalue)}</code>`}</td>` : "",
|
|
815
|
+
` <td>${details}</td>`,
|
|
816
|
+
` <td>${renderLocalizedField(doclet, fieldPath, item.description || "", pageI18n, renderOptions, {
|
|
817
|
+
empty: formatUiLabel(labels, "value.empty")
|
|
818
|
+
})}</td>`,
|
|
819
|
+
"</tr>"
|
|
820
|
+
].filter(Boolean).join("\n");
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
const body = [
|
|
824
|
+
' <div class="table-wrap">',
|
|
825
|
+
' <table class="doc-table">',
|
|
826
|
+
" <thead>",
|
|
827
|
+
" <tr>",
|
|
828
|
+
` <th ${renderLabelAttributes("table.name")}>${escapeHtml(formatUiLabel(labels, "table.name"))}</th>`,
|
|
829
|
+
` <th ${renderLabelAttributes("table.type")}>${escapeHtml(formatUiLabel(labels, "table.type"))}</th>`,
|
|
830
|
+
includeDefault ? ` <th ${renderLabelAttributes("table.default")}>${escapeHtml(formatUiLabel(labels, "table.default"))}</th>` : "",
|
|
831
|
+
` <th ${renderLabelAttributes("table.attributes")}>${escapeHtml(formatUiLabel(labels, "table.attributes"))}</th>`,
|
|
832
|
+
` <th ${renderLabelAttributes("table.description")}>${escapeHtml(formatUiLabel(labels, "table.description"))}</th>`,
|
|
833
|
+
" </tr>",
|
|
834
|
+
" </thead>",
|
|
835
|
+
" <tbody>",
|
|
836
|
+
rows.join("\n"),
|
|
837
|
+
" </tbody>",
|
|
838
|
+
" </table>",
|
|
839
|
+
" </div>"
|
|
840
|
+
].filter(Boolean).join("\n");
|
|
841
|
+
|
|
842
|
+
return renderCollapsibleSection(titleKey, body, labels, renderOptions);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function renderReturns(doclet, pageI18n, renderOptions = {}, labels = UI_LABELS.en) {
|
|
846
|
+
const returns = Array.isArray(doclet.returns) ? doclet.returns : [];
|
|
847
|
+
|
|
848
|
+
if (!returns.length) {
|
|
849
|
+
return "";
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const rows = returns.map((item, index) => [
|
|
853
|
+
"<tr>",
|
|
854
|
+
` <td><code>${renderType(item.type)}</code></td>`,
|
|
855
|
+
` <td>${renderLocalizedField(doclet, `returns.${index}.description`, item.description || "", pageI18n, renderOptions, {
|
|
856
|
+
empty: formatUiLabel(labels, "value.empty")
|
|
857
|
+
})}</td>`,
|
|
858
|
+
"</tr>"
|
|
859
|
+
].join("\n"));
|
|
860
|
+
|
|
861
|
+
const body = [
|
|
862
|
+
' <div class="table-wrap">',
|
|
863
|
+
' <table class="doc-table compact">',
|
|
864
|
+
` <thead><tr><th ${renderLabelAttributes("table.type")}>${escapeHtml(formatUiLabel(labels, "table.type"))}</th><th ${renderLabelAttributes("table.description")}>${escapeHtml(formatUiLabel(labels, "table.description"))}</th></tr></thead>`,
|
|
865
|
+
` <tbody>${rows.join("\n")}</tbody>`,
|
|
866
|
+
" </table>",
|
|
867
|
+
" </div>"
|
|
868
|
+
].join("\n");
|
|
869
|
+
|
|
870
|
+
return renderCollapsibleSection("section.returns", body, labels, renderOptions);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function renderMetadataList(doclet, hiaSummary, labels = UI_LABELS.en, renderOptions = {}) {
|
|
874
|
+
const definedIn = hiaSummary.sourceDefinedIn || (doclet.hia && doclet.hia.source && doclet.hia.source.definedIn);
|
|
875
|
+
const entries = [
|
|
876
|
+
["meta.kind", doclet.kind || "unknown"],
|
|
877
|
+
["meta.name", doclet.name || formatUiLabel(labels, "value.empty")],
|
|
878
|
+
["meta.longname", doclet.longname || formatUiLabel(labels, "value.empty")]
|
|
879
|
+
];
|
|
880
|
+
|
|
881
|
+
if (doclet.memberof) {
|
|
882
|
+
entries.push(["meta.memberof", doclet.memberof]);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (definedIn) {
|
|
886
|
+
entries.push(["meta.definedIn", formatSourceLocation(definedIn) || formatUiLabel(labels, "value.empty")]);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
entries.push(["meta.hiaMetadata", hiaSummary.hasHia ? "value.yes" : "value.no", true]);
|
|
890
|
+
entries.push(["meta.microPlugins", hiaSummary.microPlugins.join(", ") || formatUiLabel(labels, "value.empty")]);
|
|
891
|
+
entries.push(["meta.sourceRefs", String(hiaSummary.sourceReferenceCount)]);
|
|
892
|
+
entries.push(["meta.locales", hiaSummary.locales.join(", ") || formatUiLabel(labels, "value.empty")]);
|
|
893
|
+
entries.push(["meta.defaultLocale", hiaSummary.defaultLocale || formatUiLabel(labels, "value.empty")]);
|
|
894
|
+
entries.push(["meta.fallbackLocale", hiaSummary.fallbackLocale || formatUiLabel(labels, "value.empty")]);
|
|
895
|
+
entries.push(["meta.i18nMode", hiaSummary.i18nMode || formatUiLabel(labels, "value.empty")]);
|
|
896
|
+
entries.push(["meta.i18nFields", String(hiaSummary.i18nFieldCount || 0)]);
|
|
897
|
+
entries.push(["meta.missingLocales", hiaSummary.missingLocales.join(", ") || formatUiLabel(labels, "value.empty")]);
|
|
898
|
+
|
|
899
|
+
const body = [
|
|
900
|
+
'<dl class="meta-list">',
|
|
901
|
+
entries
|
|
902
|
+
.map(([labelKey, value, valueIsLabel]) => {
|
|
903
|
+
const renderedValue = valueIsLabel
|
|
904
|
+
? `<dd ${renderLabelAttributes(value)}>${escapeHtml(formatUiLabel(labels, value))}</dd>`
|
|
905
|
+
: `<dd>${escapeHtml(value)}</dd>`;
|
|
906
|
+
|
|
907
|
+
return ` <dt ${renderLabelAttributes(labelKey)}>${escapeHtml(formatUiLabel(labels, labelKey))}</dt>${renderedValue}`;
|
|
908
|
+
})
|
|
909
|
+
.join("\n"),
|
|
910
|
+
"</dl>"
|
|
911
|
+
].join("\n");
|
|
912
|
+
|
|
913
|
+
return renderCollapsibleSection("section.metadata", body, labels, renderOptions, {
|
|
914
|
+
className: "doc-section metadata-section",
|
|
915
|
+
target: "metadata"
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function getSourceReferences(doclet) {
|
|
920
|
+
const source = doclet && doclet.hia && doclet.hia.source;
|
|
921
|
+
return source && Array.isArray(source.references) ? source.references : [];
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function getDocletSource(doclet) {
|
|
925
|
+
return doclet && doclet.hia && doclet.hia.source && typeof doclet.hia.source === "object"
|
|
926
|
+
? doclet.hia.source
|
|
927
|
+
: null;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function formatSourceLocation(fragment) {
|
|
931
|
+
if (!fragment || !fragment.relativePath) {
|
|
932
|
+
return "";
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const range = fragment.range || {};
|
|
936
|
+
const position = fragment.position || range.start || {};
|
|
937
|
+
const line = position.line;
|
|
938
|
+
|
|
939
|
+
return line ? `${fragment.relativePath}:${line}` : fragment.relativePath;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function renderDocletSource(doclet, labels = UI_LABELS.en, renderOptions = {}) {
|
|
943
|
+
const source = getDocletSource(doclet);
|
|
944
|
+
|
|
945
|
+
if (!source || (!source.definedIn && !source.primaryBlock)) {
|
|
946
|
+
return "";
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const definedIn = source.definedIn || source.primaryBlock;
|
|
950
|
+
const primaryBlock = source.primaryBlock;
|
|
951
|
+
const definedInText = formatSourceLocation(definedIn);
|
|
952
|
+
const link = (definedIn && definedIn.link) || {};
|
|
953
|
+
const preview = primaryBlock && primaryBlock.preview ? primaryBlock.preview : null;
|
|
954
|
+
const canPreview = primaryBlock && preview && preview.enabled !== false && typeof preview.content === "string";
|
|
955
|
+
const body = [
|
|
956
|
+
' <div class="source-reference primary-source">',
|
|
957
|
+
` <div class="source-caption"><span ${renderLabelAttributes("source.definedIn")}>${escapeHtml(formatUiLabel(labels, "source.definedIn"))}</span>${definedInText ? ` - ${escapeHtml(definedInText)}` : ""}</div>`,
|
|
958
|
+
definedIn ? renderSourceLinks(definedIn, link, labels) : "",
|
|
959
|
+
canPreview
|
|
960
|
+
? renderSourcePreview(primaryBlock, preview, labels)
|
|
961
|
+
: "",
|
|
962
|
+
" </div>"
|
|
963
|
+
].filter(Boolean).join("\n");
|
|
964
|
+
|
|
965
|
+
return renderCollapsibleSection("section.source", body, labels, renderOptions, {
|
|
966
|
+
className: "doc-section source-section doclet-source"
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function renderSourceReferences(doclet, labels = UI_LABELS.en, renderOptions = {}) {
|
|
971
|
+
const references = getSourceReferences(doclet);
|
|
972
|
+
|
|
973
|
+
if (!references.length) {
|
|
974
|
+
return "";
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
const body = references.map((reference, index) => renderSourceReference(reference, index, labels)).join("\n");
|
|
978
|
+
|
|
979
|
+
return renderCollapsibleSection("section.sourceReferences", body, labels, renderOptions, {
|
|
980
|
+
className: "doc-section source-section source-references"
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function renderSourceReference(reference, index, labels = UI_LABELS.en) {
|
|
985
|
+
const fragment = reference.fragment;
|
|
986
|
+
const fallbackCaption = formatUiLabel(labels, "source.reference", { index: index + 1 });
|
|
987
|
+
const caption = fragment
|
|
988
|
+
? `${fragment.id || reference.targetId || fallbackCaption} - ${fragment.relativePath || "source"}:${fragment.range.start.line}-${fragment.range.end.line}`
|
|
989
|
+
: `${reference.targetId || fallbackCaption} - unresolved`;
|
|
990
|
+
|
|
991
|
+
if (!fragment) {
|
|
992
|
+
return [
|
|
993
|
+
'<div class="source-reference unresolved">',
|
|
994
|
+
` <div class="source-caption">${escapeHtml(caption)}</div>`,
|
|
995
|
+
` <p class="source-fallback" ${renderLabelAttributes("source.unresolved")}>${escapeHtml(formatUiLabel(labels, "source.unresolved"))}</p>`,
|
|
996
|
+
"</div>"
|
|
997
|
+
].join("\n");
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const preview = fragment.preview || {};
|
|
1001
|
+
const link = fragment.link || {};
|
|
1002
|
+
const canPreview = preview.enabled !== false && typeof preview.content === "string";
|
|
1003
|
+
|
|
1004
|
+
return [
|
|
1005
|
+
'<div class="source-reference">',
|
|
1006
|
+
` <div class="source-caption">${escapeHtml(caption)}</div>`,
|
|
1007
|
+
renderSourceLinks(fragment, link, labels),
|
|
1008
|
+
canPreview
|
|
1009
|
+
? renderSourcePreview(fragment, preview, labels)
|
|
1010
|
+
: ` <p class="source-fallback" ${renderLabelAttributes("source.previewDisabled")}>${escapeHtml(formatUiLabel(labels, "source.previewDisabled"))}</p>`,
|
|
1011
|
+
"</div>"
|
|
1012
|
+
].filter(Boolean).join("\n");
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function renderSourceLinks(fragment, link, labels = UI_LABELS.en) {
|
|
1016
|
+
const items = [];
|
|
1017
|
+
|
|
1018
|
+
if (link.enabled && link.fileUrl) {
|
|
1019
|
+
items.push(renderSourceAnchor(formatUiLabel(labels, "source.file"), link.fileUrl, link.openMode, "source.file"));
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (link.enabled && link.lineUrl) {
|
|
1023
|
+
items.push(renderSourceAnchor(formatUiLabel(labels, "source.lines"), link.lineUrl, link.openMode, "source.lines"));
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (!items.length && fragment.relativePath) {
|
|
1027
|
+
items.push(`<span>${escapeHtml(fragment.relativePath)}</span>`);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return ` <div class="source-links">${items.join("")}</div>`;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function renderSourceAnchor(label, href, openMode, labelKey = "") {
|
|
1034
|
+
const external = openMode === "new-tab" || /^https?:\/\//i.test(href);
|
|
1035
|
+
const target = external ? ' target="_blank" rel="noreferrer"' : "";
|
|
1036
|
+
const labelAttrs = labelKey ? ` ${renderLabelAttributes(labelKey)}` : "";
|
|
1037
|
+
return `<a href="${escapeHtml(href)}"${target}${labelAttrs}>${escapeHtml(label)}</a>`;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function renderSourcePreview(fragment, preview, labels = UI_LABELS.en) {
|
|
1041
|
+
const open = preview.defaultExpanded ? " open" : "";
|
|
1042
|
+
const language = preview.language || fragment.language || "text";
|
|
1043
|
+
const range = preview.range || fragment.range;
|
|
1044
|
+
const hasRange = Boolean(range && range.start && range.end);
|
|
1045
|
+
const rangeText = hasRange
|
|
1046
|
+
? formatUiLabel(labels, "source.lineRange", { start: range.start.line, end: range.end.line })
|
|
1047
|
+
: formatUiLabel(labels, "source.preview");
|
|
1048
|
+
const summaryLabelAttrs = hasRange
|
|
1049
|
+
? renderLabelAttributes("source.lineRange", { start: range.start.line, end: range.end.line })
|
|
1050
|
+
: renderLabelAttributes("source.preview");
|
|
1051
|
+
|
|
1052
|
+
return [
|
|
1053
|
+
` <details class="source-preview"${open}>`,
|
|
1054
|
+
` <summary ${summaryLabelAttrs}>${escapeHtml(rangeText)}</summary>`,
|
|
1055
|
+
renderCodeBlock(preview.content, {
|
|
1056
|
+
language,
|
|
1057
|
+
startLine: hasRange ? range.start.line : 1,
|
|
1058
|
+
indent: " "
|
|
1059
|
+
}),
|
|
1060
|
+
" </details>"
|
|
1061
|
+
].join("\n");
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function inferLanguageFromName(filename) {
|
|
1065
|
+
const lower = String(filename || "").toLowerCase();
|
|
1066
|
+
|
|
1067
|
+
if (lower.endsWith(".ts") || lower.endsWith(".tsx")) {
|
|
1068
|
+
return "typescript";
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (lower.endsWith(".js") || lower.endsWith(".jsx") || lower.endsWith(".cjs") || lower.endsWith(".mjs")) {
|
|
1072
|
+
return "javascript";
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (lower.endsWith(".css")) {
|
|
1076
|
+
return "css";
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
return "text";
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function renderCodeBlock(code, options = {}) {
|
|
1083
|
+
const language = options.language || "text";
|
|
1084
|
+
const startLine = Number.isInteger(options.startLine) ? options.startLine : null;
|
|
1085
|
+
const indent = options.indent || "";
|
|
1086
|
+
const codeHtml = renderCodeLines(code, language, startLine);
|
|
1087
|
+
|
|
1088
|
+
return [
|
|
1089
|
+
`${indent}<pre class="code-block" data-language="${escapeHtml(language)}"><code class="language-${escapeHtml(language)}">`,
|
|
1090
|
+
codeHtml,
|
|
1091
|
+
`${indent}</code></pre>`
|
|
1092
|
+
].join("\n");
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function renderCodeLines(code, language, startLine) {
|
|
1096
|
+
const lines = String(code ?? "").replace(/\r\n/g, "\n").split("\n");
|
|
1097
|
+
|
|
1098
|
+
return lines.map((line, index) => {
|
|
1099
|
+
const content = highlightLine(line, language) || " ";
|
|
1100
|
+
const lineNumber = startLine === null ? "" : ` data-line="${startLine + index}"`;
|
|
1101
|
+
return `<span class="code-line"${lineNumber}>${content}</span>`;
|
|
1102
|
+
}).join("\n");
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function highlightLine(line, language) {
|
|
1106
|
+
if (!["javascript", "typescript"].includes(language)) {
|
|
1107
|
+
return escapeHtml(line);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const escaped = escapeHtml(line);
|
|
1111
|
+
const commentIndex = escaped.indexOf("//");
|
|
1112
|
+
const codePart = commentIndex >= 0 ? escaped.slice(0, commentIndex) : escaped;
|
|
1113
|
+
const commentPart = commentIndex >= 0 ? escaped.slice(commentIndex) : "";
|
|
1114
|
+
const highlighted = codePart.replace(/\b([A-Za-z_$][\w$]*)\b/g, (match) => {
|
|
1115
|
+
if (JAVASCRIPT_KEYWORDS.has(match)) {
|
|
1116
|
+
return `<span class="tok-keyword">${match}</span>`;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
return match;
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
return commentPart
|
|
1123
|
+
? `${highlighted}<span class="tok-comment">${commentPart}</span>`
|
|
1124
|
+
: highlighted;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function renderDoclet(doclet, pageI18n, renderOptions = {}) {
|
|
1128
|
+
const hia = summarizeHiaMetadata(doclet);
|
|
1129
|
+
const title = getDocletTitle(doclet);
|
|
1130
|
+
const labels = getUiLabels(getRuntimeLocale(pageI18n, renderOptions));
|
|
1131
|
+
const open = getCollapseOpenAttribute(renderOptions, "doclets");
|
|
1132
|
+
|
|
1133
|
+
return [
|
|
1134
|
+
`<article class="doclet" id="${escapeHtml(getDocletId(doclet))}" data-kind="${escapeHtml(doclet.kind || "unknown")}">`,
|
|
1135
|
+
` <details class="doclet-details"${open}>`,
|
|
1136
|
+
' <summary class="doclet-summary collapse-summary">',
|
|
1137
|
+
' <div class="doclet-summary-main">',
|
|
1138
|
+
` <h2>${escapeHtml(title)}</h2>`,
|
|
1139
|
+
renderBadges(doclet, hia),
|
|
1140
|
+
" </div>",
|
|
1141
|
+
" </summary>",
|
|
1142
|
+
' <div class="doclet-body">',
|
|
1143
|
+
` <pre class="signature"><code>${escapeHtml(renderSignature(doclet))}</code></pre>`,
|
|
1144
|
+
renderLocalizedDescription(doclet, pageI18n, renderOptions),
|
|
1145
|
+
renderParamTable("section.parameters", doclet.params, doclet, pageI18n, renderOptions, {
|
|
1146
|
+
labels,
|
|
1147
|
+
name: getParamDisplayName,
|
|
1148
|
+
fieldBase: "params"
|
|
1149
|
+
}),
|
|
1150
|
+
renderReturns(doclet, pageI18n, renderOptions, labels),
|
|
1151
|
+
renderParamTable("section.properties", doclet.properties, doclet, pageI18n, renderOptions, {
|
|
1152
|
+
labels,
|
|
1153
|
+
fieldBase: "properties"
|
|
1154
|
+
}),
|
|
1155
|
+
renderExamples(doclet, pageI18n, renderOptions, labels),
|
|
1156
|
+
renderDocletSource(doclet, labels, renderOptions),
|
|
1157
|
+
renderSourceReferences(doclet, labels, renderOptions),
|
|
1158
|
+
renderMetadataList(doclet, hia, labels, renderOptions),
|
|
1159
|
+
" </div>",
|
|
1160
|
+
" </details>",
|
|
1161
|
+
"</article>"
|
|
1162
|
+
]
|
|
1163
|
+
.filter(Boolean)
|
|
1164
|
+
.join("\n");
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function renderNavigation(groups, labels = UI_LABELS.en) {
|
|
1168
|
+
if (!groups.length) {
|
|
1169
|
+
return `<p class="empty" ${renderLabelAttributes("empty.symbols")}>${escapeHtml(formatUiLabel(labels, "empty.symbols"))}</p>`;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
return groups.map((group) => {
|
|
1173
|
+
const labelKey = `kind.${group.kind || "unknown"}`;
|
|
1174
|
+
const groupLabel = formatUiLabel(labels, labelKey) || group.label;
|
|
1175
|
+
|
|
1176
|
+
return [
|
|
1177
|
+
'<section class="nav-group">',
|
|
1178
|
+
` <h2><span ${renderLabelAttributes(labelKey)}>${escapeHtml(groupLabel)}</span> <span>${group.doclets.length}</span></h2>`,
|
|
1179
|
+
group.doclets
|
|
1180
|
+
.map((doclet) => `<a href="#${escapeHtml(getDocletId(doclet))}">${escapeHtml(doclet.name || doclet.longname || "(anonymous)")}</a>`)
|
|
1181
|
+
.join("\n"),
|
|
1182
|
+
"</section>"
|
|
1183
|
+
].join("\n");
|
|
1184
|
+
}).join("\n");
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function buildSearchIndex(doclets) {
|
|
1188
|
+
const pageI18n = collectPageI18n(doclets);
|
|
1189
|
+
|
|
1190
|
+
return sortDoclets(doclets).map((doclet) => ({
|
|
1191
|
+
id: getDocletId(doclet),
|
|
1192
|
+
kind: doclet.kind || "unknown",
|
|
1193
|
+
name: doclet.name || "",
|
|
1194
|
+
longname: doclet.longname || "",
|
|
1195
|
+
memberof: doclet.memberof || "",
|
|
1196
|
+
summary: stripHtml(doclet.description || doclet.classdesc || ""),
|
|
1197
|
+
localizedSummaries: pageI18n.locales.reduce((result, locale) => {
|
|
1198
|
+
const localized = getLocalizedEntry(doclet, locale, pageI18n);
|
|
1199
|
+
result[locale] = stripHtml(getEntryText(localized, doclet));
|
|
1200
|
+
return result;
|
|
1201
|
+
}, {}),
|
|
1202
|
+
sourceReferences: getSourceReferences(doclet).length
|
|
1203
|
+
}));
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function buildI18nPageData(pageI18n, options = {}) {
|
|
1207
|
+
if (!pageI18n.enabled) {
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
return {
|
|
1212
|
+
enabled: true,
|
|
1213
|
+
mode: pageI18n.mode,
|
|
1214
|
+
runtimeSwitch: !options.locale,
|
|
1215
|
+
currentLocale: options.locale || pageI18n.defaultLocale,
|
|
1216
|
+
defaultLocale: pageI18n.defaultLocale,
|
|
1217
|
+
fallbackLocale: pageI18n.fallbackLocale,
|
|
1218
|
+
locales: pageI18n.locales,
|
|
1219
|
+
labels: UI_LABELS,
|
|
1220
|
+
perLocalePages: pageI18n.locales.reduce((result, locale) => {
|
|
1221
|
+
result[locale] = `index.${locale}.html`;
|
|
1222
|
+
return result;
|
|
1223
|
+
}, {})
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function renderLanguageControls(pageI18n, renderOptions = {}, labels = UI_LABELS.en) {
|
|
1228
|
+
if (!pageI18n.enabled || pageI18n.locales.length < 2) {
|
|
1229
|
+
return "";
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const selectedLocale = renderOptions.locale || pageI18n.defaultLocale;
|
|
1233
|
+
const ariaLabel = escapeHtml(formatUiLabel(labels, "language.controls"));
|
|
1234
|
+
const useSelect = shouldUseLanguageSelect(pageI18n, renderOptions);
|
|
1235
|
+
|
|
1236
|
+
if (useSelect && renderOptions.locale) {
|
|
1237
|
+
return [
|
|
1238
|
+
`<label class="language-select-control" aria-label="${ariaLabel}" ${renderAriaLabelAttributes("language.controls")}>`,
|
|
1239
|
+
` <span ${renderLabelAttributes("language.select")}>${escapeHtml(formatUiLabel(labels, "language.select"))}</span>`,
|
|
1240
|
+
' <select class="language-select" data-hia-locale-page-select>',
|
|
1241
|
+
pageI18n.locales
|
|
1242
|
+
.map((locale) => {
|
|
1243
|
+
const selected = locale === selectedLocale ? " selected" : "";
|
|
1244
|
+
return ` <option value="index.${escapeHtml(locale)}.html"${selected}>${escapeHtml(locale)}</option>`;
|
|
1245
|
+
})
|
|
1246
|
+
.join("\n"),
|
|
1247
|
+
` <option value="index.html" ${renderLabelAttributes("language.runtime")}>${escapeHtml(formatUiLabel(labels, "language.runtime"))}</option>`,
|
|
1248
|
+
" </select>",
|
|
1249
|
+
"</label>"
|
|
1250
|
+
].join("\n");
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (useSelect) {
|
|
1254
|
+
return [
|
|
1255
|
+
`<label class="language-select-control" aria-label="${ariaLabel}" ${renderAriaLabelAttributes("language.controls")}>`,
|
|
1256
|
+
` <span ${renderLabelAttributes("language.select")}>${escapeHtml(formatUiLabel(labels, "language.select"))}</span>`,
|
|
1257
|
+
' <select class="language-select" data-hia-locale-select>',
|
|
1258
|
+
pageI18n.locales
|
|
1259
|
+
.map((locale) => {
|
|
1260
|
+
const selected = locale === selectedLocale ? " selected" : "";
|
|
1261
|
+
return ` <option value="${escapeHtml(locale)}"${selected}>${escapeHtml(locale)}</option>`;
|
|
1262
|
+
})
|
|
1263
|
+
.join("\n"),
|
|
1264
|
+
" </select>",
|
|
1265
|
+
"</label>"
|
|
1266
|
+
].join("\n");
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if (renderOptions.locale) {
|
|
1270
|
+
return [
|
|
1271
|
+
`<nav class="language-controls" aria-label="${ariaLabel}" ${renderAriaLabelAttributes("language.controls")}>`,
|
|
1272
|
+
pageI18n.locales
|
|
1273
|
+
.map((locale) => {
|
|
1274
|
+
const active = locale === selectedLocale ? " active" : "";
|
|
1275
|
+
const aria = locale === selectedLocale ? ' aria-current="page"' : "";
|
|
1276
|
+
return `<a class="language-link${active}" href="index.${escapeHtml(locale)}.html"${aria}>${escapeHtml(locale)}</a>`;
|
|
1277
|
+
})
|
|
1278
|
+
.join(""),
|
|
1279
|
+
`<a class="language-link" href="index.html" ${renderLabelAttributes("language.runtime")}>${escapeHtml(formatUiLabel(labels, "language.runtime"))}</a>`,
|
|
1280
|
+
"</nav>"
|
|
1281
|
+
].join("\n");
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return [
|
|
1285
|
+
`<div class="language-controls" role="group" aria-label="${ariaLabel}" ${renderAriaLabelAttributes("language.controls")}>`,
|
|
1286
|
+
pageI18n.locales
|
|
1287
|
+
.map((locale) => {
|
|
1288
|
+
const active = locale === selectedLocale ? " active" : "";
|
|
1289
|
+
const pressed = locale === selectedLocale ? "true" : "false";
|
|
1290
|
+
return `<button type="button" class="language-button${active}" data-hia-locale-control="${escapeHtml(locale)}" aria-pressed="${pressed}">${escapeHtml(locale)}</button>`;
|
|
1291
|
+
})
|
|
1292
|
+
.join(""),
|
|
1293
|
+
"</div>"
|
|
1294
|
+
].join("\n");
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function renderPage(options) {
|
|
1298
|
+
const title = options.title || "HIA JSDoc";
|
|
1299
|
+
const doclets = sortDoclets(options.doclets || []);
|
|
1300
|
+
const pageI18n = collectPageI18n(doclets);
|
|
1301
|
+
const groups = groupDoclets(doclets);
|
|
1302
|
+
const renderOptions = {
|
|
1303
|
+
locale: options.locale || "",
|
|
1304
|
+
theme: normalizeThemeOptions(options.theme || {})
|
|
1305
|
+
};
|
|
1306
|
+
const labels = getUiLabels(getRuntimeLocale(pageI18n, renderOptions));
|
|
1307
|
+
const body = doclets.map((doclet) => renderDoclet(doclet, pageI18n, renderOptions)).join("\n");
|
|
1308
|
+
const searchIndex = JSON.stringify(buildSearchIndex(doclets));
|
|
1309
|
+
const i18nPageData = JSON.stringify(buildI18nPageData(pageI18n, renderOptions) || {});
|
|
1310
|
+
const themeData = JSON.stringify({ code: renderOptions.theme.code });
|
|
1311
|
+
const htmlLang = renderOptions.locale || pageI18n.defaultLocale || "en";
|
|
1312
|
+
const symbolCountLabel = formatUiLabel(labels, "summary.symbols", { count: doclets.length });
|
|
1313
|
+
const skinName = renderOptions.theme.skin.name;
|
|
1314
|
+
const codeWrapClass = renderOptions.theme.code.wrap ? " hia-code-wrap" : "";
|
|
1315
|
+
const codeStyle = renderCodeStyle(renderOptions.theme.code);
|
|
1316
|
+
|
|
1317
|
+
return `<!doctype html>
|
|
1318
|
+
<html lang="${escapeHtml(htmlLang)}">
|
|
1319
|
+
<head>
|
|
1320
|
+
<meta charset="utf-8">
|
|
1321
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1322
|
+
<title>${escapeHtml(title)}</title>
|
|
1323
|
+
<link rel="icon" href="data:,">
|
|
1324
|
+
<link rel="stylesheet" href="hia-theme.css">
|
|
1325
|
+
</head>
|
|
1326
|
+
<body class="hia-skin hia-skin-${escapeHtml(skinName)}${codeWrapClass}" data-hia-skin="${escapeHtml(skinName)}" style="${escapeHtml(codeStyle)}">
|
|
1327
|
+
<a class="skip-link" href="#hia-content" ${renderLabelAttributes("skip.content")}>${escapeHtml(formatUiLabel(labels, "skip.content"))}</a>
|
|
1328
|
+
<header class="site-header">
|
|
1329
|
+
<div>
|
|
1330
|
+
<p class="eyebrow" ${renderLabelAttributes("header.eyebrow")}>${escapeHtml(formatUiLabel(labels, "header.eyebrow"))}</p>
|
|
1331
|
+
<h1>${escapeHtml(title)}</h1>
|
|
1332
|
+
</div>
|
|
1333
|
+
<div class="site-actions">
|
|
1334
|
+
${renderLanguageControls(pageI18n, renderOptions, labels)}
|
|
1335
|
+
<div class="summary-stat" ${renderLabelAttributes("summary.symbols", { count: doclets.length })}>${escapeHtml(symbolCountLabel)}</div>
|
|
1336
|
+
</div>
|
|
1337
|
+
</header>
|
|
1338
|
+
<main class="layout">
|
|
1339
|
+
<nav class="sidebar" aria-label="${escapeHtml(formatUiLabel(labels, "nav.symbols"))}" ${renderAriaLabelAttributes("nav.symbols")}>
|
|
1340
|
+
<label class="search-label" for="hia-symbol-search" ${renderLabelAttributes("search.label")}>${escapeHtml(formatUiLabel(labels, "search.label"))}</label>
|
|
1341
|
+
<input id="hia-symbol-search" class="search-input" type="search" placeholder="${escapeHtml(formatUiLabel(labels, "search.placeholder"))}" ${renderPlaceholderLabelAttributes("search.placeholder")} autocomplete="off" aria-controls="hia-content">
|
|
1342
|
+
${renderCodeControls(renderOptions, labels)}
|
|
1343
|
+
${renderNavigation(groups, labels)}
|
|
1344
|
+
</nav>
|
|
1345
|
+
<section class="content" id="hia-content" tabindex="-1">
|
|
1346
|
+
${body || `<p class="empty" ${renderLabelAttributes("empty.symbols")}>${escapeHtml(formatUiLabel(labels, "empty.symbols"))}</p>`}
|
|
1347
|
+
</section>
|
|
1348
|
+
</main>
|
|
1349
|
+
<script type="application/json" id="hia-search-data">${escapeJsonScript(searchIndex)}</script>
|
|
1350
|
+
<script type="application/json" id="hia-i18n-data">${escapeJsonScript(i18nPageData)}</script>
|
|
1351
|
+
<script type="application/json" id="hia-theme-data">${escapeJsonScript(themeData)}</script>
|
|
1352
|
+
<script src="hia-theme.js" defer></script>
|
|
1353
|
+
</body>
|
|
1354
|
+
</html>
|
|
1355
|
+
`;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
module.exports = {
|
|
1359
|
+
buildI18nPageData,
|
|
1360
|
+
buildSearchIndex,
|
|
1361
|
+
escapeHtml,
|
|
1362
|
+
getDocletId,
|
|
1363
|
+
groupDoclets,
|
|
1364
|
+
renderPage,
|
|
1365
|
+
renderSourceReferences
|
|
1366
|
+
};
|