From bb3f03fb17610105c3da001347889cf2456d1ea0 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Tue, 15 Dec 2020 22:43:46 +0800 Subject: [PATCH] toc support --- src/data/core/vnotex.json | 3 +- src/data/extra/extra.qrc | 3 +- src/data/extra/web/css/globalstyles.css | 32 ++++++++ src/data/extra/web/js/markdown-it/README.md | 11 +-- .../markdown-it/markdown-it-headinganchor.js | 80 ------------------- .../js/markdown-it/markdownItAnchor.umd.js | 2 + .../markdown-it/markdownItTocDoneRight.umd.js | 2 + src/data/extra/web/js/markdownit.js | 46 +++++++---- src/data/extra/web/js/nodelinemapper.js | 9 ++- src/widgets/notebookexplorer.cpp | 2 + src/widgets/viewwindow.cpp | 6 +- 11 files changed, 91 insertions(+), 105 deletions(-) delete mode 100644 src/data/extra/web/js/markdown-it/markdown-it-headinganchor.js create mode 100644 src/data/extra/web/js/markdown-it/markdownItAnchor.umd.js create mode 100644 src/data/extra/web/js/markdown-it/markdownItTocDoneRight.umd.js diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json index 291566c2..2c3fc5d4 100644 --- a/src/data/core/vnotex.json +++ b/src/data/core/vnotex.json @@ -124,13 +124,14 @@ "web/js/markdown-it/markdown-it-emoji.min.js", "web/js/markdown-it/markdown-it-footnote.min.js", "web/js/markdown-it/markdown-it-front-matter.js", - "web/js/markdown-it/markdown-it-headinganchor.js", "web/js/markdown-it/markdown-it-imsize.min.js", "web/js/markdown-it/markdown-it-sub.min.js", "web/js/markdown-it/markdown-it-sup.min.js", "web/js/markdown-it/markdown-it-task-lists.js", "web/js/markdown-it/markdown-it-texmath.js", "web/js/markdown-it/markdown-it-inject-linenumbers.js", + "web/js/markdown-it/markdownItAnchor.umd.js", + "web/js/markdown-it/markdownItTocDoneRight.umd.js", "web/js/markdownit.js" ], "styles" : [ diff --git a/src/data/extra/extra.qrc b/src/data/extra/extra.qrc index 2fade3d1..5fd5928f 100644 --- a/src/data/extra/extra.qrc +++ b/src/data/extra/extra.qrc @@ -30,7 +30,8 @@ web/js/markdown-it/markdown-it-emoji.min.js web/js/markdown-it/markdown-it-footnote.min.js web/js/markdown-it/markdown-it-front-matter.js - web/js/markdown-it/markdown-it-headinganchor.js + web/js/markdown-it/markdownItAnchor.umd.js + web/js/markdown-it/markdownItTocDoneRight.umd.js web/js/markdown-it/markdown-it-imsize.min.js web/js/markdown-it/markdown-it-sub.min.js web/js/markdown-it/markdown-it-sup.min.js diff --git a/src/data/extra/web/css/globalstyles.css b/src/data/extra/web/css/globalstyles.css index 088b507e..3a11411f 100644 --- a/src/data/extra/web/css/globalstyles.css +++ b/src/data/extra/web/css/globalstyles.css @@ -49,3 +49,35 @@ max-width: 100%; height: auto; } + +/* Table of Contents */ +.vx-table-of-contents ol { + list-style: none; +} + +#vx-content.vx-section-number .vx-table-of-contents > ol > li ol { + counter-reset: toc; +} + +#vx-content.vx-section-number .vx-table-of-contents > ol > li ol li { + counter-increment: toc; +} + +#vx-content.vx-section-number .vx-table-of-contents > ol > li ol li:before { + content: counters(toc, '.') '. '; + font-family: cursive; +} + +.vx-header-anchor { + visibility: hidden; + vertical-align: middle; +} + +.vx-header-anchor::after { + content: attr(vx-data-anchor-icon); +} + +#vx-content h1:hover .vx-header-anchor, #vx-content h2:hover .vx-header-anchor, #vx-content h3:hover .vx-header-anchor, #vx-content h4:hover .vx-header-anchor, #vx-content h5:hover .vx-header-anchor, #vx-content h6:hover .vx-header-anchor { + visibility: visible; + vertical-align: middle; +} diff --git a/src/data/extra/web/js/markdown-it/README.md b/src/data/extra/web/js/markdown-it/README.md index 21f05a42..aea4444a 100644 --- a/src/data/extra/web/js/markdown-it/README.md +++ b/src/data/extra/web/js/markdown-it/README.md @@ -3,11 +3,6 @@ v11.0.0 Alex Kocharin Vitaly Puzrin -# [markdown-it-headinganchor](https://github.com/adam-p/markdown-it-headinganchor) -v1.3.0 -Adam Pritchard -Modified by Le Tan - # [markdown-it-task-lists](https://github.com/revin/markdown-it-task-lists) v2.1.0 Revin Guillen @@ -52,3 +47,9 @@ Modified by Le Tan # [markdown-it-xss](https://github.com/yzyeengang/markdown-it-xss) v1.0.0 + +# [markdonw-it-anchor](https://github.com/valeriangalliat/markdown-it-anchor) +v6.0.1 + +# [markdonw-it-toc-done-right](https://github.com/nagaozen/markdown-it-toc-done-right) +v4.2.0 diff --git a/src/data/extra/web/js/markdown-it/markdown-it-headinganchor.js b/src/data/extra/web/js/markdown-it/markdown-it-headinganchor.js deleted file mode 100644 index 93c872d6..00000000 --- a/src/data/extra/web/js/markdown-it/markdown-it-headinganchor.js +++ /dev/null @@ -1,80 +0,0 @@ -/*! markdown-it-headinganchor 1.2.1 https://github.com//adam-p/markdown-it-headinganchor @license MIT */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.markdownitHeadingAnchor = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o'; - } else { - anchorToken.content = ''; - } - - headingInlineToken.children.push(anchorToken); - } - - // Advance past the inline and heading_close tokens. - i += 2; - } - }; -} - -module.exports = function headinganchor_plugin(md, opts) { - var defaults = { - anchorClass: 'markdown-it-headinganchor', - addHeadingID: true, - addHeadingAnchor: true, - // Added by Le Tan (github.com/tamlok) - anchorIcon: '#', - slugify: slugify, - headingHook: function(openToken, inlineToken, anchor) {} - }; - var options = md.utils.assign(defaults, opts); - md.core.ruler.push('heading_anchors', makeRule(md, options)); -}; - -},{}]},{},[1])(1) -}); diff --git a/src/data/extra/web/js/markdown-it/markdownItAnchor.umd.js b/src/data/extra/web/js/markdown-it/markdownItAnchor.umd.js new file mode 100644 index 00000000..f0346c8e --- /dev/null +++ b/src/data/extra/web/js/markdown-it/markdownItAnchor.umd.js @@ -0,0 +1,2 @@ +!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(n=n||self).markdownItAnchor=e()}(this,function(){var n={false:"push",true:"unshift"},e=Object.prototype.hasOwnProperty,t=function(n,t,r,i){var u=n,o=i;if(r&&e.call(t,u))throw Error("User defined id attribute '"+n+"' is NOT unique. Please fix it in your markdown to continue.");for(;e.call(t,u);)u=n+"-"+o++;return t[u]=!0,u},r=function n(e,r){r=Object.assign({},n.defaults,r),e.core.ruler.push("anchor",function(n){var e,i={},u=n.tokens,o=Array.isArray(r.level)?(e=r.level,function(n){return e.includes(n)}):function(n){return function(e){return e>=n}}(r.level);u.filter(function(n){return"heading_open"===n.type}).filter(function(n){return o(Number(n.tag.substr(1)))}).forEach(function(e){var o=u[u.indexOf(e)+1].children.filter(function(n){return"text"===n.type||"code_inline"===n.type}).reduce(function(n,e){return n+e.content},""),c=e.attrGet("id");c=null==c?t(r.slugify(o),i,!1,r.uniqueSlugStartIndex):t(c,i,!0,r.uniqueSlugStartIndex),e.attrSet("id",c),r.permalink&&r.renderPermalink(c,r,n,u.indexOf(e)),r.callback&&r.callback(e,{slug:c,title:o})})})};return r.defaults={level:1,slugify:function(n){return encodeURIComponent(String(n).trim().toLowerCase().replace(/\s+/g,"-"))},uniqueSlugStartIndex:1,permalink:!1,renderPermalink:function(e,t,r,i){var u,o=[Object.assign(new r.Token("link_open","a",1),{attrs:[].concat(t.permalinkClass?[["class",t.permalinkClass]]:[],[["href",t.permalinkHref(e,r)]],Object.entries(t.permalinkAttrs(e,r)))}),Object.assign(new r.Token("html_block","",0),{content:t.permalinkSymbol}),new r.Token("link_close","a",-1)];t.permalinkSpace&&o[n[!t.permalinkBefore]](Object.assign(new r.Token("text","",0),{content:" "})),(u=r.tokens[i+1].children)[n[t.permalinkBefore]].apply(u,o)},permalinkClass:"header-anchor",permalinkSpace:!0,permalinkSymbol:"¶",permalinkBefore:!1,permalinkHref:function(n){return"#"+n},permalinkAttrs:function(n){return{}}},r}); +//# sourceMappingURL=markdownItAnchor.umd.js.map diff --git a/src/data/extra/web/js/markdown-it/markdownItTocDoneRight.umd.js b/src/data/extra/web/js/markdown-it/markdownItTocDoneRight.umd.js new file mode 100644 index 00000000..aaddc06b --- /dev/null +++ b/src/data/extra/web/js/markdown-it/markdownItTocDoneRight.umd.js @@ -0,0 +1,2 @@ +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).markdownItTocDoneRight=n()}(this,function(){function e(e){return encodeURIComponent(String(e).trim().toLowerCase().replace(/\s+/g,"-"))}function n(e){return String(e).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}return function(t,r){var l;r=Object.assign({},{placeholder:"(\\$\\{toc\\}|\\[\\[?_?toc_?\\]?\\]|\\$\\)",slugify:e,uniqueSlugStartIndex:1,containerClass:"table-of-contents",containerId:void 0,listClass:void 0,itemClass:void 0,linkClass:void 0,level:1,listType:"ol",format:void 0,callback:void 0},r);var i=new RegExp("^"+r.placeholder+"$","i");t.renderer.rules.tocOpen=function(e,t){var l=Object.assign({},r);return e&&t>=0&&(l=Object.assign(l,e[t].inlineOptions)),"'},t.renderer.rules.tocClose=function(){return""},t.renderer.rules.tocBody=function(e,t){var i=Object.assign({},r);e&&t>=0&&(i=Object.assign(i,e[t].inlineOptions));var o,s={},c=Array.isArray(i.level)?(o=i.level,function(e){return o.includes(e)}):function(e){return function(n){return n>=e}}(i.level);return function e(t){var l=i.listClass?' class="'+n(i.listClass)+'"':"",o=i.itemClass?' class="'+n(i.itemClass)+'"':"",a=i.linkClass?' class="'+n(i.linkClass)+'"':"";if(0===t.c.length)return"";var u="";return(0===t.l||c(t.l))&&(u+="<"+(n(i.listType)+l)+">"),t.c.forEach(function(t){c(t.l)?u+="'+("function"==typeof i.format?i.format(t.n,n):n(t.n))+""+e(t)+"":u+=e(t)}),(0===t.l||c(t.l))&&(u+=""),u}(l)},t.core.ruler.push("generateTocAst",function(e){l=function(e){for(var n={l:0,n:"",c:[]},t=[n],r=0,l=e.length;rt[0].l)t[0].c.push(s),t.unshift(s);else if(s.l===t[0].l)t[1].c.push(s),t[0]=s;else{for(;s.l<=t[0].l;)t.shift();t[0].c.push(s),t.unshift(s)}}}return n}(e.tokens),"function"==typeof r.callback&&r.callback(t.renderer.rules.tocOpen()+t.renderer.rules.tocBody()+t.renderer.rules.tocClose(),l)}),t.block.ruler.before("heading","toc",function(e,n,t,r){var l,o=e.src.slice(e.bMarks[n]+e.tShift[n],e.eMarks[n]).split(" ")[0];if(!i.test(o))return!1;if(r)return!0;var s=i.exec(o),c={};if(null!==s&&3===s.length)try{c=JSON.parse(s[2])}catch(e){}return e.line=n+1,(l=e.push("tocOpen","nav",1)).markup="",l.map=[n,e.line],l.inlineOptions=c,(l=e.push("tocBody","",0)).markup="",l.map=[n,e.line],l.inlineOptions=c,l.children=[],(l=e.push("tocClose","nav",-1)).markup="",!0},{alt:["paragraph","reference","blockquote"]})}}); +//# sourceMappingURL=markdownItTocDoneRight.umd.js.map diff --git a/src/data/extra/web/js/markdownit.js b/src/data/extra/web/js/markdownit.js index 101e1960..7248227e 100644 --- a/src/data/extra/web/js/markdownit.js +++ b/src/data/extra/web/js/markdownit.js @@ -111,7 +111,8 @@ class MarkdownIt extends VxWorker { this.codeNodesCollected = false; // Used to deduplicate header Ids. - this.headerIds = new Set(); + // One for markdownItAnchor and one for markdownItTocDoneRight. + this.headerIds = [new Set(), new Set()]; this.mdit = window.markdownit({ html: this.options.enableHtmlTag, @@ -140,16 +141,6 @@ class MarkdownIt extends VxWorker { return /^file:/.test(str) ? true : this.defaultValidateLink(p_url); }; - this.mdit.use(window.markdownitHeadingAnchor, { - anchorClass: 'vx-anchor', - addHeadingID: true, - addHeadingAnchor: false, - anchorIcon: '#', - slugify: (md, str) => { - return this.generateHeaderId(str); - }, - }); - this.mdit.use(window.markdownitTaskLists); this.mdit.use(window.markdownitSub); @@ -216,6 +207,30 @@ class MarkdownIt extends VxWorker { this.mdit.use(window['markdown-it-xss']); }); } + + this.mdit.use(window.markdownItAnchor, { + slugify: (str) => { + return this.generateHeaderId(this.headerIds[0], str); + }, + permalink: true, + permalinkBefore: false, + permalinkClass: 'vx-header-anchor', + permalinkSpace: false, + // We use CSS:after to add the mark. + permalinkSymbol: '', + permalinkAttrs: (slug, state) => { + return { + 'vx-data-anchor-icon': '¶' + } + } + }); + + this.mdit.use(window.markdownItTocDoneRight, { + slugify: (str) => { + return this.generateHeaderId(this.headerIds[1], str); + }, + containerClass: 'vx-table-of-contents' + }); } registerInternal() { @@ -233,7 +248,8 @@ class MarkdownIt extends VxWorker { this.frontMatterNode = null; this.codeNodesStore.clearNodes(); this.codeNodesCollected = false; - this.headerIds.clear(); + this.headerIds[0].clear(); + this.headerIds[1].clear(); if (p_node != this.lastContainerNode) { this.lastContainerNode = p_node; @@ -300,15 +316,15 @@ class MarkdownIt extends VxWorker { return this.codeNodesStore.getNodes(p_langs); } - generateHeaderId(p_str) { + generateHeaderId(p_headerIds, p_str) { let idBase = p_str.replace(/\s/g, '-').toLowerCase(); let id = idBase; let idx = 1; - while (this.headerIds.has(id)) { + while (p_headerIds.has(id)) { id = idBase + '-' + idx; ++idx; } - this.headerIds.add(id); + p_headerIds.add(id); return id; } } diff --git a/src/data/extra/web/js/nodelinemapper.js b/src/data/extra/web/js/nodelinemapper.js index cd3d357b..f509a907 100644 --- a/src/data/extra/web/js/nodelinemapper.js +++ b/src/data/extra/web/js/nodelinemapper.js @@ -38,6 +38,10 @@ class NodeLineMapper { } } + getHeadingContent(p_node) { + return p_node.textContent; + } + updateHeadingNodes() { this.headingNodes = this.container.querySelectorAll("h1, h2, h3, h4, h5, h6"); let headings = []; @@ -45,12 +49,13 @@ class NodeLineMapper { let regExp = /^\d(?:\.\d)*\.? /; for (let i = 0; i < this.headingNodes.length; ++i) { let node = this.headingNodes[i]; + let headingContent = this.getHeadingContent(node); headings.push({ - name: node.textContent, + name: headingContent, level: parseInt(node.tagName.substr(1)), anchor: node.id }); - if (needSectionNumber && regExp.test(node.textContent)) { + if (needSectionNumber && regExp.test(headingContent)) { needSectionNumber = false; } } diff --git a/src/widgets/notebookexplorer.cpp b/src/widgets/notebookexplorer.cpp index 94bf9998..6620a006 100644 --- a/src/widgets/notebookexplorer.cpp +++ b/src/widgets/notebookexplorer.cpp @@ -293,3 +293,5 @@ void NotebookExplorer::locateNode(Node *p_node) m_nodeExplorer->setCurrentNode(p_node); m_nodeExplorer->setFocus(); } + + diff --git a/src/widgets/viewwindow.cpp b/src/widgets/viewwindow.cpp index 512a3d8d..6bc5d37a 100644 --- a/src/widgets/viewwindow.cpp +++ b/src/widgets/viewwindow.cpp @@ -409,7 +409,11 @@ QAction *ViewWindow::addAction(QToolBar *p_toolBar, ViewWindowToolBarHelper::Act act = ViewWindowToolBarHelper::addAction(p_toolBar, p_action); connect(act, &QAction::triggered, this, [this]() { - showFindAndReplaceWidget(); + if (m_findAndReplace && m_findAndReplace->isVisible()) { + hideFindAndReplaceWidget(); + } else { + showFindAndReplaceWidget(); + } }); break; }