mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-04 21:39:52 +08:00
support export
This commit is contained in:
parent
7595b03639
commit
04a57f4f8d
@ -562,3 +562,8 @@ Buffer::StateFlags Buffer::state() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
QSharedPointer<File> Buffer::getFile() const
|
||||
{
|
||||
return m_provider->getFile();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ namespace vnotex
|
||||
class ViewWindow;
|
||||
struct FileOpenParameters;
|
||||
class BufferProvider;
|
||||
class File;
|
||||
|
||||
struct BufferParameters
|
||||
{
|
||||
@ -83,6 +84,9 @@ namespace vnotex
|
||||
// Get the base path to resolve resources.
|
||||
QString getResourcePath() const;
|
||||
|
||||
// Return nullptr if not available.
|
||||
QSharedPointer<File> getFile() const;
|
||||
|
||||
ID getID() const;
|
||||
|
||||
// Get buffer content.
|
||||
|
@ -74,6 +74,9 @@ namespace vnotex
|
||||
|
||||
virtual bool isReadOnly() const = 0;
|
||||
|
||||
// Return nullptr if not available.
|
||||
virtual QSharedPointer<File> getFile() const = 0;
|
||||
|
||||
protected:
|
||||
virtual QDateTime getLastModifiedFromFile() const;
|
||||
|
||||
|
@ -178,3 +178,8 @@ bool FileBufferProvider::isReadOnly() const
|
||||
{
|
||||
return m_readOnly;
|
||||
}
|
||||
|
||||
QSharedPointer<File> FileBufferProvider::getFile() const
|
||||
{
|
||||
return m_file;
|
||||
}
|
||||
|
@ -65,6 +65,8 @@ namespace vnotex
|
||||
|
||||
bool isReadOnly() const Q_DECL_OVERRIDE;
|
||||
|
||||
QSharedPointer<File> getFile() const Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
QSharedPointer<File> m_file;
|
||||
|
||||
|
@ -8,6 +8,16 @@
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
QString FileType::preferredSuffix() const
|
||||
{
|
||||
return m_suffixes.isEmpty() ? QString() : m_suffixes.first();
|
||||
}
|
||||
|
||||
bool FileType::isMarkdown() const
|
||||
{
|
||||
return m_type == Type::Markdown;
|
||||
}
|
||||
|
||||
FileTypeHelper::FileTypeHelper()
|
||||
{
|
||||
setupBuiltInTypes();
|
||||
@ -21,7 +31,7 @@ void FileTypeHelper::setupBuiltInTypes()
|
||||
{
|
||||
{
|
||||
FileType type;
|
||||
type.m_type = Type::Markdown;
|
||||
type.m_type = FileType::Markdown;
|
||||
type.m_displayName = Buffer::tr("Markdown");
|
||||
type.m_typeName = QStringLiteral("Markdown");
|
||||
type.m_suffixes << QStringLiteral("md")
|
||||
@ -33,7 +43,7 @@ void FileTypeHelper::setupBuiltInTypes()
|
||||
|
||||
{
|
||||
FileType type;
|
||||
type.m_type = Type::Text;
|
||||
type.m_type = FileType::Text;
|
||||
type.m_typeName = QStringLiteral("Text");
|
||||
type.m_displayName = Buffer::tr("Text");
|
||||
type.m_suffixes << QStringLiteral("txt") << QStringLiteral("text") << QStringLiteral("log");
|
||||
@ -42,7 +52,7 @@ void FileTypeHelper::setupBuiltInTypes()
|
||||
|
||||
{
|
||||
FileType type;
|
||||
type.m_type = Type::Others;
|
||||
type.m_type = FileType::Others;
|
||||
type.m_typeName = QStringLiteral("Others");
|
||||
type.m_displayName = Buffer::tr("Others");
|
||||
m_fileTypes.push_back(type);
|
||||
@ -62,10 +72,10 @@ const FileType &FileTypeHelper::getFileType(const QString &p_filePath) const
|
||||
|
||||
// Treat all unknown text files as plain text files.
|
||||
if (FileUtils::isText(p_filePath)) {
|
||||
return m_fileTypes[Type::Text];
|
||||
return m_fileTypes[FileType::Text];
|
||||
}
|
||||
|
||||
return m_fileTypes[Type::Others];
|
||||
return m_fileTypes[FileType::Others];
|
||||
}
|
||||
|
||||
const FileType &FileTypeHelper::getFileTypeBySuffix(const QString &p_suffix) const
|
||||
@ -74,7 +84,7 @@ const FileType &FileTypeHelper::getFileTypeBySuffix(const QString &p_suffix) con
|
||||
if (it != m_suffixTypeMap.end()) {
|
||||
return m_fileTypes.at(it.value());
|
||||
} else {
|
||||
return m_fileTypes[Type::Others];
|
||||
return m_fileTypes[FileType::Others];
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,8 +107,9 @@ const QVector<FileType> &FileTypeHelper::getAllFileTypes() const
|
||||
return m_fileTypes;
|
||||
}
|
||||
|
||||
const FileType &FileTypeHelper::getFileType(Type p_type) const
|
||||
const FileType &FileTypeHelper::getFileType(int p_type) const
|
||||
{
|
||||
Q_ASSERT(p_type < m_fileTypes.size());
|
||||
return m_fileTypes[p_type];
|
||||
}
|
||||
|
||||
@ -108,9 +119,9 @@ const FileTypeHelper &FileTypeHelper::getInst()
|
||||
return helper;
|
||||
}
|
||||
|
||||
bool FileTypeHelper::checkFileType(const QString &p_filePath, Type p_type) const
|
||||
bool FileTypeHelper::checkFileType(const QString &p_filePath, int p_type) const
|
||||
{
|
||||
return getFileType(p_filePath).m_type == static_cast<int>(p_type);
|
||||
return getFileType(p_filePath).m_type == p_type;
|
||||
}
|
||||
|
||||
const FileType &FileTypeHelper::getFileTypeByName(const QString &p_typeName) const
|
||||
@ -122,5 +133,5 @@ const FileType &FileTypeHelper::getFileTypeByName(const QString &p_typeName) con
|
||||
}
|
||||
|
||||
Q_ASSERT(false);
|
||||
return m_fileTypes[Type::Others];
|
||||
return m_fileTypes[FileType::Others];
|
||||
}
|
||||
|
@ -10,7 +10,15 @@ namespace vnotex
|
||||
class FileType
|
||||
{
|
||||
public:
|
||||
// FileTypeHelper::Type.
|
||||
// There may be other types after Others.
|
||||
enum Type
|
||||
{
|
||||
Markdown = 0,
|
||||
Text,
|
||||
Others
|
||||
};
|
||||
|
||||
// Type.
|
||||
int m_type = -1;
|
||||
|
||||
QString m_typeName;
|
||||
@ -19,25 +27,17 @@ namespace vnotex
|
||||
|
||||
QStringList m_suffixes;
|
||||
|
||||
QString preferredSuffix() const
|
||||
{
|
||||
return m_suffixes.isEmpty() ? QString() : m_suffixes.first();
|
||||
}
|
||||
QString preferredSuffix() const;
|
||||
|
||||
bool isMarkdown() const;
|
||||
};
|
||||
|
||||
class FileTypeHelper
|
||||
{
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
Markdown = 0,
|
||||
Text,
|
||||
Others
|
||||
};
|
||||
|
||||
const FileType &getFileType(const QString &p_filePath) const;
|
||||
|
||||
const FileType &getFileType(Type p_type) const;
|
||||
const FileType &getFileType(int p_type) const;
|
||||
|
||||
const FileType &getFileTypeByName(const QString &p_typeName) const;
|
||||
|
||||
@ -45,7 +45,7 @@ namespace vnotex
|
||||
|
||||
const QVector<FileType> &getAllFileTypes() const;
|
||||
|
||||
bool checkFileType(const QString &p_filePath, Type p_type) const;
|
||||
bool checkFileType(const QString &p_filePath, int p_type) const;
|
||||
|
||||
static const FileTypeHelper &getInst();
|
||||
|
||||
|
@ -8,12 +8,13 @@
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
NodeBufferProvider::NodeBufferProvider(const QSharedPointer<Node> &p_node, QObject *p_parent)
|
||||
NodeBufferProvider::NodeBufferProvider(const QSharedPointer<Node> &p_node,
|
||||
const QSharedPointer<File> &p_file,
|
||||
QObject *p_parent)
|
||||
: BufferProvider(p_parent),
|
||||
m_node(p_node),
|
||||
m_nodeFile(p_node->getContentFile())
|
||||
m_nodeFile(p_file)
|
||||
{
|
||||
Q_ASSERT(m_nodeFile);
|
||||
}
|
||||
|
||||
Buffer::ProviderType NodeBufferProvider::getType() const
|
||||
@ -156,3 +157,8 @@ bool NodeBufferProvider::isReadOnly() const
|
||||
{
|
||||
return m_node->isReadOnly();
|
||||
}
|
||||
|
||||
QSharedPointer<File> NodeBufferProvider::getFile() const
|
||||
{
|
||||
return m_nodeFile;
|
||||
}
|
||||
|
@ -15,7 +15,9 @@ namespace vnotex
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeBufferProvider(const QSharedPointer<Node> &p_node, QObject *p_parent = nullptr);
|
||||
NodeBufferProvider(const QSharedPointer<Node> &p_node,
|
||||
const QSharedPointer<File> &p_file,
|
||||
QObject *p_parent = nullptr);
|
||||
|
||||
Buffer::ProviderType getType() const Q_DECL_OVERRIDE;
|
||||
|
||||
@ -65,6 +67,8 @@ namespace vnotex
|
||||
|
||||
bool isReadOnly() const Q_DECL_OVERRIDE;
|
||||
|
||||
QSharedPointer<File> getFile() const Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
QSharedPointer<Node> m_node;
|
||||
|
||||
|
@ -42,11 +42,11 @@ void BufferMgr::initBufferServer()
|
||||
|
||||
// Markdown.
|
||||
auto markdownFactory = QSharedPointer<MarkdownBufferFactory>::create();
|
||||
m_bufferServer->registerItem(helper.getFileType(FileTypeHelper::Markdown).m_typeName, markdownFactory);
|
||||
m_bufferServer->registerItem(helper.getFileType(FileType::Markdown).m_typeName, markdownFactory);
|
||||
|
||||
// Text.
|
||||
auto textFactory = QSharedPointer<TextBufferFactory>::create();
|
||||
m_bufferServer->registerItem(helper.getFileType(FileTypeHelper::Text).m_typeName, textFactory);
|
||||
m_bufferServer->registerItem(helper.getFileType(FileType::Text).m_typeName, textFactory);
|
||||
}
|
||||
|
||||
void BufferMgr::open(Node *p_node, const QSharedPointer<FileOpenParameters> &p_paras)
|
||||
@ -62,7 +62,9 @@ void BufferMgr::open(Node *p_node, const QSharedPointer<FileOpenParameters> &p_p
|
||||
auto buffer = findBuffer(p_node);
|
||||
if (!buffer) {
|
||||
auto nodePath = p_node->fetchAbsolutePath();
|
||||
auto fileType = FileTypeHelper::getInst().getFileType(nodePath).m_typeName;
|
||||
auto nodeFile = p_node->getContentFile();
|
||||
Q_ASSERT(nodeFile);
|
||||
auto fileType = nodeFile->getContentType().m_typeName;
|
||||
auto factory = m_bufferServer->getItem(fileType);
|
||||
if (!factory) {
|
||||
// No factory to open this file type.
|
||||
@ -72,7 +74,7 @@ void BufferMgr::open(Node *p_node, const QSharedPointer<FileOpenParameters> &p_p
|
||||
}
|
||||
|
||||
BufferParameters paras;
|
||||
paras.m_provider.reset(new NodeBufferProvider(p_node->sharedFromThis()));
|
||||
paras.m_provider.reset(new NodeBufferProvider(p_node->sharedFromThis(), nodeFile));
|
||||
buffer = factory->createBuffer(paras, this);
|
||||
addBuffer(buffer);
|
||||
}
|
||||
@ -114,7 +116,8 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointer<FileOpenPar
|
||||
auto buffer = findBuffer(p_filePath);
|
||||
if (!buffer) {
|
||||
// Open it as external file.
|
||||
auto fileType = FileTypeHelper::getInst().getFileType(p_filePath).m_typeName;
|
||||
auto externalFile = QSharedPointer<ExternalFile>::create(p_filePath);
|
||||
auto fileType = externalFile->getContentType().m_typeName;
|
||||
auto factory = m_bufferServer->getItem(fileType);
|
||||
if (!factory) {
|
||||
// No factory to open this file type.
|
||||
@ -124,7 +127,7 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointer<FileOpenPar
|
||||
}
|
||||
|
||||
BufferParameters paras;
|
||||
paras.m_provider.reset(new FileBufferProvider(QSharedPointer<ExternalFile>::create(p_filePath),
|
||||
paras.m_provider.reset(new FileBufferProvider(externalFile,
|
||||
p_paras->m_nodeAttachedTo,
|
||||
p_paras->m_readOnly));
|
||||
buffer = factory->createBuffer(paras, this);
|
||||
|
@ -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";
|
||||
@ -320,6 +320,18 @@ QString ConfigMgr::getUserThemeFolder() const
|
||||
return folderPath;
|
||||
}
|
||||
|
||||
QString ConfigMgr::getAppWebStylesFolder() const
|
||||
{
|
||||
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("web-styles"));
|
||||
}
|
||||
|
||||
QString ConfigMgr::getUserWebStylesFolder() const
|
||||
{
|
||||
auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("web-styles"));
|
||||
QDir().mkpath(folderPath);
|
||||
return folderPath;
|
||||
}
|
||||
|
||||
QString ConfigMgr::getAppDocsFolder() const
|
||||
{
|
||||
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("docs"));
|
||||
@ -406,3 +418,18 @@ QString ConfigMgr::getApplicationDirPath()
|
||||
{
|
||||
return PathUtils::parentDirPath(getApplicationFilePath());
|
||||
}
|
||||
|
||||
QString ConfigMgr::getDocumentOrHomePath()
|
||||
{
|
||||
static QString docHomePath;
|
||||
if (docHomePath.isEmpty()) {
|
||||
QStringList folders = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
|
||||
if (folders.isEmpty()) {
|
||||
docHomePath = QDir::homePath();
|
||||
} else {
|
||||
docHomePath = folders[0];
|
||||
}
|
||||
}
|
||||
|
||||
return docHomePath;
|
||||
}
|
||||
|
@ -77,6 +77,10 @@ namespace vnotex
|
||||
|
||||
QString getUserThemeFolder() const;
|
||||
|
||||
QString getAppWebStylesFolder() const;
|
||||
|
||||
QString getUserWebStylesFolder() const;
|
||||
|
||||
QString getAppDocsFolder() const;
|
||||
|
||||
QString getUserDocsFolder() const;
|
||||
@ -98,6 +102,8 @@ namespace vnotex
|
||||
|
||||
static QString getApplicationDirPath();
|
||||
|
||||
static QString getDocumentOrHomePath();
|
||||
|
||||
static const QString c_orgName;
|
||||
|
||||
static const QString c_appName;
|
||||
|
@ -32,7 +32,6 @@ SOURCES += \
|
||||
$$PWD/widgetconfig.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/ViewerResource.h \
|
||||
$$PWD/buffermgr.h \
|
||||
$$PWD/configmgr.h \
|
||||
$$PWD/coreconfig.h \
|
||||
@ -58,4 +57,5 @@ HEADERS += \
|
||||
$$PWD/theme.h \
|
||||
$$PWD/sessionconfig.h \
|
||||
$$PWD/clipboarddata.h \
|
||||
$$PWD/webresource.h \
|
||||
$$PWD/widgetconfig.h
|
||||
|
@ -31,6 +31,7 @@ namespace vnotex
|
||||
DistributeSplits,
|
||||
RemoveSplitAndWorkspace,
|
||||
NewWorkspace,
|
||||
Export,
|
||||
MaxShortcut
|
||||
};
|
||||
Q_ENUM(Shortcut)
|
||||
|
@ -8,6 +8,7 @@ using namespace vnotex;
|
||||
ExternalFile::ExternalFile(const QString &p_filePath)
|
||||
: c_filePath(p_filePath)
|
||||
{
|
||||
setContentType(FileTypeHelper::getInst().getFileType(c_filePath).m_type);
|
||||
}
|
||||
|
||||
QString ExternalFile::read() const
|
||||
|
@ -7,7 +7,7 @@ const FileType &File::getContentType() const
|
||||
return FileTypeHelper::getInst().getFileType(m_contentType);
|
||||
}
|
||||
|
||||
void File::setContentType(FileTypeHelper::Type p_type)
|
||||
void File::setContentType(int p_type)
|
||||
{
|
||||
m_contentType = p_type;
|
||||
}
|
||||
|
@ -60,10 +60,10 @@ namespace vnotex
|
||||
const FileType &getContentType() const;
|
||||
|
||||
protected:
|
||||
void setContentType(FileTypeHelper::Type p_type);
|
||||
void setContentType(int p_type);
|
||||
|
||||
private:
|
||||
FileTypeHelper::Type m_contentType = FileTypeHelper::Others;
|
||||
int m_contentType = FileType::Others;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <utils/utils.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/pathutils.h>
|
||||
#include <utils/htmlutils.h>
|
||||
#include <core/thememgr.h>
|
||||
#include <core/vnotex.h>
|
||||
|
||||
@ -14,6 +15,8 @@ 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")
|
||||
@ -26,21 +29,22 @@ QString WebGlobalOptions::toJavascriptObject() const
|
||||
+ QString("linkifyEnabled: %1,\n").arg(Utils::boolToString(m_linkifyEnabled))
|
||||
+ QString("indentFirstLineEnabled: %1,\n").arg(Utils::boolToString(m_indentFirstLineEnabled))
|
||||
+ QString("sectionNumberEnabled: %1,\n").arg(Utils::boolToString(m_sectionNumberEnabled))
|
||||
+ QString("transparentBackgroundEnabled: %1,\n").arg(Utils::boolToString(m_transparentBackgroundEnabled))
|
||||
+ QString("scrollable: %1,\n").arg(Utils::boolToString(m_scrollable))
|
||||
+ QString("bodyWidth: %1,\n").arg(m_bodyWidth)
|
||||
+ QString("bodyHeight: %1,\n").arg(m_bodyHeight)
|
||||
+ QString("transformSvgToPngEnabled: %1,\n").arg(Utils::boolToString(m_transformSvgToPngEnabled))
|
||||
+ QString("mathJaxScale: %1,\n").arg(m_mathJaxScale)
|
||||
+ QString("sectionNumberBaseLevel: %1\n").arg(m_sectionNumberBaseLevel)
|
||||
+ QStringLiteral("}");
|
||||
}
|
||||
|
||||
static bool isGlobalStyles(const ViewerResource::Resource &p_resource)
|
||||
{
|
||||
return p_resource.m_name == QStringLiteral("global_styles");
|
||||
}
|
||||
|
||||
// Read "global_styles" from resource and fill the holder with the content.
|
||||
static void fillGlobalStyles(QString &p_template, const ViewerResource &p_resource)
|
||||
static void fillGlobalStyles(QString &p_template, const WebResource &p_resource, const QString &p_additionalStyles)
|
||||
{
|
||||
QString styles;
|
||||
for (const auto &ele : p_resource.m_resources) {
|
||||
if (isGlobalStyles(ele)) {
|
||||
if (ele.isGlobal()) {
|
||||
if (ele.m_enabled) {
|
||||
for (const auto &style : ele.m_styles) {
|
||||
// Read the style file content.
|
||||
@ -52,9 +56,10 @@ static void fillGlobalStyles(QString &p_template, const ViewerResource &p_resour
|
||||
}
|
||||
}
|
||||
|
||||
styles += p_additionalStyles;
|
||||
|
||||
if (!styles.isEmpty()) {
|
||||
p_template.replace(QStringLiteral("/* VX_GLOBAL_STYLES_PLACEHOLDER */"),
|
||||
styles);
|
||||
p_template.replace(c_globalStylesPlaceholder, styles);
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,13 +81,11 @@ static QString fillScriptTag(const QString &p_scriptFile)
|
||||
return QString("<script type=\"text/javascript\" src=\"%1\"></script>\n").arg(url.toString());
|
||||
}
|
||||
|
||||
static void fillThemeStyles(QString &p_template)
|
||||
static void fillThemeStyles(QString &p_template, const QString &p_webStyleSheetFile, const QString &p_highlightStyleSheetFile)
|
||||
{
|
||||
QString styles;
|
||||
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
||||
|
||||
styles += fillStyleTag(themeMgr.getFile(Theme::File::WebStyleSheet));
|
||||
styles += fillStyleTag(themeMgr.getFile(Theme::File::HighlightStyleSheet));
|
||||
styles += fillStyleTag(p_webStyleSheetFile);
|
||||
styles += fillStyleTag(p_highlightStyleSheetFile);
|
||||
|
||||
if (!styles.isEmpty()) {
|
||||
p_template.replace(QStringLiteral("<!-- VX_THEME_STYLES_PLACEHOLDER -->"),
|
||||
@ -97,13 +100,13 @@ static void fillGlobalOptions(QString &p_template, const WebGlobalOptions &p_opt
|
||||
}
|
||||
|
||||
// Read all other resources in @p_resource and fill the holder with proper resource path.
|
||||
static void fillResources(QString &p_template, const ViewerResource &p_resource)
|
||||
static void fillResources(QString &p_template, const WebResource &p_resource)
|
||||
{
|
||||
QString styles;
|
||||
QString scripts;
|
||||
|
||||
for (const auto &ele : p_resource.m_resources) {
|
||||
if (ele.m_enabled && !isGlobalStyles(ele)) {
|
||||
if (ele.m_enabled && !ele.isGlobal()) {
|
||||
// Styles.
|
||||
for (const auto &style : ele.m_styles) {
|
||||
auto styleFile = ConfigMgr::getInst().getUserOrAppFile(style);
|
||||
@ -129,6 +132,36 @@ static void fillResources(QString &p_template, const ViewerResource &p_resource)
|
||||
}
|
||||
}
|
||||
|
||||
static void fillResourcesByContent(QString &p_template, const WebResource &p_resource)
|
||||
{
|
||||
QString styles;
|
||||
QString scripts;
|
||||
|
||||
for (const auto &ele : p_resource.m_resources) {
|
||||
if (ele.m_enabled && !ele.isGlobal()) {
|
||||
// Styles.
|
||||
for (const auto &style : ele.m_styles) {
|
||||
auto styleFile = ConfigMgr::getInst().getUserOrAppFile(style);
|
||||
styles += FileUtils::readTextFile(styleFile);
|
||||
}
|
||||
|
||||
// Scripts.
|
||||
for (const auto &script : ele.m_scripts) {
|
||||
auto scriptFile = ConfigMgr::getInst().getUserOrAppFile(script);
|
||||
scripts += FileUtils::readTextFile(scriptFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!styles.isEmpty()) {
|
||||
p_template.replace(QStringLiteral("/* VX_STYLES_PLACEHOLDER */"), styles);
|
||||
}
|
||||
|
||||
if (!scripts.isEmpty()) {
|
||||
p_template.replace(QStringLiteral("/* VX_SCRIPTS_PLACEHOLDER */"), scripts);
|
||||
}
|
||||
}
|
||||
|
||||
const QString &HtmlTemplateHelper::getMarkdownViewerTemplate()
|
||||
{
|
||||
return s_markdownViewerTemplate.m_template;
|
||||
@ -142,16 +175,30 @@ void HtmlTemplateHelper::updateMarkdownViewerTemplate(const MarkdownEditorConfig
|
||||
|
||||
s_markdownViewerTemplate.m_revision = p_config.revision();
|
||||
|
||||
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
||||
s_markdownViewerTemplate.m_template =
|
||||
generateMarkdownViewerTemplate(p_config,
|
||||
themeMgr.getFile(Theme::File::WebStyleSheet),
|
||||
themeMgr.getFile(Theme::File::HighlightStyleSheet));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
const auto &viewerResource = p_config.getViewerResource();
|
||||
const auto templateFile = ConfigMgr::getInst().getUserOrAppFile(viewerResource.m_template);
|
||||
auto htmlTemplate = FileUtils::readTextFile(templateFile);
|
||||
|
||||
{
|
||||
auto templateFile = ConfigMgr::getInst().getUserOrAppFile(viewerResource.m_template);
|
||||
s_markdownViewerTemplate.m_template = FileUtils::readTextFile(templateFile);
|
||||
}
|
||||
fillGlobalStyles(htmlTemplate, viewerResource, "");
|
||||
|
||||
fillGlobalStyles(s_markdownViewerTemplate.m_template, viewerResource);
|
||||
|
||||
fillThemeStyles(s_markdownViewerTemplate.m_template);
|
||||
fillThemeStyles(htmlTemplate, p_webStyleSheetFile, p_highlightStyleSheetFile);
|
||||
|
||||
{
|
||||
WebGlobalOptions opts;
|
||||
@ -165,8 +212,66 @@ void HtmlTemplateHelper::updateMarkdownViewerTemplate(const MarkdownEditorConfig
|
||||
opts.m_autoBreakEnabled = p_config.getAutoBreakEnabled();
|
||||
opts.m_linkifyEnabled = p_config.getLinkifyEnabled();
|
||||
opts.m_indentFirstLineEnabled = p_config.getIndentFirstLineEnabled();
|
||||
fillGlobalOptions(s_markdownViewerTemplate.m_template, opts);
|
||||
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;
|
||||
fillGlobalOptions(htmlTemplate, opts);
|
||||
}
|
||||
|
||||
fillResources(s_markdownViewerTemplate.m_template, viewerResource);
|
||||
fillResources(htmlTemplate, viewerResource);
|
||||
|
||||
return htmlTemplate;
|
||||
}
|
||||
|
||||
QString HtmlTemplateHelper::generateExportTemplate(const MarkdownEditorConfig &p_config,
|
||||
bool p_addOutlinePanel)
|
||||
{
|
||||
auto exportResource = p_config.getExportResource();
|
||||
const auto templateFile = ConfigMgr::getInst().getUserOrAppFile(exportResource.m_template);
|
||||
auto htmlTemplate = FileUtils::readTextFile(templateFile);
|
||||
|
||||
fillGlobalStyles(htmlTemplate, exportResource, "");
|
||||
|
||||
// Outline panel.
|
||||
for (auto &ele : exportResource.m_resources) {
|
||||
if (ele.m_name == QStringLiteral("outline")) {
|
||||
ele.m_enabled = p_addOutlinePanel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fillResourcesByContent(htmlTemplate, exportResource);
|
||||
|
||||
return htmlTemplate;
|
||||
}
|
||||
|
||||
void HtmlTemplateHelper::fillTitle(QString &p_template, const QString &p_title)
|
||||
{
|
||||
if (!p_title.isEmpty()) {
|
||||
p_template.replace("<!-- VX_TITLE_PLACEHOLDER -->",
|
||||
QString("<title>%1</title>").arg(HtmlUtils::escapeHtml(p_title)));
|
||||
}
|
||||
}
|
||||
|
||||
void HtmlTemplateHelper::fillStyleContent(QString &p_template, const QString &p_styles)
|
||||
{
|
||||
p_template.replace("/* VX_STYLES_CONTENT_PLACEHOLDER */", p_styles);
|
||||
}
|
||||
|
||||
void HtmlTemplateHelper::fillHeadContent(QString &p_template, const QString &p_head)
|
||||
{
|
||||
p_template.replace("<!-- VX_HEAD_PLACEHOLDER -->", p_head);
|
||||
}
|
||||
|
||||
void HtmlTemplateHelper::fillContent(QString &p_template, const QString &p_content)
|
||||
{
|
||||
p_template.replace("<!-- VX_CONTENT_PLACEHOLDER -->", p_content);
|
||||
}
|
||||
|
||||
void HtmlTemplateHelper::fillBodyClassList(QString &p_template, const QString &p_classList)
|
||||
{
|
||||
p_template.replace("<!-- VX_BODY_CLASS_LIST_PLACEHOLDER -->", p_classList);
|
||||
}
|
||||
|
@ -30,6 +30,24 @@ namespace vnotex
|
||||
|
||||
bool m_indentFirstLineEnabled = false;
|
||||
|
||||
// Force to use transparent background.
|
||||
bool m_transparentBackgroundEnabled = false;
|
||||
|
||||
// Whether the content elements are scrollable. Like PDF, it is false.
|
||||
bool m_scrollable = true;
|
||||
|
||||
int m_bodyWidth = -1;
|
||||
|
||||
int m_bodyHeight = -1;
|
||||
|
||||
// Whether transform inlie SVG to PNG.
|
||||
// For wkhtmltopdf converter, it could not render some inline SVG correctly.
|
||||
// This is just a hint not mandatory. For now, PlantUML and Graphviz needs this.
|
||||
bool m_transformSvgToPngEnabled = false;
|
||||
|
||||
// wkhtmltopdf will make the MathJax formula too small.
|
||||
qreal m_mathJaxScale = -1;
|
||||
|
||||
QString toJavascriptObject() const;
|
||||
};
|
||||
|
||||
@ -42,6 +60,29 @@ namespace vnotex
|
||||
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 generateExportTemplate(const MarkdownEditorConfig &p_config,
|
||||
bool p_addOutlinePanel);
|
||||
|
||||
static void fillTitle(QString &p_template, const QString &p_title);
|
||||
|
||||
static void fillStyleContent(QString &p_template, const QString &p_styles);
|
||||
|
||||
static void fillHeadContent(QString &p_template, const QString &p_head);
|
||||
|
||||
static void fillContent(QString &p_template, const QString &p_content);
|
||||
|
||||
static void fillBodyClassList(QString &p_template, const QString &p_classList);
|
||||
|
||||
private:
|
||||
struct Template
|
||||
{
|
||||
|
@ -27,6 +27,7 @@ void MarkdownEditorConfig::init(const QJsonObject &p_app, const QJsonObject &p_u
|
||||
const auto userObj = p_user.value(m_sessionName).toObject();
|
||||
|
||||
loadViewerResource(appObj, userObj);
|
||||
loadExportResource(appObj, userObj);
|
||||
|
||||
m_webPlantUml = READBOOL(QStringLiteral("web_plantuml"));
|
||||
m_webGraphviz = READBOOL(QStringLiteral("web_graphviz"));
|
||||
@ -58,6 +59,7 @@ QJsonObject MarkdownEditorConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("viewer_resource")] = saveViewerResource();
|
||||
obj[QStringLiteral("export_resource")] = saveExportResource();
|
||||
obj[QStringLiteral("web_plantuml")] = m_webPlantUml;
|
||||
obj[QStringLiteral("web_graphviz")] = m_webGraphviz;
|
||||
obj[QStringLiteral("prepend_dot_in_relative_link")] = m_prependDotInRelativeLink;
|
||||
@ -122,11 +124,41 @@ QJsonObject MarkdownEditorConfig::saveViewerResource() const
|
||||
return m_viewerResource.toJson();
|
||||
}
|
||||
|
||||
const ViewerResource &MarkdownEditorConfig::getViewerResource() const
|
||||
void MarkdownEditorConfig::loadExportResource(const QJsonObject &p_app, const QJsonObject &p_user)
|
||||
{
|
||||
const QString name(QStringLiteral("export_resource"));
|
||||
|
||||
if (MainConfig::isVersionChanged()) {
|
||||
bool needOverride = p_app[QStringLiteral("override_viewer_resource")].toBool();
|
||||
if (needOverride) {
|
||||
qInfo() << "override \"viewer_resource\" in user configuration due to version change";
|
||||
m_exportResource.init(p_app[name].toObject());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_user.contains(name)) {
|
||||
m_exportResource.init(p_user[name].toObject());
|
||||
} else {
|
||||
m_exportResource.init(p_app[name].toObject());
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject MarkdownEditorConfig::saveExportResource() const
|
||||
{
|
||||
return m_exportResource.toJson();
|
||||
}
|
||||
|
||||
const WebResource &MarkdownEditorConfig::getViewerResource() const
|
||||
{
|
||||
return m_viewerResource;
|
||||
}
|
||||
|
||||
const WebResource &MarkdownEditorConfig::getExportResource() const
|
||||
{
|
||||
return m_exportResource;
|
||||
}
|
||||
|
||||
bool MarkdownEditorConfig::getWebPlantUml() const
|
||||
{
|
||||
return m_webPlantUml;
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include "iconfig.h"
|
||||
|
||||
#include "viewerresource.h"
|
||||
#include "webresource.h"
|
||||
|
||||
#include <QSharedPointer>
|
||||
#include <QVector>
|
||||
@ -38,15 +38,14 @@ namespace vnotex
|
||||
|
||||
QJsonObject toJson() const Q_DECL_OVERRIDE;
|
||||
|
||||
void loadViewerResource(const QJsonObject &p_app, const QJsonObject &p_user);
|
||||
QJsonObject saveViewerResource() const;
|
||||
|
||||
int revision() const Q_DECL_OVERRIDE;
|
||||
|
||||
TextEditorConfig &getTextEditorConfig();
|
||||
const TextEditorConfig &getTextEditorConfig() const;
|
||||
|
||||
const ViewerResource &getViewerResource() const;
|
||||
const WebResource &getViewerResource() const;
|
||||
|
||||
const WebResource &getExportResource() const;
|
||||
|
||||
bool getWebPlantUml() const;
|
||||
|
||||
@ -107,9 +106,17 @@ namespace vnotex
|
||||
QString sectionNumberStyleToString(SectionNumberStyle p_style) const;
|
||||
SectionNumberStyle stringToSectionNumberStyle(const QString &p_str) const;
|
||||
|
||||
void loadViewerResource(const QJsonObject &p_app, const QJsonObject &p_user);
|
||||
QJsonObject saveViewerResource() const;
|
||||
|
||||
void loadExportResource(const QJsonObject &p_app, const QJsonObject &p_user);
|
||||
QJsonObject saveExportResource() const;
|
||||
|
||||
QSharedPointer<TextEditorConfig> m_textEditorConfig;
|
||||
|
||||
ViewerResource m_viewerResource;
|
||||
WebResource m_viewerResource;
|
||||
|
||||
WebResource m_exportResource;
|
||||
|
||||
// Whether use javascript or external program to render PlantUML.
|
||||
bool m_webPlantUml = true;
|
||||
|
@ -263,6 +263,10 @@ QDir Node::toDir() const
|
||||
|
||||
void Node::load()
|
||||
{
|
||||
if (isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
getConfigMgr()->loadNode(this);
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ VXNodeFile::VXNodeFile(const QSharedPointer<VXNode> &p_node)
|
||||
: m_node(p_node)
|
||||
{
|
||||
Q_ASSERT(m_node && m_node->hasContent());
|
||||
setContentType(FileTypeHelper::getInst().getFileType(getContentPath()).m_type);
|
||||
}
|
||||
|
||||
QString VXNodeFile::read() const
|
||||
|
@ -1,5 +1,4 @@
|
||||
SOURCES += \
|
||||
$$PWD/nodecontentmediautils.cpp \
|
||||
$$PWD/vxnotebookconfigmgr.cpp \
|
||||
$$PWD/vxnotebookconfigmgrfactory.cpp \
|
||||
$$PWD/inotebookconfigmgr.cpp \
|
||||
@ -8,7 +7,6 @@ SOURCES += \
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/inotebookconfigmgr.h \
|
||||
$$PWD/nodecontentmediautils.h \
|
||||
$$PWD/vxnotebookconfigmgr.h \
|
||||
$$PWD/inotebookconfigmgrfactory.h \
|
||||
$$PWD/vxnotebookconfigmgrfactory.h \
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include <utils/pathutils.h>
|
||||
#include <exception.h>
|
||||
|
||||
#include "nodecontentmediautils.h"
|
||||
#include <utils/contentmediautils.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
@ -581,13 +581,13 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileNodeAsChildOf(const QSharedPoi
|
||||
getBackend()->copyFile(srcFilePath, destFilePath);
|
||||
|
||||
// Copy media files fetched from content.
|
||||
NodeContentMediaUtils::copyMediaFiles(p_src.data(), getBackend().data(), destFilePath);
|
||||
ContentMediaUtils::copyMediaFiles(p_src.data(), getBackend().data(), destFilePath);
|
||||
|
||||
// Copy attachment folder. Rename attachment folder if conflicts.
|
||||
QString attachmentFolder = p_src->getAttachmentFolder();
|
||||
if (!attachmentFolder.isEmpty()) {
|
||||
auto destAttachmentFolderPath = fetchNodeAttachmentFolder(destFilePath, attachmentFolder);
|
||||
NodeContentMediaUtils::copyAttachment(p_src.data(), getBackend().data(), destFilePath, destAttachmentFolderPath);
|
||||
ContentMediaUtils::copyAttachment(p_src.data(), getBackend().data(), destFilePath, destAttachmentFolderPath);
|
||||
}
|
||||
|
||||
// Create a file node.
|
||||
@ -690,7 +690,7 @@ void VXNotebookConfigMgr::removeFilesOfNode(Node *p_node, bool p_force)
|
||||
}
|
||||
|
||||
// Delete media files fetched from content.
|
||||
NodeContentMediaUtils::removeMediaFiles(p_node);
|
||||
ContentMediaUtils::removeMediaFiles(p_node);
|
||||
|
||||
// Delete node file itself.
|
||||
auto filePath = p_node->fetchPath();
|
||||
@ -787,7 +787,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_src
|
||||
getBackend()->copyFile(p_srcPath, destFilePath);
|
||||
|
||||
// Copy media files fetched from content.
|
||||
NodeContentMediaUtils::copyMediaFiles(p_srcPath, getBackend().data(), destFilePath);
|
||||
ContentMediaUtils::copyMediaFiles(p_srcPath, getBackend().data(), destFilePath);
|
||||
|
||||
// Create a file node.
|
||||
auto currentTime = QDateTime::currentDateTimeUtc();
|
||||
|
@ -55,9 +55,13 @@ void SessionConfig::init()
|
||||
|
||||
loadCore(sessionJobj);
|
||||
|
||||
loadStateAndGeometry(sessionJobj);
|
||||
|
||||
if (MainConfig::isVersionChanged()) {
|
||||
doVersionSpecificOverride();
|
||||
}
|
||||
|
||||
m_exportOption.fromJson(sessionJobj[QStringLiteral("export_option")].toObject());
|
||||
}
|
||||
|
||||
void SessionConfig::loadCore(const QJsonObject &p_session)
|
||||
@ -172,6 +176,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();
|
||||
return obj;
|
||||
}
|
||||
|
||||
@ -185,22 +190,12 @@ QJsonObject SessionConfig::saveStateAndGeometry() const
|
||||
|
||||
SessionConfig::MainWindowStateGeometry SessionConfig::getMainWindowStateGeometry() const
|
||||
{
|
||||
auto sessionSettings = getMgr()->getSettings(ConfigMgr::Source::Session);
|
||||
const auto &sessionJobj = sessionSettings->getJson();
|
||||
const auto obj = sessionJobj.value(QStringLiteral("state_geometry")).toObject();
|
||||
|
||||
MainWindowStateGeometry sg;
|
||||
sg.m_mainState = readByteArray(obj, QStringLiteral("main_window_state"));
|
||||
sg.m_mainGeometry = readByteArray(obj, QStringLiteral("main_window_geometry"));
|
||||
|
||||
return sg;
|
||||
return m_mainWindowStateGeometry;
|
||||
}
|
||||
|
||||
void SessionConfig::setMainWindowStateGeometry(const SessionConfig::MainWindowStateGeometry &p_state)
|
||||
{
|
||||
m_mainWindowStateGeometry = p_state;
|
||||
++m_revision;
|
||||
writeToSettings();
|
||||
updateConfig(m_mainWindowStateGeometry, p_state, this);
|
||||
}
|
||||
|
||||
SessionConfig::OpenGL SessionConfig::getOpenGLAtBootstrap()
|
||||
@ -283,3 +278,20 @@ void SessionConfig::doVersionSpecificOverride()
|
||||
// In a new version, we may want to change one value by force.
|
||||
// SHOULD set the in memory variable only, or will override the notebook list.
|
||||
}
|
||||
|
||||
const ExportOption &SessionConfig::getExportOption() const
|
||||
{
|
||||
return m_exportOption;
|
||||
}
|
||||
|
||||
void SessionConfig::setExportOption(const ExportOption &p_option)
|
||||
{
|
||||
updateConfig(m_exportOption, p_option, this);
|
||||
}
|
||||
|
||||
void SessionConfig::loadStateAndGeometry(const QJsonObject &p_session)
|
||||
{
|
||||
const auto obj = p_session.value(QStringLiteral("state_geometry")).toObject();
|
||||
m_mainWindowStateGeometry.m_mainState = readByteArray(obj, QStringLiteral("main_window_state"));
|
||||
m_mainWindowStateGeometry.m_mainGeometry = readByteArray(obj, QStringLiteral("main_window_geometry"));
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include <export/exportdata.h>
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class SessionConfig : public IConfig
|
||||
@ -82,6 +84,9 @@ namespace vnotex
|
||||
int getMinimizeToSystemTray() const;
|
||||
void setMinimizeToSystemTray(bool p_enabled);
|
||||
|
||||
const ExportOption &getExportOption() const;
|
||||
void setExportOption(const ExportOption &p_option);
|
||||
|
||||
private:
|
||||
void loadCore(const QJsonObject &p_session);
|
||||
|
||||
@ -91,6 +96,8 @@ namespace vnotex
|
||||
|
||||
QJsonArray saveNotebooks() const;
|
||||
|
||||
void loadStateAndGeometry(const QJsonObject &p_session);
|
||||
|
||||
QJsonObject saveStateAndGeometry() const;
|
||||
|
||||
void doVersionSpecificOverride();
|
||||
@ -102,8 +109,6 @@ namespace vnotex
|
||||
|
||||
QVector<SessionConfig::NotebookItem> m_notebooks;
|
||||
|
||||
// Used to store newly-set state and geometry, since there is no need to store the read-in
|
||||
// data all the time.
|
||||
MainWindowStateGeometry m_mainWindowStateGeometry;
|
||||
|
||||
OpenGL m_openGL = OpenGL::None;
|
||||
@ -116,6 +121,8 @@ namespace vnotex
|
||||
// 0 for disabling minimizing to system tray;
|
||||
// 1 for enabling minimizing to system tray.
|
||||
int m_minimizeToSystemTray = -1;
|
||||
|
||||
ExportOption m_exportOption;
|
||||
};
|
||||
} // ns vnotex
|
||||
|
||||
|
@ -368,7 +368,12 @@ bool Theme::isRef(const QString &p_str)
|
||||
|
||||
QString Theme::getFile(File p_fileType) const
|
||||
{
|
||||
QDir dir(m_themeFolderPath);
|
||||
return getFile(m_themeFolderPath, p_fileType);
|
||||
}
|
||||
|
||||
QString Theme::getFile(const QString &p_themeFolder, File p_fileType)
|
||||
{
|
||||
QDir dir(p_themeFolder);
|
||||
if (dir.exists(getFileName(p_fileType))) {
|
||||
return dir.filePath(getFileName(p_fileType));
|
||||
} else if (p_fileType == File::MarkdownEditorStyle) {
|
||||
|
@ -54,6 +54,8 @@ namespace vnotex
|
||||
|
||||
static QPixmap getCover(const QString &p_folder);
|
||||
|
||||
static QString getFile(const QString &p_themeFolder, File p_fileType);
|
||||
|
||||
private:
|
||||
struct Metadata
|
||||
{
|
||||
|
@ -15,6 +15,8 @@ using namespace vnotex;
|
||||
|
||||
QStringList ThemeMgr::s_searchPaths;
|
||||
|
||||
QStringList ThemeMgr::s_webStylesSearchPaths;
|
||||
|
||||
ThemeMgr::ThemeMgr(const QString &p_currentThemeName, QObject *p_parent)
|
||||
: QObject(p_parent)
|
||||
{
|
||||
@ -203,3 +205,39 @@ void ThemeMgr::refresh()
|
||||
{
|
||||
loadAvailableThemes();
|
||||
}
|
||||
|
||||
void ThemeMgr::addWebStylesSearchPath(const QString &p_path)
|
||||
{
|
||||
s_webStylesSearchPaths << p_path;
|
||||
}
|
||||
|
||||
QVector<QPair<QString, QString>> ThemeMgr::getWebStyles() const
|
||||
{
|
||||
QVector<QPair<QString, QString>> styles;
|
||||
|
||||
// From themes.
|
||||
for (const auto &th : m_themes) {
|
||||
auto filePath = Theme::getFile(th.m_folderPath, Theme::File::WebStyleSheet);
|
||||
if (!filePath.isEmpty()) {
|
||||
styles.push_back(qMakePair(tr("[Theme] %1 %2").arg(th.m_displayName, PathUtils::fileName(filePath)),
|
||||
filePath));
|
||||
}
|
||||
|
||||
filePath = Theme::getFile(th.m_folderPath, Theme::File::HighlightStyleSheet);
|
||||
if (!filePath.isEmpty()) {
|
||||
styles.push_back(qMakePair(tr("[Theme] %1 %2").arg(th.m_displayName, PathUtils::fileName(filePath)),
|
||||
filePath));
|
||||
}
|
||||
}
|
||||
|
||||
// From search paths.
|
||||
for (const auto &pa : s_webStylesSearchPaths) {
|
||||
QDir dir(pa);
|
||||
auto styleFiles = dir.entryList({"*.css"}, QDir::Files);
|
||||
for (const auto &file : styleFiles) {
|
||||
styles.push_back(qMakePair(file, dir.filePath(file)));
|
||||
}
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
@ -64,10 +64,16 @@ namespace vnotex
|
||||
// Won't affect current theme since we do not support changing theme real time for now.
|
||||
void refresh();
|
||||
|
||||
// Return all web stylesheets available, including those from themes and web styles search paths.
|
||||
// <DisplayName, FilePath>.
|
||||
QVector<QPair<QString, QString>> getWebStyles() const;
|
||||
|
||||
static void addSearchPath(const QString &p_path);
|
||||
|
||||
static void addSyntaxHighlightingSearchPaths(const QStringList &p_paths);
|
||||
|
||||
static void addWebStylesSearchPath(const QString &p_path);
|
||||
|
||||
private:
|
||||
void loadAvailableThemes();
|
||||
|
||||
@ -89,8 +95,11 @@ namespace vnotex
|
||||
// Set at runtime, not from the theme config.
|
||||
QColor m_baseBackground;
|
||||
|
||||
// List of path to search for themes.
|
||||
// List of paths to search for themes.
|
||||
static QStringList s_searchPaths;
|
||||
|
||||
// List of paths to search for CSS styles, including CSS syntax highlighting styles.
|
||||
static QStringList s_webStylesSearchPaths;
|
||||
};
|
||||
} // ns vnotex
|
||||
|
||||
|
@ -46,6 +46,8 @@ void VNoteX::initThemeMgr()
|
||||
ThemeMgr::addSyntaxHighlightingSearchPaths(
|
||||
QStringList() << configMgr.getUserSyntaxHighlightingFolder()
|
||||
<< configMgr.getAppSyntaxHighlightingFolder());
|
||||
ThemeMgr::addWebStylesSearchPath(configMgr.getAppWebStylesFolder());
|
||||
ThemeMgr::addWebStylesSearchPath(configMgr.getUserWebStylesFolder());
|
||||
m_themeMgr = new ThemeMgr(configMgr.getCoreConfig().getTheme(), this);
|
||||
}
|
||||
|
||||
|
@ -97,6 +97,8 @@ namespace vnotex
|
||||
// Requested to locate node in explorer.
|
||||
void locateNodeRequested(Node *p_node);
|
||||
|
||||
void exportRequested();
|
||||
|
||||
private:
|
||||
explicit VNoteX(QObject *p_parent = nullptr);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
#ifndef VIEWERRESOURCE_H
|
||||
#define VIEWERRESOURCE_H
|
||||
#ifndef WEBRESOURCE_H
|
||||
#define WEBRESOURCE_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
@ -8,8 +8,8 @@
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
// Resource for Web viewer.
|
||||
struct ViewerResource
|
||||
// Resource for Web.
|
||||
struct WebResource
|
||||
{
|
||||
struct Resource
|
||||
{
|
||||
@ -51,6 +51,11 @@ namespace vnotex
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool isGlobal() const
|
||||
{
|
||||
return m_name == QStringLiteral("global_styles");
|
||||
}
|
||||
|
||||
QString m_name;
|
||||
|
||||
bool m_enabled = true;
|
||||
@ -96,4 +101,4 @@ namespace vnotex
|
||||
|
||||
}
|
||||
|
||||
#endif // VIEWERRESOURCE_H
|
||||
#endif // WEBRESOURCE_H
|
@ -24,7 +24,8 @@
|
||||
"MaximizeSplit" : "Ctrl+G, Shift+\\",
|
||||
"DistributeSplits" : "Ctrl+G, =",
|
||||
"RemoveSplitAndWorkspace" : "Ctrl+G, R",
|
||||
"NewWorkspace" : "Ctrl+G, N"
|
||||
"NewWorkspace" : "Ctrl+G, N",
|
||||
"Export" : "Ctrl+G, T"
|
||||
},
|
||||
"toolbar_icon_size" : 16
|
||||
},
|
||||
@ -83,7 +84,7 @@
|
||||
"markdown_editor" : {
|
||||
"override_viewer_resource" : true,
|
||||
"viewer_resource" : {
|
||||
"template" : "web/markdownviewertemplate.html",
|
||||
"template" : "web/markdown-viewer-template.html",
|
||||
"resources" : [
|
||||
{
|
||||
"name" : "global_styles",
|
||||
@ -217,6 +218,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"export_resource" : {
|
||||
"template" : "web/markdown-export-template.html",
|
||||
"resources" : [
|
||||
{
|
||||
"name" : "global_styles",
|
||||
"enabled" : true,
|
||||
"styles" : [
|
||||
"web/css/exportglobalstyles.css"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : "outline",
|
||||
"enabled" : true,
|
||||
"styles" : [
|
||||
"web/css/outline.css"
|
||||
],
|
||||
"scripts" : [
|
||||
"web/js/outline.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"//comment" : "Whether use javascript or external program to render PlantUML",
|
||||
"web_plantuml" : true,
|
||||
"//comment" : "Whether use javascript or external program to render Graphviz",
|
||||
|
@ -8,10 +8,13 @@
|
||||
<file>docs/zh_CN/about_vnotex.txt</file>
|
||||
<file>docs/zh_CN/shortcuts.md</file>
|
||||
<file>docs/zh_CN/markdown_guide.md</file>
|
||||
<file>web/markdownviewertemplate.html</file>
|
||||
<file>web/markdown-viewer-template.html</file>
|
||||
<file>web/markdown-export-template.html</file>
|
||||
<file>web/css/globalstyles.css</file>
|
||||
<file>web/css/markdownit.css</file>
|
||||
<file>web/css/imageviewer.css</file>
|
||||
<file>web/css/outline.css</file>
|
||||
<file>web/css/exportglobalstyles.css</file>
|
||||
<file>web/js/qwebchannel.js</file>
|
||||
<file>web/js/eventemitter.js</file>
|
||||
<file>web/js/utils.js</file>
|
||||
@ -26,6 +29,7 @@
|
||||
<file>web/js/imageviewer.js</file>
|
||||
<file>web/js/easyaccess.js</file>
|
||||
<file>web/js/crosscopy.js</file>
|
||||
<file>web/js/outline.js</file>
|
||||
<file>web/js/markdown-it/markdown-it-container.min.js</file>
|
||||
<file>web/js/markdown-it/markdown-it-emoji.min.js</file>
|
||||
<file>web/js/markdown-it/markdown-it-footnote.min.js</file>
|
||||
|
@ -20,15 +20,15 @@ QWidget[DialogCentralWidget="true"] {
|
||||
|
||||
/* All widgets */
|
||||
*[State="info"] {
|
||||
border: 2px solid @widgets#qwidget#info#border;
|
||||
border: 1px solid @widgets#qwidget#info#border;
|
||||
}
|
||||
|
||||
*[State="warning"] {
|
||||
border: 2px solid @widgets#qwidget#warning#border;
|
||||
border: 1px solid @widgets#qwidget#warning#border;
|
||||
}
|
||||
|
||||
*[State="error"] {
|
||||
border: 2px solid @widgets#qwidget#error#border;
|
||||
border: 1px solid @widgets#qwidget#error#border;
|
||||
}
|
||||
|
||||
/* QAbstractScrollArea */
|
||||
@ -430,6 +430,14 @@ QLineEdit:disabled {
|
||||
color: @widgets#qlineedit#disabled#fg;
|
||||
}
|
||||
|
||||
/* QPlainTextEdit */
|
||||
QPlainTextEdit[ConsoleTextEdit="true"] {
|
||||
color: @widgets#qlineedit#fg;
|
||||
background-color: @widgets#qlineedit#bg;
|
||||
selection-color: @widgets#qlineedit#selection#fg;
|
||||
selection-background-color: @widgets#qlineedit#selection#bg;
|
||||
}
|
||||
|
||||
/* QTabWidget */
|
||||
QTabWidget {
|
||||
border: none;
|
||||
|
@ -44,7 +44,7 @@
|
||||
"bg2_9" : "#919cd8",
|
||||
"fg10" : "#b71c1c",
|
||||
"fg11" : "#ab5683",
|
||||
"fg12" : "#283593",
|
||||
"fg12" : "#5768c4",
|
||||
"fg13" : "#b42b1f",
|
||||
"fg15_3" : "#4f5666",
|
||||
"fg15_4" : "#60697c",
|
||||
|
@ -10,15 +10,15 @@
|
||||
|
||||
/* All widgets */
|
||||
*[State="info"] {
|
||||
border: 2px solid @base#info#fg;
|
||||
border: 1px solid @base#info#fg;
|
||||
}
|
||||
|
||||
*[State="warning"] {
|
||||
border: 2px solid @base#warning#fg;
|
||||
border: 1px solid @base#warning#fg;
|
||||
}
|
||||
|
||||
*[State="error"] {
|
||||
border: 2px solid @base#error#fg;
|
||||
border: 1px solid @base#error#fg;
|
||||
}
|
||||
|
||||
/* ToolBox */
|
||||
|
@ -33,14 +33,14 @@ pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selectio
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background-color: #1976d2;
|
||||
color: #f1f1f1;
|
||||
color: #f5f5f5;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background-color: #1976d2;
|
||||
color: #f1f1f1;
|
||||
color: #f5f5f5;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
@ -20,15 +20,15 @@ QWidget[DialogCentralWidget="true"] {
|
||||
|
||||
/* All widgets */
|
||||
*[State="info"] {
|
||||
border: 2px solid @widgets#qwidget#info#border;
|
||||
border: 1px solid @widgets#qwidget#info#border;
|
||||
}
|
||||
|
||||
*[State="warning"] {
|
||||
border: 2px solid @widgets#qwidget#warning#border;
|
||||
border: 1px solid @widgets#qwidget#warning#border;
|
||||
}
|
||||
|
||||
*[State="error"] {
|
||||
border: 2px solid @widgets#qwidget#error#border;
|
||||
border: 1px solid @widgets#qwidget#error#border;
|
||||
}
|
||||
|
||||
/* QAbstractScrollArea */
|
||||
@ -430,6 +430,14 @@ QLineEdit:disabled {
|
||||
color: @widgets#qlineedit#disabled#fg;
|
||||
}
|
||||
|
||||
/* QPlainTextEdit */
|
||||
QPlainTextEdit[ConsoleTextEdit="true"] {
|
||||
color: @widgets#qlineedit#fg;
|
||||
background-color: @widgets#qlineedit#bg;
|
||||
selection-color: @widgets#qlineedit#selection#fg;
|
||||
selection-background-color: @widgets#qlineedit#selection#bg;
|
||||
}
|
||||
|
||||
/* QTabWidget */
|
||||
QTabWidget {
|
||||
border: none;
|
||||
|
@ -20,7 +20,7 @@
|
||||
"bg3_4" : "#dadada",
|
||||
"bg3_41" : "#e0e0e0",
|
||||
"bg3_5" : "#eaeaea",
|
||||
"bg3_6" : "#f1f1f1",
|
||||
"bg3_6" : "#f5f5f5",
|
||||
"fg3_5" : "#222222",
|
||||
"fg3_6" : "#646464",
|
||||
"fg3_7" : "#7a7a7a",
|
||||
@ -33,7 +33,7 @@
|
||||
"bg2_7" : "#e5f3f1",
|
||||
"fg10" : "#b71c1c",
|
||||
"fg11" : "#ab5683",
|
||||
"fg12" : "#283593",
|
||||
"fg12" : "#007b6e",
|
||||
"fg13" : "#b42b1f",
|
||||
"fg15_3" : "#b0b0b0",
|
||||
"fg15_4" : "#7a7a7a",
|
||||
|
@ -10,8 +10,8 @@
|
||||
"font-family" : "YaHei Consolas Hybrid, Consolas, Monaco, Andale Mono, Monospace, Courier New",
|
||||
"font-size" : 12,
|
||||
"text-color" : "#222222",
|
||||
"background-color" : "#f1f1f1",
|
||||
"selected-text-color" : "#f1f1f1",
|
||||
"background-color" : "#f5f5f5",
|
||||
"selected-text-color" : "#f5f5f5",
|
||||
"selected-background-color" : "#1976d2"
|
||||
},
|
||||
"CursorLine" : {
|
||||
@ -30,7 +30,7 @@
|
||||
},
|
||||
"IndicatorsBorder" : {
|
||||
"text-color" : "#aaaaaa",
|
||||
"background-color" : "#ededed"
|
||||
"background-color" : "#f1f1f1"
|
||||
},
|
||||
"CurrentLineNumber" : {
|
||||
"text-color" : "#222222"
|
||||
@ -70,8 +70,8 @@
|
||||
"font-family" : "冬青黑体, YaHei Consolas Hybrid, Microsoft YaHei, 微软雅黑, Microsoft YaHei UI, WenQuanYi Micro Hei, 文泉驿雅黑, Dengxian, 等线体, STXihei, 华文细黑, Liberation Sans, Droid Sans, NSimSun, 新宋体, SimSun, 宋体, Verdana, Helvetica, sans-serif, Tahoma, Arial, Geneva, Georgia, Times New Roman",
|
||||
"font-size" : 12,
|
||||
"text-color" : "#222222",
|
||||
"background-color" : "#f1f1f1",
|
||||
"selected-text-color" : "#f1f1f1",
|
||||
"background-color" : "#f5f5f5",
|
||||
"selected-text-color" : "#f5f5f5",
|
||||
"selected-background-color" : "#1976d2"
|
||||
}
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ body {
|
||||
color: #222222;
|
||||
line-height: 1.5;
|
||||
padding: 15px;
|
||||
background-color: #f1f1f1;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@ -202,7 +202,7 @@ div.vx-plantuml-graph {
|
||||
|
||||
::selection {
|
||||
background-color: #1976d2;
|
||||
color: #f1f1f1;
|
||||
color: #f5f5f5;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
|
3
src/data/extra/web/css/exportglobalstyles.css
Normal file
3
src/data/extra/web/css/exportglobalstyles.css
Normal file
@ -0,0 +1,3 @@
|
||||
div.code-toolbar > div.toolbar {
|
||||
display: none;
|
||||
}
|
@ -73,9 +73,13 @@
|
||||
content: counter(section1) "." counter(section2) "." counter(section3) "." counter(section4) "." counter(section5) "." counter(section6) ". ";
|
||||
}
|
||||
|
||||
#vx-content.vx-constrain-image-width img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
#vx-content.vx-constrain-image-width img,
|
||||
#vx-content.vx-constrain-image-width div.vx-plantuml-graph > svg,
|
||||
#vx-content.vx-constrain-image-width div.vx-mermaid-graph,
|
||||
#vx-content.vx-constrain-image-width div.vx-flowchartjs-graph,
|
||||
#vx-content.vx-constrain-image-width div.vx-wavedrom-graph {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* Table of Contents */
|
||||
@ -133,3 +137,23 @@
|
||||
#vx-content.vx-indent-first-line p {
|
||||
text-indent: 2em;
|
||||
}
|
||||
|
||||
body.vx-transparent-background {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
#vx-content.vx-nonscrollable pre {
|
||||
white-space: pre-wrap !important;
|
||||
word-break: break-all !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
#vx-content.vx-nonscrollable pre code {
|
||||
white-space: pre-wrap !important;
|
||||
word-break: break-all !important;
|
||||
}
|
||||
|
||||
#vx-content.vx-nonscrollable code,
|
||||
#vx-content.vx-nonscrollable a {
|
||||
word-break: break-all !important;
|
||||
}
|
||||
|
205
src/data/extra/web/css/outline.css
Normal file
205
src/data/extra/web/css/outline.css
Normal file
@ -0,0 +1,205 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.col, .col-1, .col-10, .col-11, .col-12, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-auto, .col-lg, .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-auto, .col-md, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-auto, .col-sm, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-auto, .col-xl, .col-xl-1, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-auto {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 1px;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.col-12 {
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex: 0 0 100%;
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.col-md-3 {
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex: 0 0 25%;
|
||||
flex: 0 0 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.col-md-9 {
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex: 0 0 75%;
|
||||
flex: 0 0 75%;
|
||||
max-width: 75%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.col-xl-2 {
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex: 0 0 16.666667%;
|
||||
flex: 0 0 16.666667%;
|
||||
max-width: 16.666667%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.col-xl-10 {
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex: 0 0 83.333333%;
|
||||
flex: 0 0 83.333333%;
|
||||
max-width: 83.333333%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.pt-md-3, .py-md-3 {
|
||||
padding-top: 1rem!important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.pb-md-3, .py-md-3 {
|
||||
padding-bottom: 1rem!important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.pl-md-5, .px-md-5 {
|
||||
padding-left: 3rem!important;
|
||||
}
|
||||
}
|
||||
|
||||
.d-none {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.d-xl-block {
|
||||
display: block!important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.d-md-block {
|
||||
display: block!important;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-content {
|
||||
-webkit-box-ordinal-group: 1;
|
||||
-ms-flex-order: 0;
|
||||
order: 0;
|
||||
}
|
||||
|
||||
.bd-toc {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 4rem;
|
||||
height: calc(100vh - 10rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.bd-toc {
|
||||
-webkit-box-ordinal-group: 2;
|
||||
-ms-flex-order: 1;
|
||||
order: 1;
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.section-nav {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.section-nav ul {
|
||||
font-size: .875rem;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.section-nav li {
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.section-nav a {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
margin-right: -15px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.flex-xl-nowrap {
|
||||
flex-wrap: nowrap !important;
|
||||
}
|
||||
}
|
||||
|
||||
#floating-button {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
background: #00897B;
|
||||
position: fixed;
|
||||
top: .5rem;
|
||||
right: .5rem;
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 2px 5px #666;
|
||||
}
|
||||
|
||||
#floating-button .more {
|
||||
color: #F5F5F5;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: block;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: 2.5rem;
|
||||
font-size: 2rem;
|
||||
font-family: 'monospace';
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.hide-none {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.col-expand {
|
||||
-webkit-box-flex: 0;
|
||||
-ms-flex: 0 0 100% !important;
|
||||
flex: 0 0 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding-right: 3rem !important;
|
||||
}
|
||||
|
||||
.outline-bold {
|
||||
font-weight: bolder !important;
|
||||
}
|
||||
|
||||
@media print {
|
||||
#floating-button {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
@ -19,7 +19,8 @@ class Graphviz extends GraphRenderer {
|
||||
registerInternal() {
|
||||
this.vnotex.on('basicMarkdownRendered', () => {
|
||||
this.reset();
|
||||
this.renderCodeNodes(this.vnotex.contentContainer, 'svg');
|
||||
this.renderCodeNodes(this.vnotex.contentContainer,
|
||||
window.vxOptions.transformSvgToPngEnabled ? 'png' : 'svg');
|
||||
});
|
||||
|
||||
this.vnotex.getWorker('markdownit').addLangsToSkipHighlight(this.langs);
|
||||
|
@ -240,10 +240,6 @@ class MarkdownIt extends VxWorker {
|
||||
}
|
||||
|
||||
registerInternal() {
|
||||
this.vnotex.on('ready', () => {
|
||||
this.setConstrainImageWidthEnabled(window.vxOptions.constrainImageWidthEnabled);
|
||||
this.setIndentFirstLineEnabled(window.vxOptions.indentFirstLineEnabled);
|
||||
});
|
||||
this.vnotex.on('markdownTextUpdated', (p_text) => {
|
||||
this.render(this.vnotex.contentContainer,
|
||||
p_text,
|
||||
@ -251,24 +247,6 @@ class MarkdownIt extends VxWorker {
|
||||
});
|
||||
}
|
||||
|
||||
setConstrainImageWidthEnabled(p_enabled) {
|
||||
let constrainClass = 'vx-constrain-image-width';
|
||||
if (p_enabled) {
|
||||
this.vnotex.contentContainer.classList.add(constrainClass);
|
||||
} else {
|
||||
this.vnotex.contentContainer.classList.remove(constrainClass);
|
||||
}
|
||||
}
|
||||
|
||||
setIndentFirstLineEnabled(p_enabled) {
|
||||
let constrainClass = 'vx-indent-first-line';
|
||||
if (p_enabled) {
|
||||
this.vnotex.contentContainer.classList.add(constrainClass);
|
||||
} else {
|
||||
this.vnotex.contentContainer.classList.remove(constrainClass);
|
||||
}
|
||||
}
|
||||
|
||||
// Render Markdown @p_text to HTML in @p_node.
|
||||
// @p_finishCbStr will be called after finishing loading new content nodes.
|
||||
// This could prevent Mermaid Gantt from negative width error.
|
||||
|
@ -43,6 +43,10 @@ new QWebChannel(qt.webChannelTransport,
|
||||
window.vnotex.findText(p_text, p_options);
|
||||
});
|
||||
|
||||
adapter.contentRequested.connect(function() {
|
||||
window.vnotex.saveContent();
|
||||
});
|
||||
|
||||
console.log('QWebChannel has been set up');
|
||||
if (window.vnotex.initialized) {
|
||||
window.vnotex.kickOffMarkdown();
|
||||
|
@ -18,7 +18,8 @@ window.MathJax = {
|
||||
},
|
||||
svg: {
|
||||
// Make SVG self-contained.
|
||||
fontCache: 'local'
|
||||
fontCache: 'local',
|
||||
scale: window.vxOptions.mathJaxScale > 0 ? window.vxOptions.mathJaxScale : 1
|
||||
}
|
||||
};
|
||||
|
||||
|
241
src/data/extra/web/js/outline.js
Normal file
241
src/data/extra/web/js/outline.js
Normal file
@ -0,0 +1,241 @@
|
||||
var toc = [];
|
||||
|
||||
var setVisible = function(node, visible) {
|
||||
var cl = 'hide-none';
|
||||
if (visible) {
|
||||
node.classList.remove(cl);
|
||||
} else {
|
||||
node.classList.add(cl);
|
||||
}
|
||||
};
|
||||
|
||||
var isVisible = function(node) {
|
||||
var cl = 'hide-none';
|
||||
return !node.classList.contains(cl);
|
||||
};
|
||||
|
||||
var setPostContentExpanded = function(node, expanded) {
|
||||
var cl = 'col-expand';
|
||||
if (expanded) {
|
||||
node.classList.add(cl);
|
||||
} else {
|
||||
node.classList.remove(cl);
|
||||
}
|
||||
};
|
||||
|
||||
var setOutlinePanelVisible = function(visible) {
|
||||
var outlinePanel = document.getElementById('outline-panel');
|
||||
var postContent = document.getElementById('post-content');
|
||||
|
||||
setVisible(outlinePanel, visible);
|
||||
setPostContentExpanded(postContent, !visible);
|
||||
};
|
||||
|
||||
var isOutlinePanelVisible = function() {
|
||||
var outlinePanel = document.getElementById('outline-panel');
|
||||
return isVisible(outlinePanel);
|
||||
};
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
var outlinePanel = document.getElementById('outline-panel');
|
||||
outlinePanel.style.display = 'initial';
|
||||
|
||||
var floatingContainer = document.getElementById('container-floating');
|
||||
floatingContainer.style.display = 'initial';
|
||||
|
||||
var outlineContent = document.getElementById('outline-content');
|
||||
var postContent = document.getElementById('post-content');
|
||||
|
||||
// Escape @text to Html.
|
||||
var escapeHtml = function(text) {
|
||||
var map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
// Fetch the outline.
|
||||
var headers = postContent.querySelectorAll("h1, h2, h3, h4, h5, h6");
|
||||
toc = [];
|
||||
for (var i = 0; i < headers.length; ++i) {
|
||||
var header = headers[i];
|
||||
|
||||
toc.push({
|
||||
level: parseInt(header.tagName.substr(1)),
|
||||
anchor: header.id,
|
||||
title: escapeHtml(header.textContent)
|
||||
});
|
||||
}
|
||||
|
||||
if (toc.length == 0) {
|
||||
setOutlinePanelVisible(false);
|
||||
setVisible(floatingContainer, false);
|
||||
return;
|
||||
}
|
||||
|
||||
var baseLevel = baseLevelOfToc(toc);
|
||||
var tocTree = tocToTree(toPerfectToc(toc, baseLevel), baseLevel);
|
||||
|
||||
outlineContent.innerHTML = tocTree;
|
||||
setOutlinePanelVisible(true);
|
||||
setVisible(floatingContainer, true);
|
||||
});
|
||||
|
||||
// Return the topest level of @toc, starting from 1.
|
||||
var baseLevelOfToc = function(p_toc) {
|
||||
var level = -1;
|
||||
for (i in p_toc) {
|
||||
if (level == -1) {
|
||||
level = p_toc[i].level;
|
||||
} else if (level > p_toc[i].level) {
|
||||
level = p_toc[i].level;
|
||||
}
|
||||
}
|
||||
|
||||
if (level == -1) {
|
||||
level = 1;
|
||||
}
|
||||
|
||||
return level;
|
||||
};
|
||||
|
||||
// Handle wrong title levels, such as '#' followed by '###'
|
||||
var toPerfectToc = function(p_toc, p_baseLevel) {
|
||||
var i;
|
||||
var curLevel = p_baseLevel - 1;
|
||||
var perfToc = [];
|
||||
for (i in p_toc) {
|
||||
var item = p_toc[i];
|
||||
|
||||
// Insert empty header.
|
||||
while (item.level > curLevel + 1) {
|
||||
curLevel += 1;
|
||||
var tmp = { level: curLevel,
|
||||
anchor: '',
|
||||
title: '[EMPTY]'
|
||||
};
|
||||
perfToc.push(tmp);
|
||||
}
|
||||
|
||||
perfToc.push(item);
|
||||
curLevel = item.level;
|
||||
}
|
||||
|
||||
return perfToc;
|
||||
};
|
||||
|
||||
var itemToHtml = function(item) {
|
||||
return '<a href="#' + item.anchor + '" data="' + item.anchor + '">' + item.title + '</a>';
|
||||
};
|
||||
|
||||
// Turn a perfect toc to a tree using <ul>
|
||||
var tocToTree = function(p_toc, p_baseLevel) {
|
||||
var i;
|
||||
var front = '<li>';
|
||||
var ending = ['</li>'];
|
||||
var curLevel = p_baseLevel;
|
||||
for (i in p_toc) {
|
||||
var item = p_toc[i];
|
||||
if (item.level == curLevel) {
|
||||
front += '</li>';
|
||||
front += '<li>';
|
||||
front += itemToHtml(item);
|
||||
} else if (item.level > curLevel) {
|
||||
// assert(item.level - curLevel == 1)
|
||||
front += '<ul>';
|
||||
ending.push('</ul>');
|
||||
front += '<li>';
|
||||
front += itemToHtml(item);
|
||||
ending.push('</li>');
|
||||
curLevel = item.level;
|
||||
} else {
|
||||
while (item.level < curLevel) {
|
||||
var ele = ending.pop();
|
||||
front += ele;
|
||||
if (ele == '</ul>') {
|
||||
curLevel--;
|
||||
}
|
||||
}
|
||||
front += '</li>';
|
||||
front += '<li>';
|
||||
front += itemToHtml(item);
|
||||
}
|
||||
}
|
||||
while (ending.length > 0) {
|
||||
front += ending.pop();
|
||||
}
|
||||
front = front.replace("<li></li>", "");
|
||||
front = '<ul>' + front + '</ul>';
|
||||
return front;
|
||||
};
|
||||
|
||||
var toggleMore = function() {
|
||||
if (toc.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var p = document.getElementById('floating-more');
|
||||
if (isOutlinePanelVisible()) {
|
||||
p.textContent = '<';
|
||||
setOutlinePanelVisible(false);
|
||||
} else {
|
||||
p.textContent = '>';
|
||||
setOutlinePanelVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', function() {
|
||||
if (toc.length == 0 || !isOutlinePanelVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var postContent = document.getElementById('post-content');
|
||||
var scrollTop = document.documentElement.scrollTop
|
||||
|| document.body.scrollTop
|
||||
|| window.pageYOffset;
|
||||
var eles = postContent.querySelectorAll("h1, h2, h3, h4, h5, h6");
|
||||
|
||||
if (eles.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var idx = -1;
|
||||
var biaScrollTop = scrollTop + 50;
|
||||
for (var i = 0; i < eles.length; ++i) {
|
||||
if (biaScrollTop >= eles[i].offsetTop) {
|
||||
idx = i;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var header = '';
|
||||
if (idx != -1) {
|
||||
header = eles[idx].id;
|
||||
}
|
||||
|
||||
highlightItemOnlyInOutline(header);
|
||||
});
|
||||
|
||||
var highlightItemOnlyInOutline = function(id) {
|
||||
var cl = 'outline-bold';
|
||||
var outlineContent = document.getElementById('outline-content');
|
||||
var eles = outlineContent.querySelectorAll("a");
|
||||
var target = null;
|
||||
for (var i = 0; i < eles.length; ++i) {
|
||||
var ele = eles[i];
|
||||
if (ele.getAttribute('data') == id) {
|
||||
target = ele;
|
||||
ele.classList.add(cl);
|
||||
} else {
|
||||
ele.classList.remove(cl);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: scroll target into view within the outline panel scroll area.
|
||||
};
|
@ -19,7 +19,8 @@ class PlantUml extends GraphRenderer {
|
||||
registerInternal() {
|
||||
this.vnotex.on('basicMarkdownRendered', () => {
|
||||
this.reset();
|
||||
this.renderCodeNodes(this.vnotex.contentContainer, 'svg');
|
||||
this.renderCodeNodes(this.vnotex.contentContainer,
|
||||
window.vxOptions.transformSvgToPngEnabled ? 'png' : 'svg');
|
||||
});
|
||||
|
||||
this.vnotex.getWorker('markdownit').addLangsToSkipHighlight(this.langs);
|
||||
|
@ -113,4 +113,55 @@ class Utils {
|
||||
static headingSequenceRegExp() {
|
||||
return /^\d{1,3}(?:\.\d+)*\. /;
|
||||
}
|
||||
|
||||
static fetchStyleContent() {
|
||||
let styles = "";
|
||||
for (let styleIdx = 0; styleIdx < document.styleSheets.length; ++styleIdx) {
|
||||
let styleSheet = document.styleSheets[styleIdx];
|
||||
if (styleSheet.cssRules) {
|
||||
let baseUrl = null;
|
||||
if (styleSheet.href) {
|
||||
let scheme = Utils.getUrlScheme(styleSheet.href);
|
||||
// We only translate local resources.
|
||||
if (scheme === 'file' || scheme === 'qrc') {
|
||||
baseUrl = styleSheet.href.substr(0, styleSheet.href.lastIndexOf('/'));
|
||||
}
|
||||
}
|
||||
|
||||
for (let ruleIdx = 0; ruleIdx < styleSheet.cssRules.length; ++ruleIdx) {
|
||||
let css = styleSheet.cssRules[ruleIdx].cssText;
|
||||
if (baseUrl) {
|
||||
// Try to replace the url() with absolute path.
|
||||
css = Utils.translateCssUrlToAbsolute(baseUrl, css);
|
||||
}
|
||||
|
||||
styles = styles + css + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
static translateCssUrlToAbsolute(p_baseUrl, p_css) {
|
||||
let replaceCssUrl = function(baseUrl, match, p1, offset, str) {
|
||||
if (Utils.getUrlScheme(p1)) {
|
||||
return match;
|
||||
}
|
||||
|
||||
let url = baseUrl + '/' + p1;
|
||||
return "url(\"" + url + "\");";
|
||||
};
|
||||
|
||||
return p_css.replace(/\burl\(\"([^\"\)]+)\"\);/g, replaceCssUrl.bind(undefined, p_baseUrl));
|
||||
}
|
||||
|
||||
static getUrlScheme(p_url) {
|
||||
let idx = p_url.indexOf(':');
|
||||
if (idx > -1) {
|
||||
return p_url.substr(0, idx);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,18 @@ class VNoteX extends EventEmitter {
|
||||
this.sectionNumberBaseLevel = 3;
|
||||
}
|
||||
|
||||
this.setContentContainerOption('vx-constrain-image-width',
|
||||
window.vxOptions.constrainImageWidthEnabled || !window.vxOptions.scrollable);
|
||||
this.setContentContainerOption('vx-indent-first-line',
|
||||
window.vxOptions.indentFirstLineEnabled);
|
||||
this.setBodyOption('vx-transparent-background',
|
||||
window.vxOptions.transparentBackgroundEnabled);
|
||||
this.setContentContainerOption('vx-nonscrollable',
|
||||
!window.vxOptions.scrollable);
|
||||
|
||||
this.setBodySize(window.vxOptions.bodyWidth, window.vxOptions.bodyHeight);
|
||||
document.body.style.height = '800';
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
// Signal out.
|
||||
@ -68,6 +80,22 @@ class VNoteX extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
setContentContainerOption(p_class, p_enabled) {
|
||||
if (p_enabled) {
|
||||
this.contentContainer.classList.add(p_class);
|
||||
} else {
|
||||
this.contentContainer.classList.remove(p_class);
|
||||
}
|
||||
}
|
||||
|
||||
setBodyOption(p_class, p_enabled) {
|
||||
if (p_enabled) {
|
||||
document.body.classList.add(p_class);
|
||||
} else {
|
||||
document.body.classList.remove(p_class);
|
||||
}
|
||||
}
|
||||
|
||||
registerWorker(p_worker) {
|
||||
this.workers.set(p_worker.name, p_worker);
|
||||
|
||||
@ -79,6 +107,7 @@ class VNoteX extends EventEmitter {
|
||||
if (this.numOfOngoingWorkers == 0) {
|
||||
// Signal out anyway.
|
||||
this.emit('fullMarkdownRendered');
|
||||
window.vxMarkdownAdapter.setWorkFinished();
|
||||
|
||||
// Check pending work.
|
||||
if (this.pendingData.text) {
|
||||
@ -211,13 +240,8 @@ class VNoteX extends EventEmitter {
|
||||
setSectionNumberEnabled(p_enabled) {
|
||||
let sectionClass = 'vx-section-number';
|
||||
let sectionLevelClass = 'vx-section-number-' + this.sectionNumberBaseLevel;
|
||||
if (p_enabled) {
|
||||
this.contentContainer.classList.add(sectionClass);
|
||||
this.contentContainer.classList.add(sectionLevelClass);
|
||||
} else {
|
||||
this.contentContainer.classList.remove(sectionClass);
|
||||
this.contentContainer.classList.remove(sectionLevelClass);
|
||||
}
|
||||
this.setContentContainerOption(sectionClass, p_enabled);
|
||||
this.setContentContainerOption(sectionLevelClass, p_enabled);
|
||||
}
|
||||
|
||||
scroll(p_up) {
|
||||
@ -261,6 +285,28 @@ class VNoteX extends EventEmitter {
|
||||
window.vxMarkdownAdapter.setFindText(p_text, p_totalMatches, p_currentMatchIndex);
|
||||
}
|
||||
|
||||
saveContent() {
|
||||
if (!this.initialized) {
|
||||
console.warn('saveContent() called before initialization');
|
||||
window.vxMarkdownAdapter.setSavedContent('', '', '');
|
||||
return;
|
||||
}
|
||||
window.vxMarkdownAdapter.setSavedContent("",
|
||||
Utils.fetchStyleContent(),
|
||||
this.contentContainer.outerHTML,
|
||||
document.body.classList.value);
|
||||
}
|
||||
|
||||
setBodySize(p_width, p_height) {
|
||||
if (p_width > 0) {
|
||||
document.body.style.width = p_width + 'px';
|
||||
}
|
||||
|
||||
if (p_height > 0) {
|
||||
document.body.style.height = p_height + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
static detectOS() {
|
||||
let osName="Unknown OS";
|
||||
if (navigator.appVersion.indexOf("Win")!=-1) {
|
||||
|
45
src/data/extra/web/markdown-export-template.html
Normal file
45
src/data/extra/web/markdown-export-template.html
Normal file
@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="generator" content="VNote">
|
||||
|
||||
<!-- VX_TITLE_PLACEHOLDER -->
|
||||
|
||||
<style type="text/css">
|
||||
/* VX_GLOBAL_STYLES_PLACEHOLDER */
|
||||
</style>
|
||||
|
||||
<style type="text/css">
|
||||
/* VX_STYLES_PLACEHOLDER */
|
||||
|
||||
/* VX_STYLES_CONTENT_PLACEHOLDER */
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
/* VX_SCRIPTS_PLACEHOLDER */
|
||||
</script>
|
||||
|
||||
<!-- VX_SCRIPTS_PLACEHOLDER -->
|
||||
|
||||
<!-- VX_HEAD_PLACEHOLDER -->
|
||||
</head>
|
||||
<body class="<!-- VX_BODY_CLASS_LIST_PLACEHOLDER -->">
|
||||
<div class="container-fluid">
|
||||
<div class="row flex-xl-nowrap">
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
11
src/export/export.pri
Normal file
11
src/export/export.pri
Normal file
@ -0,0 +1,11 @@
|
||||
QT += widgets
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/exportdata.cpp \
|
||||
$$PWD/exporter.cpp \
|
||||
$$PWD/webviewexporter.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/exportdata.h \
|
||||
$$PWD/exporter.h \
|
||||
$$PWD/webviewexporter.h
|
126
src/export/exportdata.cpp
Normal file
126
src/export/exportdata.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
#include "exportdata.h"
|
||||
|
||||
#include <QPageLayout>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
QJsonObject ExportHtmlOption::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["embed_styles"] = m_embedStyles;
|
||||
obj["complete_page"] = m_completePage;
|
||||
obj["embed_images"] = m_embedImages;
|
||||
obj["use_mime_html_format"] = m_useMimeHtmlFormat;
|
||||
obj["add_outline_panel"] = m_addOutlinePanel;
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ExportHtmlOption::fromJson(const QJsonObject &p_obj)
|
||||
{
|
||||
if (p_obj.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_embedStyles = p_obj["embed_styles"].toBool();
|
||||
m_completePage = p_obj["complete_page"].toBool();
|
||||
m_embedImages = p_obj["embed_images"].toBool();
|
||||
m_useMimeHtmlFormat = p_obj["use_mime_html_format"].toBool();
|
||||
m_addOutlinePanel = p_obj["add_outline_panel"].toBool();
|
||||
}
|
||||
|
||||
bool ExportHtmlOption::operator==(const ExportHtmlOption &p_other) const
|
||||
{
|
||||
return m_embedStyles == p_other.m_embedStyles
|
||||
&& m_completePage == p_other.m_completePage
|
||||
&& m_embedImages == p_other.m_embedImages
|
||||
&& m_useMimeHtmlFormat == p_other.m_useMimeHtmlFormat
|
||||
&& m_addOutlinePanel == p_other.m_addOutlinePanel;
|
||||
}
|
||||
|
||||
ExportPdfOption::ExportPdfOption()
|
||||
: m_layout(new QPageLayout(QPageSize(QPageSize::A4),
|
||||
QPageLayout::Portrait,
|
||||
QMarginsF(10, 16, 10, 10),
|
||||
QPageLayout::Millimeter))
|
||||
{
|
||||
}
|
||||
|
||||
QJsonObject ExportPdfOption::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["add_table_of_contents"] = m_addTableOfContents;
|
||||
obj["use_wkhtmltopdf"] = m_useWkhtmltopdf;
|
||||
obj["wkhtmltopdf_exe_path"] = m_wkhtmltopdfExePath;
|
||||
obj["wkhtmltopdf_args"] = m_wkhtmltopdfArgs;
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ExportPdfOption::fromJson(const QJsonObject &p_obj)
|
||||
{
|
||||
if (p_obj.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_addTableOfContents = p_obj["add_table_of_contents"].toBool();
|
||||
m_useWkhtmltopdf = p_obj["use_wkhtmltopdf"].toBool();
|
||||
m_wkhtmltopdfExePath = p_obj["wkhtmltopdf_exe_path"].toString();
|
||||
m_wkhtmltopdfArgs = p_obj["wkhtmltopdf_args"].toString();
|
||||
}
|
||||
|
||||
bool ExportPdfOption::operator==(const ExportPdfOption &p_other) const
|
||||
{
|
||||
return m_addTableOfContents == p_other.m_addTableOfContents
|
||||
&& m_useWkhtmltopdf == p_other.m_useWkhtmltopdf
|
||||
&& m_wkhtmltopdfExePath == p_other.m_wkhtmltopdfExePath
|
||||
&& m_wkhtmltopdfArgs == p_other.m_wkhtmltopdfArgs;
|
||||
}
|
||||
|
||||
QJsonObject ExportOption::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["use_transparent_bg"] = m_useTransparentBg;
|
||||
obj["output_dir"] = m_outputDir;
|
||||
obj["recursive"] = m_recursive;
|
||||
obj["export_attachments"] = m_exportAttachments;
|
||||
obj["html_option"] = m_htmlOption.toJson();
|
||||
obj["pdf_option"] = m_pdfOption.toJson();
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ExportOption::fromJson(const QJsonObject &p_obj)
|
||||
{
|
||||
if (p_obj.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_useTransparentBg = p_obj["use_transparent_bg"].toBool();
|
||||
m_outputDir = p_obj["output_dir"].toString();
|
||||
m_recursive = p_obj["recursive"].toBool();
|
||||
m_exportAttachments = p_obj["export_attachments"].toBool();
|
||||
m_htmlOption.fromJson(p_obj["html_option"].toObject());
|
||||
m_pdfOption.fromJson(p_obj["pdf_option"].toObject());
|
||||
}
|
||||
|
||||
bool ExportOption::operator==(const ExportOption &p_other) const
|
||||
{
|
||||
bool ret = m_useTransparentBg == p_other.m_useTransparentBg
|
||||
&& m_outputDir == p_other.m_outputDir
|
||||
&& m_recursive == p_other.m_recursive
|
||||
&& m_exportAttachments == p_other.m_exportAttachments;
|
||||
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(m_htmlOption == p_other.m_htmlOption)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(m_pdfOption == p_other.m_pdfOption)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
114
src/export/exportdata.h
Normal file
114
src/export/exportdata.h
Normal file
@ -0,0 +1,114 @@
|
||||
#ifndef EXPORTDATA_H
|
||||
#define EXPORTDATA_H
|
||||
|
||||
#include <QSharedPointer>
|
||||
|
||||
class QPageLayout;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
enum class ExportSource
|
||||
{
|
||||
CurrentBuffer = 0,
|
||||
CurrentFolder,
|
||||
CurrentNotebook
|
||||
};
|
||||
|
||||
enum class ExportFormat
|
||||
{
|
||||
Markdown = 0,
|
||||
HTML,
|
||||
PDF,
|
||||
Custom
|
||||
};
|
||||
|
||||
struct ExportHtmlOption
|
||||
{
|
||||
QJsonObject toJson() const;
|
||||
void fromJson(const QJsonObject &p_obj);
|
||||
|
||||
bool operator==(const ExportHtmlOption &p_other) const;
|
||||
|
||||
bool m_embedStyles = true;
|
||||
|
||||
bool m_completePage = true;
|
||||
|
||||
bool m_embedImages = true;
|
||||
|
||||
bool m_useMimeHtmlFormat = false;
|
||||
|
||||
// Whether add outline panel.
|
||||
bool m_addOutlinePanel = true;
|
||||
};
|
||||
|
||||
struct ExportPdfOption
|
||||
{
|
||||
ExportPdfOption();
|
||||
|
||||
QJsonObject toJson() const;
|
||||
void fromJson(const QJsonObject &p_obj);
|
||||
|
||||
bool operator==(const ExportPdfOption &p_other) const;
|
||||
|
||||
QSharedPointer<QPageLayout> m_layout;
|
||||
|
||||
// Add TOC at the front to complement the missing of outline.
|
||||
bool m_addTableOfContents = false;
|
||||
|
||||
bool m_useWkhtmltopdf = false;
|
||||
|
||||
QString m_wkhtmltopdfExePath;
|
||||
|
||||
QString m_wkhtmltopdfArgs;
|
||||
};
|
||||
|
||||
struct ExportOption
|
||||
{
|
||||
QJsonObject toJson() const;
|
||||
void fromJson(const QJsonObject &p_obj);
|
||||
|
||||
bool operator==(const ExportOption &p_other) const;
|
||||
|
||||
ExportSource m_source = ExportSource::CurrentBuffer;
|
||||
|
||||
ExportFormat m_targetFormat = ExportFormat::HTML;
|
||||
|
||||
bool m_useTransparentBg = true;
|
||||
|
||||
QString m_renderingStyleFile;
|
||||
|
||||
QString m_syntaxHighlightStyleFile;
|
||||
|
||||
QString m_outputDir;
|
||||
|
||||
bool m_recursive = true;
|
||||
|
||||
bool m_exportAttachments = true;
|
||||
|
||||
ExportHtmlOption m_htmlOption;
|
||||
|
||||
ExportPdfOption m_pdfOption;
|
||||
};
|
||||
|
||||
inline QString exportFormatString(ExportFormat p_format)
|
||||
{
|
||||
switch (p_format)
|
||||
{
|
||||
case ExportFormat::Markdown:
|
||||
return QStringLiteral("Markdown");
|
||||
|
||||
case ExportFormat::HTML:
|
||||
return QStringLiteral("HTML");
|
||||
|
||||
case ExportFormat::PDF:
|
||||
return QStringLiteral("PDF");
|
||||
|
||||
case ExportFormat::Custom:
|
||||
return QStringLiteral("Custom");
|
||||
}
|
||||
|
||||
return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
#endif // EXPORTDATA_H
|
312
src/export/exporter.cpp
Normal file
312
src/export/exporter.cpp
Normal file
@ -0,0 +1,312 @@
|
||||
#include "exporter.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <notebook/notebook.h>
|
||||
#include <notebook/node.h>
|
||||
#include <buffer/buffer.h>
|
||||
#include <core/file.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/pathutils.h>
|
||||
#include <utils/contentmediautils.h>
|
||||
#include "webviewexporter.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
Exporter::Exporter(QWidget *p_parent)
|
||||
: QObject(p_parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString Exporter::doExport(const ExportOption &p_option, Buffer *p_buffer)
|
||||
{
|
||||
m_askedToStop = false;
|
||||
|
||||
QString outputFile;
|
||||
auto file = p_buffer->getFile();
|
||||
if (!file) {
|
||||
emit logRequested(tr("Skipped buffer (%1) without file base.").arg(p_buffer->getName()));
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
// Make sure output folder exists.
|
||||
if (!QDir().mkpath(p_option.m_outputDir)) {
|
||||
emit logRequested(tr("Failed to create output folder %1.").arg(p_option.m_outputDir));
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
outputFile = doExport(p_option, p_option.m_outputDir, file.data());
|
||||
|
||||
cleanUp();
|
||||
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
QString Exporter::doExportMarkdown(const ExportOption &p_option, const QString &p_outputDir, const File *p_file)
|
||||
{
|
||||
QString outputFile;
|
||||
if (!p_file->getContentType().isMarkdown()) {
|
||||
emit logRequested(tr("Format %1 is not supported to export as Markdown.").arg(p_file->getContentType().m_displayName));
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
// Export it to a folder with the same name.
|
||||
auto name = FileUtils::generateFileNameWithSequence(p_outputDir, p_file->getName(), "");
|
||||
auto outputFolder = PathUtils::concatenateFilePath(p_outputDir, name);
|
||||
QDir outDir(outputFolder);
|
||||
if (!outDir.mkpath(outputFolder)) {
|
||||
emit logRequested(tr("Failed to create output folder %1.").arg(outputFolder));
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
// Copy source file itself.
|
||||
const auto srcFilePath = p_file->getFilePath();
|
||||
auto destFilePath = outDir.filePath(p_file->getName());
|
||||
FileUtils::copyFile(srcFilePath, destFilePath, false);
|
||||
outputFile = destFilePath;
|
||||
|
||||
ContentMediaUtils::copyMediaFiles(p_file, destFilePath);
|
||||
|
||||
// Copy attachments if available.
|
||||
if (p_option.m_exportAttachments) {
|
||||
exportAttachments(p_file->getNode(), srcFilePath, outputFolder, destFilePath);
|
||||
}
|
||||
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
void Exporter::exportAttachments(Node *p_node,
|
||||
const QString &p_srcFilePath,
|
||||
const QString &p_outputFolder,
|
||||
const QString &p_destFilePath)
|
||||
{
|
||||
const auto &attachmentFolder = p_node->getAttachmentFolder();
|
||||
if (!attachmentFolder.isEmpty()) {
|
||||
auto relativePath = PathUtils::relativePath(PathUtils::parentDirPath(p_srcFilePath),
|
||||
p_node->fetchAttachmentFolderPath());
|
||||
auto destAttachmentFolderPath = QDir(p_outputFolder).filePath(relativePath);
|
||||
destAttachmentFolderPath = FileUtils::renameIfExistsCaseInsensitive(destAttachmentFolderPath);
|
||||
ContentMediaUtils::copyAttachment(p_node, nullptr, p_destFilePath, destAttachmentFolderPath);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList Exporter::doExport(const ExportOption &p_option, Node *p_folder)
|
||||
{
|
||||
m_askedToStop = false;
|
||||
|
||||
auto outputFiles = doExport(p_option, p_option.m_outputDir, p_folder);
|
||||
|
||||
cleanUp();
|
||||
|
||||
return outputFiles;
|
||||
}
|
||||
|
||||
QStringList Exporter::doExport(const ExportOption &p_option, const QString &p_outputDir, Node *p_folder)
|
||||
{
|
||||
Q_ASSERT(p_folder->isContainer());
|
||||
|
||||
QStringList outputFiles;
|
||||
|
||||
// Make path.
|
||||
auto name = FileUtils::generateFileNameWithSequence(p_outputDir, p_folder->getName());
|
||||
auto outputFolder = PathUtils::concatenateFilePath(p_outputDir, name);
|
||||
if (!QDir().mkpath(outputFolder)) {
|
||||
emit logRequested(tr("Failed to create output folder %1.").arg(outputFolder));
|
||||
return outputFiles;
|
||||
}
|
||||
|
||||
p_folder->load();
|
||||
const auto &children = p_folder->getChildren();
|
||||
emit progressUpdated(0, children.size());
|
||||
for (int i = 0; i < children.size(); ++i) {
|
||||
if (checkAskedToStop()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto &child = children[i];
|
||||
if (child->hasContent()) {
|
||||
auto outputFile = doExport(p_option, outputFolder, child->getContentFile().data());
|
||||
if (!outputFile.isEmpty()) {
|
||||
outputFiles << outputFile;
|
||||
}
|
||||
}
|
||||
if (p_option.m_recursive && child->isContainer() && child->getUse() == Node::Use::Normal) {
|
||||
outputFiles.append(doExport(p_option, outputFolder, child.data()));
|
||||
}
|
||||
|
||||
emit progressUpdated(i + 1, children.size());
|
||||
}
|
||||
|
||||
return outputFiles;
|
||||
}
|
||||
|
||||
QString Exporter::doExport(const ExportOption &p_option, const QString &p_outputDir, const File *p_file)
|
||||
{
|
||||
QString outputFile;
|
||||
|
||||
switch (p_option.m_targetFormat) {
|
||||
case ExportFormat::Markdown:
|
||||
outputFile = doExportMarkdown(p_option, p_outputDir, p_file);
|
||||
break;
|
||||
|
||||
case ExportFormat::HTML:
|
||||
outputFile = doExportHtml(p_option, p_outputDir, p_file);
|
||||
break;
|
||||
|
||||
case ExportFormat::PDF:
|
||||
outputFile = doExportPdf(p_option, p_outputDir, p_file);
|
||||
break;
|
||||
|
||||
default:
|
||||
emit logRequested(tr("Unknown target format %1.").arg(exportFormatString(p_option.m_targetFormat)));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!outputFile.isEmpty()) {
|
||||
emit logRequested(tr("File (%1) exported to (%2)").arg(p_file->getFilePath(), outputFile));
|
||||
} else {
|
||||
emit logRequested(tr("Failed to export file (%1)").arg(p_file->getFilePath()));
|
||||
}
|
||||
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
QStringList Exporter::doExport(const ExportOption &p_option, Notebook *p_notebook)
|
||||
{
|
||||
m_askedToStop = false;
|
||||
|
||||
QStringList outputFiles;
|
||||
|
||||
// Make path.
|
||||
auto name = FileUtils::generateFileNameWithSequence(p_option.m_outputDir,
|
||||
tr("notebook_%1").arg(p_notebook->getName()));
|
||||
auto outputFolder = PathUtils::concatenateFilePath(p_option.m_outputDir, name);
|
||||
if (!QDir().mkpath(outputFolder)) {
|
||||
emit logRequested(tr("Failed to create output folder %1.").arg(outputFolder));
|
||||
return outputFiles;
|
||||
}
|
||||
|
||||
auto rootNode = p_notebook->getRootNode();
|
||||
Q_ASSERT(rootNode->isLoaded());
|
||||
|
||||
const auto &children = rootNode->getChildren();
|
||||
emit progressUpdated(0, children.size());
|
||||
for (int i = 0; i < children.size(); ++i) {
|
||||
if (checkAskedToStop()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto &child = children[i];
|
||||
if (child->hasContent()) {
|
||||
auto outputFile = doExport(p_option, outputFolder, child->getContentFile().data());
|
||||
if (!outputFile.isEmpty()) {
|
||||
outputFiles << outputFile;
|
||||
}
|
||||
}
|
||||
if (child->isContainer() && child->getUse() == Node::Use::Normal) {
|
||||
outputFiles.append(doExport(p_option, outputFolder, child.data()));
|
||||
}
|
||||
|
||||
emit progressUpdated(i + 1, children.size());
|
||||
}
|
||||
|
||||
cleanUp();
|
||||
|
||||
return outputFiles;
|
||||
}
|
||||
|
||||
QString Exporter::doExportHtml(const ExportOption &p_option, const QString &p_outputDir, const File *p_file)
|
||||
{
|
||||
QString outputFile;
|
||||
if (!p_file->getContentType().isMarkdown()) {
|
||||
emit logRequested(tr("Format %1 is not supported to export as HTML.").arg(p_file->getContentType().m_displayName));
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
QString suffix = p_option.m_htmlOption.m_useMimeHtmlFormat ? QStringLiteral("mht") : QStringLiteral("html");
|
||||
auto fileName = FileUtils::generateFileNameWithSequence(p_outputDir,
|
||||
QFileInfo(p_file->getName()).completeBaseName(),
|
||||
suffix);
|
||||
auto destFilePath = PathUtils::concatenateFilePath(p_outputDir, fileName);
|
||||
|
||||
bool success = getWebViewExporter(p_option)->doExport(p_option, p_file, destFilePath);
|
||||
if (success) {
|
||||
outputFile = destFilePath;
|
||||
|
||||
// Copy attachments if available.
|
||||
if (p_option.m_exportAttachments) {
|
||||
exportAttachments(p_file->getNode(), p_file->getFilePath(), p_outputDir, destFilePath);
|
||||
}
|
||||
}
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
WebViewExporter *Exporter::getWebViewExporter(const ExportOption &p_option)
|
||||
{
|
||||
if (!m_webViewExporter) {
|
||||
m_webViewExporter = new WebViewExporter(static_cast<QWidget *>(parent()));
|
||||
connect(m_webViewExporter, &WebViewExporter::logRequested,
|
||||
this, &Exporter::logRequested);
|
||||
m_webViewExporter->prepare(p_option);
|
||||
}
|
||||
|
||||
return m_webViewExporter;
|
||||
}
|
||||
|
||||
void Exporter::cleanUpWebViewExporter()
|
||||
{
|
||||
if (m_webViewExporter) {
|
||||
m_webViewExporter->clear();
|
||||
delete m_webViewExporter;
|
||||
m_webViewExporter = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Exporter::cleanUp()
|
||||
{
|
||||
cleanUpWebViewExporter();
|
||||
}
|
||||
|
||||
void Exporter::stop()
|
||||
{
|
||||
m_askedToStop = true;
|
||||
|
||||
if (m_webViewExporter) {
|
||||
m_webViewExporter->stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool Exporter::checkAskedToStop() const
|
||||
{
|
||||
if (m_askedToStop) {
|
||||
emit const_cast<Exporter *>(this)->logRequested(tr("Asked to stop. Aborting."));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Exporter::doExportPdf(const ExportOption &p_option, const QString &p_outputDir, const File *p_file)
|
||||
{
|
||||
QString outputFile;
|
||||
if (!p_file->getContentType().isMarkdown()) {
|
||||
emit logRequested(tr("Format %1 is not supported to export as PDF.").arg(p_file->getContentType().m_displayName));
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
auto fileName = FileUtils::generateFileNameWithSequence(p_outputDir,
|
||||
QFileInfo(p_file->getName()).completeBaseName(),
|
||||
"pdf");
|
||||
auto destFilePath = PathUtils::concatenateFilePath(p_outputDir, fileName);
|
||||
|
||||
bool success = getWebViewExporter(p_option)->doExport(p_option, p_file, destFilePath);
|
||||
if (success) {
|
||||
outputFile = destFilePath;
|
||||
|
||||
// Copy attachments if available.
|
||||
if (p_option.m_exportAttachments) {
|
||||
exportAttachments(p_file->getNode(), p_file->getFilePath(), p_outputDir, destFilePath);
|
||||
}
|
||||
}
|
||||
return outputFile;
|
||||
}
|
70
src/export/exporter.h
Normal file
70
src/export/exporter.h
Normal file
@ -0,0 +1,70 @@
|
||||
#ifndef EXPORTER_H
|
||||
#define EXPORTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
|
||||
#include "exportdata.h"
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class Notebook;
|
||||
class Node;
|
||||
class Buffer;
|
||||
class File;
|
||||
class WebViewExporter;
|
||||
|
||||
class Exporter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// We need the QWidget as parent.
|
||||
explicit Exporter(QWidget *p_parent);
|
||||
|
||||
// Return exported output file.
|
||||
QString doExport(const ExportOption &p_option, Buffer *p_buffer);
|
||||
|
||||
// Return exported output files.
|
||||
QStringList doExport(const ExportOption &p_option, Node *p_folder);
|
||||
|
||||
QStringList doExport(const ExportOption &p_option, Notebook *p_notebook);
|
||||
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void progressUpdated(int p_val, int p_maximum);
|
||||
|
||||
void logRequested(const QString &p_log);
|
||||
|
||||
private:
|
||||
QStringList doExport(const ExportOption &p_option, const QString &p_outputDir, Node *p_folder);
|
||||
|
||||
QString doExport(const ExportOption &p_option, const QString &p_outputDir, const File *p_file);
|
||||
|
||||
QString doExportMarkdown(const ExportOption &p_option, const QString &p_outputDir, const File *p_file);
|
||||
|
||||
QString doExportHtml(const ExportOption &p_option, const QString &p_outputDir, const File *p_file);
|
||||
|
||||
QString doExportPdf(const ExportOption &p_option, const QString &p_outputDir, const File *p_file);
|
||||
|
||||
void exportAttachments(Node *p_node,
|
||||
const QString &p_srcFilePath,
|
||||
const QString &p_outputFolder,
|
||||
const QString &p_destFilePath);
|
||||
|
||||
WebViewExporter *getWebViewExporter(const ExportOption &p_option);
|
||||
|
||||
void cleanUpWebViewExporter();
|
||||
|
||||
void cleanUp();
|
||||
|
||||
bool checkAskedToStop() const;
|
||||
|
||||
// Managed by QObject.
|
||||
WebViewExporter *m_webViewExporter = nullptr;
|
||||
|
||||
bool m_askedToStop = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // EXPORTER_H
|
568
src/export/webviewexporter.cpp
Normal file
568
src/export/webviewexporter.cpp
Normal file
@ -0,0 +1,568 @@
|
||||
#include "webviewexporter.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QWebEnginePage>
|
||||
#include <QFileInfo>
|
||||
#include <QTemporaryDir>
|
||||
#include <QProcess>
|
||||
|
||||
#include <widgets/editors/markdownviewer.h>
|
||||
#include <widgets/editors/editormarkdownvieweradapter.h>
|
||||
#include <core/editorconfig.h>
|
||||
#include <core/markdowneditorconfig.h>
|
||||
#include <core/configmgr.h>
|
||||
#include <core/htmltemplatehelper.h>
|
||||
#include <utils/utils.h>
|
||||
#include <utils/pathutils.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/webutils.h>
|
||||
#include <utils/processutils.h>
|
||||
#include <core/file.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
static const QString c_imgRegExp = "<img ([^>]*)src=\"(?!data:)([^\"]+)\"([^>]*)>";
|
||||
|
||||
WebViewExporter::WebViewExporter(QWidget *p_parent)
|
||||
: QObject(p_parent)
|
||||
{
|
||||
}
|
||||
|
||||
WebViewExporter::~WebViewExporter()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void WebViewExporter::clear()
|
||||
{
|
||||
m_askedToStop = false;
|
||||
|
||||
delete m_viewer;
|
||||
m_viewer = nullptr;
|
||||
|
||||
m_htmlTemplate.clear();
|
||||
m_exportHtmlTemplate.clear();
|
||||
|
||||
m_exportOngoing = false;
|
||||
}
|
||||
|
||||
bool WebViewExporter::doExport(const ExportOption &p_option,
|
||||
const File *p_file,
|
||||
const QString &p_outputFile)
|
||||
{
|
||||
bool ret = false;
|
||||
m_askedToStop = false;
|
||||
|
||||
Q_ASSERT(p_file->getContentType().isMarkdown());
|
||||
|
||||
Q_ASSERT(!m_exportOngoing);
|
||||
m_exportOngoing = true;
|
||||
|
||||
m_webViewStates = WebViewState::Started;
|
||||
|
||||
auto baseUrl = PathUtils::pathToUrl(p_file->getContentPath());
|
||||
m_viewer->setHtml(m_htmlTemplate, baseUrl);
|
||||
|
||||
auto textContent = p_file->read();
|
||||
if (p_option.m_targetFormat == ExportFormat::PDF
|
||||
&& p_option.m_pdfOption.m_addTableOfContents
|
||||
&& !p_option.m_pdfOption.m_useWkhtmltopdf) {
|
||||
// Add `[TOC]` at the beginning.
|
||||
m_viewer->adapter()->setText("[TOC]\n\n" + textContent);
|
||||
} else {
|
||||
m_viewer->adapter()->setText(textContent);
|
||||
}
|
||||
|
||||
while (!isWebViewReady()) {
|
||||
Utils::sleepWait(100);
|
||||
|
||||
if (m_askedToStop) {
|
||||
goto exit_export;
|
||||
}
|
||||
|
||||
if (isWebViewFailed()) {
|
||||
qWarning() << "WebView failed when exporting" << p_file->getFilePath();
|
||||
goto exit_export;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "WebView is ready";
|
||||
|
||||
// Add extra wait to make sure Web side is really ready.
|
||||
Utils::sleepWait(200);
|
||||
|
||||
switch (p_option.m_targetFormat) {
|
||||
case ExportFormat::HTML:
|
||||
// TODO: not supported yet.
|
||||
Q_ASSERT(!p_option.m_htmlOption.m_useMimeHtmlFormat);
|
||||
ret = doExportHtml(p_option.m_htmlOption, p_outputFile, baseUrl);
|
||||
break;
|
||||
|
||||
case ExportFormat::PDF:
|
||||
if (p_option.m_pdfOption.m_useWkhtmltopdf) {
|
||||
ret = doExportWkhtmltopdf(p_option.m_pdfOption, p_outputFile, baseUrl);
|
||||
} else {
|
||||
ret = doExportPdf(p_option.m_pdfOption, p_outputFile);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
exit_export:
|
||||
m_exportOngoing = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void WebViewExporter::stop()
|
||||
{
|
||||
m_askedToStop = true;
|
||||
}
|
||||
|
||||
bool WebViewExporter::isWebViewReady() const
|
||||
{
|
||||
return m_webViewStates == (WebViewState::LoadFinished | WebViewState::WorkFinished);
|
||||
}
|
||||
|
||||
bool WebViewExporter::isWebViewFailed() const
|
||||
{
|
||||
return m_webViewStates & WebViewState::Failed;
|
||||
}
|
||||
|
||||
bool WebViewExporter::doExportHtml(const ExportHtmlOption &p_htmlOption,
|
||||
const QString &p_outputFile,
|
||||
const QUrl &p_baseUrl)
|
||||
{
|
||||
ExportState state = ExportState::Busy;
|
||||
|
||||
connect(m_viewer->adapter(), &MarkdownViewerAdapter::contentReady,
|
||||
this, [&, this](const QString &p_headContent,
|
||||
const QString &p_styleContent,
|
||||
const QString &p_content,
|
||||
const QString &p_bodyClassList) {
|
||||
qDebug() << "doExportHtml contentReady";
|
||||
// Maybe unnecessary. Just to avoid duplicated signal connections.
|
||||
disconnect(m_viewer->adapter(), &MarkdownViewerAdapter::contentReady, this, 0);
|
||||
|
||||
if (p_content.isEmpty() || m_askedToStop) {
|
||||
state = ExportState::Failed;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!writeHtmlFile(p_outputFile,
|
||||
p_baseUrl,
|
||||
p_headContent,
|
||||
p_styleContent,
|
||||
p_content,
|
||||
p_bodyClassList,
|
||||
p_htmlOption.m_embedStyles,
|
||||
p_htmlOption.m_completePage,
|
||||
p_htmlOption.m_embedImages)) {
|
||||
state = ExportState::Failed;
|
||||
return;
|
||||
}
|
||||
|
||||
state = ExportState::Finished;
|
||||
});
|
||||
|
||||
m_viewer->adapter()->saveContent();
|
||||
|
||||
while (state == ExportState::Busy) {
|
||||
Utils::sleepWait(100);
|
||||
|
||||
if (m_askedToStop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return state == ExportState::Finished;
|
||||
}
|
||||
|
||||
bool WebViewExporter::writeHtmlFile(const QString &p_file,
|
||||
const QUrl &p_baseUrl,
|
||||
const QString &p_headContent,
|
||||
QString p_styleContent,
|
||||
const QString &p_content,
|
||||
const QString &p_bodyClassList,
|
||||
bool p_embedStyles,
|
||||
bool p_completePage,
|
||||
bool p_embedImages)
|
||||
{
|
||||
const auto baseName = QFileInfo(p_file).completeBaseName();
|
||||
auto title = QString("%1 - %2").arg(baseName, ConfigMgr::c_appName);
|
||||
const QString resourceFolderName = baseName + "_files";
|
||||
auto resourceFolder = PathUtils::concatenateFilePath(PathUtils::parentDirPath(p_file), resourceFolderName);
|
||||
|
||||
qDebug() << "HTML files folder" << resourceFolder;
|
||||
|
||||
auto htmlContent = m_exportHtmlTemplate;
|
||||
HtmlTemplateHelper::fillTitle(htmlContent, title);
|
||||
|
||||
if (!p_styleContent.isEmpty() && p_embedStyles) {
|
||||
embedStyleResources(p_styleContent);
|
||||
HtmlTemplateHelper::fillStyleContent(htmlContent, p_styleContent);
|
||||
}
|
||||
|
||||
if (!p_headContent.isEmpty()) {
|
||||
HtmlTemplateHelper::fillHeadContent(htmlContent, p_headContent);
|
||||
}
|
||||
|
||||
if (p_completePage) {
|
||||
QString content(p_content);
|
||||
if (p_embedImages) {
|
||||
embedBodyResources(p_baseUrl, content);
|
||||
} else {
|
||||
fixBodyResources(p_baseUrl, resourceFolder, content);
|
||||
}
|
||||
|
||||
HtmlTemplateHelper::fillContent(htmlContent, content);
|
||||
} else {
|
||||
HtmlTemplateHelper::fillContent(htmlContent, p_content);
|
||||
}
|
||||
|
||||
if (!p_bodyClassList.isEmpty()) {
|
||||
HtmlTemplateHelper::fillBodyClassList(htmlContent, p_bodyClassList);
|
||||
}
|
||||
|
||||
FileUtils::writeFile(p_file, htmlContent);
|
||||
|
||||
// Delete empty resource folder.
|
||||
QDir dir(resourceFolder);
|
||||
if (dir.exists() && dir.isEmpty()) {
|
||||
dir.cdUp();
|
||||
dir.rmdir(resourceFolderName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QSize WebViewExporter::pageLayoutSize(const QPageLayout &p_layout) const
|
||||
{
|
||||
Q_ASSERT(m_viewer);
|
||||
auto rect = p_layout.paintRect(QPageLayout::Inch);
|
||||
return QSize(rect.width() * m_viewer->logicalDpiX(), rect.height() * m_viewer->logicalDpiY());
|
||||
}
|
||||
|
||||
void WebViewExporter::prepare(const ExportOption &p_option)
|
||||
{
|
||||
Q_ASSERT(!m_viewer && !m_exportOngoing);
|
||||
{
|
||||
// Adapter will be managed by MarkdownViewer.
|
||||
auto adapter = new MarkdownViewerAdapter(this);
|
||||
m_viewer = new MarkdownViewer(adapter, QColor(), 1, static_cast<QWidget *>(parent()));
|
||||
m_viewer->hide();
|
||||
connect(m_viewer->page(), &QWebEnginePage::loadFinished,
|
||||
this, [this]() {
|
||||
m_webViewStates |= WebViewState::LoadFinished;
|
||||
});
|
||||
connect(adapter, &MarkdownViewerAdapter::workFinished,
|
||||
this, [this]() {
|
||||
m_webViewStates |= WebViewState::WorkFinished;
|
||||
});
|
||||
}
|
||||
|
||||
const bool scrollable = p_option.m_targetFormat != ExportFormat::PDF;
|
||||
const auto &config = ConfigMgr::getInst().getEditorConfig().getMarkdownEditorConfig();
|
||||
bool useWkhtmltopdf = false;
|
||||
QSize pageBodySize(1024, 768);
|
||||
if (p_option.m_targetFormat == ExportFormat::PDF) {
|
||||
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);
|
||||
|
||||
{
|
||||
const bool addOutlinePanel = p_option.m_targetFormat == ExportFormat::HTML && p_option.m_htmlOption.m_addOutlinePanel;
|
||||
m_exportHtmlTemplate = HtmlTemplateHelper::generateExportTemplate(config, addOutlinePanel);
|
||||
}
|
||||
|
||||
if (useWkhtmltopdf) {
|
||||
prepareWkhtmltopdfArguments(p_option.m_pdfOption);
|
||||
}
|
||||
}
|
||||
|
||||
static QString marginToStrMM(qreal p_margin)
|
||||
{
|
||||
return QString("%1mm").arg(p_margin);
|
||||
}
|
||||
|
||||
void WebViewExporter::prepareWkhtmltopdfArguments(const ExportPdfOption &p_pdfOption)
|
||||
{
|
||||
m_wkhtmltopdfArgs.clear();
|
||||
|
||||
// Page layout.
|
||||
{
|
||||
const auto &layout = p_pdfOption.m_layout;
|
||||
m_wkhtmltopdfArgs << "--page-size" << layout->pageSize().key();
|
||||
m_wkhtmltopdfArgs << "--orientation"
|
||||
<< (layout->orientation() == QPageLayout::Portrait ? "Portrait" : "Landscape");
|
||||
|
||||
const auto marginsMM = layout->margins(QPageLayout::Millimeter);
|
||||
m_wkhtmltopdfArgs << "--margin-bottom" << marginToStrMM(marginsMM.bottom());
|
||||
m_wkhtmltopdfArgs << "--margin-left" << marginToStrMM(marginsMM.left());
|
||||
m_wkhtmltopdfArgs << "--margin-right" << marginToStrMM(marginsMM.right());
|
||||
m_wkhtmltopdfArgs << "--margin-top" << marginToStrMM(marginsMM.top());
|
||||
|
||||
// Footer.
|
||||
m_wkhtmltopdfArgs << "--footer-right" << "[page]"
|
||||
<< "--footer-spacing" << QString::number(marginsMM.bottom() / 3, 'f', 2);
|
||||
}
|
||||
|
||||
m_wkhtmltopdfArgs << "--encoding" << "utf-8";
|
||||
|
||||
// Delay 10 seconds for MathJax.
|
||||
m_wkhtmltopdfArgs << "--javascript-delay" << "5000";
|
||||
|
||||
m_wkhtmltopdfArgs << "--enable-local-file-access";
|
||||
|
||||
// Append additional global option.
|
||||
if (!p_pdfOption.m_wkhtmltopdfArgs.isEmpty()) {
|
||||
m_wkhtmltopdfArgs.append(ProcessUtils::parseCombinedArgString(p_pdfOption.m_wkhtmltopdfArgs));
|
||||
}
|
||||
|
||||
// Must be put after the global object options.
|
||||
if (p_pdfOption.m_addTableOfContents) {
|
||||
m_wkhtmltopdfArgs << "toc" << "--toc-text-size-shrink" << "1.0";
|
||||
}
|
||||
}
|
||||
|
||||
bool WebViewExporter::embedStyleResources(QString &p_html) const
|
||||
{
|
||||
bool altered = false;
|
||||
QRegExp reg("\\burl\\(\"((file|qrc):[^\"\\)]+)\"\\);");
|
||||
|
||||
int pos = 0;
|
||||
while (pos < p_html.size()) {
|
||||
int idx = p_html.indexOf(reg, pos);
|
||||
if (idx == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
QString dataURI = WebUtils::toDataUri(QUrl(reg.cap(1)), false);
|
||||
if (dataURI.isEmpty()) {
|
||||
pos = idx + reg.matchedLength();
|
||||
} else {
|
||||
// Replace the url string in html.
|
||||
QString newUrl = QString("url('%1');").arg(dataURI);
|
||||
p_html.replace(idx, reg.matchedLength(), newUrl);
|
||||
pos = idx + newUrl.size();
|
||||
altered = true;
|
||||
}
|
||||
}
|
||||
|
||||
return altered;
|
||||
}
|
||||
|
||||
bool WebViewExporter::embedBodyResources(const QUrl &p_baseUrl, QString &p_html)
|
||||
{
|
||||
bool altered = false;
|
||||
if (p_baseUrl.isEmpty()) {
|
||||
return altered;
|
||||
}
|
||||
|
||||
QRegExp reg(c_imgRegExp);
|
||||
|
||||
int pos = 0;
|
||||
while (pos < p_html.size()) {
|
||||
int idx = p_html.indexOf(reg, pos);
|
||||
if (idx == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (reg.cap(2).isEmpty()) {
|
||||
pos = idx + reg.matchedLength();
|
||||
continue;
|
||||
}
|
||||
|
||||
QUrl srcUrl(p_baseUrl.resolved(reg.cap(2)));
|
||||
const auto dataURI = WebUtils::toDataUri(srcUrl, true);
|
||||
if (dataURI.isEmpty()) {
|
||||
pos = idx + reg.matchedLength();
|
||||
} else {
|
||||
// Replace the url string in html.
|
||||
QString newUrl = QString("<img %1src='%2'%3>").arg(reg.cap(1), dataURI, reg.cap(3));
|
||||
p_html.replace(idx, reg.matchedLength(), newUrl);
|
||||
pos = idx + newUrl.size();
|
||||
altered = true;
|
||||
}
|
||||
}
|
||||
|
||||
return altered;
|
||||
}
|
||||
|
||||
static QString getResourceRelativePath(const QString &p_file)
|
||||
{
|
||||
int idx = p_file.lastIndexOf('/');
|
||||
int idx2 = p_file.lastIndexOf('/', idx - 1);
|
||||
Q_ASSERT(idx > 0 && idx2 < idx);
|
||||
return "." + p_file.mid(idx2);
|
||||
}
|
||||
|
||||
bool WebViewExporter::fixBodyResources(const QUrl &p_baseUrl,
|
||||
const QString &p_folder,
|
||||
QString &p_html)
|
||||
{
|
||||
bool altered = false;
|
||||
if (p_baseUrl.isEmpty()) {
|
||||
return altered;
|
||||
}
|
||||
|
||||
QRegExp reg(c_imgRegExp);
|
||||
|
||||
int pos = 0;
|
||||
while (pos < p_html.size()) {
|
||||
int idx = p_html.indexOf(reg, pos);
|
||||
if (idx == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (reg.cap(2).isEmpty()) {
|
||||
pos = idx + reg.matchedLength();
|
||||
continue;
|
||||
}
|
||||
|
||||
QUrl srcUrl(p_baseUrl.resolved(reg.cap(2)));
|
||||
QString targetFile = WebUtils::copyResource(srcUrl, p_folder);
|
||||
if (targetFile.isEmpty()) {
|
||||
pos = idx + reg.matchedLength();
|
||||
} else {
|
||||
// Replace the url string in html.
|
||||
QString newUrl = QString("<img %1src=\"%2\"%3>").arg(reg.cap(1), getResourceRelativePath(targetFile), reg.cap(3));
|
||||
p_html.replace(idx, reg.matchedLength(), newUrl);
|
||||
pos = idx + newUrl.size();
|
||||
altered = true;
|
||||
}
|
||||
}
|
||||
|
||||
return altered;
|
||||
}
|
||||
|
||||
bool WebViewExporter::doExportPdf(const ExportPdfOption &p_pdfOption, const QString &p_outputFile)
|
||||
{
|
||||
ExportState state = ExportState::Busy;
|
||||
|
||||
m_viewer->page()->printToPdf([&, this](const QByteArray &p_result) {
|
||||
qDebug() << "doExportPdf printToPdf ready";
|
||||
if (p_result.isEmpty() || m_askedToStop) {
|
||||
state = ExportState::Failed;
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(!p_outputFile.isEmpty());
|
||||
|
||||
FileUtils::writeFile(p_outputFile, p_result);
|
||||
|
||||
state = ExportState::Finished;
|
||||
}, *p_pdfOption.m_layout);
|
||||
|
||||
while (state == ExportState::Busy) {
|
||||
Utils::sleepWait(100);
|
||||
|
||||
if (m_askedToStop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return state == ExportState::Finished;
|
||||
}
|
||||
|
||||
bool WebViewExporter::doExportWkhtmltopdf(const ExportPdfOption &p_pdfOption, const QString &p_outputFile, const QUrl &p_baseUrl)
|
||||
{
|
||||
ExportState state = ExportState::Busy;
|
||||
|
||||
connect(m_viewer->adapter(), &MarkdownViewerAdapter::contentReady,
|
||||
this, [&, this](const QString &p_headContent,
|
||||
const QString &p_styleContent,
|
||||
const QString &p_content,
|
||||
const QString &p_bodyClassList) {
|
||||
qDebug() << "doExportWkhtmltopdf contentReady";
|
||||
// Maybe unnecessary. Just to avoid duplicated signal connections.
|
||||
disconnect(m_viewer->adapter(), &MarkdownViewerAdapter::contentReady, this, 0);
|
||||
|
||||
if (p_content.isEmpty() || m_askedToStop) {
|
||||
state = ExportState::Failed;
|
||||
return;
|
||||
}
|
||||
|
||||
// Save HTML to a temp dir.
|
||||
QTemporaryDir tmpDir;
|
||||
if (!tmpDir.isValid()) {
|
||||
state = ExportState::Failed;
|
||||
return;
|
||||
}
|
||||
|
||||
auto tmpHtmlFile = tmpDir.filePath("vnote_export_tmp.html");
|
||||
if (!writeHtmlFile(tmpHtmlFile,
|
||||
p_baseUrl,
|
||||
p_headContent,
|
||||
p_styleContent,
|
||||
p_content,
|
||||
p_bodyClassList,
|
||||
true,
|
||||
true,
|
||||
false)) {
|
||||
state = ExportState::Failed;
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert HTML to PDF via wkhtmltopdf.
|
||||
if (doWkhtmltopdf(p_pdfOption, QStringList() << tmpHtmlFile, p_outputFile)) {
|
||||
state = ExportState::Finished;
|
||||
} else {
|
||||
state = ExportState::Failed;
|
||||
}
|
||||
});
|
||||
|
||||
m_viewer->adapter()->saveContent();
|
||||
|
||||
while (state == ExportState::Busy) {
|
||||
Utils::sleepWait(100);
|
||||
|
||||
if (m_askedToStop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return state == ExportState::Finished;
|
||||
}
|
||||
|
||||
bool WebViewExporter::doWkhtmltopdf(const ExportPdfOption &p_pdfOption, const QStringList &p_htmlFiles, const QString &p_outputFile)
|
||||
{
|
||||
// Note: system's locale settings (Language for non-Unicode programs) is important to wkhtmltopdf.
|
||||
// Input file could be encoded via QUrl::fromLocalFile(p_htmlFile).toString(QUrl::EncodeUnicode) to
|
||||
// handle non-ASCII path.
|
||||
|
||||
QStringList args(m_wkhtmltopdfArgs);
|
||||
|
||||
// Prepare the args.
|
||||
for (auto const &file : p_htmlFiles) {
|
||||
args << QDir::toNativeSeparators(file);
|
||||
}
|
||||
|
||||
args << QDir::toNativeSeparators(p_outputFile);
|
||||
|
||||
return startProcess(p_pdfOption.m_wkhtmltopdfExePath, args);
|
||||
}
|
||||
|
||||
bool WebViewExporter::startProcess(const QString &p_program, const QStringList &p_args)
|
||||
{
|
||||
emit logRequested(p_program + " " + ProcessUtils::combineArgString(p_args));
|
||||
|
||||
auto ret = ProcessUtils::start(p_program,
|
||||
p_args,
|
||||
[this](const QString &p_log) {
|
||||
emit logRequested(p_log);
|
||||
},
|
||||
m_askedToStop);
|
||||
return ret == ProcessUtils::State::Succeeded;
|
||||
}
|
110
src/export/webviewexporter.h
Normal file
110
src/export/webviewexporter.h
Normal file
@ -0,0 +1,110 @@
|
||||
#ifndef WEBVIEWEXPORTER_H
|
||||
#define WEBVIEWEXPORTER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "exportdata.h"
|
||||
|
||||
class QWidget;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class File;
|
||||
class MarkdownViewer;
|
||||
|
||||
class WebViewExporter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum WebViewState
|
||||
{
|
||||
Started = 0,
|
||||
LoadFinished = 0x1,
|
||||
WorkFinished = 0x2,
|
||||
Failed = 0x4
|
||||
};
|
||||
Q_DECLARE_FLAGS(WebViewStates, WebViewState);
|
||||
|
||||
// We need QWidget as parent.
|
||||
explicit WebViewExporter(QWidget *p_parent);
|
||||
|
||||
~WebViewExporter();
|
||||
|
||||
bool doExport(const ExportOption &p_option,
|
||||
const File *p_file,
|
||||
const QString &p_outputFile);
|
||||
|
||||
void prepare(const ExportOption &p_option);
|
||||
|
||||
// Release resources after one batch of export.
|
||||
void clear();
|
||||
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void logRequested(const QString &p_log);
|
||||
|
||||
private:
|
||||
enum class ExportState
|
||||
{
|
||||
Busy = 0,
|
||||
Finished,
|
||||
Failed
|
||||
};
|
||||
|
||||
bool isWebViewReady() const;
|
||||
|
||||
bool isWebViewFailed() const;
|
||||
|
||||
bool doExportHtml(const ExportHtmlOption &p_htmlOption,
|
||||
const QString &p_outputFile,
|
||||
const QUrl &p_baseUrl);
|
||||
|
||||
bool writeHtmlFile(const QString &p_file,
|
||||
const QUrl &p_baseUrl,
|
||||
const QString &p_headContent,
|
||||
QString p_styleContent,
|
||||
const QString &p_content,
|
||||
const QString &p_bodyClassList,
|
||||
bool p_embedStyles,
|
||||
bool p_completePage,
|
||||
bool p_embedImages);
|
||||
|
||||
bool embedStyleResources(QString &p_html) const;
|
||||
|
||||
bool embedBodyResources(const QUrl &p_baseUrl, QString &p_html);
|
||||
|
||||
bool fixBodyResources(const QUrl &p_baseUrl, const QString &p_folder, QString &p_html);
|
||||
|
||||
bool doExportPdf(const ExportPdfOption &p_pdfOption, const QString &p_outputFile);
|
||||
|
||||
bool doExportWkhtmltopdf(const ExportPdfOption &p_pdfOption, const QString &p_outputFile, const QUrl &p_baseUrl);
|
||||
|
||||
QSize pageLayoutSize(const QPageLayout &p_layout) const;
|
||||
|
||||
bool doWkhtmltopdf(const ExportPdfOption &p_pdfOption, const QStringList &p_htmlFiles, const QString &p_outputFile);
|
||||
|
||||
void prepareWkhtmltopdfArguments(const ExportPdfOption &p_pdfOption);
|
||||
|
||||
bool startProcess(const QString &p_program, const QStringList &p_args);
|
||||
|
||||
bool m_askedToStop = false;
|
||||
|
||||
bool m_exportOngoing = false;
|
||||
|
||||
WebViewStates m_webViewStates = WebViewState::Started;
|
||||
|
||||
// Managed by QObject.
|
||||
MarkdownViewer *m_viewer = nullptr;
|
||||
|
||||
QString m_htmlTemplate;
|
||||
|
||||
QString m_exportHtmlTemplate;
|
||||
|
||||
QStringList m_wkhtmltopdfArgs;
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::WebViewExporter::WebViewStates)
|
||||
|
||||
#endif // WEBVIEWEXPORTER_H
|
@ -42,6 +42,8 @@ include($$LIBS_FOLDER/vtextedit/src/libs/syntax-highlighting/syntax-highlighting
|
||||
|
||||
include($$PWD/utils/utils.pri)
|
||||
|
||||
include($$PWD/export/export.pri)
|
||||
|
||||
include($$PWD/core/core.pri)
|
||||
|
||||
include($$PWD/widgets/widgets.pri)
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "nodecontentmediautils.h"
|
||||
#include "contentmediautils.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QSet>
|
||||
@ -15,46 +15,53 @@
|
||||
|
||||
#include <utils/pathutils.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <core/file.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
void NodeContentMediaUtils::copyMediaFiles(const Node *p_node,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath)
|
||||
void ContentMediaUtils::copyMediaFiles(Node *p_node,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath)
|
||||
{
|
||||
Q_ASSERT(p_node->hasContent());
|
||||
/*
|
||||
const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath());
|
||||
if (fileType.m_type == FileTypeHelper::Markdown) {
|
||||
copyMarkdownMediaFiles(p_node->read(),
|
||||
PathUtils::parentDirPath(p_node->fetchContentPath()),
|
||||
auto file = p_node->getContentFile();
|
||||
if (file->getContentType().isMarkdown()) {
|
||||
copyMarkdownMediaFiles(file->read(),
|
||||
PathUtils::parentDirPath(file->getContentPath()),
|
||||
p_backend,
|
||||
p_destFilePath);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void NodeContentMediaUtils::copyMediaFiles(const QString &p_filePath,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath)
|
||||
void ContentMediaUtils::copyMediaFiles(const QString &p_filePath,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath)
|
||||
{
|
||||
/*
|
||||
const auto &fileType = FileTypeHelper::getInst().getFileType(p_filePath);
|
||||
if (fileType.m_type == FileTypeHelper::Markdown) {
|
||||
if (fileType.isMarkdown()) {
|
||||
copyMarkdownMediaFiles(FileUtils::readTextFile(p_filePath),
|
||||
PathUtils::parentDirPath(p_filePath),
|
||||
p_backend,
|
||||
p_destFilePath);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
|
||||
const QString &p_basePath,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath)
|
||||
void ContentMediaUtils::copyMediaFiles(const File *p_file,
|
||||
const QString &p_destFilePath)
|
||||
{
|
||||
if (p_file->getContentType().isMarkdown()) {
|
||||
copyMarkdownMediaFiles(p_file->read(),
|
||||
p_file->getResourcePath(),
|
||||
nullptr,
|
||||
p_destFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
|
||||
const QString &p_basePath,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath)
|
||||
{
|
||||
/*
|
||||
auto content = p_content;
|
||||
|
||||
// Images.
|
||||
@ -82,19 +89,20 @@ void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
|
||||
handledImages.insert(link.m_path);
|
||||
|
||||
if (!QFileInfo::exists(link.m_path)) {
|
||||
qWarning() << "Image of Markdown file does not exist" << link.m_path << link.m_urlInLink;
|
||||
qWarning() << "image of Markdown file does not exist" << link.m_path << link.m_urlInLink;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the relative path of the image and apply it to the dest file path.
|
||||
const auto oldDestFilePath = destDir.filePath(link.m_urlInLink);
|
||||
destDir.mkpath(PathUtils::parentDirPath(oldDestFilePath));
|
||||
auto destFilePath = p_backend->renameIfExistsCaseInsensitive(oldDestFilePath);
|
||||
auto destFilePath = p_backend ? p_backend->renameIfExistsCaseInsensitive(oldDestFilePath)
|
||||
: FileUtils::renameIfExistsCaseInsensitive(oldDestFilePath);
|
||||
if (oldDestFilePath != destFilePath) {
|
||||
// Rename happens.
|
||||
const auto oldFileName = PathUtils::fileName(oldDestFilePath);
|
||||
const auto newFileName = PathUtils::fileName(destFilePath);
|
||||
qWarning() << QString("Image name conflicts when copy. Renamed from (%1) to (%2)").arg(oldFileName, newFileName);
|
||||
qWarning() << QString("image name conflicts when copy. Renamed from (%1) to (%2)").arg(oldFileName, newFileName);
|
||||
|
||||
// Update the text content.
|
||||
auto newUrlInLink(link.m_urlInLink);
|
||||
@ -106,38 +114,41 @@ void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
|
||||
renamedImages.insert(link.m_path, newUrlInLink);
|
||||
}
|
||||
|
||||
p_backend->copyFile(link.m_path, destFilePath);
|
||||
if (p_backend) {
|
||||
p_backend->copyFile(link.m_path, destFilePath);
|
||||
} else {
|
||||
FileUtils::copyFile(link.m_path, destFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!renamedImages.isEmpty()) {
|
||||
p_backend->writeFile(p_destFilePath, content);
|
||||
if (p_backend) {
|
||||
p_backend->writeFile(p_destFilePath, content);
|
||||
} else {
|
||||
FileUtils::writeFile(p_destFilePath, content);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void NodeContentMediaUtils::removeMediaFiles(const Node *p_node)
|
||||
void ContentMediaUtils::removeMediaFiles(Node *p_node)
|
||||
{
|
||||
/*
|
||||
Q_ASSERT(p_node->getType() == Node::Type::File);
|
||||
const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath());
|
||||
if (fileType.m_type == FileTypeHelper::Markdown) {
|
||||
removeMarkdownMediaFiles(p_node);
|
||||
Q_ASSERT(p_node->hasContent());
|
||||
auto file = p_node->getContentFile();
|
||||
if (file->getContentType().isMarkdown()) {
|
||||
removeMarkdownMediaFiles(file.data(), p_node->getBackend());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void NodeContentMediaUtils::removeMarkdownMediaFiles(const Node *p_node)
|
||||
void ContentMediaUtils::removeMarkdownMediaFiles(const File *p_file, INotebookBackend *p_backend)
|
||||
{
|
||||
/*
|
||||
auto content = p_node->read();
|
||||
auto content = p_file->read();
|
||||
|
||||
// Images.
|
||||
const auto images =
|
||||
vte::MarkdownUtils::fetchImagesFromMarkdownText(content,
|
||||
PathUtils::parentDirPath(p_node->fetchContentPath()),
|
||||
p_file->getResourcePath(),
|
||||
vte::MarkdownLink::TypeFlag::LocalRelativeInternal);
|
||||
|
||||
auto backend = p_node->getBackend();
|
||||
QSet<QString> handledImages;
|
||||
for (const auto &link : images) {
|
||||
if (handledImages.contains(link.m_path)) {
|
||||
@ -150,40 +161,42 @@ void NodeContentMediaUtils::removeMarkdownMediaFiles(const Node *p_node)
|
||||
qWarning() << "Image of Markdown file does not exist" << link.m_path << link.m_urlInLink;
|
||||
continue;
|
||||
}
|
||||
backend->removeFile(link.m_path);
|
||||
p_backend->removeFile(link.m_path);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void NodeContentMediaUtils::copyAttachment(Node *p_node,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath,
|
||||
const QString &p_destAttachmentFolderPath)
|
||||
void ContentMediaUtils::copyAttachment(Node *p_node,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath,
|
||||
const QString &p_destAttachmentFolderPath)
|
||||
{
|
||||
/*
|
||||
Q_ASSERT(p_node->getType() == Node::Type::File);
|
||||
Q_ASSERT(p_node->hasContent());
|
||||
Q_ASSERT(!p_node->getAttachmentFolder().isEmpty());
|
||||
|
||||
// Copy the whole folder.
|
||||
const auto srcAttachmentFolderPath = p_node->fetchAttachmentFolderPath();
|
||||
p_backend->copyDir(srcAttachmentFolderPath, p_destAttachmentFolderPath);
|
||||
if (p_backend) {
|
||||
p_backend->copyDir(srcAttachmentFolderPath, p_destAttachmentFolderPath);
|
||||
} else {
|
||||
FileUtils::copyDir(srcAttachmentFolderPath, p_destAttachmentFolderPath);
|
||||
}
|
||||
|
||||
// Check if we need to modify links in content.
|
||||
// FIXME: check the whole relative path.
|
||||
if (p_node->getAttachmentFolder() == PathUtils::dirName(p_destAttachmentFolderPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath());
|
||||
if (fileType.m_type == FileTypeHelper::Markdown) {
|
||||
auto file = p_node->getContentFile();
|
||||
if (file->getContentType().isMarkdown()) {
|
||||
fixMarkdownLinks(srcAttachmentFolderPath, p_backend, p_destFilePath, p_destAttachmentFolderPath);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void NodeContentMediaUtils::fixMarkdownLinks(const QString &p_srcFolderPath,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath,
|
||||
const QString &p_destFolderPath)
|
||||
void ContentMediaUtils::fixMarkdownLinks(const QString &p_srcFolderPath,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath,
|
||||
const QString &p_destFolderPath)
|
||||
{
|
||||
// TODO.
|
||||
Q_UNUSED(p_srcFolderPath);
|
@ -1,5 +1,5 @@
|
||||
#ifndef NODECONTENTMEDIAUTILS_H
|
||||
#define NODECONTENTMEDIAUTILS_H
|
||||
#ifndef CONTENTMEDIAUTILS_H
|
||||
#define CONTENTMEDIAUTILS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
@ -7,16 +7,17 @@ namespace vnotex
|
||||
{
|
||||
class INotebookBackend;
|
||||
class Node;
|
||||
class File;
|
||||
|
||||
// Utils to operate on the media files from node's content.
|
||||
class NodeContentMediaUtils
|
||||
class ContentMediaUtils
|
||||
{
|
||||
public:
|
||||
NodeContentMediaUtils() = delete;
|
||||
ContentMediaUtils() = delete;
|
||||
|
||||
// Fetch media files from @p_node and copy them to dest folder.
|
||||
// @p_destFilePath: @p_node has been copied to @p_destFilePath.
|
||||
static void copyMediaFiles(const Node *p_node,
|
||||
static void copyMediaFiles(Node *p_node,
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath);
|
||||
|
||||
@ -25,7 +26,10 @@ namespace vnotex
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath);
|
||||
|
||||
static void removeMediaFiles(const Node *p_node);
|
||||
static void copyMediaFiles(const File *p_file,
|
||||
const QString &p_destFilePath);
|
||||
|
||||
static void removeMediaFiles(Node *p_node);
|
||||
|
||||
// Copy attachment folder.
|
||||
static void copyAttachment(Node *p_node,
|
||||
@ -39,7 +43,7 @@ namespace vnotex
|
||||
INotebookBackend *p_backend,
|
||||
const QString &p_destFilePath);
|
||||
|
||||
static void removeMarkdownMediaFiles(const Node *p_node);
|
||||
static void removeMarkdownMediaFiles(const File *p_file, INotebookBackend *p_backend);
|
||||
|
||||
// Fix local relative internal links locating in @p_srcFolderPath.
|
||||
static void fixMarkdownLinks(const QString &p_srcFolderPath,
|
||||
@ -49,4 +53,4 @@ namespace vnotex
|
||||
};
|
||||
}
|
||||
|
||||
#endif // NODECONTENTMEDIAUTILS_H
|
||||
#endif // CONTENTMEDIAUTILS_H
|
@ -63,7 +63,7 @@ namespace vnotex
|
||||
|
||||
static QString generateFileNameWithSequence(const QString &p_folderPath,
|
||||
const QString &p_baseName,
|
||||
const QString &p_suffix);
|
||||
const QString &p_suffix = QString());
|
||||
|
||||
static QTemporaryFile *createTemporaryFile(const QString &p_suffix);
|
||||
|
||||
|
@ -10,3 +10,9 @@ bool HtmlUtils::hasOnlyImgTag(const QString &p_html)
|
||||
QRegExp reg(QStringLiteral("<(?:p|span|div) "));
|
||||
return !p_html.contains(reg);
|
||||
}
|
||||
|
||||
QString HtmlUtils::escapeHtml(QString p_text)
|
||||
{
|
||||
p_text.replace(">", ">").replace("<", "<").replace("&", "&");
|
||||
return p_text;
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ namespace vnotex
|
||||
HtmlUtils() = delete;
|
||||
|
||||
static bool hasOnlyImgTag(const QString &p_html);
|
||||
|
||||
static QString escapeHtml(QString p_text);
|
||||
};
|
||||
}
|
||||
|
||||
|
158
src/utils/processutils.cpp
Normal file
158
src/utils/processutils.cpp
Normal file
@ -0,0 +1,158 @@
|
||||
#include "processutils.h"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QScopedPointer>
|
||||
#include <QDebug>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
ProcessUtils::State ProcessUtils::start(const QString &p_program,
|
||||
const QStringList &p_args,
|
||||
const QByteArray &p_stdIn,
|
||||
int &p_exitCodeOnSuccess,
|
||||
QByteArray &p_stdOut,
|
||||
QByteArray &p_stdErr)
|
||||
{
|
||||
QScopedPointer<QProcess> proc(new QProcess());
|
||||
proc->start(p_program, p_args);
|
||||
return handleProcess(proc.data(), p_stdIn, p_exitCodeOnSuccess, p_stdOut, p_stdErr);
|
||||
}
|
||||
|
||||
ProcessUtils::State ProcessUtils::handleProcess(QProcess *p_process,
|
||||
const QByteArray &p_stdIn,
|
||||
int &p_exitCodeOnSuccess,
|
||||
QByteArray &p_stdOut,
|
||||
QByteArray &p_stdErr)
|
||||
{
|
||||
if (!p_process->waitForStarted()) {
|
||||
return State::FailedToStart;
|
||||
}
|
||||
|
||||
if (!p_stdIn.isEmpty()) {
|
||||
if (p_process->write(p_stdIn) == -1) {
|
||||
p_process->closeWriteChannel();
|
||||
qWarning() << "failed to write to stdin of QProcess" << p_process->errorString();
|
||||
return State::FailedToWrite;
|
||||
} else {
|
||||
p_process->closeWriteChannel();
|
||||
}
|
||||
}
|
||||
|
||||
p_process->waitForFinished();
|
||||
|
||||
State state = State::Succeeded;
|
||||
if (p_process->exitStatus() == QProcess::CrashExit) {
|
||||
state = State::Crashed;
|
||||
} else {
|
||||
p_exitCodeOnSuccess = p_process->exitCode();
|
||||
}
|
||||
|
||||
p_stdOut = p_process->readAllStandardOutput();
|
||||
p_stdErr = p_process->readAllStandardError();
|
||||
return state;
|
||||
}
|
||||
|
||||
QStringList ProcessUtils::parseCombinedArgString(const QString &p_args)
|
||||
{
|
||||
QStringList args;
|
||||
QString tmp;
|
||||
int quoteCount = 0;
|
||||
bool inQuote = false;
|
||||
|
||||
// Handle quoting.
|
||||
// Tokens can be surrounded by double quotes "hello world".
|
||||
// Three consecutive double quotes represent the quote character itself.
|
||||
for (int i = 0; i < p_args.size(); ++i) {
|
||||
if (p_args.at(i) == QLatin1Char('"')) {
|
||||
++quoteCount;
|
||||
if (quoteCount == 3) {
|
||||
// Third consecutive quote.
|
||||
quoteCount = 0;
|
||||
tmp += p_args.at(i);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (quoteCount) {
|
||||
if (quoteCount == 1) {
|
||||
inQuote = !inQuote;
|
||||
}
|
||||
|
||||
quoteCount = 0;
|
||||
}
|
||||
|
||||
if (!inQuote && p_args.at(i).isSpace()) {
|
||||
if (!tmp.isEmpty()) {
|
||||
args += tmp;
|
||||
tmp.clear();
|
||||
}
|
||||
} else {
|
||||
tmp += p_args.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!tmp.isEmpty()) {
|
||||
args += tmp;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
QString ProcessUtils::combineArgString(const QStringList &p_args)
|
||||
{
|
||||
QString argStr;
|
||||
for (const auto &arg : p_args) {
|
||||
QString tmp(arg);
|
||||
tmp.replace("\"", "\"\"\"");
|
||||
if (tmp.contains(' ')) {
|
||||
tmp = '"' + tmp + '"';
|
||||
}
|
||||
|
||||
if (argStr.isEmpty()) {
|
||||
argStr = tmp;
|
||||
} else {
|
||||
argStr = argStr + ' ' + tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return argStr;
|
||||
}
|
||||
|
||||
ProcessUtils::State ProcessUtils::start(const QString &p_program,
|
||||
const QStringList &p_args,
|
||||
const std::function<void(const QString &)> &p_logger,
|
||||
const bool &p_askedToStop)
|
||||
{
|
||||
QProcess proc;
|
||||
proc.start(p_program, p_args);
|
||||
|
||||
if (!proc.waitForStarted()) {
|
||||
return State::FailedToStart;
|
||||
}
|
||||
|
||||
while (proc.state() != QProcess::NotRunning) {
|
||||
Utils::sleepWait(100);
|
||||
|
||||
auto outBa = proc.readAllStandardOutput();
|
||||
auto errBa = proc.readAllStandardError();
|
||||
QString msg;
|
||||
if (!outBa.isEmpty()) {
|
||||
msg += QString::fromLocal8Bit(outBa);
|
||||
}
|
||||
if (!errBa.isEmpty()) {
|
||||
msg += QString::fromLocal8Bit(errBa);
|
||||
}
|
||||
if (!msg.isEmpty()) {
|
||||
p_logger(msg);
|
||||
}
|
||||
|
||||
if (p_askedToStop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return proc.exitStatus() == QProcess::NormalExit ? State::Succeeded : State::Crashed;
|
||||
}
|
52
src/utils/processutils.h
Normal file
52
src/utils/processutils.h
Normal file
@ -0,0 +1,52 @@
|
||||
#ifndef PROCESSUTILS_H
|
||||
#define PROCESSUTILS_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QStringList>
|
||||
#include <QByteArray>
|
||||
|
||||
class QProcess;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class ProcessUtils
|
||||
{
|
||||
public:
|
||||
enum State
|
||||
{
|
||||
Succeeded,
|
||||
Crashed,
|
||||
FailedToStart,
|
||||
FailedToWrite
|
||||
};
|
||||
|
||||
ProcessUtils() = delete;
|
||||
|
||||
static State start(const QString &p_program,
|
||||
const QStringList &p_args,
|
||||
const QByteArray &p_stdIn,
|
||||
int &p_exitCodeOnSuccess,
|
||||
QByteArray &p_stdOut,
|
||||
QByteArray &p_stdErr);
|
||||
|
||||
static State start(const QString &p_program,
|
||||
const QStringList &p_args,
|
||||
const std::function<void(const QString &)> &p_logger,
|
||||
const bool &p_askedToStop);
|
||||
|
||||
// Copied from QProcess code.
|
||||
static QStringList parseCombinedArgString(const QString &p_args);
|
||||
|
||||
static QString combineArgString(const QStringList &p_args);
|
||||
|
||||
private:
|
||||
static State handleProcess(QProcess *p_process,
|
||||
const QByteArray &p_stdIn,
|
||||
int &p_exitCodeOnSuccess,
|
||||
QByteArray &p_stdOut,
|
||||
QByteArray &p_stdErr);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PROCESSUTILS_H
|
@ -75,13 +75,3 @@ QString TextUtils::unindentTextMultiLines(const QString &p_text)
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
QString TextUtils::purifyUrl(const QString &p_url)
|
||||
{
|
||||
int idx = p_url.indexOf('?');
|
||||
if (idx > -1) {
|
||||
return p_url.left(idx);
|
||||
}
|
||||
|
||||
return p_url;
|
||||
}
|
||||
|
@ -20,9 +20,6 @@ namespace vnotex
|
||||
|
||||
// Unindent multi-lines text according to the indentation of the first line.
|
||||
static QString unindentTextMultiLines(const QString &p_text);
|
||||
|
||||
// Remove query in the url (?xxx).
|
||||
static QString purifyUrl(const QString &p_url);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,25 +1,31 @@
|
||||
QT += widgets svg
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/contentmediautils.cpp \
|
||||
$$PWD/docsutils.cpp \
|
||||
$$PWD/htmlutils.cpp \
|
||||
$$PWD/pathutils.cpp \
|
||||
$$PWD/processutils.cpp \
|
||||
$$PWD/textutils.cpp \
|
||||
$$PWD/urldragdroputils.cpp \
|
||||
$$PWD/utils.cpp \
|
||||
$$PWD/fileutils.cpp \
|
||||
$$PWD/iconutils.cpp \
|
||||
$$PWD/webutils.cpp \
|
||||
$$PWD/widgetutils.cpp \
|
||||
$$PWD/clipboardutils.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/contentmediautils.h \
|
||||
$$PWD/docsutils.h \
|
||||
$$PWD/htmlutils.h \
|
||||
$$PWD/pathutils.h \
|
||||
$$PWD/processutils.h \
|
||||
$$PWD/textutils.h \
|
||||
$$PWD/urldragdroputils.h \
|
||||
$$PWD/utils.h \
|
||||
$$PWD/fileutils.h \
|
||||
$$PWD/iconutils.h \
|
||||
$$PWD/webutils.h \
|
||||
$$PWD/widgetutils.h \
|
||||
$$PWD/clipboardutils.h
|
||||
|
103
src/utils/webutils.cpp
Normal file
103
src/utils/webutils.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include "webutils.h"
|
||||
|
||||
#include <QUrl>
|
||||
#include <QFileInfo>
|
||||
#include <QImageReader>
|
||||
|
||||
#include "fileutils.h"
|
||||
#include "pathutils.h"
|
||||
#include <vtextedit/networkutils.h>
|
||||
#include <core/exception.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
QString WebUtils::purifyUrl(const QString &p_url)
|
||||
{
|
||||
int idx = p_url.indexOf('?');
|
||||
if (idx > -1) {
|
||||
return p_url.left(idx);
|
||||
}
|
||||
|
||||
return p_url;
|
||||
}
|
||||
|
||||
QString WebUtils::toDataUri(const QUrl &p_url, bool p_keepTitle)
|
||||
{
|
||||
QString uri;
|
||||
Q_ASSERT(!p_url.isRelative());
|
||||
QString file = p_url.isLocalFile() ? p_url.toLocalFile() : p_url.toString();
|
||||
const auto filePath = purifyUrl(file);
|
||||
const QFileInfo finfo(filePath);
|
||||
const QString suffix(finfo.suffix().toLower());
|
||||
if (!QImageReader::supportedImageFormats().contains(suffix.toLatin1())) {
|
||||
return uri;
|
||||
}
|
||||
|
||||
QByteArray data;
|
||||
if (p_url.scheme() == "https" || p_url.scheme() == "http") {
|
||||
// Download it.
|
||||
data = vte::Downloader::download(p_url);
|
||||
} else if (finfo.exists()) {
|
||||
data = FileUtils::readFile(filePath);
|
||||
}
|
||||
|
||||
if (data.isEmpty()) {
|
||||
return uri;
|
||||
}
|
||||
|
||||
if (suffix == "svg") {
|
||||
uri = QString("data:image/svg+xml;utf8,%1").arg(QString::fromUtf8(data));
|
||||
uri.replace('\r', "").replace('\n', "");
|
||||
|
||||
// Using unescaped '#' characters in a data URI body is deprecated and
|
||||
// will be removed in M68, around July 2018. Please use '%23' instead.
|
||||
uri.replace("#", "%23");
|
||||
|
||||
// Escape "'" to avoid conflict with src='...' attribute.
|
||||
uri.replace("'", "%27");
|
||||
|
||||
if (!p_keepTitle) {
|
||||
// Remove <title>...</title>.
|
||||
QRegExp reg("<title>.*</title>", Qt::CaseInsensitive);
|
||||
uri.remove(reg);
|
||||
}
|
||||
} else {
|
||||
uri = QString("data:image/%1;base64,%2").arg(suffix, QString::fromUtf8(data.toBase64()));
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
QString WebUtils::copyResource(const QUrl &p_url, const QString &p_folder)
|
||||
{
|
||||
Q_ASSERT(!p_url.isRelative());
|
||||
|
||||
QDir dir(p_folder);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(p_folder);
|
||||
}
|
||||
|
||||
QString file = p_url.isLocalFile() ? p_url.toLocalFile() : p_url.toString();
|
||||
QFileInfo finfo(file);
|
||||
auto fileName = FileUtils::generateFileNameWithSequence(p_folder, finfo.completeBaseName(), finfo.suffix());
|
||||
QString targetFile = dir.absoluteFilePath(fileName);
|
||||
|
||||
bool succ = true;
|
||||
try {
|
||||
if (p_url.scheme() == "https" || p_url.scheme() == "http") {
|
||||
// Download it.
|
||||
auto data = vte::Downloader::download(p_url);
|
||||
if (!data.isEmpty()) {
|
||||
FileUtils::writeFile(targetFile, data);
|
||||
}
|
||||
} else if (finfo.exists()) {
|
||||
// Do a copy.
|
||||
FileUtils::copyFile(file, targetFile, false);
|
||||
}
|
||||
} catch (Exception &p_e) {
|
||||
Q_UNUSED(p_e);
|
||||
succ = false;
|
||||
}
|
||||
|
||||
return succ ? targetFile : QString();
|
||||
}
|
24
src/utils/webutils.h
Normal file
24
src/utils/webutils.h
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef WEBUTILS_H
|
||||
#define WEBUTILS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class QUrl;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class WebUtils
|
||||
{
|
||||
public:
|
||||
WebUtils() = delete;
|
||||
|
||||
// Remove query in the url (?xxx).
|
||||
static QString purifyUrl(const QString &p_url);
|
||||
|
||||
static QString toDataUri(const QUrl &p_url, bool p_keepTitle);
|
||||
|
||||
static QString copyResource(const QUrl &p_url, const QString &p_folder);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // WEBUTILS_H
|
@ -21,7 +21,6 @@
|
||||
#include <QFontDatabase>
|
||||
#include <QMenu>
|
||||
#include <QDebug>
|
||||
#include <QFormLayout>
|
||||
#include <QLineEdit>
|
||||
|
||||
using namespace vnotex;
|
||||
@ -356,18 +355,6 @@ void WidgetUtils::insertActionAfter(QMenu *p_menu, QAction *p_after, QAction *p_
|
||||
}
|
||||
}
|
||||
|
||||
QFormLayout *WidgetUtils::createFormLayout(QWidget *p_parent)
|
||||
{
|
||||
auto layout = new QFormLayout(p_parent);
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
layout->setFormAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
#endif
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
void WidgetUtils::selectBaseName(QLineEdit *p_lineEdit)
|
||||
{
|
||||
auto text = p_lineEdit->text();
|
||||
|
@ -17,7 +17,6 @@ class QScrollArea;
|
||||
class QListView;
|
||||
class QMenu;
|
||||
class QShortcut;
|
||||
class QFormLayout;
|
||||
class QLineEdit;
|
||||
|
||||
namespace vnotex
|
||||
@ -79,8 +78,6 @@ namespace vnotex
|
||||
|
||||
static void insertActionAfter(QMenu *p_menu, QAction *p_after, QAction *p_action);
|
||||
|
||||
static QFormLayout *createFormLayout(QWidget *p_parent = nullptr);
|
||||
|
||||
// Select the base name part of the line edit content.
|
||||
static void selectBaseName(QLineEdit *p_lineEdit);
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include <utils/widgetutils.h>
|
||||
#include "../propertydefs.h"
|
||||
#include "../widgetsfactory.h"
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
@ -29,6 +30,11 @@ void Dialog::setCentralWidget(QWidget *p_widget)
|
||||
m_layout->addWidget(m_centralWidget);
|
||||
}
|
||||
|
||||
void Dialog::addBottomWidget(QWidget *p_widget)
|
||||
{
|
||||
m_layout->insertWidget(m_layout->indexOf(m_centralWidget) + 1, p_widget);
|
||||
}
|
||||
|
||||
void Dialog::setDialogButtonBox(QDialogButtonBox::StandardButtons p_buttons,
|
||||
QDialogButtonBox::StandardButton p_defaultButton)
|
||||
{
|
||||
@ -38,7 +44,8 @@ void Dialog::setDialogButtonBox(QDialogButtonBox::StandardButtons p_buttons,
|
||||
m_dialogButtonBox = new QDialogButtonBox(p_buttons, this);
|
||||
connect(m_dialogButtonBox, &QDialogButtonBox::accepted,
|
||||
this, &Dialog::acceptedButtonClicked);
|
||||
connect(m_dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(m_dialogButtonBox, &QDialogButtonBox::rejected,
|
||||
this, &Dialog::rejectedButtonClicked);
|
||||
connect(m_dialogButtonBox, &QDialogButtonBox::clicked,
|
||||
this, [this](QAbstractButton *p_button) {
|
||||
switch (m_dialogButtonBox->buttonRole(p_button)) {
|
||||
@ -80,13 +87,13 @@ void Dialog::setInformationText(const QString &p_text, InformationLevel p_level)
|
||||
return;
|
||||
}
|
||||
|
||||
m_infoTextEdit = new QPlainTextEdit(this);
|
||||
m_infoTextEdit->setReadOnly(true);
|
||||
m_infoTextEdit = WidgetsFactory::createPlainTextConsole(this);
|
||||
m_infoTextEdit->setMaximumHeight(m_infoTextEdit->minimumSizeHint().height());
|
||||
m_layout->insertWidget(1, m_infoTextEdit);
|
||||
m_layout->insertWidget(m_layout->count() - 1, m_infoTextEdit);
|
||||
}
|
||||
|
||||
m_infoTextEdit->setPlainText(p_text);
|
||||
m_infoTextEdit->ensureCursorVisible();
|
||||
|
||||
const bool visible = !p_text.isEmpty();
|
||||
const bool needResize = visible != m_infoTextEdit->isVisible();
|
||||
@ -114,11 +121,34 @@ void Dialog::setInformationText(const QString &p_text, InformationLevel p_level)
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::appendInformationText(const QString &p_text)
|
||||
{
|
||||
if (!m_infoTextEdit) {
|
||||
setInformationText(p_text);
|
||||
} else {
|
||||
m_infoTextEdit->appendPlainText(p_text);
|
||||
m_infoTextEdit->moveCursor(QTextCursor::End);
|
||||
m_infoTextEdit->ensureCursorVisible();
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::clearInformationText()
|
||||
{
|
||||
if (m_infoTextEdit) {
|
||||
m_infoTextEdit->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::acceptedButtonClicked()
|
||||
{
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void Dialog::rejectedButtonClicked()
|
||||
{
|
||||
QDialog::reject();
|
||||
}
|
||||
|
||||
void Dialog::resetButtonClicked()
|
||||
{
|
||||
}
|
||||
|
@ -15,8 +15,6 @@ namespace vnotex
|
||||
public:
|
||||
explicit Dialog(QWidget *p_parent = nullptr, Qt::WindowFlags p_flags = Qt::WindowFlags());
|
||||
|
||||
virtual void setCentralWidget(QWidget *p_widget);
|
||||
|
||||
void setDialogButtonBox(QDialogButtonBox::StandardButtons p_buttons,
|
||||
QDialogButtonBox::StandardButton p_defaultButton = QDialogButtonBox::NoButton);
|
||||
|
||||
@ -31,6 +29,10 @@ namespace vnotex
|
||||
|
||||
void setInformationText(const QString &p_text, InformationLevel p_level = InformationLevel::Info);
|
||||
|
||||
void appendInformationText(const QString &p_text);
|
||||
|
||||
void clearInformationText();
|
||||
|
||||
void setButtonEnabled(QDialogButtonBox::StandardButton p_button, bool p_enabled);
|
||||
|
||||
// Dialog has completed but just stay the GUI to let user know information.
|
||||
@ -41,10 +43,17 @@ namespace vnotex
|
||||
protected:
|
||||
virtual void acceptedButtonClicked();
|
||||
|
||||
virtual void rejectedButtonClicked();
|
||||
|
||||
virtual void resetButtonClicked();
|
||||
|
||||
virtual void appliedButtonClicked();
|
||||
|
||||
virtual void setCentralWidget(QWidget *p_widget);
|
||||
|
||||
// Add @p_widget below the central widget.
|
||||
virtual void addBottomWidget(QWidget *p_widget);
|
||||
|
||||
QBoxLayout *m_layout = nullptr;
|
||||
|
||||
QWidget *m_centralWidget = nullptr;
|
||||
|
679
src/widgets/dialogs/exportdialog.cpp
Normal file
679
src/widgets/dialogs/exportdialog.cpp
Normal file
@ -0,0 +1,679 @@
|
||||
#include "exportdialog.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QFormLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
#include <QLineEdit>
|
||||
#include <QProgressBar>
|
||||
#include <QFileInfo>
|
||||
#include <QFileDialog>
|
||||
#include <QUrl>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QCoreApplication>
|
||||
#include <QPrinter>
|
||||
#include <QPageSetupDialog>
|
||||
#include <QPageLayout>
|
||||
|
||||
#include <notebook/notebook.h>
|
||||
#include <notebook/node.h>
|
||||
#include <buffer/buffer.h>
|
||||
#include <widgets/widgetsfactory.h>
|
||||
#include <core/thememgr.h>
|
||||
#include <core/configmgr.h>
|
||||
#include <core/sessionconfig.h>
|
||||
#include <core/vnotex.h>
|
||||
#include <utils/widgetutils.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/pathutils.h>
|
||||
#include <utils/clipboardutils.h>
|
||||
#include <export/exporter.h>
|
||||
|
||||
using namespace vnotex;
|
||||
|
||||
ExportDialog::ExportDialog(Notebook *p_notebook,
|
||||
Node *p_folder,
|
||||
Buffer *p_buffer,
|
||||
QWidget *p_parent)
|
||||
: ScrollDialog(p_parent),
|
||||
m_notebook(p_notebook),
|
||||
m_folder(p_folder),
|
||||
m_buffer(p_buffer)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
initOptions();
|
||||
|
||||
restoreFields(m_option);
|
||||
|
||||
connect(this, &QDialog::finished,
|
||||
this, [this]() {
|
||||
saveFields(m_option);
|
||||
ConfigMgr::getInst().getSessionConfig().setExportOption(m_option);
|
||||
});
|
||||
}
|
||||
|
||||
void ExportDialog::setupUI()
|
||||
{
|
||||
auto widget = new QWidget(this);
|
||||
setCentralWidget(widget);
|
||||
|
||||
auto mainLayout = new QVBoxLayout(widget);
|
||||
|
||||
auto sourceBox = setupSourceGroup(widget);
|
||||
mainLayout->addWidget(sourceBox);
|
||||
|
||||
auto targetBox = setupTargetGroup(widget);
|
||||
mainLayout->addWidget(targetBox);
|
||||
|
||||
m_advancedGroupBox = setupAdvancedGroup(widget);
|
||||
mainLayout->addWidget(m_advancedGroupBox);
|
||||
|
||||
m_progressBar = new QProgressBar(widget);
|
||||
m_progressBar->setRange(0, 0);
|
||||
m_progressBar->hide();
|
||||
addBottomWidget(m_progressBar);
|
||||
|
||||
setupButtonBox();
|
||||
|
||||
setWindowTitle(tr("Export"));
|
||||
}
|
||||
|
||||
QGroupBox *ExportDialog::setupSourceGroup(QWidget *p_parent)
|
||||
{
|
||||
auto box = new QGroupBox(tr("Source"), p_parent);
|
||||
auto layout = WidgetsFactory::createFormLayout(box);
|
||||
|
||||
{
|
||||
m_sourceComboBox = WidgetsFactory::createComboBox(box);
|
||||
if (m_buffer) {
|
||||
m_sourceComboBox->addItem(tr("Current Buffer (%1)").arg(m_buffer->getName()),
|
||||
static_cast<int>(ExportSource::CurrentBuffer));
|
||||
}
|
||||
if (m_folder && m_folder->isContainer()) {
|
||||
m_sourceComboBox->addItem(tr("Current Folder (%1)").arg(m_folder->getName()),
|
||||
static_cast<int>(ExportSource::CurrentFolder));
|
||||
}
|
||||
if (m_notebook) {
|
||||
m_sourceComboBox->addItem(tr("Current Notebook (%1)").arg(m_notebook->getName()),
|
||||
static_cast<int>(ExportSource::CurrentNotebook));
|
||||
}
|
||||
layout->addRow(tr("Source:"), m_sourceComboBox);
|
||||
}
|
||||
|
||||
{
|
||||
// TODO: Source format filtering.
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
QString ExportDialog::getDefaultOutputDir() const
|
||||
{
|
||||
return PathUtils::concatenateFilePath(ConfigMgr::getDocumentOrHomePath(), tr("vnote_exports"));
|
||||
}
|
||||
|
||||
QGroupBox *ExportDialog::setupTargetGroup(QWidget *p_parent)
|
||||
{
|
||||
auto box = new QGroupBox(tr("Target"), p_parent);
|
||||
auto layout = WidgetsFactory::createFormLayout(box);
|
||||
|
||||
{
|
||||
m_targetFormatComboBox = WidgetsFactory::createComboBox(box);
|
||||
m_targetFormatComboBox->addItem(tr("Markdown"),
|
||||
static_cast<int>(ExportFormat::Markdown));
|
||||
m_targetFormatComboBox->addItem(tr("HTML"),
|
||||
static_cast<int>(ExportFormat::HTML));
|
||||
m_targetFormatComboBox->addItem(tr("PDF"),
|
||||
static_cast<int>(ExportFormat::PDF));
|
||||
m_targetFormatComboBox->addItem(tr("Custom"),
|
||||
static_cast<int>(ExportFormat::Custom));
|
||||
connect(m_targetFormatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, [this]() {
|
||||
AdvancedSettings settings = AdvancedSettings::Max;
|
||||
int format = m_targetFormatComboBox->currentData().toInt();
|
||||
switch (format) {
|
||||
case ExportFormat::HTML:
|
||||
settings = AdvancedSettings::HTML;
|
||||
break;
|
||||
|
||||
case ExportFormat::PDF:
|
||||
settings = AdvancedSettings::PDF;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
showAdvancedSettings(settings);
|
||||
});
|
||||
layout->addRow(tr("Format:"), m_targetFormatComboBox);
|
||||
}
|
||||
|
||||
{
|
||||
m_transparentBgCheckBox = WidgetsFactory::createCheckBox(tr("Use transparent background"), box);
|
||||
layout->addRow(m_transparentBgCheckBox);
|
||||
}
|
||||
|
||||
{
|
||||
const auto webStyles = VNoteX::getInst().getThemeMgr().getWebStyles();
|
||||
|
||||
m_renderingStyleComboBox = WidgetsFactory::createComboBox(box);
|
||||
layout->addRow(tr("Rendering style:"), m_renderingStyleComboBox);
|
||||
for (const auto &pa : webStyles) {
|
||||
m_renderingStyleComboBox->addItem(pa.first, pa.second);
|
||||
}
|
||||
|
||||
m_syntaxHighlightStyleComboBox = WidgetsFactory::createComboBox(box);
|
||||
layout->addRow(tr("Syntax highlighting style:"), m_syntaxHighlightStyleComboBox);
|
||||
for (const auto &pa : webStyles) {
|
||||
m_syntaxHighlightStyleComboBox->addItem(pa.first, pa.second);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto outputLayout = new QHBoxLayout();
|
||||
|
||||
m_outputDirLineEdit = WidgetsFactory::createLineEdit(box);
|
||||
outputLayout->addWidget(m_outputDirLineEdit);
|
||||
|
||||
auto browseBtn = new QPushButton(tr("Browse"), box);
|
||||
outputLayout->addWidget(browseBtn);
|
||||
connect(browseBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
QString initPath = getOutputDir();
|
||||
if (!QFileInfo::exists(initPath)) {
|
||||
initPath = getDefaultOutputDir();
|
||||
}
|
||||
|
||||
QString dirPath = QFileDialog::getExistingDirectory(this,
|
||||
tr("Select Export Output Directory"),
|
||||
initPath,
|
||||
QFileDialog::ShowDirsOnly
|
||||
| QFileDialog::DontResolveSymlinks);
|
||||
|
||||
if (!dirPath.isEmpty()) {
|
||||
m_outputDirLineEdit->setText(dirPath);
|
||||
}
|
||||
});
|
||||
|
||||
layout->addRow(tr("Output directory:"), outputLayout);
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
QGroupBox *ExportDialog::setupAdvancedGroup(QWidget *p_parent)
|
||||
{
|
||||
auto box = new QGroupBox(tr("Advanced"), p_parent);
|
||||
auto layout = new QVBoxLayout(box);
|
||||
|
||||
m_advancedSettings.resize(AdvancedSettings::Max);
|
||||
|
||||
m_advancedSettings[AdvancedSettings::General] = setupGeneralAdvancedSettings(box);
|
||||
layout->addWidget(m_advancedSettings[AdvancedSettings::General]);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
QWidget *ExportDialog::setupGeneralAdvancedSettings(QWidget *p_parent)
|
||||
{
|
||||
QWidget *widget = new QWidget(p_parent);
|
||||
auto layout = WidgetsFactory::createFormLayout(widget);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
{
|
||||
m_recursiveCheckBox = WidgetsFactory::createCheckBox(tr("Process sub-folders"), widget);
|
||||
layout->addRow(m_recursiveCheckBox);
|
||||
}
|
||||
|
||||
{
|
||||
m_exportAttachmentsCheckBox = WidgetsFactory::createCheckBox(tr("Export attachments"), widget);
|
||||
layout->addRow(m_exportAttachmentsCheckBox);
|
||||
}
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
void ExportDialog::setupButtonBox()
|
||||
{
|
||||
setDialogButtonBox(QDialogButtonBox::Close);
|
||||
|
||||
auto box = getDialogButtonBox();
|
||||
|
||||
m_exportBtn = box->addButton(tr("Export"), QDialogButtonBox::ActionRole);
|
||||
connect(m_exportBtn, &QPushButton::clicked,
|
||||
this, &ExportDialog::startExport);
|
||||
|
||||
m_openDirBtn = box->addButton(tr("Open Directory"), QDialogButtonBox::ActionRole);
|
||||
connect(m_openDirBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
auto dir = getOutputDir();
|
||||
if (!dir.isEmpty()) {
|
||||
WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(dir));
|
||||
}
|
||||
});
|
||||
|
||||
m_copyContentBtn = box->addButton(tr("Copy Content"), QDialogButtonBox::ActionRole);
|
||||
m_copyContentBtn->setToolTip(tr("Copy exported file content"));
|
||||
m_copyContentBtn->setEnabled(false);
|
||||
connect(m_copyContentBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
if (m_exportedFile.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto content = FileUtils::readTextFile(m_exportedFile);
|
||||
if (!content.isEmpty()) {
|
||||
ClipboardUtils::setTextToClipboard(content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QString ExportDialog::getOutputDir() const
|
||||
{
|
||||
return m_outputDirLineEdit->text();
|
||||
}
|
||||
|
||||
void ExportDialog::initOptions()
|
||||
{
|
||||
// Read it from config.
|
||||
m_option = ConfigMgr::getInst().getSessionConfig().getExportOption();
|
||||
|
||||
const auto &theme = VNoteX::getInst().getThemeMgr().getCurrentTheme();
|
||||
m_option.m_renderingStyleFile = theme.getFile(Theme::File::WebStyleSheet);
|
||||
m_option.m_syntaxHighlightStyleFile = theme.getFile(Theme::File::HighlightStyleSheet);
|
||||
|
||||
if (m_option.m_outputDir.isEmpty()) {
|
||||
m_option.m_outputDir = getDefaultOutputDir();
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::restoreFields(const ExportOption &p_option)
|
||||
{
|
||||
{
|
||||
int idx = m_sourceComboBox->findData(static_cast<int>(p_option.m_source));
|
||||
if (idx != -1) {
|
||||
m_sourceComboBox->setCurrentIndex(idx);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int idx = m_targetFormatComboBox->findData(static_cast<int>(p_option.m_targetFormat));
|
||||
if (idx != -1) {
|
||||
m_targetFormatComboBox->setCurrentIndex(idx);
|
||||
}
|
||||
}
|
||||
|
||||
m_transparentBgCheckBox->setChecked(p_option.m_useTransparentBg);
|
||||
|
||||
{
|
||||
int idx = m_renderingStyleComboBox->findData(p_option.m_renderingStyleFile);
|
||||
if (idx != -1) {
|
||||
m_renderingStyleComboBox->setCurrentIndex(idx);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int idx = m_syntaxHighlightStyleComboBox->findData(p_option.m_syntaxHighlightStyleFile);
|
||||
if (idx != -1) {
|
||||
m_syntaxHighlightStyleComboBox->setCurrentIndex(idx);
|
||||
}
|
||||
}
|
||||
|
||||
m_outputDirLineEdit->setText(p_option.m_outputDir);
|
||||
|
||||
m_recursiveCheckBox->setChecked(p_option.m_recursive);
|
||||
|
||||
m_exportAttachmentsCheckBox->setChecked(p_option.m_exportAttachments);
|
||||
}
|
||||
|
||||
void ExportDialog::saveFields(ExportOption &p_option)
|
||||
{
|
||||
p_option.m_source = static_cast<ExportSource>(m_sourceComboBox->currentData().toInt());
|
||||
p_option.m_targetFormat = static_cast<ExportFormat>(m_targetFormatComboBox->currentData().toInt());
|
||||
p_option.m_useTransparentBg = m_transparentBgCheckBox->isChecked();
|
||||
p_option.m_renderingStyleFile = m_renderingStyleComboBox->currentData().toString();
|
||||
p_option.m_syntaxHighlightStyleFile = m_syntaxHighlightStyleComboBox->currentData().toString();
|
||||
p_option.m_outputDir = getOutputDir();
|
||||
p_option.m_recursive = m_recursiveCheckBox->isChecked();
|
||||
p_option.m_exportAttachments = m_exportAttachmentsCheckBox->isChecked();
|
||||
|
||||
if (m_advancedSettings[AdvancedSettings::HTML]) {
|
||||
saveFields(p_option.m_htmlOption);
|
||||
}
|
||||
|
||||
if (m_advancedSettings[AdvancedSettings::PDF]) {
|
||||
saveFields(p_option.m_pdfOption);
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::startExport()
|
||||
{
|
||||
if (m_exportOngoing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// On start.
|
||||
{
|
||||
m_exportedFile.clear();
|
||||
m_exportOngoing = true;
|
||||
updateUIOnExport();
|
||||
}
|
||||
|
||||
saveFields(m_option);
|
||||
|
||||
int ret = doExport(m_option);
|
||||
appendLog(tr("%n file(s) exported", "", ret));
|
||||
|
||||
// On end.
|
||||
{
|
||||
m_exportOngoing = false;
|
||||
updateUIOnExport();
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::rejectedButtonClicked()
|
||||
{
|
||||
if (m_exportOngoing) {
|
||||
// Just cancel the export.
|
||||
appendLog(tr("Cancelling the export."));
|
||||
m_exporter->stop();
|
||||
} else {
|
||||
Dialog::rejectedButtonClicked();
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::appendLog(const QString &p_log)
|
||||
{
|
||||
appendInformationText(">>> " + p_log);
|
||||
QCoreApplication::sendPostedEvents();
|
||||
}
|
||||
|
||||
void ExportDialog::updateUIOnExport()
|
||||
{
|
||||
m_exportBtn->setEnabled(!m_exportOngoing);
|
||||
if (m_exportOngoing) {
|
||||
m_progressBar->setMaximum(0);
|
||||
m_progressBar->show();
|
||||
} else {
|
||||
m_progressBar->hide();
|
||||
}
|
||||
m_copyContentBtn->setEnabled(!m_exportedFile.isEmpty());
|
||||
}
|
||||
|
||||
int ExportDialog::doExport(ExportOption p_option)
|
||||
{
|
||||
// TODO: Check ExportOption.
|
||||
|
||||
int exportedFilesCount = 0;
|
||||
|
||||
switch (p_option.m_source) {
|
||||
case ExportSource::CurrentBuffer:
|
||||
{
|
||||
Q_ASSERT(m_buffer);
|
||||
const auto outputFile = getExporter()->doExport(p_option, m_buffer);
|
||||
exportedFilesCount = outputFile.isEmpty() ? 0 : 1;
|
||||
if (exportedFilesCount == 1 && p_option.m_targetFormat == ExportFormat::HTML) {
|
||||
m_exportedFile = outputFile;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ExportSource::CurrentFolder:
|
||||
{
|
||||
Q_ASSERT(m_folder);
|
||||
const auto outputFiles = getExporter()->doExport(p_option, m_folder);
|
||||
exportedFilesCount = outputFiles.size();
|
||||
break;
|
||||
}
|
||||
|
||||
case ExportSource::CurrentNotebook:
|
||||
{
|
||||
Q_ASSERT(m_notebook);
|
||||
const auto outputFiles = getExporter()->doExport(p_option, m_notebook);
|
||||
exportedFilesCount = outputFiles.size();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return exportedFilesCount;
|
||||
}
|
||||
|
||||
Exporter *ExportDialog::getExporter()
|
||||
{
|
||||
if (!m_exporter) {
|
||||
m_exporter = new Exporter(this);
|
||||
connect(m_exporter, &Exporter::progressUpdated,
|
||||
this, &ExportDialog::updateProgress);
|
||||
connect(m_exporter, &Exporter::logRequested,
|
||||
this, &ExportDialog::appendLog);
|
||||
}
|
||||
return m_exporter;
|
||||
}
|
||||
|
||||
void ExportDialog::updateProgress(int p_val, int p_maximum)
|
||||
{
|
||||
m_progressBar->setMaximum(p_maximum);
|
||||
m_progressBar->setValue(p_val);
|
||||
}
|
||||
|
||||
QWidget *ExportDialog::getHtmlAdvancedSettings()
|
||||
{
|
||||
if (!m_advancedSettings[AdvancedSettings::HTML]) {
|
||||
// Setup HTML advanced settings.
|
||||
QWidget *widget = new QWidget(m_advancedGroupBox);
|
||||
auto layout = WidgetsFactory::createFormLayout(widget);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
{
|
||||
m_embedStylesCheckBox = WidgetsFactory::createCheckBox(tr("Embed styles"), widget);
|
||||
layout->addRow(m_embedStylesCheckBox);
|
||||
}
|
||||
|
||||
{
|
||||
m_embedImagesCheckBox = WidgetsFactory::createCheckBox(tr("Embed images"), widget);
|
||||
layout->addRow(m_embedImagesCheckBox);
|
||||
}
|
||||
|
||||
{
|
||||
m_completePageCheckBox = WidgetsFactory::createCheckBox(tr("Complete page"), widget);
|
||||
m_completePageCheckBox->setToolTip(tr("Export the whole page along with images which may change the links structure"));
|
||||
connect(m_completePageCheckBox, &QCheckBox::stateChanged,
|
||||
this, [this](int p_state) {
|
||||
bool checked = p_state == Qt::Checked;
|
||||
m_embedImagesCheckBox->setEnabled(checked);
|
||||
});
|
||||
layout->addRow(m_completePageCheckBox);
|
||||
}
|
||||
|
||||
{
|
||||
m_useMimeHtmlFormatCheckBox = WidgetsFactory::createCheckBox(tr("Mime HTML format"), widget);
|
||||
connect(m_useMimeHtmlFormatCheckBox, &QCheckBox::stateChanged,
|
||||
this, [this](int p_state) {
|
||||
bool checked = p_state == Qt::Checked;
|
||||
m_embedStylesCheckBox->setEnabled(!checked);
|
||||
m_completePageCheckBox->setEnabled(!checked);
|
||||
});
|
||||
// TODO: do not support MHTML for now.
|
||||
m_useMimeHtmlFormatCheckBox->setEnabled(false);
|
||||
layout->addRow(m_useMimeHtmlFormatCheckBox);
|
||||
}
|
||||
|
||||
{
|
||||
m_addOutlinePanelCheckBox = WidgetsFactory::createCheckBox(tr("Add outline panel"), widget);
|
||||
layout->addRow(m_addOutlinePanelCheckBox);
|
||||
}
|
||||
|
||||
m_advancedGroupBox->layout()->addWidget(widget);
|
||||
|
||||
m_advancedSettings[AdvancedSettings::HTML] = widget;
|
||||
|
||||
restoreFields(m_option.m_htmlOption);
|
||||
}
|
||||
|
||||
return m_advancedSettings[AdvancedSettings::HTML];
|
||||
}
|
||||
|
||||
void ExportDialog::showAdvancedSettings(AdvancedSettings p_settings)
|
||||
{
|
||||
for (int i = AdvancedSettings::General + 1; i < m_advancedSettings.size(); ++i) {
|
||||
if (m_advancedSettings[i]) {
|
||||
m_advancedSettings[i]->hide();
|
||||
}
|
||||
}
|
||||
|
||||
QWidget *widget = nullptr;
|
||||
switch (p_settings) {
|
||||
case AdvancedSettings::HTML:
|
||||
widget = getHtmlAdvancedSettings();
|
||||
break;
|
||||
|
||||
case AdvancedSettings::PDF:
|
||||
widget = getPdfAdvancedSettings();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (widget) {
|
||||
widget->show();
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::restoreFields(const ExportHtmlOption &p_option)
|
||||
{
|
||||
m_embedStylesCheckBox->setChecked(p_option.m_embedStyles);
|
||||
m_embedImagesCheckBox->setChecked(p_option.m_embedImages);
|
||||
m_completePageCheckBox->setChecked(p_option.m_completePage);
|
||||
m_useMimeHtmlFormatCheckBox->setChecked(p_option.m_useMimeHtmlFormat);
|
||||
m_addOutlinePanelCheckBox->setChecked(p_option.m_addOutlinePanel);
|
||||
}
|
||||
|
||||
void ExportDialog::saveFields(ExportHtmlOption &p_option)
|
||||
{
|
||||
p_option.m_embedStyles = m_embedStylesCheckBox->isChecked();
|
||||
p_option.m_embedImages = m_embedImagesCheckBox->isChecked();
|
||||
p_option.m_completePage = m_completePageCheckBox->isChecked();
|
||||
p_option.m_useMimeHtmlFormat = m_useMimeHtmlFormatCheckBox->isChecked();
|
||||
p_option.m_addOutlinePanel = m_addOutlinePanelCheckBox->isChecked();
|
||||
}
|
||||
|
||||
QWidget *ExportDialog::getPdfAdvancedSettings()
|
||||
{
|
||||
if (!m_advancedSettings[AdvancedSettings::PDF]) {
|
||||
QWidget *widget = new QWidget(m_advancedGroupBox);
|
||||
auto layout = WidgetsFactory::createFormLayout(widget);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
{
|
||||
m_pageLayoutBtn = new QPushButton(tr("Settings"), widget);
|
||||
connect(m_pageLayoutBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
QPrinter printer;
|
||||
printer.setPageLayout(*m_pageLayout);
|
||||
|
||||
QPageSetupDialog dlg(&printer, this);
|
||||
if (dlg.exec() != QDialog::Accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_pageLayout->setUnits(QPageLayout::Millimeter);
|
||||
m_pageLayout->setPageSize(printer.pageLayout().pageSize());
|
||||
m_pageLayout->setMargins(printer.pageLayout().margins(QPageLayout::Millimeter));
|
||||
m_pageLayout->setOrientation(printer.pageLayout().orientation());
|
||||
|
||||
updatePageLayoutButtonLabel();
|
||||
});
|
||||
layout->addRow(tr("Page layout:"), m_pageLayoutBtn);
|
||||
}
|
||||
|
||||
{
|
||||
m_addTableOfContentsCheckBox = WidgetsFactory::createCheckBox(tr("Add Table-Of-Contents"), widget);
|
||||
layout->addRow(m_addTableOfContentsCheckBox);
|
||||
}
|
||||
|
||||
{
|
||||
auto useLayout = new QHBoxLayout();
|
||||
|
||||
m_useWkhtmltopdfCheckBox = WidgetsFactory::createCheckBox(tr("Use wkhtmltopdf"), widget);
|
||||
useLayout->addWidget(m_useWkhtmltopdfCheckBox);
|
||||
|
||||
auto downloadBtn = new QPushButton(tr("Download"), widget);
|
||||
connect(downloadBtn, &QPushButton::clicked,
|
||||
this, []() {
|
||||
WidgetUtils::openUrlByDesktop(QUrl("https://wkhtmltopdf.org/downloads.html"));
|
||||
});
|
||||
useLayout->addWidget(downloadBtn);
|
||||
|
||||
layout->addRow(useLayout);
|
||||
}
|
||||
|
||||
{
|
||||
auto pathLayout = new QHBoxLayout();
|
||||
|
||||
m_wkhtmltopdfExePathLineEdit = WidgetsFactory::createLineEdit(widget);
|
||||
pathLayout->addWidget(m_wkhtmltopdfExePathLineEdit);
|
||||
|
||||
auto browseBtn = new QPushButton(tr("Browse"), widget);
|
||||
pathLayout->addWidget(browseBtn);
|
||||
connect(browseBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
QString filePath = QFileDialog::getOpenFileName(this,
|
||||
tr("Select wkhtmltopdf Executable"),
|
||||
QCoreApplication::applicationDirPath());
|
||||
|
||||
if (!filePath.isEmpty()) {
|
||||
m_wkhtmltopdfExePathLineEdit->setText(filePath);
|
||||
}
|
||||
});
|
||||
|
||||
layout->addRow(tr("Wkhtmltopdf path:"), pathLayout);
|
||||
}
|
||||
|
||||
{
|
||||
m_wkhtmltopdfArgsLineEdit = WidgetsFactory::createLineEdit(widget);
|
||||
layout->addRow(tr("Wkhtmltopdf arguments:"), m_wkhtmltopdfArgsLineEdit);
|
||||
}
|
||||
|
||||
m_advancedGroupBox->layout()->addWidget(widget);
|
||||
|
||||
m_advancedSettings[AdvancedSettings::PDF] = widget;
|
||||
|
||||
restoreFields(m_option.m_pdfOption);
|
||||
}
|
||||
|
||||
return m_advancedSettings[AdvancedSettings::PDF];
|
||||
}
|
||||
|
||||
void ExportDialog::restoreFields(const ExportPdfOption &p_option)
|
||||
{
|
||||
m_pageLayout = p_option.m_layout;
|
||||
updatePageLayoutButtonLabel();
|
||||
|
||||
m_addTableOfContentsCheckBox->setChecked(p_option.m_addTableOfContents);
|
||||
m_useWkhtmltopdfCheckBox->setChecked(p_option.m_useWkhtmltopdf);
|
||||
m_wkhtmltopdfExePathLineEdit->setText(p_option.m_wkhtmltopdfExePath);
|
||||
m_wkhtmltopdfArgsLineEdit->setText(p_option.m_wkhtmltopdfArgs);
|
||||
}
|
||||
|
||||
void ExportDialog::saveFields(ExportPdfOption &p_option)
|
||||
{
|
||||
p_option.m_layout = m_pageLayout;
|
||||
p_option.m_addTableOfContents = m_addTableOfContentsCheckBox->isChecked();
|
||||
p_option.m_useWkhtmltopdf = m_useWkhtmltopdfCheckBox->isChecked();
|
||||
p_option.m_wkhtmltopdfExePath = m_wkhtmltopdfExePathLineEdit->text();
|
||||
p_option.m_wkhtmltopdfArgs = m_wkhtmltopdfArgsLineEdit->text();
|
||||
}
|
||||
|
||||
void ExportDialog::updatePageLayoutButtonLabel()
|
||||
{
|
||||
Q_ASSERT(m_pageLayout);
|
||||
m_pageLayoutBtn->setText(
|
||||
QString("%1, %2").arg(m_pageLayout->pageSize().name(),
|
||||
m_pageLayout->orientation() == QPageLayout::Portrait ? tr("Portrait") : tr("Landscape")));
|
||||
}
|
171
src/widgets/dialogs/exportdialog.h
Normal file
171
src/widgets/dialogs/exportdialog.h
Normal file
@ -0,0 +1,171 @@
|
||||
#ifndef EXPORTDIALOG_H
|
||||
#define EXPORTDIALOG_H
|
||||
|
||||
#include "scrolldialog.h"
|
||||
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include <export/exportdata.h>
|
||||
|
||||
class QGroupBox;
|
||||
class QPushButton;
|
||||
class QComboBox;
|
||||
class QCheckBox;
|
||||
class QLineEdit;
|
||||
class QProgressBar;
|
||||
class QPlainTextEdit;
|
||||
class QPageLayout;
|
||||
|
||||
namespace vnotex
|
||||
{
|
||||
class Notebook;
|
||||
class Node;
|
||||
class Buffer;
|
||||
class Exporter;
|
||||
|
||||
class ExportDialog : public ScrollDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// Current notebook/folder/buffer.
|
||||
ExportDialog(Notebook *p_notebook,
|
||||
Node *p_folder,
|
||||
Buffer *p_buffer,
|
||||
QWidget *p_parent = nullptr);
|
||||
|
||||
protected:
|
||||
void rejectedButtonClicked() Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
void updateProgress(int p_val, int p_maximum);
|
||||
|
||||
void appendLog(const QString &p_log);
|
||||
|
||||
private:
|
||||
enum AdvancedSettings
|
||||
{
|
||||
General,
|
||||
HTML,
|
||||
PDF,
|
||||
Max
|
||||
};
|
||||
|
||||
void setupUI();
|
||||
|
||||
QGroupBox *setupSourceGroup(QWidget *p_parent);
|
||||
|
||||
QGroupBox *setupTargetGroup(QWidget *p_parent);
|
||||
|
||||
QGroupBox *setupAdvancedGroup(QWidget *p_parent);
|
||||
|
||||
QWidget *setupGeneralAdvancedSettings(QWidget *p_parent);
|
||||
|
||||
QWidget *getHtmlAdvancedSettings();
|
||||
|
||||
QWidget *getPdfAdvancedSettings();
|
||||
|
||||
void showAdvancedSettings(AdvancedSettings p_settings);
|
||||
|
||||
void setupButtonBox();
|
||||
|
||||
QString getOutputDir() const;
|
||||
|
||||
void initOptions();
|
||||
|
||||
void restoreFields(const ExportOption &p_option);
|
||||
|
||||
void saveFields(ExportOption &p_option);
|
||||
|
||||
void restoreFields(const ExportHtmlOption &p_option);
|
||||
|
||||
void saveFields(ExportHtmlOption &p_option);
|
||||
|
||||
void restoreFields(const ExportPdfOption &p_option);
|
||||
|
||||
void saveFields(ExportPdfOption &p_option);
|
||||
|
||||
void startExport();
|
||||
|
||||
void updateUIOnExport();
|
||||
|
||||
// Return exported files count.
|
||||
int doExport(ExportOption p_option);
|
||||
|
||||
Exporter *getExporter();
|
||||
|
||||
QString getDefaultOutputDir() const;
|
||||
|
||||
void updatePageLayoutButtonLabel();
|
||||
|
||||
// Managed by QObject.
|
||||
Exporter *m_exporter = nullptr;
|
||||
|
||||
Notebook *m_notebook = nullptr;
|
||||
|
||||
Node *m_folder = nullptr;
|
||||
|
||||
Buffer *m_buffer = nullptr;
|
||||
|
||||
// Last exported single file.
|
||||
QString m_exportedFile;
|
||||
|
||||
bool m_exportOngoing = false;
|
||||
|
||||
QPushButton *m_exportBtn = nullptr;
|
||||
|
||||
QPushButton *m_openDirBtn = nullptr;
|
||||
|
||||
QPushButton *m_copyContentBtn = nullptr;
|
||||
|
||||
QComboBox *m_sourceComboBox = nullptr;
|
||||
|
||||
QComboBox *m_targetFormatComboBox = nullptr;
|
||||
|
||||
QCheckBox *m_transparentBgCheckBox = nullptr;
|
||||
|
||||
QComboBox *m_renderingStyleComboBox = nullptr;
|
||||
|
||||
QComboBox *m_syntaxHighlightStyleComboBox = nullptr;
|
||||
|
||||
QLineEdit *m_outputDirLineEdit = nullptr;
|
||||
|
||||
QProgressBar *m_progressBar = nullptr;
|
||||
|
||||
QGroupBox *m_advancedGroupBox = nullptr;
|
||||
|
||||
QVector<QWidget *> m_advancedSettings;
|
||||
|
||||
// General settings.
|
||||
QCheckBox *m_recursiveCheckBox = nullptr;
|
||||
|
||||
QCheckBox *m_exportAttachmentsCheckBox = nullptr;
|
||||
|
||||
// HTML settings.
|
||||
QCheckBox *m_embedStylesCheckBox = nullptr;
|
||||
|
||||
QCheckBox *m_embedImagesCheckBox = nullptr;
|
||||
|
||||
QCheckBox *m_completePageCheckBox = nullptr;
|
||||
|
||||
QCheckBox *m_useMimeHtmlFormatCheckBox = nullptr;
|
||||
|
||||
QCheckBox *m_addOutlinePanelCheckBox = nullptr;
|
||||
|
||||
// PDF settings.
|
||||
QPushButton *m_pageLayoutBtn = nullptr;
|
||||
|
||||
QCheckBox *m_addTableOfContentsCheckBox = nullptr;
|
||||
|
||||
QCheckBox *m_useWkhtmltopdfCheckBox = nullptr;
|
||||
|
||||
QLineEdit *m_wkhtmltopdfExePathLineEdit = nullptr;
|
||||
|
||||
QLineEdit *m_wkhtmltopdfArgsLineEdit = nullptr;
|
||||
|
||||
QSharedPointer<QPageLayout> m_pageLayout;
|
||||
|
||||
ExportOption m_option;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // EXPORTDIALOG_H
|
@ -30,7 +30,7 @@ void FilePropertiesDialog::setupUI()
|
||||
auto widget = new QWidget(this);
|
||||
setCentralWidget(widget);
|
||||
|
||||
auto mainLayout = WidgetUtils::createFormLayout(widget);
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(widget);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
const QFileInfo info(m_path);
|
||||
|
@ -35,7 +35,7 @@ FolderFilesFilterWidget::FolderFilesFilterWidget(QWidget *p_parent)
|
||||
|
||||
void FolderFilesFilterWidget::setupUI()
|
||||
{
|
||||
auto mainLayout = WidgetUtils::createFormLayout(this);
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
{
|
||||
|
@ -30,7 +30,7 @@ void LinkInsertDialog::setupUI(const QString &p_title,
|
||||
auto mainWidget = new QWidget(this);
|
||||
setCentralWidget(mainWidget);
|
||||
|
||||
auto mainLayout = WidgetUtils::createFormLayout(mainWidget);
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(mainWidget);
|
||||
|
||||
m_linkTextEdit = WidgetsFactory::createLineEdit(p_linkText, mainWidget);
|
||||
mainLayout->addRow(tr("&Text:"), m_linkTextEdit);
|
||||
|
@ -36,7 +36,7 @@ void NodeInfoWidget::setupUI(const Node *p_parentNode, Node::Flags p_newNodeFlag
|
||||
const bool createMode = m_mode == Mode::Create;
|
||||
const bool isNote = p_newNodeFlags & Node::Flag::Content;
|
||||
|
||||
m_mainLayout = WidgetUtils::createFormLayout(this);
|
||||
m_mainLayout = WidgetsFactory::createFormLayout(this);
|
||||
|
||||
m_mainLayout->addRow(tr("Notebook:"),
|
||||
new QLabel(p_parentNode->getNotebook()->getName(), this));
|
||||
@ -84,7 +84,7 @@ void NodeInfoWidget::setupNameLineEdit(QWidget *p_parent)
|
||||
const auto &fileType = FileTypeHelper::getInst().getFileTypeBySuffix(suffix);
|
||||
typeName = fileType.m_typeName;
|
||||
} else {
|
||||
typeName = FileTypeHelper::getInst().getFileType(FileTypeHelper::Others).m_typeName;
|
||||
typeName = FileTypeHelper::getInst().getFileType(FileType::Others).m_typeName;
|
||||
}
|
||||
|
||||
int idx = m_fileTypeComboBox->findData(typeName);
|
||||
|
@ -44,7 +44,7 @@ void NotebookInfoWidget::setupUI()
|
||||
QGroupBox *NotebookInfoWidget::setupBasicInfoGroupBox(QWidget *p_parent)
|
||||
{
|
||||
auto box = new QGroupBox(tr("Basic Information"), p_parent);
|
||||
auto mainLayout = WidgetUtils::createFormLayout(box);
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(box);
|
||||
|
||||
{
|
||||
setupNotebookTypeComboBox(box);
|
||||
@ -131,7 +131,7 @@ QLayout *NotebookInfoWidget::setupNotebookRootFolderPath(QWidget *p_parent)
|
||||
QGroupBox *NotebookInfoWidget::setupAdvancedInfoGroupBox(QWidget *p_parent)
|
||||
{
|
||||
auto box = new QGroupBox(tr("Advanced Information"), p_parent);
|
||||
auto mainLayout = WidgetUtils::createFormLayout(box);
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(box);
|
||||
|
||||
{
|
||||
setupConfigMgrComboBox(box);
|
||||
|
@ -33,6 +33,11 @@ void ScrollDialog::setCentralWidget(QWidget *p_widget)
|
||||
m_scrollArea->setWidget(p_widget);
|
||||
}
|
||||
|
||||
void ScrollDialog::addBottomWidget(QWidget *p_widget)
|
||||
{
|
||||
m_layout->insertWidget(m_layout->indexOf(m_scrollArea) + 1, p_widget);
|
||||
}
|
||||
|
||||
void ScrollDialog::showEvent(QShowEvent *p_event)
|
||||
{
|
||||
QDialog::showEvent(p_event);
|
||||
|
@ -13,11 +13,13 @@ namespace vnotex
|
||||
public:
|
||||
ScrollDialog(QWidget *p_parent = nullptr, Qt::WindowFlags p_flags = Qt::WindowFlags());
|
||||
|
||||
void setCentralWidget(QWidget *p_widget) Q_DECL_OVERRIDE;
|
||||
|
||||
void resizeToHideScrollBarLater(bool p_vertical, bool p_horizontal);
|
||||
|
||||
protected:
|
||||
void setCentralWidget(QWidget *p_widget) Q_DECL_OVERRIDE;
|
||||
|
||||
void addBottomWidget(QWidget *p_widget) Q_DECL_OVERRIDE;
|
||||
|
||||
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
|
@ -20,7 +20,7 @@ AppearancePage::AppearancePage(QWidget *p_parent)
|
||||
|
||||
void AppearancePage::setupUI()
|
||||
{
|
||||
auto mainLayout = WidgetUtils::createFormLayout(this);
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(this);
|
||||
|
||||
{
|
||||
const QString label(tr("System title bar"));
|
||||
|
@ -20,7 +20,7 @@ EditorPage::EditorPage(QWidget *p_parent)
|
||||
|
||||
void EditorPage::setupUI()
|
||||
{
|
||||
auto mainLayout = WidgetUtils::createFormLayout(this);
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(this);
|
||||
|
||||
{
|
||||
m_autoSavePolicyComboBox = WidgetsFactory::createComboBox(this);
|
||||
|
@ -20,7 +20,7 @@ GeneralPage::GeneralPage(QWidget *p_parent)
|
||||
|
||||
void GeneralPage::setupUI()
|
||||
{
|
||||
auto mainLayout = WidgetUtils::createFormLayout(this);
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(this);
|
||||
|
||||
{
|
||||
m_localeComboBox = WidgetsFactory::createComboBox(this);
|
||||
|
@ -125,7 +125,7 @@ QString MarkdownEditorPage::title() const
|
||||
QGroupBox *MarkdownEditorPage::setupReadGroup()
|
||||
{
|
||||
auto box = new QGroupBox(tr("Read"), this);
|
||||
auto layout = WidgetUtils::createFormLayout(box);
|
||||
auto layout = WidgetsFactory::createFormLayout(box);
|
||||
|
||||
{
|
||||
const QString label(tr("Constrain image width"));
|
||||
@ -197,7 +197,7 @@ QGroupBox *MarkdownEditorPage::setupReadGroup()
|
||||
QGroupBox *MarkdownEditorPage::setupEditGroup()
|
||||
{
|
||||
auto box = new QGroupBox(tr("Edit"), this);
|
||||
auto layout = WidgetUtils::createFormLayout(box);
|
||||
auto layout = WidgetsFactory::createFormLayout(box);
|
||||
|
||||
{
|
||||
const QString label(tr("Insert file name as title"));
|
||||
@ -245,7 +245,7 @@ QGroupBox *MarkdownEditorPage::setupEditGroup()
|
||||
QGroupBox *MarkdownEditorPage::setupGeneralGroup()
|
||||
{
|
||||
auto box = new QGroupBox(tr("General"), this);
|
||||
auto layout = WidgetUtils::createFormLayout(box);
|
||||
auto layout = WidgetsFactory::createFormLayout(box);
|
||||
|
||||
{
|
||||
auto sectionLayout = new QHBoxLayout();
|
||||
|
@ -23,7 +23,7 @@ TextEditorPage::TextEditorPage(QWidget *p_parent)
|
||||
|
||||
void TextEditorPage::setupUI()
|
||||
{
|
||||
auto mainLayout = WidgetUtils::createFormLayout(this);
|
||||
auto mainLayout = WidgetsFactory::createFormLayout(this);
|
||||
|
||||
{
|
||||
m_lineNumberComboBox = WidgetsFactory::createComboBox(this);
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include <utils/htmlutils.h>
|
||||
#include <utils/widgetutils.h>
|
||||
#include <utils/textutils.h>
|
||||
#include <utils/webutils.h>
|
||||
#include <core/exception.h>
|
||||
#include <core/markdowneditorconfig.h>
|
||||
#include <core/texteditorconfig.h>
|
||||
@ -1075,7 +1076,7 @@ void MarkdownEditor::fetchImagesToLocalAndReplace(QString &p_text)
|
||||
|
||||
// Only handle absolute file path or network path.
|
||||
QString srcImagePath;
|
||||
QFileInfo info(TextUtils::purifyUrl(imageUrl));
|
||||
QFileInfo info(WebUtils::purifyUrl(imageUrl));
|
||||
|
||||
// For network image.
|
||||
QScopedPointer<QTemporaryFile> tmpFile;
|
||||
|
@ -94,6 +94,16 @@ void MarkdownViewerAdapter::setText(int p_revision,
|
||||
}
|
||||
}
|
||||
|
||||
void MarkdownViewerAdapter::setText(const QString &p_text)
|
||||
{
|
||||
m_revision = 0;
|
||||
if (m_viewerReady) {
|
||||
emit textUpdated(p_text);
|
||||
} else {
|
||||
m_pendingData.reset(new MarkdownData(p_text, -1, ""));
|
||||
}
|
||||
}
|
||||
|
||||
void MarkdownViewerAdapter::setReady(bool p_ready)
|
||||
{
|
||||
if (m_viewerReady == p_ready) {
|
||||
@ -328,3 +338,21 @@ void MarkdownViewerAdapter::setFindText(const QString &p_text, int p_totalMatche
|
||||
{
|
||||
emit findTextReady(p_text, p_totalMatches, p_currentMatchIndex);
|
||||
}
|
||||
|
||||
void MarkdownViewerAdapter::setWorkFinished()
|
||||
{
|
||||
emit workFinished();
|
||||
}
|
||||
|
||||
void MarkdownViewerAdapter::saveContent()
|
||||
{
|
||||
emit contentRequested();
|
||||
}
|
||||
|
||||
void MarkdownViewerAdapter::setSavedContent(const QString &p_headContent,
|
||||
const QString &p_styleContent,
|
||||
const QString &p_content,
|
||||
const QString &p_bodyClassList)
|
||||
{
|
||||
emit contentReady(p_headContent, p_styleContent, p_content, p_bodyClassList);
|
||||
}
|
||||
|
@ -100,6 +100,8 @@ namespace vnotex
|
||||
const QString &p_text,
|
||||
int p_lineNumber);
|
||||
|
||||
void setText(const QString &p_text);
|
||||
|
||||
void scrollToPosition(const Position &p_pos);
|
||||
|
||||
int getTopLineNumber() const;
|
||||
@ -119,10 +121,14 @@ namespace vnotex
|
||||
|
||||
void findText(const QString &p_text, FindOptions p_options);
|
||||
|
||||
void saveContent();
|
||||
|
||||
// Functions to be called from web side.
|
||||
public slots:
|
||||
void setReady(bool p_ready);
|
||||
|
||||
void setWorkFinished();
|
||||
|
||||
// The line number at the top.
|
||||
void setTopLineNumber(int p_lineNumber);
|
||||
|
||||
@ -161,6 +167,8 @@ namespace vnotex
|
||||
|
||||
void setFindText(const QString &p_text, int p_totalMatches, int p_currentMatchIndex);
|
||||
|
||||
void setSavedContent(const QString &p_headContent, const QString &p_styleContent, const QString &p_content, const QString &p_bodyClassList);
|
||||
|
||||
// Signals to be connected at web side.
|
||||
signals:
|
||||
// Current Markdown text is updated.
|
||||
@ -194,6 +202,9 @@ namespace vnotex
|
||||
|
||||
void findTextRequested(const QString &p_text, const QJsonObject &p_options);
|
||||
|
||||
// Request to get the whole HTML content.
|
||||
void contentRequested();
|
||||
|
||||
// Signals to be connected at cpp side.
|
||||
signals:
|
||||
void graphPreviewDataReady(const PreviewData &p_data);
|
||||
@ -202,6 +213,9 @@ namespace vnotex
|
||||
|
||||
void viewerReady();
|
||||
|
||||
// All rendering work has finished.
|
||||
void workFinished();
|
||||
|
||||
void headingsChanged();
|
||||
|
||||
void currentHeadingChanged();
|
||||
@ -216,6 +230,11 @@ namespace vnotex
|
||||
|
||||
void findTextReady(const QString &p_text, int p_totalMatches, int p_currentMatchIndex);
|
||||
|
||||
void contentReady(const QString &p_headContent,
|
||||
const QString &p_styleContent,
|
||||
const QString &p_content,
|
||||
const QString &p_bodyClassList);
|
||||
|
||||
private:
|
||||
void scrollToLine(int p_lineNumber);
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
#include <core/coreconfig.h>
|
||||
#include <core/events.h>
|
||||
#include <core/fileopenparameters.h>
|
||||
#include <widgets/dialogs/scrolldialog.h>
|
||||
#include <widgets/dialogs/exportdialog.h>
|
||||
#include "viewwindow.h"
|
||||
#include "outlineviewer.h"
|
||||
#include <utils/widgetutils.h>
|
||||
@ -57,6 +57,9 @@ MainWindow::MainWindow(QWidget *p_parent)
|
||||
// Note that no user interaction is possible in this state.
|
||||
connect(qApp, &QCoreApplication::aboutToQuit,
|
||||
this, &MainWindow::closeOnQuit);
|
||||
|
||||
connect(&VNoteX::getInst(), &VNoteX::exportRequested,
|
||||
this, &MainWindow::exportNotes);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
@ -582,3 +585,18 @@ void MainWindow::updateTabBarStyle()
|
||||
tabBar->setDrawBase(false);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::exportNotes()
|
||||
{
|
||||
auto currentNotebook = m_notebookExplorer->currentNotebook().data();
|
||||
auto viewWindow = m_viewArea->getCurrentViewWindow();
|
||||
auto folderNode = m_notebookExplorer->currentExploredFolderNode();
|
||||
if (folderNode && (folderNode->isRoot() || currentNotebook->isRecycleBinNode(folderNode))) {
|
||||
folderNode = nullptr;
|
||||
}
|
||||
ExportDialog dialog(currentNotebook,
|
||||
folderNode,
|
||||
viewWindow ? viewWindow->getBuffer() : nullptr,
|
||||
this);
|
||||
dialog.exec();
|
||||
}
|
||||
|
@ -72,6 +72,8 @@ namespace vnotex
|
||||
|
||||
void updateTabBarStyle();
|
||||
|
||||
void exportNotes();
|
||||
|
||||
private:
|
||||
// Index in m_docks.
|
||||
enum DockIndex
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user