@redocly/realm-plugin-asciidoc 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/lib/.tsbuildinfo +1 -0
- package/lib/asciidoc-parser.d.ts +15 -0
- package/lib/asciidoc-parser.js +747 -0
- package/lib/asciidoc-parser.js.map +1 -0
- package/lib/get-ai-search-documents.d.ts +24 -0
- package/lib/get-ai-search-documents.js +133 -0
- package/lib/get-ai-search-documents.js.map +1 -0
- package/lib/plugin.d.ts +2 -0
- package/lib/plugin.js +314 -0
- package/lib/plugin.js.map +1 -0
- package/lib/search-facets.d.ts +3 -0
- package/lib/search-facets.js +34 -0
- package/lib/search-facets.js.map +1 -0
- package/lib/search-resolver.d.ts +4 -0
- package/lib/search-resolver.js +72 -0
- package/lib/search-resolver.js.map +1 -0
- package/lib/template.d.ts +18 -0
- package/lib/template.js +346 -0
- package/lib/template.js.map +1 -0
- package/lib/types.d.ts +130 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/package.json +45 -0
- package/src/asciidoc-parser.ts +927 -0
- package/src/get-ai-search-documents.ts +193 -0
- package/src/plugin.ts +483 -0
- package/src/search-facets.ts +46 -0
- package/src/search-resolver.ts +98 -0
- package/src/template.tsx +628 -0
- package/src/types.ts +171 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import Asciidoctor from '@asciidoctor/core';
|
|
3
|
+
const asciidoctor = Asciidoctor();
|
|
4
|
+
export function parseAsciidoc(content, relativePath, contentRoot) {
|
|
5
|
+
var _a;
|
|
6
|
+
const absolutePath = path.resolve(contentRoot, relativePath);
|
|
7
|
+
const doc = asciidoctor.load(content, {
|
|
8
|
+
safe: 'safe',
|
|
9
|
+
base_dir: path.dirname(absolutePath),
|
|
10
|
+
docfile: absolutePath,
|
|
11
|
+
attributes: {
|
|
12
|
+
showtitle: true,
|
|
13
|
+
'source-highlighter': 'highlight.js',
|
|
14
|
+
icons: 'font',
|
|
15
|
+
idprefix: '',
|
|
16
|
+
idseparator: '-',
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
const title = ((_a = doc.getDocumentTitle()) === null || _a === void 0 ? void 0 : _a.toString()) || '';
|
|
20
|
+
const rawAttributes = doc.getAttributes();
|
|
21
|
+
const attributes = {};
|
|
22
|
+
for (const [key, value] of Object.entries(rawAttributes)) {
|
|
23
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
24
|
+
attributes[key] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Walk the AST to produce serializable content nodes
|
|
28
|
+
const topBlocks = doc.getBlocks();
|
|
29
|
+
const contentNodes = [];
|
|
30
|
+
// AsciiDoc `= Title` (level 0) is the document title — asciidoctor excludes it from
|
|
31
|
+
// getBlocks()/getSections(). Prepend it as an H1 heading so it renders on the page.
|
|
32
|
+
if (title) {
|
|
33
|
+
const { html: titleHtml, icons: titleIcons } = extractInlineIcons(title);
|
|
34
|
+
contentNodes.push({
|
|
35
|
+
type: 'section',
|
|
36
|
+
id: '',
|
|
37
|
+
title: stripInlineHtml(title),
|
|
38
|
+
titleHtml,
|
|
39
|
+
titleIcons,
|
|
40
|
+
level: 1,
|
|
41
|
+
children: [],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
contentNodes.push(...convertBlocks(topBlocks, relativePath));
|
|
45
|
+
// Build section tree for TOC, search, and AI docs
|
|
46
|
+
const sections = buildSectionTree(doc.getSections());
|
|
47
|
+
// Extract links from converted HTML (used for sitemap / link checking)
|
|
48
|
+
const convertedHtml = doc.convert();
|
|
49
|
+
const links = extractLinks(convertedHtml, relativePath);
|
|
50
|
+
return {
|
|
51
|
+
title,
|
|
52
|
+
attributes,
|
|
53
|
+
sections,
|
|
54
|
+
contentNodes,
|
|
55
|
+
links,
|
|
56
|
+
sourceContent: content,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function convertBlocks(blocks, relativePath) {
|
|
60
|
+
const nodes = [];
|
|
61
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
62
|
+
const block = blocks[i];
|
|
63
|
+
const context = block.getContext();
|
|
64
|
+
switch (context) {
|
|
65
|
+
case 'section':
|
|
66
|
+
nodes.push(convertSection(block, relativePath));
|
|
67
|
+
break;
|
|
68
|
+
case 'paragraph':
|
|
69
|
+
nodes.push(convertParagraph(block));
|
|
70
|
+
break;
|
|
71
|
+
case 'listing': {
|
|
72
|
+
const codeNode = convertListing(block, blocks[i + 1]);
|
|
73
|
+
// If we consumed a colist, skip it
|
|
74
|
+
if (codeNode.type === 'codeBlock' &&
|
|
75
|
+
codeNode.calloutDescriptions &&
|
|
76
|
+
codeNode.calloutDescriptions.length > 0) {
|
|
77
|
+
const next = blocks[i + 1];
|
|
78
|
+
if (next && next.getContext() === 'colist') {
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
nodes.push(codeNode);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case 'literal':
|
|
86
|
+
nodes.push(convertLiteral(block));
|
|
87
|
+
break;
|
|
88
|
+
case 'admonition':
|
|
89
|
+
nodes.push(convertAdmonition(block, relativePath));
|
|
90
|
+
break;
|
|
91
|
+
case 'ulist':
|
|
92
|
+
nodes.push(convertList(block, false, relativePath));
|
|
93
|
+
break;
|
|
94
|
+
case 'olist':
|
|
95
|
+
nodes.push(convertList(block, true, relativePath));
|
|
96
|
+
break;
|
|
97
|
+
case 'dlist':
|
|
98
|
+
nodes.push(convertDescriptionList(block, relativePath));
|
|
99
|
+
break;
|
|
100
|
+
case 'table':
|
|
101
|
+
nodes.push(convertTable(block));
|
|
102
|
+
break;
|
|
103
|
+
case 'image':
|
|
104
|
+
nodes.push(convertImage(block, relativePath));
|
|
105
|
+
break;
|
|
106
|
+
case 'quote':
|
|
107
|
+
case 'verse':
|
|
108
|
+
nodes.push(convertQuote(block, relativePath));
|
|
109
|
+
break;
|
|
110
|
+
case 'sidebar':
|
|
111
|
+
nodes.push(convertSidebar(block, relativePath));
|
|
112
|
+
break;
|
|
113
|
+
case 'example':
|
|
114
|
+
nodes.push(convertExample(block, relativePath));
|
|
115
|
+
break;
|
|
116
|
+
case 'open':
|
|
117
|
+
nodes.push(convertOpen(block, relativePath));
|
|
118
|
+
break;
|
|
119
|
+
case 'thematic_break':
|
|
120
|
+
nodes.push({ type: 'thematicBreak' });
|
|
121
|
+
break;
|
|
122
|
+
case 'pass':
|
|
123
|
+
case 'stem':
|
|
124
|
+
nodes.push(convertPassthrough(block));
|
|
125
|
+
break;
|
|
126
|
+
case 'preamble':
|
|
127
|
+
// Preamble is a wrapper; recurse into its children
|
|
128
|
+
nodes.push(...convertBlocks(block.getBlocks(), relativePath));
|
|
129
|
+
break;
|
|
130
|
+
case 'toc':
|
|
131
|
+
// Skip generated TOC — we use our own
|
|
132
|
+
break;
|
|
133
|
+
case 'colist':
|
|
134
|
+
// Colist is consumed alongside listing blocks; if orphaned, skip
|
|
135
|
+
break;
|
|
136
|
+
default:
|
|
137
|
+
console.warn(`[asciidoc-plugin] Unsupported block type "${context}" — skipping.`);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return nodes;
|
|
142
|
+
}
|
|
143
|
+
function convertSection(section, relativePath) {
|
|
144
|
+
const id = section.getId() || slugify(section.getTitle() || '');
|
|
145
|
+
const rawTitle = section.getTitle() || '';
|
|
146
|
+
const level = section.getLevel();
|
|
147
|
+
// Section titles can contain inline icons (e.g. `== icon:flag[] Heading`)
|
|
148
|
+
const { html: titleHtml, icons: titleIcons } = extractInlineIcons(rawTitle);
|
|
149
|
+
// Plain text title (strip HTML) for TOC and search
|
|
150
|
+
const title = stripInlineHtml(rawTitle);
|
|
151
|
+
const children = convertBlocks(section.getBlocks(), relativePath);
|
|
152
|
+
return {
|
|
153
|
+
type: 'section',
|
|
154
|
+
id,
|
|
155
|
+
title,
|
|
156
|
+
titleHtml,
|
|
157
|
+
titleIcons,
|
|
158
|
+
level,
|
|
159
|
+
children,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function convertParagraph(block) {
|
|
163
|
+
const html = block.getContent() || '';
|
|
164
|
+
const { html: processedHtml, icons } = extractInlineIcons(html);
|
|
165
|
+
const role = block.getRole() || undefined;
|
|
166
|
+
return {
|
|
167
|
+
type: 'paragraph',
|
|
168
|
+
contentHtml: processedHtml,
|
|
169
|
+
icons,
|
|
170
|
+
role,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function convertListing(block, nextBlock) {
|
|
174
|
+
const lang = block.getAttribute('language') || '';
|
|
175
|
+
const rawSource = block.getSource() || '';
|
|
176
|
+
const title = block.getTitle() || undefined;
|
|
177
|
+
// Parse callout markers from source: <1>, <2>, etc.
|
|
178
|
+
const { cleaned, callouts } = parseCallouts(rawSource);
|
|
179
|
+
// Check if next block is a colist (callout list)
|
|
180
|
+
let calloutDescriptions;
|
|
181
|
+
if (nextBlock && nextBlock.getContext() === 'colist') {
|
|
182
|
+
calloutDescriptions = extractColistDescriptions(nextBlock);
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
type: 'codeBlock',
|
|
186
|
+
lang,
|
|
187
|
+
source: cleaned,
|
|
188
|
+
title,
|
|
189
|
+
callouts: callouts.length > 0 ? callouts : undefined,
|
|
190
|
+
calloutDescriptions: calloutDescriptions && calloutDescriptions.length > 0 ? calloutDescriptions : undefined,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function convertLiteral(block) {
|
|
194
|
+
const source = block.getSource() || '';
|
|
195
|
+
return {
|
|
196
|
+
type: 'codeBlock',
|
|
197
|
+
lang: '',
|
|
198
|
+
source,
|
|
199
|
+
title: block.getTitle() || undefined,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const ADMONITION_TYPE_MAP = {
|
|
203
|
+
note: 'info',
|
|
204
|
+
tip: 'success',
|
|
205
|
+
warning: 'warning',
|
|
206
|
+
caution: 'warning',
|
|
207
|
+
important: 'danger',
|
|
208
|
+
};
|
|
209
|
+
function convertAdmonition(block, relativePath) {
|
|
210
|
+
const style = (block.getStyle() || 'note').toLowerCase();
|
|
211
|
+
const admonitionType = ADMONITION_TYPE_MAP[style] || 'info';
|
|
212
|
+
const name = style.charAt(0).toUpperCase() + style.slice(1);
|
|
213
|
+
const innerBlocks = block.getBlocks();
|
|
214
|
+
const children = convertBlocks(innerBlocks, relativePath);
|
|
215
|
+
return {
|
|
216
|
+
type: 'admonition',
|
|
217
|
+
admonitionType,
|
|
218
|
+
name,
|
|
219
|
+
children,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function convertList(block, ordered, relativePath) {
|
|
223
|
+
const items = [];
|
|
224
|
+
for (const item of block.getItems()) {
|
|
225
|
+
const textHtml = item.getText() || '';
|
|
226
|
+
const { html: processedHtml, icons } = extractInlineIcons(textHtml);
|
|
227
|
+
const childBlocks = item.hasBlocks() ? item.getBlocks() : [];
|
|
228
|
+
const children = convertBlocks(childBlocks, relativePath);
|
|
229
|
+
items.push({
|
|
230
|
+
contentHtml: processedHtml,
|
|
231
|
+
icons,
|
|
232
|
+
children,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
type: 'list',
|
|
237
|
+
ordered,
|
|
238
|
+
items,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function convertDescriptionList(block, relativePath) {
|
|
242
|
+
const items = [];
|
|
243
|
+
const rawItems = block.getItems();
|
|
244
|
+
for (const pair of rawItems) {
|
|
245
|
+
if (!Array.isArray(pair) || pair.length < 2)
|
|
246
|
+
continue;
|
|
247
|
+
const [terms, definition] = pair;
|
|
248
|
+
const termsHtml = [];
|
|
249
|
+
if (Array.isArray(terms)) {
|
|
250
|
+
for (const term of terms) {
|
|
251
|
+
const text = typeof term.getText === 'function' ? term.getText() : String(term);
|
|
252
|
+
termsHtml.push(text);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
let descriptionHtml = '';
|
|
256
|
+
let descriptionIcons = [];
|
|
257
|
+
let children = [];
|
|
258
|
+
if (definition && typeof definition.getText === 'function') {
|
|
259
|
+
const rawHtml = definition.getText() || '';
|
|
260
|
+
const extracted = extractInlineIcons(rawHtml);
|
|
261
|
+
descriptionHtml = extracted.html;
|
|
262
|
+
descriptionIcons = extracted.icons;
|
|
263
|
+
if (typeof definition.hasBlocks === 'function' && definition.hasBlocks()) {
|
|
264
|
+
children = convertBlocks(definition.getBlocks(), relativePath);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
items.push({
|
|
268
|
+
termsHtml,
|
|
269
|
+
descriptionHtml,
|
|
270
|
+
descriptionIcons,
|
|
271
|
+
children,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
type: 'descriptionList',
|
|
276
|
+
items,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function convertTable(block) {
|
|
280
|
+
const rows = block.getRows();
|
|
281
|
+
const title = block.getTitle() || undefined;
|
|
282
|
+
const convertRow = (row) => row.map((cell) => {
|
|
283
|
+
var _a;
|
|
284
|
+
const colSpan = cell.getColumnSpan();
|
|
285
|
+
const rowSpan = cell.getRowSpan();
|
|
286
|
+
// getContent() returns rendered HTML for asciidoc cells; getText() for plain text cells
|
|
287
|
+
const style = (_a = cell.getStyle) === null || _a === void 0 ? void 0 : _a.call(cell);
|
|
288
|
+
const contentHtml = (style === 'asciidoc' ? cell.getContent() : cell.getText()) || '';
|
|
289
|
+
return Object.assign(Object.assign({ contentHtml }, (colSpan > 1 ? { colSpan } : {})), (rowSpan > 1 ? { rowSpan } : {}));
|
|
290
|
+
});
|
|
291
|
+
return {
|
|
292
|
+
type: 'table',
|
|
293
|
+
title,
|
|
294
|
+
head: (rows.head || []).map(convertRow),
|
|
295
|
+
body: (rows.body || []).map(convertRow),
|
|
296
|
+
foot: (rows.foot || []).map(convertRow),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function convertImage(block, relativePath) {
|
|
300
|
+
const target = block.getAttribute('target') || '';
|
|
301
|
+
const alt = block.getAttribute('alt') || '';
|
|
302
|
+
const width = block.getAttribute('width');
|
|
303
|
+
const height = block.getAttribute('height');
|
|
304
|
+
const title = block.getTitle() || undefined;
|
|
305
|
+
const src = normalizeImageSrc(target, relativePath);
|
|
306
|
+
return Object.assign(Object.assign({ type: 'image', src,
|
|
307
|
+
alt,
|
|
308
|
+
title }, (width ? { width: String(width) } : {})), (height ? { height: String(height) } : {}));
|
|
309
|
+
}
|
|
310
|
+
function convertQuote(block, relativePath) {
|
|
311
|
+
const attribution = block.getAttribute('attribution') || undefined;
|
|
312
|
+
const citeTitle = block.getAttribute('citetitle') || undefined;
|
|
313
|
+
const innerBlocks = block.getBlocks();
|
|
314
|
+
const children = innerBlocks.length > 0
|
|
315
|
+
? convertBlocks(innerBlocks, relativePath)
|
|
316
|
+
: block.getContent()
|
|
317
|
+
? [
|
|
318
|
+
{
|
|
319
|
+
type: 'paragraph',
|
|
320
|
+
contentHtml: block.getContent() || '',
|
|
321
|
+
icons: [],
|
|
322
|
+
},
|
|
323
|
+
]
|
|
324
|
+
: [];
|
|
325
|
+
return {
|
|
326
|
+
type: 'quote',
|
|
327
|
+
attribution,
|
|
328
|
+
citeTitle,
|
|
329
|
+
children,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function convertSidebar(block, relativePath) {
|
|
333
|
+
return {
|
|
334
|
+
type: 'sidebar',
|
|
335
|
+
title: block.getTitle() || undefined,
|
|
336
|
+
children: convertBlocks(block.getBlocks(), relativePath),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function convertExample(block, relativePath) {
|
|
340
|
+
const isCollapsible = block.isOption('collapsible');
|
|
341
|
+
if (isCollapsible) {
|
|
342
|
+
return {
|
|
343
|
+
type: 'collapsible',
|
|
344
|
+
title: block.getTitle() || undefined,
|
|
345
|
+
children: convertBlocks(block.getBlocks(), relativePath),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
type: 'example',
|
|
350
|
+
title: block.getTitle() || undefined,
|
|
351
|
+
children: convertBlocks(block.getBlocks(), relativePath),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function convertOpen(block, relativePath) {
|
|
355
|
+
// An open block is a generic container — just recurse into children
|
|
356
|
+
const children = convertBlocks(block.getBlocks(), relativePath);
|
|
357
|
+
if (children.length === 1)
|
|
358
|
+
return children[0];
|
|
359
|
+
// Return as an example-like container if it has a role
|
|
360
|
+
const role = block.getRole();
|
|
361
|
+
if (role) {
|
|
362
|
+
return {
|
|
363
|
+
type: 'paragraph',
|
|
364
|
+
contentHtml: block.getContent() || '',
|
|
365
|
+
icons: [],
|
|
366
|
+
role,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
// Just flatten open blocks
|
|
370
|
+
return {
|
|
371
|
+
type: 'example',
|
|
372
|
+
title: undefined,
|
|
373
|
+
children,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function convertPassthrough(block) {
|
|
377
|
+
return {
|
|
378
|
+
type: 'htmlPassthrough',
|
|
379
|
+
html: block.getContent() || '',
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Parses raw AsciiDoc source callout markers like `<1>` `<2>` from code.
|
|
384
|
+
* Returns cleaned source and callout positions.
|
|
385
|
+
*/
|
|
386
|
+
function parseCallouts(source) {
|
|
387
|
+
const callouts = [];
|
|
388
|
+
const calloutRegex = /\s*<(\d+)>\s*$/;
|
|
389
|
+
const lines = source.split('\n');
|
|
390
|
+
const cleanedLines = [];
|
|
391
|
+
for (let i = 0; i < lines.length; i++) {
|
|
392
|
+
let line = lines[i];
|
|
393
|
+
const match = line.match(calloutRegex);
|
|
394
|
+
if (match) {
|
|
395
|
+
callouts.push({ line: i + 1, number: parseInt(match[1], 10) });
|
|
396
|
+
line = line.replace(calloutRegex, '');
|
|
397
|
+
}
|
|
398
|
+
cleanedLines.push(line);
|
|
399
|
+
}
|
|
400
|
+
return { cleaned: cleanedLines.join('\n'), callouts };
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Extracts callout descriptions from a colist (callout list) block.
|
|
404
|
+
*/
|
|
405
|
+
function extractColistDescriptions(colist) {
|
|
406
|
+
const descriptions = [];
|
|
407
|
+
try {
|
|
408
|
+
const items = colist.getItems();
|
|
409
|
+
if (items && Array.isArray(items)) {
|
|
410
|
+
for (const item of items) {
|
|
411
|
+
if (typeof item.getText === 'function') {
|
|
412
|
+
descriptions.push(stripInlineHtml(item.getText()));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
catch (_a) {
|
|
418
|
+
// Colist may not support getItems on all versions
|
|
419
|
+
}
|
|
420
|
+
return descriptions;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Known Font Awesome brand icon names that live under the `brands/` style.
|
|
424
|
+
*/
|
|
425
|
+
const FA_BRAND_ICONS = new Set([
|
|
426
|
+
'github',
|
|
427
|
+
'twitter',
|
|
428
|
+
'facebook',
|
|
429
|
+
'linkedin',
|
|
430
|
+
'youtube',
|
|
431
|
+
'google',
|
|
432
|
+
'apple',
|
|
433
|
+
'android',
|
|
434
|
+
'windows',
|
|
435
|
+
'docker',
|
|
436
|
+
'npm',
|
|
437
|
+
'node-js',
|
|
438
|
+
'node',
|
|
439
|
+
'react',
|
|
440
|
+
'angular',
|
|
441
|
+
'vuejs',
|
|
442
|
+
'python',
|
|
443
|
+
'java',
|
|
444
|
+
'php',
|
|
445
|
+
'rust',
|
|
446
|
+
'golang',
|
|
447
|
+
'aws',
|
|
448
|
+
'slack',
|
|
449
|
+
'discord',
|
|
450
|
+
'reddit',
|
|
451
|
+
'stackoverflow',
|
|
452
|
+
'stack-overflow',
|
|
453
|
+
'gitlab',
|
|
454
|
+
'bitbucket',
|
|
455
|
+
'codepen',
|
|
456
|
+
'jsfiddle',
|
|
457
|
+
'bootstrap',
|
|
458
|
+
'sass',
|
|
459
|
+
'less',
|
|
460
|
+
'css3',
|
|
461
|
+
'html5',
|
|
462
|
+
'js',
|
|
463
|
+
'markdown',
|
|
464
|
+
'stripe',
|
|
465
|
+
'paypal',
|
|
466
|
+
'shopify',
|
|
467
|
+
'wordpress',
|
|
468
|
+
'medium',
|
|
469
|
+
'dev',
|
|
470
|
+
'figma',
|
|
471
|
+
'sketch',
|
|
472
|
+
'dribbble',
|
|
473
|
+
'behance',
|
|
474
|
+
'instagram',
|
|
475
|
+
'pinterest',
|
|
476
|
+
'tiktok',
|
|
477
|
+
'whatsapp',
|
|
478
|
+
'telegram',
|
|
479
|
+
'x-twitter',
|
|
480
|
+
]);
|
|
481
|
+
/**
|
|
482
|
+
* Extracts `<i class="fa fa-*">` icons from inline HTML and replaces them
|
|
483
|
+
* with `<!--icon:N-->` placeholders. The extracted icon data is stored in
|
|
484
|
+
* the InlineIcon array so the template can render CDNIcon components.
|
|
485
|
+
*/
|
|
486
|
+
function extractInlineIcons(html) {
|
|
487
|
+
const icons = [];
|
|
488
|
+
const processed = html.replace(/<i class="fa\s+fa-([a-z0-9-]+)([^"]*)"[^>]*><\/i>/gi, (_match, name, extraClasses) => {
|
|
489
|
+
let iconName = name;
|
|
490
|
+
let style = 'solid';
|
|
491
|
+
if (iconName.endsWith('-o')) {
|
|
492
|
+
iconName = iconName.slice(0, -2);
|
|
493
|
+
style = 'regular';
|
|
494
|
+
}
|
|
495
|
+
if (FA_BRAND_ICONS.has(iconName)) {
|
|
496
|
+
style = 'brands';
|
|
497
|
+
}
|
|
498
|
+
const size = extraClasses.includes('fa-2x') ? '2em' : undefined;
|
|
499
|
+
const idx = icons.length;
|
|
500
|
+
icons.push({ name: iconName, style, size });
|
|
501
|
+
return `<!--icon:${idx}-->`;
|
|
502
|
+
});
|
|
503
|
+
return { html: processed, icons };
|
|
504
|
+
}
|
|
505
|
+
function buildSectionTree(sections) {
|
|
506
|
+
return sections.map((section) => {
|
|
507
|
+
const id = section.getId() || slugify(section.getTitle() || '');
|
|
508
|
+
const title = section.getTitle() || '';
|
|
509
|
+
const level = section.getLevel();
|
|
510
|
+
// Extract plain text from the section's non-section blocks
|
|
511
|
+
const textContent = extractTextFromBlocks(section.getBlocks());
|
|
512
|
+
// Recurse into child sections
|
|
513
|
+
const childSections = section
|
|
514
|
+
.getBlocks()
|
|
515
|
+
.filter((b) => b.getContext() === 'section');
|
|
516
|
+
return {
|
|
517
|
+
id,
|
|
518
|
+
title,
|
|
519
|
+
level,
|
|
520
|
+
textContent,
|
|
521
|
+
children: buildSectionTree(childSections),
|
|
522
|
+
};
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
function extractTextFromBlocks(blocks) {
|
|
526
|
+
var _a;
|
|
527
|
+
const parts = [];
|
|
528
|
+
for (const block of blocks) {
|
|
529
|
+
const context = block.getContext();
|
|
530
|
+
if (context === 'section')
|
|
531
|
+
continue;
|
|
532
|
+
if (context === 'paragraph' || context === 'verse') {
|
|
533
|
+
const html = block.getContent();
|
|
534
|
+
if (html)
|
|
535
|
+
parts.push(stripInlineHtml(html));
|
|
536
|
+
}
|
|
537
|
+
else if (context === 'literal') {
|
|
538
|
+
const source = block.getSource();
|
|
539
|
+
if (source)
|
|
540
|
+
parts.push(normalizeUnicode(source));
|
|
541
|
+
}
|
|
542
|
+
else if (context === 'listing') {
|
|
543
|
+
const lang = block.getAttribute('language') || '';
|
|
544
|
+
const source = block.getSource();
|
|
545
|
+
if (source)
|
|
546
|
+
parts.push('```' + lang + '\n' + normalizeUnicode(source) + '\n```');
|
|
547
|
+
}
|
|
548
|
+
else if (context === 'ulist' || context === 'olist') {
|
|
549
|
+
try {
|
|
550
|
+
const items = block.getItems();
|
|
551
|
+
if (items && Array.isArray(items)) {
|
|
552
|
+
const isOrdered = context === 'olist';
|
|
553
|
+
const lines = [];
|
|
554
|
+
for (let idx = 0; idx < items.length; idx++) {
|
|
555
|
+
const item = items[idx];
|
|
556
|
+
if (typeof item.getText === 'function') {
|
|
557
|
+
const prefix = isOrdered ? `${idx + 1}. ` : '- ';
|
|
558
|
+
lines.push(prefix + stripInlineHtml(item.getText()));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (lines.length)
|
|
562
|
+
parts.push(lines.join('\n'));
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
catch (_b) {
|
|
566
|
+
/* skip if getItems fails */
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else if (context === 'dlist') {
|
|
570
|
+
try {
|
|
571
|
+
const items = block.getItems();
|
|
572
|
+
if (items && Array.isArray(items)) {
|
|
573
|
+
const lines = [];
|
|
574
|
+
for (const pair of items) {
|
|
575
|
+
if (Array.isArray(pair)) {
|
|
576
|
+
const [terms, definition] = pair;
|
|
577
|
+
if (Array.isArray(terms)) {
|
|
578
|
+
for (const t of terms) {
|
|
579
|
+
if (typeof t.getText === 'function') {
|
|
580
|
+
lines.push('**' + stripInlineHtml(t.getText()) + '**');
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (definition && typeof definition.getText === 'function') {
|
|
585
|
+
lines.push(': ' + stripInlineHtml(definition.getText()));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (lines.length)
|
|
590
|
+
parts.push(lines.join('\n'));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch (_c) {
|
|
594
|
+
/* skip */
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
else if (context === 'table') {
|
|
598
|
+
try {
|
|
599
|
+
parts.push(tableToMarkdown(block));
|
|
600
|
+
}
|
|
601
|
+
catch (_d) {
|
|
602
|
+
/* skip */
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
else if (context === 'admonition') {
|
|
606
|
+
const style = (((_a = block.getStyle) === null || _a === void 0 ? void 0 : _a.call(block)) || 'note').toUpperCase();
|
|
607
|
+
const innerBlocks = block.getBlocks();
|
|
608
|
+
const inner = (innerBlocks === null || innerBlocks === void 0 ? void 0 : innerBlocks.length)
|
|
609
|
+
? extractTextFromBlocks(innerBlocks)
|
|
610
|
+
: stripInlineHtml(block.getContent() || '');
|
|
611
|
+
if (inner) {
|
|
612
|
+
parts.push(`> [!${style}]\n> ${inner.replace(/\n/g, '\n> ')}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
else if (context === 'quote' ||
|
|
616
|
+
context === 'open' ||
|
|
617
|
+
context === 'example' ||
|
|
618
|
+
context === 'sidebar' ||
|
|
619
|
+
context === 'preamble') {
|
|
620
|
+
const innerBlocks = block.getBlocks();
|
|
621
|
+
if (innerBlocks && innerBlocks.length > 0) {
|
|
622
|
+
parts.push(extractTextFromBlocks(innerBlocks));
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
const html = block.getContent();
|
|
626
|
+
if (html)
|
|
627
|
+
parts.push(stripInlineHtml(html));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return parts.filter(Boolean).join('\n\n');
|
|
632
|
+
}
|
|
633
|
+
function tableToMarkdown(block) {
|
|
634
|
+
const rows = block.getRows();
|
|
635
|
+
const headRows = [];
|
|
636
|
+
const bodyRows = [];
|
|
637
|
+
const footRows = [];
|
|
638
|
+
const extractRow = (row) => row.map((cell) => { var _a; return stripInlineHtml(((_a = cell.getText) === null || _a === void 0 ? void 0 : _a.call(cell)) || '').replace(/\|/g, '\\|'); });
|
|
639
|
+
for (const row of rows.head || []) {
|
|
640
|
+
if (Array.isArray(row))
|
|
641
|
+
headRows.push(extractRow(row));
|
|
642
|
+
}
|
|
643
|
+
for (const row of rows.body || []) {
|
|
644
|
+
if (Array.isArray(row))
|
|
645
|
+
bodyRows.push(extractRow(row));
|
|
646
|
+
}
|
|
647
|
+
for (const row of rows.foot || []) {
|
|
648
|
+
if (Array.isArray(row))
|
|
649
|
+
footRows.push(extractRow(row));
|
|
650
|
+
}
|
|
651
|
+
const allRows = [...headRows, ...bodyRows, ...footRows];
|
|
652
|
+
if (allRows.length === 0)
|
|
653
|
+
return '';
|
|
654
|
+
const colCount = Math.max(...allRows.map((r) => r.length));
|
|
655
|
+
const pad = (row) => {
|
|
656
|
+
while (row.length < colCount)
|
|
657
|
+
row.push('');
|
|
658
|
+
return row;
|
|
659
|
+
};
|
|
660
|
+
const lines = [];
|
|
661
|
+
if (headRows.length > 0) {
|
|
662
|
+
for (const row of headRows) {
|
|
663
|
+
lines.push('| ' + pad(row).join(' | ') + ' |');
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
// Use first body row as header if no explicit header
|
|
668
|
+
const firstRow = bodyRows.shift();
|
|
669
|
+
if (firstRow) {
|
|
670
|
+
lines.push('| ' + pad(firstRow).join(' | ') + ' |');
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// Separator row
|
|
674
|
+
lines.push('| ' + Array(colCount).fill('---').join(' | ') + ' |');
|
|
675
|
+
for (const row of bodyRows) {
|
|
676
|
+
lines.push('| ' + pad(row).join(' | ') + ' |');
|
|
677
|
+
}
|
|
678
|
+
for (const row of footRows) {
|
|
679
|
+
lines.push('| ' + pad(row).join(' | ') + ' |');
|
|
680
|
+
}
|
|
681
|
+
return lines.join('\n');
|
|
682
|
+
}
|
|
683
|
+
function extractLinks(html, _relativePath) {
|
|
684
|
+
const links = [];
|
|
685
|
+
const linkRegex = /<a\s+[^>]*href="([^"]*)"[^>]*>([^<]*)<\/a>/gi;
|
|
686
|
+
let match;
|
|
687
|
+
while ((match = linkRegex.exec(html)) !== null) {
|
|
688
|
+
const href = match[1];
|
|
689
|
+
const text = match[2];
|
|
690
|
+
let type = 'link';
|
|
691
|
+
if (href.startsWith('#')) {
|
|
692
|
+
type = 'xref';
|
|
693
|
+
}
|
|
694
|
+
else if (href.startsWith('http://') || href.startsWith('https://')) {
|
|
695
|
+
type = 'url';
|
|
696
|
+
}
|
|
697
|
+
links.push({ href, text, type });
|
|
698
|
+
}
|
|
699
|
+
return links;
|
|
700
|
+
}
|
|
701
|
+
function normalizeImageSrc(src, relativePath) {
|
|
702
|
+
if (!src)
|
|
703
|
+
return src;
|
|
704
|
+
if (src.startsWith('/') ||
|
|
705
|
+
src.startsWith('//') ||
|
|
706
|
+
src.startsWith('data:') ||
|
|
707
|
+
/^[a-z][a-z\d+\-.]*:/i.test(src)) {
|
|
708
|
+
return src;
|
|
709
|
+
}
|
|
710
|
+
const baseDir = path.posix.dirname(relativePath);
|
|
711
|
+
const resolvedPath = path.posix.normalize(path.posix.join(baseDir, src));
|
|
712
|
+
return `/${resolvedPath.replace(/^\/+/, '')}`;
|
|
713
|
+
}
|
|
714
|
+
function stripInlineHtml(text) {
|
|
715
|
+
return decodeHtmlEntities(text
|
|
716
|
+
.replace(/<span class="icon"><i class="fa[^"]*fa-([^"\s]+)"[^>]*><\/i><\/span>/gi, (_m, name) => `:${name}:`)
|
|
717
|
+
.replace(/<i class="fa[^"]*fa-([^"\s]+)"[^>]*><\/i>/gi, (_m, name) => `:${name}:`)
|
|
718
|
+
.replace(/<[^>]+>/g, ''));
|
|
719
|
+
}
|
|
720
|
+
function decodeHtmlEntities(text) {
|
|
721
|
+
return normalizeUnicode(text
|
|
722
|
+
.replace(/&#(\d+);/g, (_m, code) => String.fromCodePoint(Number(code)))
|
|
723
|
+
.replace(/&#x([0-9a-f]+);/gi, (_m, hex) => String.fromCodePoint(parseInt(hex, 16)))
|
|
724
|
+
.replace(/&/g, '&')
|
|
725
|
+
.replace(/</g, '<')
|
|
726
|
+
.replace(/>/g, '>')
|
|
727
|
+
.replace(/"/g, '"')
|
|
728
|
+
.replace(/'/g, "'"));
|
|
729
|
+
}
|
|
730
|
+
function normalizeUnicode(text) {
|
|
731
|
+
return text
|
|
732
|
+
.replace(/\u2014/g, '--')
|
|
733
|
+
.replace(/\u2013/g, '-')
|
|
734
|
+
.replace(/\u2018|\u2019/g, "'")
|
|
735
|
+
.replace(/\u201C|\u201D/g, '"')
|
|
736
|
+
.replace(/\u2026/g, '...')
|
|
737
|
+
.replace(/\u00A0/g, ' ');
|
|
738
|
+
}
|
|
739
|
+
function slugify(text) {
|
|
740
|
+
return text
|
|
741
|
+
.toLowerCase()
|
|
742
|
+
.replace(/[^\w\s-]/g, '')
|
|
743
|
+
.replace(/\s+/g, '-')
|
|
744
|
+
.replace(/-+/g, '-')
|
|
745
|
+
.replace(/^-|-$/g, '');
|
|
746
|
+
}
|
|
747
|
+
//# sourceMappingURL=asciidoc-parser.js.map
|