支持目录TOC
This commit is contained in:
parent
a957432749
commit
fc74346b4b
@ -34,6 +34,7 @@ StackEdit中文版的docker镜像地址:[mafgwo/stackedit](https://hub.docker.
|
|||||||
- 支持了右上角一键切换主题,补全了深色主题的样式(2022-08-07)
|
- 支持了右上角一键切换主题,补全了深色主题的样式(2022-08-07)
|
||||||
- 编辑与预览区域样式优化(2022-08-10)
|
- 编辑与预览区域样式优化(2022-08-10)
|
||||||
- 左边栏文件资源管理支持搜索文件(2022-08-17)
|
- 左边栏文件资源管理支持搜索文件(2022-08-17)
|
||||||
|
- 支持[TOC]目录(2022-09-04)
|
||||||
|
|
||||||
## 国外开源版本弊端:
|
## 国外开源版本弊端:
|
||||||
- 作者已经不维护了
|
- 作者已经不维护了
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "StackEdit中文版",
|
"name": "StackEdit中文版",
|
||||||
"description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器",
|
"description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器",
|
||||||
"version": "5.15.11",
|
"version": "5.15.12",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"container" : "GITEE",
|
"container" : "GITEE",
|
||||||
"api_console_project_id" : "241271498917",
|
"api_console_project_id" : "241271498917",
|
||||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stackedit",
|
"name": "stackedit",
|
||||||
"version": "5.15.11",
|
"version": "5.15.12",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stackedit",
|
"name": "stackedit",
|
||||||
"version": "5.15.11",
|
"version": "5.15.12",
|
||||||
"description": "免费, 开源, 功能齐全的 Markdown 编辑器",
|
"description": "免费, 开源, 功能齐全的 Markdown 编辑器",
|
||||||
"author": "Benoit Schweblin, 豆萁",
|
"author": "Benoit Schweblin, 豆萁",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
@ -43,6 +43,10 @@ export default {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
|
|
||||||
|
.app--dark & {
|
||||||
|
caret-color: $editor-color-dark-low;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
line-height: $line-height-base;
|
line-height: $line-height-base;
|
||||||
font-size: inherit !important;
|
font-size: inherit !important;
|
||||||
|
@ -15,6 +15,7 @@ const zero = {
|
|||||||
table: false,
|
table: false,
|
||||||
tasklist: false,
|
tasklist: false,
|
||||||
typographer: false,
|
typographer: false,
|
||||||
|
toc: false,
|
||||||
},
|
},
|
||||||
// Emoji extension
|
// Emoji extension
|
||||||
emoji: {
|
emoji: {
|
||||||
@ -49,6 +50,13 @@ const zero = {
|
|||||||
mermaid: {
|
mermaid: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
|
Toc extension
|
||||||
|
把 [TOC] 转换为目录
|
||||||
|
*/
|
||||||
|
toc: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -66,6 +74,7 @@ export default {
|
|||||||
linkify: true,
|
linkify: true,
|
||||||
table: true,
|
table: true,
|
||||||
tasklist: true,
|
tasklist: true,
|
||||||
|
toc: true,
|
||||||
},
|
},
|
||||||
emoji: {
|
emoji: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -86,6 +95,7 @@ export default {
|
|||||||
sup: true,
|
sup: true,
|
||||||
table: true,
|
table: true,
|
||||||
tasklist: true,
|
tasklist: true,
|
||||||
|
toc: true,
|
||||||
typographer: true,
|
typographer: true,
|
||||||
},
|
},
|
||||||
emoji: {
|
emoji: {
|
||||||
|
@ -1,8 +1,52 @@
|
|||||||
|
function groupHeadings(headings, level = 1) {
|
||||||
|
const result = [];
|
||||||
|
let currentItem;
|
||||||
|
|
||||||
|
function pushCurrentItem() {
|
||||||
|
if (currentItem) {
|
||||||
|
if (currentItem.children.length > 0) {
|
||||||
|
currentItem.children = groupHeadings(currentItem.children, level + 1);
|
||||||
|
}
|
||||||
|
result.push(currentItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headings.forEach((heading) => {
|
||||||
|
if (heading.level !== level) {
|
||||||
|
currentItem = currentItem || {
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
currentItem.children.push(heading);
|
||||||
|
} else {
|
||||||
|
pushCurrentItem();
|
||||||
|
currentItem = heading;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pushCurrentItem();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayToHtml(arr) {
|
||||||
|
if (!arr || !arr.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const ulHtml = arr.map((item) => {
|
||||||
|
let result = `<li class="preview-toc-item preview-toc-l${item.level}">`;
|
||||||
|
if (item.anchor && item.title) {
|
||||||
|
result += `<a href="#${item.anchor}">${item.title}</a>`;
|
||||||
|
}
|
||||||
|
result += arrayToHtml(item.children);
|
||||||
|
return `${result}</li>`;
|
||||||
|
}).join('\n');
|
||||||
|
return `<ul>${ulHtml}</ul>`;
|
||||||
|
}
|
||||||
|
|
||||||
export default (md) => {
|
export default (md) => {
|
||||||
md.core.ruler.before('replacements', 'anchors', (state) => {
|
md.core.ruler.before('replacements', 'anchors', (state) => {
|
||||||
const anchorHash = {};
|
const anchorHash = {};
|
||||||
let headingOpenToken;
|
let headingOpenToken;
|
||||||
let headingContent;
|
let headingContent;
|
||||||
|
const tocTokens = [];
|
||||||
|
const headings = [];
|
||||||
state.tokens.forEach((token) => {
|
state.tokens.forEach((token) => {
|
||||||
if (token.type === 'heading_open') {
|
if (token.type === 'heading_open') {
|
||||||
headingContent = '';
|
headingContent = '';
|
||||||
@ -39,6 +83,12 @@ export default (md) => {
|
|||||||
headingOpenToken.attrs = [
|
headingOpenToken.attrs = [
|
||||||
['id', anchor],
|
['id', anchor],
|
||||||
];
|
];
|
||||||
|
headings.push({
|
||||||
|
title: headingOpenToken.headingContent,
|
||||||
|
anchor: headingOpenToken.headingAnchor,
|
||||||
|
level: parseInt(headingOpenToken.tag.slice(1), 10),
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
headingOpenToken = undefined;
|
headingOpenToken = undefined;
|
||||||
} else if (headingOpenToken) {
|
} else if (headingOpenToken) {
|
||||||
headingContent += token.children.reduce((result, child) => {
|
headingContent += token.children.reduce((result, child) => {
|
||||||
@ -48,6 +98,33 @@ export default (md) => {
|
|||||||
return result;
|
return result;
|
||||||
}, '');
|
}, '');
|
||||||
}
|
}
|
||||||
|
if (token.type === 'inline' && token.content === '[TOC]') {
|
||||||
|
tocTokens.push(token);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
// 没有TOC 直接返回
|
||||||
|
if (tocTokens.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 没有header
|
||||||
|
if (headings.length === 0) {
|
||||||
|
// 置空[TOC]文案为空字符串
|
||||||
|
tocTokens.forEach((tocToken) => {
|
||||||
|
tocToken.children[0].content = '';
|
||||||
|
tocToken.content = '';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
tocTokens.forEach((tocToken) => {
|
||||||
|
// toc目录
|
||||||
|
const toc = groupHeadings(headings);
|
||||||
|
// 拼接为html
|
||||||
|
const tocHtml = arrayToHtml(toc);
|
||||||
|
const tocDiv = new state.Token('html_inline', '', 0);
|
||||||
|
tocDiv.content = `<div class="preview-toc">${tocHtml}</div>`;
|
||||||
|
tocToken.children.unshift(tocDiv);
|
||||||
|
tocToken.children[1].content = '';
|
||||||
|
tocToken.content = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -134,7 +134,7 @@ extensionSvc.onInitConverter(0, (markdown, options) => {
|
|||||||
if (tokens[idx].meta.subId > 0) {
|
if (tokens[idx].meta.subId > 0) {
|
||||||
id += `:${tokens[idx].meta.subId}`;
|
id += `:${tokens[idx].meta.subId}`;
|
||||||
}
|
}
|
||||||
return `<sup class="footnote-ref"><a href="#fn${n}" id="${id}">${n}</a></sup>`;
|
return `<sup class="footnote-ref"><a href="#fn${n}" id="${id}">[${n}]</a></sup>`;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -106,6 +106,9 @@ export default {
|
|||||||
'cl cl-hash': /-+[ \t]*$/,
|
'cl cl-hash': /-+[ \t]*$/,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
grammars.main['cn-toc'] = {
|
||||||
|
pattern: /^\[TOC\]$/gm,
|
||||||
|
};
|
||||||
for (let i = 6; i >= 1; i -= 1) {
|
for (let i = 6; i >= 1; i -= 1) {
|
||||||
grammars.main[`h${i} cn-head`] = {
|
grammars.main[`h${i} cn-head`] = {
|
||||||
pattern: new RegExp(`^#{${i}}[ \t].+$`, 'gm'),
|
pattern: new RegExp(`^#{${i}}[ \t].+$`, 'gm'),
|
||||||
|
@ -23,6 +23,11 @@ body {
|
|||||||
color: $body-color-dark;
|
color: $body-color-dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-toc ul {
|
||||||
|
list-style-type: none;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
p,
|
p,
|
||||||
blockquote,
|
blockquote,
|
||||||
pre,
|
pre,
|
||||||
@ -43,7 +48,7 @@ h3,
|
|||||||
h4,
|
h4,
|
||||||
h5,
|
h5,
|
||||||
h6 {
|
h6 {
|
||||||
margin: 1.8em 0 0.9em;
|
margin: 1.2em 0 0.9em;
|
||||||
line-height: $line-height-title;
|
line-height: $line-height-title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,6 +327,17 @@
|
|||||||
color: #95cc5e;
|
color: #95cc5e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cn-toc {
|
||||||
|
color: #d7a55b;
|
||||||
|
font-size: 2.5em;
|
||||||
|
padding: 0.2em;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.app--dark & {
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-highlighting--inline {
|
.markdown-highlighting--inline {
|
||||||
|
Loading…
Reference in New Issue
Block a user