mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 22:09:52 +08:00
support exporting note as PDF file
TODO: Currently the exported PDF does not have the outline which is needed to fix via third-party utils.
This commit is contained in:
parent
871c53743f
commit
0131569c02
@ -94,7 +94,7 @@ void VInsertImageDialog::handleBrowseBtnClicked()
|
|||||||
static QString lastPath = QDir::homePath();
|
static QString lastPath = QDir::homePath();
|
||||||
QString filePath = QFileDialog::getOpenFileName(this, tr("Select The Image To Be Inserted"),
|
QString filePath = QFileDialog::getOpenFileName(this, tr("Select The Image To Be Inserted"),
|
||||||
lastPath, tr("Images (*.png *.xpm *.jpg *.bmp *.gif)"));
|
lastPath, tr("Images (*.png *.xpm *.jpg *.bmp *.gif)"));
|
||||||
if (filePath.isNull() || filePath.isEmpty()) {
|
if (filePath.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,13 +49,18 @@ var updateHtml = function(html) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If you add new logics after handling MathJax, please pay attention to
|
||||||
|
// finishLoading logic.
|
||||||
// MathJax may be not loaded for now.
|
// MathJax may be not loaded for now.
|
||||||
if (VEnableMathjax && (typeof MathJax != "undefined")) {
|
if (VEnableMathjax && (typeof MathJax != "undefined")) {
|
||||||
try {
|
try {
|
||||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, placeholder]);
|
MathJax.Hub.Queue(["Typeset", MathJax.Hub, placeholder, finishLoading]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
content.setLog("err: " + err);
|
content.setLog("err: " + err);
|
||||||
|
finishLoading();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
finishLoading();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,12 +169,18 @@ var updateText = function(text) {
|
|||||||
handleToc(needToc);
|
handleToc(needToc);
|
||||||
insertImageCaption();
|
insertImageCaption();
|
||||||
renderMermaid('lang-mermaid');
|
renderMermaid('lang-mermaid');
|
||||||
|
|
||||||
|
// If you add new logics after handling MathJax, please pay attention to
|
||||||
|
// finishLoading logic.
|
||||||
if (VEnableMathjax) {
|
if (VEnableMathjax) {
|
||||||
try {
|
try {
|
||||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, placeholder]);
|
MathJax.Hub.Queue(["Typeset", MathJax.Hub, placeholder, finishLoading]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
content.setLog("err: " + err);
|
content.setLog("err: " + err);
|
||||||
|
finishLoading();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
finishLoading();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -245,3 +245,9 @@ var insertImageCaption = function() {
|
|||||||
img.insertAdjacentElement('afterend', captionDiv);
|
img.insertAdjacentElement('afterend', captionDiv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The renderer specific code should call this function once thay have finished
|
||||||
|
// loading the page.
|
||||||
|
var finishLoading = function() {
|
||||||
|
content.finishLoading();
|
||||||
|
};
|
||||||
|
@ -127,12 +127,18 @@ var updateText = function(text) {
|
|||||||
handleToc(needToc);
|
handleToc(needToc);
|
||||||
insertImageCaption();
|
insertImageCaption();
|
||||||
renderMermaid('lang-mermaid');
|
renderMermaid('lang-mermaid');
|
||||||
|
|
||||||
|
// If you add new logics after handling MathJax, please pay attention to
|
||||||
|
// finishLoading logic.
|
||||||
if (VEnableMathjax) {
|
if (VEnableMathjax) {
|
||||||
try {
|
try {
|
||||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, placeholder]);
|
MathJax.Hub.Queue(["Typeset", MathJax.Hub, placeholder, finishLoading]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
content.setLog("err: " + err);
|
content.setLog("err: " + err);
|
||||||
|
finishLoading();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
finishLoading();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -151,12 +151,18 @@ var updateText = function(text) {
|
|||||||
insertImageCaption();
|
insertImageCaption();
|
||||||
highlightCodeBlocks(document, VEnableMermaid);
|
highlightCodeBlocks(document, VEnableMermaid);
|
||||||
renderMermaid('language-mermaid');
|
renderMermaid('language-mermaid');
|
||||||
|
|
||||||
|
// If you add new logics after handling MathJax, please pay attention to
|
||||||
|
// finishLoading logic.
|
||||||
if (VEnableMathjax) {
|
if (VEnableMathjax) {
|
||||||
try {
|
try {
|
||||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, placeholder]);
|
MathJax.Hub.Queue(["Typeset", MathJax.Hub, placeholder, finishLoading]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
content.setLog("err: " + err);
|
content.setLog("err: " + err);
|
||||||
|
finishLoading();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
finishLoading();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,7 +61,8 @@ SOURCES += main.cpp\
|
|||||||
vorphanfile.cpp \
|
vorphanfile.cpp \
|
||||||
vcodeblockhighlighthelper.cpp \
|
vcodeblockhighlighthelper.cpp \
|
||||||
vwebview.cpp \
|
vwebview.cpp \
|
||||||
vimagepreviewer.cpp
|
vimagepreviewer.cpp \
|
||||||
|
vexporter.cpp
|
||||||
|
|
||||||
HEADERS += vmainwindow.h \
|
HEADERS += vmainwindow.h \
|
||||||
vdirectorytree.h \
|
vdirectorytree.h \
|
||||||
@ -109,7 +110,8 @@ HEADERS += vmainwindow.h \
|
|||||||
vorphanfile.h \
|
vorphanfile.h \
|
||||||
vcodeblockhighlighthelper.h \
|
vcodeblockhighlighthelper.h \
|
||||||
vwebview.h \
|
vwebview.h \
|
||||||
vimagepreviewer.h
|
vimagepreviewer.h \
|
||||||
|
vexporter.h
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
vnote.qrc \
|
vnote.qrc \
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
#include "vfile.h"
|
#include "vfile.h"
|
||||||
|
|
||||||
@ -441,3 +442,16 @@ QString VUtils::getLocale()
|
|||||||
}
|
}
|
||||||
return locale;
|
return locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VUtils::sleepWait(int p_milliseconds)
|
||||||
|
{
|
||||||
|
if (p_milliseconds <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QElapsedTimer t;
|
||||||
|
t.start();
|
||||||
|
while (t.elapsed() < p_milliseconds) {
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -92,6 +92,8 @@ public:
|
|||||||
static QChar keyToChar(int p_key);
|
static QChar keyToChar(int p_key);
|
||||||
static QString getLocale();
|
static QString getLocale();
|
||||||
|
|
||||||
|
static void sleepWait(int p_milliseconds);
|
||||||
|
|
||||||
// Regular expression for image link.
|
// Regular expression for image link.
|
||||||
// 
|
// 
|
||||||
// Captured texts (need to be trimmed):
|
// Captured texts (need to be trimmed):
|
||||||
|
@ -14,4 +14,8 @@ static const qreal c_webZoomFactorMax = 5;
|
|||||||
static const qreal c_webZoomFactorMin = 0.25;
|
static const qreal c_webZoomFactorMin = 0.25;
|
||||||
|
|
||||||
static const int c_tabSequenceBase = 1;
|
static const int c_tabSequenceBase = 1;
|
||||||
|
|
||||||
|
// HTML and JS.
|
||||||
|
static const QString c_htmlJSHolder = "JS_PLACE_HOLDER";
|
||||||
|
static const QString c_htmlExtraHolder = "<!-- EXTRA_PLACE_HOLDER -->";
|
||||||
#endif
|
#endif
|
||||||
|
@ -9,8 +9,10 @@ VDocument::VDocument(const VFile *v_file, QObject *p_parent)
|
|||||||
|
|
||||||
void VDocument::updateText()
|
void VDocument::updateText()
|
||||||
{
|
{
|
||||||
|
if (m_file) {
|
||||||
emit textChanged(m_file->getContent());
|
emit textChanged(m_file->getContent());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void VDocument::setToc(const QString &toc)
|
void VDocument::setToc(const QString &toc)
|
||||||
{
|
{
|
||||||
@ -74,3 +76,14 @@ void VDocument::noticeReadyToHighlightText()
|
|||||||
{
|
{
|
||||||
emit readyToHighlightText();
|
emit readyToHighlightText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VDocument::setFile(const VFile *p_file)
|
||||||
|
{
|
||||||
|
m_file = p_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VDocument::finishLoading()
|
||||||
|
{
|
||||||
|
qDebug() << "Web side finished loading";
|
||||||
|
emit loadFinished();
|
||||||
|
}
|
||||||
|
@ -14,6 +14,7 @@ class VDocument : public QObject
|
|||||||
Q_PROPERTY(QString html MEMBER m_html NOTIFY htmlChanged)
|
Q_PROPERTY(QString html MEMBER m_html NOTIFY htmlChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// @p_file could be NULL.
|
||||||
VDocument(const VFile *p_file, QObject *p_parent = 0);
|
VDocument(const VFile *p_file, QObject *p_parent = 0);
|
||||||
QString getToc();
|
QString getToc();
|
||||||
void scrollToAnchor(const QString &anchor);
|
void scrollToAnchor(const QString &anchor);
|
||||||
@ -22,6 +23,8 @@ public:
|
|||||||
// Use p_id to identify the result.
|
// Use p_id to identify the result.
|
||||||
void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp);
|
void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp);
|
||||||
|
|
||||||
|
void setFile(const VFile *p_file);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// Will be called in the HTML side
|
// Will be called in the HTML side
|
||||||
void setToc(const QString &toc);
|
void setToc(const QString &toc);
|
||||||
@ -32,6 +35,9 @@ public slots:
|
|||||||
void highlightTextCB(const QString &p_html, int p_id, int p_timeStamp);
|
void highlightTextCB(const QString &p_html, int p_id, int p_timeStamp);
|
||||||
void noticeReadyToHighlightText();
|
void noticeReadyToHighlightText();
|
||||||
|
|
||||||
|
// Page is finished loading.
|
||||||
|
void finishLoading();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void textChanged(const QString &text);
|
void textChanged(const QString &text);
|
||||||
void tocChanged(const QString &toc);
|
void tocChanged(const QString &toc);
|
||||||
@ -43,6 +49,7 @@ signals:
|
|||||||
void requestHighlightText(const QString &p_text, int p_id, int p_timeStamp);
|
void requestHighlightText(const QString &p_text, int p_id, int p_timeStamp);
|
||||||
void textHighlighted(const QString &p_html, int p_id, int p_timeStamp);
|
void textHighlighted(const QString &p_html, int p_id, int p_timeStamp);
|
||||||
void readyToHighlightText();
|
void readyToHighlightText();
|
||||||
|
void loadFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_toc;
|
QString m_toc;
|
||||||
|
@ -155,25 +155,14 @@ void VEditTab::scrollPreviewToHeader(int p_outlineIndex)
|
|||||||
void VEditTab::previewByConverter()
|
void VEditTab::previewByConverter()
|
||||||
{
|
{
|
||||||
VMarkdownConverter mdConverter;
|
VMarkdownConverter mdConverter;
|
||||||
const QString &content = m_file->getContent();
|
QString toc;
|
||||||
QString html = mdConverter.generateHtml(content, vconfig.getMarkdownExtensions());
|
QString html = mdConverter.generateHtml(m_file->getContent(),
|
||||||
QRegularExpression tocExp("<p>\\[TOC\\]<\\/p>", QRegularExpression::CaseInsensitiveOption);
|
vconfig.getMarkdownExtensions(),
|
||||||
QString toc = mdConverter.generateToc(content, vconfig.getMarkdownExtensions());
|
toc);
|
||||||
processHoedownToc(toc);
|
|
||||||
html.replace(tocExp, toc);
|
|
||||||
document.setHtml(html);
|
document.setHtml(html);
|
||||||
updateTocFromHtml(toc);
|
updateTocFromHtml(toc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VEditTab::processHoedownToc(QString &p_toc)
|
|
||||||
{
|
|
||||||
// Hoedown will add '\n'.
|
|
||||||
p_toc.replace("\n", "");
|
|
||||||
// Hoedown will translate `_` in title to `<em>`.
|
|
||||||
p_toc.replace("<em>", "_");
|
|
||||||
p_toc.replace("</em>", "_");
|
|
||||||
}
|
|
||||||
|
|
||||||
void VEditTab::showFileEditMode()
|
void VEditTab::showFileEditMode()
|
||||||
{
|
{
|
||||||
if (!m_file->isModifiable()) {
|
if (!m_file->isModifiable()) {
|
||||||
@ -294,8 +283,8 @@ void VEditTab::discardAndRead()
|
|||||||
|
|
||||||
void VEditTab::setupMarkdownPreview()
|
void VEditTab::setupMarkdownPreview()
|
||||||
{
|
{
|
||||||
const QString jsHolder("JS_PLACE_HOLDER");
|
const QString &jsHolder = c_htmlJSHolder;
|
||||||
const QString extraHolder("<!-- EXTRA_PLACE_HOLDER -->");
|
const QString &extraHolder = c_htmlExtraHolder;
|
||||||
|
|
||||||
webPreviewer = new VWebView(m_file, this);
|
webPreviewer = new VWebView(m_file, this);
|
||||||
connect(webPreviewer, &VWebView::editNote,
|
connect(webPreviewer, &VWebView::editNote,
|
||||||
@ -373,24 +362,7 @@ void VEditTab::setupMarkdownPreview()
|
|||||||
htmlTemplate.replace(extraHolder, extraFile);
|
htmlTemplate.replace(extraHolder, extraFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to judge the path: Url, local file, resource file.
|
webPreviewer->setHtml(htmlTemplate, m_file->getBaseUrl());
|
||||||
QUrl baseUrl;
|
|
||||||
QString basePath = m_file->retriveBasePath();
|
|
||||||
QFileInfo pathInfo(basePath);
|
|
||||||
if (pathInfo.exists()) {
|
|
||||||
if (pathInfo.isNativePath()) {
|
|
||||||
// Local file.
|
|
||||||
baseUrl = QUrl::fromLocalFile(basePath + QDir::separator());
|
|
||||||
} else {
|
|
||||||
// Resource file.
|
|
||||||
baseUrl = QUrl("qrc" + basePath + QDir::separator());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Url.
|
|
||||||
baseUrl = QUrl(basePath + QDir::separator());
|
|
||||||
}
|
|
||||||
|
|
||||||
webPreviewer->setHtml(htmlTemplate, baseUrl);
|
|
||||||
addWidget(webPreviewer);
|
addWidget(webPreviewer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,3 +705,7 @@ VWebView *VEditTab::getWebViewer() const
|
|||||||
return webPreviewer;
|
return webPreviewer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MarkdownConverterType VEditTab::getMarkdownConverterType() const
|
||||||
|
{
|
||||||
|
return mdConverterType;
|
||||||
|
}
|
||||||
|
@ -51,6 +51,8 @@ public:
|
|||||||
|
|
||||||
VWebView *getWebViewer() const;
|
VWebView *getWebViewer() const;
|
||||||
|
|
||||||
|
MarkdownConverterType getMarkdownConverterType() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// Enter edit mode
|
// Enter edit mode
|
||||||
void editFile();
|
void editFile();
|
||||||
@ -82,7 +84,6 @@ private:
|
|||||||
void showFileEditMode();
|
void showFileEditMode();
|
||||||
void setupMarkdownPreview();
|
void setupMarkdownPreview();
|
||||||
void previewByConverter();
|
void previewByConverter();
|
||||||
void processHoedownToc(QString &p_toc);
|
|
||||||
inline bool isChild(QObject *obj);
|
inline bool isChild(QObject *obj);
|
||||||
void parseTocUl(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
|
void parseTocUl(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
|
||||||
void parseTocLi(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
|
void parseTocLi(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
|
||||||
|
404
src/vexporter.cpp
Normal file
404
src/vexporter.cpp
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
#include "vexporter.h"
|
||||||
|
|
||||||
|
#include <QtWidgets>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QWebChannel>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QShowEvent>
|
||||||
|
|
||||||
|
#ifndef QT_NO_PRINTER
|
||||||
|
#include <QPrinter>
|
||||||
|
#include <QPageSetupDialog>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "vconfigmanager.h"
|
||||||
|
#include "utils/vutils.h"
|
||||||
|
#include "vfile.h"
|
||||||
|
#include "vwebview.h"
|
||||||
|
#include "vpreviewpage.h"
|
||||||
|
#include "vconstants.h"
|
||||||
|
#include "vnote.h"
|
||||||
|
#include "vmarkdownconverter.h"
|
||||||
|
|
||||||
|
extern VConfigManager vconfig;
|
||||||
|
|
||||||
|
QString VExporter::s_defaultPathDir = QDir::homePath();
|
||||||
|
|
||||||
|
VExporter::VExporter(MarkdownConverterType p_mdType, QWidget *p_parent)
|
||||||
|
: QDialog(p_parent), m_document(NULL, this), m_mdType(p_mdType),
|
||||||
|
m_file(NULL), m_type(ExportType::PDF), m_source(ExportSource::Invalid),
|
||||||
|
m_webReady(false), m_state(ExportState::Idle),
|
||||||
|
m_pageLayout(QPageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(0.0, 0.0, 0.0, 0.0)))
|
||||||
|
{
|
||||||
|
setupUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::setupUI()
|
||||||
|
{
|
||||||
|
setupMarkdownViewer();
|
||||||
|
|
||||||
|
m_infoLabel = new QLabel();
|
||||||
|
m_infoLabel->setWordWrap(true);
|
||||||
|
|
||||||
|
// Target file path.
|
||||||
|
QLabel *pathLabel = new QLabel(tr("Target &path:"));
|
||||||
|
m_pathEdit = new QLineEdit();
|
||||||
|
pathLabel->setBuddy(m_pathEdit);
|
||||||
|
m_browseBtn = new QPushButton(tr("&Browse"));
|
||||||
|
connect(m_browseBtn, &QPushButton::clicked,
|
||||||
|
this, &VExporter::handleBrowseBtnClicked);
|
||||||
|
|
||||||
|
// Page layout.
|
||||||
|
QLabel *layoutLabel = new QLabel(tr("Page layout:"));
|
||||||
|
m_layoutLabel = new QLabel();
|
||||||
|
m_layoutBtn = new QPushButton(tr("&Settings"));
|
||||||
|
|
||||||
|
#ifndef QT_NO_PRINTER
|
||||||
|
connect(m_layoutBtn, &QPushButton::clicked,
|
||||||
|
this, &VExporter::handleLayoutBtnClicked);
|
||||||
|
#else
|
||||||
|
m_layoutBtn->hide();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Progress.
|
||||||
|
m_proLabel = new QLabel(this);
|
||||||
|
m_proBar = new QProgressBar(this);
|
||||||
|
|
||||||
|
// Ok is the default button.
|
||||||
|
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||||
|
connect(m_btnBox, &QDialogButtonBox::accepted, this, &VExporter::startExport);
|
||||||
|
connect(m_btnBox, &QDialogButtonBox::rejected, this, &VExporter::cancelExport);
|
||||||
|
|
||||||
|
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||||
|
m_pathEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
|
||||||
|
|
||||||
|
QGridLayout *mainLayout = new QGridLayout();
|
||||||
|
mainLayout->addWidget(m_webViewer, 0, 0, 1, 3);
|
||||||
|
mainLayout->addWidget(m_infoLabel, 1, 0, 1, 3);
|
||||||
|
mainLayout->addWidget(pathLabel, 2, 0);
|
||||||
|
mainLayout->addWidget(m_pathEdit, 2, 1);
|
||||||
|
mainLayout->addWidget(m_browseBtn, 2, 2);
|
||||||
|
mainLayout->addWidget(layoutLabel, 3, 0);
|
||||||
|
mainLayout->addWidget(m_layoutLabel, 3, 1);
|
||||||
|
mainLayout->addWidget(m_layoutBtn, 3, 2);
|
||||||
|
mainLayout->addWidget(m_proLabel, 4, 1, 1, 2);
|
||||||
|
mainLayout->addWidget(m_proBar, 5, 1, 1, 2);
|
||||||
|
mainLayout->addWidget(m_btnBox, 6, 1, 1, 2);
|
||||||
|
|
||||||
|
// Only use VWebView to do the conversion.
|
||||||
|
m_webViewer->hide();
|
||||||
|
|
||||||
|
m_proLabel->hide();
|
||||||
|
m_proBar->hide();
|
||||||
|
|
||||||
|
setLayout(mainLayout);
|
||||||
|
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||||
|
setWindowTitle(tr("Export Note"));
|
||||||
|
|
||||||
|
updatePageLayoutLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString exportTypeStr(ExportType p_type)
|
||||||
|
{
|
||||||
|
if (p_type == ExportType::PDF) {
|
||||||
|
return "PDF";
|
||||||
|
} else {
|
||||||
|
return "HTML";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::handleBrowseBtnClicked()
|
||||||
|
{
|
||||||
|
QFileInfo fi(getFilePath());
|
||||||
|
QString fileType = m_type == ExportType::PDF ?
|
||||||
|
tr("Portable Document Format (*.pdf)") :
|
||||||
|
tr("WebPage, Complete (*.html)");
|
||||||
|
QString path = QFileDialog::getSaveFileName(this, tr("Export As"),
|
||||||
|
fi.absolutePath(),
|
||||||
|
fileType);
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilePath(path);
|
||||||
|
s_defaultPathDir = VUtils::basePathFromPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::handleLayoutBtnClicked()
|
||||||
|
{
|
||||||
|
#ifndef QT_NO_PRINTER
|
||||||
|
QPrinter printer;
|
||||||
|
printer.setPageLayout(m_pageLayout);
|
||||||
|
|
||||||
|
QPageSetupDialog dlg(&printer, this);
|
||||||
|
if (dlg.exec() != QDialog::Accepted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pageLayout.setPageSize(printer.pageLayout().pageSize());
|
||||||
|
m_pageLayout.setOrientation(printer.pageLayout().orientation());
|
||||||
|
|
||||||
|
updatePageLayoutLabel();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::updatePageLayoutLabel()
|
||||||
|
{
|
||||||
|
m_layoutLabel->setText(QString("%1, %2").arg(m_pageLayout.pageSize().name())
|
||||||
|
.arg(m_pageLayout.orientation() == QPageLayout::Portrait ?
|
||||||
|
tr("Portrait") : tr("Landscape")));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VExporter::getFilePath() const
|
||||||
|
{
|
||||||
|
return QDir::cleanPath(m_pathEdit->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::setFilePath(const QString &p_path)
|
||||||
|
{
|
||||||
|
m_pathEdit->setText(QDir::toNativeSeparators(p_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::exportNote(VFile *p_file, ExportType p_type)
|
||||||
|
{
|
||||||
|
m_file = p_file;
|
||||||
|
m_type = p_type;
|
||||||
|
m_source = ExportSource::Note;
|
||||||
|
|
||||||
|
if (!m_file || m_file->getDocType() != DocType::Markdown) {
|
||||||
|
// Do not support non-Markdown note now.
|
||||||
|
m_btnBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_infoLabel->setText(tr("Export note <span style=\"%1\">%2</span> as %3.")
|
||||||
|
.arg(vconfig.c_dataTextStyle)
|
||||||
|
.arg(m_file->getName())
|
||||||
|
.arg(exportTypeStr(p_type)));
|
||||||
|
|
||||||
|
setWindowTitle(tr("Export As %1").arg(exportTypeStr(p_type)));
|
||||||
|
|
||||||
|
setFilePath(QDir(s_defaultPathDir).filePath(QFileInfo(p_file->retrivePath()).baseName() +
|
||||||
|
"." + exportTypeStr(p_type).toLower()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::setupMarkdownViewer()
|
||||||
|
{
|
||||||
|
m_webViewer = new VWebView(NULL, this);
|
||||||
|
VPreviewPage *page = new VPreviewPage(this);
|
||||||
|
m_webViewer->setPage(page);
|
||||||
|
|
||||||
|
QWebChannel *channel = new QWebChannel(this);
|
||||||
|
channel->registerObject(QStringLiteral("content"), &m_document);
|
||||||
|
page->setWebChannel(channel);
|
||||||
|
|
||||||
|
connect(&m_document, &VDocument::loadFinished,
|
||||||
|
this, &VExporter::readyToExport);
|
||||||
|
|
||||||
|
QString jsFile, extraFile;
|
||||||
|
switch (m_mdType) {
|
||||||
|
case MarkdownConverterType::Marked:
|
||||||
|
jsFile = "qrc" + VNote::c_markedJsFile;
|
||||||
|
extraFile = "<script src=\"qrc" + VNote::c_markedExtraFile + "\"></script>\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MarkdownConverterType::Hoedown:
|
||||||
|
jsFile = "qrc" + VNote::c_hoedownJsFile;
|
||||||
|
// Use Marked to highlight code blocks.
|
||||||
|
extraFile = "<script src=\"qrc" + VNote::c_markedExtraFile + "\"></script>\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MarkdownConverterType::MarkdownIt:
|
||||||
|
jsFile = "qrc" + VNote::c_markdownitJsFile;
|
||||||
|
extraFile = "<script src=\"qrc" + VNote::c_markdownitExtraFile + "\"></script>\n" +
|
||||||
|
"<script src=\"qrc" + VNote::c_markdownitAnchorExtraFile + "\"></script>\n" +
|
||||||
|
"<script src=\"qrc" + VNote::c_markdownitTaskListExtraFile + "\"></script>\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MarkdownConverterType::Showdown:
|
||||||
|
jsFile = "qrc" + VNote::c_showdownJsFile;
|
||||||
|
extraFile = "<script src=\"qrc" + VNote::c_showdownExtraFile + "\"></script>\n" +
|
||||||
|
"<script src=\"qrc" + VNote::c_showdownAnchorExtraFile + "\"></script>\n";
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Q_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vconfig.getEnableMermaid()) {
|
||||||
|
extraFile += "<link rel=\"stylesheet\" type=\"text/css\" href=\"qrc" + VNote::c_mermaidCssFile +
|
||||||
|
"\"/>\n" + "<script src=\"qrc" + VNote::c_mermaidApiJsFile + "\"></script>\n" +
|
||||||
|
"<script>var VEnableMermaid = true;</script>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vconfig.getEnableMathjax()) {
|
||||||
|
extraFile += "<script type=\"text/x-mathjax-config\">"
|
||||||
|
"MathJax.Hub.Config({\n"
|
||||||
|
" tex2jax: {inlineMath: [['$','$'], ['\\\\(','\\\\)']]},\n"
|
||||||
|
" showProcessingMessages: false,\n"
|
||||||
|
" messageStyle: \"none\"});\n"
|
||||||
|
"</script>\n"
|
||||||
|
"<script type=\"text/javascript\" async src=\"" + VNote::c_mathjaxJsFile + "\"></script>\n" +
|
||||||
|
"<script>var VEnableMathjax = true;</script>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vconfig.getEnableImageCaption()) {
|
||||||
|
extraFile += "<script>var VEnableImageCaption = true;</script>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
m_htmlTemplate = VNote::s_markdownTemplatePDF;
|
||||||
|
m_htmlTemplate.replace(c_htmlJSHolder, jsFile);
|
||||||
|
if (!extraFile.isEmpty()) {
|
||||||
|
m_htmlTemplate.replace(c_htmlExtraHolder, extraFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::updateWebViewer(VFile *p_file)
|
||||||
|
{
|
||||||
|
m_document.setFile(p_file);
|
||||||
|
|
||||||
|
// Need to generate HTML using Hoedown.
|
||||||
|
if (m_mdType == MarkdownConverterType::Hoedown) {
|
||||||
|
VMarkdownConverter mdConverter;
|
||||||
|
QString toc;
|
||||||
|
QString html = mdConverter.generateHtml(p_file->getContent(),
|
||||||
|
vconfig.getMarkdownExtensions(),
|
||||||
|
toc);
|
||||||
|
m_document.setHtml(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_webViewer->setHtml(m_htmlTemplate, p_file->getBaseUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::readyToExport()
|
||||||
|
{
|
||||||
|
Q_ASSERT(!m_webReady);
|
||||||
|
m_webReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::startExport()
|
||||||
|
{
|
||||||
|
enableUserInput(false);
|
||||||
|
V_ASSERT(m_state == ExportState::Idle);
|
||||||
|
m_state = ExportState::Busy;
|
||||||
|
|
||||||
|
if (m_source == ExportSource::Note) {
|
||||||
|
V_ASSERT(m_file);
|
||||||
|
bool isOpened = m_file->isOpened();
|
||||||
|
if (!isOpened && !m_file->open()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_webReady = false;
|
||||||
|
updateWebViewer(m_file);
|
||||||
|
|
||||||
|
// Update progress info.
|
||||||
|
m_proLabel->setText(tr("Exporting %1").arg(m_file->getName()));
|
||||||
|
m_proBar->setMinimum(0);
|
||||||
|
m_proBar->setMaximum(100);
|
||||||
|
m_proBar->reset();
|
||||||
|
m_proLabel->show();
|
||||||
|
m_proBar->show();
|
||||||
|
|
||||||
|
while (!m_webReady) {
|
||||||
|
VUtils::sleepWait(100);
|
||||||
|
if (m_proBar->value() < 70) {
|
||||||
|
m_proBar->setValue(m_proBar->value() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_state == ExportState::Cancelled) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait to ensure Web side is really ready.
|
||||||
|
VUtils::sleepWait(200);
|
||||||
|
|
||||||
|
if (m_state == ExportState::Cancelled) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_proBar->setValue(80);
|
||||||
|
|
||||||
|
exportToPDF(m_webViewer, getFilePath(), m_pageLayout);
|
||||||
|
|
||||||
|
m_proBar->setValue(100);
|
||||||
|
|
||||||
|
m_webReady = false;
|
||||||
|
|
||||||
|
if (!isOpened) {
|
||||||
|
m_file->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
m_proLabel->setText("");
|
||||||
|
m_proBar->reset();
|
||||||
|
m_proLabel->hide();
|
||||||
|
m_proBar->hide();
|
||||||
|
enableUserInput(true);
|
||||||
|
|
||||||
|
if (m_state == ExportState::Cancelled) {
|
||||||
|
reject();
|
||||||
|
} else {
|
||||||
|
accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_state = ExportState::Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::cancelExport()
|
||||||
|
{
|
||||||
|
if (m_state == ExportState::Idle) {
|
||||||
|
reject();
|
||||||
|
} else {
|
||||||
|
m_state = ExportState::Cancelled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::exportToPDF(VWebView *p_webViewer, const QString &p_filePath,
|
||||||
|
const QPageLayout &p_layout)
|
||||||
|
{
|
||||||
|
int pdfPrinted = 0;
|
||||||
|
p_webViewer->page()->printToPdf([&, this](const QByteArray &p_result) {
|
||||||
|
if (p_result.isEmpty() || this->m_state == ExportState::Cancelled) {
|
||||||
|
pdfPrinted = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
V_ASSERT(!p_filePath.isEmpty());
|
||||||
|
|
||||||
|
QFile file(p_filePath);
|
||||||
|
|
||||||
|
if (!file.open(QFile::WriteOnly)) {
|
||||||
|
pdfPrinted = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write(p_result.data(), p_result.size());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
pdfPrinted = 1;
|
||||||
|
}, p_layout);
|
||||||
|
|
||||||
|
while (pdfPrinted == 0) {
|
||||||
|
VUtils::sleepWait(100);
|
||||||
|
|
||||||
|
if (m_state == ExportState::Cancelled) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "export to PDF" << p_filePath << "state" << pdfPrinted;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VExporter::enableUserInput(bool p_enabled)
|
||||||
|
{
|
||||||
|
m_btnBox->button(QDialogButtonBox::Ok)->setEnabled(p_enabled);
|
||||||
|
m_pathEdit->setEnabled(p_enabled);
|
||||||
|
m_browseBtn->setEnabled(p_enabled);
|
||||||
|
m_layoutBtn->setEnabled(p_enabled);
|
||||||
|
}
|
101
src/vexporter.h
Normal file
101
src/vexporter.h
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#ifndef VEXPORTER_H
|
||||||
|
#define VEXPORTER_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QPageLayout>
|
||||||
|
#include <QString>
|
||||||
|
#include "vconfigmanager.h"
|
||||||
|
#include "vdocument.h"
|
||||||
|
|
||||||
|
class VWebView;
|
||||||
|
class VFile;
|
||||||
|
class QLineEdit;
|
||||||
|
class QLabel;
|
||||||
|
class QDialogButtonBox;
|
||||||
|
class QPushButton;
|
||||||
|
class QProgressBar;
|
||||||
|
|
||||||
|
enum class ExportType
|
||||||
|
{
|
||||||
|
PDF = 0,
|
||||||
|
HTML
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ExportSource
|
||||||
|
{
|
||||||
|
Note = 0,
|
||||||
|
Directory,
|
||||||
|
Notebook,
|
||||||
|
Invalid
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ExportState
|
||||||
|
{
|
||||||
|
Idle = 0,
|
||||||
|
Cancelled,
|
||||||
|
Busy
|
||||||
|
};
|
||||||
|
|
||||||
|
class VExporter : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit VExporter(MarkdownConverterType p_mdType = MarkdownIt, QWidget *p_parent = 0);
|
||||||
|
|
||||||
|
void exportNote(VFile *p_file, ExportType p_type);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleBrowseBtnClicked();
|
||||||
|
void handleLayoutBtnClicked();
|
||||||
|
void startExport();
|
||||||
|
void cancelExport();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUI();
|
||||||
|
|
||||||
|
// Init m_webViewer, m_document, and m_htmlTemplate.
|
||||||
|
void setupMarkdownViewer();
|
||||||
|
|
||||||
|
void updatePageLayoutLabel();
|
||||||
|
|
||||||
|
void setFilePath(const QString &p_path);
|
||||||
|
|
||||||
|
QString getFilePath() const;
|
||||||
|
|
||||||
|
void updateWebViewer(VFile *p_file);
|
||||||
|
|
||||||
|
void readyToExport();
|
||||||
|
|
||||||
|
void enableUserInput(bool p_enabled);
|
||||||
|
|
||||||
|
void exportToPDF(VWebView *p_webViewer, const QString &p_filePath, const QPageLayout &p_layout);
|
||||||
|
|
||||||
|
VWebView *m_webViewer;
|
||||||
|
VDocument m_document;
|
||||||
|
MarkdownConverterType m_mdType;
|
||||||
|
QString m_htmlTemplate;
|
||||||
|
VFile *m_file;
|
||||||
|
ExportType m_type;
|
||||||
|
ExportSource m_source;
|
||||||
|
bool m_webReady;
|
||||||
|
|
||||||
|
ExportState m_state;
|
||||||
|
|
||||||
|
QLabel *m_infoLabel;
|
||||||
|
QLineEdit *m_pathEdit;
|
||||||
|
QPushButton *m_browseBtn;
|
||||||
|
QLabel *m_layoutLabel;
|
||||||
|
QPushButton *m_layoutBtn;
|
||||||
|
QDialogButtonBox *m_btnBox;
|
||||||
|
|
||||||
|
// Progress label and bar.
|
||||||
|
QLabel *m_proLabel;
|
||||||
|
QProgressBar *m_proBar;
|
||||||
|
|
||||||
|
QPageLayout m_pageLayout;
|
||||||
|
|
||||||
|
// The default directory.
|
||||||
|
static QString s_defaultPathDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VEXPORTER_H
|
@ -3,6 +3,7 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
|
#include <QFileInfo>
|
||||||
#include "utils/vutils.h"
|
#include "utils/vutils.h"
|
||||||
|
|
||||||
VFile::VFile(const QString &p_name, QObject *p_parent,
|
VFile::VFile(const QString &p_name, QObject *p_parent,
|
||||||
@ -214,3 +215,25 @@ bool VFile::isInternalImageFolder(const QString &p_path) const
|
|||||||
{
|
{
|
||||||
return VUtils::basePathFromPath(p_path) == getDirectory()->retrivePath();
|
return VUtils::basePathFromPath(p_path) == getDirectory()->retrivePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl VFile::getBaseUrl() const
|
||||||
|
{
|
||||||
|
// Need to judge the path: Url, local file, resource file.
|
||||||
|
QUrl baseUrl;
|
||||||
|
QString basePath = retriveBasePath();
|
||||||
|
QFileInfo pathInfo(basePath);
|
||||||
|
if (pathInfo.exists()) {
|
||||||
|
if (pathInfo.isNativePath()) {
|
||||||
|
// Local file.
|
||||||
|
baseUrl = QUrl::fromLocalFile(basePath + QDir::separator());
|
||||||
|
} else {
|
||||||
|
// Resource file.
|
||||||
|
baseUrl = QUrl("qrc" + basePath + QDir::separator());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Url.
|
||||||
|
baseUrl = QUrl(basePath + QDir::separator());
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
#include "vdirectory.h"
|
#include "vdirectory.h"
|
||||||
#include "vconstants.h"
|
#include "vconstants.h"
|
||||||
|
|
||||||
@ -40,6 +41,9 @@ public:
|
|||||||
bool isOpened() const;
|
bool isOpened() const;
|
||||||
FileType getType() const;
|
FileType getType() const;
|
||||||
|
|
||||||
|
// Return the base URL for this file when loaded in VWebView.
|
||||||
|
QUrl getBaseUrl() const;
|
||||||
|
|
||||||
// Whether the directory @p_path is an internal image folder of this file.
|
// Whether the directory @p_path is an internal image folder of this file.
|
||||||
// It is true only when the folder is in the same directory as the parent
|
// It is true only when the folder is in the same directory as the parent
|
||||||
// directory of this file.
|
// directory of this file.
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "vcaptain.h"
|
#include "vcaptain.h"
|
||||||
#include "vedittab.h"
|
#include "vedittab.h"
|
||||||
#include "vwebview.h"
|
#include "vwebview.h"
|
||||||
|
#include "vexporter.h"
|
||||||
|
|
||||||
extern VConfigManager vconfig;
|
extern VConfigManager vconfig;
|
||||||
|
|
||||||
@ -453,6 +454,18 @@ void VMainWindow::initFileMenu()
|
|||||||
|
|
||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
|
|
||||||
|
// Export as PDF.
|
||||||
|
m_exportAsPDFAct = new QAction(QIcon(":/resources/icons/export_pdf.svg"),
|
||||||
|
tr("Export As &PDF"), this);
|
||||||
|
m_exportAsPDFAct->setToolTip(tr("Export current note as PDF file"));
|
||||||
|
connect(m_exportAsPDFAct, &QAction::triggered,
|
||||||
|
this, &VMainWindow::exportAsPDF);
|
||||||
|
m_exportAsPDFAct->setEnabled(false);
|
||||||
|
|
||||||
|
fileMenu->addAction(m_exportAsPDFAct);
|
||||||
|
|
||||||
|
fileMenu->addSeparator();
|
||||||
|
|
||||||
// Print.
|
// Print.
|
||||||
m_printAct = new QAction(QIcon(":/resources/icons/print.svg"),
|
m_printAct = new QAction(QIcon(":/resources/icons/print.svg"),
|
||||||
tr("&Print"), this);
|
tr("&Print"), this);
|
||||||
@ -1016,6 +1029,7 @@ void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file,
|
|||||||
bool p_editMode)
|
bool p_editMode)
|
||||||
{
|
{
|
||||||
m_printAct->setEnabled(p_file && p_file->getDocType() == DocType::Markdown);
|
m_printAct->setEnabled(p_file && p_file->getDocType() == DocType::Markdown);
|
||||||
|
m_exportAsPDFAct->setEnabled(p_file && p_file->getDocType() == DocType::Markdown);
|
||||||
|
|
||||||
editNoteAct->setVisible(p_file && p_file->isModifiable() && !p_editMode);
|
editNoteAct->setVisible(p_file && p_file->isModifiable() && !p_editMode);
|
||||||
discardExitAct->setVisible(p_file && p_editMode);
|
discardExitAct->setVisible(p_file && p_editMode);
|
||||||
@ -1393,3 +1407,13 @@ void VMainWindow::printNote()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VMainWindow::exportAsPDF()
|
||||||
|
{
|
||||||
|
V_ASSERT(m_curTab);
|
||||||
|
V_ASSERT(m_curFile);
|
||||||
|
|
||||||
|
VExporter exporter(m_curTab->getMarkdownConverterType(), this);
|
||||||
|
exporter.exportNote(m_curFile, ExportType::PDF);
|
||||||
|
exporter.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ private slots:
|
|||||||
void enableImageConstraint(bool p_checked);
|
void enableImageConstraint(bool p_checked);
|
||||||
void enableImageCaption(bool p_checked);
|
void enableImageCaption(bool p_checked);
|
||||||
void printNote();
|
void printNote();
|
||||||
|
void exportAsPDF();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
|
void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
|
||||||
@ -155,6 +156,7 @@ private:
|
|||||||
QAction *expandViewAct;
|
QAction *expandViewAct;
|
||||||
QAction *m_importNoteAct;
|
QAction *m_importNoteAct;
|
||||||
QAction *m_printAct;
|
QAction *m_printAct;
|
||||||
|
QAction *m_exportAsPDFAct;
|
||||||
|
|
||||||
QAction *m_insertImageAct;
|
QAction *m_insertImageAct;
|
||||||
QAction *m_findReplaceAct;
|
QAction *m_findReplaceAct;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "vmarkdownconverter.h"
|
#include "vmarkdownconverter.h"
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
VMarkdownConverter::VMarkdownConverter()
|
VMarkdownConverter::VMarkdownConverter()
|
||||||
{
|
{
|
||||||
@ -36,11 +37,35 @@ QString VMarkdownConverter::generateHtml(const QString &markdown, hoedown_extens
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString VMarkdownConverter::generateHtml(const QString &markdown, hoedown_extensions options, QString &toc)
|
||||||
|
{
|
||||||
|
if (markdown.isEmpty()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString html = generateHtml(markdown, options);
|
||||||
|
QRegularExpression tocExp("<p>\\[TOC\\]<\\/p>", QRegularExpression::CaseInsensitiveOption);
|
||||||
|
toc = generateToc(markdown, options);
|
||||||
|
html.replace(tocExp, toc);
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void processToc(QString &p_toc)
|
||||||
|
{
|
||||||
|
// Hoedown will add '\n'.
|
||||||
|
p_toc.replace("\n", "");
|
||||||
|
// Hoedown will translate `_` in title to `<em>`.
|
||||||
|
p_toc.replace("<em>", "_");
|
||||||
|
p_toc.replace("</em>", "_");
|
||||||
|
}
|
||||||
|
|
||||||
QString VMarkdownConverter::generateToc(const QString &markdown, hoedown_extensions options)
|
QString VMarkdownConverter::generateToc(const QString &markdown, hoedown_extensions options)
|
||||||
{
|
{
|
||||||
if (markdown.isEmpty()) {
|
if (markdown.isEmpty()) {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
hoedown_document *document = hoedown_document_new(tocRenderer, options, nestingLevel);
|
hoedown_document *document = hoedown_document_new(tocRenderer, options, nestingLevel);
|
||||||
QByteArray data = markdown.toUtf8();
|
QByteArray data = markdown.toUtf8();
|
||||||
hoedown_buffer *outBuf = hoedown_buffer_new(16);
|
hoedown_buffer *outBuf = hoedown_buffer_new(16);
|
||||||
@ -48,5 +73,8 @@ QString VMarkdownConverter::generateToc(const QString &markdown, hoedown_extensi
|
|||||||
hoedown_document_free(document);
|
hoedown_document_free(document);
|
||||||
QString toc = QString::fromUtf8(hoedown_buffer_cstr(outBuf));
|
QString toc = QString::fromUtf8(hoedown_buffer_cstr(outBuf));
|
||||||
hoedown_buffer_free(outBuf);
|
hoedown_buffer_free(outBuf);
|
||||||
|
|
||||||
|
processToc(toc);
|
||||||
|
|
||||||
return toc;
|
return toc;
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,13 @@ public:
|
|||||||
VMarkdownConverter();
|
VMarkdownConverter();
|
||||||
~VMarkdownConverter();
|
~VMarkdownConverter();
|
||||||
|
|
||||||
QString generateHtml(const QString &markdown, hoedown_extensions options);
|
QString generateHtml(const QString &markdown, hoedown_extensions options, QString &toc);
|
||||||
|
|
||||||
QString generateToc(const QString &markdown, hoedown_extensions options);
|
QString generateToc(const QString &markdown, hoedown_extensions options);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QString generateHtml(const QString &markdown, hoedown_extensions options);
|
||||||
|
|
||||||
// VMarkdownDocument *generateDocument(const QString &markdown);
|
// VMarkdownDocument *generateDocument(const QString &markdown);
|
||||||
hoedown_html_flags hoedownHtmlFlags;
|
hoedown_html_flags hoedownHtmlFlags;
|
||||||
int nestingLevel;
|
int nestingLevel;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
extern VConfigManager vconfig;
|
extern VConfigManager vconfig;
|
||||||
|
|
||||||
QString VNote::s_markdownTemplate;
|
QString VNote::s_markdownTemplate;
|
||||||
|
QString VNote::s_markdownTemplatePDF;
|
||||||
|
|
||||||
const QString VNote::c_hoedownJsFile = ":/resources/hoedown.js";
|
const QString VNote::c_hoedownJsFile = ":/resources/hoedown.js";
|
||||||
const QString VNote::c_markedJsFile = ":/resources/marked.js";
|
const QString VNote::c_markedJsFile = ":/resources/marked.js";
|
||||||
@ -170,14 +171,22 @@ void VNote::updateTemplate()
|
|||||||
cssStyle += "img { max-width: 100% !important; height: auto !important; }\n";
|
cssStyle += "img { max-width: 100% !important; height: auto !important; }\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
QString styleHolder("<!-- BACKGROUND_PLACE_HOLDER -->");
|
const QString styleHolder("<!-- BACKGROUND_PLACE_HOLDER -->");
|
||||||
QString cssHolder("CSS_PLACE_HOLDER");
|
const QString cssHolder("CSS_PLACE_HOLDER");
|
||||||
|
|
||||||
s_markdownTemplate = VUtils::readFileFromDisk(c_markdownTemplatePath);
|
s_markdownTemplate = VUtils::readFileFromDisk(c_markdownTemplatePath);
|
||||||
s_markdownTemplate.replace(cssHolder, vconfig.getTemplateCssUrl());
|
s_markdownTemplate.replace(cssHolder, vconfig.getTemplateCssUrl());
|
||||||
|
|
||||||
|
s_markdownTemplatePDF = s_markdownTemplate;
|
||||||
|
|
||||||
if (!cssStyle.isEmpty()) {
|
if (!cssStyle.isEmpty()) {
|
||||||
s_markdownTemplate.replace(styleHolder, cssStyle);
|
s_markdownTemplate.replace(styleHolder, cssStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shoudl not display scrollbar in PDF.
|
||||||
|
cssStyle += "pre code { white-space: pre-wrap !important; "
|
||||||
|
"word-break: break-all !important; }\n";
|
||||||
|
s_markdownTemplatePDF.replace(styleHolder, cssStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QVector<VNotebook *> &VNote::getNotebooks() const
|
const QVector<VNotebook *> &VNote::getNotebooks() const
|
||||||
|
@ -28,6 +28,7 @@ public:
|
|||||||
void initTemplate();
|
void initTemplate();
|
||||||
|
|
||||||
static QString s_markdownTemplate;
|
static QString s_markdownTemplate;
|
||||||
|
static QString s_markdownTemplatePDF;
|
||||||
|
|
||||||
// Hoedown
|
// Hoedown
|
||||||
static const QString c_hoedownJsFile;
|
static const QString c_hoedownJsFile;
|
||||||
|
@ -20,7 +20,7 @@ void VWebView::contextMenuEvent(QContextMenuEvent *p_event)
|
|||||||
|
|
||||||
const QList<QAction *> actions = menu->actions();
|
const QList<QAction *> actions = menu->actions();
|
||||||
|
|
||||||
if (!hasSelection() && m_file->isModifiable()) {
|
if (!hasSelection() && m_file && m_file->isModifiable()) {
|
||||||
QAction *editAct= new QAction(QIcon(":/resources/icons/edit_note.svg"),
|
QAction *editAct= new QAction(QIcon(":/resources/icons/edit_note.svg"),
|
||||||
tr("&Edit"), this);
|
tr("&Edit"), this);
|
||||||
editAct->setToolTip(tr("Edit current note"));
|
editAct->setToolTip(tr("Edit current note"));
|
||||||
|
@ -9,6 +9,7 @@ class VWebView : public QWebEngineView
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
// @p_file could be NULL.
|
||||||
explicit VWebView(VFile *p_file, QWidget *p_parent = Q_NULLPTR);
|
explicit VWebView(VFile *p_file, QWidget *p_parent = Q_NULLPTR);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user