diff --git a/docs/bash.md b/docs/bash.md index 19b4d21..4dabb37 100644 --- a/docs/bash.md +++ b/docs/bash.md @@ -34,11 +34,9 @@ NAME = "John" # => Error (关于空间) ### 注释 -```bash -# 这是一个内联 Bash 注释。 -``` - ```shell +# 这是一个内联 Bash 注释。 + : ' 这是一个 非常整洁的评论 diff --git a/scripts/assets/menu.svg b/scripts/assets/menu.svg new file mode 100644 index 0000000..c9be56e --- /dev/null +++ b/scripts/assets/menu.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/scripts/create.mjs b/scripts/create.mjs index 30a5564..256ade6 100644 --- a/scripts/create.mjs +++ b/scripts/create.mjs @@ -1,6 +1,9 @@ import markdown from '@wcj/markdown-to-html'; import rehypeDocument from 'rehype-document'; import remarkGemoji from 'remark-gemoji'; +import rehypeRaw from 'rehype-raw'; +import rehypeAttrs from 'rehype-attr'; +import rehypeKatex from 'rehype-katex'; import rehypeAutolinkHeadings from 'rehype-autolink-headings'; import rehypeSlug from 'rehype-slug'; import { htmlTagAddAttri } from './nodes/htmlTagAddAttri.mjs'; @@ -9,7 +12,7 @@ import { header } from './nodes/header.mjs'; import { rehypeUrls } from './utils/rehypeUrls.mjs'; import { tooltips } from './utils/tooltips.mjs'; import { homeCardIcons } from './utils/homeCardIcons.mjs'; -import { getTocsTree } from './utils/getTocsTree.mjs'; +import { getTocsTree, getTocsTitleNode, getTocsTitleNodeWarpper, addTocsInWarp } from './utils/getTocsTree.mjs'; import { rehypeTitle } from './utils/rehypeTitle.mjs'; import { anchorPoint } from './utils/anchorPoint.mjs'; import { rehypePreviewHTML } from './utils/rehypePreviewHTML.mjs'; @@ -29,18 +32,29 @@ export function create(str = '', options = {}) { rehypePlugins: [ rehypeSlug, rehypeAutolinkHeadings, - [rehypeDocument, { - title: `${title ? `${title} & ` : ''} ${subTitle} Quick Reference`, - css: [ ...options.css ], - link: [ - {rel: 'icon', href: favicon, type: 'image/svg+xml'} - ], - meta: [ - { description: `${description}为开发人员分享快速参考备忘单。` }, - { keywords: `Quick,Reference,cheatsheet,${!options.isHome && options.filename || ''}` } - ] - }], + [rehypeDocument, { + title: `${title ? `${title} & ` : ''} ${subTitle} Quick Reference`, + css: [ ...options.css ], + link: [ + {rel: 'icon', href: favicon, type: 'image/svg+xml'} + ], + meta: [ + { description: `${description}为开发人员分享快速参考备忘单。` }, + { keywords: `Quick,Reference,cheatsheet,${!options.isHome && options.filename || ''}` } + ] + }] ], + filterPlugins: (type, plugins = []) => { + if (type === 'rehype') { + const dt = plugins.filter(plug => { + return /(rehypeRaw)/.test(plug.name) ? false : true; + }); + // 放在 rehypeDocument 前面 + dt.unshift(rehypeRaw) + return dt; + } + return plugins + }, rewrite: (node, index, parent) => { rehypePreviewHTML(node, parent); rehypeTitle(node, options.filename); @@ -48,11 +62,19 @@ export function create(str = '', options = {}) { tooltips(node, index, parent); htmlTagAddAttri(node, options); rehypeUrls(node); - if (node.type === 'element' && node.tagName === 'body') { - node.children = getTocsTree([ ...node.children ]); - node.children.unshift(header(options)); - node.children.push(footer()); - node.children.push(anchorPoint()); + if (node.children) { + if (node.type === 'element' && node.tagName === 'body') { + const tocsData = getTocsTree([ ...node.children ]); + if (!options.isHome) { + const tocsMenus = getTocsTitleNode([...tocsData]); + node.children = addTocsInWarp([...tocsData], getTocsTitleNodeWarpper(tocsMenus)) + } else { + node.children = tocsData; + } + node.children.unshift(header(options)); + node.children.push(footer()); + node.children.push(anchorPoint()); + } } } } diff --git a/scripts/style.css b/scripts/style.css index 800ecfa..58b5102 100644 --- a/scripts/style.css +++ b/scripts/style.css @@ -66,6 +66,7 @@ body { --color-accent-emphasis: #0969da; --color-attention-subtle: #fff8c5; --color-danger-fg: #cf222e; + --box-shadow: 109 109 109; } [data-color-mode*='dark'], [data-color-mode*='dark'] body { @@ -112,6 +113,7 @@ body { --color-accent-emphasis: #1f6feb; --color-attention-subtle: rgba(187,128,9,0.15); --color-danger-fg: #f85149; + --box-shadow: 0 0 0; } body { @@ -474,6 +476,64 @@ a.text-grey { display: flex; flex-direction: column; gap: 3rem; + position: relative; +} +.menu-tocs { + position: sticky; + top: 0; + z-index: 88; + display: inline-flex; +} +.menu-tocs:hover > .menu-modal { + display: block; + border-radius: 0.5rem; + padding: 0.3rem; + max-height: 100vh; + overflow: auto; + background-color: var(--color-canvas-subtle); + box-shadow: 0 8px 24px rgba(var(--box-shadow)/0.2); +} +.menu-tocs > .menu-btn { + border: 1px solid var(--color-border-default); + display: flex; + border-radius: 0.3rem; + padding: 0.3rem 0.4rem; + font-size: 1.3rem; + margin-left: -3rem; + margin-top: 0.3rem; + position: absolute; +} +.menu-tocs > .menu-modal { + width: 260px; + position:absolute; + display: none; + margin-left: -1rem; +} +.menu-tocs > .menu-modal a + a { + margin-bottom: 0.2rem; +} +.menu-tocs > .menu-modal a:hover { + background-color: var(--color-neutral-muted); +} +.menu-tocs > .menu-modal a.is-active-link { + background-color: var(--color-border-muted); + text-decoration-color: #10b981; +} +.menu-tocs > .menu-modal a { + display: block; + overflow: hidden; + padding: 0.3rem 0.5rem; +} + +.menu-tocs > .menu-modal a.leve2 { + font-weight: bold; +} + +.menu-tocs > .menu-modal a.leve3 { + padding-left: 1.2rem; +} +.menu-tocs > .menu-modal a.leve4, .menu-tocs > .menu-modal a.leve5, .menu-tocs > .menu-modal a.leve6 { + padding-left: 2.1rem; } .wrap-header.h2wrap > h2 { diff --git a/scripts/utils/anchorPoint.mjs b/scripts/utils/anchorPoint.mjs index f747191..5d67447 100644 --- a/scripts/utils/anchorPoint.mjs +++ b/scripts/utils/anchorPoint.mjs @@ -3,6 +3,7 @@ const scripts = ` if(('onhashchange' in window) && ((typeof document.documentMode==='undefined') || document.documentMode==8)) { window.onhashchange = function () { anchorPoint() + updateAnchor() }; } function anchorPoint() { @@ -16,6 +17,26 @@ function anchorPoint() { } } anchorPoint(); + +function updateAnchor(element) { + const anchorContainer = document.querySelectorAll('.menu-tocs .menu-modal a.tocs-link'); + anchorContainer.forEach((tocanchor) => { + tocanchor.classList.remove('is-active-link'); + }); + const anchor = element || document.querySelector(\`a.tocs-link[href='\${decodeURIComponent(window.location.hash)}']\`); + console.log('anchor', anchor) + if (anchor) { + anchor.classList.add('is-active-link'); + } +} +// toc 定位 +updateAnchor() +const anchor = document.querySelectorAll('.menu-tocs .menu-modal a.tocs-link'); +anchor.forEach((item) => { + item.addEventListener('click', (e) => { + updateAnchor() + }) +}) `; export function anchorPoint() { diff --git a/scripts/utils/getSVGNode.mjs b/scripts/utils/getSVGNode.mjs index 00e7138..9d6d424 100644 --- a/scripts/utils/getSVGNode.mjs +++ b/scripts/utils/getSVGNode.mjs @@ -1,8 +1,11 @@ import fs from 'fs-extra'; +import path from 'path'; import rehypeParse from 'rehype-parse'; import {unified} from 'unified'; import { VFile } from 'vfile'; +export const ICONS_PATH = path.resolve(process.cwd(), 'scripts/assets') + export function getSVGNode(iconPath, space = 'svg') { const svgStr = fs.readFileSync(iconPath); const processor = unified().use(rehypeParse,{ fragment: true, space }) diff --git a/scripts/utils/getTocsTree.mjs b/scripts/utils/getTocsTree.mjs index e3698e2..fd44690 100644 --- a/scripts/utils/getTocsTree.mjs +++ b/scripts/utils/getTocsTree.mjs @@ -1,5 +1,71 @@ +import path from 'path'; import { panelAddNumber } from './panelAddNumber.mjs'; import { getChilds, getHeader } from './childs.mjs'; +import { ICONS_PATH, getSVGNode } from './getSVGNode.mjs'; + +export const titleNum = (tagName = '') => Number(tagName.replace(/^h/, '')); + +export function getTocsTitleNode(arr = [], result = []) { + arr.forEach(({ tagName, type, properties, children }) => { + if (/^h[23456]/.test(tagName)) { + const num = titleNum(tagName) + const props = { 'aria-hidden': "true", class: `leve${num} tocs-link`, href: '#' + (properties.id || '') } + result.push({ tagName: 'a', type, properties: props, children: (children || []).filter(m => m.type === 'text') }) + } else if (children?.length > 0) { + result = result.concat(getTocsTitleNode(children)) + } + }); + return result +} + +export function addTocsInWarp(tocsData = [], menuData, isDone = false) { + const childs = tocsData.map((item) => { + if (item.properties?.class?.includes('h1wrap-body')) { + isDone = true; + } + if (!isDone && item.children) { + item.children = addTocsInWarp([...item.children], menuData, isDone) + } + return item + }); + if (isDone) { + childs.splice(1, 0, menuData); + } + return childs +} + +export const getTocsTitleNodeWarpper = (children = []) => { + const iconPath = path.resolve(ICONS_PATH, `menu.svg`); + const svgNode = getSVGNode(iconPath); + return { + type: 'element', + tagName: 'div', + properties: { + class: 'menu-tocs', + }, + children: [ + { + type: 'element', + tagName: 'div', + properties: { + class: 'menu-btn', + }, + children: [ + // { type: 'text', value: 'menu' } + ...svgNode + ] + }, + { + type: 'element', + tagName: 'div', + properties: { + class: 'menu-modal', + }, + children: children + } + ] + } +} /** Markdown 文档转成树形结构 */ export function getTocsTree(arr = [], result = []) { @@ -14,9 +80,7 @@ export function getTocsTree(arr = [], result = []) { if (level === -1) { level = toc.number; } - const titleNum = Number(toc.tagName?.replace(/^h/, '')); - - if (toc.number === level && titleNum === level) { + if (toc.number === level && titleNum(toc.tagName) === level) { const header = getHeader(data.slice(n), level); const wrapCls = ['wrap']; const headerCls = ['wrap-header', `h${level}wrap`]; diff --git a/scripts/utils/homeCardIcons.mjs b/scripts/utils/homeCardIcons.mjs index 1982c68..2fad267 100644 --- a/scripts/utils/homeCardIcons.mjs +++ b/scripts/utils/homeCardIcons.mjs @@ -1,8 +1,6 @@ import fs from 'fs-extra'; import path from 'path'; -import { getSVGNode } from './getSVGNode.mjs'; - -export const ICONS_PATH = path.resolve(process.cwd(), 'scripts/assets') +import { getSVGNode, ICONS_PATH } from './getSVGNode.mjs'; export function homeCardIcons(node, parent, isHome) { if (isHome && node && node.type === 'element' && node.properties?.class?.includes('home-card')) { diff --git a/scripts/utils/rehypeTitle.mjs b/scripts/utils/rehypeTitle.mjs index 36b028d..d88a448 100644 --- a/scripts/utils/rehypeTitle.mjs +++ b/scripts/utils/rehypeTitle.mjs @@ -1,7 +1,6 @@ import fs from 'fs-extra'; import path from 'path'; -import { getSVGNode } from './getSVGNode.mjs'; -import { ICONS_PATH } from './homeCardIcons.mjs'; +import { getSVGNode, ICONS_PATH } from './getSVGNode.mjs'; export function rehypeTitle(node, iconName) { if (node.type === 'element' && node.tagName === 'h1' && iconName !== 'index') {