From 06fc4d58313ad0ea5cce5ecb8aec9586b38397f9 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Sat, 30 Sep 2017 19:01:21 +0800 Subject: [PATCH] support custom code block style of highlightjs Add two config: - template_code_block_css - template_code_block_css_url --- src/resources/hoedown.js | 1 + src/resources/markdown-it.js | 1 + src/resources/markdown_template.html | 2 +- src/resources/markdown_template.js | 11 +++ src/resources/marked.js | 1 + src/resources/showdown.js | 1 + src/resources/styles/default.css | 13 ++- src/resources/vnote.ini | 10 +++ src/vconfigmanager.cpp | 114 ++++++++++++++++++++++----- src/vconfigmanager.h | 72 +++++++++++++++++ src/vmainwindow.cpp | 85 +++++++++++++++++++- src/vmainwindow.h | 21 +++++ src/vnote.cpp | 5 ++ 13 files changed, 308 insertions(+), 29 deletions(-) diff --git a/src/resources/hoedown.js b/src/resources/hoedown.js index e111ae63..408e0569 100644 --- a/src/resources/hoedown.js +++ b/src/resources/hoedown.js @@ -41,6 +41,7 @@ var updateHtml = function(html) { } } + addClassToCodeBlock(); renderCodeBlockLineNumber(); // If you add new logics after handling MathJax, please pay attention to diff --git a/src/resources/markdown-it.js b/src/resources/markdown-it.js index aac79d6a..51962184 100644 --- a/src/resources/markdown-it.js +++ b/src/resources/markdown-it.js @@ -97,6 +97,7 @@ var updateText = function(text) { insertImageCaption(); renderMermaid('lang-mermaid'); renderFlowchart('lang-flowchart'); + addClassToCodeBlock(); renderCodeBlockLineNumber(); // If you add new logics after handling MathJax, please pay attention to diff --git a/src/resources/markdown_template.html b/src/resources/markdown_template.html index 5ed8d112..e06c9626 100644 --- a/src/resources/markdown_template.html +++ b/src/resources/markdown_template.html @@ -22,7 +22,7 @@ - + diff --git a/src/resources/markdown_template.js b/src/resources/markdown_template.js index 59f7662c..7d6ae805 100644 --- a/src/resources/markdown_template.js +++ b/src/resources/markdown_template.js @@ -833,3 +833,14 @@ var renderCodeBlockLineNumber = function() { } } }; + +var addClassToCodeBlock = function() { + var hljsClass = 'hljs'; + var codes = document.getElementsByTagName('code'); + for (var i = 0; i < codes.length; ++i) { + var code = codes[i]; + if (code.parentElement.tagName.toLowerCase() == 'pre') { + code.classList.add(hljsClass); + } + } +}; diff --git a/src/resources/marked.js b/src/resources/marked.js index 50e5f7b7..32a1162e 100644 --- a/src/resources/marked.js +++ b/src/resources/marked.js @@ -49,6 +49,7 @@ var updateText = function(text) { insertImageCaption(); renderMermaid('lang-mermaid'); renderFlowchart('lang-flowchart'); + addClassToCodeBlock(); renderCodeBlockLineNumber(); // If you add new logics after handling MathJax, please pay attention to diff --git a/src/resources/showdown.js b/src/resources/showdown.js index 348dc3c1..f51d0675 100644 --- a/src/resources/showdown.js +++ b/src/resources/showdown.js @@ -76,6 +76,7 @@ var updateText = function(text) { highlightCodeBlocks(document, VEnableMermaid, VEnableFlowchart); renderMermaid('language-mermaid'); renderFlowchart('language-flowchart'); + addClassToCodeBlock(); renderCodeBlockLineNumber(); // If you add new logics after handling MathJax, please pay attention to diff --git a/src/resources/styles/default.css b/src/resources/styles/default.css index 464a36f8..19ef55d6 100644 --- a/src/resources/styles/default.css +++ b/src/resources/styles/default.css @@ -77,7 +77,6 @@ p, ul, ol { } pre { - padding: 0px 24px; background-color: #f8f8f8; border-radius: 3px; border: 1px solid #cccccc; @@ -92,12 +91,12 @@ code { } pre code { - margin: 0; - padding: 0; - border: none; - background: transparent; - overflow: auto; - color: #363636; + margin: 0; + padding: 0; + border: none; + background: transparent; + overflow: auto; + color: #363636; } aside { diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index 9907d03d..564adf03 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -1,6 +1,16 @@ [global] welcome_page_path=:/resources/welcome.html + +; CSS style for Markdown template template_css=default + +; Code block CSS style for Markdown template +template_code_block_css=vnote + +; Code block CSS style file URL for Markdown template +; If not empty, VNote will ignore template_code_block_css +template_code_block_css_url= + editor_style=default current_notebook=0 tab_stop_width=4 diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index a085f820..688504c1 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -18,7 +18,9 @@ const QString VConfigManager::c_obsoleteDirConfigFile = QString(".vnote.json"); const QString VConfigManager::c_dirConfigFile = QString("_vnote.json"); const QString VConfigManager::defaultConfigFilePath = QString(":/resources/vnote.ini"); const QString VConfigManager::c_styleConfigFolder = QString("styles"); +const QString VConfigManager::c_codeBlockStyleConfigFolder = QString("codeblock_styles"); const QString VConfigManager::c_defaultCssFile = QString(":/resources/styles/default.css"); +const QString VConfigManager::c_defaultCodeBlockCssFile = QString(":/utils/highlightjs/styles/vnote.css"); const QString VConfigManager::c_defaultMdhlFile = QString(":/resources/styles/default.mdhl"); const QString VConfigManager::c_solarizedDarkMdhlFile = QString(":/resources/styles/solarized-dark.mdhl"); const QString VConfigManager::c_solarizedLightMdhlFile = QString(":/resources/styles/solarized-light.mdhl"); @@ -55,6 +57,7 @@ void VConfigManager::initialize() // Override the default css styles on start up. outputDefaultCssStyle(); + outputDefaultCodeBlockCssStyle(); outputDefaultEditorStyle(); m_defaultEditPalette = QTextEdit().palette(); @@ -63,6 +66,8 @@ void VConfigManager::initialize() welcomePagePath = getConfigFromSettings("global", "welcome_page_path").toString(); m_templateCss = getConfigFromSettings("global", "template_css").toString(); + m_templateCodeBlockCss = getConfigFromSettings("global", "template_code_block_css").toString(); + m_templateCodeBlockCssUrl = getConfigFromSettings("global", "template_code_block_css_url").toString(); curNotebookIndex = getConfigFromSettings("global", "current_notebook").toInt(); markdownExtensions = hoedown_extensions(HOEDOWN_EXT_TABLES | HOEDOWN_EXT_FENCED_CODE | @@ -606,6 +611,11 @@ QString VConfigManager::getStyleConfigFolder() const return getConfigFolder() + QDir::separator() + c_styleConfigFolder; } +QString VConfigManager::getCodeBlockStyleConfigFolder() const +{ + return getStyleConfigFolder() + QDir::separator() + c_codeBlockStyleConfigFolder; +} + QVector VConfigManager::getCssStyles() const { QVector res; @@ -627,6 +637,27 @@ QVector VConfigManager::getCssStyles() const return res; } +QVector VConfigManager::getCodeBlockCssStyles() const +{ + QVector res; + QDir dir(getCodeBlockStyleConfigFolder()); + if (!dir.exists()) { + // Output pre-defined CSS styles to this folder. + outputDefaultCodeBlockCssStyle(); + } + + // Get all the .css files in the folder. + dir.setFilter(QDir::Files | QDir::NoSymLinks); + dir.setNameFilters(QStringList("*.css")); + QStringList files = dir.entryList(); + res.reserve(files.size()); + for (auto const &item : files) { + res.push_back(item.left(item.size() - 4)); + } + + return res; +} + QVector VConfigManager::getEditorStyles() const { QVector res; @@ -651,15 +682,46 @@ QVector VConfigManager::getEditorStyles() const bool VConfigManager::outputDefaultCssStyle() const { // Make sure the styles folder exists. - QDir dir(getConfigFolder()); - if (!dir.exists(c_styleConfigFolder)) { - if (!dir.mkdir(c_styleConfigFolder)) { + QString folderPath = getStyleConfigFolder(); + QDir dir(folderPath); + if (!dir.exists()) { + if (!dir.mkpath(folderPath)) { return false; } } QString srcPath = c_defaultCssFile; - QString destPath = getStyleConfigFolder() + QDir::separator() + QFileInfo(srcPath).fileName(); + QString destPath = folderPath + QDir::separator() + QFileInfo(srcPath).fileName(); + + if (QFileInfo::exists(destPath)) { + QString bakPath = destPath + ".bak"; + // We only keep one bak file. + if (!QFileInfo::exists(bakPath)) { + QFile::rename(destPath, bakPath); + } else { + // Just delete the default style. + QFile file(destPath); + file.setPermissions(QFile::ReadUser | QFile::WriteUser); + file.remove(); + } + } + + return VUtils::copyFile(srcPath, destPath, false); +} + +bool VConfigManager::outputDefaultCodeBlockCssStyle() const +{ + // Make sure the styles folder exists. + QString folderPath = getCodeBlockStyleConfigFolder(); + QDir dir(folderPath); + if (!dir.exists()) { + if (!dir.mkpath(folderPath)) { + return false; + } + } + + QString srcPath = c_defaultCodeBlockCssFile; + QString destPath = folderPath + QDir::separator() + QFileInfo(srcPath).fileName(); if (QFileInfo::exists(destPath)) { QString bakPath = destPath + ".bak"; @@ -753,6 +815,36 @@ QString VConfigManager::getTemplateCssUrl() return cssPath; } +// The URL will be used in the Web page. +QString VConfigManager::getTemplateCodeBlockCssUrl() +{ + if (!m_templateCodeBlockCssUrl.isEmpty()) { + return m_templateCodeBlockCssUrl; + } + + QString cssPath = getCodeBlockStyleConfigFolder() + + QDir::separator() + + m_templateCodeBlockCss + ".css"; + QUrl cssUrl = QUrl::fromLocalFile(cssPath); + cssPath = cssUrl.toString(); + if (!QFile::exists(cssUrl.toLocalFile())) { + // Specified css not exists. + if (m_templateCss == "vnote") { + bool ret = outputDefaultCodeBlockCssStyle(); + if (!ret) { + // Use embedded file. + cssPath = "qrc" + c_defaultCodeBlockCssFile; + } + } else { + setTemplateCodeBlockCss("vnote"); + return getTemplateCodeBlockCssUrl(); + } + } + + qDebug() << "use template code block css:" << cssPath; + return cssPath; +} + QString VConfigManager::getEditorStyleUrl() { QString mdhlPath = getStyleConfigFolder() + QDir::separator() + m_editorStyle + ".mdhl"; @@ -775,20 +867,6 @@ QString VConfigManager::getEditorStyleUrl() } -const QString &VConfigManager::getTemplateCss() const -{ - return m_templateCss; -} - -void VConfigManager::setTemplateCss(const QString &p_css) -{ - if (m_templateCss == p_css) { - return; - } - m_templateCss = p_css; - setConfigToSettings("global", "template_css", m_templateCss); -} - const QString &VConfigManager::getEditorStyle() const { return m_editorStyle; diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index a767c956..5212c4a0 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -87,11 +87,16 @@ public: QString getTemplateCssUrl(); + QString getTemplateCodeBlockCssUrl(); + QString getEditorStyleUrl(); const QString &getTemplateCss() const; void setTemplateCss(const QString &p_css); + const QString &getTemplateCodeBlockCss() const; + void setTemplateCodeBlockCss(const QString &p_css); + const QString &getEditorStyle() const; void setEditorStyle(const QString &p_style); @@ -283,6 +288,9 @@ public: bool getDoubleClickCloseTab() const; + // Whether user specify template_code_block_css_url directly. + bool getUserSpecifyTemplateCodeBlockCssUrl() const; + // Return the configured key sequence of @p_operation. // Return empty if there is no corresponding config. QString getShortcutKeySequence(const QString &p_operation) const; @@ -299,6 +307,12 @@ public: // Read all available css files in c_styleConfigFolder. QVector getCssStyles() const; + // Get the folder c_codeBlockStyleConfigFolder in the config folder. + QString getCodeBlockStyleConfigFolder() const; + + // Read all available css files in c_codeBlockStyleConfigFolder. + QVector getCodeBlockCssStyles() const; + // Read all available mdhl files in c_styleConfigFolder. QVector getEditorStyles() const; @@ -326,7 +340,11 @@ private: // This is for the change of org name. void migrateIniFile(); + // Output pre-defined CSS styles to style folder. bool outputDefaultCssStyle() const; + + bool outputDefaultCodeBlockCssStyle() const; + bool outputDefaultEditorStyle() const; // See if the old c_obsoleteDirConfigFile exists. If so, rename it to @@ -363,7 +381,17 @@ private: QHash m_codeBlockStyles; QString welcomePagePath; + + // CSS style for Markdown template. QString m_templateCss; + + // Code block CSS style for Markdown template. + QString m_templateCodeBlockCss; + + // Code block CSS style file URL for Markdown template. + // If not empty, VNote will ignore m_templateCodeBlockCss. + QString m_templateCodeBlockCssUrl; + QString m_editorStyle; int curNotebookIndex; @@ -585,10 +613,19 @@ private: QSettings *userSettings; // Qsettings for @defaultConfigFileName QSettings *defaultSettings; + // The folder name of style files. static const QString c_styleConfigFolder; + + // The folder name of code block style files. + static const QString c_codeBlockStyleConfigFolder; + + // Default CSS file in resource system. static const QString c_defaultCssFile; + // Default code block CSS file in resource system. + static const QString c_defaultCodeBlockCssFile; + // MDHL files for editor styles. static const QString c_defaultMdhlFile; static const QString c_solarizedDarkMdhlFile; @@ -1498,4 +1535,39 @@ inline bool VConfigManager::getDoubleClickCloseTab() const return m_doubleClickCloseTab; } +inline const QString &VConfigManager::getTemplateCss() const +{ + return m_templateCss; +} + +inline void VConfigManager::setTemplateCss(const QString &p_css) +{ + if (m_templateCss == p_css) { + return; + } + + m_templateCss = p_css; + setConfigToSettings("global", "template_css", m_templateCss); +} + +inline const QString &VConfigManager::getTemplateCodeBlockCss() const +{ + return m_templateCodeBlockCss; +} + +inline void VConfigManager::setTemplateCodeBlockCss(const QString &p_css) +{ + if (m_templateCodeBlockCss == p_css) { + return; + } + + m_templateCodeBlockCss = p_css; + setConfigToSettings("global", "template_code_block_css", m_templateCodeBlockCss); +} + +inline bool VConfigManager::getUserSpecifyTemplateCodeBlockCssUrl() const +{ + return !m_templateCodeBlockCssUrl.isEmpty(); +} + #endif // VCONFIGMANAGER_H diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 2622a6bb..fe1a0eec 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -579,6 +579,8 @@ void VMainWindow::initMarkdownMenu() initRenderBackgroundMenu(markdownMenu); + initCodeBlockStyleMenu(markdownMenu); + QAction *constrainImageAct = new QAction(tr("Constrain The Width of Images"), this); constrainImageAct->setToolTip(tr("Constrain the width of images to the window in read mode (re-open current tabs to make it work)")); constrainImageAct->setCheckable(true); @@ -1319,6 +1321,7 @@ void VMainWindow::updateRenderStyleMenu() } // Update the menu actions with styles. + QString curStyle = g_config->getTemplateCss(); QVector styles = g_config->getCssStyles(); for (auto const &style : styles) { QAction *act = new QAction(style, m_renderStyleActs); @@ -1329,7 +1332,7 @@ void VMainWindow::updateRenderStyleMenu() // Add it to the menu. menu->addAction(act); - if (g_config->getTemplateCss() == style) { + if (curStyle == style) { act->setChecked(true); } } @@ -1347,14 +1350,73 @@ void VMainWindow::initRenderStyleMenu(QMenu *p_menu) this, &VMainWindow::setRenderStyle); QAction *addAct = newAction(QIcon(":/resources/icons/add_style.svg"), - tr("&Add Style"), m_renderStyleActs); - addAct->setToolTip(tr("Open the folder to add your custom CSS style files")); + tr("&Add Style"), + m_renderStyleActs); + addAct->setToolTip(tr("Open the folder to add your custom CSS style files " + "for Markdown rendering")); addAct->setCheckable(true); addAct->setData("AddStyle"); styleMenu->addAction(addAct); } +void VMainWindow::updateCodeBlockStyleMenu() +{ + QMenu *menu = dynamic_cast(sender()); + V_ASSERT(menu); + + QList actions = menu->actions(); + // Remove all other actions except the first one. + for (int i = 1; i < actions.size(); ++i) { + menu->removeAction(actions[i]); + m_codeBlockStyleActs->removeAction(actions[i]); + delete actions[i]; + } + + // Update the menu actions with styles. + QString curStyle = g_config->getTemplateCodeBlockCss(); + QVector styles = g_config->getCodeBlockCssStyles(); + for (auto const &style : styles) { + QAction *act = new QAction(style, m_codeBlockStyleActs); + act->setToolTip(tr("Set as the code block CSS style for Markdown rendering")); + act->setCheckable(true); + act->setData(style); + + // Add it to the menu. + menu->addAction(act); + + if (curStyle == style) { + act->setChecked(true); + } + } +} + +void VMainWindow::initCodeBlockStyleMenu(QMenu *p_menu) +{ + QMenu *styleMenu = p_menu->addMenu(tr("Code Block Style")); + styleMenu->setToolTipsVisible(true); + connect(styleMenu, &QMenu::aboutToShow, + this, &VMainWindow::updateCodeBlockStyleMenu); + + m_codeBlockStyleActs = new QActionGroup(this); + connect(m_codeBlockStyleActs, &QActionGroup::triggered, + this, &VMainWindow::setCodeBlockStyle); + + QAction *addAct = newAction(QIcon(":/resources/icons/add_style.svg"), + tr("&Add Style"), + m_codeBlockStyleActs); + addAct->setToolTip(tr("Open the folder to add your custom CSS style files " + "for Markdown code block rendering")); + addAct->setCheckable(true); + addAct->setData("AddStyle"); + + styleMenu->addAction(addAct); + + if (g_config->getUserSpecifyTemplateCodeBlockCssUrl()) { + styleMenu->setEnabled(false); + } +} + void VMainWindow::initEditorBackgroundMenu(QMenu *menu) { QMenu *backgroundColorMenu = menu->addMenu(tr("&Background Color")); @@ -1537,6 +1599,23 @@ void VMainWindow::setEditorStyle(QAction *p_action) } } +void VMainWindow::setCodeBlockStyle(QAction *p_action) +{ + if (!p_action) { + return; + } + + QString data = p_action->data().toString(); + if (data == "AddStyle") { + // Add custom style. + QUrl url = QUrl::fromLocalFile(g_config->getCodeBlockStyleConfigFolder()); + QDesktopServices::openUrl(url); + } else { + g_config->setTemplateCodeBlockCss(data); + vnote->updateTemplate(); + } +} + void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file, bool p_editMode) { diff --git a/src/vmainwindow.h b/src/vmainwindow.h index 8b755200..a0e1bc61 100644 --- a/src/vmainwindow.h +++ b/src/vmainwindow.h @@ -83,10 +83,22 @@ private slots: void setTabStopWidth(QAction *action); void setEditorBackgroundColor(QAction *action); void setRenderBackgroundColor(QAction *action); + void setRenderStyle(QAction *p_action); + void setEditorStyle(QAction *p_action); + + // Set code block render style. + void setCodeBlockStyle(QAction *p_action); + + // Update the render styles menu according to existing files. void updateRenderStyleMenu(); + void updateEditorStyleMenu(); + + // Update the code block styles menu according to existing files. + void updateCodeBlockStyleMenu(); + void changeHighlightCursorLine(bool p_checked); void changeHighlightSelectedWord(bool p_checked); void changeHighlightSearchedWord(bool p_checked); @@ -161,7 +173,11 @@ private: void initAvatar(); void initPredefinedColorPixmaps(); void initRenderBackgroundMenu(QMenu *menu); + void initRenderStyleMenu(QMenu *p_menu); + + void initCodeBlockStyleMenu(QMenu *p_menu); + void initConverterMenu(QMenu *p_menu); void initMarkdownitOptionMenu(QMenu *p_menu); void initEditorBackgroundMenu(QMenu *menu); @@ -245,9 +261,14 @@ private: QAction *m_autoIndentAct; + // Act group for render styles. QActionGroup *m_renderStyleActs; + QActionGroup *m_editorStyleActs; + // Act group for code block render styles. + QActionGroup *m_codeBlockStyleActs; + QShortcut *m_closeNoteShortcut; // Menus diff --git a/src/vnote.cpp b/src/vnote.cpp index 8a025351..1c318527 100644 --- a/src/vnote.cpp +++ b/src/vnote.cpp @@ -180,6 +180,7 @@ void VNote::updateTemplate() } } } + QString cssStyle; if (!rgb.isEmpty()) { cssStyle += "body { background-color: #" + rgb + " !important; }\n"; @@ -192,8 +193,12 @@ void VNote::updateTemplate() const QString styleHolder(""); const QString cssHolder("CSS_PLACE_HOLDER"); + const QString codeBlockCssHolder("HIGHLIGHTJS_CSS_PLACE_HOLDER"); s_markdownTemplate = VUtils::readFileFromDisk(c_markdownTemplatePath); + + // Must replace the code block holder first. + s_markdownTemplate.replace(codeBlockCssHolder, g_config->getTemplateCodeBlockCssUrl()); s_markdownTemplate.replace(cssHolder, g_config->getTemplateCssUrl()); s_markdownTemplatePDF = s_markdownTemplate;