diff --git a/Makefile b/Makefile
index c50a5c26da16f5..32eafa993ed8aa 100644
--- a/Makefile
+++ b/Makefile
@@ -821,8 +821,13 @@ VERSION=v$(RAWVER)
.NOTPARALLEL: doc-only
doc-only: $(apidoc_dirs) $(apidocs_html) $(apidocs_json) out/doc/api/all.html out/doc/api/all.json out/doc/apilinks.json ## Builds the docs with the local or the global Node.js binary.
+.PHONY: doc-add-js
+doc-add-js:
+ cp tools/doc/add_toc_tree_to_leftnav.js out/doc/api/
+ $(NODE_EXE) tools/doc/addjs2html.mjs
+
.PHONY: doc
-doc: $(NODE_EXE) doc-only ## Build Node.js, and then build the documentation with the new binary.
+doc: $(NODE_EXE) doc-only doc-add-js ## Build Node.js, and then build the documentation with the new binary.
out/doc:
mkdir -p $@
diff --git a/tools/doc/add_toc_tree_to_leftnav.js b/tools/doc/add_toc_tree_to_leftnav.js
new file mode 100644
index 00000000000000..007fdd9d3dc5c8
--- /dev/null
+++ b/tools/doc/add_toc_tree_to_leftnav.js
@@ -0,0 +1,84 @@
+
+function add_toc_tree_to_leftnav(force){
+ if (force) document.querySelector('#toc_in_leftnav')?.remove();
+
+ var toc_in_leftnav = document.querySelector('#toc_in_leftnav');
+ var toc_tree, toc_tree_ul, left_nav_tree;
+ if (!toc_in_leftnav) {
+ console.log('href', document.location.href, 'nodejs doc add toc_tree');
+
+ const leftnav = document.querySelector('#column2.interior');
+ if (leftnav) {
+ leftnav.style.overflow = 'scroll'; // default x is hidden
+ }
+
+ toc_tree = document.querySelector('#toc-picker > li')?.cloneNode(true);
+ left_nav_tree = document.querySelector('#content > #column2');
+ if (!toc_tree || !left_nav_tree) {
+ console.warn('not found nodejs doc ?')
+ return false;
+ }
+
+ toc_tree.id = 'toc_in_leftnav';
+ left_nav_tree.insertAdjacentElement('afterbegin', toc_tree);
+
+ toc_tree.insertAdjacentHTML('afterbegin', `
+
+
+
+
+
+
+
`);
+ toc_tree_ul = toc_tree.querySelector('ul');
+
+ toc_tree_ul.querySelectorAll('li > ul').forEach(ul => {
+ const li = ul.parentElement;
+ const detail1 = document.createElement('details');
+ const summ = document.createElement('summary');
+ detail1.title = 'show details ' + (li.querySelector('a')?.textContent || '');
+ summ.innerHTML = '';
+ summ.style.color = 'lightyellow';
+ detail1.open = false;
+ detail1.appendChild(summ);
+ detail1.appendChild(ul);
+ li.appendChild(detail1);
+ });
+
+ // TODO use css?
+ toc_tree_ul.querySelectorAll('*').forEach(x => {
+ x.style.marginTop = '0px';
+ x.style.marginBottom = '0px';
+ x.style.paddingTop = '0px';
+ x.style.paddingBottom = '0px';
+ x.style.textWrapMode = 'nowrap';
+ x.style.fontSize = 'small';
+ });
+ };
+}
+
+document.addEventListener('DOMContentLoaded', () => add_toc_tree_to_leftnav())
+
+
+function setDetailOpen(stat) {
+ document.getElementById('toc_in_leftnav').querySelectorAll('details').forEach(x => x.open=stat);
+}
+
+function setLeftNavWidth(inc_px) {
+ const leftnav = document.querySelector('#column2.interior');
+ const rightcont = document.querySelector('#column1.interior');
+ let cur_width = parseInt(leftnav.style.width);
+ if (isNaN(cur_width)) {
+ cur_width = 234; // in css, default 234px
+ }
+
+ let pxstr = '';
+ if (typeof inc_px == 'number' && !isNaN(inc_px) && inc_px != 0) {
+ cur_width += inc_px;
+ pxstr = '' + cur_width + 'px';
+ }
+
+ leftnav.style.width = pxstr;
+ rightcont.style.marginLeft = pxstr;
+}
+
diff --git a/tools/doc/addjs2html.mjs b/tools/doc/addjs2html.mjs
new file mode 100644
index 00000000000000..936d184472a2e0
--- /dev/null
+++ b/tools/doc/addjs2html.mjs
@@ -0,0 +1,35 @@
+import fs from 'node:fs'
+import fsp from 'node:fs/promises'
+
+const fns = fs.globSync('out/doc/api/*.html')
+
+const scripts = '';
+
+const last_nbyte = 512
+const buffer = Buffer.alloc(last_nbyte)
+let no = 0
+
+for (let fn of fns) {
+ if (fn.endsWith('all.html')) continue;
+
+ let fh
+ try {
+ fh = await fsp.open(fn, 'a+')
+ let st = await fh.stat()
+ if (st.size < last_nbyte) continue;
+
+ await fh.read({buffer, position: st.size - last_nbyte})
+ if (buffer.toString().includes(scripts)) continue;
+ await fh.appendFile('\n' + scripts)
+ console.log(` * append