Export: support custom export

This commit is contained in:
Le Tan 2021-08-22 19:06:06 +08:00
parent c9be7a7a7f
commit 8326d3c702
23 changed files with 877 additions and 88 deletions

View File

@ -24,7 +24,7 @@
using namespace vnotex;
#ifndef QT_NO_DEBUG
// #define VX_DEBUG_WEB
// #define VX_DEBUG_WEB
#endif
const QString ConfigMgr::c_orgName = "VNote";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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">&gt;</p>
</div>
</div>
<!-- VX_OUTLINE_BUTTON_END -->
</body>
</html>

View File

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

View File

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

View File

@ -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,40 +178,78 @@ 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 (!tmpDir.isValid()) {
emit logRequested(tr("Failed to create temporary dir to hold HTML files."));
return QString();
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()));
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();
}
if (checkAskedToStop()) {
return QString();
}
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();
}
}
auto tmpOption(getExportOptionForIntermediateHtml(p_option, tmpDir.path()));
auto htmlFiles = doExportNotebook(tmpOption, tmpDir.path(), p_notebook);
if (htmlFiles.isEmpty()) {
if (inputFiles.isEmpty()) {
return QString();
}
if (checkAskedToStop()) {
return QString();
}
cleanUpWebViewExporter();
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;
}
p_folder->load();
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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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