Forráskód Böngészése

support Mermaid and Flowchart.js preview

Mermaid preview is disabled for some issues.
Le Tan 7 éve
szülő
commit
10e2bba7f6

+ 1 - 1
src/resources/export_template.html

@@ -3,7 +3,7 @@
 <meta charset="utf-8">
 <head>
     <style type="text/css">
-    /* BACKGROUND_PLACE_HOLDER */
+    /* STYLE_GLOBAL_PLACE_HOLDER */
     </style>
 
     <style type="text/css">

+ 1 - 1
src/resources/markdown_template.html

@@ -3,7 +3,7 @@
 <meta charset="utf-8">
 <head>
     <style type="text/css">
-    /* BACKGROUND_PLACE_HOLDER */
+    /* STYLE_GLOBAL_PLACE_HOLDER */
     </style>
 
     <style type="text/css">

+ 2 - 2
src/resources/markdown_template.js

@@ -1328,7 +1328,7 @@ var specialCodeBlock = function(lang) {
            || (VEnableGraphviz && lang == 'dot');
 };
 
-var handlePlantUMLResult = function(id, format, result) {
+var handlePlantUMLResult = function(id, timeStamp, format, result) {
     var code = document.getElementsByClassName(plantUMLCodeClass + id)[0];
     if (code && result.length > 0) {
         var obj = null;
@@ -1348,7 +1348,7 @@ var handlePlantUMLResult = function(id, format, result) {
     finishOneAsyncJob();
 };
 
-var handleGraphvizResult = function(id, format, result) {
+var handleGraphvizResult = function(id, timeStamp, format, result) {
     var code = document.getElementsByClassName(graphvizCodeClass + id)[0];
     if (code && result.length > 0) {
         var obj = null;

+ 113 - 5
src/resources/mathjax_preview.js

@@ -4,22 +4,25 @@ var contentDiv = document.getElementById('content-div');
 
 var content;
 
+var VMermaidDivClass = 'mermaid-diagram';
+var VFlowchartDivClass = 'flowchart-diagram';
+
 new QWebChannel(qt.webChannelTransport,
     function(channel) {
         content = channel.objects.content;
 
         content.requestPreviewMathJax.connect(previewMathJax);
+        content.requestPreviewDiagram.connect(previewDiagram);
 
         channelInitialized = true;
     });
 
-var previewMathJax = function(identifier, id, text) {
+var previewMathJax = function(identifier, id, timeStamp, text) {
     if (text.length == 0) {
         return;
     }
 
     var p = document.createElement('p');
-    p.id = identifier + '_' + id;
     p.textContent = text;
     contentDiv.appendChild(p);
 
@@ -27,7 +30,7 @@ var previewMathJax = function(identifier, id, text) {
         MathJax.Hub.Queue(["Typeset",
                            MathJax.Hub,
                            p,
-                           postProcessMathJax.bind(undefined, identifier, id, p)]);
+                           postProcessMathJax.bind(undefined, identifier, id, timeStamp, p)]);
     } catch (err) {
         console.log("err: " + err);
         contentDiv.removeChild(p);
@@ -35,14 +38,119 @@ var previewMathJax = function(identifier, id, text) {
     }
 };
 
-var postProcessMathJax = function(identifier, id, container) {
+var postProcessMathJax = function(identifier, id, timeStamp, container) {
     domtoimage.toPng(container, { height: container.clientHeight * 1.5 }).then(function (dataUrl) {
         var png = dataUrl.substring(dataUrl.indexOf(',') + 1);
-        content.mathjaxResultReady(identifier, id, 'png', png);
+        content.mathjaxResultReady(identifier, id, timeStamp, 'png', png);
 
         contentDiv.removeChild(container);
         delete container;
     }).catch(function (err) {
         console.log("err: " + err);
+        contentDiv.removeChild(container);
+        delete container;
+    });
+};
+
+var mermaidParserErr = false;
+var mermaidIdx = 0;
+
+var flowchartIdx = 0;
+
+if (typeof mermaidAPI != "undefined") {
+    mermaidAPI.parseError = function(err, hash) {
+        mermaidParserErr = true;
+
+        // Clean the container element, or mermaidAPI won't render the graph with
+        // the same id.
+        var errGraph = document.getElementById('mermaid-diagram-' + mermaidIdx);
+        if (errGraph) {
+            var parentNode = errGraph.parentElement;
+            parentNode.outerHTML = '';
+            delete parentNode;
+        }
+    };
+}
+
+var previewDiagram = function(identifier, id, timeStamp, lang, text) {
+    if (text.length == 0) {
+        return;
+    }
+
+    var div = null;
+    if (lang == 'flow' || lang == 'flowchart') {
+        flowchartIdx++;
+        try {
+            var graph = flowchart.parse(text);
+        } catch (err) {
+            console.log("err: " + err);
+            content.diagramResultReady(identifier, id, timeStamp, 'png', '');
+            return;
+        }
+
+        if (typeof graph == "undefined") {
+            content.diagramResultReady(identifier, id, timeStamp, 'png', '');
+            return;
+        }
+
+        div = document.createElement('div');
+        div.id = 'flowchart-diagram-' + flowchartIdx;
+        div.classList.add(VFlowchartDivClass);
+
+        contentDiv.appendChild(div);
+
+        // Draw on it after adding it to page.
+        try {
+            graph.drawSVG(div.id);
+        } catch (err) {
+            console.log("err: " + err);
+            contentDiv.removeChild(div);
+            delete div;
+            content.diagramResultReady(identifier, id, timeStamp, 'png', '');
+            return;
+        }
+    } else if (lang == 'mermaid') {
+        mermaidParserErr = false;
+        mermaidIdx++;
+        try {
+            // Do not increment mermaidIdx here.
+            var graph = mermaidAPI.render('mermaid-diagram-' + mermaidIdx,
+                                          text,
+                                          function(){});
+        } catch (err) {
+            console.log("err: " + err);
+            content.diagramResultReady(identifier, id, timeStamp, 'png', '');
+            return;
+        }
+
+        if (mermaidParserErr || typeof graph == "undefined") {
+            content.diagramResultReady(identifier, id, timeStamp, 'png', '');
+            return;
+        }
+
+        div = document.createElement('div');
+        div.classList.add(VMermaidDivClass);
+        div.innerHTML = graph;
+        contentDiv.appendChild(div);
+    }
+
+    if (!div) {
+        content.diagramResultReady(identifier, id, timeStamp, 'png', '');
+        return;
+    }
+
+    // For Flowchart.js, we need to add addtitional height. Since Mermaid is not
+    // supported now, we just add it simply here.
+    domtoimage.toPng(div, { height: div.clientHeight + 30 }).then(function (dataUrl) {
+        var png = dataUrl.substring(dataUrl.indexOf(',') + 1);
+        content.diagramResultReady(identifier, id, timeStamp, 'png', png);
+
+        contentDiv.removeChild(div);
+        delete div;
+    }).catch(function (err) {
+        console.log("err: " + err);
+        contentDiv.removeChild(div);
+        content.diagramResultReady(identifier, id, timeStamp, 'png', '');
+        delete div;
     });
 };

+ 4 - 0
src/resources/mathjax_preview_template.html

@@ -2,6 +2,10 @@
 <html>
 <meta charset="utf-8">
 <head>
+    <style type="text/css">
+    /* STYLE_GLOBAL_PLACE_HOLDER */
+    </style>
+
     <link rel="stylesheet" type="text/css" href="CSS_PLACE_HOLDER">
     <script src="qrc:/resources/qwebchannel.js"></script>
     <!-- EXTRA_PLACE_HOLDER -->

+ 2 - 0
src/resources/themes/v_moonlight/v_moonlight.css

@@ -171,6 +171,7 @@ div.mermaid-diagram {
 }
 
 div.flowchart-diagram {
+    padding: 0px 5px 0px 5px;
     margin: 16px 0px 16px 0px;
     width: fit-content;
     overflow: hidden;
@@ -179,6 +180,7 @@ div.flowchart-diagram {
 }
 
 div.plantuml-diagram {
+    padding: 5px 5px 0px 5px;
     margin: 16px 0px 16px 0px;
     width: fit-content;
     overflow: hidden;

+ 2 - 0
src/resources/themes/v_native/v_native.css

@@ -174,12 +174,14 @@ div.mermaid-diagram {
 }
 
 div.flowchart-diagram {
+    padding: 0px 5px 0px 5px;
     margin: 16px 0px 16px 0px;
     width: fit-content;
     overflow: hidden;
 }
 
 div.plantuml-diagram {
+    padding: 5px 5px 0px 5px;
     margin: 16px 0px 16px 0px;
     width: fit-content;
     overflow: hidden;

+ 2 - 0
src/resources/themes/v_pure/v_pure.css

@@ -175,12 +175,14 @@ div.mermaid-diagram {
 }
 
 div.flowchart-diagram {
+    padding: 0px 5px 0px 5px;
     margin: 16px 0px 16px 0px;
     width: fit-content;
     overflow: hidden;
 }
 
 div.plantuml-diagram {
+    padding: 5px 5px 0px 5px;
     margin: 16px 0px 16px 0px;
     width: fit-content;
     overflow: hidden;

+ 1 - 1
src/resources/vnote.ini

@@ -38,7 +38,7 @@ markdown_converter=2
 enable_mermaid=false
 
 ; Enable MathJax
-enable_mathjax=true
+enable_mathjax=false
 
 ; Enable Flowchart.js
 enable_flowchart=false

+ 18 - 6
src/utils/vutils.cpp

@@ -818,19 +818,31 @@ QString VUtils::generateExportHtmlTemplate(const QString &p_renderBg, bool p_inc
 QString VUtils::generateMathJaxPreviewTemplate()
 {
     QString templ = VNote::generateMathJaxPreviewTemplate();
-    QString mj = g_config->getMathjaxJavascript();
-    // Chante MathJax to be rendered as SVG.
-    QRegExp reg("(Mathjax\\.js\\?config=)\\S+", Qt::CaseInsensitive);
-    // mj.replace(reg, QString("\\1%1").arg("TeX-MML-AM_SVG"));
-
-    templ.replace(HtmlHolder::c_JSHolder, mj);
+    templ.replace(HtmlHolder::c_JSHolder, g_config->getMathjaxJavascript());
 
     QString extraFile;
+
+    QString mathjaxScale = QString::number((int)(100 * VUtils::calculateScaleFactor()));
+
+    /*
+    // Mermaid.
+    extraFile += "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + g_config->getMermaidCssStyleUrl() + "\"/>\n" +
+                 "<script src=\"qrc" + VNote::c_mermaidApiJsFile + "\"></script>\n";
+    */
+
+    // Flowchart.
+    extraFile += "<script src=\"qrc" + VNote::c_raphaelJsFile + "\"></script>\n" +
+                 "<script src=\"qrc" + VNote::c_flowchartJsFile + "\"></script>\n";
+
+    // MathJax.
     extraFile += "<script type=\"text/x-mathjax-config\">"
                  "MathJax.Hub.Config({\n"
                  "                    tex2jax: {inlineMath: [['$','$'], ['\\\\(','\\\\)']],\n"
                                                "processEscapes: true,\n"
                                                "processClass: \"tex2jax_process|language-mathjax|lang-mathjax\"},\n"
+                 "                    \"HTML-CSS\": {\n"
+                 "                                   scale: " + mathjaxScale + "\n"
+                 "                                  },\n"
                  "                    showProcessingMessages: false,\n"
                  "                    messageStyle: \"none\"});\n"
                  "</script>\n";

+ 3 - 1
src/vconstants.h

@@ -4,6 +4,8 @@
 #include <QString>
 #include <QStringList>
 
+typedef unsigned long long TimeStamp;
+
 // Html: rich text file;
 // Markdown: Markdown text file;
 // List: Infinite list file like WorkFlowy;
@@ -38,7 +40,7 @@ namespace HtmlHolder
     static const QString c_JSHolder = "JS_PLACE_HOLDER";
     static const QString c_cssHolder = "CSS_PLACE_HOLDER";
     static const QString c_codeBlockCssHolder = "HIGHLIGHTJS_CSS_PLACE_HOLDER";
-    static const QString c_globalStyleHolder = "/* BACKGROUND_PLACE_HOLDER */";
+    static const QString c_globalStyleHolder = "/* STYLE_GLOBAL_PLACE_HOLDER */";
     static const QString c_extraHolder = "<!-- EXTRA_PLACE_HOLDER -->";
     static const QString c_bodyHolder = "<!-- BODY_PLACE_HOLDER -->";
     static const QString c_headHolder = "<!-- HEAD_PLACE_HOLDER -->";

+ 2 - 2
src/vdocument.cpp

@@ -147,7 +147,7 @@ void VDocument::processPlantUML(int p_id, const QString &p_format, const QString
                 this, &VDocument::plantUMLResultReady);
     }
 
-    m_plantUMLHelper->processAsync(p_id, p_format, p_text);
+    m_plantUMLHelper->processAsync(p_id, 0, p_format, p_text);
 }
 
 void VDocument::processGraphviz(int p_id, const QString &p_format, const QString &p_text)
@@ -158,7 +158,7 @@ void VDocument::processGraphviz(int p_id, const QString &p_format, const QString
                 this, &VDocument::graphvizResultReady);
     }
 
-    m_graphvizHelper->processAsync(p_id, p_format, p_text);
+    m_graphvizHelper->processAsync(p_id, 0, p_format, p_text);
 }
 
 void VDocument::setPreviewEnabled(bool p_enabled)

+ 9 - 3
src/vdocument.h

@@ -142,9 +142,15 @@ signals:
 
     void wordCountInfoUpdated();
 
-    void plantUMLResultReady(int p_id, const QString &p_format, const QString &p_result);
-
-    void graphvizResultReady(int p_id, const QString &p_format, const QString &p_result);
+    void plantUMLResultReady(int p_id,
+                             unsigned long long p_timeStamp,
+                             const QString &p_format,
+                             const QString &p_result);
+
+    void graphvizResultReady(int p_id,
+                             unsigned long long p_timeStamp,
+                             const QString &p_format,
+                             const QString &p_result);
 
     void requestPreviewEnabled(bool p_enabled);
 

+ 8 - 5
src/vgraphvizhelper.cpp

@@ -9,6 +9,7 @@ extern VConfigManager *g_config;
 
 #define TaskIdProperty "GraphvizTaskId"
 #define TaskFormatProperty "GraphvizTaskFormat"
+#define TaskTimeStampProperty "GraphvizTaskTimeStamp"
 
 VGraphvizHelper::VGraphvizHelper(QObject *p_parent)
     : QObject(p_parent)
@@ -16,10 +17,11 @@ VGraphvizHelper::VGraphvizHelper(QObject *p_parent)
     prepareCommand(m_program, m_args);
 }
 
-void VGraphvizHelper::processAsync(int p_id, const QString &p_format, const QString &p_text)
+void VGraphvizHelper::processAsync(int p_id, TimeStamp p_timeStamp, const QString &p_format, const QString &p_text)
 {
     QProcess *process = new QProcess(this);
     process->setProperty(TaskIdProperty, p_id);
+    process->setProperty(TaskTimeStampProperty, p_timeStamp);
     process->setProperty(TaskFormatProperty, p_format);
     connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
             this, SLOT(handleProcessFinished(int, QProcess::ExitStatus)));
@@ -51,7 +53,8 @@ void VGraphvizHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus
     QProcess *process = static_cast<QProcess *>(sender());
     int id = process->property(TaskIdProperty).toInt();
     QString format = process->property(TaskFormatProperty).toString();
-    qDebug() << "process finished" << id << format << p_exitCode << p_exitStatus;
+    TimeStamp timeStamp = process->property(TaskTimeStampProperty).toULongLong();
+    qDebug() << "process finished" << id << timeStamp << format << p_exitCode << p_exitStatus;
     bool failed = true;
     if (p_exitStatus == QProcess::NormalExit) {
         if (p_exitCode < 0) {
@@ -60,9 +63,9 @@ void VGraphvizHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus
             failed = false;
             QByteArray outBa = process->readAllStandardOutput();
             if (format == "svg") {
-                emit resultReady(id, format, QString::fromLocal8Bit(outBa));
+                emit resultReady(id, timeStamp, format, QString::fromLocal8Bit(outBa));
             } else {
-                emit resultReady(id, format, QString::fromLocal8Bit(outBa.toBase64()));
+                emit resultReady(id, timeStamp, format, QString::fromLocal8Bit(outBa.toBase64()));
             }
         }
     } else {
@@ -76,7 +79,7 @@ void VGraphvizHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus
             qWarning() << "Graphviz stderr:" << errStr;
         }
 
-        emit resultReady(id, format, "");
+        emit resultReady(id, timeStamp, format, "");
     }
 
     process->deleteLater();

+ 4 - 2
src/vgraphvizhelper.h

@@ -6,18 +6,20 @@
 #include <QStringList>
 #include <QProcess>
 
+#include "vconstants.h"
+
 class VGraphvizHelper : public QObject
 {
     Q_OBJECT
 public:
     explicit VGraphvizHelper(QObject *p_parent = nullptr);
 
-    void processAsync(int p_id, const QString &p_format, const QString &p_text);
+    void processAsync(int p_id, TimeStamp p_timeStamp, const QString &p_format, const QString &p_text);
 
     void prepareCommand(QString &p_cmd, QStringList &p_args) const;
 
 signals:
-    void resultReady(int p_id, const QString &p_format, const QString &p_result);
+    void resultReady(int p_id, TimeStamp p_timeStamp, const QString &p_format, const QString &p_result);
 
 private slots:
     void handleProcessFinished(int p_exitCode, QProcess::ExitStatus p_exitStatus);

+ 53 - 25
src/vlivepreviewhelper.cpp

@@ -42,6 +42,7 @@ CodeBlockPreviewInfo::CodeBlockPreviewInfo(const VCodeBlock &p_cb)
 void CodeBlockPreviewInfo::clearImageData()
 {
     m_imgData.clear();
+    m_imgDataBa.clear();
     m_inplacePreview.clear();
 }
 
@@ -64,7 +65,6 @@ void CodeBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc,
     }
 }
 
-// Update inplace preview according to m_imgData.
 void CodeBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor,
                                                 const QTextDocument *p_doc)
 {
@@ -100,12 +100,12 @@ VLivePreviewHelper::VLivePreviewHelper(VEditor *p_editor,
       m_livePreviewEnabled(false),
       m_inplacePreviewEnabled(false),
       m_graphvizHelper(NULL),
-      m_plantUMLHelper(NULL)
+      m_plantUMLHelper(NULL),
+      m_lastInplacePreviewSize(0),
+      m_timeStamp(0)
 {
     connect(m_editor->object(), &VEditorObject::cursorPositionChanged,
             this, &VLivePreviewHelper::handleCursorPositionChanged);
-    connect(m_document, &VDocument::codeBlockPreviewReady,
-            this, &VLivePreviewHelper::webAsyncResultReady);
 
     m_flowchartEnabled = g_config->getEnableFlowchart();
     m_mermaidEnabled = g_config->getEnableMermaid();
@@ -117,6 +117,9 @@ VLivePreviewHelper::VLivePreviewHelper(VEditor *p_editor,
     m_mathJaxID = m_mathJaxHelper->registerIdentifier();
     connect(m_mathJaxHelper, &VMathJaxPreviewHelper::mathjaxPreviewResultReady,
             this, &VLivePreviewHelper::mathjaxPreviewResultReady);
+    connect(m_mathJaxHelper, &VMathJaxPreviewHelper::diagramPreviewResultReady,
+            // The same handle logics.
+            this, &VLivePreviewHelper::mathjaxPreviewResultReady);
 }
 
 bool VLivePreviewHelper::isPreviewLang(const QString &p_lang) const
@@ -134,6 +137,8 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
         return;
     }
 
+    ++m_timeStamp;
+
     int lastIndex = m_cbIndex;
     m_cbIndex = -1;
     int cursorBlock = m_editor->textCursorW().block().blockNumber();
@@ -239,6 +244,7 @@ void VLivePreviewHelper::updateLivePreview()
 
         if (!cb.hasImageData()) {
             m_graphvizHelper->processAsync(m_cbIndex | LANG_PREFIX_GRAPHVIZ | TYPE_LIVE_PREVIEW,
+                                           m_timeStamp,
                                            "svg",
                                            removeFence(vcb.m_text));
         } else {
@@ -253,6 +259,7 @@ void VLivePreviewHelper::updateLivePreview()
 
         if (!cb.hasImageData()) {
             m_plantUMLHelper->processAsync(m_cbIndex | LANG_PREFIX_PLANTUML | TYPE_LIVE_PREVIEW,
+                                           m_timeStamp,
                                            "svg",
                                            removeFence(vcb.m_text));
         } else {
@@ -292,13 +299,20 @@ void VLivePreviewHelper::setInplacePreviewEnabled(bool p_enabled)
         for (auto & cb : m_codeBlocks) {
             cb.clearImageData();
         }
+
+        updateInplacePreview();
     }
 }
 
 void VLivePreviewHelper::localAsyncResultReady(int p_id,
+                                               TimeStamp p_timeStamp,
                                                const QString &p_format,
                                                const QString &p_result)
 {
+    if (p_timeStamp != m_timeStamp) {
+        return;
+    }
+
     Q_UNUSED(p_format);
     Q_ASSERT(p_format == "svg");
     int idx = p_id & INDEX_MASK;
@@ -354,6 +368,7 @@ void VLivePreviewHelper::processForInplacePreview(int p_idx)
             updateInplacePreview();
         } else {
             m_graphvizHelper->processAsync(p_idx | LANG_PREFIX_GRAPHVIZ | TYPE_INPLACE_PREVIEW,
+                                           m_timeStamp,
                                            "svg",
                                            removeFence(vcb.m_text));
         }
@@ -369,43 +384,66 @@ void VLivePreviewHelper::processForInplacePreview(int p_idx)
             updateInplacePreview();
         } else {
             m_plantUMLHelper->processAsync(p_idx | LANG_PREFIX_PLANTUML | TYPE_INPLACE_PREVIEW,
+                                           m_timeStamp,
                                            "svg",
                                            removeFence(vcb.m_text));
         }
     } else if (vcb.m_lang == "flow"
                || vcb.m_lang == "flowchart") {
-        m_document->previewCodeBlock(p_idx,
-                                     vcb.m_lang,
-                                     removeFence(vcb.m_text),
-                                     false);
+        m_mathJaxHelper->previewDiagram(m_mathJaxID,
+                                        p_idx,
+                                        m_timeStamp,
+                                        vcb.m_lang,
+                                        removeFence(vcb.m_text));
     } else if (vcb.m_lang == "mathjax") {
         m_mathJaxHelper->previewMathJax(m_mathJaxID,
                                         p_idx,
+                                        m_timeStamp,
                                         removeFence(vcb.m_text));
     }
 }
 
 void VLivePreviewHelper::updateInplacePreview()
 {
+    QSet<int> blocks;
     QVector<QSharedPointer<VImageToPreview> > images;
     for (int i = 0; i < m_codeBlocks.size(); ++i) {
         CodeBlockPreviewInfo &cb = m_codeBlocks[i];
         if (cb.inplacePreviewReady()) {
             // Generate the image.
+            bool valid = false;
             if (cb.hasImageData()) {
                 cb.inplacePreview()->m_image.loadFromData(cb.imageData().toUtf8(),
                                                           cb.imageFormat().toLocal8Bit().data());
                 images.append(cb.inplacePreview());
+                valid = true;
             } else if (cb.hasImageDataBa()) {
                 cb.inplacePreview()->m_image.loadFromData(cb.imageDataBa(),
                                                           cb.imageFormat().toLocal8Bit().data());
                 images.append(cb.inplacePreview());
+                valid = true;
+            }
+
+            if (!valid) {
+                blocks.insert(cb.inplacePreview()->m_blockNumber);
             }
+        } else {
+            blocks.insert(cb.codeBlock().m_endBlock);
         }
     }
 
+    if (images.isEmpty() && m_lastInplacePreviewSize == 0) {
+        return;
+    }
+
     emit inplacePreviewCodeBlockUpdated(images);
 
+    m_lastInplacePreviewSize = images.size();
+
+    if (!blocks.isEmpty()) {
+        emit checkBlocksForObsoletePreview(blocks.toList());
+    }
+
     // Clear image.
     for (int i = 0; i < m_codeBlocks.size(); ++i) {
         CodeBlockPreviewInfo &cb = m_codeBlocks[i];
@@ -416,28 +454,18 @@ void VLivePreviewHelper::updateInplacePreview()
     }
 }
 
-void VLivePreviewHelper::webAsyncResultReady(int p_id,
-                                             const QString &p_lang,
-                                             const QString &p_result)
-{
-    Q_UNUSED(p_lang);
-    if (p_id >= m_codeBlocks.size() || p_result.isEmpty()) {
-        return;
-    }
-
-    CodeBlockPreviewInfo &cb = m_codeBlocks[p_id];
-    cb.setImageData(QStringLiteral("svg"), p_result);
-    cb.updateInplacePreview(m_editor, m_doc);
-    updateInplacePreview();
-}
-
 void VLivePreviewHelper::mathjaxPreviewResultReady(int p_identitifer,
                                                    int p_id,
+                                                   TimeStamp p_timeStamp,
                                                    const QString &p_format,
                                                    const QByteArray &p_data)
 {
-    if (p_identitifer != m_mathJaxID
-        || (p_id >= m_codeBlocks.size() || p_data.isEmpty())) {
+    if (p_identitifer != m_mathJaxID || p_timeStamp != m_timeStamp) {
+        return;
+    }
+
+    if (p_id >= m_codeBlocks.size() || p_data.isEmpty()) {
+        updateInplacePreview();
         return;
     }
 

+ 9 - 3
src/vlivepreviewhelper.h

@@ -6,6 +6,7 @@
 
 #include "hgmarkdownhighlighter.h"
 #include "vpreviewmanager.h"
+#include "vconstants.h"
 
 class VEditor;
 class VDocument;
@@ -133,18 +134,19 @@ public:
 public slots:
     void updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlocks);
 
-    void webAsyncResultReady(int p_id, const QString &p_lang, const QString &p_result);
-
 signals:
     void inplacePreviewCodeBlockUpdated(const QVector<QSharedPointer<VImageToPreview> > &p_images);
 
+    void checkBlocksForObsoletePreview(const QList<int> &p_blocks);
+
 private slots:
     void handleCursorPositionChanged();
 
-    void localAsyncResultReady(int p_id, const QString &p_format, const QString &p_result);
+    void localAsyncResultReady(int p_id, TimeStamp p_timeStamp, const QString &p_format, const QString &p_result);
 
     void mathjaxPreviewResultReady(int p_identitifer,
                                    int p_id,
+                                   TimeStamp p_timeStamp,
                                    const QString &p_format,
                                    const QByteArray &p_data);
 
@@ -187,6 +189,10 @@ private:
 
     // Identification for VMathJaxPreviewHelper.
     int m_mathJaxID;
+
+    int m_lastInplacePreviewSize;
+
+    TimeStamp m_timeStamp;
 };
 
 inline bool VLivePreviewHelper::isPreviewEnabled() const

+ 30 - 5
src/vmathjaxpreviewhelper.cpp

@@ -24,6 +24,7 @@ VMathJaxPreviewHelper::~VMathJaxPreviewHelper()
 void VMathJaxPreviewHelper::doInit()
 {
     Q_ASSERT(!m_initialized);
+    m_initialized = true;
 
     m_webView = new QWebEngineView(m_parentWidget);
     connect(m_webView, &QWebEngineView::loadFinished,
@@ -36,9 +37,23 @@ void VMathJaxPreviewHelper::doInit()
 
     m_webDoc = new VMathJaxWebDocument(m_webView);
     connect(m_webDoc, &VMathJaxWebDocument::mathjaxPreviewResultReady,
-            this, [this](int p_identifier, int p_id, const QString &p_format, const QString &p_data) {
+            this, [this](int p_identifier,
+                         int p_id,
+                         TimeStamp p_timeStamp,
+                         const QString &p_format,
+                         const QString &p_data) {
+                QByteArray ba = QByteArray::fromBase64(p_data.toUtf8());
+                emit mathjaxPreviewResultReady(p_identifier, p_id, p_timeStamp, p_format, ba);
+            });
+
+    connect(m_webDoc, &VMathJaxWebDocument::diagramPreviewResultReady,
+            this, [this](int p_identifier,
+                        int p_id,
+                        TimeStamp p_timeStamp,
+                        const QString &p_format,
+                        const QString &p_data) {
                 QByteArray ba = QByteArray::fromBase64(p_data.toUtf8());
-                emit mathjaxPreviewResultReady(p_identifier, p_id, p_format, ba);
+                emit diagramPreviewResultReady(p_identifier, p_id, p_timeStamp, p_format, ba);
             });
 
     QWebChannel *channel = new QWebChannel(m_webView);
@@ -50,15 +65,25 @@ void VMathJaxPreviewHelper::doInit()
     while (!m_webReady) {
         VUtils::sleepWait(100);
     }
-
-    m_initialized = true;
 }
 
 void VMathJaxPreviewHelper::previewMathJax(int p_identifier,
                                            int p_id,
+                                           TimeStamp p_timeStamp,
+                                           const QString &p_text)
+{
+    init();
+
+    m_webDoc->previewMathJax(p_identifier, p_id, p_timeStamp, p_text);
+}
+
+void VMathJaxPreviewHelper::previewDiagram(int p_identifier,
+                                           int p_id,
+                                           TimeStamp p_timeStamp,
+                                           const QString &p_lang,
                                            const QString &p_text)
 {
     init();
 
-    m_webDoc->previewMathJax(p_identifier, p_id, p_text);
+    m_webDoc->previewDiagram(p_identifier, p_id, p_timeStamp, p_lang, p_text);
 }

+ 22 - 2
src/vmathjaxpreviewhelper.h

@@ -3,6 +3,8 @@
 
 #include <QObject>
 
+#include "vconstants.h"
+
 class QWebEngineView;
 class VMathJaxWebDocument;
 class QWidget;
@@ -18,15 +20,33 @@ public:
     // Get an ID for identification.
     int registerIdentifier();
 
-    // Preview @p_text and return SVG data asynchronously.
+    // Preview @p_text and return PNG data asynchronously.
     // @p_identifier: identifier the caller registered;
     // @p_id: internal id for each caller;
     // @p_text: raw text of the MathJax script.
-    void previewMathJax(int p_identifier, int p_id, const QString &p_text);
+    void previewMathJax(int p_identifier, int p_id, TimeStamp p_timeStamp, const QString &p_text);
+
+    // Preview @p_text and return PNG data asynchronously.
+    // @p_identifier: identifier the caller registered;
+    // @p_id: internal id for each caller;
+    // @p_lang: language of the diagram;
+    // @p_text: raw text of the script.
+    void previewDiagram(int p_identifier,
+                        int p_id,
+                        TimeStamp p_timeStamp,
+                        const QString &p_lang,
+                        const QString &p_text);
 
 signals:
     void mathjaxPreviewResultReady(int p_identifier,
                                    int p_id,
+                                   TimeStamp p_timeStamp,
+                                   const QString &p_format,
+                                   const QByteArray &p_data);
+
+    void diagramPreviewResultReady(int p_identifier,
+                                   int p_id,
+                                   TimeStamp p_timeStamp,
                                    const QString &p_format,
                                    const QByteArray &p_data);
 

+ 26 - 2
src/vmathjaxwebdocument.cpp

@@ -9,15 +9,39 @@ VMathJaxWebDocument::VMathJaxWebDocument(QObject *p_parent)
 
 void VMathJaxWebDocument::previewMathJax(int p_identifier,
                                          int p_id,
+                                         TimeStamp p_timeStamp,
                                          const QString &p_text)
 {
-    emit requestPreviewMathJax(p_identifier, p_id, p_text);
+    emit requestPreviewMathJax(p_identifier, p_id, p_timeStamp, p_text);
 }
 
 void VMathJaxWebDocument::mathjaxResultReady(int p_identifier,
                                              int p_id,
+                                             unsigned long long p_timeStamp,
                                              const QString &p_format,
                                              const QString &p_data)
 {
-    emit mathjaxPreviewResultReady(p_identifier, p_id, p_format, p_data);
+    emit mathjaxPreviewResultReady(p_identifier, p_id, p_timeStamp, p_format, p_data);
+}
+
+void VMathJaxWebDocument::diagramResultReady(int p_identifier,
+                                             int p_id,
+                                             unsigned long long p_timeStamp,
+                                             const QString &p_format,
+                                             const QString &p_data)
+{
+    emit diagramPreviewResultReady(p_identifier, p_id, p_timeStamp, p_format, p_data);
+}
+
+void VMathJaxWebDocument::previewDiagram(int p_identifier,
+                                         int p_id,
+                                         TimeStamp p_timeStamp,
+                                         const QString &p_lang,
+                                         const QString &p_text)
+{
+    emit requestPreviewDiagram(p_identifier,
+                               p_id,
+                               p_timeStamp,
+                               p_lang,
+                               p_text);
 }

+ 33 - 2
src/vmathjaxwebdocument.h

@@ -3,27 +3,58 @@
 
 #include <QObject>
 
+#include "vconstants.h"
+
 class VMathJaxWebDocument : public QObject
 {
     Q_OBJECT
 public:
     explicit VMathJaxWebDocument(QObject *p_parent = nullptr);
 
-    void previewMathJax(int p_identifier, int p_id, const QString &p_text);
+    void previewMathJax(int p_identifier, int p_id, TimeStamp p_timeStamp, const QString &p_text);
+
+    void previewDiagram(int p_identifier,
+                        int p_id,
+                        TimeStamp p_timeStamp,
+                        const QString &p_lang,
+                        const QString &p_text);
 
 public slots:
     // Will be called in the HTML side
 
     void mathjaxResultReady(int p_identifier,
                             int p_id,
+                            unsigned long long p_timeStamp,
+                            const QString &p_format,
+                            const QString &p_data);
+
+    void diagramResultReady(int p_identifier,
+                            int p_id,
+                            unsigned long long p_timeStamp,
                             const QString &p_format,
                             const QString &p_data);
 
 signals:
-    void requestPreviewMathJax(int p_identifier, int p_id, const QString &p_text);
+    void requestPreviewMathJax(int p_identifier,
+                               int p_id,
+                               unsigned long long p_timeStamp,
+                               const QString &p_text);
+
+    void requestPreviewDiagram(int p_identifier,
+                               int p_id,
+                               unsigned long long p_timeStamp,
+                               const QString &p_lang,
+                               const QString &p_text);
 
     void mathjaxPreviewResultReady(int p_identifier,
                                    int p_id,
+                                   TimeStamp p_timeStamp,
+                                   const QString &p_format,
+                                   const QString &p_data);
+
+    void diagramPreviewResultReady(int p_identifier,
+                                   int p_id,
+                                   TimeStamp p_timeStamp,
                                    const QString &p_format,
                                    const QString &p_data);
 };

+ 2 - 0
src/vmdtab.cpp

@@ -523,6 +523,8 @@ void VMdTab::setupMarkdownEditor()
             m_livePreviewHelper, &VLivePreviewHelper::setInplacePreviewEnabled);
     connect(m_livePreviewHelper, &VLivePreviewHelper::inplacePreviewCodeBlockUpdated,
             m_editor->getPreviewManager(), &VPreviewManager::updateCodeBlocks);
+    connect(m_livePreviewHelper, &VLivePreviewHelper::checkBlocksForObsoletePreview,
+            m_editor->getPreviewManager(), &VPreviewManager::checkBlocksForObsoletePreview);
     m_livePreviewHelper->setInplacePreviewEnabled(m_editor->getPreviewManager()->isPreviewEnabled());
 }
 

+ 10 - 0
src/vnote.cpp

@@ -174,7 +174,17 @@ QString VNote::generateMathJaxPreviewTemplate()
     const QString c_templatePath(":/resources/mathjax_preview_template.html");
     QString templ = VUtils::readFileFromDisk(c_templatePath);
     g_palette->fillStyle(templ);
+
+    QString cssStyle;
+    cssStyle += "div.flowchart-diagram { margin: 0px !important; "
+                "                        padding: 0px 5px 0px 5px !important; }\n"
+                "div.mermaid-diagram { margin: 0px !important; "
+                "                      padding: 0px 5px 0px 5px !important; }\n";
+
+    templ.replace(HtmlHolder::c_globalStyleHolder, cssStyle);
+
     templ.replace(HtmlHolder::c_cssHolder, g_config->getCssStyleUrl());
+
     return templ;
 }
 

+ 2 - 0
src/vnotebook.cpp

@@ -182,6 +182,8 @@ VNotebook *VNotebook::createNotebook(const QString &p_name,
     if (!nb->writeToConfig()) {
         delete nb;
         return NULL;
+    } else {
+        nb->m_valid = true;
     }
 
     return nb;

+ 11 - 5
src/vplantumlhelper.cpp

@@ -9,6 +9,7 @@ extern VConfigManager *g_config;
 
 #define TaskIdProperty "PlantUMLTaskId"
 #define TaskFormatProperty "PlantUMLTaskFormat"
+#define TaskTimeStampProperty "PlantUMLTaskTimeStamp"
 
 VPlantUMLHelper::VPlantUMLHelper(QObject *p_parent)
     : QObject(p_parent)
@@ -16,10 +17,14 @@ VPlantUMLHelper::VPlantUMLHelper(QObject *p_parent)
     prepareCommand(m_program, m_args);
 }
 
-void VPlantUMLHelper::processAsync(int p_id, const QString &p_format, const QString &p_text)
+void VPlantUMLHelper::processAsync(int p_id,
+                                   TimeStamp p_timeStamp,
+                                   const QString &p_format,
+                                   const QString &p_text)
 {
     QProcess *process = new QProcess(this);
     process->setProperty(TaskIdProperty, p_id);
+    process->setProperty(TaskTimeStampProperty, p_timeStamp);
     process->setProperty(TaskFormatProperty, p_format);
     connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
             this, SLOT(handleProcessFinished(int, QProcess::ExitStatus)));
@@ -58,7 +63,8 @@ void VPlantUMLHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus
     QProcess *process = static_cast<QProcess *>(sender());
     int id = process->property(TaskIdProperty).toInt();
     QString format = process->property(TaskFormatProperty).toString();
-    qDebug() << "process finished" << id << format << p_exitCode << p_exitStatus;
+    TimeStamp timeStamp = process->property(TaskTimeStampProperty).toULongLong();
+    qDebug() << "process finished" << id << timeStamp << format << p_exitCode << p_exitStatus;
     bool failed = true;
     if (p_exitStatus == QProcess::NormalExit) {
         if (p_exitCode < 0) {
@@ -67,9 +73,9 @@ void VPlantUMLHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus
             failed = false;
             QByteArray outBa = process->readAllStandardOutput();
             if (format == "svg") {
-                emit resultReady(id, format, QString::fromLocal8Bit(outBa));
+                emit resultReady(id, timeStamp, format, QString::fromLocal8Bit(outBa));
             } else {
-                emit resultReady(id, format, QString::fromLocal8Bit(outBa.toBase64()));
+                emit resultReady(id, timeStamp, format, QString::fromLocal8Bit(outBa.toBase64()));
             }
         }
     } else {
@@ -83,7 +89,7 @@ void VPlantUMLHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus
             qWarning() << "PlantUML stderr:" << errStr;
         }
 
-        emit resultReady(id, format, "");
+        emit resultReady(id, timeStamp, format, "");
     }
 
     process->deleteLater();

+ 7 - 2
src/vplantumlhelper.h

@@ -6,18 +6,23 @@
 #include <QStringList>
 #include <QProcess>
 
+#include "vconstants.h"
+
 class VPlantUMLHelper : public QObject
 {
     Q_OBJECT
 public:
     explicit VPlantUMLHelper(QObject *p_parent = nullptr);
 
-    void processAsync(int p_id, const QString &p_format, const QString &p_text);
+    void processAsync(int p_id,
+                      TimeStamp p_timeStamp,
+                      const QString &p_format,
+                      const QString &p_text);
 
     void prepareCommand(QString &p_cmd, QStringList &p_args) const;
 
 signals:
-    void resultReady(int p_id, const QString &p_format, const QString &p_result);
+    void resultReady(int p_id, TimeStamp p_timeStamp, const QString &p_format, const QString &p_result);
 
 private slots:
     void handleProcessFinished(int p_exitCode, QProcess::ExitStatus p_exitStatus);

+ 37 - 0
src/vpreviewmanager.cpp

@@ -473,3 +473,40 @@ void VPreviewManager::updateCodeBlocks(const QVector<QSharedPointer<VImageToPrev
 
     clearObsoleteImages(ts, PreviewSource::CodeBlock);
 }
+
+void VPreviewManager::checkBlocksForObsoletePreview(const QList<int> &p_blocks)
+{
+    if (p_blocks.isEmpty()) {
+        return;
+    }
+
+    QSet<int> affectedBlocks;
+    for (auto i : p_blocks) {
+        QTextBlock block = m_document->findBlockByNumber(i);
+        if (!block.isValid()) {
+            continue;
+        }
+
+        VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
+        if (!blockData) {
+            continue;
+        }
+
+        if (blockData->getPreviews().isEmpty()) {
+            continue;
+        }
+
+        for (int i = 0; i < (int)PreviewSource::MaxNumberOfSources; ++i) {
+            if (blockData->getPreviews().isEmpty()) {
+                break;
+            }
+
+            PreviewSource ps = static_cast<PreviewSource>(i);
+            if (blockData->clearObsoletePreview(timeStamp(ps), ps)) {
+                affectedBlocks.insert(i);
+            }
+        }
+    }
+
+    m_editor->relayout(affectedBlocks);
+}

+ 4 - 0
src/vpreviewmanager.h

@@ -67,6 +67,10 @@ public:
 
     bool isPreviewEnabled() const;
 
+    // Check @p_blocks to see if there is any obsolete preview and clear them
+    // if there is any.
+    void checkBlocksForObsoletePreview(const QList<int> &p_blocks);
+
     // Calculate the block margin (prefix spaces) in pixels.
     static int calculateBlockMargin(const QTextBlock &p_block, int p_tabStopWidth);
 

+ 1 - 2
src/vtextblockdata.cpp

@@ -89,9 +89,8 @@ bool VTextBlockData::clearObsoletePreview(long long p_timeStamp, PreviewSource p
     bool deleted = false;
     for (auto it = m_previews.begin(); it != m_previews.end();) {
         VPreviewInfo *ele = *it;
-
         if (ele->m_source == p_source
-            && ele->m_timeStamp < p_timeStamp) {
+            && ele->m_timeStamp != p_timeStamp) {
             // Remove it.
             qDebug() << "clear obsolete preview" << ele->m_imageInfo.toString();
             delete ele;