|
1 | | -/* Centralized markdown renderer for the docs. |
2 | | - Loads marked.js if necessary, then renders the markdown embedded |
3 | | - in the page's `#md` element into `#content` and normalizes heading ids. */ |
4 | | -(function(){ |
5 | | - function slugify(text) { |
6 | | - return (text || '') |
7 | | - .trim() |
8 | | - .toLowerCase() |
9 | | - .replace(/<[^>]+>/g, '') |
10 | | - .replace(/[^a-z0-9\s-]/g, '') |
11 | | - .replace(/\s+/g, '-'); |
12 | | - } |
13 | | - |
14 | | - function ensureTocStyle() { |
15 | | - if (document.getElementById('generated-toc-style')) return; |
16 | | - var style = document.createElement('style'); |
17 | | - style.id = 'generated-toc-style'; |
18 | | - style.textContent = '\n' + |
19 | | - '.generated-toc { padding-left: 1.2rem; }\n' + |
20 | | - '.generated-toc > li { margin: 0.6rem 0; }\n' + |
21 | | - '.generated-toc li ul { margin-top: 0.25rem; margin-bottom: 0.25rem; }\n'; |
22 | | - document.head.appendChild(style); |
23 | | - } |
24 | | - |
25 | | - function findTocHeading(root) { |
26 | | - return Array.from(root.querySelectorAll('h2')).find(function(h){ |
27 | | - return (h.textContent || '').trim().toLowerCase() === 'table of contents'; |
28 | | - }); |
29 | | - } |
30 | | - |
31 | | - function buildTocList(root, tocHeading) { |
32 | | - var tocLevels = new Set([2,3,4]); |
33 | | - var items = Array.from(root.querySelectorAll('h2,h3,h4')).filter(function(h){ return h !== tocHeading; }); |
34 | | - if (items.length === 0) return null; |
35 | | - |
36 | | - var tocRoot = document.createElement('ul'); |
37 | | - tocRoot.className = 'generated-toc'; |
38 | | - var listStack = [tocRoot]; |
39 | | - var currentLevel = 2; |
40 | | - |
41 | | - items.forEach(function(h){ |
42 | | - var level = Number(h.tagName.slice(1)); |
43 | | - if (!tocLevels.has(level)) return; |
44 | | - |
45 | | - while (currentLevel < level) { |
46 | | - var parentList = listStack[listStack.length - 1]; |
47 | | - var parentLi = parentList.lastElementChild; |
48 | | - if (!parentLi || parentLi.tagName.toLowerCase() !== 'li') { |
49 | | - parentLi = document.createElement('li'); |
50 | | - parentLi.textContent = ''; |
51 | | - parentList.appendChild(parentLi); |
52 | | - } |
53 | | - var nested = document.createElement('ul'); |
54 | | - parentLi.appendChild(nested); |
55 | | - listStack.push(nested); |
56 | | - currentLevel += 1; |
57 | | - } |
58 | | - |
59 | | - while (currentLevel > level) { |
60 | | - listStack.pop(); |
61 | | - currentLevel -= 1; |
62 | | - } |
63 | | - |
64 | | - var li = document.createElement('li'); |
65 | | - var a = document.createElement('a'); |
66 | | - a.href = '#' + h.id; |
67 | | - a.textContent = (h.textContent || '').trim(); |
68 | | - li.appendChild(a); |
69 | | - listStack[listStack.length - 1].appendChild(li); |
70 | | - }); |
71 | | - |
72 | | - return tocRoot; |
73 | | - } |
74 | | - |
75 | | - function replaceTocBlock(root) { |
76 | | - var tocHeading = findTocHeading(root); |
77 | | - if (!tocHeading) return; |
78 | | - |
79 | | - var stop = tocHeading.nextElementSibling; |
80 | | - while (stop && stop.tagName.toLowerCase() !== 'hr') { |
81 | | - stop = stop.nextElementSibling; |
82 | | - } |
83 | | - |
84 | | - var node = tocHeading.nextSibling; |
85 | | - while (node && node !== stop) { |
86 | | - var next = node.nextSibling; |
87 | | - node.remove(); |
88 | | - node = next; |
89 | | - } |
90 | | - |
91 | | - ensureTocStyle(); |
92 | | - var tocList = buildTocList(root, tocHeading); |
93 | | - if (!tocList) return; |
94 | | - |
95 | | - if (stop) { |
96 | | - root.insertBefore(tocList, stop); |
97 | | - } else { |
98 | | - root.appendChild(tocList); |
99 | | - } |
100 | | - } |
101 | | - |
102 | | - function render() { |
103 | | - var mdEl = document.getElementById('md'); |
104 | | - if (!mdEl) return; |
105 | | - var md = mdEl.textContent; |
106 | | - var rendered = (window.marked && typeof marked.parse === 'function') ? marked.parse(md) : md; |
107 | | - var container = document.getElementById('content'); |
108 | | - if (container) container.innerHTML = rendered; |
109 | | - |
110 | | - var headings = container ? container.querySelectorAll('h1,h2,h3,h4,h5,h6') : []; |
111 | | - Array.prototype.forEach.call(headings, function(h){ |
112 | | - if (!h.id || h.id.trim() === '') { |
113 | | - h.id = slugify(h.textContent || h.innerText || ''); |
114 | | - } |
115 | | - }); |
116 | | - |
117 | | - replaceTocBlock(container); |
118 | | - } |
119 | | - |
120 | | - function ensureMarked(cb) { |
121 | | - if (window.marked && typeof marked.parse === 'function') { |
122 | | - cb(); |
123 | | - return; |
124 | | - } |
125 | | - var s = document.createElement('script'); |
126 | | - s.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js'; |
127 | | - s.onload = cb; |
128 | | - s.onerror = cb; |
129 | | - document.head.appendChild(s); |
130 | | - } |
131 | | - |
132 | | - document.addEventListener('DOMContentLoaded', function(){ |
133 | | - ensureMarked(render); |
134 | | - }); |
135 | | -})(); |
| 1 | +/* Centralized markdown renderer + fully auto-generated multi-file TOC. |
| 2 | + - Renders the markdown in #md into #content (using marked.js). |
| 3 | + - Assigns github-slugger-style unique ids to every heading. |
| 4 | + - Builds a Table of Contents covering EVERY spec file and ALL of their |
| 5 | + subheadings, straight from window.SPEC_INDEX (generated from source). |
| 6 | + Links belonging to the current file are marked .current (bold). */ |
| 7 | +(function () { |
| 8 | + 'use strict'; |
| 9 | + |
| 10 | + function slugify(text) { |
| 11 | + return (text || '') |
| 12 | + .trim() |
| 13 | + .toLowerCase() |
| 14 | + .replace(/<[^>]+>/g, '') |
| 15 | + .replace(/[^a-z0-9\s-]/g, '') |
| 16 | + .replace(/\s+/g, '-'); |
| 17 | + } |
| 18 | + |
| 19 | + function ensureTocStyle() { |
| 20 | + if (document.getElementById('generated-toc-style')) return; |
| 21 | + var style = document.createElement('style'); |
| 22 | + style.id = 'generated-toc-style'; |
| 23 | + style.textContent = |
| 24 | + '\n' + |
| 25 | + '.generated-toc { padding-left: 1.2rem; }\n' + |
| 26 | + '.generated-toc > li { margin: 0.6rem 0; }\n' + |
| 27 | + '.generated-toc li ul { margin-top: 0.25rem; margin-bottom: 0.25rem; }\n' + |
| 28 | + '.generated-toc a.current { font-weight: 700; }\n' + |
| 29 | + '.toc-title { text-align: left; }\n'; |
| 30 | + document.head.appendChild(style); |
| 31 | + } |
| 32 | + |
| 33 | + function render() { |
| 34 | + var mdEl = document.getElementById('md'); |
| 35 | + if (!mdEl) return; |
| 36 | + var md = mdEl.textContent; |
| 37 | + var rendered = |
| 38 | + window.marked && typeof marked.parse === 'function' ? marked.parse(md) : md; |
| 39 | + var container = document.getElementById('content'); |
| 40 | + if (container) container.innerHTML = rendered; |
| 41 | + |
| 42 | + var headings = container |
| 43 | + ? container.querySelectorAll('h1,h2,h3,h4,h5,h6') |
| 44 | + : []; |
| 45 | + var used = Object.create(null); |
| 46 | + Array.prototype.forEach.call(headings, function (h) { |
| 47 | + var slug = slugify(h.textContent || h.innerText || ''); |
| 48 | + var cand = slug, |
| 49 | + n = 1; |
| 50 | + while (used[cand]) { |
| 51 | + cand = slug + '-' + n; |
| 52 | + n++; |
| 53 | + } |
| 54 | + used[cand] = true; |
| 55 | + h.id = cand; |
| 56 | + }); |
| 57 | + |
| 58 | + buildMultiFileToc(container); |
| 59 | + } |
| 60 | + |
| 61 | + function buildMultiFileToc(container) { |
| 62 | + var index = window.SPEC_INDEX; |
| 63 | + if (!index || !index.length || !container) return; |
| 64 | + |
| 65 | + var cur = (location.pathname.split('/').pop() || location.href.split('/').pop() || '') |
| 66 | + .toLowerCase(); |
| 67 | + |
| 68 | + ensureTocStyle(); |
| 69 | + |
| 70 | + var nav = document.createElement('nav'); |
| 71 | + nav.id = 'spec-toc'; |
| 72 | + |
| 73 | + var title = document.createElement('h2'); |
| 74 | + title.className = 'toc-title'; |
| 75 | + title.textContent = 'Table of contents'; |
| 76 | + nav.appendChild(title); |
| 77 | + |
| 78 | + var rootUl = document.createElement('ul'); |
| 79 | + rootUl.className = 'generated-toc'; |
| 80 | + |
| 81 | + // One continuous stack across every file so each file's top-level |
| 82 | + // heading is a sibling of the others (no empty wrapper <li>). |
| 83 | + var listStack = [rootUl]; |
| 84 | + var currentLevel = 2; |
| 85 | + |
| 86 | + index.forEach(function (fileEntry) { |
| 87 | + (fileEntry.headings || []).forEach(function (h) { |
| 88 | + var level = h.level; |
| 89 | + while (currentLevel < level) { |
| 90 | + var parentList = listStack[listStack.length - 1]; |
| 91 | + var parentLi = parentList.lastElementChild; |
| 92 | + if (!parentLi || parentLi.tagName.toLowerCase() !== 'li') { |
| 93 | + parentLi = document.createElement('li'); |
| 94 | + parentList.appendChild(parentLi); |
| 95 | + } |
| 96 | + var nested = document.createElement('ul'); |
| 97 | + parentLi.appendChild(nested); |
| 98 | + listStack.push(nested); |
| 99 | + currentLevel += 1; |
| 100 | + } |
| 101 | + while (currentLevel > level) { |
| 102 | + listStack.pop(); |
| 103 | + currentLevel -= 1; |
| 104 | + } |
| 105 | + var li = document.createElement('li'); |
| 106 | + var a = document.createElement('a'); |
| 107 | + var sameFile = (fileEntry.file || '').toLowerCase() === cur; |
| 108 | + a.href = (sameFile ? '#' : fileEntry.file + '#') + h.slug; |
| 109 | + a.textContent = h.text; |
| 110 | + if (sameFile) a.className = 'current'; |
| 111 | + li.appendChild(a); |
| 112 | + listStack[listStack.length - 1].appendChild(li); |
| 113 | + }); |
| 114 | + }); |
| 115 | + |
| 116 | + nav.appendChild(rootUl); |
| 117 | + |
| 118 | + var hr = document.createElement('hr'); |
| 119 | + |
| 120 | + var h1 = container.querySelector('h1'); |
| 121 | + var ref = h1 && h1.nextSibling ? h1.nextSibling : container.firstChild; |
| 122 | + container.insertBefore(hr, ref); |
| 123 | + container.insertBefore(nav, ref); |
| 124 | + } |
| 125 | + |
| 126 | + function ensureMarked(cb) { |
| 127 | + if (window.marked && typeof marked.parse === 'function') { |
| 128 | + cb(); |
| 129 | + return; |
| 130 | + } |
| 131 | + var s = document.createElement('script'); |
| 132 | + s.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js'; |
| 133 | + s.onload = cb; |
| 134 | + s.onerror = cb; |
| 135 | + document.head.appendChild(s); |
| 136 | + } |
| 137 | + |
| 138 | + document.addEventListener('DOMContentLoaded', function () { |
| 139 | + ensureMarked(render); |
| 140 | + }); |
| 141 | +})(); |
0 commit comments