diff --git a/src/resources/hoedown.js b/src/resources/hoedown.js new file mode 100644 index 00000000..2e1ddd2e --- /dev/null +++ b/src/resources/hoedown.js @@ -0,0 +1,41 @@ +var placeholder = document.getElementById('placeholder'); + +var scrollToAnchor = function(anchor) { + var anc = document.getElementById(anchor); + if (anc != null) { + anc.scrollIntoView(); + } +}; + +var updateHtml = function(html) { + placeholder.innerHTML = html; + var codes = document.getElementsByTagName('code'); + for (var i = 0; i < codes.length; ++i) { + if (codes[i].parentElement.tagName.toLowerCase() == 'pre') { + hljs.highlightBlock(codes[i]); + } + } +} + +var onWindowScroll = function() { + var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset; + var eles = document.querySelectorAll("h1, h2, h3, h4, h5, h6"); + + if (eles.length == 0) { + return; + } + var curIdx = 0; + var biaScrollTop = scrollTop + 50; + for (var i = 0; i < eles.length; ++i) { + if (biaScrollTop >= eles[i].offsetTop) { + curIdx = i; + } else { + break; + } + } + + var curHeader = eles[curIdx].getAttribute("id"); + if (curHeader != null) { + content.setHeader(curHeader); + } +} diff --git a/src/resources/markdown_template.html b/src/resources/markdown_template.html new file mode 100644 index 00000000..f88c7b6b --- /dev/null +++ b/src/resources/markdown_template.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + +
+ + diff --git a/src/resources/markdown_template.js b/src/resources/markdown_template.js new file mode 100644 index 00000000..a3a05700 --- /dev/null +++ b/src/resources/markdown_template.js @@ -0,0 +1,96 @@ +var content; +var keyState = 0; + +new QWebChannel(qt.webChannelTransport, + function(channel) { + content = channel.objects.content; + if (typeof updateHtml == "function") { + updateHtml(content.html); + content.htmlChanged.connect(updateHtml); + } + if (typeof updateText == "function") { + content.textChanged.connect(updateText); + content.updateText(); + } + content.requestScrollToAnchor.connect(scrollToAnchor); + }); + +window.onscroll = onWindowScroll; + +document.onkeydown = function(e) { + e = e || window.event; + var key; + var shift; + var ctrl; + if (e.which) { + key = e.which; + } else { + key = e.keyCode; + } + shift = !!e.shiftKey; + ctrl = !!e.ctrlKey; + switch (key) { + case 74: // J + window.scrollBy(0, 100); + keyState = 0; + break; + + case 75: // K + window.scrollBy(0, -100); + keyState = 0; + break; + + case 72: // H + window.scrollBy(-100, 0); + keyState = 0; + break; + + case 76: // L + window.scrollBy(100, 0); + keyState = 0; + break; + + case 71: // G + if (shift) { + var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft || window.pageXOffset; + var scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight; + window.scrollTo(scrollLeft, scrollHeight); + keyState = 0; + break; + } else { + if (keyState == 0) { + keyState = 1; + } else if (keyState == 1) { + keyState = 0; + var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft || window.pageXOffset; + window.scrollTo(scrollLeft, 0); + } + break; + } + return; + + case 85: // U + keyState = 0; + if (ctrl) { + var clientHeight = document.documentElement.clientHeight; + window.scrollBy(0, -clientHeight); + break; + } + return; + + case 68: // D + keyState = 0; + if (ctrl) { + var clientHeight = document.documentElement.clientHeight; + window.scrollBy(0, clientHeight); + break; + } + return; + + default: + content.keyPressEvent(key); + keyState = 0; + return; + } + e.preventDefault(); +} diff --git a/src/resources/marked.js b/src/resources/marked.js new file mode 100644 index 00000000..7a01aa82 --- /dev/null +++ b/src/resources/marked.js @@ -0,0 +1,154 @@ +var placeholder = document.getElementById('placeholder'); +var renderer = new marked.Renderer(); +var toc = []; // Table of contents as a list +var nameCounter = 0; + +renderer.heading = function(text, level) { + // Use number to avoid issues with Chinese + var escapedText = 'toc_' + nameCounter++; + toc.push({ + level: level, + anchor: escapedText, + title: text + }); + return '' + text + ''; +}; + +// Highlight.js to highlight code block +marked.setOptions({ + highlight: function(code) { + return hljs.highlightAuto(code).value; + } +}); + +var markdownToHtml = function(markdown, needToc) { + toc = []; + var html = marked(markdown, { renderer: renderer }); + nameCounter = 0; + 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 ') { + curLevel--; + } + } + front += ''; + front += '
  • '; + front += itemToHtml(item); + } + } + while (ending.length > 0) { + front += ending.pop(); + } + front = front.replace("
  • ", ""); + front = ''; + 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 updateText = function(text) { + var needToc = mdHasTocSection(text); + var html = markdownToHtml(text, needToc); + placeholder.innerHTML = html; + handleToc(needToc); +}; + +var scrollToAnchor = function(anchor) { + var anc = document.getElementById(anchor); + if (anc != null) { + anc.scrollIntoView(); + } +}; + +var onWindowScroll = function() { + var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset; + var eles = document.querySelectorAll("h1, h2, h3, h4, h5, h6"); + + if (eles.length == 0) { + return; + } + var curIdx = 0; + var biaScrollTop = scrollTop + 50; + for (var i = 0; i < eles.length; ++i) { + if (biaScrollTop >= eles[i].offsetTop) { + curIdx = i; + } else { + break; + } + } + + var curHeader = eles[curIdx].getAttribute("id"); + if (curHeader != null) { + content.setHeader(curHeader); + } +} diff --git a/src/resources/post_template.html b/src/resources/post_template.html deleted file mode 100644 index 308b1d01..00000000 --- a/src/resources/post_template.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/resources/pre_template.html b/src/resources/pre_template.html deleted file mode 100644 index f3b7a0c6..00000000 --- a/src/resources/pre_template.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - - - - -
    - diff --git a/src/resources/template.html b/src/resources/template.html deleted file mode 100644 index d0a4095c..00000000 --- a/src/resources/template.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - - - - - - - -
    - - - - - - diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index ac074333..cad5003e 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -1,8 +1,5 @@ [global] welcome_page_path=:/resources/welcome.html -template_path=:/resources/template.html -pre_template_path=:/resources/pre_template.html -post_template_path=:/resources/post_template.html template_css_url=qrc:/resources/markdown.css current_notebook=0 tab_stop_width=4 diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index 62b96a49..46fad81b 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -43,9 +43,6 @@ void VConfigManager::initialize() baseEditPalette = QTextEdit().palette(); welcomePagePath = getConfigFromSettings("global", "welcome_page_path").toString(); - templatePath = getConfigFromSettings("global", "template_path").toString(); - preTemplatePath = getConfigFromSettings("global", "pre_template_path").toString(); - postTemplatePath = getConfigFromSettings("global", "post_template_path").toString(); templateCssUrl = getConfigFromSettings("global", "template_css_url").toString(); curNotebookIndex = getConfigFromSettings("global", "current_notebook").toInt(); diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index 746dcb46..21d71810 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -51,8 +51,6 @@ public: inline QString getWelcomePagePath() const; - inline QString getTemplatePath() const; - inline QString getTemplateCssUrl() const; inline QFont getBaseEditFont() const; @@ -68,9 +66,6 @@ public: inline MarkdownConverterType getMdConverterType() const; inline void setMarkdownConverterType(MarkdownConverterType type); - inline QString getPreTemplatePath() const; - inline QString getPostTemplatePath() const; - inline int getTabStopWidth() const; inline void setTabStopWidth(int tabStopWidth); inline bool getIsExpandTab() const; @@ -137,9 +132,6 @@ private: QPalette mdEditPalette; QVector mdHighlightingStyles; QString welcomePagePath; - QString templatePath; - QString preTemplatePath; - QString postTemplatePath; QString templateCssUrl; int curNotebookIndex; @@ -212,11 +204,6 @@ inline QString VConfigManager::getWelcomePagePath() const return welcomePagePath; } -inline QString VConfigManager::getTemplatePath() const -{ - return templatePath; -} - inline QString VConfigManager::getTemplateCssUrl() const { return templateCssUrl; @@ -266,16 +253,6 @@ inline MarkdownConverterType VConfigManager::getMdConverterType() const return mdConverterType; } -inline QString VConfigManager::getPreTemplatePath() const -{ - return preTemplatePath; -} - -inline QString VConfigManager::getPostTemplatePath() const -{ - return postTemplatePath; -} - inline void VConfigManager::setMarkdownConverterType(MarkdownConverterType type) { if (mdConverterType == type) { diff --git a/src/vdocument.cpp b/src/vdocument.cpp index da8a06d2..2291c9f1 100644 --- a/src/vdocument.cpp +++ b/src/vdocument.cpp @@ -1,29 +1,15 @@ #include "vdocument.h" +#include "vfile.h" #include -VDocument::VDocument(QObject *parent) : QObject(parent) +VDocument::VDocument(const VFile *v_file, QObject *p_parent) + : QObject(p_parent), m_file(v_file) { - } -VDocument::VDocument(const QString &text, QObject *parent) - : QObject(parent) +void VDocument::updateText() { - m_text = text; -} - -void VDocument::setText(const QString &text) -{ - if (text == m_text) { - return; - } - m_text = text; - emit textChanged(m_text); -} - -QString VDocument::getText() -{ - return m_text; + emit textChanged(m_file->getContent()); } void VDocument::setToc(const QString &toc) diff --git a/src/vdocument.h b/src/vdocument.h index f526acc1..0a8901c8 100644 --- a/src/vdocument.h +++ b/src/vdocument.h @@ -4,6 +4,8 @@ #include #include +class VFile; + class VDocument : public QObject { Q_OBJECT @@ -12,10 +14,7 @@ class VDocument : public QObject Q_PROPERTY(QString html MEMBER m_html NOTIFY htmlChanged) public: - explicit VDocument(QObject *parent = 0); - VDocument(const QString &text, QObject *parent = 0); - void setText(const QString &text); - QString getText(); + VDocument(const VFile *p_file, QObject *p_parent = 0); QString getToc(); void scrollToAnchor(const QString &anchor); void setHtml(const QString &html); @@ -26,6 +25,7 @@ public slots: void setHeader(const QString &anchor); void setLog(const QString &p_log); void keyPressEvent(int p_key); + void updateText(); signals: void textChanged(const QString &text); @@ -37,10 +37,16 @@ signals: void keyPressed(int p_key); private: - QString m_text; QString m_toc; QString m_header; + + // m_text does NOT contain actual content. + QString m_text; + + // When using Hoedown, m_html will contain the html content. QString m_html; + + const VFile *m_file; }; #endif // VDOCUMENT_H diff --git a/src/vedittab.cpp b/src/vedittab.cpp index f2f8e426..dec4bc8a 100644 --- a/src/vedittab.cpp +++ b/src/vedittab.cpp @@ -21,7 +21,7 @@ extern VConfigManager vconfig; VEditTab::VEditTab(VFile *p_file, OpenFileMode p_mode, QWidget *p_parent) - : QStackedWidget(p_parent), m_file(p_file), isEditMode(false), + : QStackedWidget(p_parent), m_file(p_file), isEditMode(false), document(p_file, this), mdConverterType(vconfig.getMdConverterType()), m_fileModified(false), m_editArea(NULL) { @@ -109,7 +109,7 @@ void VEditTab::showFileReadMode() break; case DocType::Markdown: if (mdConverterType == MarkdownConverterType::Marked) { - document.setText(m_file->getContent()); + document.updateText(); updateTocFromHtml(document.getToc()); } else { previewByConverter(); @@ -140,7 +140,7 @@ void VEditTab::scrollPreviewToHeader(int p_outlineIndex) void VEditTab::previewByConverter() { VMarkdownConverter mdConverter; - QString &content = m_file->getContent(); + const QString &content = m_file->getContent(); QString html = mdConverter.generateHtml(content, vconfig.getMarkdownExtensions()); QRegularExpression tocExp("

    \\[TOC\\]<\\/p>", QRegularExpression::CaseInsensitiveOption); QString toc = mdConverter.generateToc(content, vconfig.getMarkdownExtensions()); @@ -259,6 +259,9 @@ bool VEditTab::saveFile() void VEditTab::setupMarkdownPreview() { + const QString jsHolder("JS_PLACE_HOLDER"); + const QString extraHolder(""); + webPreviewer = new QWebEngineView(this); VPreviewPage *page = new VPreviewPage(this); webPreviewer->setPage(page); @@ -273,14 +276,26 @@ void VEditTab::setupMarkdownPreview() this, &VEditTab::handleWebKeyPressed); page->setWebChannel(channel); - if (mdConverterType == MarkdownConverterType::Marked) { - webPreviewer->setHtml(VNote::templateHtml, - QUrl::fromLocalFile(m_file->retriveBasePath() + QDir::separator())); - } else { - webPreviewer->setHtml(VNote::preTemplateHtml + VNote::postTemplateHtml, - QUrl::fromLocalFile(m_file->retriveBasePath() + QDir::separator())); - } + QString jsFile, extraFile; + switch (mdConverterType) { + case MarkdownConverterType::Marked: + jsFile = "qrc" + VNote::c_markedJsFile; + extraFile = ""; + break; + case MarkdownConverterType::Hoedown: + jsFile = "qrc" + VNote::c_hoedownJsFile; + break; + + default: + Q_ASSERT(false); + } + QString htmlTemplate = VNote::s_markdownTemplate; + htmlTemplate.replace(jsHolder, jsFile); + if (!extraFile.isEmpty()) { + htmlTemplate.replace(extraHolder, extraFile); + } + webPreviewer->setHtml(htmlTemplate, QUrl::fromLocalFile(m_file->retriveBasePath() + QDir::separator())); addWidget(webPreviewer); } diff --git a/src/vfile.h b/src/vfile.h index 252f6226..3f57febd 100644 --- a/src/vfile.h +++ b/src/vfile.h @@ -25,7 +25,7 @@ public: inline VDirectory *getDirectory(); inline const VDirectory *getDirectory() const; inline DocType getDocType() const; - inline QString &getContent(); + inline const QString &getContent() const; inline void setContent(const QString &p_content); inline VNotebook *getNotebook(); inline QString getNotebookName() const; @@ -78,7 +78,7 @@ inline DocType VFile::getDocType() const return m_docType; } -inline QString &VFile::getContent() +inline const QString &VFile::getContent() const { return m_content; } diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp index 9bb5a611..5a0c76e7 100644 --- a/src/vmdedit.cpp +++ b/src/vmdedit.cpp @@ -85,7 +85,7 @@ void VMdEdit::saveFile() void VMdEdit::reloadFile() { - QString &content = m_file->getContent(); + const QString &content = m_file->getContent(); Q_ASSERT(content.indexOf(QChar::ObjectReplacementCharacter) == -1); setPlainText(content); setModified(false); diff --git a/src/vnote.cpp b/src/vnote.cpp index 5b20f1a5..f156e65b 100644 --- a/src/vnote.cpp +++ b/src/vnote.cpp @@ -10,9 +10,10 @@ extern VConfigManager vconfig; -QString VNote::templateHtml; -QString VNote::preTemplateHtml; -QString VNote::postTemplateHtml; +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"; VNote::VNote(QObject *parent) : QObject(parent), m_mainWindow(dynamic_cast(parent)) @@ -83,14 +84,15 @@ QString VNote::getColorFromPalette(const QString &p_name) const void VNote::initTemplate() { - if (templateHtml.isEmpty() || preTemplateHtml.isEmpty() - || postTemplateHtml.isEmpty()) { + if (s_markdownTemplate.isEmpty()) { updateTemplate(); } } void VNote::updateTemplate() { + const QString c_markdownTemplatePath(":/resources/markdown_template.html"); + // Get background color QString rgb; const QString &curRenderBg = vconfig.getCurRenderBackgroundColor(); @@ -109,19 +111,12 @@ void VNote::updateTemplate() } QString styleHolder(""); QString cssHolder("CSS_PLACE_HOLDER"); - templateHtml = VUtils::readFileFromDisk(vconfig.getTemplatePath()); - templateHtml.replace(cssHolder, vconfig.getTemplateCssUrl()); - if (!cssStyle.isEmpty()) { - templateHtml.replace(styleHolder, cssStyle); - } - preTemplateHtml = VUtils::readFileFromDisk(vconfig.getPreTemplatePath()); - preTemplateHtml.replace(cssHolder, vconfig.getTemplateCssUrl()); + s_markdownTemplate = VUtils::readFileFromDisk(c_markdownTemplatePath); + s_markdownTemplate.replace(cssHolder, vconfig.getTemplateCssUrl()); if (!cssStyle.isEmpty()) { - preTemplateHtml.replace(styleHolder, cssStyle); + s_markdownTemplate.replace(styleHolder, cssStyle); } - - postTemplateHtml = VUtils::readFileFromDisk(vconfig.getPostTemplatePath()); } const QVector &VNote::getNotebooks() const diff --git a/src/vnote.h b/src/vnote.h index fd710b61..cbb692ef 100644 --- a/src/vnote.h +++ b/src/vnote.h @@ -25,12 +25,10 @@ public: void initTemplate(); - // Used by Marked - static QString templateHtml; - - // Used by other markdown converter - static QString preTemplateHtml; - static QString postTemplateHtml; + static QString s_markdownTemplate; + static const QString c_hoedownJsFile; + static const QString c_markedJsFile; + static const QString c_markedExtraFile; inline const QVector > &getPalette() const; void initPalette(QPalette palette); diff --git a/src/vnote.qrc b/src/vnote.qrc index 98e507ae..0180ee04 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -2,11 +2,8 @@ resources/welcome.html resources/qwebchannel.js - resources/template.html resources/markdown.css utils/marked/marked.min.js - resources/post_template.html - resources/pre_template.html utils/highlightjs/highlight.pack.js utils/highlightjs/styles/androidstudio.css utils/highlightjs/styles/atom-one-dark.css @@ -85,5 +82,9 @@ resources/icons/find_replace.svg resources/icons/search_wrap.svg resources/icons/settings.svg + resources/markdown_template.html + resources/markdown_template.js + resources/hoedown.js + resources/marked.js