mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
Export: support custom export
This commit is contained in:
parent
c9be7a7a7f
commit
8326d3c702
@ -15,8 +15,6 @@ using namespace vnotex;
|
||||
|
||||
HtmlTemplateHelper::Template HtmlTemplateHelper::s_markdownViewerTemplate;
|
||||
|
||||
static const QString c_globalStylesPlaceholder = "/* VX_GLOBAL_STYLES_PLACEHOLDER */";
|
||||
|
||||
QString WebGlobalOptions::toJavascriptObject() const
|
||||
{
|
||||
return QStringLiteral("window.vxOptions = {\n")
|
||||
@ -35,6 +33,7 @@ QString WebGlobalOptions::toJavascriptObject() const
|
||||
+ QString("bodyHeight: %1,\n").arg(m_bodyHeight)
|
||||
+ QString("transformSvgToPngEnabled: %1,\n").arg(Utils::boolToString(m_transformSvgToPngEnabled))
|
||||
+ QString("mathJaxScale: %1,\n").arg(m_mathJaxScale)
|
||||
+ QString("removeCodeToolBarEnabled: %1,\n").arg(Utils::boolToString(m_removeCodeToolBarEnabled))
|
||||
+ QString("sectionNumberBaseLevel: %1\n").arg(m_sectionNumberBaseLevel)
|
||||
+ QStringLiteral("}");
|
||||
}
|
||||
@ -59,7 +58,7 @@ static void fillGlobalStyles(QString &p_template, const WebResource &p_resource,
|
||||
styles += p_additionalStyles;
|
||||
|
||||
if (!styles.isEmpty()) {
|
||||
p_template.replace(c_globalStylesPlaceholder, styles);
|
||||
p_template.replace("/* VX_GLOBAL_STYLES_PLACEHOLDER */", styles);
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,22 +174,15 @@ void HtmlTemplateHelper::updateMarkdownViewerTemplate(const MarkdownEditorConfig
|
||||
|
||||
s_markdownViewerTemplate.m_revision = p_config.revision();
|
||||
|
||||
Paras paras;
|
||||
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
||||
s_markdownViewerTemplate.m_template =
|
||||
generateMarkdownViewerTemplate(p_config,
|
||||
themeMgr.getFile(Theme::File::WebStyleSheet),
|
||||
themeMgr.getFile(Theme::File::HighlightStyleSheet));
|
||||
paras.m_webStyleSheetFile = themeMgr.getFile(Theme::File::WebStyleSheet);
|
||||
paras.m_highlightStyleSheetFile = themeMgr.getFile(Theme::File::HighlightStyleSheet);
|
||||
|
||||
s_markdownViewerTemplate.m_template = generateMarkdownViewerTemplate(p_config, paras);
|
||||
}
|
||||
|
||||
QString HtmlTemplateHelper::generateMarkdownViewerTemplate(const MarkdownEditorConfig &p_config,
|
||||
const QString &p_webStyleSheetFile,
|
||||
const QString &p_highlightStyleSheetFile,
|
||||
bool p_useTransparentBg,
|
||||
bool p_scrollable,
|
||||
int p_bodyWidth,
|
||||
int p_bodyHeight,
|
||||
bool p_transformSvgToPng,
|
||||
qreal p_mathJaxScale)
|
||||
QString HtmlTemplateHelper::generateMarkdownViewerTemplate(const MarkdownEditorConfig &p_config, const Paras &p_paras)
|
||||
{
|
||||
const auto &viewerResource = p_config.getViewerResource();
|
||||
const auto templateFile = ConfigMgr::getInst().getUserOrAppFile(viewerResource.m_template);
|
||||
@ -198,7 +190,7 @@ QString HtmlTemplateHelper::generateMarkdownViewerTemplate(const MarkdownEditorC
|
||||
|
||||
fillGlobalStyles(htmlTemplate, viewerResource, "");
|
||||
|
||||
fillThemeStyles(htmlTemplate, p_webStyleSheetFile, p_highlightStyleSheetFile);
|
||||
fillThemeStyles(htmlTemplate, p_paras.m_webStyleSheetFile, p_paras.m_highlightStyleSheetFile);
|
||||
|
||||
{
|
||||
WebGlobalOptions opts;
|
||||
@ -212,12 +204,13 @@ QString HtmlTemplateHelper::generateMarkdownViewerTemplate(const MarkdownEditorC
|
||||
opts.m_autoBreakEnabled = p_config.getAutoBreakEnabled();
|
||||
opts.m_linkifyEnabled = p_config.getLinkifyEnabled();
|
||||
opts.m_indentFirstLineEnabled = p_config.getIndentFirstLineEnabled();
|
||||
opts.m_transparentBackgroundEnabled = p_useTransparentBg;
|
||||
opts.m_scrollable = p_scrollable;
|
||||
opts.m_bodyWidth = p_bodyWidth;
|
||||
opts.m_bodyHeight = p_bodyHeight;
|
||||
opts.m_transformSvgToPngEnabled = p_transformSvgToPng;
|
||||
opts.m_mathJaxScale = p_mathJaxScale;
|
||||
opts.m_transparentBackgroundEnabled = p_paras.m_transparentBackgroundEnabled;
|
||||
opts.m_scrollable = p_paras.m_scrollable;
|
||||
opts.m_bodyWidth = p_paras.m_bodyWidth;
|
||||
opts.m_bodyHeight = p_paras.m_bodyHeight;
|
||||
opts.m_transformSvgToPngEnabled = p_paras.m_transformSvgToPngEnabled;
|
||||
opts.m_mathJaxScale = p_paras.m_mathJaxScale;
|
||||
opts.m_removeCodeToolBarEnabled = p_paras.m_removeCodeToolBarEnabled;
|
||||
fillGlobalOptions(htmlTemplate, opts);
|
||||
}
|
||||
|
||||
@ -235,17 +228,36 @@ QString HtmlTemplateHelper::generateExportTemplate(const MarkdownEditorConfig &p
|
||||
|
||||
fillGlobalStyles(htmlTemplate, exportResource, "");
|
||||
|
||||
// Outline panel.
|
||||
for (auto &ele : exportResource.m_resources) {
|
||||
fillOutlinePanel(htmlTemplate, exportResource, p_addOutlinePanel);
|
||||
|
||||
fillResourcesByContent(htmlTemplate, exportResource);
|
||||
|
||||
return htmlTemplate;
|
||||
}
|
||||
|
||||
void HtmlTemplateHelper::fillOutlinePanel(QString &p_template, WebResource &p_exportResource, bool p_addOutlinePanel)
|
||||
{
|
||||
for (auto &ele : p_exportResource.m_resources) {
|
||||
if (ele.m_name == QStringLiteral("outline")) {
|
||||
ele.m_enabled = p_addOutlinePanel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fillResourcesByContent(htmlTemplate, exportResource);
|
||||
// Remove static content to make the page clean.
|
||||
if (!p_addOutlinePanel) {
|
||||
int startIdx = p_template.indexOf("<!-- VX_OUTLINE_PANEL_START -->");
|
||||
QString endMark("<!-- VX_OUTLINE_PANEL_END -->");
|
||||
int endIdx = p_template.lastIndexOf(endMark);
|
||||
Q_ASSERT(startIdx > -1 && endIdx > startIdx);
|
||||
p_template.remove(startIdx, endIdx + endMark.size() - startIdx);
|
||||
|
||||
return htmlTemplate;
|
||||
startIdx = p_template.indexOf("<!-- VX_OUTLINE_BUTTON_START -->");
|
||||
endMark = "<!-- VX_OUTLINE_BUTTON_END -->";
|
||||
endIdx = p_template.lastIndexOf(endMark);
|
||||
Q_ASSERT(startIdx > -1 && endIdx > startIdx);
|
||||
p_template.remove(startIdx, endIdx + endMark.size() - startIdx);
|
||||
}
|
||||
}
|
||||
|
||||
void HtmlTemplateHelper::fillTitle(QString &p_template, const QString &p_title)
|
||||
|
@ -6,6 +6,7 @@
|
||||
namespace vnotex
|
||||
{
|
||||
class MarkdownEditorConfig;
|
||||
struct WebResource;
|
||||
|
||||
// Global options to be passed to Web side at the very beginning.
|
||||
struct WebGlobalOptions
|
||||
@ -48,6 +49,9 @@ namespace vnotex
|
||||
// wkhtmltopdf will make the MathJax formula too small.
|
||||
qreal m_mathJaxScale = -1;
|
||||
|
||||
// Whether remove the tool bar of code blocks added by Prism.js.
|
||||
bool m_removeCodeToolBarEnabled = false;
|
||||
|
||||
QString toJavascriptObject() const;
|
||||
};
|
||||
|
||||
@ -55,20 +59,33 @@ namespace vnotex
|
||||
class HtmlTemplateHelper
|
||||
{
|
||||
public:
|
||||
struct Paras
|
||||
{
|
||||
QString m_webStyleSheetFile;
|
||||
|
||||
QString m_highlightStyleSheetFile;
|
||||
|
||||
bool m_transparentBackgroundEnabled = false;
|
||||
|
||||
bool m_scrollable = true;
|
||||
|
||||
int m_bodyWidth = -1;
|
||||
|
||||
int m_bodyHeight = -1;
|
||||
|
||||
bool m_transformSvgToPngEnabled = false;
|
||||
|
||||
qreal m_mathJaxScale = -1;
|
||||
|
||||
bool m_removeCodeToolBarEnabled = false;
|
||||
};
|
||||
|
||||
HtmlTemplateHelper() = delete;
|
||||
|
||||
static const QString &getMarkdownViewerTemplate();
|
||||
static void updateMarkdownViewerTemplate(const MarkdownEditorConfig &p_config);
|
||||
|
||||
static QString generateMarkdownViewerTemplate(const MarkdownEditorConfig &p_config,
|
||||
const QString &p_webStyleSheetFile,
|
||||
const QString &p_highlightStyleSheetFile,
|
||||
bool p_useTransparentBg = false,
|
||||
bool p_scrollable = true,
|
||||
int p_bodyWidth = -1,
|
||||
int p_bodyHeight = -1,
|
||||
bool p_transformSvgToPng = false,
|
||||
qreal p_mathJaxScale = -1);
|
||||
static QString generateMarkdownViewerTemplate(const MarkdownEditorConfig &p_config, const Paras &p_paras);
|
||||
|
||||
static QString generateExportTemplate(const MarkdownEditorConfig &p_config,
|
||||
bool p_addOutlinePanel);
|
||||
@ -83,6 +100,8 @@ namespace vnotex
|
||||
|
||||
static void fillBodyClassList(QString &p_template, const QString &p_classList);
|
||||
|
||||
static void fillOutlinePanel(QString &p_template, WebResource &p_exportResource, bool p_addOutlinePanel);
|
||||
|
||||
private:
|
||||
struct Template
|
||||
{
|
||||
|
@ -413,3 +413,22 @@ bool Node::checkExists()
|
||||
}
|
||||
return after;
|
||||
}
|
||||
|
||||
QList<QSharedPointer<File>> Node::collectFiles()
|
||||
{
|
||||
QList<QSharedPointer<File>> files;
|
||||
|
||||
load();
|
||||
|
||||
if (hasContent()) {
|
||||
files.append(getContentFile());
|
||||
}
|
||||
|
||||
if (isContainer()) {
|
||||
for (const auto &child : m_children) {
|
||||
files.append(child->collectFiles());
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
@ -179,6 +179,9 @@ namespace vnotex
|
||||
|
||||
void sortChildren(const QVector<int> &p_beforeIdx, const QVector<int> &p_afterIdx);
|
||||
|
||||
// Get content files recursively.
|
||||
QList<QSharedPointer<File>> collectFiles();
|
||||
|
||||
static bool isAncestor(const Node *p_ancestor, const Node *p_child);
|
||||
|
||||
protected:
|
||||
|
@ -361,3 +361,20 @@ QJsonObject Notebook::getExtraConfig(const QString &p_key) const
|
||||
const auto &configs = getExtraConfigs();
|
||||
return configs.value(p_key).toObject();
|
||||
}
|
||||
|
||||
QList<QSharedPointer<File>> Notebook::collectFiles()
|
||||
{
|
||||
QList<QSharedPointer<File>> files;
|
||||
|
||||
auto rootNode = getRootNode();
|
||||
|
||||
const auto &children = rootNode->getChildrenRef();
|
||||
for (const auto &child : children) {
|
||||
if (child->getUse() != Node::Use::Normal) {
|
||||
continue;
|
||||
}
|
||||
files.append(child->collectFiles());
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ namespace vnotex
|
||||
class IVersionController;
|
||||
class INotebookConfigMgr;
|
||||
struct NodeParameters;
|
||||
class File;
|
||||
|
||||
// Base class of notebook.
|
||||
class Notebook : public QObject
|
||||
@ -140,6 +141,9 @@ namespace vnotex
|
||||
QJsonObject getExtraConfig(const QString &p_key) const;
|
||||
virtual void setExtraConfig(const QString &p_key, const QJsonObject &p_obj) = 0;
|
||||
|
||||
// Get content files recursively.
|
||||
QList<QSharedPointer<File>> collectFiles();
|
||||
|
||||
static const QString c_defaultAttachmentFolder;
|
||||
|
||||
static const QString c_defaultImageFolder;
|
||||
|
@ -48,6 +48,7 @@ QString VXNode::fetchAbsolutePath() const
|
||||
|
||||
QSharedPointer<File> VXNode::getContentFile()
|
||||
{
|
||||
Q_ASSERT(hasContent());
|
||||
// We should not keep the shared ptr of VXNodeFile, or there is a cyclic ref.
|
||||
return QSharedPointer<VXNodeFile>::create(sharedFromThis().dynamicCast<VXNode>());
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ void SessionConfig::init()
|
||||
|
||||
loadStateAndGeometry(sessionJobj);
|
||||
|
||||
m_exportOption.fromJson(sessionJobj[QStringLiteral("export_option")].toObject());
|
||||
loadExportOption(sessionJobj);
|
||||
|
||||
m_searchOption.fromJson(sessionJobj[QStringLiteral("search_option")].toObject());
|
||||
|
||||
@ -206,7 +206,7 @@ QJsonObject SessionConfig::toJson() const
|
||||
obj[QStringLiteral("core")] = saveCore();
|
||||
obj[QStringLiteral("notebooks")] = saveNotebooks();
|
||||
obj[QStringLiteral("state_geometry")] = saveStateAndGeometry();
|
||||
obj[QStringLiteral("export_option")] = m_exportOption.toJson();
|
||||
obj[QStringLiteral("export")] = saveExportOption();
|
||||
obj[QStringLiteral("search_option")] = m_searchOption.toJson();
|
||||
writeByteArray(obj, QStringLiteral("viewarea_session"), m_viewAreaSession);
|
||||
writeByteArray(obj, QStringLiteral("notebook_explorer_session"), m_notebookExplorerSession);
|
||||
@ -325,6 +325,16 @@ void SessionConfig::setExportOption(const ExportOption &p_option)
|
||||
updateConfig(m_exportOption, p_option, this);
|
||||
}
|
||||
|
||||
const QVector<ExportCustomOption> &SessionConfig::getCustomExportOptions() const
|
||||
{
|
||||
return m_customExportOptions;
|
||||
}
|
||||
|
||||
void SessionConfig::setCustomExportOptions(const QVector<ExportCustomOption> &p_options)
|
||||
{
|
||||
updateConfig(m_customExportOptions, p_options, this);
|
||||
}
|
||||
|
||||
const SearchOption &SessionConfig::getSearchOption() const
|
||||
{
|
||||
return m_searchOption;
|
||||
@ -444,3 +454,31 @@ QJsonArray SessionConfig::saveHistory() const
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
void SessionConfig::loadExportOption(const QJsonObject &p_session)
|
||||
{
|
||||
auto exportObj = p_session[QStringLiteral("export")].toObject();
|
||||
|
||||
m_exportOption.fromJson(exportObj[QStringLiteral("export_option")].toObject());
|
||||
|
||||
auto customArr = exportObj[QStringLiteral("custom_options")].toArray();
|
||||
m_customExportOptions.resize(customArr.size());
|
||||
for (int i = 0; i < customArr.size(); ++i) {
|
||||
m_customExportOptions[i].fromJson(customArr[i].toObject());
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject SessionConfig::saveExportOption() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
|
||||
obj[QStringLiteral("export_option")] = m_exportOption.toJson();
|
||||
|
||||
QJsonArray customArr;
|
||||
for (int i = 0; i < m_customExportOptions.size(); ++i) {
|
||||
customArr.push_back(m_customExportOptions[i].toJson());
|
||||
}
|
||||
obj[QStringLiteral("custom_options")] = customArr;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
@ -107,6 +107,9 @@ namespace vnotex
|
||||
const ExportOption &getExportOption() const;
|
||||
void setExportOption(const ExportOption &p_option);
|
||||
|
||||
const QVector<ExportCustomOption> &getCustomExportOptions() const;
|
||||
void setCustomExportOptions(const QVector<ExportCustomOption> &p_options);
|
||||
|
||||
const SearchOption &getSearchOption() const;
|
||||
void setSearchOption(const SearchOption &p_option);
|
||||
|
||||
@ -151,6 +154,10 @@ namespace vnotex
|
||||
|
||||
QJsonArray saveHistory() const;
|
||||
|
||||
void loadExportOption(const QJsonObject &p_session);
|
||||
|
||||
QJsonObject saveExportOption() const;
|
||||
|
||||
QString m_newNotebookDefaultRootFolderPath;
|
||||
|
||||
// Use root folder to identify a notebook uniquely.
|
||||
@ -173,6 +180,8 @@ namespace vnotex
|
||||
|
||||
ExportOption m_exportOption;
|
||||
|
||||
QVector<ExportCustomOption> m_customExportOptions;
|
||||
|
||||
SearchOption m_searchOption;
|
||||
|
||||
QByteArray m_viewAreaSession;
|
||||
|
@ -86,10 +86,24 @@ class PrismRenderer extends VxWorker {
|
||||
p_containerNode.classList.add('line-numbers');
|
||||
|
||||
Prism.highlightAllUnder(p_containerNode, false /* async or not */);
|
||||
|
||||
// Remove the toolbar.
|
||||
if (window.vxOptions.removeCodeToolBarEnabled) {
|
||||
this.removeToolBar(p_containerNode);
|
||||
}
|
||||
}
|
||||
|
||||
this.finishWork();
|
||||
}
|
||||
|
||||
removeToolBar(p_containerNode) {
|
||||
// Static list.
|
||||
let toolBarNodes = p_containerNode.querySelectorAll('div.code-toolbar > div.toolbar');
|
||||
for (let i = 0; i < toolBarNodes.length; ++i) {
|
||||
toolBarNodes[i].outerHTML = '';
|
||||
delete toolBarNodes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.vnotex.registerWorker(new PrismRenderer());
|
||||
|
@ -27,19 +27,24 @@
|
||||
<body class="<!-- VX_BODY_CLASS_LIST_PLACEHOLDER -->">
|
||||
<div class="container-fluid">
|
||||
<div class="row flex-xl-nowrap">
|
||||
<!-- VX_OUTLINE_PANEL_START -->
|
||||
<div id="outline-panel" style="display:none;" class="d-none d-md-block d-xl-block col-md-3 col-xl-2 bd-toc">
|
||||
<div id="outline-content" class="section-nav"></div>
|
||||
</div>
|
||||
<!-- VX_OUTLINE_PANEL_END -->
|
||||
|
||||
<div id="post-content" class="col-12 col-md-9 col-xl-10 py-md-3 pl-md-5 bd-content">
|
||||
<!-- VX_CONTENT_PLACEHOLDER -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VX_OUTLINE_BUTTON_START -->
|
||||
<div id="container-floating" style="display:none;" class="d-none d-md-block d-xl-block">
|
||||
<div id="floating-button" onclick="toggleMore()">
|
||||
<p id="floating-more" class="more">></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- VX_OUTLINE_BUTTON_END -->
|
||||
</body>
|
||||
</html>
|
||||
|
@ -83,6 +83,45 @@ bool ExportPdfOption::operator==(const ExportPdfOption &p_other) const
|
||||
&& m_wkhtmltopdfArgs == p_other.m_wkhtmltopdfArgs;
|
||||
}
|
||||
|
||||
QJsonObject ExportCustomOption::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["name"] = m_name;
|
||||
obj["target_suffix"] = m_targetSuffix;
|
||||
obj["command"] = m_command;
|
||||
obj["use_html_input"] = m_useHtmlInput;
|
||||
obj["all_in_one"] = m_allInOne;
|
||||
obj["target_page_scrollable"] = m_targetPageScrollable;
|
||||
obj["resource_path_separator"] = m_resourcePathSeparator;
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ExportCustomOption::fromJson(const QJsonObject &p_obj)
|
||||
{
|
||||
if (p_obj.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_name = p_obj["name"].toString();
|
||||
m_targetSuffix = p_obj["target_suffix"].toString();
|
||||
m_command = p_obj["command"].toString();
|
||||
m_useHtmlInput = p_obj["use_html_input"].toBool();
|
||||
m_allInOne = p_obj["all_in_one"].toBool();
|
||||
m_targetPageScrollable = p_obj["target_page_scrollable"].toBool();
|
||||
m_resourcePathSeparator = p_obj["resource_path_separator"].toString();
|
||||
}
|
||||
|
||||
bool ExportCustomOption::operator==(const ExportCustomOption &p_other) const
|
||||
{
|
||||
return m_name == p_other.m_name
|
||||
&& m_useHtmlInput == p_other.m_useHtmlInput
|
||||
&& m_targetSuffix == p_other.m_targetSuffix
|
||||
&& m_command == p_other.m_command
|
||||
&& m_allInOne == p_other.m_allInOne
|
||||
&& m_targetPageScrollable == p_other.m_targetPageScrollable
|
||||
&& m_resourcePathSeparator == p_other.m_resourcePathSeparator;
|
||||
}
|
||||
|
||||
QJsonObject ExportOption::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@ -93,6 +132,7 @@ QJsonObject ExportOption::toJson() const
|
||||
obj["export_attachments"] = m_exportAttachments;
|
||||
obj["html_option"] = m_htmlOption.toJson();
|
||||
obj["pdf_option"] = m_pdfOption.toJson();
|
||||
obj["custom_export"] = m_customExport;
|
||||
return obj;
|
||||
}
|
||||
|
||||
@ -131,6 +171,7 @@ void ExportOption::fromJson(const QJsonObject &p_obj)
|
||||
m_exportAttachments = p_obj["export_attachments"].toBool();
|
||||
m_htmlOption.fromJson(p_obj["html_option"].toObject());
|
||||
m_pdfOption.fromJson(p_obj["pdf_option"].toObject());
|
||||
m_customExport = p_obj["custom_export"].toString();
|
||||
}
|
||||
|
||||
bool ExportOption::operator==(const ExportOption &p_other) const
|
||||
|
@ -69,6 +69,34 @@ namespace vnotex
|
||||
QString m_wkhtmltopdfArgs;
|
||||
};
|
||||
|
||||
struct ExportCustomOption
|
||||
{
|
||||
QJsonObject toJson() const;
|
||||
void fromJson(const QJsonObject &p_obj);
|
||||
|
||||
bool operator==(const ExportCustomOption &p_other) const;
|
||||
|
||||
QString m_name;
|
||||
|
||||
QString m_targetSuffix;
|
||||
|
||||
QString m_command;
|
||||
|
||||
bool m_useHtmlInput = true;
|
||||
|
||||
bool m_allInOne = false;
|
||||
|
||||
// Whether the page of target format is scrollable.
|
||||
bool m_targetPageScrollable = false;
|
||||
|
||||
// The default value here follows the rules of Pandoc.
|
||||
#if defined(Q_OS_WIN)
|
||||
QString m_resourcePathSeparator = ";";
|
||||
#else
|
||||
QString m_resourcePathSeparator = ":";
|
||||
#endif
|
||||
};
|
||||
|
||||
struct ExportOption
|
||||
{
|
||||
QJsonObject toJson() const;
|
||||
@ -95,6 +123,15 @@ namespace vnotex
|
||||
ExportHtmlOption m_htmlOption;
|
||||
|
||||
ExportPdfOption m_pdfOption;
|
||||
|
||||
QString m_customExport;
|
||||
|
||||
// Following fields are used in runtime only.
|
||||
ExportCustomOption *m_customOption = nullptr;
|
||||
|
||||
bool m_transformSvgToPngEnabled = false;
|
||||
|
||||
bool m_removeCodeToolBarEnabled = true;
|
||||
};
|
||||
|
||||
inline QString exportFormatString(ExportFormat p_format)
|
||||
|
@ -8,9 +8,12 @@
|
||||
#include <buffer/buffer.h>
|
||||
#include <core/file.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/utils.h>
|
||||
#include <utils/pathutils.h>
|
||||
#include <utils/processutils.h>
|
||||
#include <utils/contentmediautils.h>
|
||||
#include "webviewexporter.h"
|
||||
#include <core/exception.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
@ -125,10 +128,13 @@ QString Exporter::doExport(const ExportOption &p_option, Node *p_note)
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
QString Exporter::doExportAllInOne(const ExportOption &p_option, Node *p_folder)
|
||||
QString Exporter::doExportPdfAllInOne(const ExportOption &p_option, Notebook *p_notebook, Node *p_folder)
|
||||
{
|
||||
Q_ASSERT((p_notebook || p_folder) && !(p_notebook && p_folder));
|
||||
|
||||
// Make path.
|
||||
const auto outputFolder = makeOutputFolder(p_option.m_outputDir, p_folder->getName());
|
||||
const auto name = p_notebook ? tr("notebook_%1").arg(p_notebook->getName()) : p_folder->getName();
|
||||
const auto outputFolder = makeOutputFolder(p_option.m_outputDir, name);
|
||||
if (outputFolder.isEmpty()) {
|
||||
emit logRequested(tr("Failed to create output folder under (%1).").arg(p_option.m_outputDir));
|
||||
return QString();
|
||||
@ -143,7 +149,15 @@ QString Exporter::doExportAllInOne(const ExportOption &p_option, Node *p_folder)
|
||||
|
||||
auto tmpOption(getExportOptionForIntermediateHtml(p_option, tmpDir.path()));
|
||||
|
||||
auto htmlFiles = doExport(tmpOption, tmpDir.path(), p_folder);
|
||||
QStringList htmlFiles;
|
||||
if (p_notebook) {
|
||||
htmlFiles = doExportNotebook(tmpOption, tmpDir.path(), p_notebook);
|
||||
} else {
|
||||
htmlFiles = doExport(tmpOption, tmpDir.path(), p_folder);
|
||||
}
|
||||
|
||||
cleanUpWebViewExporter();
|
||||
|
||||
if (htmlFiles.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
@ -152,8 +166,6 @@ QString Exporter::doExportAllInOne(const ExportOption &p_option, Node *p_folder)
|
||||
return QString();
|
||||
}
|
||||
|
||||
cleanUpWebViewExporter();
|
||||
|
||||
auto fileName = FileUtils::generateFileNameWithSequence(outputFolder,
|
||||
tr("all_in_one_export"),
|
||||
"pdf");
|
||||
@ -166,17 +178,24 @@ QString Exporter::doExportAllInOne(const ExportOption &p_option, Node *p_folder)
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString Exporter::doExportAllInOne(const ExportOption &p_option, Notebook *p_notebook)
|
||||
QString Exporter::doExportCustomAllInOne(const ExportOption &p_option, Notebook *p_notebook, Node *p_folder)
|
||||
{
|
||||
Q_ASSERT((p_notebook || p_folder) && !(p_notebook && p_folder));
|
||||
|
||||
// Make path.
|
||||
const auto outputFolder = makeOutputFolder(p_option.m_outputDir, tr("notebook_%1").arg(p_notebook->getName()));
|
||||
const auto name = p_notebook ? tr("notebook_%1").arg(p_notebook->getName()) : p_folder->getName();
|
||||
const auto outputFolder = makeOutputFolder(p_option.m_outputDir, name);
|
||||
if (outputFolder.isEmpty()) {
|
||||
emit logRequested(tr("Failed to create output folder under (%1).").arg(p_option.m_outputDir));
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Export to HTML to a tmp dir first.
|
||||
QStringList inputFiles;
|
||||
QStringList resourcePaths;
|
||||
|
||||
QTemporaryDir tmpDir;
|
||||
if (p_option.m_customOption->m_useHtmlInput) {
|
||||
// Export to HTML to a tmp dir first.
|
||||
if (!tmpDir.isValid()) {
|
||||
emit logRequested(tr("Failed to create temporary dir to hold HTML files."));
|
||||
return QString();
|
||||
@ -184,7 +203,15 @@ QString Exporter::doExportAllInOne(const ExportOption &p_option, Notebook *p_not
|
||||
|
||||
auto tmpOption(getExportOptionForIntermediateHtml(p_option, tmpDir.path()));
|
||||
|
||||
auto htmlFiles = doExportNotebook(tmpOption, tmpDir.path(), p_notebook);
|
||||
QStringList htmlFiles;
|
||||
if (p_notebook) {
|
||||
htmlFiles = doExportNotebook(tmpOption, tmpDir.path(), p_notebook);
|
||||
} else {
|
||||
htmlFiles = doExport(tmpOption, tmpDir.path(), p_folder);
|
||||
}
|
||||
|
||||
cleanUpWebViewExporter();
|
||||
|
||||
if (htmlFiles.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
@ -193,13 +220,36 @@ QString Exporter::doExportAllInOne(const ExportOption &p_option, Notebook *p_not
|
||||
return QString();
|
||||
}
|
||||
|
||||
cleanUpWebViewExporter();
|
||||
inputFiles = htmlFiles;
|
||||
for (const auto &file : htmlFiles) {
|
||||
resourcePaths << PathUtils::parentDirPath(file);
|
||||
}
|
||||
} else {
|
||||
// Collect source files.
|
||||
if (p_notebook) {
|
||||
collectFiles(p_notebook->collectFiles(), inputFiles, resourcePaths);
|
||||
} else {
|
||||
collectFiles(p_folder->collectFiles(), inputFiles, resourcePaths);
|
||||
}
|
||||
|
||||
if (checkAskedToStop()) {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
if (inputFiles.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
auto fileName = FileUtils::generateFileNameWithSequence(outputFolder,
|
||||
tr("all_in_one_export"),
|
||||
"pdf");
|
||||
p_option.m_customOption->m_targetSuffix);
|
||||
auto destFilePath = PathUtils::concatenateFilePath(outputFolder, fileName);
|
||||
if (getWebViewExporter(p_option)->htmlToPdfViaWkhtmltopdf(p_option.m_pdfOption, htmlFiles, destFilePath)) {
|
||||
bool success = doExportCustom(p_option,
|
||||
inputFiles,
|
||||
resourcePaths,
|
||||
destFilePath);
|
||||
if (success) {
|
||||
emit logRequested(tr("Exported to (%1).").arg(destFilePath));
|
||||
return destFilePath;
|
||||
}
|
||||
@ -213,8 +263,16 @@ QStringList Exporter::doExportFolder(const ExportOption &p_option, Node *p_folde
|
||||
|
||||
QStringList outputFiles;
|
||||
|
||||
if (p_option.m_targetFormat == ExportFormat::PDF && p_option.m_pdfOption.m_useWkhtmltopdf && p_option.m_pdfOption.m_allInOne) {
|
||||
auto file = doExportAllInOne(p_option, p_folder);
|
||||
if (p_option.m_targetFormat == ExportFormat::PDF
|
||||
&& p_option.m_pdfOption.m_useWkhtmltopdf
|
||||
&& p_option.m_pdfOption.m_allInOne) {
|
||||
auto file = doExportPdfAllInOne(p_option, nullptr, p_folder);
|
||||
if (!file.isEmpty()) {
|
||||
outputFiles << file;
|
||||
}
|
||||
} else if (p_option.m_targetFormat == ExportFormat::Custom
|
||||
&& p_option.m_customOption->m_allInOne) {
|
||||
auto file = doExportCustomAllInOne(p_option, nullptr, p_folder);
|
||||
if (!file.isEmpty()) {
|
||||
outputFiles << file;
|
||||
}
|
||||
@ -240,7 +298,15 @@ QStringList Exporter::doExport(const ExportOption &p_option, const QString &p_ou
|
||||
return outputFiles;
|
||||
}
|
||||
|
||||
try {
|
||||
p_folder->load();
|
||||
} catch (Exception &p_e) {
|
||||
QString msg = tr("Failed to load node (%1) (%2).").arg(p_folder->fetchPath(), p_e.what());
|
||||
qWarning() << msg;
|
||||
emit logRequested(msg);
|
||||
return outputFiles;
|
||||
}
|
||||
|
||||
const auto &children = p_folder->getChildrenRef();
|
||||
emit progressUpdated(0, children.size());
|
||||
for (int i = 0; i < children.size(); ++i) {
|
||||
@ -282,6 +348,10 @@ QString Exporter::doExport(const ExportOption &p_option, const QString &p_output
|
||||
outputFile = doExportPdf(p_option, p_outputDir, p_file);
|
||||
break;
|
||||
|
||||
case ExportFormat::Custom:
|
||||
outputFile = doExportCustom(p_option, p_outputDir, p_file);
|
||||
break;
|
||||
|
||||
default:
|
||||
emit logRequested(tr("Unknown target format %1.").arg(exportFormatString(p_option.m_targetFormat)));
|
||||
break;
|
||||
@ -302,8 +372,16 @@ QStringList Exporter::doExport(const ExportOption &p_option, Notebook *p_noteboo
|
||||
|
||||
QStringList outputFiles;
|
||||
|
||||
if (p_option.m_targetFormat == ExportFormat::PDF && p_option.m_pdfOption.m_useWkhtmltopdf && p_option.m_pdfOption.m_allInOne) {
|
||||
auto file = doExportAllInOne(p_option, p_notebook);
|
||||
if (p_option.m_targetFormat == ExportFormat::PDF
|
||||
&& p_option.m_pdfOption.m_useWkhtmltopdf
|
||||
&& p_option.m_pdfOption.m_allInOne) {
|
||||
auto file = doExportPdfAllInOne(p_option, p_notebook, nullptr);
|
||||
if (!file.isEmpty()) {
|
||||
outputFiles << file;
|
||||
}
|
||||
} else if (p_option.m_targetFormat == ExportFormat::Custom
|
||||
&& p_option.m_customOption->m_allInOne) {
|
||||
auto file = doExportCustomAllInOne(p_option, p_notebook, nullptr);
|
||||
if (!file.isEmpty()) {
|
||||
outputFiles << file;
|
||||
}
|
||||
@ -454,15 +532,158 @@ QString Exporter::doExportPdf(const ExportOption &p_option, const QString &p_out
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
QString Exporter::doExportCustom(const ExportOption &p_option, const QString &p_outputDir, const File *p_file)
|
||||
{
|
||||
Q_ASSERT(p_option.m_customOption);
|
||||
QStringList inputFiles;
|
||||
QStringList resourcePaths;
|
||||
|
||||
QTemporaryDir tmpDir;
|
||||
if (p_option.m_customOption->m_useHtmlInput) {
|
||||
// Export to HTML to a tmp dir first.
|
||||
if (!tmpDir.isValid()) {
|
||||
emit logRequested(tr("Failed to create temporary dir to hold HTML files."));
|
||||
return QString();
|
||||
}
|
||||
|
||||
auto tmpOption(getExportOptionForIntermediateHtml(p_option, tmpDir.path()));
|
||||
auto htmlFile = doExport(tmpOption, tmpDir.path(), p_file);
|
||||
if (htmlFile.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (checkAskedToStop()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
cleanUpWebViewExporter();
|
||||
|
||||
inputFiles << htmlFile;
|
||||
resourcePaths << PathUtils::parentDirPath(htmlFile);
|
||||
} else {
|
||||
inputFiles << p_file->getContentPath();
|
||||
resourcePaths << p_file->getResourcePath();
|
||||
}
|
||||
|
||||
auto fileName = FileUtils::generateFileNameWithSequence(p_outputDir,
|
||||
QFileInfo(p_file->getName()).completeBaseName(),
|
||||
p_option.m_customOption->m_targetSuffix);
|
||||
auto destFilePath = PathUtils::concatenateFilePath(p_outputDir, fileName);
|
||||
|
||||
bool success = doExportCustom(p_option,
|
||||
inputFiles,
|
||||
resourcePaths,
|
||||
destFilePath);
|
||||
if (success) {
|
||||
// Copy attachments if available.
|
||||
if (p_option.m_exportAttachments) {
|
||||
exportAttachments(p_file->getNode(), p_file->getFilePath(), p_outputDir, destFilePath);
|
||||
}
|
||||
|
||||
return destFilePath;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
ExportOption Exporter::getExportOptionForIntermediateHtml(const ExportOption &p_option, const QString &p_outputDir)
|
||||
{
|
||||
ExportOption tmpOption(p_option);
|
||||
tmpOption.m_exportAttachments = false;
|
||||
tmpOption.m_targetFormat = ExportFormat::HTML;
|
||||
tmpOption.m_transformSvgToPngEnabled = true;
|
||||
tmpOption.m_removeCodeToolBarEnabled = true;
|
||||
|
||||
tmpOption.m_htmlOption.m_embedStyles = true;
|
||||
tmpOption.m_htmlOption.m_completePage = true;
|
||||
tmpOption.m_htmlOption.m_embedImages = false;
|
||||
tmpOption.m_htmlOption.m_useMimeHtmlFormat = false;
|
||||
tmpOption.m_htmlOption.m_addOutlinePanel = false;
|
||||
tmpOption.m_htmlOption.m_scrollable = false;
|
||||
if (p_option.m_targetFormat == ExportFormat::Custom && p_option.m_customOption->m_targetPageScrollable) {
|
||||
tmpOption.m_htmlOption.m_scrollable = true;
|
||||
}
|
||||
tmpOption.m_outputDir = p_outputDir;
|
||||
|
||||
return tmpOption;
|
||||
}
|
||||
|
||||
bool Exporter::doExportCustom(const ExportOption &p_option,
|
||||
const QStringList &p_files,
|
||||
const QStringList &p_resourcePaths,
|
||||
const QString &p_filePath)
|
||||
{
|
||||
const auto cmd = evaluateCommand(p_option,
|
||||
p_files,
|
||||
p_resourcePaths,
|
||||
p_filePath);
|
||||
|
||||
emit logRequested(tr("Custom command: %1").arg(cmd));
|
||||
qDebug() << "custom export" << cmd;
|
||||
|
||||
auto state = ProcessUtils::start(cmd,
|
||||
[this](const QString &msg) {
|
||||
emit logRequested(msg);
|
||||
},
|
||||
m_askedToStop);
|
||||
|
||||
return state == ProcessUtils::Succeeded;
|
||||
}
|
||||
|
||||
QString Exporter::evaluateCommand(const ExportOption &p_option,
|
||||
const QStringList &p_files,
|
||||
const QStringList &p_resourcePaths,
|
||||
const QString &p_filePath)
|
||||
{
|
||||
auto cmd(p_option.m_customOption->m_command);
|
||||
|
||||
QString inputs;
|
||||
for (int i = 0; i < p_files.size(); ++i) {
|
||||
if (i > 0) {
|
||||
inputs += " ";
|
||||
}
|
||||
|
||||
inputs += getQuotedPath(p_files[i]);
|
||||
}
|
||||
|
||||
QString resourcePath;
|
||||
for (int i = 0; i < p_resourcePaths.size(); ++i) {
|
||||
bool duplicated = false;
|
||||
for (int j = 0; j < i; ++j) {
|
||||
if (p_resourcePaths[j] == p_resourcePaths[i]) {
|
||||
// Deduplicate.
|
||||
duplicated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicated) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
resourcePath += p_option.m_customOption->m_resourcePathSeparator;
|
||||
}
|
||||
|
||||
resourcePath += getQuotedPath(p_resourcePaths[i]);
|
||||
}
|
||||
|
||||
cmd.replace("%1", inputs);
|
||||
cmd.replace("%2", resourcePath);
|
||||
cmd.replace("%3", getQuotedPath(p_option.m_renderingStyleFile));
|
||||
cmd.replace("%4", getQuotedPath(p_option.m_syntaxHighlightStyleFile));
|
||||
cmd.replace("%5", getQuotedPath(p_filePath));
|
||||
return cmd;
|
||||
}
|
||||
|
||||
QString Exporter::getQuotedPath(const QString &p_path)
|
||||
{
|
||||
return QString("\"%1\"").arg(QDir::toNativeSeparators(p_path));
|
||||
}
|
||||
|
||||
void Exporter::collectFiles(const QList<QSharedPointer<File>> &p_files, QStringList &p_inputFiles, QStringList &p_resourcePaths)
|
||||
{
|
||||
for (const auto &file : p_files) {
|
||||
p_inputFiles << file->getContentPath();
|
||||
p_resourcePaths << file->getResourcePath();
|
||||
}
|
||||
}
|
||||
|
@ -50,9 +50,17 @@ namespace vnotex
|
||||
|
||||
QString doExportPdf(const ExportOption &p_option, const QString &p_outputDir, const File *p_file);
|
||||
|
||||
QString doExportAllInOne(const ExportOption &p_option, Node *p_folder);
|
||||
QString doExportCustom(const ExportOption &p_option, const QString &p_outputDir, const File *p_file);
|
||||
|
||||
QString doExportAllInOne(const ExportOption &p_option, Notebook *p_notebook);
|
||||
bool doExportCustom(const ExportOption &p_option,
|
||||
const QStringList &p_files,
|
||||
const QStringList &p_resourcePaths,
|
||||
const QString &p_filePath);
|
||||
|
||||
// Export @p_notebook or @p_folder. @p_folder will be considered only when @p_notebook is null.
|
||||
QString doExportPdfAllInOne(const ExportOption &p_option, Notebook *p_notebook, Node *p_folder);
|
||||
|
||||
QString doExportCustomAllInOne(const ExportOption &p_option, Notebook *p_notebook, Node *p_folder);
|
||||
|
||||
void exportAttachments(Node *p_node,
|
||||
const QString &p_srcFilePath,
|
||||
@ -71,6 +79,15 @@ namespace vnotex
|
||||
|
||||
static ExportOption getExportOptionForIntermediateHtml(const ExportOption &p_option, const QString &p_outputDir);
|
||||
|
||||
static QString evaluateCommand(const ExportOption &p_option,
|
||||
const QStringList &p_files,
|
||||
const QStringList &p_resourcePaths,
|
||||
const QString &p_filePath);
|
||||
|
||||
static QString getQuotedPath(const QString &p_path);
|
||||
|
||||
static void collectFiles(const QList<QSharedPointer<File>> &p_files, QStringList &p_inputFiles, QStringList &p_resourcePaths);
|
||||
|
||||
// Managed by QObject.
|
||||
WebViewExporter *m_webViewExporter = nullptr;
|
||||
|
||||
|
@ -248,6 +248,8 @@ QSize WebViewExporter::pageLayoutSize(const QPageLayout &p_layout) const
|
||||
void WebViewExporter::prepare(const ExportOption &p_option)
|
||||
{
|
||||
Q_ASSERT(!m_viewer && !m_exportOngoing);
|
||||
Q_ASSERT(p_option.m_targetFormat == ExportFormat::PDF || p_option.m_targetFormat == ExportFormat::HTML);
|
||||
|
||||
{
|
||||
// Adapter will be managed by MarkdownViewer.
|
||||
auto adapter = new MarkdownViewerAdapter(this);
|
||||
@ -265,7 +267,8 @@ void WebViewExporter::prepare(const ExportOption &p_option)
|
||||
|
||||
bool scrollable = true;
|
||||
if (p_option.m_targetFormat == ExportFormat::PDF
|
||||
|| (p_option.m_targetFormat == ExportFormat::HTML && !p_option.m_htmlOption.m_scrollable)) {
|
||||
|| (p_option.m_targetFormat == ExportFormat::HTML && !p_option.m_htmlOption.m_scrollable)
|
||||
|| (p_option.m_targetFormat == ExportFormat::Custom && !p_option.m_customOption->m_targetPageScrollable)) {
|
||||
scrollable = false;
|
||||
}
|
||||
|
||||
@ -276,16 +279,21 @@ void WebViewExporter::prepare(const ExportOption &p_option)
|
||||
useWkhtmltopdf = p_option.m_pdfOption.m_useWkhtmltopdf;
|
||||
pageBodySize = pageLayoutSize(*(p_option.m_pdfOption.m_layout));
|
||||
}
|
||||
|
||||
qDebug() << "export page body size" << pageBodySize;
|
||||
m_htmlTemplate = HtmlTemplateHelper::generateMarkdownViewerTemplate(config,
|
||||
p_option.m_renderingStyleFile,
|
||||
p_option.m_syntaxHighlightStyleFile,
|
||||
p_option.m_useTransparentBg,
|
||||
scrollable,
|
||||
pageBodySize.width(),
|
||||
pageBodySize.height(),
|
||||
useWkhtmltopdf,
|
||||
useWkhtmltopdf ? 2.5 : -1);
|
||||
|
||||
HtmlTemplateHelper::Paras paras;
|
||||
paras.m_webStyleSheetFile = p_option.m_renderingStyleFile;
|
||||
paras.m_highlightStyleSheetFile = p_option.m_syntaxHighlightStyleFile;
|
||||
paras.m_transparentBackgroundEnabled = p_option.m_useTransparentBg;
|
||||
paras.m_scrollable = scrollable;
|
||||
paras.m_bodyWidth = pageBodySize.width();
|
||||
paras.m_bodyHeight = pageBodySize.height();
|
||||
paras.m_transformSvgToPngEnabled = p_option.m_transformSvgToPngEnabled;
|
||||
paras.m_mathJaxScale = useWkhtmltopdf ? 2.5 : -1;
|
||||
paras.m_removeCodeToolBarEnabled = p_option.m_removeCodeToolBarEnabled;
|
||||
|
||||
m_htmlTemplate = HtmlTemplateHelper::generateMarkdownViewerTemplate(config, paras);
|
||||
|
||||
{
|
||||
const bool addOutlinePanel = p_option.m_targetFormat == ExportFormat::HTML && p_option.m_htmlOption.m_addOutlinePanel;
|
||||
|
@ -128,16 +128,31 @@ ProcessUtils::State ProcessUtils::start(const QString &p_program,
|
||||
{
|
||||
QProcess proc;
|
||||
proc.start(p_program, p_args);
|
||||
return handleProcess(&proc, p_logger, p_askedToStop);
|
||||
}
|
||||
|
||||
if (!proc.waitForStarted()) {
|
||||
ProcessUtils::State ProcessUtils::start(const QString &p_command,
|
||||
const std::function<void(const QString &)> &p_logger,
|
||||
const bool &p_askedToStop)
|
||||
{
|
||||
QProcess proc;
|
||||
proc.start(p_command);
|
||||
return handleProcess(&proc, p_logger, p_askedToStop);
|
||||
}
|
||||
|
||||
ProcessUtils::State ProcessUtils::handleProcess(QProcess *p_process,
|
||||
const std::function<void(const QString &)> &p_logger,
|
||||
const bool &p_askedToStop)
|
||||
{
|
||||
if (!p_process->waitForStarted()) {
|
||||
return State::FailedToStart;
|
||||
}
|
||||
|
||||
while (proc.state() != QProcess::NotRunning) {
|
||||
while (p_process->state() != QProcess::NotRunning) {
|
||||
Utils::sleepWait(100);
|
||||
|
||||
auto outBa = proc.readAllStandardOutput();
|
||||
auto errBa = proc.readAllStandardError();
|
||||
auto outBa = p_process->readAllStandardOutput();
|
||||
auto errBa = p_process->readAllStandardError();
|
||||
QString msg;
|
||||
if (!outBa.isEmpty()) {
|
||||
msg += QString::fromLocal8Bit(outBa);
|
||||
@ -154,7 +169,7 @@ ProcessUtils::State ProcessUtils::start(const QString &p_program,
|
||||
}
|
||||
}
|
||||
|
||||
return proc.exitStatus() == QProcess::NormalExit ? State::Succeeded : State::Crashed;
|
||||
return p_process->exitStatus() == QProcess::NormalExit ? State::Succeeded : State::Crashed;
|
||||
}
|
||||
|
||||
void ProcessUtils::startDetached(const QString &p_command)
|
||||
|
@ -35,6 +35,10 @@ namespace vnotex
|
||||
const std::function<void(const QString &)> &p_logger,
|
||||
const bool &p_askedToStop);
|
||||
|
||||
static State start(const QString &p_command,
|
||||
const std::function<void(const QString &)> &p_logger,
|
||||
const bool &p_askedToStop);
|
||||
|
||||
static void startDetached(const QString &p_command);
|
||||
|
||||
// Copied from QProcess code.
|
||||
@ -48,6 +52,10 @@ namespace vnotex
|
||||
int &p_exitCodeOnSuccess,
|
||||
QByteArray &p_stdOut,
|
||||
QByteArray &p_stdErr);
|
||||
|
||||
static State handleProcess(QProcess *p_process,
|
||||
const std::function<void(const QString &)> &p_logger,
|
||||
const bool &p_askedToStop);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <QPrinter>
|
||||
#include <QPageSetupDialog>
|
||||
#include <QPageLayout>
|
||||
#include <QInputDialog>
|
||||
|
||||
#include <notebook/notebook.h>
|
||||
#include <notebook/node.h>
|
||||
@ -33,6 +34,7 @@
|
||||
#include <utils/clipboardutils.h>
|
||||
#include <export/exporter.h>
|
||||
#include <widgets/locationinputwithbrowsebutton.h>
|
||||
#include <widgets/messageboxhelper.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
@ -154,6 +156,10 @@ QGroupBox *ExportDialog::setupTargetGroup(QWidget *p_parent)
|
||||
settings = AdvancedSettings::PDF;
|
||||
break;
|
||||
|
||||
case static_cast<int>(ExportFormat::Custom):
|
||||
settings = AdvancedSettings::Custom;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -283,7 +289,9 @@ QString ExportDialog::getOutputDir() const
|
||||
void ExportDialog::initOptions()
|
||||
{
|
||||
// Read it from config.
|
||||
m_option = ConfigMgr::getInst().getSessionConfig().getExportOption();
|
||||
const auto &sessionConfig = ConfigMgr::getInst().getSessionConfig();
|
||||
m_option = sessionConfig.getExportOption();
|
||||
m_customOptions = sessionConfig.getCustomExportOptions();
|
||||
|
||||
const auto &theme = VNoteX::getInst().getThemeMgr().getCurrentTheme();
|
||||
m_option.m_renderingStyleFile = theme.getFile(Theme::File::WebStyleSheet);
|
||||
@ -292,6 +300,10 @@ void ExportDialog::initOptions()
|
||||
if (m_option.m_outputDir.isEmpty()) {
|
||||
m_option.m_outputDir = getDefaultOutputDir();
|
||||
}
|
||||
|
||||
if (findCustomOption(m_option.m_customExport) == -1) {
|
||||
m_option.m_customExport = m_customOptions.isEmpty() ? QString() : m_customOptions[0].m_name;
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::restoreFields(const ExportOption &p_option)
|
||||
@ -351,6 +363,10 @@ void ExportDialog::saveFields(ExportOption &p_option)
|
||||
if (m_advancedSettings[AdvancedSettings::PDF]) {
|
||||
saveFields(p_option.m_pdfOption);
|
||||
}
|
||||
|
||||
if (m_advancedSettings[AdvancedSettings::Custom]) {
|
||||
saveCustomFields(p_option);
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::startExport()
|
||||
@ -417,6 +433,17 @@ int ExportDialog::doExport(ExportOption p_option)
|
||||
appendLog(tr("Please specify a valid wkhtmltopdf executable file (%1)").arg(wkExePath));
|
||||
return 0;
|
||||
}
|
||||
|
||||
p_option.m_transformSvgToPngEnabled = true;
|
||||
p_option.m_removeCodeToolBarEnabled = true;
|
||||
} else if (p_option.m_targetFormat == ExportFormat::Custom) {
|
||||
int optIdx = findCustomOption(p_option.m_customExport);
|
||||
if (optIdx == -1) {
|
||||
appendLog(tr("Please specify a valid scheme"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
p_option.m_customOption = &m_customOptions[optIdx];
|
||||
}
|
||||
|
||||
int exportedFilesCount = 0;
|
||||
@ -562,6 +589,10 @@ void ExportDialog::showAdvancedSettings(AdvancedSettings p_settings)
|
||||
widget = getPdfAdvancedSettings();
|
||||
break;
|
||||
|
||||
case AdvancedSettings::Custom:
|
||||
widget = getCustomAdvancedSettings();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -640,7 +671,7 @@ QWidget *ExportDialog::getPdfAdvancedSettings()
|
||||
}
|
||||
|
||||
{
|
||||
m_allInOneCheckBox = WidgetsFactory::createCheckBox(tr("All In One"), widget);
|
||||
m_allInOneCheckBox = WidgetsFactory::createCheckBox(tr("All-In-One"), widget);
|
||||
m_allInOneCheckBox->setToolTip(tr("Export all source files into one file"));
|
||||
connect(m_useWkhtmltopdfCheckBox, &QCheckBox::stateChanged,
|
||||
this, [this](int p_state) {
|
||||
@ -686,6 +717,103 @@ QWidget *ExportDialog::getPdfAdvancedSettings()
|
||||
return m_advancedSettings[AdvancedSettings::PDF];
|
||||
}
|
||||
|
||||
QWidget *ExportDialog::getCustomAdvancedSettings()
|
||||
{
|
||||
if (!m_advancedSettings[AdvancedSettings::Custom]) {
|
||||
QWidget *widget = new QWidget(m_advancedGroupBox);
|
||||
auto layout = WidgetsFactory::createFormLayout(widget);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
{
|
||||
auto schemeLayout = new QHBoxLayout();
|
||||
layout->addRow(tr("Scheme:"), schemeLayout);
|
||||
|
||||
m_customExportComboBox = WidgetsFactory::createComboBox(widget);
|
||||
m_customExportComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
schemeLayout->addWidget(m_customExportComboBox, 1);
|
||||
|
||||
auto addBtn = new QPushButton(tr("New"), widget);
|
||||
addBtn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
connect(addBtn, &QPushButton::clicked,
|
||||
this, &ExportDialog::addCustomExportScheme);
|
||||
schemeLayout->addWidget(addBtn);
|
||||
|
||||
auto delBtn = new QPushButton(tr("Delete"), widget);
|
||||
delBtn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
connect(delBtn, &QPushButton::clicked,
|
||||
this, &ExportDialog::removeCustomExportScheme);
|
||||
schemeLayout->addWidget(delBtn);
|
||||
}
|
||||
|
||||
{
|
||||
m_targetSuffixLineEdit = WidgetsFactory::createLineEdit(widget);
|
||||
m_targetSuffixLineEdit->setToolTip(tr("Suffix of the target file like docs/pdf/epub"));
|
||||
m_targetSuffixLineEdit->setEnabled(false);
|
||||
layout->addRow(tr("Target file suffix:"), m_targetSuffixLineEdit);
|
||||
}
|
||||
|
||||
{
|
||||
m_resourcePathSeparatorLineEdit = WidgetsFactory::createLineEdit(widget);
|
||||
m_resourcePathSeparatorLineEdit->setToolTip(tr("Separator used to concatenate resource folder paths"));
|
||||
m_resourcePathSeparatorLineEdit->setEnabled(false);
|
||||
layout->addRow(tr("Resource path separator:"), m_resourcePathSeparatorLineEdit);
|
||||
}
|
||||
|
||||
{
|
||||
m_useHtmlInputCheckBox = WidgetsFactory::createCheckBox(tr("Use HTML format as input"), widget);
|
||||
m_useHtmlInputCheckBox->setToolTip(tr("Convert to HTMl format first as the input of the custom export command"));
|
||||
m_useHtmlInputCheckBox->setEnabled(false);
|
||||
layout->addRow(m_useHtmlInputCheckBox);
|
||||
}
|
||||
|
||||
{
|
||||
m_allInOneCheckBox = WidgetsFactory::createCheckBox(tr("All-In-One"), widget);
|
||||
m_allInOneCheckBox->setToolTip(tr("Export all source files into one file"));
|
||||
m_allInOneCheckBox->setEnabled(false);
|
||||
layout->addRow(m_allInOneCheckBox);
|
||||
}
|
||||
|
||||
{
|
||||
m_targetPageScrollableCheckBox = WidgetsFactory::createCheckBox(tr("Target page scrollable"), widget);
|
||||
m_targetPageScrollableCheckBox->setToolTip(tr("Whether the page of the target file is scrollable"));
|
||||
m_targetPageScrollableCheckBox->setEnabled(false);
|
||||
layout->addRow(m_targetPageScrollableCheckBox);
|
||||
}
|
||||
|
||||
{
|
||||
auto usage = tr("%1: List of input files.\n"
|
||||
"%2: List of paths to search for images and other resources.\n"
|
||||
"%3: Path of rendering CSS style sheet.\n"
|
||||
"%4: Path of syntax highlighting CSS style sheet.\n"
|
||||
"%5: Path of output file.\n");
|
||||
layout->addRow(tr("Command usage:"), new QLabel(usage, widget));
|
||||
}
|
||||
|
||||
{
|
||||
m_commandTextEdit = WidgetsFactory::createPlainTextEdit(widget);
|
||||
#if defined(Q_OS_WIN)
|
||||
m_commandTextEdit->setPlaceholderText("pandoc.exe --resource-path=.;%2 --css=%3 --css=%4 -s -o %5 %1");
|
||||
#else
|
||||
m_commandTextEdit->setPlaceholderText("pandoc --resource-path=.:%2 --css=%3 --css=%4 -s -o %5 %1");
|
||||
#endif
|
||||
m_commandTextEdit->setMaximumHeight(m_commandTextEdit->minimumSizeHint().height());
|
||||
m_commandTextEdit->setEnabled(false);
|
||||
layout->addRow(tr("Command:"), m_commandTextEdit);
|
||||
}
|
||||
|
||||
connect(m_customExportComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, &ExportDialog::customExportCurrentSchemeChanged);
|
||||
|
||||
m_advancedGroupBox->layout()->addWidget(widget);
|
||||
|
||||
m_advancedSettings[AdvancedSettings::Custom] = widget;
|
||||
|
||||
restoreCustomFields(m_option);
|
||||
}
|
||||
|
||||
return m_advancedSettings[AdvancedSettings::Custom];
|
||||
}
|
||||
|
||||
void ExportDialog::restoreFields(const ExportPdfOption &p_option)
|
||||
{
|
||||
m_pageLayout = p_option.m_layout;
|
||||
@ -708,6 +836,42 @@ void ExportDialog::saveFields(ExportPdfOption &p_option)
|
||||
p_option.m_wkhtmltopdfArgs = m_wkhtmltopdfArgsLineEdit->text();
|
||||
}
|
||||
|
||||
void ExportDialog::restoreCustomFields(const ExportOption &p_option)
|
||||
{
|
||||
m_customExportComboBox->clear();
|
||||
int curIndex = -1;
|
||||
for (int i = 0; i < m_customOptions.size(); ++i) {
|
||||
m_customExportComboBox->addItem(m_customOptions[i].m_name, m_customOptions[i].m_name);
|
||||
if (m_customOptions[i].m_name == p_option.m_customExport) {
|
||||
curIndex = i;
|
||||
}
|
||||
}
|
||||
m_customExportComboBox->setCurrentIndex(curIndex);
|
||||
}
|
||||
|
||||
void ExportDialog::saveCustomFields(ExportOption &p_option)
|
||||
{
|
||||
p_option.m_customExport = m_customExportComboBox->currentData().toString();
|
||||
|
||||
int idx = findCustomOption(p_option.m_customExport);
|
||||
if (idx > -1) {
|
||||
auto &opt = m_customOptions[idx];
|
||||
opt.m_targetSuffix = m_targetSuffixLineEdit->text();
|
||||
opt.m_resourcePathSeparator = m_resourcePathSeparatorLineEdit->text();
|
||||
opt.m_useHtmlInput = m_useHtmlInputCheckBox->isChecked();
|
||||
opt.m_allInOne = m_allInOneCheckBox->isChecked();
|
||||
opt.m_targetPageScrollable = m_targetPageScrollableCheckBox->isChecked();
|
||||
|
||||
opt.m_command = m_commandTextEdit->toPlainText().trimmed();
|
||||
int lineIdx = opt.m_command.indexOf(QLatin1Char('\n'));
|
||||
if (lineIdx > -1) {
|
||||
opt.m_command = opt.m_command.left(lineIdx);
|
||||
}
|
||||
|
||||
ConfigMgr::getInst().getSessionConfig().setCustomExportOptions(m_customOptions);
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::updatePageLayoutButtonLabel()
|
||||
{
|
||||
Q_ASSERT(m_pageLayout);
|
||||
@ -715,3 +879,107 @@ void ExportDialog::updatePageLayoutButtonLabel()
|
||||
QString("%1, %2").arg(m_pageLayout->pageSize().name(),
|
||||
m_pageLayout->orientation() == QPageLayout::Portrait ? tr("Portrait") : tr("Landscape")));
|
||||
}
|
||||
|
||||
int ExportDialog::findCustomOption(const QString &p_name) const
|
||||
{
|
||||
if (p_name.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_customOptions.size(); ++i) {
|
||||
if (m_customOptions[i].m_name == p_name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ExportDialog::addCustomExportScheme()
|
||||
{
|
||||
QString name;
|
||||
while (true) {
|
||||
name = QInputDialog::getText(this, tr("New Custom Export Scheme"), tr("Scheme name:"));
|
||||
if (name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (findCustomOption(name) != -1) {
|
||||
MessageBoxHelper::notify(MessageBoxHelper::Warning,
|
||||
tr("Name conflicts with existing scheme."),
|
||||
this);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Based on current scheme.
|
||||
ExportCustomOption newOption;
|
||||
|
||||
{
|
||||
int curIndex = findCustomOption(m_customExportComboBox->currentData().toString());
|
||||
if (curIndex > -1) {
|
||||
newOption = m_customOptions[curIndex];
|
||||
}
|
||||
}
|
||||
|
||||
newOption.m_name = name;
|
||||
m_customOptions.append(newOption);
|
||||
ConfigMgr::getInst().getSessionConfig().setCustomExportOptions(m_customOptions);
|
||||
|
||||
// Add it to combo box.
|
||||
m_customExportComboBox->addItem(name, name);
|
||||
m_customExportComboBox->setCurrentIndex(m_customExportComboBox->findData(name));
|
||||
}
|
||||
|
||||
void ExportDialog::removeCustomExportScheme()
|
||||
{
|
||||
auto name = m_customExportComboBox->currentData().toString();
|
||||
if (name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning,
|
||||
tr("Delete scheme (%1)?").arg(name),
|
||||
QString(),
|
||||
QString(),
|
||||
this);
|
||||
if (ret != QMessageBox::Ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = findCustomOption(name);
|
||||
Q_ASSERT(idx > -1);
|
||||
m_customOptions.remove(idx);
|
||||
ConfigMgr::getInst().getSessionConfig().setCustomExportOptions(m_customOptions);
|
||||
|
||||
m_customExportComboBox->removeItem(m_customExportComboBox->currentIndex());
|
||||
}
|
||||
|
||||
void ExportDialog::customExportCurrentSchemeChanged(int p_comboIdx)
|
||||
{
|
||||
const bool enabled = p_comboIdx >= 0;
|
||||
m_targetSuffixLineEdit->setEnabled(enabled);
|
||||
m_resourcePathSeparatorLineEdit->setEnabled(enabled);
|
||||
m_useHtmlInputCheckBox->setEnabled(enabled);
|
||||
m_allInOneCheckBox->setEnabled(enabled);
|
||||
m_targetPageScrollableCheckBox->setEnabled(enabled);
|
||||
m_commandTextEdit->setEnabled(enabled);
|
||||
|
||||
if (p_comboIdx < 0) {
|
||||
m_option.m_customExport.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
auto name = m_customExportComboBox->currentData().toString();
|
||||
m_option.m_customExport = name;
|
||||
int curIndex = findCustomOption(name);
|
||||
Q_ASSERT(curIndex > -1);
|
||||
const auto &opt = m_customOptions[curIndex];
|
||||
m_targetSuffixLineEdit->setText(opt.m_targetSuffix);
|
||||
m_resourcePathSeparatorLineEdit->setText(opt.m_resourcePathSeparator);
|
||||
m_useHtmlInputCheckBox->setChecked(opt.m_useHtmlInput);
|
||||
m_allInOneCheckBox->setChecked(opt.m_allInOne);
|
||||
m_targetPageScrollableCheckBox->setChecked(opt.m_targetPageScrollable);
|
||||
m_commandTextEdit->setPlainText(opt.m_command);
|
||||
}
|
||||
|
@ -43,12 +43,15 @@ namespace vnotex
|
||||
|
||||
void appendLog(const QString &p_log);
|
||||
|
||||
void customExportCurrentSchemeChanged(int p_comboIdx);
|
||||
|
||||
private:
|
||||
enum AdvancedSettings
|
||||
{
|
||||
General,
|
||||
HTML,
|
||||
PDF,
|
||||
Custom,
|
||||
Max
|
||||
};
|
||||
|
||||
@ -66,6 +69,8 @@ namespace vnotex
|
||||
|
||||
QWidget *getPdfAdvancedSettings();
|
||||
|
||||
QWidget *getCustomAdvancedSettings();
|
||||
|
||||
void showAdvancedSettings(AdvancedSettings p_settings);
|
||||
|
||||
void setupButtonBox();
|
||||
@ -86,6 +91,10 @@ namespace vnotex
|
||||
|
||||
void saveFields(ExportPdfOption &p_option);
|
||||
|
||||
void restoreCustomFields(const ExportOption &p_option);
|
||||
|
||||
void saveCustomFields(ExportOption &p_option);
|
||||
|
||||
void startExport();
|
||||
|
||||
void updateUIOnExport();
|
||||
@ -99,6 +108,12 @@ namespace vnotex
|
||||
|
||||
void updatePageLayoutButtonLabel();
|
||||
|
||||
int findCustomOption(const QString &p_name) const;
|
||||
|
||||
void addCustomExportScheme();
|
||||
|
||||
void removeCustomExportScheme();
|
||||
|
||||
// Managed by QObject.
|
||||
Exporter *m_exporter = nullptr;
|
||||
|
||||
@ -170,7 +185,24 @@ namespace vnotex
|
||||
|
||||
QSharedPointer<QPageLayout> m_pageLayout;
|
||||
|
||||
// Custom settings.
|
||||
QComboBox *m_customExportComboBox = nullptr;
|
||||
|
||||
QLineEdit *m_targetSuffixLineEdit = nullptr;
|
||||
|
||||
QLineEdit *m_resourcePathSeparatorLineEdit = nullptr;
|
||||
|
||||
QCheckBox *m_useHtmlInputCheckBox = nullptr;
|
||||
|
||||
QCheckBox *m_customAllInOneCheckBox = nullptr;
|
||||
|
||||
QCheckBox *m_targetPageScrollableCheckBox = nullptr;
|
||||
|
||||
QPlainTextEdit *m_commandTextEdit = nullptr;
|
||||
|
||||
ExportOption m_option;
|
||||
|
||||
QVector<ExportCustomOption> m_customOptions;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ void ImportFolderUtils::importFolderContentsByLegacyConfig(Notebook *p_notebook,
|
||||
try {
|
||||
config = LegacyNotebookUtils::getFolderConfig(rootDir.absolutePath());
|
||||
} catch (Exception &p_e) {
|
||||
Utils::appendMsg(p_errMsg, ImportFolderUtilsTranslate::tr("Failed to read folder config (%1).").arg(rootDir.absolutePath()));
|
||||
Utils::appendMsg(p_errMsg, ImportFolderUtilsTranslate::tr("Failed to read folder config (%1) (%2).").arg(rootDir.absolutePath(), p_e.what()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -109,5 +109,6 @@ QPlainTextEdit *WidgetsFactory::createPlainTextConsole(QWidget *p_parent)
|
||||
QPlainTextEdit *WidgetsFactory::createPlainTextEdit(QWidget *p_parent)
|
||||
{
|
||||
auto edit = new QPlainTextEdit(p_parent);
|
||||
edit->setProperty("ConsoleTextEdit", true);
|
||||
return edit;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user