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
|
```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 markdown from '@wcj/markdown-to-html';
|
||||||
import rehypeDocument from 'rehype-document';
|
import rehypeDocument from 'rehype-document';
|
||||||
import remarkGemoji from 'remark-gemoji';
|
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 rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||||
import rehypeSlug from 'rehype-slug';
|
import rehypeSlug from 'rehype-slug';
|
||||||
import { htmlTagAddAttri } from './nodes/htmlTagAddAttri.mjs';
|
import { htmlTagAddAttri } from './nodes/htmlTagAddAttri.mjs';
|
||||||
@ -9,7 +12,7 @@ import { header } from './nodes/header.mjs';
|
|||||||
import { rehypeUrls } from './utils/rehypeUrls.mjs';
|
import { rehypeUrls } from './utils/rehypeUrls.mjs';
|
||||||
import { tooltips } from './utils/tooltips.mjs';
|
import { tooltips } from './utils/tooltips.mjs';
|
||||||
import { homeCardIcons } from './utils/homeCardIcons.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 { rehypeTitle } from './utils/rehypeTitle.mjs';
|
||||||
import { anchorPoint } from './utils/anchorPoint.mjs';
|
import { anchorPoint } from './utils/anchorPoint.mjs';
|
||||||
import { rehypePreviewHTML } from './utils/rehypePreviewHTML.mjs';
|
import { rehypePreviewHTML } from './utils/rehypePreviewHTML.mjs';
|
||||||
@ -29,18 +32,29 @@ export function create(str = '', options = {}) {
|
|||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
rehypeSlug,
|
rehypeSlug,
|
||||||
rehypeAutolinkHeadings,
|
rehypeAutolinkHeadings,
|
||||||
[rehypeDocument, {
|
[rehypeDocument, {
|
||||||
title: `${title ? `${title} & ` : ''} ${subTitle} Quick Reference`,
|
title: `${title ? `${title} & ` : ''} ${subTitle} Quick Reference`,
|
||||||
css: [ ...options.css ],
|
css: [ ...options.css ],
|
||||||
link: [
|
link: [
|
||||||
{rel: 'icon', href: favicon, type: 'image/svg+xml'}
|
{rel: 'icon', href: favicon, type: 'image/svg+xml'}
|
||||||
],
|
],
|
||||||
meta: [
|
meta: [
|
||||||
{ description: `${description}为开发人员分享快速参考备忘单。` },
|
{ description: `${description}为开发人员分享快速参考备忘单。` },
|
||||||
{ keywords: `Quick,Reference,cheatsheet,${!options.isHome && options.filename || ''}` }
|
{ 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) => {
|
rewrite: (node, index, parent) => {
|
||||||
rehypePreviewHTML(node, parent);
|
rehypePreviewHTML(node, parent);
|
||||||
rehypeTitle(node, options.filename);
|
rehypeTitle(node, options.filename);
|
||||||
@ -48,11 +62,19 @@ export function create(str = '', options = {}) {
|
|||||||
tooltips(node, index, parent);
|
tooltips(node, index, parent);
|
||||||
htmlTagAddAttri(node, options);
|
htmlTagAddAttri(node, options);
|
||||||
rehypeUrls(node);
|
rehypeUrls(node);
|
||||||
if (node.type === 'element' && node.tagName === 'body') {
|
if (node.children) {
|
||||||
node.children = getTocsTree([ ...node.children ]);
|
if (node.type === 'element' && node.tagName === 'body') {
|
||||||
node.children.unshift(header(options));
|
const tocsData = getTocsTree([ ...node.children ]);
|
||||||
node.children.push(footer());
|
if (!options.isHome) {
|
||||||
node.children.push(anchorPoint());
|
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-accent-emphasis: #0969da;
|
||||||
--color-attention-subtle: #fff8c5;
|
--color-attention-subtle: #fff8c5;
|
||||||
--color-danger-fg: #cf222e;
|
--color-danger-fg: #cf222e;
|
||||||
|
--box-shadow: 109 109 109;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-color-mode*='dark'], [data-color-mode*='dark'] body {
|
[data-color-mode*='dark'], [data-color-mode*='dark'] body {
|
||||||
@ -112,6 +113,7 @@ body {
|
|||||||
--color-accent-emphasis: #1f6feb;
|
--color-accent-emphasis: #1f6feb;
|
||||||
--color-attention-subtle: rgba(187,128,9,0.15);
|
--color-attention-subtle: rgba(187,128,9,0.15);
|
||||||
--color-danger-fg: #f85149;
|
--color-danger-fg: #f85149;
|
||||||
|
--box-shadow: 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -474,6 +476,64 @@ a.text-grey {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 3rem;
|
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 {
|
.wrap-header.h2wrap > h2 {
|
||||||
|
@ -3,6 +3,7 @@ const scripts = `
|
|||||||
if(('onhashchange' in window) && ((typeof document.documentMode==='undefined') || document.documentMode==8)) {
|
if(('onhashchange' in window) && ((typeof document.documentMode==='undefined') || document.documentMode==8)) {
|
||||||
window.onhashchange = function () {
|
window.onhashchange = function () {
|
||||||
anchorPoint()
|
anchorPoint()
|
||||||
|
updateAnchor()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function anchorPoint() {
|
function anchorPoint() {
|
||||||
@ -16,6 +17,26 @@ function anchorPoint() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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() {
|
export function anchorPoint() {
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import path from 'path';
|
||||||
import rehypeParse from 'rehype-parse';
|
import rehypeParse from 'rehype-parse';
|
||||||
import {unified} from 'unified';
|
import {unified} from 'unified';
|
||||||
import { VFile } from 'vfile';
|
import { VFile } from 'vfile';
|
||||||
|
|
||||||
|
export const ICONS_PATH = path.resolve(process.cwd(), 'scripts/assets')
|
||||||
|
|
||||||
export function getSVGNode(iconPath, space = 'svg') {
|
export function getSVGNode(iconPath, space = 'svg') {
|
||||||
const svgStr = fs.readFileSync(iconPath);
|
const svgStr = fs.readFileSync(iconPath);
|
||||||
const processor = unified().use(rehypeParse,{ fragment: true, space })
|
const processor = unified().use(rehypeParse,{ fragment: true, space })
|
||||||
|
@ -1,5 +1,71 @@
|
|||||||
|
import path from 'path';
|
||||||
import { panelAddNumber } from './panelAddNumber.mjs';
|
import { panelAddNumber } from './panelAddNumber.mjs';
|
||||||
import { getChilds, getHeader } from './childs.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 文档转成树形结构 */
|
/** Markdown 文档转成树形结构 */
|
||||||
export function getTocsTree(arr = [], result = []) {
|
export function getTocsTree(arr = [], result = []) {
|
||||||
@ -14,9 +80,7 @@ export function getTocsTree(arr = [], result = []) {
|
|||||||
if (level === -1) {
|
if (level === -1) {
|
||||||
level = toc.number;
|
level = toc.number;
|
||||||
}
|
}
|
||||||
const titleNum = Number(toc.tagName?.replace(/^h/, ''));
|
if (toc.number === level && titleNum(toc.tagName) === level) {
|
||||||
|
|
||||||
if (toc.number === level && titleNum === level) {
|
|
||||||
const header = getHeader(data.slice(n), level);
|
const header = getHeader(data.slice(n), level);
|
||||||
const wrapCls = ['wrap'];
|
const wrapCls = ['wrap'];
|
||||||
const headerCls = ['wrap-header', `h${level}wrap`];
|
const headerCls = ['wrap-header', `h${level}wrap`];
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { getSVGNode } from './getSVGNode.mjs';
|
import { getSVGNode, ICONS_PATH } from './getSVGNode.mjs';
|
||||||
|
|
||||||
export const ICONS_PATH = path.resolve(process.cwd(), 'scripts/assets')
|
|
||||||
|
|
||||||
export function homeCardIcons(node, parent, isHome) {
|
export function homeCardIcons(node, parent, isHome) {
|
||||||
if (isHome && node && node.type === 'element' && node.properties?.class?.includes('home-card')) {
|
if (isHome && node && node.type === 'element' && node.properties?.class?.includes('home-card')) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { getSVGNode } from './getSVGNode.mjs';
|
import { getSVGNode, ICONS_PATH } from './getSVGNode.mjs';
|
||||||
import { ICONS_PATH } from './homeCardIcons.mjs';
|
|
||||||
|
|
||||||
export function rehypeTitle(node, iconName) {
|
export function rehypeTitle(node, iconName) {
|
||||||
if (node.type === 'element' && node.tagName === 'h1' && iconName !== 'index') {
|
if (node.type === 'element' && node.tagName === 'h1' && iconName !== 'index') {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user