Browse Source

preview non-codeblock MathJax

Le Tan 7 years ago
parent
commit
aa5960f974

+ 125 - 10
src/hgmarkdownhighlighter.cpp

@@ -87,7 +87,10 @@ HGMarkdownHighlighter::HGMarkdownHighlighter(const QVector<HighlightingStyle> &s
     m_completeTimer->setSingleShot(true);
     m_completeTimer->setInterval(completeWaitTime);
     connect(m_completeTimer, &QTimer::timeout,
-            this, &HGMarkdownHighlighter::highlightCompleted);
+            this, [this]() {
+                updateMathjaxBlocks();
+                emit highlightCompleted();
+            });
 
     connect(document, &QTextDocument::contentsChange,
             this, &HGMarkdownHighlighter::handleContentChange);
@@ -183,7 +186,7 @@ void HGMarkdownHighlighter::highlightBlock(const QString &text)
             setCurrentBlockState(HighlightBlockState::Verbatim);
             goto exit;
         } else if (m_enableMathjax) {
-            highlightMathJax(curBlock, text);
+            highlightMathJax(text);
         }
     }
 
@@ -520,11 +523,14 @@ static bool intersect(const QList<QPair<int, int>> &p_indices, int &p_start, int
     return false;
 }
 
-void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QString &p_text)
+void HGMarkdownHighlighter::highlightMathJax(const QString &p_text)
 {
     const int blockMarkLength = 2;
     const int inlineMarkLength = 1;
 
+    VTextBlockData *blockData = currentBlockData();
+    Q_ASSERT(blockData);
+
     int startIdx = 0;
     // Next position to search.
     int pos = 0;
@@ -532,8 +538,10 @@ void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QS
 
     QList<QPair<int, int>> blockIdices;
 
+    bool fromPreBlock = true;
     // Mathjax block formula.
     if (state != HighlightBlockState::MathjaxBlock) {
+        fromPreBlock = false;
         startIdx = m_mathjaxBlockExp.indexIn(p_text);
         pos = startIdx + m_mathjaxBlockExp.matchedLength();
         startIdx = pos - blockMarkLength;
@@ -542,14 +550,56 @@ void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QS
     while (startIdx >= 0) {
         int endIdx = m_mathjaxBlockExp.indexIn(p_text, pos);
         int mathLength = 0;
+        MathjaxInfo info;
         if (endIdx == -1) {
             setCurrentBlockState(HighlightBlockState::MathjaxBlock);
             mathLength = p_text.length() - startIdx;
+            pos = startIdx + mathLength;
+
+            info.m_previewedAsBlock = false;
+            info.m_index = startIdx,
+            info.m_length = mathLength;
+            if (fromPreBlock) {
+                VTextBlockData *preBlockData = previousBlockData();
+                Q_ASSERT(preBlockData);
+                const MathjaxInfo &preInfo = preBlockData->getPendingMathjax();
+                info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength);
+            } else {
+                info.m_text = p_text.mid(startIdx, mathLength);
+            }
+
+            blockData->setPendingMathjax(info);
         } else {
+            // Found end marker of a formula.
             mathLength = endIdx - startIdx + m_mathjaxBlockExp.matchedLength();
+            pos = startIdx + mathLength;
+
+            info.m_previewedAsBlock = false;
+            info.m_index = startIdx;
+            info.m_length = mathLength;
+            if (fromPreBlock) {
+                // A cross-block formula.
+                if (pos >= p_text.length()) {
+                    info.m_previewedAsBlock = true;
+                }
+
+                VTextBlockData *preBlockData = previousBlockData();
+                Q_ASSERT(preBlockData);
+                const MathjaxInfo &preInfo = preBlockData->getPendingMathjax();
+                info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength);
+            } else {
+                // A formula within one block.
+                if (pos >= p_text.length() && startIdx == 0) {
+                    info.m_previewedAsBlock = true;
+                }
+
+                info.m_text = p_text.mid(startIdx, mathLength);
+            }
+
+            blockData->addMathjax(info);
         }
 
-        pos = startIdx + mathLength;
+        fromPreBlock = false;
 
         blockIdices.append(QPair<int, int>(startIdx, pos));
 
@@ -562,7 +612,9 @@ void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QS
     // Mathjax inline formula.
     startIdx = 0;
     pos = 0;
+    fromPreBlock = true;
     if (state != HighlightBlockState::MathjaxInline) {
+        fromPreBlock = false;
         startIdx = m_mathjaxInlineExp.indexIn(p_text);
         pos = startIdx + m_mathjaxInlineExp.matchedLength();
         startIdx = pos - inlineMarkLength;
@@ -582,8 +634,47 @@ void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QS
         // Check if it intersect with blocks.
         if (!intersect(blockIdices, startIdx, pos)) {
             // A valid inline mathjax.
+            MathjaxInfo info;
             if (endIdx == -1) {
                 setCurrentBlockState(HighlightBlockState::MathjaxInline);
+
+                info.m_previewedAsBlock = false;
+                info.m_index = startIdx,
+                info.m_length = mathLength;
+                if (fromPreBlock) {
+                    VTextBlockData *preBlockData = previousBlockData();
+                    Q_ASSERT(preBlockData);
+                    const MathjaxInfo &preInfo = preBlockData->getPendingMathjax();
+                    info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength);
+                } else {
+                    info.m_text = p_text.mid(startIdx, mathLength);
+                }
+
+                blockData->setPendingMathjax(info);
+            } else {
+                info.m_previewedAsBlock = false;
+                info.m_index = startIdx;
+                info.m_length = mathLength;
+                if (fromPreBlock) {
+                    // A cross-block formula.
+                    if (pos >= p_text.length()) {
+                        info.m_previewedAsBlock = true;
+                    }
+
+                    VTextBlockData *preBlockData = previousBlockData();
+                    Q_ASSERT(preBlockData);
+                    const MathjaxInfo &preInfo = preBlockData->getPendingMathjax();
+                    info.m_text = preInfo.text() + "\n" + p_text.mid(startIdx, mathLength);
+                } else {
+                    // A formula within one block.
+                    if (pos >= p_text.length() && startIdx == 0) {
+                        info.m_previewedAsBlock = true;
+                    }
+
+                    info.m_text = p_text.mid(startIdx, mathLength);
+                }
+
+                blockData->addMathjax(info);
             }
 
             setFormat(startIdx, mathLength, m_mathjaxFormat);
@@ -599,6 +690,8 @@ void HGMarkdownHighlighter::highlightMathJax(const QTextBlock &p_block, const QS
 
             startIdx = pos - inlineMarkLength;
         }
+
+        fromPreBlock = false;
     }
 }
 
@@ -813,12 +906,8 @@ bool HGMarkdownHighlighter::updateCodeBlocks()
     }
 
     m_numOfCodeBlockHighlightsToRecv = codeBlocks.size();
-    if (m_numOfCodeBlockHighlightsToRecv > 0) {
-        emit codeBlocksUpdated(codeBlocks);
-        return true;
-    } else {
-        return false;
-    }
+    emit codeBlocksUpdated(codeBlocks);
+    return m_numOfCodeBlockHighlightsToRecv > 0;
 }
 
 static bool compHLUnitStyle(const HLUnitStyle &a, const HLUnitStyle &b)
@@ -977,3 +1066,29 @@ void HGMarkdownHighlighter::highlightHeaderFast(int p_blockNumber, const QString
         }
     }
 }
+
+void HGMarkdownHighlighter::updateMathjaxBlocks()
+{
+    if (!m_enableMathjax) {
+        return;
+    }
+
+    QVector<VMathjaxBlock> blocks;
+    QTextBlock bl = document->firstBlock();
+    while (bl.isValid()) {
+        VTextBlockData *data = static_cast<VTextBlockData *>(bl.userData());
+        if (!data) {
+            bl = bl.next();
+            continue;
+        }
+
+        const QVector<MathjaxInfo> &info = data->getMathjax();
+        for (auto const & it : info) {
+            blocks.append(VMathjaxBlock(bl.blockNumber(), it));
+        }
+
+        bl = bl.next();
+    }
+
+    emit mathjaxBlocksUpdated(blocks);
+}

+ 52 - 1
src/hgmarkdownhighlighter.h

@@ -69,6 +69,52 @@ struct VCodeBlock
     }
 };
 
+
+struct VMathjaxBlock
+{
+    VMathjaxBlock()
+        : m_blockNumber(-1),
+          m_previewedAsBlock(false),
+          m_index(-1),
+          m_length(-1)
+    {
+    }
+
+    VMathjaxBlock(int p_blockNumber, const MathjaxInfo &p_info)
+        : m_blockNumber(p_blockNumber),
+          m_previewedAsBlock(p_info.m_previewedAsBlock),
+          m_index(p_info.m_index),
+          m_length(p_info.m_length),
+          m_text(p_info.m_text)
+    {
+    }
+
+    bool equalContent(const VMathjaxBlock &p_block) const
+    {
+        return m_text == p_block.m_text;
+    }
+
+    void updateNonContent(const VMathjaxBlock &p_block)
+    {
+        m_blockNumber = p_block.m_blockNumber;
+        m_previewedAsBlock = p_block.m_previewedAsBlock;
+        m_index = p_block.m_index;
+        m_length = p_block.m_length;
+    }
+
+    int m_blockNumber;
+
+    bool m_previewedAsBlock;
+
+    // Start index within the block.
+    int m_index;
+
+    int m_length;
+
+    QString m_text;
+};
+
+
 // Highlight unit with global position and string style name.
 struct HLUnitPos
 {
@@ -169,6 +215,9 @@ signals:
     // Emitted when header regions have been fetched from a new parsing result.
     void headersUpdated(const QVector<VElementRegion> &p_headerRegions);
 
+    // Emitted when Mathjax blocks updated.
+    void mathjaxBlocksUpdated(const QVector<VMathjaxBlock> &p_mathjaxBlocks);
+
 protected:
     void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
 
@@ -272,7 +321,7 @@ private:
 
     void highlightCodeBlock(const QTextBlock &p_block, const QString &p_text);
 
-    void highlightMathJax(const QTextBlock &p_block, const QString &p_text);
+    void highlightMathJax(const QString &p_text);
 
     // Highlight links using regular expression.
     // PEG Markdown Highlight treat URLs with spaces illegal. This function is
@@ -295,6 +344,8 @@ private:
     // Return false if there is none.
     bool updateCodeBlocks();
 
+    void updateMathjaxBlocks();
+
     // Fetch all the HTML comment regions from parsing result.
     void initHtmlCommentRegionsFromResult();
 

+ 8 - 8
src/resources/hoedown.js

@@ -96,14 +96,14 @@ var highlightText = function(text, id, timeStamp) {
     content.highlightTextCB(html, id, timeStamp);
 }
 
-var textToHtml = function(text) {
+var textToHtml = function(identifier, id, timeStamp, text, inlineStyle) {
     var html = marked(text);
-    var container = textHtmlDiv;
-    container.innerHTML = html;
-
-    html = getHtmlWithInlineStyles(container);
-
-    container.innerHTML = "";
+    if (inlineStyle) {
+        var container = textHtmlDiv;
+        container.innerHTML = html;
+        html = getHtmlWithInlineStyles(container);
+        container.innerHTML = "";
+    }
 
-    content.textToHtmlCB(text, html);
+    content.textToHtmlCB(identifier, id, timeStamp, html);
 }

+ 8 - 8
src/resources/markdown-it.js

@@ -140,14 +140,14 @@ var highlightText = function(text, id, timeStamp) {
     content.highlightTextCB(html, id, timeStamp);
 };
 
-var textToHtml = function(text) {
+var textToHtml = function(identifier, id, timeStamp, text, inlineStyle) {
     var html = mdit.render(text);
-    var container = textHtmlDiv;
-    container.innerHTML = html;
-
-    html = getHtmlWithInlineStyles(container);
-
-    container.innerHTML = "";
+    if (inlineStyle) {
+        var container = textHtmlDiv;
+        container.innerHTML = html;
+        html = getHtmlWithInlineStyles(container);
+        container.innerHTML = "";
+    }
 
-    content.textToHtmlCB(text, html);
+    content.textToHtmlCB(identifier, id, timeStamp, html);
 };

+ 8 - 8
src/resources/marked.js

@@ -84,14 +84,14 @@ var highlightText = function(text, id, timeStamp) {
     content.highlightTextCB(html, id, timeStamp);
 }
 
-var textToHtml = function(text) {
+var textToHtml = function(identifier, id, timeStamp, text, inlineStyle) {
     var html = marked(text);
-    var container = textHtmlDiv;
-    container.innerHTML = html;
-
-    html = getHtmlWithInlineStyles(container);
-
-    container.innerHTML = "";
+    if (inlineStyle) {
+        var container = textHtmlDiv;
+        container.innerHTML = html;
+        html = getHtmlWithInlineStyles(container);
+        container.innerHTML = "";
+    }
 
-    content.textToHtmlCB(text, html);
+    content.textToHtmlCB(identifier, id, timeStamp, html);
 }

+ 51 - 7
src/resources/mathjax_preview.js

@@ -17,29 +17,72 @@ new QWebChannel(qt.webChannelTransport,
         channelInitialized = true;
     });
 
-var previewMathJax = function(identifier, id, timeStamp, text) {
-    if (text.length == 0) {
+var timeStamps = new Map();
+
+var htmlToElement = function(html) {
+    var template = document.createElement('template');
+    html = html.trim();
+    template.innerHTML = html;
+    return template.content.firstChild;
+};
+
+var isEmptyMathJax = function(text) {
+    return text.replace(/\$/g, '').trim().length == 0;
+};
+
+var previewMathJax = function(identifier, id, timeStamp, text, isHtml) {
+    timeStamps.set(identifier, timeStamp);
+
+    if (isEmptyMathJax(text)) {
+        content.mathjaxResultReady(identifier, id, timeStamp, 'png', '');
+        return;
+    }
+
+    var p = null;
+    if (isHtml) {
+        p = htmlToElement(text);
+        if (isEmptyMathJax(p.textContent)) {
+            p = null;
+        }
+    } else {
+        p = document.createElement('p');
+        p.textContent = text;
+    }
+
+    if (!p) {
+        content.mathjaxResultReady(identifier, id, timeStamp, 'png', '');
         return;
     }
 
-    var p = document.createElement('p');
-    p.textContent = text;
     contentDiv.appendChild(p);
 
+    var isBlock = false;
+    if (text.indexOf('$$') !== -1) {
+        isBlock = true;
+    }
+
     try {
         MathJax.Hub.Queue(["Typeset",
                            MathJax.Hub,
                            p,
-                           postProcessMathJax.bind(undefined, identifier, id, timeStamp, p)]);
+                           [postProcessMathJax, identifier, id, timeStamp, p, isBlock]]);
     } catch (err) {
         console.log("err: " + err);
+        content.mathjaxResultReady(identifier, id, timeStamp, 'png', '');
         contentDiv.removeChild(p);
         delete p;
     }
 };
 
-var postProcessMathJax = function(identifier, id, timeStamp, container) {
-    domtoimage.toPng(container, { height: container.clientHeight * 1.5 }).then(function (dataUrl) {
+var postProcessMathJax = function(identifier, id, timeStamp, container, isBlock) {
+    if (timeStamps.get(identifier) != timeStamp) {
+        contentDiv.removeChild(container);
+        delete container;
+        return;
+    }
+
+    var hei = (isBlock ? container.clientHeight * 1.5 : container.clientHeight * 1.8) + 5;
+    domtoimage.toPng(container, { height: hei }).then(function (dataUrl) {
         var png = dataUrl.substring(dataUrl.indexOf(',') + 1);
         content.mathjaxResultReady(identifier, id, timeStamp, 'png', png);
 
@@ -47,6 +90,7 @@ var postProcessMathJax = function(identifier, id, timeStamp, container) {
         delete container;
     }).catch(function (err) {
         console.log("err: " + err);
+        content.mathjaxResultReady(identifier, id, timeStamp, 'png', '');
         contentDiv.removeChild(container);
         delete container;
     });

+ 8 - 8
src/resources/showdown.js

@@ -137,7 +137,7 @@ var highlightText = function(text, id, timeStamp) {
     content.highlightTextCB(html, id, timeStamp);
 }
 
-var textToHtml = function(text) {
+var textToHtml = function(identifier, id, timeStamp, text, inlineStyle) {
     var html = renderer.makeHtml(text);
 
     var parser = new DOMParser();
@@ -148,12 +148,12 @@ var textToHtml = function(text) {
 
     delete parser;
 
-    var container = textHtmlDiv;
-    container.innerHTML = html;
-
-    html = getHtmlWithInlineStyles(container);
-
-    container.innerHTML = "";
+    if (inlineStyle) {
+        var container = textHtmlDiv;
+        container.innerHTML = html;
+        html = getHtmlWithInlineStyles(container);
+        container.innerHTML = "";
+    }
 
-    content.textToHtmlCB(text, html);
+    content.textToHtmlCB(identifier, id, timeStamp, html);
 }

+ 3 - 3
src/resources/themes/v_moonlight/v_moonlight.css

@@ -23,15 +23,15 @@ p {
 }
 
 h1 {
-    font-size: 36px;
+    font-size: 30px;
 }
 
 h2 {
-    font-size: 30px;
+    font-size: 26px;
 }
 
 h3 {
-    font-size: 26px;
+    font-size: 24px;
 }
 
 h4 {

+ 1 - 1
src/resources/themes/v_moonlight/v_moonlight.palette

@@ -7,7 +7,7 @@ mdhl_file=v_moonlight.mdhl
 css_file=v_moonlight.css
 codeblock_css_file=v_moonlight_codeblock.css
 mermaid_css_file=v_moonlight_mermaid.css
-version=8
+version=9
 
 ; This mapping will be used to translate colors when the content of HTML is copied
 ; without background. You could just specify the foreground colors mapping here.

+ 3 - 3
src/resources/themes/v_native/v_native.css

@@ -22,15 +22,15 @@ p {
 }
 
 h1 {
-    font-size: 36px;
+    font-size: 30px;
 }
 
 h2 {
-    font-size: 30px;
+    font-size: 26px;
 }
 
 h3 {
-    font-size: 26px;
+    font-size: 24px;
 }
 
 h4 {

+ 1 - 1
src/resources/themes/v_native/v_native.palette

@@ -7,7 +7,7 @@ mdhl_file=v_native.mdhl
 css_file=v_native.css
 codeblock_css_file=v_native_codeblock.css
 mermaid_css_file=v_native_mermaid.css
-version=8
+version=9
 
 [phony]
 ; Abstract color attributes.

+ 3 - 3
src/resources/themes/v_pure/v_pure.css

@@ -23,15 +23,15 @@ p {
 }
 
 h1 {
-    font-size: 36px;
+    font-size: 30px;
 }
 
 h2 {
-    font-size: 30px;
+    font-size: 26px;
 }
 
 h3 {
-    font-size: 26px;
+    font-size: 24px;
 }
 
 h4 {

+ 1 - 1
src/resources/themes/v_pure/v_pure.palette

@@ -7,7 +7,7 @@ mdhl_file=v_pure.mdhl
 css_file=v_pure.css
 codeblock_css_file=v_pure_codeblock.css
 mermaid_css_file=v_pure_mermaid.css
-version=8
+version=9
 
 [phony]
 ; Abstract color attributes.

+ 4 - 2
src/src.pro

@@ -130,7 +130,8 @@ SOURCES += main.cpp\
     vgraphvizhelper.cpp \
     vlivepreviewhelper.cpp \
     vmathjaxpreviewhelper.cpp \
-    vmathjaxwebdocument.cpp
+    vmathjaxwebdocument.cpp \
+    vmathjaxinplacepreviewhelper.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -251,7 +252,8 @@ HEADERS  += vmainwindow.h \
     vgraphvizhelper.h \
     vlivepreviewhelper.h \
     vmathjaxpreviewhelper.h \
-    vmathjaxwebdocument.h
+    vmathjaxwebdocument.h \
+    vmathjaxinplacepreviewhelper.h
 
 RESOURCES += \
     vnote.qrc \

+ 3 - 0
src/utils/vutils.cpp

@@ -535,6 +535,9 @@ qreal VUtils::calculateScaleFactor()
         factor = dpi / refDpi;
         if (factor < 1) {
             factor = 1;
+        } else {
+            // Keep only two digits after the dot.
+            factor = (int)(factor * 100) / 100.0;
         }
     }
 

+ 10 - 5
src/vdocument.cpp

@@ -11,7 +11,8 @@ VDocument::VDocument(const VFile *v_file, QObject *p_parent)
       m_file(v_file),
       m_readyToHighlight(false),
       m_plantUMLHelper(NULL),
-      m_graphvizHelper(NULL)
+      m_graphvizHelper(NULL),
+      m_nextID(0)
 {
 }
 
@@ -83,9 +84,13 @@ void VDocument::highlightTextCB(const QString &p_html, int p_id, int p_timeStamp
     emit textHighlighted(p_html, p_id, p_timeStamp);
 }
 
-void VDocument::textToHtmlAsync(const QString &p_text)
+void VDocument::textToHtmlAsync(int p_identitifer,
+                                int p_id,
+                                int p_timeStamp,
+                                const QString &p_text,
+                                bool p_inlineStyle)
 {
-    emit requestTextToHtml(p_text);
+    emit requestTextToHtml(p_identitifer, p_id, p_timeStamp, p_text, p_inlineStyle);
 }
 
 void VDocument::getHtmlContentAsync()
@@ -93,9 +98,9 @@ void VDocument::getHtmlContentAsync()
     emit requestHtmlContent();
 }
 
-void VDocument::textToHtmlCB(const QString &p_text, const QString &p_html)
+void VDocument::textToHtmlCB(int p_identitifer, int p_id, int p_timeStamp, const QString &p_html)
 {
-    emit textToHtmlFinished(p_text, p_html);
+    emit textToHtmlFinished(p_identitifer, p_id, p_timeStamp, p_html);
 }
 
 void VDocument::noticeReadyToHighlightText()

+ 20 - 4
src/vdocument.h

@@ -34,7 +34,11 @@ public:
     void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp);
 
     // Request to convert @p_text to HTML.
-    void textToHtmlAsync(const QString &p_text);
+    void textToHtmlAsync(int p_identitifer,
+                         int p_id,
+                         int p_timeStamp,
+                         const QString &p_text,
+                         bool p_inlineStyle);
 
     void setFile(const VFile *p_file);
 
@@ -61,6 +65,8 @@ public:
     // Set the content of the preview.
     void setPreviewContent(const QString &p_lang, const QString &p_html);
 
+    int registerIdentifier();
+
 public slots:
     // Will be called in the HTML side
 
@@ -82,7 +88,7 @@ public slots:
 
     void noticeReadyToHighlightText();
 
-    void textToHtmlCB(const QString &p_text, const QString &p_html);
+    void textToHtmlCB(int p_identitifer, int p_id, int p_timeStamp, const QString &p_html);
 
     void noticeReadyToTextToHtml();
 
@@ -130,9 +136,13 @@ signals:
 
     void logicsFinished();
 
-    void requestTextToHtml(const QString &p_text);
+    void requestTextToHtml(int p_identitifer,
+                           int p_id,
+                           int p_timeStamp,
+                           const QString &p_text,
+                           bool p_inlineStyle);
 
-    void textToHtmlFinished(const QString &p_text, const QString &p_html);
+    void textToHtmlFinished(int p_identitifer, int p_id, int p_timeStamp, const QString &p_html);
 
     void requestHtmlContent();
 
@@ -186,6 +196,8 @@ private:
     VPlantUMLHelper *m_plantUMLHelper;
 
     VGraphvizHelper *m_graphvizHelper;
+
+    int m_nextID;
 };
 
 inline bool VDocument::isReadyToHighlight() const
@@ -203,4 +215,8 @@ inline const VWordCountInfo &VDocument::getWordCountInfo() const
     return m_wordCountInfo;
 }
 
+inline int VDocument::registerIdentifier()
+{
+    return ++m_nextID;
+}
 #endif // VDOCUMENT_H

+ 32 - 35
src/vlivepreviewhelper.cpp

@@ -39,13 +39,6 @@ CodeBlockPreviewInfo::CodeBlockPreviewInfo(const VCodeBlock &p_cb)
 {
 }
 
-void CodeBlockPreviewInfo::clearImageData()
-{
-    m_imgData.clear();
-    m_imgDataBa.clear();
-    m_inplacePreview.clear();
-}
-
 void CodeBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc,
                                             const VCodeBlock &p_cb)
 {
@@ -60,6 +53,7 @@ void CodeBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc,
         m_inplacePreview->m_endPos = block.position() + block.length();
         m_inplacePreview->m_blockPos = block.position();
         m_inplacePreview->m_blockNumber = m_codeBlock.m_endBlock;
+        // Padding is not changed since content is not changed.
     } else {
         m_inplacePreview->clear();
     }
@@ -143,7 +137,8 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
     m_cbIndex = -1;
     int cursorBlock = m_editor->textCursorW().block().blockNumber();
     int idx = 0;
-    bool needUpdate = true;
+    bool needUpdate = m_livePreviewEnabled;
+    bool manualInplacePreview = m_inplacePreviewEnabled;
     for (auto const & vcb : p_codeBlocks) {
         if (!isPreviewLang(vcb.m_lang)) {
             continue;
@@ -165,6 +160,7 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
         if (m_inplacePreviewEnabled
             && !m_codeBlocks[idx].inplacePreviewReady()) {
             processForInplacePreview(idx);
+            manualInplacePreview = false;
         }
 
         if (m_livePreviewEnabled
@@ -180,9 +176,17 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector<VCodeBlock> &p_codeBlock
         ++idx;
     }
 
-    m_codeBlocks.resize(idx);
+    if (idx == m_codeBlocks.size()) {
+        manualInplacePreview = false;
+    } else {
+        m_codeBlocks.resize(idx);
+    }
+
+    if (manualInplacePreview) {
+        updateInplacePreview();
+    }
 
-    if (m_livePreviewEnabled && needUpdate) {
+    if (needUpdate) {
         updateLivePreview();
     }
 }
@@ -283,8 +287,12 @@ void VLivePreviewHelper::setLivePreviewEnabled(bool p_enabled)
     m_livePreviewEnabled = p_enabled;
     if (!m_livePreviewEnabled) {
         m_cbIndex = -1;
-        m_codeBlocks.clear();
         m_document->previewCodeBlock(-1, "", "", true);
+
+        if (!m_inplacePreviewEnabled) {
+            m_codeBlocks.clear();
+            updateInplacePreview();
+        }
     }
 }
 
@@ -295,13 +303,11 @@ void VLivePreviewHelper::setInplacePreviewEnabled(bool p_enabled)
     }
 
     m_inplacePreviewEnabled = p_enabled;
-    if (!m_livePreviewEnabled) {
-        for (auto & cb : m_codeBlocks) {
-            cb.clearImageData();
-        }
-
-        updateInplacePreview();
+    if (!m_inplacePreviewEnabled && !m_livePreviewEnabled) {
+        m_codeBlocks.clear();
     }
+
+    updateInplacePreview();
 }
 
 void VLivePreviewHelper::localAsyncResultReady(int p_id,
@@ -356,6 +362,7 @@ void VLivePreviewHelper::processForInplacePreview(int p_idx)
 {
     CodeBlockPreviewInfo &cb = m_codeBlocks[p_idx];
     const VCodeBlock &vcb = cb.codeBlock();
+    Q_ASSERT(!(cb.hasImageData() || cb.hasImageDataBa()));
     if (vcb.m_lang == "dot") {
         if (!m_graphvizHelper) {
             m_graphvizHelper = new VGraphvizHelper(this);
@@ -363,15 +370,10 @@ void VLivePreviewHelper::processForInplacePreview(int p_idx)
                     this, &VLivePreviewHelper::localAsyncResultReady);
         }
 
-        if (cb.hasImageData()) {
-            cb.updateInplacePreview(m_editor, m_doc);
-            updateInplacePreview();
-        } else {
-            m_graphvizHelper->processAsync(p_idx | LANG_PREFIX_GRAPHVIZ | TYPE_INPLACE_PREVIEW,
-                                           m_timeStamp,
-                                           "svg",
-                                           removeFence(vcb.m_text));
-        }
+        m_graphvizHelper->processAsync(p_idx | LANG_PREFIX_GRAPHVIZ | TYPE_INPLACE_PREVIEW,
+                                       m_timeStamp,
+                                       "svg",
+                                       removeFence(vcb.m_text));
     } else if (vcb.m_lang == "puml" && m_plantUMLMode == PlantUMLMode::LocalPlantUML) {
         if (!m_plantUMLHelper) {
             m_plantUMLHelper = new VPlantUMLHelper(this);
@@ -379,15 +381,10 @@ void VLivePreviewHelper::processForInplacePreview(int p_idx)
                     this, &VLivePreviewHelper::localAsyncResultReady);
         }
 
-        if (cb.hasImageData()) {
-            cb.updateInplacePreview(m_editor, m_doc);
-            updateInplacePreview();
-        } else {
-            m_plantUMLHelper->processAsync(p_idx | LANG_PREFIX_PLANTUML | TYPE_INPLACE_PREVIEW,
-                                           m_timeStamp,
-                                           "svg",
-                                           removeFence(vcb.m_text));
-        }
+        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_mathJaxHelper->previewDiagram(m_mathJaxID,

+ 8 - 3
src/vlivepreviewhelper.h

@@ -21,9 +21,15 @@ public:
 
     explicit CodeBlockPreviewInfo(const VCodeBlock &p_cb);
 
-    void clearImageData();
+    void clearImageData()
+    {
+        m_imgData.clear();
+        m_imgDataBa.clear();
+        m_inplacePreview.clear();
+    }
 
-    void updateNonContent(const QTextDocument *p_doc, const VCodeBlock &p_cb);
+    void updateNonContent(const QTextDocument *p_doc,
+                          const VCodeBlock &p_cb);
 
     void updateInplacePreview(const VEditor *p_editor, const QTextDocument *p_doc);
 
@@ -151,7 +157,6 @@ private slots:
                                    const QByteArray &p_data);
 
 private:
-
     bool isPreviewLang(const QString &p_lang) const;
 
     // Get image data for this code block for inplace preview.

+ 242 - 0
src/vmathjaxinplacepreviewhelper.cpp

@@ -0,0 +1,242 @@
+#include "vmathjaxinplacepreviewhelper.h"
+
+#include <QDebug>
+
+#include "veditor.h"
+#include "vdocument.h"
+#include "vmainwindow.h"
+#include "veditarea.h"
+#include "vmathjaxpreviewhelper.h"
+
+extern VMainWindow *g_mainWin;
+
+MathjaxBlockPreviewInfo::MathjaxBlockPreviewInfo()
+{
+}
+
+MathjaxBlockPreviewInfo::MathjaxBlockPreviewInfo(const VMathjaxBlock &p_mb)
+    : m_mathjaxBlock(p_mb)
+{
+}
+
+void MathjaxBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc,
+                                               const VEditor *p_editor,
+                                               const VMathjaxBlock &p_mb)
+{
+    m_mathjaxBlock.updateNonContent(p_mb);
+    if (m_inplacePreview.isNull()) {
+        return;
+    }
+
+    QTextBlock block = p_doc->findBlockByNumber(m_mathjaxBlock.m_blockNumber);
+    if (block.isValid()) {
+        m_inplacePreview->m_startPos = block.position() + m_mathjaxBlock.m_index;
+        m_inplacePreview->m_endPos = m_inplacePreview->m_startPos + m_mathjaxBlock.m_length;
+        m_inplacePreview->m_blockPos = block.position();
+        m_inplacePreview->m_blockNumber = m_mathjaxBlock.m_blockNumber;
+        // Padding may changed.
+        m_inplacePreview->m_padding = VPreviewManager::calculateBlockMargin(block,
+                                                                            p_editor->tabStopWidthW());
+        m_inplacePreview->m_isBlock = m_mathjaxBlock.m_previewedAsBlock;
+    } else {
+        m_inplacePreview->clear();
+    }
+}
+
+void MathjaxBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor,
+                                                   const QTextDocument *p_doc)
+{
+    QTextBlock block = p_doc->findBlockByNumber(m_mathjaxBlock.m_blockNumber);
+    if (block.isValid()) {
+        if (m_inplacePreview.isNull()) {
+            m_inplacePreview.reset(new VImageToPreview());
+        }
+
+        m_inplacePreview->m_startPos = block.position() + m_mathjaxBlock.m_index;
+        m_inplacePreview->m_endPos = m_inplacePreview->m_startPos + m_mathjaxBlock.m_length;
+        m_inplacePreview->m_blockPos = block.position();
+        m_inplacePreview->m_blockNumber = m_mathjaxBlock.m_blockNumber;
+        m_inplacePreview->m_padding = VPreviewManager::calculateBlockMargin(block,
+                                                                            p_editor->tabStopWidthW());
+        m_inplacePreview->m_name = QString::number(getImageIndex());
+        m_inplacePreview->m_isBlock = m_mathjaxBlock.m_previewedAsBlock;
+
+        if (hasImageDataBa()) {
+            m_inplacePreview->m_image.loadFromData(m_imgDataBa,
+                                                   m_imgFormat.toLocal8Bit().data());
+        } else {
+            m_inplacePreview->m_image = QPixmap();
+        }
+    } else {
+        m_inplacePreview->clear();
+    }
+}
+
+VMathJaxInplacePreviewHelper::VMathJaxInplacePreviewHelper(VEditor *p_editor,
+                                                           VDocument *p_document,
+                                                           QObject *p_parent)
+    : QObject(p_parent),
+      m_editor(p_editor),
+      m_document(p_document),
+      m_doc(p_editor->documentW()),
+      m_enabled(false),
+      m_lastInplacePreviewSize(0),
+      m_timeStamp(0)
+{
+    m_mathJaxHelper = g_mainWin->getEditArea()->getMathJaxPreviewHelper();
+    m_mathJaxID = m_mathJaxHelper->registerIdentifier();
+    connect(m_mathJaxHelper, &VMathJaxPreviewHelper::mathjaxPreviewResultReady,
+            this, &VMathJaxInplacePreviewHelper::mathjaxPreviewResultReady);
+
+    m_documentID = m_document->registerIdentifier();
+    connect(m_document, &VDocument::textToHtmlFinished,
+            this, &VMathJaxInplacePreviewHelper::textToHtmlFinished);
+}
+
+void VMathJaxInplacePreviewHelper::setEnabled(bool p_enabled)
+{
+    if (m_enabled != p_enabled) {
+        m_enabled = p_enabled;
+
+        if (!m_enabled) {
+            m_mathjaxBlocks.clear();
+        }
+
+        updateInplacePreview();
+    }
+}
+
+void VMathJaxInplacePreviewHelper::updateMathjaxBlocks(const QVector<VMathjaxBlock> &p_blocks)
+{
+    if (!m_enabled) {
+        return;
+    }
+
+    ++m_timeStamp;
+
+    int idx = 0;
+    bool manualUpdate = true;
+    for (auto const & vmb : p_blocks) {
+        if (idx < m_mathjaxBlocks.size()) {
+            MathjaxBlockPreviewInfo &mb = m_mathjaxBlocks[idx];
+            if (mb.mathjaxBlock().equalContent(vmb)) {
+                mb.updateNonContent(m_doc, m_editor, vmb);
+            } else {
+                mb.setMathjaxBlock(vmb);
+            }
+        } else {
+            m_mathjaxBlocks.append(MathjaxBlockPreviewInfo(vmb));
+        }
+
+        if (m_enabled
+            && !m_mathjaxBlocks[idx].inplacePreviewReady()) {
+            manualUpdate = false;
+            processForInplacePreview(idx);
+        }
+
+        ++idx;
+    }
+
+    m_mathjaxBlocks.resize(idx);
+
+    if (manualUpdate) {
+        updateInplacePreview();
+    }
+}
+
+void VMathJaxInplacePreviewHelper::processForInplacePreview(int p_idx)
+{
+    MathjaxBlockPreviewInfo &mb = m_mathjaxBlocks[p_idx];
+    const VMathjaxBlock &vmb = mb.mathjaxBlock();
+    if (vmb.m_text.isEmpty()) {
+        updateInplacePreview();
+    } else {
+        textToHtmlViaWebView(vmb.m_text, p_idx, m_timeStamp);
+    }
+}
+
+void VMathJaxInplacePreviewHelper::textToHtmlViaWebView(const QString &p_text,
+                                                        int p_id,
+                                                        int p_timeStamp)
+{
+    int maxRetry = 50;
+    while (!m_document->isReadyToTextToHtml() && maxRetry > 0) {
+        qDebug() << "wait for web side ready to convert text to HTML";
+        VUtils::sleepWait(100);
+        --maxRetry;
+    }
+
+    if (maxRetry == 0) {
+        qWarning() << "web side is not ready to convert text to HTML";
+        return;
+    }
+
+    m_document->textToHtmlAsync(m_documentID, p_id, p_timeStamp, p_text, false);
+}
+
+void VMathJaxInplacePreviewHelper::updateInplacePreview()
+{
+    QSet<int> blocks;
+    QVector<QSharedPointer<VImageToPreview> > images;
+    for (int i = 0; i < m_mathjaxBlocks.size(); ++i) {
+        MathjaxBlockPreviewInfo &mb = m_mathjaxBlocks[i];
+        if (mb.inplacePreviewReady()) {
+            if (!mb.inplacePreview()->m_image.isNull()) {
+                images.append(mb.inplacePreview());
+            } else {
+                blocks.insert(mb.inplacePreview()->m_blockNumber);
+            }
+        } else {
+            blocks.insert(mb.mathjaxBlock().m_blockNumber);
+        }
+    }
+
+    if (images.isEmpty() && m_lastInplacePreviewSize == 0) {
+        return;
+    }
+
+    emit inplacePreviewMathjaxBlockUpdated(images);
+
+    m_lastInplacePreviewSize = images.size();
+
+    if (!blocks.isEmpty()) {
+        emit checkBlocksForObsoletePreview(blocks.toList());
+    }
+}
+
+void VMathJaxInplacePreviewHelper::mathjaxPreviewResultReady(int p_identitifer,
+                                                             int p_id,
+                                                             TimeStamp p_timeStamp,
+                                                             const QString &p_format,
+                                                             const QByteArray &p_data)
+{
+    if (p_identitifer != m_mathJaxID || p_timeStamp != m_timeStamp) {
+        return;
+    }
+
+    if (p_id >= m_mathjaxBlocks.size() || p_data.isEmpty()) {
+        updateInplacePreview();
+        return;
+    }
+
+    MathjaxBlockPreviewInfo &mb = m_mathjaxBlocks[p_id];
+    mb.setImageDataBa(p_format, p_data);
+    mb.updateInplacePreview(m_editor, m_doc);
+    updateInplacePreview();
+}
+
+void VMathJaxInplacePreviewHelper::textToHtmlFinished(int p_identitifer,
+                                                      int p_id,
+                                                      int p_timeStamp,
+                                                      const QString &p_html)
+{
+    if (m_documentID != p_identitifer || m_timeStamp != p_timeStamp) {
+        return;
+    }
+
+    Q_ASSERT(p_html.startsWith("<"));
+    m_mathJaxHelper->previewMathJaxFromHtml(m_mathJaxID,
+                                            p_id,
+                                            p_timeStamp,
+                                            p_html);
+}

+ 147 - 0
src/vmathjaxinplacepreviewhelper.h

@@ -0,0 +1,147 @@
+#ifndef VMATHJAXINPLACEPREVIEWHELPER_H
+#define VMATHJAXINPLACEPREVIEWHELPER_H
+
+#include <QObject>
+
+#include "hgmarkdownhighlighter.h"
+#include "vpreviewmanager.h"
+#include "vconstants.h"
+
+class VEditor;
+class VDocument;
+class QTextDocument;
+class VMathJaxPreviewHelper;
+
+class MathjaxBlockPreviewInfo
+{
+public:
+    MathjaxBlockPreviewInfo();
+
+    explicit MathjaxBlockPreviewInfo(const VMathjaxBlock &p_mb);
+
+    void clearImageData()
+    {
+        m_imgDataBa.clear();
+        m_inplacePreview.clear();
+    }
+
+    void updateNonContent(const QTextDocument *p_doc,
+                          const VEditor *p_editor,
+                          const VMathjaxBlock &p_mb);
+
+    void updateInplacePreview(const VEditor *p_editor, const QTextDocument *p_doc);
+
+    VMathjaxBlock &mathjaxBlock()
+    {
+        return m_mathjaxBlock;
+    }
+
+    const VMathjaxBlock &mathjaxBlock() const
+    {
+        return m_mathjaxBlock;
+    }
+
+    void setMathjaxBlock(const VMathjaxBlock &p_mb)
+    {
+        m_mathjaxBlock = p_mb;
+        clearImageData();
+    }
+
+    bool inplacePreviewReady() const
+    {
+        return !m_inplacePreview.isNull();
+    }
+
+    void setImageDataBa(const QString &p_format, const QByteArray &p_data)
+    {
+        m_imgFormat = p_format;
+        m_imgDataBa = p_data;
+    }
+
+    bool hasImageDataBa() const
+    {
+        return !m_imgDataBa.isEmpty();
+    }
+
+    const QSharedPointer<VImageToPreview> inplacePreview() const
+    {
+        return m_inplacePreview;
+    }
+
+private:
+    static int getImageIndex()
+    {
+        static int index = 0;
+        return ++index;
+    }
+
+    VMathjaxBlock m_mathjaxBlock;
+
+    QByteArray m_imgDataBa;
+
+    QString m_imgFormat;
+
+    QSharedPointer<VImageToPreview> m_inplacePreview;
+};
+
+class VMathJaxInplacePreviewHelper : public QObject
+{
+    Q_OBJECT
+public:
+    VMathJaxInplacePreviewHelper(VEditor *p_editor,
+                                 VDocument *p_document,
+                                 QObject *p_parent = nullptr);
+
+    void setEnabled(bool p_enabled);
+
+public slots:
+    void updateMathjaxBlocks(const QVector<VMathjaxBlock> &p_blocks);
+
+signals:
+    void inplacePreviewMathjaxBlockUpdated(const QVector<QSharedPointer<VImageToPreview> > &p_images);
+
+    void checkBlocksForObsoletePreview(const QList<int> &p_blocks);
+
+private slots:
+    void mathjaxPreviewResultReady(int p_identitifer,
+                                   int p_id,
+                                   TimeStamp p_timeStamp,
+                                   const QString &p_format,
+                                   const QByteArray &p_data);
+
+    void textToHtmlFinished(int p_identitifer, int p_id, int p_timeStamp, const QString &p_html);
+
+private:
+    void processForInplacePreview(int p_idx);
+
+    // Emit signal to update inplace preview.
+    void updateInplacePreview();
+
+    void textToHtmlViaWebView(const QString &p_text,
+                              int p_id,
+                              int p_timeStamp);
+
+    VEditor *m_editor;
+
+    VDocument *m_document;
+
+    QTextDocument *m_doc;
+
+    bool m_enabled;
+
+    VMathJaxPreviewHelper *m_mathJaxHelper;
+
+    // Identification for VMathJaxPreviewHelper.
+    int m_mathJaxID;
+
+    int m_lastInplacePreviewSize;
+
+    TimeStamp m_timeStamp;
+
+    // Sorted by m_blockNumber in ascending order.
+    QVector<MathjaxBlockPreviewInfo> m_mathjaxBlocks;
+
+    int m_documentID;
+};
+
+#endif // VMATHJAXINPLACEPREVIEWHELPER_H

+ 61 - 8
src/vmathjaxpreviewhelper.cpp

@@ -1,8 +1,8 @@
 #include "vmathjaxpreviewhelper.h"
 
-#include <QDebug>
 #include <QWebEngineView>
 #include <QWebChannel>
+#include <QApplication>
 
 #include "utils/vutils.h"
 #include "vmathjaxwebdocument.h"
@@ -24,17 +24,31 @@ VMathJaxPreviewHelper::~VMathJaxPreviewHelper()
 void VMathJaxPreviewHelper::doInit()
 {
     Q_ASSERT(!m_initialized);
+    Q_ASSERT(m_parentWidget);
+
     m_initialized = true;
 
+    QWidget *focusWid = QApplication::focusWidget();
+
     m_webView = new QWebEngineView(m_parentWidget);
     connect(m_webView, &QWebEngineView::loadFinished,
             this, [this]() {
                 m_webReady = true;
-            });
+                for (auto const & it : m_pendingFunc) {
+                    it();
+                }
 
+                m_pendingFunc.clear();
+            });
     m_webView->hide();
     m_webView->setFocusPolicy(Qt::NoFocus);
 
+    if (focusWid) {
+        focusWid->setFocus();
+    } else {
+        m_parentWidget->setFocus();
+    }
+
     m_webDoc = new VMathJaxWebDocument(m_webView);
     connect(m_webDoc, &VMathJaxWebDocument::mathjaxPreviewResultReady,
             this, [this](int p_identifier,
@@ -61,10 +75,6 @@ void VMathJaxPreviewHelper::doInit()
     m_webView->page()->setWebChannel(channel);
 
     m_webView->setHtml(VUtils::generateMathJaxPreviewTemplate(), QUrl("qrc:/resources"));
-
-    while (!m_webReady) {
-        VUtils::sleepWait(100);
-    }
 }
 
 void VMathJaxPreviewHelper::previewMathJax(int p_identifier,
@@ -74,7 +84,39 @@ void VMathJaxPreviewHelper::previewMathJax(int p_identifier,
 {
     init();
 
-    m_webDoc->previewMathJax(p_identifier, p_id, p_timeStamp, p_text);
+    if (!m_webReady) {
+        auto func = std::bind(&VMathJaxWebDocument::previewMathJax,
+                              m_webDoc,
+                              p_identifier,
+                              p_id,
+                              p_timeStamp,
+                              p_text,
+                              false);
+        m_pendingFunc.append(func);
+    } else {
+        m_webDoc->previewMathJax(p_identifier, p_id, p_timeStamp, p_text, false);
+    }
+}
+
+void VMathJaxPreviewHelper::previewMathJaxFromHtml(int p_identifier,
+                                                   int p_id,
+                                                   TimeStamp p_timeStamp,
+                                                   const QString &p_html)
+{
+    init();
+
+    if (!m_webReady) {
+        auto func = std::bind(&VMathJaxWebDocument::previewMathJax,
+                              m_webDoc,
+                              p_identifier,
+                              p_id,
+                              p_timeStamp,
+                              p_html,
+                              true);
+        m_pendingFunc.append(func);
+    } else {
+        m_webDoc->previewMathJax(p_identifier, p_id, p_timeStamp, p_html, true);
+    }
 }
 
 void VMathJaxPreviewHelper::previewDiagram(int p_identifier,
@@ -85,5 +127,16 @@ void VMathJaxPreviewHelper::previewDiagram(int p_identifier,
 {
     init();
 
-    m_webDoc->previewDiagram(p_identifier, p_id, p_timeStamp, p_lang, p_text);
+    if (!m_webReady) {
+        auto func = std::bind(&VMathJaxWebDocument::previewDiagram,
+                              m_webDoc,
+                              p_identifier,
+                              p_id,
+                              p_timeStamp,
+                              p_lang,
+                              p_text);
+        m_pendingFunc.append(func);
+    } else {
+        m_webDoc->previewDiagram(p_identifier, p_id, p_timeStamp, p_lang, p_text);
+    }
 }

+ 8 - 0
src/vmathjaxpreviewhelper.h

@@ -2,6 +2,8 @@
 #define VMATHJAXPREVIEWHELPER_H
 
 #include <QObject>
+#include <functional>
+#include <QVector>
 
 #include "vconstants.h"
 
@@ -9,6 +11,8 @@ class QWebEngineView;
 class VMathJaxWebDocument;
 class QWidget;
 
+typedef std::function<void(void)> PendingFunc;
+
 class VMathJaxPreviewHelper : public QObject
 {
     Q_OBJECT
@@ -26,6 +30,8 @@ public:
     // @p_text: raw text of the MathJax script.
     void previewMathJax(int p_identifier, int p_id, TimeStamp p_timeStamp, const QString &p_text);
 
+    void previewMathJaxFromHtml(int p_identitifer, int p_id, TimeStamp p_timeStamp, const QString &p_html);
+
     // Preview @p_text and return PNG data asynchronously.
     // @p_identifier: identifier the caller registered;
     // @p_id: internal id for each caller;
@@ -66,6 +72,8 @@ private:
     VMathJaxWebDocument *m_webDoc;
 
     bool m_webReady;
+
+    QVector<PendingFunc> m_pendingFunc;
 };
 
 inline int VMathJaxPreviewHelper::registerIdentifier()

+ 3 - 4
src/vmathjaxwebdocument.cpp

@@ -1,7 +1,5 @@
 #include "vmathjaxwebdocument.h"
 
-#include <QDebug>
-
 VMathJaxWebDocument::VMathJaxWebDocument(QObject *p_parent)
     : QObject(p_parent)
 {
@@ -10,9 +8,10 @@ VMathJaxWebDocument::VMathJaxWebDocument(QObject *p_parent)
 void VMathJaxWebDocument::previewMathJax(int p_identifier,
                                          int p_id,
                                          TimeStamp p_timeStamp,
-                                         const QString &p_text)
+                                         const QString &p_text,
+                                         bool p_isHtml)
 {
-    emit requestPreviewMathJax(p_identifier, p_id, p_timeStamp, p_text);
+    emit requestPreviewMathJax(p_identifier, p_id, p_timeStamp, p_text, p_isHtml);
 }
 
 void VMathJaxWebDocument::mathjaxResultReady(int p_identifier,

+ 7 - 2
src/vmathjaxwebdocument.h

@@ -11,7 +11,11 @@ class VMathJaxWebDocument : public QObject
 public:
     explicit VMathJaxWebDocument(QObject *p_parent = nullptr);
 
-    void previewMathJax(int p_identifier, int p_id, TimeStamp p_timeStamp, const QString &p_text);
+    void previewMathJax(int p_identifier,
+                        int p_id,
+                        TimeStamp p_timeStamp,
+                        const QString &p_text,
+                        bool p_isHtml);
 
     void previewDiagram(int p_identifier,
                         int p_id,
@@ -38,7 +42,8 @@ signals:
     void requestPreviewMathJax(int p_identifier,
                                int p_id,
                                unsigned long long p_timeStamp,
-                               const QString &p_text);
+                               const QString &p_text,
+                               bool p_isHtml);
 
     void requestPreviewDiagram(int p_identifier,
                                int p_id,

+ 9 - 4
src/vmdeditor.cpp

@@ -39,7 +39,8 @@ VMdEditor::VMdEditor(VFile *p_file,
       m_freshEdit(true),
       m_textToHtmlDialog(NULL),
       m_zoomDelta(0),
-      m_editTab(NULL)
+      m_editTab(NULL),
+      m_copyTimeStamp(0)
 {
     Q_ASSERT(p_file->getDocType() == DocType::Markdown);
 
@@ -1124,6 +1125,8 @@ void VMdEditor::updateInitAndInsertedImages(bool p_fileChanged, UpdateAction p_a
 
 void VMdEditor::handleCopyAsAction(QAction *p_act)
 {
+    ++m_copyTimeStamp;
+
     QTextCursor cursor = textCursor();
     Q_ASSERT(cursor.hasSelection());
 
@@ -1134,7 +1137,7 @@ void VMdEditor::handleCopyAsAction(QAction *p_act)
     m_textToHtmlDialog = new VCopyTextAsHtmlDialog(text, p_act->data().toString(), this);
 
     // For Hoedown, we use marked.js to convert the text to have a general interface.
-    emit requestTextToHtml(text);
+    emit requestTextToHtml(text, 0, m_copyTimeStamp);
 
     m_textToHtmlDialog->exec();
 
@@ -1142,11 +1145,13 @@ void VMdEditor::handleCopyAsAction(QAction *p_act)
     m_textToHtmlDialog = NULL;
 }
 
-void VMdEditor::textToHtmlFinished(const QString &p_text,
+void VMdEditor::textToHtmlFinished(int p_id,
+                                   int p_timeStamp,
                                    const QUrl &p_baseUrl,
                                    const QString &p_html)
 {
-    if (m_textToHtmlDialog && m_textToHtmlDialog->getText() == p_text) {
+    Q_UNUSED(p_id);
+    if (m_textToHtmlDialog && p_timeStamp == m_copyTimeStamp) {
         m_textToHtmlDialog->setConvertedHtml(p_baseUrl, p_html);
     }
 }

+ 4 - 2
src/vmdeditor.h

@@ -79,7 +79,7 @@ public:
 public slots:
     bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
 
-    void textToHtmlFinished(const QString &p_text, const QUrl &p_baseUrl, const QString &p_html);
+    void textToHtmlFinished(int p_id, int p_timeStamp, const QUrl &p_baseUrl, const QString &p_html);
 
 // Wrapper functions for QPlainTextEdit/QTextEdit.
 public:
@@ -194,7 +194,7 @@ signals:
     void statusChanged();
 
     // Request to convert @p_text to Html.
-    void requestTextToHtml(const QString &p_text);
+    void requestTextToHtml(const QString &p_text, int p_id, int p_timeStamp);
 
 protected:
     void updateFontAndPalette() Q_DECL_OVERRIDE;
@@ -274,6 +274,8 @@ private:
     int m_zoomDelta;
 
     VEditTab *m_editTab;
+
+    int m_copyTimeStamp;
 };
 
 inline HGMarkdownHighlighter *VMdEditor::getMarkdownHighlighter() const

+ 23 - 5
src/vmdtab.cpp

@@ -23,6 +23,7 @@
 #include "vinsertselector.h"
 #include "vsnippetlist.h"
 #include "vlivepreviewhelper.h"
+#include "vmathjaxinplacepreviewhelper.h"
 
 extern VMainWindow *g_mainWin;
 
@@ -39,7 +40,8 @@ VMdTab::VMdTab(VFile *p_file, VEditArea *p_editArea,
       m_enableHeadingSequence(false),
       m_backupFileChecked(false),
       m_mode(Mode::InvalidMode),
-      m_livePreviewHelper(NULL)
+      m_livePreviewHelper(NULL),
+      m_mathjaxPreviewHelper(NULL)
 {
     V_ASSERT(m_file->getDocType() == DocType::Markdown);
 
@@ -412,6 +414,7 @@ void VMdTab::setupMarkdownViewer()
     page->setBackgroundColor(Qt::transparent);
 
     m_document = new VDocument(m_file, m_webViewer);
+    m_documentID = m_document->registerIdentifier();
 
     QWebChannel *channel = new QWebChannel(m_webViewer);
     channel->registerObject(QStringLiteral("content"), m_document);
@@ -435,9 +438,13 @@ void VMdTab::setupMarkdownViewer()
                 tabIsReady(TabReady::ReadMode);
             });
     connect(m_document, &VDocument::textToHtmlFinished,
-            this, [this](const QString &p_text, const QString &p_html) {
+            this, [this](int p_identitifer, int p_id, int p_timeStamp, const QString &p_html) {
                 Q_ASSERT(m_editor);
-                m_editor->textToHtmlFinished(p_text, m_webViewer->url(), p_html);
+                if (m_documentID != p_identitifer) {
+                    return;
+                }
+
+                m_editor->textToHtmlFinished(p_id, p_timeStamp, m_webViewer->url(), p_html);
             });
     connect(m_document, &VDocument::wordCountInfoUpdated,
             this, [this]() {
@@ -526,6 +533,17 @@ void VMdTab::setupMarkdownEditor()
     connect(m_livePreviewHelper, &VLivePreviewHelper::checkBlocksForObsoletePreview,
             m_editor->getPreviewManager(), &VPreviewManager::checkBlocksForObsoletePreview);
     m_livePreviewHelper->setInplacePreviewEnabled(m_editor->getPreviewManager()->isPreviewEnabled());
+
+    m_mathjaxPreviewHelper = new VMathJaxInplacePreviewHelper(m_editor, m_document, this);
+    connect(m_editor->getMarkdownHighlighter(), &HGMarkdownHighlighter::mathjaxBlocksUpdated,
+            m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::updateMathjaxBlocks);
+    connect(m_editor->getPreviewManager(), &VPreviewManager::previewEnabledChanged,
+            m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::setEnabled);
+    connect(m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::inplacePreviewMathjaxBlockUpdated,
+            m_editor->getPreviewManager(), &VPreviewManager::updateMathjaxBlocks);
+    connect(m_mathjaxPreviewHelper, &VMathJaxInplacePreviewHelper::checkBlocksForObsoletePreview,
+            m_editor->getPreviewManager(), &VPreviewManager::checkBlocksForObsoletePreview);
+    m_mathjaxPreviewHelper->setEnabled(m_editor->getPreviewManager()->isPreviewEnabled());
 }
 
 void VMdTab::updateOutlineFromHtml(const QString &p_tocHtml)
@@ -1129,7 +1147,7 @@ void VMdTab::handleFileOrDirectoryChange(bool p_isFile, UpdateAction p_act)
     }
 }
 
-void VMdTab::textToHtmlViaWebView(const QString &p_text)
+void VMdTab::textToHtmlViaWebView(const QString &p_text, int p_id, int p_timeStamp)
 {
     int maxRetry = 50;
     while (!m_document->isReadyToTextToHtml() && maxRetry > 0) {
@@ -1143,7 +1161,7 @@ void VMdTab::textToHtmlViaWebView(const QString &p_text)
         return;
     }
 
-    m_document->textToHtmlAsync(p_text);
+    m_document->textToHtmlAsync(m_documentID, p_id, p_timeStamp, p_text, true);
 }
 
 void VMdTab::handleVimCmdCommandCancelled()

+ 5 - 1
src/vmdtab.h

@@ -17,6 +17,7 @@ class QTimer;
 class QWebEngineDownloadItem;
 class QSplitter;
 class VLivePreviewHelper;
+class VMathJaxInplacePreviewHelper;
 
 class VMdTab : public VEditTab
 {
@@ -218,7 +219,7 @@ private:
     // updateStatus() with only cursor position information.
     void updateCursorStatus();
 
-    void textToHtmlViaWebView(const QString &p_text);
+    void textToHtmlViaWebView(const QString &p_text, int p_id, int p_timeStamp);
 
     bool executeVimCommandInWebView(const QString &p_cmd);
 
@@ -253,6 +254,9 @@ private:
     QSharedPointer<WebViewState> m_previewWebViewState;
 
     VLivePreviewHelper *m_livePreviewHelper;
+    VMathJaxInplacePreviewHelper *m_mathjaxPreviewHelper;
+
+    int m_documentID;
 };
 
 inline VMdEditor *VMdTab::getEditor()

+ 19 - 4
src/vpreviewmanager.cpp

@@ -258,9 +258,10 @@ QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
     return name;
 }
 
-QString VPreviewManager::imageResourceNameFromCodeBlock(const QSharedPointer<VImageToPreview> &p_image)
+QString VPreviewManager::imageResourceNameForSource(PreviewSource p_source,
+                                                    const QSharedPointer<VImageToPreview> &p_image)
 {
-    QString name = "CODE_BLOCK_" + p_image->m_name;
+    QString name = QString::number((int)p_source) + "_" + p_image->m_name;
     if (m_editor->containsImage(name)) {
         return name;
     }
@@ -371,7 +372,7 @@ void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
             continue;
         }
 
-        QString name = imageResourceNameFromCodeBlock(img);
+        QString name = imageResourceNameForSource(p_source, img);
         if (name.isEmpty()) {
             continue;
         }
@@ -420,7 +421,6 @@ void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp,
     QSet<int> affectedBlocks;
     QVector<int> obsoleteBlocks;
     const QSet<int> &blocks = m_highlighter->getPossiblePreviewBlocks();
-    qDebug() << "possible preview blocks" << blocks;
     for (auto i : blocks) {
         QTextBlock block = m_document->findBlockByNumber(i);
         if (!block.isValid()) {
@@ -474,6 +474,21 @@ void VPreviewManager::updateCodeBlocks(const QVector<QSharedPointer<VImageToPrev
     clearObsoleteImages(ts, PreviewSource::CodeBlock);
 }
 
+void VPreviewManager::updateMathjaxBlocks(const QVector<QSharedPointer<VImageToPreview> > &p_images)
+{
+    if (!m_previewEnabled) {
+        return;
+    }
+
+    TS ts = ++timeStamp(PreviewSource::MathjaxBlock);
+
+    updateBlockPreviewInfo(ts, PreviewSource::MathjaxBlock, p_images);
+
+    clearBlockObsoletePreviewInfo(ts, PreviewSource::MathjaxBlock);
+
+    clearObsoleteImages(ts, PreviewSource::MathjaxBlock);
+}
+
 void VPreviewManager::checkBlocksForObsoletePreview(const QList<int> &p_blocks)
 {
     if (p_blocks.isEmpty()) {

+ 3 - 1
src/vpreviewmanager.h

@@ -80,6 +80,8 @@ public slots:
 
     void updateCodeBlocks(const QVector<QSharedPointer<VImageToPreview> > &p_images);
 
+    void updateMathjaxBlocks(const QVector<QSharedPointer<VImageToPreview> > &p_images);
+
 signals:
     // Request highlighter to update image links.
     void requestUpdateImageLinks();
@@ -168,7 +170,7 @@ private:
     // Returns empty if fail to add the image to the resource manager.
     QString imageResourceName(const ImageLinkInfo &p_link);
 
-    QString imageResourceNameFromCodeBlock(const QSharedPointer<VImageToPreview> &p_image);
+    QString imageResourceNameForSource(PreviewSource p_source, const QSharedPointer<VImageToPreview> &p_image);
 
     QHash<QString, long long> &imageCache(PreviewSource p_source);
 

+ 0 - 2
src/vtextblockdata.cpp

@@ -35,13 +35,11 @@ bool VTextBlockData::insertPreviewInfo(VPreviewInfo *p_info)
             *it = p_info;
             inserted = true;
             tsUpdated = true;
-            qDebug() << "update eixsting image's timestamp" << p_info->m_imageInfo.toString();
             break;
         } else if (p_info->m_imageInfo.intersect(ele->m_imageInfo)) {
             // The new one intersect with an old one.
             // Remove the old one.
             Q_ASSERT(ele->m_timeStamp < p_info->m_timeStamp);
-            qDebug() << "remove intersecting old image" << ele->m_imageInfo.toString();
             delete ele;
             it = m_previews.erase(it);
         } else {

+ 24 - 8
src/vtextblockdata.h

@@ -9,6 +9,7 @@ enum class PreviewSource
 {
     ImageLink = 0,
     CodeBlock,
+    MathjaxBlock,
     MaxNumberOfSources
 };
 
@@ -133,7 +134,7 @@ struct MathjaxInfo
 {
 public:
     MathjaxInfo()
-        : m_isBlock(false),
+        : m_previewedAsBlock(false),
           m_index(-1),
           m_length(0)
     {
@@ -145,26 +146,41 @@ public:
         return m_index >= 0 && m_length > 0;
     }
 
-    bool isBlock() const
+    bool previewedAsBlock() const
     {
-        return m_isBlock;
+        return m_previewedAsBlock;
     }
 
     void clear()
     {
-        m_isBlock = false;
+        m_previewedAsBlock = false;
         m_index = -1;
         m_length = 0;
     }
 
-    // Inline or block formula.
-    bool m_isBlock;
+    const QString &text() const
+    {
+        return m_text;
+    }
+
+    QString toString() const
+    {
+        return QString("MathjaxInfo %1 (%2,%3) %4").arg(m_previewedAsBlock)
+                                                   .arg(m_index)
+                                                   .arg(m_length)
+                                                   .arg(m_text);
+    }
+
+    // Whether it should be previewed as block or not.
+    bool m_previewedAsBlock;
 
     // Start index wihtin block, including the start mark.
     int m_index;
 
     // Length of this mathjax, including the end mark.
     int m_length;
+
+    QString m_text;
 };
 
 
@@ -198,7 +214,7 @@ public:
 
     void setPendingMathjax(const MathjaxInfo &p_info);
 
-    const QVector<MathjaxInfo> getMathjax() const;
+    const QVector<MathjaxInfo> &getMathjax() const;
 
     void addMathjax(const MathjaxInfo &p_info);
 
@@ -250,7 +266,7 @@ inline void VTextBlockData::setPendingMathjax(const MathjaxInfo &p_info)
     m_pendingMathjax = p_info;
 }
 
-inline const QVector<MathjaxInfo> VTextBlockData::getMathjax() const
+inline const QVector<MathjaxInfo> &VTextBlockData::getMathjax() const
 {
     return m_mathjax;
 }