mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 22:09:52 +08:00
add Showdown renderer
This commit is contained in:
parent
5bb692bcc5
commit
f43f9c4afc
176
src/resources/showdown.js
Normal file
176
src/resources/showdown.js
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -14,10 +14,13 @@ current_background_color=System
|
|||||||
current_render_background_color=System
|
current_render_background_color=System
|
||||||
language=System
|
language=System
|
||||||
editor_font_size=12
|
editor_font_size=12
|
||||||
; 0 - Hoedown, 1 - Marked, 2 - Markdown-it
|
|
||||||
|
; 0 - Hoedown, 1 - Marked, 2 - Markdown-it, 3 - Showdown
|
||||||
markdown_converter=2
|
markdown_converter=2
|
||||||
|
|
||||||
enable_mermaid=false
|
enable_mermaid=false
|
||||||
enable_mathjax=false
|
enable_mathjax=false
|
||||||
|
|
||||||
; -1 - calculate the factor
|
; -1 - calculate the factor
|
||||||
web_zoom_factor=-1
|
web_zoom_factor=-1
|
||||||
|
|
||||||
|
2
src/utils/showdown/README.md
Normal file
2
src/utils/showdown/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# [showdown](https://github.com/showdownjs/showdown)
|
||||||
|
v1.6.4
|
50
src/utils/showdown/showdown-headinganchor.js
Normal file
50
src/utils/showdown/showdown-headinganchor.js
Normal 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
5
src/utils/showdown/showdown.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -16,7 +16,8 @@ enum MarkdownConverterType
|
|||||||
{
|
{
|
||||||
Hoedown = 0,
|
Hoedown = 0,
|
||||||
Marked,
|
Marked,
|
||||||
MarkdownIt
|
MarkdownIt,
|
||||||
|
Showdown
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VColor
|
struct VColor
|
||||||
|
@ -335,6 +335,13 @@ void VEditTab::setupMarkdownPreview()
|
|||||||
"<script src=\"qrc" + VNote::c_markdownitTaskListExtraFile + "\"></script>\n";
|
"<script src=\"qrc" + VNote::c_markdownitTaskListExtraFile + "\"></script>\n";
|
||||||
break;
|
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:
|
default:
|
||||||
Q_ASSERT(false);
|
Q_ASSERT(false);
|
||||||
}
|
}
|
||||||
|
@ -324,11 +324,17 @@ void VMainWindow::initMarkdownMenu()
|
|||||||
markdownitAct->setCheckable(true);
|
markdownitAct->setCheckable(true);
|
||||||
markdownitAct->setData(int(MarkdownConverterType::MarkdownIt));
|
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,
|
connect(converterAct, &QActionGroup::triggered,
|
||||||
this, &VMainWindow::changeMarkdownConverter);
|
this, &VMainWindow::changeMarkdownConverter);
|
||||||
converterMenu->addAction(hoedownAct);
|
converterMenu->addAction(hoedownAct);
|
||||||
converterMenu->addAction(markedAct);
|
converterMenu->addAction(markedAct);
|
||||||
converterMenu->addAction(markdownitAct);
|
converterMenu->addAction(markdownitAct);
|
||||||
|
converterMenu->addAction(showdownAct);
|
||||||
|
|
||||||
MarkdownConverterType converterType = vconfig.getMdConverterType();
|
MarkdownConverterType converterType = vconfig.getMdConverterType();
|
||||||
switch (converterType) {
|
switch (converterType) {
|
||||||
@ -344,6 +350,10 @@ void VMainWindow::initMarkdownMenu()
|
|||||||
markdownitAct->setChecked(true);
|
markdownitAct->setChecked(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MarkdownConverterType::Showdown:
|
||||||
|
showdownAct->setChecked(true);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Q_ASSERT(false);
|
Q_ASSERT(false);
|
||||||
}
|
}
|
||||||
@ -697,8 +707,11 @@ void VMainWindow::changeMarkdownConverter(QAction *action)
|
|||||||
if (!action) {
|
if (!action) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MarkdownConverterType type = (MarkdownConverterType)action->data().toInt();
|
MarkdownConverterType type = (MarkdownConverterType)action->data().toInt();
|
||||||
|
|
||||||
qDebug() << "switch to converter" << type;
|
qDebug() << "switch to converter" << type;
|
||||||
|
|
||||||
vconfig.setMarkdownConverterType(type);
|
vconfig.setMarkdownConverterType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
extern VConfigManager vconfig;
|
extern VConfigManager vconfig;
|
||||||
|
|
||||||
QString VNote::s_markdownTemplate;
|
QString VNote::s_markdownTemplate;
|
||||||
|
|
||||||
const QString VNote::c_hoedownJsFile = ":/resources/hoedown.js";
|
const QString VNote::c_hoedownJsFile = ":/resources/hoedown.js";
|
||||||
const QString VNote::c_markedJsFile = ":/resources/marked.js";
|
const QString VNote::c_markedJsFile = ":/resources/marked.js";
|
||||||
const QString VNote::c_markedExtraFile = ":/utils/marked/marked.min.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_markdownitExtraFile = ":/utils/markdown-it/markdown-it.min.js";
|
||||||
const QString VNote::c_markdownitAnchorExtraFile = ":/utils/markdown-it/markdown-it-headinganchor.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_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_mermaidApiJsFile = ":/utils/mermaid/mermaidAPI.min.js";
|
||||||
const QString VNote::c_mermaidCssFile = ":/utils/mermaid/mermaid.css";
|
const QString VNote::c_mermaidCssFile = ":/utils/mermaid/mermaid.css";
|
||||||
const QString VNote::c_mermaidDarkCssFile = ":/utils/mermaid/mermaid.dark.css";
|
const QString VNote::c_mermaidDarkCssFile = ":/utils/mermaid/mermaid.dark.css";
|
||||||
|
@ -42,6 +42,11 @@ public:
|
|||||||
static const QString c_markdownitAnchorExtraFile;
|
static const QString c_markdownitAnchorExtraFile;
|
||||||
static const QString c_markdownitTaskListExtraFile;
|
static const QString c_markdownitTaskListExtraFile;
|
||||||
|
|
||||||
|
// Showdown
|
||||||
|
static const QString c_showdownJsFile;
|
||||||
|
static const QString c_showdownExtraFile;
|
||||||
|
static const QString c_showdownAnchorExtraFile;
|
||||||
|
|
||||||
// Mermaid
|
// Mermaid
|
||||||
static const QString c_mermaidApiJsFile;
|
static const QString c_mermaidApiJsFile;
|
||||||
static const QString c_mermaidCssFile;
|
static const QString c_mermaidCssFile;
|
||||||
|
@ -101,5 +101,8 @@
|
|||||||
<file>resources/styles/default.css</file>
|
<file>resources/styles/default.css</file>
|
||||||
<file>resources/icons/add_style.svg</file>
|
<file>resources/icons/add_style.svg</file>
|
||||||
<file>utils/highlightjs/styles/vnote.css</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>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user