feat: add tocs feature & fix page element generation issue (#9).
This commit is contained in:
parent
6d0801a9da
commit
dd8a3cb26d
@ -34,11 +34,9 @@ NAME = "John" # => Error (关于空间)
|
||||
|
||||
### 注释
|
||||
|
||||
```bash
|
||||
# 这是一个内联 Bash 注释。
|
||||
```
|
||||
|
||||
```shell
|
||||
# 这是一个内联 Bash 注释。
|
||||
|
||||
: '
|
||||
这是一个
|
||||
非常整洁的评论
|
||||
|
3
scripts/assets/menu.svg
Normal file
3
scripts/assets/menu.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="0 0 16 16" version="1.1" data-view-component="true">
|
||||
<path fill-rule="evenodd" d="M2 4a1 1 0 100-2 1 1 0 000 2zm3.75-1.5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zm0 5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zm0 5a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5zM3 8a1 1 0 11-2 0 1 1 0 012 0zm-1 6a1 1 0 100-2 1 1 0 000 2z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 416 B |
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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 })
|
||||
|
@ -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`];
|
||||
|
@ -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')) {
|
||||
|
@ -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') {
|
||||
|
Loading…
x
Reference in New Issue
Block a user