diff --git a/packages/omi-templates/pnpm-lock.yaml b/packages/omi-templates/pnpm-lock.yaml index eec90f64f..0213cbb03 100644 --- a/packages/omi-templates/pnpm-lock.yaml +++ b/packages/omi-templates/pnpm-lock.yaml @@ -33,8 +33,8 @@ importers: specifier: ^14.0.0 version: 14.1.0 omi: - specifier: 7.7.0 - version: 7.7.0 + specifier: 7.7.3 + version: 7.7.3 omi-ripple: specifier: ^0.1.2 version: 0.1.2 @@ -327,42 +327,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.0': resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.0': resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.0': resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.0': resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.0': resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.0': resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} @@ -938,11 +932,11 @@ packages: omi-suspense@0.1.4: resolution: {integrity: sha512-zijzn+H3e6+Tkbs2Eda/vEod/kH+9lOLBntiDBauMQyRh3HZnMjq1dClI9QySUD9c4LQneo8rQolG6kkuqh/Yw==} - omi@7.7.0: - resolution: {integrity: sha512-OrU7qKVoAc9dFmCjkdcYFmKvWLdpvaHNSmcHSNuG5yx8fupAg0MksVK/fZ1QDTMGYxCnZvJT1T25esI4NdGJrA==} + omi@7.7.3: + resolution: {integrity: sha512-lohYvoHnDvyRHUfDh5sRzXE0o/eDJh40HXohAG64/iamaeIt8/NQNSo3lHHtg3akgksHQejkQR/dvK3k28W5CQ==} - omi@7.7.2: - resolution: {integrity: sha512-AZNVUKur2lIotRM5H8XeYOm/FF1TcOX/sT4+WalA7vDeyGGslxqv4b4zXHx/ytdwxBkMxuYO2KcjxdJdhKG5TQ==} + omi@7.7.5: + resolution: {integrity: sha512-e5SaFstMi65Cw2QecWGa9BLae3hpIu5BFlUw5pX7XQhQIqJ4RurWinfC/MzsBKBlbSzdjrST9eFYJUEoEUElng==} once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -2070,24 +2064,24 @@ snapshots: omi-ripple@0.1.2: dependencies: - omi: 7.7.2 + omi: 7.7.5 omi-router@4.1.8: dependencies: - omi: 7.7.2 + omi: 7.7.5 path-to-regexp: 6.3.0 omi-suspense@0.1.4: dependencies: - omi: 7.7.2 + omi: 7.7.5 - omi@7.7.0: + omi@7.7.3: dependencies: construct-style-sheets-polyfill: 3.0.1 reactive-signal: 2.0.1 weakmap-polyfill: 2.0.4 - omi@7.7.2: + omi@7.7.5: dependencies: construct-style-sheets-polyfill: 3.0.1 reactive-signal: 2.0.1 diff --git a/packages/omi-templates/src/pages/food.tsx b/packages/omi-templates/src/pages/food.tsx index 33c5843bb..8b34cfb5d 100644 --- a/packages/omi-templates/src/pages/food.tsx +++ b/packages/omi-templates/src/pages/food.tsx @@ -46,13 +46,6 @@ const techniques = signal([ }, ]) -const navItems = [ - { label: '首页' }, - { label: '菜谱' }, - { label: '烹饪技艺' }, - { label: '关于我们' }, - { label: '联系我们' }, -] export function Food() { return ( diff --git a/packages/omi-templates/src/pages/product-docs.tsx b/packages/omi-templates/src/pages/product-docs.tsx index f9ff0cb8d..834c31198 100644 --- a/packages/omi-templates/src/pages/product-docs.tsx +++ b/packages/omi-templates/src/pages/product-docs.tsx @@ -9,8 +9,10 @@ const MdIt = MarkdownIt.default ? MarkdownIt.default : MarkdownIt type NavTreeNode = { title: string + level: number children: NavTreeNode[] } +//这里新加入的level是为了对应markdown标题 type Props = { lang: string @@ -27,10 +29,10 @@ export class ProductDocs extends Component { navTree: NavTreeNode active: [string, string] } = { - markdownContent: '', - navTree: { title: '', children: [] }, - active: ['', ''], - } + markdownContent: '', + navTree: { title: '', level: 1, children: [] }, + active: ['', ''], + } @bind async onChange(evt: CustomEvent) { @@ -46,8 +48,85 @@ export class ProductDocs extends Component { install() { this.state.markdownContent = this.props.markdownContent - this.setNavTree() + // 添加滚动监听 + window.addEventListener('scroll', this.handleScroll) + } + + // 在组件销毁时移除监听器 + uninstall() { + window.removeEventListener('scroll', this.handleScroll) + } + + @bind + handleScroll() { + const mdDocs = this.rootElement?.querySelector('md-docs') as HTMLElement & { + rootElement: HTMLElement + } + if (!mdDocs) return + + const h2Elements = mdDocs.rootElement.getElementsByTagName('h2') + const h3Elements = mdDocs.rootElement.getElementsByTagName('h3') + + const threshold = window.innerHeight / 4 + + let activeH2: string | null = null + let activeH3: string | null = null + + // 找到当前活动的 h2 + for (let i = h2Elements.length - 1; i >= 0; i--) { + const h2 = h2Elements[i] + const rect = h2.getBoundingClientRect() + + if (rect.top <= threshold) { + activeH2 = h2.textContent + break + } + } + + // 如果找到活动的 h2,则在其范围内查找 h3 + if (activeH2) { + const activeH2Node = this.state.navTree.children.find(node => node.title === activeH2) + if (activeH2Node) { + const validH3Titles = new Set(activeH2Node.children.map(child => child.title)) + + // 获取当前 h2 元素的位置 + const currentH2 = Array.from(h2Elements).find(h2 => h2.textContent === activeH2) + const currentH2Rect = currentH2?.getBoundingClientRect() + + // 获取下一个 h2 元素的位置 + const nextH2Index = Array.from(h2Elements).findIndex(h2 => h2.textContent === activeH2) + 1 + const nextH2Rect = h2Elements[nextH2Index]?.getBoundingClientRect() + + // 从下往上查找第一个在当前 h2 范围内的可见 h3 + for (let i = h3Elements.length - 1; i >= 0; i--) { + const h3 = h3Elements[i] + const rect = h3.getBoundingClientRect() + + // 确保 h3 在当前 h2 和下一个 h2 之间 + const isInCurrentSection = + rect.top >= (currentH2Rect?.top || 0) && + (!nextH2Rect || rect.top <= nextH2Rect.top) + + if (rect.top <= threshold && + isInCurrentSection && + validH3Titles.has(h3.textContent || '')) { + activeH3 = h3.textContent + break + } + } + } + } + + // 如果切换到新的 h2,但没有找到对应的 h3,就清空 h3 的激活状态 + if (this.state.active[0] !== activeH2) { + activeH3 = null + } + + if (this.state.active[0] !== activeH2 || this.state.active[1] !== activeH3) { + this.state.active = [activeH2 || '', activeH3 || ''] + this.update() + } } // 提取 markdown 中的标题 @@ -60,7 +139,20 @@ export class ProductDocs extends Component { const token = tokens[i] if (token.type === 'heading_open') { const title = tokens[i + 1].content - const newNode: NavTreeNode = { title, children: [] } + let level = 1 + + // 根据标题标签设置层级 + if (token.tag === 'h2') { + level = 2 + } else if (token.tag === 'h3') { + level = 3 + } + + const newNode: NavTreeNode = { + title, + children: [], + level + } if (token.tag === 'h2') { this.state.navTree.children.push(newNode)