From fc74346b4bb4f1f76dd0bbc7e971078bccdbbd2f Mon Sep 17 00:00:00 2001 From: "xiaoqi.cxq" Date: Sun, 4 Sep 2022 17:28:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=9B=AE=E5=BD=95TOC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + chrome-app/manifest.json | 2 +- package-lock.json | 2 +- package.json | 2 +- src/components/CodeEditor.vue | 4 ++ src/data/presets.js | 10 ++++ src/extensions/libs/markdownItAnchor.js | 77 +++++++++++++++++++++++++ src/extensions/markdownExtension.js | 2 +- src/services/markdownGrammarSvc.js | 3 + src/styles/base.scss | 7 ++- src/styles/markdownHighlighting.scss | 11 ++++ 11 files changed, 116 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index afde4311..25cb6976 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ StackEdit中文版的docker镜像地址:[mafgwo/stackedit](https://hub.docker. - 支持了右上角一键切换主题,补全了深色主题的样式(2022-08-07) - 编辑与预览区域样式优化(2022-08-10) - 左边栏文件资源管理支持搜索文件(2022-08-17) +- 支持[TOC]目录(2022-09-04) ## 国外开源版本弊端: - 作者已经不维护了 diff --git a/chrome-app/manifest.json b/chrome-app/manifest.json index b4c708c7..41725db8 100644 --- a/chrome-app/manifest.json +++ b/chrome-app/manifest.json @@ -1,7 +1,7 @@ { "name": "StackEdit中文版", "description": "支持Gitee仓库/粘贴图片自动上传的浏览器内 Markdown 编辑器", - "version": "5.15.11", + "version": "5.15.12", "manifest_version": 2, "container" : "GITEE", "api_console_project_id" : "241271498917", diff --git a/package-lock.json b/package-lock.json index 3b8384a8..27227349 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "stackedit", - "version": "5.15.11", + "version": "5.15.12", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 957eb761..bb269509 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stackedit", - "version": "5.15.11", + "version": "5.15.12", "description": "免费, 开源, 功能齐全的 Markdown 编辑器", "author": "Benoit Schweblin, 豆萁", "license": "Apache-2.0", diff --git a/src/components/CodeEditor.vue b/src/components/CodeEditor.vue index 1ea9596b..ab36ad5d 100644 --- a/src/components/CodeEditor.vue +++ b/src/components/CodeEditor.vue @@ -43,6 +43,10 @@ export default { overflow: auto; padding: 0.2em 0.4em; + .app--dark & { + caret-color: $editor-color-dark-low; + } + * { line-height: $line-height-base; font-size: inherit !important; diff --git a/src/data/presets.js b/src/data/presets.js index b37095c5..fb60a502 100644 --- a/src/data/presets.js +++ b/src/data/presets.js @@ -15,6 +15,7 @@ const zero = { table: false, tasklist: false, typographer: false, + toc: false, }, // Emoji extension emoji: { @@ -49,6 +50,13 @@ const zero = { mermaid: { enabled: false, }, + /* + Toc extension + 把 [TOC] 转换为目录 + */ + toc: { + enabled: false, + }, }; export default { @@ -66,6 +74,7 @@ export default { linkify: true, table: true, tasklist: true, + toc: true, }, emoji: { enabled: true, @@ -86,6 +95,7 @@ export default { sup: true, table: true, tasklist: true, + toc: true, typographer: true, }, emoji: { diff --git a/src/extensions/libs/markdownItAnchor.js b/src/extensions/libs/markdownItAnchor.js index 70061878..8abeab1c 100644 --- a/src/extensions/libs/markdownItAnchor.js +++ b/src/extensions/libs/markdownItAnchor.js @@ -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 = `
  • `; + if (item.anchor && item.title) { + result += `${item.title}`; + } + result += arrayToHtml(item.children); + return `${result}
  • `; + }).join('\n'); + return ``; +} + export default (md) => { md.core.ruler.before('replacements', 'anchors', (state) => { const anchorHash = {}; let headingOpenToken; let headingContent; + const tocTokens = []; + const headings = []; state.tokens.forEach((token) => { if (token.type === 'heading_open') { headingContent = ''; @@ -39,6 +83,12 @@ export default (md) => { headingOpenToken.attrs = [ ['id', anchor], ]; + headings.push({ + title: headingOpenToken.headingContent, + anchor: headingOpenToken.headingAnchor, + level: parseInt(headingOpenToken.tag.slice(1), 10), + children: [], + }); headingOpenToken = undefined; } else if (headingOpenToken) { headingContent += token.children.reduce((result, child) => { @@ -48,6 +98,33 @@ export default (md) => { 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 = `
    ${tocHtml}
    `; + tocToken.children.unshift(tocDiv); + tocToken.children[1].content = ''; + tocToken.content = ''; + }); + } }); }; diff --git a/src/extensions/markdownExtension.js b/src/extensions/markdownExtension.js index 405a3825..f2c33e05 100644 --- a/src/extensions/markdownExtension.js +++ b/src/extensions/markdownExtension.js @@ -134,7 +134,7 @@ extensionSvc.onInitConverter(0, (markdown, options) => { if (tokens[idx].meta.subId > 0) { id += `:${tokens[idx].meta.subId}`; } - return `${n}`; + return `[${n}]`; }; }); diff --git a/src/services/markdownGrammarSvc.js b/src/services/markdownGrammarSvc.js index 217e7cd4..b7601ec0 100644 --- a/src/services/markdownGrammarSvc.js +++ b/src/services/markdownGrammarSvc.js @@ -106,6 +106,9 @@ export default { 'cl cl-hash': /-+[ \t]*$/, }, }; + grammars.main['cn-toc'] = { + pattern: /^\[TOC\]$/gm, + }; for (let i = 6; i >= 1; i -= 1) { grammars.main[`h${i} cn-head`] = { pattern: new RegExp(`^#{${i}}[ \t].+$`, 'gm'), diff --git a/src/styles/base.scss b/src/styles/base.scss index 7e128d4a..1bf3dd18 100644 --- a/src/styles/base.scss +++ b/src/styles/base.scss @@ -23,6 +23,11 @@ body { color: $body-color-dark; } +.preview-toc ul { + list-style-type: none; + margin-bottom: 15px; +} + p, blockquote, pre, @@ -43,7 +48,7 @@ h3, h4, h5, h6 { - margin: 1.8em 0 0.9em; + margin: 1.2em 0 0.9em; line-height: $line-height-title; } diff --git a/src/styles/markdownHighlighting.scss b/src/styles/markdownHighlighting.scss index 8acf9bd0..65bcebf7 100644 --- a/src/styles/markdownHighlighting.scss +++ b/src/styles/markdownHighlighting.scss @@ -327,6 +327,17 @@ 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 {