support Mermaid and Flowchart.js preview

Mermaid preview is disabled for some issues.
This commit is contained in:
Le Tan 2018-04-11 19:53:42 +08:00
parent 8239abec2a
commit 10e2bba7f6
29 changed files with 418 additions and 76 deletions

View File

@ -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">

View File

@ -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">

View File

@ -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;

View File

@ -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;
});
};

View File

@ -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 -->

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

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

View File

@ -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";

View File

@ -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 -->";

View File

@ -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)

View File

@ -142,9 +142,15 @@ signals:
void wordCountInfoUpdated();
void plantUMLResultReady(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, 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);

View File

@ -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();

View File

@ -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);

View File

@ -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,
m_mathJaxHelper->previewDiagram(m_mathJaxID,
p_idx,
m_timeStamp,
vcb.m_lang,
removeFence(vcb.m_text),
false);
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;
}

View File

@ -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

View File

@ -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_format, ba);
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 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_text);
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->previewDiagram(p_identifier, p_id, p_timeStamp, p_lang, p_text);
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
};

View File

@ -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());
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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;