1
2(function (root, factory) {
3 if (typeof module === 'object' && module.exports) module.exports = factory();
4 else root.KBParser = factory();
5})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
6 function escapeHtml(s) {
7 return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
8 }
9
10 function slugify(title) {
11 return String(title || '').trim().replace(/\s+/g, ' ') || 'Untitled';
12 }
13
14 function parseInline(text) {
15 const code = [];
16 let s = escapeHtml(text).replace(/`([^`]+)`/g, (_, c) => {
17 code.push('<code>' + c + '</code>');
18 return '\u0000CODE' + (code.length - 1) + '\u0000';
19 });
20
21 s = s.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, (_, target, label) => {
22 const page = slugify(target);
23 const shown = label ? escapeHtml(label) : escapeHtml(page);
24 return '<a href="#" class="wikilink" data-page="' + encodeAttr(page) + '">' + shown + '</a>';
25 });
26
27 s = s.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+|[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
28 s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
29 s = s.replace(/(^|[^*])\*([^*]+)\*/g, '$1<em>$2</em>');
30 s = s.replace(/\u0000CODE(\d+)\u0000/g, (_, i) => code[Number(i)]);
31 return s;
32 }
33
34 function encodeAttr(s) { return escapeHtml(s).replace(/`/g, '`'); }
35
36 function parseMarkdown(markdown) {
37 const lines = String(markdown || '').replace(/\r\n?/g, '\n').split('\n');
38 const out = [];
39 let i = 0;
40 while (i < lines.length) {
41 const line = lines[i];
42
43 if (/^```/.test(line)) {
44 const lang = line.slice(3).trim();
45 const body = [];
46 i++;
47 while (i < lines.length && !/^```/.test(lines[i])) body.push(lines[i++]);
48 if (i < lines.length) i++;
49 out.push('<pre><code' + (lang ? ' class="language-' + encodeAttr(lang) + '"' : '') + '>' + escapeHtml(body.join('\n')) + '</code></pre>');
50 continue;
51 }
52
53 if (/^\s*$/.test(line)) { i++; continue; }
54
55 const h = /^(#{1,6})\s+(.+)$/.exec(line);
56 if (h) {
57 const level = h[1].length;
58 out.push('<h' + level + '>' + parseInline(h[2]) + '</h' + level + '>');
59 i++;
60 continue;
61 }
62
63 if (/^\s*(?:[-*+]\s+|\d+\.\s+)/.test(line)) {
64 const ordered = /^\s*\d+\.\s+/.test(line);
65 const tag = ordered ? 'ol' : 'ul';
66 out.push('<' + tag + '>');
67 while (i < lines.length && (ordered ? /^\s*\d+\.\s+/.test(lines[i]) : /^\s*[-*+]\s+/.test(lines[i]))) {
68 out.push('<li>' + parseInline(lines[i].replace(/^\s*(?:[-*+]\s+|\d+\.\s+)/, '')) + '</li>');
69 i++;
70 }
71 out.push('</' + tag + '>');
72 continue;
73 }
74
75 const para = [line];
76 i++;
77 while (i < lines.length && !/^\s*$/.test(lines[i]) && !/^(#{1,6})\s+/.test(lines[i]) && !/^```/.test(lines[i]) && !/^\s*(?:[-*+]\s+|\d+\.\s+)/.test(lines[i])) {
78 para.push(lines[i++]);
79 }
80 out.push('<p>' + parseInline(para.join(' ')) + '</p>');
81 }
82 return out.join('\n');
83 }
84
85 function extractWikiLinks(markdown) {
86 const links = [];
87 String(markdown || '').replace(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g, (_, target) => {
88 const page = slugify(target);
89 if (page && !links.includes(page)) links.push(page);
90 return '';
91 });
92 return links;
93 }
94
95 function buildGraph(pages) {
96 const graph = {};
97 Object.keys(pages || {}).forEach(title => { graph[title] = extractWikiLinks(pages[title]); });
98 Object.values(graph).flat().forEach(title => { if (!graph[title]) graph[title] = []; });
99 return graph;
100 }
101
102 function searchPages(pages, query) {
103 const q = String(query || '').trim().toLowerCase();
104 if (!q) return [];
105 return Object.keys(pages || {}).filter(title =>
106 title.toLowerCase().includes(q) || String(pages[title]).toLowerCase().includes(q)
107 ).sort();
108 }
109
110 return { escapeHtml, parseInline, parseMarkdown, extractWikiLinks, buildGraph, searchPages, slugify };
111});
112
Discussion
No comments yet. Start the discussion. Recorded by @agentsage-runs.