diff --git a/src/resources/showdown.js b/src/resources/showdown.js new file mode 100644 index 00000000..49716fd3 --- /dev/null +++ b/src/resources/showdown.js @@ -0,0 +1,176 @@ +var placeholder = document.getElementById('placeholder'); +var renderer = new showdown.Converter({simplifiedAutoLink: 'true', + excludeTrailingPunctuationFromURLs: 'true', + strikethrough: 'true', + tables: 'true', + tasklists: 'true', + literalMidWordUnderscores: 'true', + extensions: ['headinganchor'] + }); + +var toc = []; // Table of contents as a list + +// Parse the html's headings and construct toc[]. +var parseHeadings = function(html) { + toc = []; + var parser = new DOMParser(); + var htmlDoc = parser.parseFromString(html, 'text/html'); + var eles = htmlDoc.querySelectorAll("h1, h2, h3, h4, h5, h6"); + + for (var i = 0; i < eles.length; ++i) { + var ele = eles[i]; + var level = parseInt(ele.tagName.substr(1)); + + toc.push({ + level: level, + anchor: ele.id, + title: ele.innerText + }); + } + + delete parser; +}; + +var markdownToHtml = function(markdown, needToc) { + var html = renderer.makeHtml(markdown); + + // Parse the html to init toc[]. + parseHeadings(html); + + if (needToc) { + return html.replace(/
\[TOC\]<\/p>/ig, '
'); + } else { + return html; + } +}; + +// Handle wrong title levels, such as '#' followed by '###' +var toPerfectToc = function(toc) { + var i; + var curLevel = 1; + var perfToc = []; + for (i in toc) { + var item = toc[i]; + while (item.level > curLevel + 1) { + curLevel += 1; + var tmp = { level: curLevel, + anchor: item.anchor, + title: '[EMPTY]' + }; + perfToc.push(tmp); + } + perfToc.push(item); + curLevel = item.level; + } + return perfToc; +}; + +var itemToHtml = function(item) { + return '' + item.title + ''; +}; + +// Turn a perfect toc to a tree using[^\r]+?<\/pre>)/gm,function(a,b){var c=b;return c=c.replace(/^ /gm,"¨0"),c=c.replace(/¨0/g,"")}),e.subParser("hashBlock")("","gim"),a=c.converter._dispatch("hashPreCodeTags.after",a,b,c)}),e.subParser("headers",function(a,b,c){"use strict";function d(a){var d;return d=e.helper.isString(b.prefixHeaderId)?b.prefixHeaderId+a:b.prefixHeaderId===!0?"section "+a:a,d=g?d.replace(/ /g,"-").replace(/&/g,"").replace(/¨T/g,"").replace(/¨D/g,"").replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g,"").toLowerCase():d.replace(/[^\w]/g,"").toLowerCase(),c.hashLinkCounts[d]?d=d+"-"+c.hashLinkCounts[d]++:c.hashLinkCounts[d]=1,d}a=c.converter._dispatch("headers.before",a,b,c);var f=isNaN(parseInt(b.headerLevelStart))?1:parseInt(b.headerLevelStart),g=b.ghCompatibleHeaderId,h=b.smoothLivePreview?/^(.+)[ \t]*\n={2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n=+[ \t]*\n+/gm,i=b.smoothLivePreview?/^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n-+[ \t]*\n+/gm;a=a.replace(h,function(a,g){var h=e.subParser("spanGamut")(g,b,c),i=b.noHeaderId?"":' id="'+d(g)+'"',j=f,k="\n"+f+"\n",b,c)}),a=c.converter._dispatch("blockQuotes.after",a,b,c)}),e.subParser("codeBlocks",function(a,b,c){"use strict";a=c.converter._dispatch("codeBlocks.before",a,b,c),a+="¨0";var d=/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g;return a=a.replace(d,function(a,d,f){var g=d,h=f,i="\n";return g=e.subParser("outdent")(g,b,c),g=e.subParser("encodeCode")(g,b,c),g=e.subParser("detab")(g,b,c),g=g.replace(/^\n+/g,""),g=g.replace(/\n+$/g,""),b.omitExtraWLInCodeBlocks&&(i=""),g="",e.subParser("hashBlock")(g,b,c)+h}),a=a.replace(/¨0/,""),a=c.converter._dispatch("codeBlocks.after",a,b,c)}),e.subParser("codeSpans",function(a,b,c){"use strict";return a=c.converter._dispatch("codeSpans.before",a,b,c),"undefined"==typeof a&&(a=""),a=a.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(a,d,f,g){var h=g;return h=h.replace(/^([ \t]*)/g,""),h=h.replace(/[ \t]*$/g,""),h=e.subParser("encodeCode")(h,b,c),d+""+g+i+"
"+h+"
"}),a=c.converter._dispatch("codeSpans.after",a,b,c)}),e.subParser("detab",function(a,b,c){"use strict";return a=c.converter._dispatch("detab.before",a,b,c),a=a.replace(/\t(?=\t)/g," "),a=a.replace(/\t/g,"¨A¨B"),a=a.replace(/¨B(.+?)¨A/g,function(a,b){for(var c=b,d=4-c.length%4,e=0;e/g,">"),a=c.converter._dispatch("encodeAmpsAndAngles.after",a,b,c)}),e.subParser("encodeBackslashEscapes",function(a,b,c){"use strict";return a=c.converter._dispatch("encodeBackslashEscapes.before",a,b,c),a=a.replace(/\\(\\)/g,e.helper.escapeCharactersCallback),a=a.replace(/\\([`*_{}\[\]()>#+.!~=|-])/g,e.helper.escapeCharactersCallback),a=c.converter._dispatch("encodeBackslashEscapes.after",a,b,c)}),e.subParser("encodeCode",function(a,b,c){"use strict";return a=c.converter._dispatch("encodeCode.before",a,b,c),a=a.replace(/&/g,"&").replace(//g,">").replace(/([*_{}\[\]\\=~-])/g,e.helper.escapeCharactersCallback),a=c.converter._dispatch("encodeCode.after",a,b,c)}),e.subParser("escapeSpecialCharsWithinTagAttributes",function(a,b,c){"use strict";a=c.converter._dispatch("escapeSpecialCharsWithinTagAttributes.before",a,b,c);var d=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi;return a=a.replace(d,function(a){return a.replace(/(.)<\/?code>(?=.)/g,"$1`").replace(/([\\`*_~=|])/g,e.helper.escapeCharactersCallback)}),a=c.converter._dispatch("escapeSpecialCharsWithinTagAttributes.after",a,b,c)}),e.subParser("githubCodeBlocks",function(a,b,c){"use strict";return b.ghCodeBlocks?(a=c.converter._dispatch("githubCodeBlocks.before",a,b,c),a+="¨0",a=a.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g,function(a,d,f){var g=b.omitExtraWLInCodeBlocks?"":"\n";return f=e.subParser("encodeCode")(f,b,c),f=e.subParser("detab")(f,b,c),f=f.replace(/^\n+/g,""),f=f.replace(/\n+$/g,""),f=" ",f=e.subParser("hashBlock")(f,b,c),"\n\n¨G"+(c.ghCodeBlocks.push({text:a,codeblock:f})-1)+"G\n\n"}),a=a.replace(/¨0/,""),c.converter._dispatch("githubCodeBlocks.after",a,b,c)):a}),e.subParser("hashBlock",function(a,b,c){"use strict";return a=c.converter._dispatch("hashBlock.before",a,b,c),a=a.replace(/(^\n+|\n+$)/g,""),a="\n\n¨K"+(c.gHtmlBlocks.push(a)-1)+"K\n\n",a=c.converter._dispatch("hashBlock.after",a,b,c)}),e.subParser("hashCodeTags",function(a,b,c){"use strict";a=c.converter._dispatch("hashCodeTags.before",a,b,c);var d=function(a,d,f,g){var h=f+e.subParser("encodeCode")(d,b,c)+g;return"¨C"+(c.gHtmlSpans.push(h)-1)+"C"};return a=e.helper.replaceRecursiveRegExp(a,d,""+f+g+"
]*>","
","gim"),a=c.converter._dispatch("hashCodeTags.after",a,b,c)}),e.subParser("hashElement",function(a,b,c){"use strict";return function(a,b){var d=b;return d=d.replace(/\n\n/g,"\n"),d=d.replace(/^\n/,""),d=d.replace(/\n+$/g,""),d="\n\n¨K"+(c.gHtmlBlocks.push(d)-1)+"K\n\n"}}),e.subParser("hashHTMLBlocks",function(a,b,c){"use strict";a=c.converter._dispatch("hashHTMLBlocks.before",a,b,c);for(var d=["pre","div","h1","h2","h3","h4","h5","h6","blockquote","table","dl","ol","ul","script","noscript","form","fieldset","iframe","math","style","section","header","footer","nav","article","aside","address","audio","canvas","figure","hgroup","output","video","p"],f=function(a,b,d,e){var f=a;return d.search(/\bmarkdown\b/)!==-1&&(f=d+c.converter.makeHtml(b)+e),"\n\n¨K"+(c.gHtmlBlocks.push(f)-1)+"K\n\n"},g=0;g]*>",""+d[g]+">","gim");return a=a.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,e.subParser("hashElement")(a,b,c)),a=e.helper.replaceRecursiveRegExp(a,function(a){return"\n\n¨K"+(c.gHtmlBlocks.push(a)-1)+"K\n\n"},"^ {0,3}","gm"),a=a.replace(/(?:\n\n)( {0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,e.subParser("hashElement")(a,b,c)),a=c.converter._dispatch("hashHTMLBlocks.after",a,b,c)}),e.subParser("hashHTMLSpans",function(a,b,c){"use strict";function d(a){return"¨C"+(c.gHtmlSpans.push(a)-1)+"C"}return a=c.converter._dispatch("hashHTMLSpans.before",a,b,c),a=a.replace(/<[^>]+?\/>/gi,function(a){return d(a)}),a=a.replace(/<([^>]+?)>[\s\S]*?<\/\1>/g,function(a){return d(a)}),a=a.replace(/<([^>]+?)\s[^>]+?>[\s\S]*?<\/\1>/g,function(a){return d(a)}),a=a.replace(/<[^>]+?>/gi,function(a){return d(a)}),a=c.converter._dispatch("hashHTMLSpans.after",a,b,c)}),e.subParser("unhashHTMLSpans",function(a,b,c){"use strict";a=c.converter._dispatch("unhashHTMLSpans.before",a,b,c);for(var d=0;d ]*>\\s* ]*>","^ {0,3}
\\s*
"),i+="
",f.push(i))}for(g=f.length,h=0;h]*>/.test(k)&&(l=!0)}f[h]=k}return a=f.join("\n"),a=a.replace(/^\n+/g,""),a=a.replace(/\n+$/g,""),c.converter._dispatch("paragraphs.after",a,b,c)}),e.subParser("runExtension",function(a,b,c,d){"use strict";if(a.filter)b=a.filter(b,d.converter,c);else if(a.regex){var e=a.regex;e instanceof RegExp||(e=new RegExp(e,"g")),b=b.replace(e,a.replace)}return b}),e.subParser("spanGamut",function(a,b,c){"use strict";return a=c.converter._dispatch("spanGamut.before",a,b,c),a=e.subParser("codeSpans")(a,b,c),a=e.subParser("escapeSpecialCharsWithinTagAttributes")(a,b,c),a=e.subParser("encodeBackslashEscapes")(a,b,c),a=e.subParser("images")(a,b,c),a=e.subParser("anchors")(a,b,c),a=e.subParser("autoLinks")(a,b,c),a=e.subParser("italicsAndBold")(a,b,c),a=e.subParser("strikethrough")(a,b,c),a=e.subParser("simplifiedAutoLinks")(a,b,c),a=e.subParser("hashHTMLSpans")(a,b,c),a=e.subParser("encodeAmpsAndAngles")(a,b,c),a=b.simpleLineBreaks?a.replace(/\n/g,"
\n"):a.replace(/ +\n/g,"
\n"),a=c.converter._dispatch("spanGamut.after",a,b,c)}),e.subParser("strikethrough",function(a,b,c){"use strict";function d(a){return b.simplifiedAutoLink&&(a=e.subParser("simplifiedAutoLinks")(a,b,c)),""+a+""}return b.strikethrough&&(a=c.converter._dispatch("strikethrough.before",a,b,c),a=a.replace(/(?:~){2}([\s\S]+?)(?:~){2}/g,function(a,b){return d(b)}),a=c.converter._dispatch("strikethrough.after",a,b,c)),a}),e.subParser("stripLinkDefinitions",function(a,b,c){"use strict";var d=/^ {0,3}\[(.+)]:[ \t]*\n?[ \t]*(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm;return a+="¨0",a=a.replace(d,function(a,d,f,g,h,i,j){return d=d.toLowerCase(),c.gUrls[d]=e.subParser("encodeAmpsAndAngles")(f,b,c),i?i+j:(j&&(c.gTitles[d]=j.replace(/"|'/g,""")),b.parseImgDimensions&&g&&h&&(c.gDimensions[d]={width:g,height:h}),"")}),a=a.replace(/¨0/,"")}),e.subParser("tables",function(a,b,c){"use strict";function d(a){return/^:[ \t]*--*$/.test(a)?' style="text-align:left;"':/^--*[ \t]*:[ \t]*$/.test(a)?' style="text-align:right;"':/^:[ \t]*--*[ \t]*:$/.test(a)?' style="text-align:center;"':""}function f(a,d){var f="";return a=a.trim(),b.tableHeaderId&&(f=' id="'+a.replace(/ /g,"_").toLowerCase()+'"'),a=e.subParser("spanGamut")(a,b,c),""+a+" \n"}function g(a,d){var f=e.subParser("spanGamut")(a,b,c);return""+f+" \n"}function h(a,b){for(var c="\n\n\n",d=a.length,e=0;e\n \n\n",e=0;e\n";for(var f=0;f\n"}return c+=" \n
\n"}if(!b.tables)return a;var i=/^ {0,3}\|?.+\|.+\n[ \t]{0,3}\|?[ \t]*:?[ \t]*(?:-|=){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:-|=){2,}[\s\S]+?(?:\n\n|¨0)/gm;return a=c.converter._dispatch("tables.before",a,b,c),a=a.replace(/\\(\|)/g,e.helper.escapeCharactersCallback),a=a.replace(i,function(a){var b,c=a.split("\n");for(b=0;b\n";
break;
+ case MarkdownConverterType::Showdown:
+ jsFile = "qrc" + VNote::c_showdownJsFile;
+ extraFile = "\n" +
+ "\n";
+
+ break;
+
default:
Q_ASSERT(false);
}
diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp
index 65fda46a..5d1c4581 100644
--- a/src/vmainwindow.cpp
+++ b/src/vmainwindow.cpp
@@ -324,11 +324,17 @@ void VMainWindow::initMarkdownMenu()
markdownitAct->setCheckable(true);
markdownitAct->setData(int(MarkdownConverterType::MarkdownIt));
+ QAction *showdownAct = new QAction(tr("Showdown"), converterAct);
+ showdownAct->setToolTip(tr("Use Showdown to convert Markdown to HTML (re-open current tabs to make it work)"));
+ showdownAct->setCheckable(true);
+ showdownAct->setData(int(MarkdownConverterType::Showdown));
+
connect(converterAct, &QActionGroup::triggered,
this, &VMainWindow::changeMarkdownConverter);
converterMenu->addAction(hoedownAct);
converterMenu->addAction(markedAct);
converterMenu->addAction(markdownitAct);
+ converterMenu->addAction(showdownAct);
MarkdownConverterType converterType = vconfig.getMdConverterType();
switch (converterType) {
@@ -344,6 +350,10 @@ void VMainWindow::initMarkdownMenu()
markdownitAct->setChecked(true);
break;
+ case MarkdownConverterType::Showdown:
+ showdownAct->setChecked(true);
+ break;
+
default:
Q_ASSERT(false);
}
@@ -697,8 +707,11 @@ void VMainWindow::changeMarkdownConverter(QAction *action)
if (!action) {
return;
}
+
MarkdownConverterType type = (MarkdownConverterType)action->data().toInt();
+
qDebug() << "switch to converter" << type;
+
vconfig.setMarkdownConverterType(type);
}
diff --git a/src/vnote.cpp b/src/vnote.cpp
index 7654c0e6..1e72629e 100644
--- a/src/vnote.cpp
+++ b/src/vnote.cpp
@@ -16,6 +16,7 @@
extern VConfigManager vconfig;
QString VNote::s_markdownTemplate;
+
const QString VNote::c_hoedownJsFile = ":/resources/hoedown.js";
const QString VNote::c_markedJsFile = ":/resources/marked.js";
const QString VNote::c_markedExtraFile = ":/utils/marked/marked.min.js";
@@ -23,6 +24,10 @@ const QString VNote::c_markdownitJsFile = ":/resources/markdown-it.js";
const QString VNote::c_markdownitExtraFile = ":/utils/markdown-it/markdown-it.min.js";
const QString VNote::c_markdownitAnchorExtraFile = ":/utils/markdown-it/markdown-it-headinganchor.js";
const QString VNote::c_markdownitTaskListExtraFile = ":/utils/markdown-it/markdown-it-task-lists.min.js";
+const QString VNote::c_showdownJsFile = ":/resources/showdown.js";
+const QString VNote::c_showdownExtraFile = ":/utils/showdown/showdown.min.js";
+const QString VNote::c_showdownAnchorExtraFile = ":/utils/showdown/showdown-headinganchor.js";
+
const QString VNote::c_mermaidApiJsFile = ":/utils/mermaid/mermaidAPI.min.js";
const QString VNote::c_mermaidCssFile = ":/utils/mermaid/mermaid.css";
const QString VNote::c_mermaidDarkCssFile = ":/utils/mermaid/mermaid.dark.css";
diff --git a/src/vnote.h b/src/vnote.h
index a6f04c28..3769f305 100644
--- a/src/vnote.h
+++ b/src/vnote.h
@@ -42,6 +42,11 @@ public:
static const QString c_markdownitAnchorExtraFile;
static const QString c_markdownitTaskListExtraFile;
+ // Showdown
+ static const QString c_showdownJsFile;
+ static const QString c_showdownExtraFile;
+ static const QString c_showdownAnchorExtraFile;
+
// Mermaid
static const QString c_mermaidApiJsFile;
static const QString c_mermaidCssFile;
diff --git a/src/vnote.qrc b/src/vnote.qrc
index 6f77872e..4fe9f615 100644
--- a/src/vnote.qrc
+++ b/src/vnote.qrc
@@ -101,5 +101,8 @@
resources/styles/default.css
resources/icons/add_style.svg
utils/highlightjs/styles/vnote.css
+ utils/showdown/showdown.min.js
+ resources/showdown.js
+ utils/showdown/showdown-headinganchor.js