Files
workinf_Blender_Wasm/web/app/public/manuals/manual.js
mes123456 b932db7642 feat(manual): add the illustrated Web Blender HTML user manual
A single-page 28-chapter manual generated from manual.json covers the
11 workspaces, all React editors, OPFS save/reopen, explicit local
downloads, import/export routes, preferences/keymap and the capability
matrix. It reuses verified course screenshots and adds 14 fresh
Chromium captures for shell, storage, area, camera, preferences, IO and
troubleshooting chapters.

Manual-driven fixes: Save As now states OPFS-only, GLTF import is
restored, quick toolbar honors its preference, startup workspace is
applied, Properties tabs jump to sections, Outliner fallback search
filters, and the editor-header ⋮ opens the area menu. Help and the
tutorial landing link the new manual; the PDF remains as an archive.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-13 11:58:40 +08:00

39 lines
1.9 KiB
JavaScript

(() => {
const sidebar = document.querySelector('.manual-sidebar');
const toggle = document.querySelector('.manual-menu-toggle');
const search = document.querySelector('.manual-search input');
const status = document.querySelector('.manual-search-status');
const links = Array.from(document.querySelectorAll('.manual-nav a[data-nav-id]'));
const articles = Array.from(document.querySelectorAll('.manual-chapter'));
const apply = () => {
const query = (search?.value || '').trim().toLowerCase();
let matches = 0;
for (const link of links) {
const article = document.querySelector('#' + CSS.escape(link.dataset.navId));
const text = (article?.dataset.search || link.textContent || '').toLowerCase();
const hit = !query || text.includes(query) || (link.textContent || '').toLowerCase().includes(query);
link.hidden = !hit;
if (hit) matches += 1;
}
for (const article of articles) {
const text = (article.dataset.search || '').toLowerCase();
article.hidden = Boolean(query) && !text.includes(query);
}
if (status) status.textContent = query ? matches + ' 个匹配' : '';
};
search?.addEventListener('input', apply);
toggle?.addEventListener('click', () => sidebar?.classList.toggle('open'));
window.addEventListener('keydown', (event) => {
if (event.key === '/' && document.activeElement !== search) { event.preventDefault(); search?.focus(); }
if (event.key === 'Escape' && search) { search.value = ''; apply(); search.blur(); }
});
const observer = 'IntersectionObserver' in window ? new IntersectionObserver((entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const id = entry.target.id;
for (const link of links) link.classList.toggle('active', link.dataset.navId === id);
}
}, { rootMargin: '-30% 0px -60% 0px' }) : null;
for (const article of articles) observer?.observe(article);
})();