add Showdown renderer

This commit is contained in:
Le Tan 2017-05-18 21:40:02 +08:00
parent 5bb692bcc5
commit f43f9c4afc
11 changed files with 272 additions and 2 deletions

176
src/resources/showdown.js Normal file
View File

@ -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(/<p>\[TOC\]<\/p>/ig, '<div class="vnote-toc"></div>');
} 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 '<a href="#' + item.anchor + '">' + item.title + '</a>';
};
// Turn a perfect toc to a tree using <ul>
var tocToTree = function(toc) {
var i;
var front = '<li>';
var ending = ['</li>'];
var curLevel = 1;
for (i in toc) {
var item = toc[i];
if (item.level == curLevel) {
front += '</li>';
front += '<li>';
front += itemToHtml(item);
} else if (item.level > curLevel) {
// assert(item.level - curLevel == 1)
front += '<ul>';
ending.push('</ul>');
front += '<li>';
front += itemToHtml(item);
ending.push('</li>');
curLevel = item.level;
} else {
while (item.level < curLevel) {
var ele = ending.pop();
front += ele;
if (ele == '</ul>') {
curLevel--;
}
}
front += '</li>';
front += '<li>';
front += itemToHtml(item);
}
}
while (ending.length > 0) {
front += ending.pop();
}
front = front.replace("<li></li>", "");
front = '<ul>' + front + '</ul>';
return front;
};
var handleToc = function(needToc) {
var tocTree = tocToTree(toPerfectToc(toc));
content.setToc(tocTree);
// Add it to html
if (needToc) {
var eles = document.getElementsByClassName('vnote-toc');
for (var i = 0; i < eles.length; ++i) {
eles[i].innerHTML = tocTree;
}
}
};
var mdHasTocSection = function(markdown) {
var n = markdown.search(/(\n|^)\[toc\]/i);
return n != -1;
};
var highlightCodeBlocks = function(doc, enableMermaid) {
var codes = doc.getElementsByTagName('code');
for (var i = 0; i < codes.length; ++i) {
var code = codes[i];
if (code.parentElement.tagName.toLowerCase() == 'pre') {
if (enableMermaid && code.classList.contains('language-mermaid')) {
// Mermaid code block.
continue;
} else {
hljs.highlightBlock(code);
}
}
}
};
var updateText = function(text) {
var needToc = mdHasTocSection(text);
var html = markdownToHtml(text, needToc);
placeholder.innerHTML = html;
handleToc(needToc);
insertImageCaption();
highlightCodeBlocks(document, VEnableMermaid);
renderMermaid('language-mermaid');
if (VEnableMathjax) {
try {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, placeholder]);
} catch (err) {
content.setLog("err: " + err);
}
}
};
var highlightText = function(text, id, timeStamp) {
var html = renderer.makeHtml(text);
var parser = new DOMParser();
var htmlDoc = parser.parseFromString("<div id=\"showdown-container\">" + html + "</div>", 'text/html');
highlightCodeBlocks(htmlDoc, false);
html = htmlDoc.getElementById('showdown-container').innerHTML;
delete parser;
content.highlightTextCB(html, id, timeStamp);
}

View File

@ -14,10 +14,13 @@ current_background_color=System
current_render_background_color=System
language=System
editor_font_size=12
; 0 - Hoedown, 1 - Marked, 2 - Markdown-it
; 0 - Hoedown, 1 - Marked, 2 - Markdown-it, 3 - Showdown
markdown_converter=2
enable_mermaid=false
enable_mathjax=false
; -1 - calculate the factor
web_zoom_factor=-1

View File

@ -0,0 +1,2 @@
# [showdown](https://github.com/showdownjs/showdown)
v1.6.4

View File

@ -0,0 +1,50 @@
(function (extension) {
if (typeof showdown !== 'undefined') {
// global (browser or nodejs global)
extension(showdown);
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['showdown'], extension);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = extension(require('showdown'));
} else {
// showdown was not found so we throw
throw Error('Could not find showdown library');
}
}(function (showdown) {
// loading extension into shodown
showdown.extension('headinganchor', function () {
var headinganchor = {
type: 'output',
filter: function(source) {
var nameCounter = 0;
var parser = new DOMParser();
var htmlDoc = parser.parseFromString("<div id=\"showdown-container\">" + source + "</div>", '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));
var escapedText = 'toc_' + nameCounter++;
toc.push({
level: level,
anchor: escapedText,
title: ele.innerText
});
ele.setAttribute('id', escapedText);
}
var html = htmlDoc.getElementById('showdown-container').innerHTML;
delete parser;
return html;
}
};
return [headinganchor];
});
}));

5
src/utils/showdown/showdown.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,8 @@ enum MarkdownConverterType
{
Hoedown = 0,
Marked,
MarkdownIt
MarkdownIt,
Showdown
};
struct VColor

View File

@ -335,6 +335,13 @@ void VEditTab::setupMarkdownPreview()
"<script src=\"qrc" + VNote::c_markdownitTaskListExtraFile + "\"></script>\n";
break;
case MarkdownConverterType::Showdown:
jsFile = "qrc" + VNote::c_showdownJsFile;
extraFile = "<script src=\"qrc" + VNote::c_showdownExtraFile + "\"></script>\n" +
"<script src=\"qrc" + VNote::c_showdownAnchorExtraFile + "\"></script>\n";
break;
default:
Q_ASSERT(false);
}

View File

@ -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);
}

View File

@ -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";

View File

@ -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;

View File

@ -101,5 +101,8 @@
<file>resources/styles/default.css</file>
<file>resources/icons/add_style.svg</file>
<file>utils/highlightjs/styles/vnote.css</file>
<file>utils/showdown/showdown.min.js</file>
<file>resources/showdown.js</file>
<file>utils/showdown/showdown-headinganchor.js</file>
</qresource>
</RCC>