Pārlūkot izejas kodu

fixes

* MarkdownViewWindow: add switch for code block line number
* MarkodwnViewWindow: fix Turndown <style> issue
* MarkdownViewWindow: add config for overridding MathJax script
* SortDialog: fix sorting issue of date
* FramelessMainWindow: fix StayOnTop issue
Le Tan 3 gadi atpakaļ
vecāks
revīzija
a4a5dea3d7

+ 9 - 0
src/core/global.h

@@ -139,6 +139,15 @@ namespace vnotex
         CRLF,
         CR
     };
+
+    enum Role
+    {
+        // Qt::UserRole = 0x0100
+        UserRole2 = 0x0101,
+        HighlightsRole = 0x0102,
+        // Used for comparison.
+        ComparisonRole = 0x0103
+    };
 } // ns vnotex
 
 Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::FindOptions);

+ 4 - 0
src/core/htmltemplatehelper.cpp

@@ -21,6 +21,7 @@ QString WebGlobalOptions::toJavascriptObject() const
     return QStringLiteral("window.vxOptions = {\n")
            + QString("webPlantUml: %1,\n").arg(Utils::boolToString(m_webPlantUml))
            + QString("webGraphviz: %1,\n").arg(Utils::boolToString(m_webGraphviz))
+           + QString("mathJaxScript: '%1',\n").arg(m_mathJaxScript)
            + QString("constrainImageWidthEnabled: %1,\n").arg(Utils::boolToString(m_constrainImageWidthEnabled))
            + QString("imageAlignCenterEnabled: %1,\n").arg(Utils::boolToString(m_imageAlignCenterEnabled))
            + QString("protectFromXss: %1,\n").arg(Utils::boolToString(m_protectFromXss))
@@ -28,6 +29,7 @@ QString WebGlobalOptions::toJavascriptObject() const
            + QString("autoBreakEnabled: %1,\n").arg(Utils::boolToString(m_autoBreakEnabled))
            + QString("linkifyEnabled: %1,\n").arg(Utils::boolToString(m_linkifyEnabled))
            + QString("indentFirstLineEnabled: %1,\n").arg(Utils::boolToString(m_indentFirstLineEnabled))
+           + QString("codeBlockLineNumberEnabled: %1,\n").arg(Utils::boolToString(m_codeBlockLineNumberEnabled))
            + QString("sectionNumberEnabled: %1,\n").arg(Utils::boolToString(m_sectionNumberEnabled))
            + QString("transparentBackgroundEnabled: %1,\n").arg(Utils::boolToString(m_transparentBackgroundEnabled))
            + QString("scrollable: %1,\n").arg(Utils::boolToString(m_scrollable))
@@ -212,6 +214,7 @@ QString HtmlTemplateHelper::generateMarkdownViewerTemplate(const MarkdownEditorC
         WebGlobalOptions opts;
         opts.m_webPlantUml = p_config.getWebPlantUml();
         opts.m_webGraphviz = p_config.getWebGraphviz();
+        opts.m_mathJaxScript = p_config.getMathJaxScript();
         opts.m_sectionNumberEnabled = p_config.getSectionNumberMode() == MarkdownEditorConfig::SectionNumberMode::Read;
         opts.m_sectionNumberBaseLevel = p_config.getSectionNumberBaseLevel();
         opts.m_constrainImageWidthEnabled = p_config.getConstrainImageWidthEnabled();
@@ -221,6 +224,7 @@ QString HtmlTemplateHelper::generateMarkdownViewerTemplate(const MarkdownEditorC
         opts.m_autoBreakEnabled = p_config.getAutoBreakEnabled();
         opts.m_linkifyEnabled = p_config.getLinkifyEnabled();
         opts.m_indentFirstLineEnabled = p_config.getIndentFirstLineEnabled();
+        opts.m_codeBlockLineNumberEnabled = p_config.getCodeBlockLineNumberEnabled();
         opts.m_transparentBackgroundEnabled = p_paras.m_transparentBackgroundEnabled;
         opts.m_scrollable = p_paras.m_scrollable;
         opts.m_bodyWidth = p_paras.m_bodyWidth;

+ 4 - 0
src/core/htmltemplatehelper.h

@@ -15,6 +15,8 @@ namespace vnotex
 
         bool m_webGraphviz = true;
 
+        QString m_mathJaxScript;
+
         bool m_sectionNumberEnabled = true;
 
         int m_sectionNumberBaseLevel = 2;
@@ -33,6 +35,8 @@ namespace vnotex
 
         bool m_indentFirstLineEnabled = false;
 
+        bool m_codeBlockLineNumberEnabled = true;
+
         // Force to use transparent background.
         bool m_transparentBackgroundEnabled = false;
 

+ 25 - 0
src/core/markdowneditorconfig.cpp

@@ -39,6 +39,8 @@ void MarkdownEditorConfig::init(const QJsonObject &p_app, const QJsonObject &p_u
 
     m_graphvizExe = READSTR(QStringLiteral("graphviz_exe"));
 
+    m_mathJaxScript = READSTR(QStringLiteral("mathjax_script"));
+
     m_prependDotInRelativeLink = READBOOL(QStringLiteral("prepend_dot_in_relative_link"));
     m_confirmBeforeClearObsoleteImages = READBOOL(QStringLiteral("confirm_before_clear_obsolete_images"));
     m_insertFileNameAsTitle = READBOOL(QStringLiteral("insert_file_name_as_title"));
@@ -58,6 +60,7 @@ void MarkdownEditorConfig::init(const QJsonObject &p_app, const QJsonObject &p_u
     m_autoBreakEnabled = READBOOL(QStringLiteral("auto_break"));
     m_linkifyEnabled = READBOOL(QStringLiteral("linkify"));
     m_indentFirstLineEnabled = READBOOL(QStringLiteral("indent_first_line"));
+    m_codeBlockLineNumberEnabled = READBOOL(QStringLiteral("code_block_line_number"));
 
     m_smartTableEnabled = READBOOL(QStringLiteral("smart_table"));
     m_smartTableInterval = READINT(QStringLiteral("smart_table_interval"));
@@ -87,6 +90,7 @@ QJsonObject MarkdownEditorConfig::toJson() const
     obj[QStringLiteral("plantuml_command")] = m_plantUmlCommand;
     obj[QStringLiteral("web_graphviz")] = m_webGraphviz;
     obj[QStringLiteral("graphviz_exe")] = m_graphvizExe;
+    obj[QStringLiteral("mathjax_script")] = m_mathJaxScript;
     obj[QStringLiteral("prepend_dot_in_relative_link")] = m_prependDotInRelativeLink;
     obj[QStringLiteral("confirm_before_clear_obsolete_images")] = m_confirmBeforeClearObsoleteImages;
     obj[QStringLiteral("insert_file_name_as_title")] = m_insertFileNameAsTitle;
@@ -105,6 +109,7 @@ QJsonObject MarkdownEditorConfig::toJson() const
     obj[QStringLiteral("auto_break")] = m_autoBreakEnabled;
     obj[QStringLiteral("linkify")] = m_linkifyEnabled;
     obj[QStringLiteral("indent_first_line")] = m_indentFirstLineEnabled;
+    obj[QStringLiteral("code_block_line_number")] = m_codeBlockLineNumberEnabled;
     obj[QStringLiteral("smart_table")] = m_smartTableEnabled;
     obj[QStringLiteral("smart_table_interval")] = m_smartTableInterval;
     obj[QStringLiteral("spell_check")] = m_spellCheckEnabled;
@@ -249,6 +254,16 @@ void MarkdownEditorConfig::setGraphvizExe(const QString &p_exe)
     updateConfig(m_graphvizExe, p_exe, this);
 }
 
+const QString &MarkdownEditorConfig::getMathJaxScript() const
+{
+    return m_mathJaxScript;
+}
+
+void MarkdownEditorConfig::setMathJaxScript(const QString &p_script)
+{
+    updateConfig(m_mathJaxScript, p_script, this);
+}
+
 bool MarkdownEditorConfig::getPrependDotInRelativeLink() const
 {
     return m_prependDotInRelativeLink;
@@ -371,6 +386,16 @@ void MarkdownEditorConfig::setIndentFirstLineEnabled(bool p_enabled)
     updateConfig(m_indentFirstLineEnabled, p_enabled, this);
 }
 
+bool MarkdownEditorConfig::getCodeBlockLineNumberEnabled() const
+{
+    return m_codeBlockLineNumberEnabled;
+}
+
+void MarkdownEditorConfig::setCodeBlockLineNumberEnabled(bool p_enabled)
+{
+    updateConfig(m_codeBlockLineNumberEnabled, p_enabled, this);
+}
+
 QString MarkdownEditorConfig::sectionNumberModeToString(SectionNumberMode p_mode) const
 {
     switch (p_mode) {

+ 12 - 0
src/core/markdowneditorconfig.h

@@ -76,6 +76,9 @@ namespace vnotex
         const QString &getGraphvizExe() const;
         void setGraphvizExe(const QString &p_exe);
 
+        const QString &getMathJaxScript() const;
+        void setMathJaxScript(const QString &p_script);
+
         bool getPrependDotInRelativeLink() const;
 
         bool getConfirmBeforeClearObsoleteImages() const;
@@ -122,6 +125,9 @@ namespace vnotex
         bool getIndentFirstLineEnabled() const;
         void setIndentFirstLineEnabled(bool p_enabled);
 
+        bool getCodeBlockLineNumberEnabled() const;
+        void setCodeBlockLineNumberEnabled(bool p_enabled);
+
         bool getSmartTableEnabled() const;
         void setSmartTableEnabled(bool p_enabled);
 
@@ -181,6 +187,9 @@ namespace vnotex
         // Graphviz executable file.
         QString m_graphvizExe;
 
+        // MathJax script to override that in mathjax.js file.
+        QString m_mathJaxScript;
+
         // Whether prepend a dot in front of the relative link, like images.
         bool m_prependDotInRelativeLink = false;
 
@@ -227,6 +236,9 @@ namespace vnotex
         // Whether indent the first line of a paragraph.
         bool m_indentFirstLineEnabled = false;
 
+        // Whether enable code block line number in read mode.
+        bool m_codeBlockLineNumberEnabled = true;
+
         bool m_smartTableEnabled = true;
 
         // Interval time to do smart table format.

+ 4 - 0
src/data/core/vnotex.json

@@ -318,6 +318,8 @@
             "web_graphviz" : true,
             "//commnet" : "Local Graphviz executable file to render Graphviz",
             "graphviz_exe" : "",
+            "//commnet" : "MathJax script to use",
+            "mathjax_script" : "",
             "//comment" : "Whether prepend a dot at front in relative link like images",
             "prepend_dot_in_relative_link" : false,
             "//comment" : "Whether ask for user confirmation before clearing obsolete images",
@@ -350,6 +352,8 @@
             "linkify" : true,
             "//comment" : "Whether add indentation to the first line of paragraph",
             "indent_first_line" : false,
+            "//comment" : "Whether add line number to fenced code block in read mode",
+            "code_block_line_number" : true,
             "//comment" : "Whether enable smart table (formatting)",
             "smart_table" : true,
             "//comment" : "Time interval (milliseconds) to do smart table formatting",

+ 17 - 16
src/data/extra/docs/en/markdown_guide.md

@@ -21,8 +21,8 @@ Here is an overview of Markdown syntax supported by VNote.
 
 **Notes**:
 
-* At least one space is needed after the `#`;
-* A header should occupy one entire line;
+* At least one space is needed after the `#`
+* A header should occupy one entire line
 
 ### Emphasis
 ```md
@@ -35,8 +35,8 @@ __This text will be bold__
 
 **Notes**:
 
-* `*` is recommended in VNote;
-* If the render failed, try to add an additional space before the first `*` and after the last `*`. The space is necessary if the surrounded text begins or ends with full width punctuation;
+* `*` is recommended in VNote
+* If the render failed, try to add an additional space before the first `*` and after the last `*`. The space is necessary if the surrounded text begins or ends with full width punctuation
 
 ### Lists
 #### Unordered
@@ -107,8 +107,8 @@ Here is another sentence within the quote.
 
 **Notes**:
 
-* Space is needed after the marker `>`;
-* You could just add only one `>` at the first line;
+* Space is needed after the marker `>`
+* You could just add only one `>` at the first line
 
 ### Fenced Code Block
     ```lang
@@ -121,15 +121,16 @@ Here is another sentence within the quote.
 
 **Notes**:
 
-* `lang` is optional to specify the language of the code; if not specified, VNote won't highlight the code;
-* It is always a good practice to add one empty line before the whole fenced code block;
+* `lang` is optional to specify the language of the code; if not specified, VNote won't highlight the code
+    * For a detailed supported languages list, please visit [Prism](https://prismjs.com/#supported-languages)
+* It is always a good practice to add one empty line before the whole fenced code block
 
 ### Diagrams
 VNote supports the following engines to draw diagrams. You should specify particular language of the fenced code block and write the definition of your diagram within it.
 
-* [Flowchart.js](http://flowchart.js.org/) for *flowchart* with language `flow` or `flowchart`;
-* [Mermaid](https://mermaidjs.github.io/) with language `mermaid`;
-* [WaveDrom](https://wavedrom.com/) for *digital timing diagram* with language `wavedrom`;
+* [Flowchart.js](http://flowchart.js.org/) for *flowchart* with language `flow` or `flowchart`
+* [Mermaid](https://mermaidjs.github.io/) with language `mermaid`
+* [WaveDrom](https://wavedrom.com/) for *digital timing diagram* with language `wavedrom`
 
 For example,
 
@@ -162,11 +163,11 @@ VNote supports [Graphviz](http://www.graphviz.org/) to draw diagrams. You should
 ### Math Formulas
 VNote supports math formulas via [MathJax](https://www.mathjax.org/). The default math delimiters are `$$...$$` for **displayed mathematics**, and `$...$` for **inline mathematics**.
 
-* Inline mathematics should not cross multiple lines;
-* Forms like `3$abc$`, `$abc$4`, `$ abc$`, and `$abc $` will not be treated as mathematics;
-* Use `\` to escape `$`;
-* There should be only space chars before opening `$$` and after closing `$$`;
-* Use `\\` to new a line within a displayed mathematics;
+* Inline mathematics should not cross multiple lines
+* Forms like `3$abc$`, `$abc$4`, `$ abc$`, and `$abc $` will not be treated as mathematics
+* Use `\` to escape `$`
+* There should be only space chars before opening `$$` and after closing `$$`
+* Use `\\` to new a line within a displayed mathematics
 
 VNote also supports displayed mathematics via fenced code block with language `mathjax` specified.
 

+ 10 - 10
src/data/extra/docs/en/shortcuts.md

@@ -1,8 +1,8 @@
 # Shortcuts
-1. All the keys without special notice are **case insensitive**;
-2. On macOS, `Ctrl` corresponds to `Command` except in Vi mode;
-3. The key sequence `Ctrl+G, I` means that first press both `Ctrl` and `G` simultaneously, release them, then press `I` and release;
-4. For a **complete latest shortcuts list** or modifying default shortcuts, please view the `vnotex.json` configuration file.
+1. All the keys without special notice are **case insensitive**
+2. On macOS, `Ctrl` corresponds to `Command` except in Vi mode
+3. The key sequence `Ctrl+G, I` means that first press both `Ctrl` and `G` simultaneously, release them, then press `I` and release
+4. For a **complete latest shortcuts list** or modifying default shortcuts, please view the `vnotex.json` configuration file
 
 ## General
 - `Ctrl+G, E`  
@@ -77,12 +77,12 @@ Zoom in/out the page through the mouse scroll.
 - `Ctrl+0`  
 Recover the page zoom factor to 100%.
 - Jump between titles
-    - `<N>[[`: jump to previous `N` title;
-    - `<N>]]`: jump to next `N` title;
-    - `<N>[]`: jump to previous `N` title at the same level;
-    - `<N>][`: jump to next `N` title at the same level;
-    - `<N>[{`: jump to previous `N` title at a higher level;
-    - `<N>]}`: jump to next `N` title at a higher level;
+    - `<N>[[`: jump to previous `N` title
+    - `<N>]]`: jump to next `N` title
+    - `<N>[]`: jump to previous `N` title at the same level
+    - `<N>][`: jump to next `N` title at the same level
+    - `<N>[{`: jump to previous `N` title at a higher level
+    - `<N>]}`: jump to next `N` title at a higher level
 
 ### Edit Mode
 Shares the same shortcuts with Text Editor.

+ 17 - 16
src/data/extra/docs/zh_CN/markdown_guide.md

@@ -21,8 +21,8 @@ Markdown是一种通过少量简单的标记字符来格式化文本的方法。
 
 **注意**:
 
-* `#`之后需要至少一个空格
-* 一个标题应该占一整行
+* `#`之后需要至少一个空格
+* 一个标题应该占一整行
 
 ### 强调
 ```md
@@ -35,8 +35,8 @@ __This text will be bold__
 
 **注意**:
 
-* VNote推荐使用`*`
-* 如果渲染错误,请尝试在第一个`*`之前以及最后一个`*`之后添加一个空格。如果被标记的文本是以全角符号开始或结尾,一般都需要前后添加一个空格
+* VNote推荐使用`*`
+* 如果渲染错误,请尝试在第一个`*`之前以及最后一个`*`之后添加一个空格。如果被标记的文本是以全角符号开始或结尾,一般都需要前后添加一个空格
 
 ### 列表
 #### 无序列表
@@ -107,8 +107,8 @@ Here is another sentence within the quote.
 
 **注意**:
 
-* `>`标记后面需要至少一个空格
-* 多行连续的引用可以只在第一行添加标记
+* `>`标记后面需要至少一个空格
+* 多行连续的引用可以只在第一行添加标记
 
 ### 代码块
     ```lang
@@ -121,15 +121,16 @@ Here is another sentence within the quote.
 
 **注意**:
 
-* `lang`用于指定代码块的代码语言,可选;如果不指定,VNote不会尝试高亮代码;
-* 总是在一个代码块前面添加一个空行是一个不错的实践;
+* `lang`用于指定代码块的代码语言,可选;如果不指定,VNote不会尝试高亮代码
+    * 请访问[Prism](https://prismjs.com/#supported-languages)获取一个完整的支持语言列表
+* 总是在一个代码块前面添加一个空行是一个不错的实践
 
 ### 图表
 VNote支持使用以下引擎来绘制图表。您需要使用代码块,并标明特定语言,然后在代码块里面定义图表。
 
-* [Flowchart.js](http://flowchart.js.org/),语言为`flow`或`flowchart`
-* [Mermaid](https://mermaidjs.github.io/),语言为`mermaid`
-* [WaveDrom](https://wavedrom.com/),数字时序图,语言为`wavedrom`
+* [Flowchart.js](http://flowchart.js.org/),语言为`flow`或`flowchart`
+* [Mermaid](https://mermaidjs.github.io/),语言为`mermaid`
+* [WaveDrom](https://wavedrom.com/),数字时序图,语言为`wavedrom`
 
 例如,
 
@@ -162,11 +163,11 @@ VNote支持[Graphviz](http://www.graphviz.org/)来绘制图表。您需要使用
 ### 数学公式
 VNote通过[MathJax](https://www.mathjax.org/)来支持数学公式。默认的**公式块**的分隔符是`$$...$$`,**行内公式**的分隔符是`$...$`。
 
-* 行内公式不能跨多行
-* 形如`3$abc$`/`$abc$4`/`$ abc$`和`$abc $`的不会被解析为公式
-* 使用`\`转义`$`
-* 开始的`$$`之前以及结束的`$$`之后都只允许出现空格字符
-* 在公式块中,使用`\\`来换行
+* 行内公式不能跨多行
+* 形如`3$abc$`/`$abc$4`/`$ abc$`和`$abc $`的不会被解析为公式
+* 使用`\`转义`$`
+* 开始的`$$`之前以及结束的`$$`之后都只允许出现空格字符
+* 在公式块中,使用`\\`来换行
 
 VNote也可以使用标明语言`mathjax`的代码块来实现公式块。
 

+ 10 - 10
src/data/extra/docs/zh_CN/shortcuts.md

@@ -1,8 +1,8 @@
 # 快捷键
-1. 以下按键除特别说明外,都不区分大小写
-2. 在macOS下,`Ctrl`对应于`Command`,在Vi模式下除外
-3. 按键序列`Ctrl+G, I`表示先同时按下`Ctrl`和`G`,释放,然后按下`I`并释放
-4. 可以通过查看配置文件`vnotex.json`来获取一个**完整的最新的快捷键列表**或者修改默认快捷键
+1. 以下按键除特别说明外,都不区分大小写
+2. 在macOS下,`Ctrl`对应于`Command`,在Vi模式下除外
+3. 按键序列`Ctrl+G, I`表示先同时按下`Ctrl`和`G`,释放,然后按下`I`并释放
+4. 可以通过查看配置文件`vnotex.json`来获取一个**完整的最新的快捷键列表**或者修改默认快捷键
 
 ## 通用
 - `Ctrl+G, E`  
@@ -77,12 +77,12 @@ VNote的很多部件均支持`Ctrl+J`和`Ctrl+K`导航。
 - `Ctrl+0`  
 恢复页面大小为100%。
 - 标题跳转
-    - `<N>[[`:跳转到上`N`个标题
-    - `<N>]]`:跳转到下`N`个标题
-    - `<N>[]`:跳转到上`N`个同层级的标题
-    - `<N>][`:跳转到下`N`个同层级的标题
-    - `<N>[{`:跳转到上`N`个高一层级的标题
-    - `<N>]}`:跳转到下`N`个高一层级的标题
+    - `<N>[[`:跳转到上`N`个标题
+    - `<N>]]`:跳转到下`N`个标题
+    - `<N>[]`:跳转到上`N`个同层级的标题
+    - `<N>][`:跳转到下`N`个同层级的标题
+    - `<N>[{`:跳转到上`N`个高一层级的标题
+    - `<N>]}`:跳转到下`N`个高一层级的标题
 
 ### 编辑模式
 和文本编辑器共享一样的快捷键。

+ 4 - 0
src/data/extra/web/js/mathjax.js

@@ -56,6 +56,10 @@ class MathJaxRenderer extends VxWorker {
 
         this.initialized = true;
         this.readyCallback = p_callback;
+        if (!!window.vxOptions.mathJaxScript) {
+            this.mathJaxScript = window.vxOptions.mathJaxScript;
+            console.log('override MathJax script', this.mathJaxScript);
+        }
         Utils.loadScript(this.mathJaxScript, null);
         return false;
     }

+ 0 - 2
src/data/extra/web/js/prism.js

@@ -83,8 +83,6 @@ class PrismRenderer extends VxWorker {
                 }
             }
 
-            p_containerNode.classList.add('line-numbers');
-
             Prism.highlightAllUnder(p_containerNode, false /* async or not */);
 
             // Remove the toolbar.

+ 3 - 0
src/data/extra/web/js/turndown.js

@@ -20,6 +20,9 @@ class TurndownConverter {
         this.ts.use(turndownPluginGfm.gfm);
 
         // TODO: verify and copy several rules from VNote 2.0.
+        // No <head> and <style> parse.
+        this.ts.remove(['head', 'style']);
+
         this.fixMark();
 
         this.fixParagraph();

+ 2 - 0
src/data/extra/web/js/vnotex.js

@@ -69,6 +69,8 @@ class VNoteX extends EventEmitter {
                                            window.vxOptions.imageAlignCenterEnabled);
             this.setContentContainerOption('vx-indent-first-line',
                                            window.vxOptions.indentFirstLineEnabled);
+            this.setContentContainerOption('line-numbers',
+                                           window.vxOptions.codeBlockLineNumberEnabled);
             this.setBodyOption('vx-transparent-background',
                                window.vxOptions.transparentBackgroundEnabled);
             this.setContentContainerOption('vx-nonscrollable',

+ 30 - 0
src/widgets/dialogs/settings/markdowneditorpage.cpp

@@ -24,6 +24,7 @@
 #include <widgets/messageboxhelper.h>
 #include <widgets/editors/plantumlhelper.h>
 #include <widgets/editors/graphvizhelper.h>
+#include <widgets/lineedit.h>
 
 using namespace vnotex;
 
@@ -90,6 +91,8 @@ void MarkdownEditorPage::loadInternal()
 
     m_indentFirstLineCheckBox->setChecked(markdownConfig.getIndentFirstLineEnabled());
 
+    m_codeBlockLineNumberCheckBox->setChecked(markdownConfig.getCodeBlockLineNumberEnabled());
+
     m_smartTableCheckBox->setChecked(markdownConfig.getSmartTableEnabled());
 
     m_spellCheckCheckBox->setChecked(markdownConfig.isSpellCheckEnabled());
@@ -108,6 +111,8 @@ void MarkdownEditorPage::loadInternal()
 
     m_graphvizFileInput->setText(markdownConfig.getGraphvizExe());
 
+    m_mathJaxScriptLineEdit->setText(markdownConfig.getMathJaxScript());
+
     {
         const auto &fontFamily = markdownConfig.getEditorOverriddenFontFamily();
         m_editorOverriddenFontFamilyCheckBox->setChecked(!fontFamily.isEmpty());
@@ -172,6 +177,8 @@ bool MarkdownEditorPage::saveInternal()
 
     markdownConfig.setIndentFirstLineEnabled(m_indentFirstLineCheckBox->isChecked());
 
+    markdownConfig.setCodeBlockLineNumberEnabled(m_codeBlockLineNumberCheckBox->isChecked());
+
     markdownConfig.setSmartTableEnabled(m_smartTableCheckBox->isChecked());
 
     markdownConfig.setSpellCheckEnabled(m_spellCheckCheckBox->isChecked());
@@ -184,6 +191,8 @@ bool MarkdownEditorPage::saveInternal()
 
     markdownConfig.setGraphvizExe(m_graphvizFileInput->text());
 
+    markdownConfig.setMathJaxScript(m_mathJaxScriptLineEdit->text());
+
     {
         bool checked = m_editorOverriddenFontFamilyCheckBox->isChecked();
         markdownConfig.setEditorOverriddenFontFamily(checked ? m_editorOverriddenFontFamilyComboBox->currentFont().family() : QString());
@@ -278,6 +287,16 @@ QGroupBox *MarkdownEditorPage::setupReadGroup()
                 this, &MarkdownEditorPage::pageIsChanged);
     }
 
+    {
+        const QString label(tr("Code block line number"));
+        m_codeBlockLineNumberCheckBox = WidgetsFactory::createCheckBox(label, box);
+        m_codeBlockLineNumberCheckBox->setToolTip(tr("Add line number to code block"));
+        layout->addRow(m_codeBlockLineNumberCheckBox);
+        addSearchItem(label, m_codeBlockLineNumberCheckBox->toolTip(), m_codeBlockLineNumberCheckBox);
+        connect(m_codeBlockLineNumberCheckBox, &QCheckBox::stateChanged,
+                this, &MarkdownEditorPage::pageIsChanged);
+    }
+
     return box;
 }
 
@@ -552,5 +571,16 @@ QGroupBox *MarkdownEditorPage::setupGeneralGroup()
                 this, &MarkdownEditorPage::pageIsChanged);
     }
 
+    {
+        m_mathJaxScriptLineEdit = WidgetsFactory::createLineEdit(box);
+        m_mathJaxScriptLineEdit->setToolTip(tr("Override the MathJax script used to render math formulas"));
+
+        const QString label(tr("MathJax script:"));
+        layout->addRow(label, m_mathJaxScriptLineEdit);
+        addSearchItem(label, m_mathJaxScriptLineEdit->toolTip(), m_mathJaxScriptLineEdit);
+        connect(m_mathJaxScriptLineEdit, &QLineEdit::textChanged,
+                this, &MarkdownEditorPage::pageIsChanged);
+    }
+
     return box;
 }

+ 5 - 0
src/widgets/dialogs/settings/markdowneditorpage.h

@@ -9,6 +9,7 @@ class QDoubleSpinBox;
 class QSpinBox;
 class QComboBox;
 class QFontComboBox;
+class QLineEdit;
 
 namespace vnotex
 {
@@ -60,6 +61,8 @@ namespace vnotex
 
         QCheckBox *m_indentFirstLineCheckBox = nullptr;
 
+        QCheckBox *m_codeBlockLineNumberCheckBox = nullptr;
+
         QDoubleSpinBox *m_zoomFactorSpinBox = nullptr;
 
         QComboBox *m_sectionNumberComboBox = nullptr;
@@ -80,6 +83,8 @@ namespace vnotex
 
         LocationInputWithBrowseButton *m_graphvizFileInput = nullptr;
 
+        QLineEdit *m_mathJaxScriptLineEdit = nullptr;
+
         QCheckBox *m_editorOverriddenFontFamilyCheckBox = nullptr;
 
         QFontComboBox *m_editorOverriddenFontFamilyComboBox = nullptr;

+ 13 - 0
src/widgets/dialogs/sortdialog.cpp

@@ -9,6 +9,7 @@
 #include <widgets/treewidget.h>
 #include <widgets/widgetsfactory.h>
 #include <widgets/treewidgetitem.h>
+#include <core/global.h>
 
 using namespace vnotex;
 
@@ -248,3 +249,15 @@ QTreeWidgetItem *SortDialog::addItem(const QStringList &p_cols)
     auto item = new TreeWidgetItem(m_treeWidget, p_cols);
     return item;
 }
+
+QTreeWidgetItem *SortDialog::addItem(const QStringList &p_cols, const QStringList &p_comparisonCols)
+{
+    Q_ASSERT(p_cols.size() == p_comparisonCols.size());
+    auto item = new TreeWidgetItem(m_treeWidget, p_cols);
+    for (int i = 0; i < p_cols.size(); ++i) {
+        if (!p_comparisonCols[i].isNull()) {
+            item->setData(i, Role::ComparisonRole, p_comparisonCols[i]);
+        }
+    }
+    return item;
+}

+ 4 - 0
src/widgets/dialogs/sortdialog.h

@@ -26,6 +26,10 @@ namespace vnotex
         // Add one item to the tree.
         QTreeWidgetItem *addItem(const QStringList &p_cols);
 
+        // Add one item to the tree.
+        // @p_comparisonCols: for column i, if it is not null, use it for comparison.
+        QTreeWidgetItem *addItem(const QStringList &p_cols, const QStringList &p_comparisonCols);
+
     private:
         enum MoveOperation
         {

+ 0 - 1
src/widgets/editors/markdowneditor.cpp

@@ -1069,7 +1069,6 @@ void MarkdownEditor::handleHtmlToMarkdownData(quint64 p_id, TimeStamp p_timeStam
     }
 }
 
-
 static QString purifyImageTitle(QString p_title)
 {
     return p_title.remove(QRegExp("[\\r\\n\\[\\]]"));

+ 5 - 0
src/widgets/framelessmainwindow/framelessmainwindow.cpp

@@ -43,3 +43,8 @@ bool FramelessMainWindow::isMaximized() const
 {
     return (m_windowStates & Qt::WindowMaximized) && !(m_windowStates & Qt::WindowFullScreen);
 }
+
+void FramelessMainWindow::setWindowFlagsOnUpdate()
+{
+    // Do nothing by default.
+}

+ 2 - 0
src/widgets/framelessmainwindow/framelessmainwindow.h

@@ -28,6 +28,8 @@ namespace vnotex
     protected:
         bool isMaximized() const;
 
+        virtual void setWindowFlagsOnUpdate();
+
         const bool m_frameless = true;
 
         bool m_movable = true;

+ 10 - 1
src/widgets/framelessmainwindow/framelessmainwindowwin.cpp

@@ -3,7 +3,6 @@
 #ifdef Q_OS_WIN
 
 #include <QTimer>
-#include <QDebug>
 #include <QEvent>
 
 #include <windows.h>
@@ -221,4 +220,14 @@ void FramelessMainWindowWin::forceRedraw()
     }
 }
 
+void FramelessMainWindowWin::setWindowFlagsOnUpdate()
+{
+    if (m_frameless) {
+        // We need to re-set the window flags again in some cases, such as after StayOnTop.
+        HWND hwnd = reinterpret_cast<HWND>(winId());
+        DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
+        ::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
+    }
+}
+
 #endif

+ 2 - 0
src/widgets/framelessmainwindow/framelessmainwindowwin.h

@@ -21,6 +21,8 @@ namespace vnotex
 
         void moveEvent(QMoveEvent *p_event) Q_DECL_OVERRIDE;
 
+        void setWindowFlagsOnUpdate() Q_DECL_OVERRIDE;
+
     private:
         // To fix some unkonwn bugs of the interface.
         void forceRedraw();

+ 2 - 0
src/widgets/mainwindow.cpp

@@ -595,6 +595,8 @@ void MainWindow::setStayOnTop(bool p_enabled)
         setWindowFlags(flags ^ magicFlag);
     }
 
+    setWindowFlagsOnUpdate();
+
     if (shown) {
         show();
     }

+ 4 - 1
src/widgets/notebooknodeexplorer.cpp

@@ -1561,7 +1561,10 @@ void NotebookNodeExplorer::manualSort()
                 QStringList cols {child->getName(),
                                   Utils::dateTimeString(child->getCreatedTimeUtc().toLocalTime()),
                                   Utils::dateTimeString(child->getModifiedTimeUtc().toLocalTime())};
-                auto item = sortDlg.addItem(cols);
+                QStringList comparisonCols {QString(),
+                                            Utils::dateTimeStringUniform(child->getCreatedTimeUtc().toLocalTime()),
+                                            Utils::dateTimeStringUniform(child->getModifiedTimeUtc().toLocalTime())};
+                auto item = sortDlg.addItem(cols, comparisonCols);
                 item->setData(0, Qt::UserRole, i);
             }
         }

+ 0 - 8
src/widgets/styleditemdelegate.h

@@ -16,14 +16,6 @@ namespace vnotex
     class TreeWidget;
     class SimpleSegmentHighlighter;
 
-    enum
-    {
-        // Qt::UserRole = 0x0100
-        UserRole2 = 0x0101,
-        HighlightsRole = 0x0102
-    };
-
-
     class StyledItemDelegateInterface
     {
     public:

+ 11 - 2
src/widgets/treewidgetitem.cpp

@@ -3,6 +3,8 @@
 #include <QTreeWidget>
 #include <QVariant>
 
+#include <core/global.h>
+
 using namespace vnotex;
 
 TreeWidgetItem::TreeWidgetItem(QTreeWidget *p_parent, const QStringList &p_strings, int p_type)
@@ -13,8 +15,15 @@ TreeWidgetItem::TreeWidgetItem(QTreeWidget *p_parent, const QStringList &p_strin
 bool TreeWidgetItem::operator<(const QTreeWidgetItem &p_other) const
 {
     int column = treeWidget() ? treeWidget()->sortColumn() : 0;
-    const QVariant v1 = data(column, Qt::DisplayRole);
-    const QVariant v2 = p_other.data(column, Qt::DisplayRole);
+
+    // Check ComparisonRole first.
+    QVariant v1 = data(column, Role::ComparisonRole);
+    QVariant v2 = p_other.data(column, Role::ComparisonRole);
+    if (v1.isNull() || v2.isNull()) {
+        v1 = data(column, Qt::DisplayRole);
+        v2 = p_other.data(column, Qt::DisplayRole);
+    }
+
     if (v1.canConvert<QString>() && v2.canConvert<QString>()) {
         const auto s1 = v1.toString().toLower();
         const auto s2 = v2.toString().toLower();