mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 05:49:53 +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;
|
return m_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSharedPointer<File> Buffer::getFile() const
|
||||||
|
{
|
||||||
|
return m_provider->getFile();
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ namespace vnotex
|
|||||||
class ViewWindow;
|
class ViewWindow;
|
||||||
struct FileOpenParameters;
|
struct FileOpenParameters;
|
||||||
class BufferProvider;
|
class BufferProvider;
|
||||||
|
class File;
|
||||||
|
|
||||||
struct BufferParameters
|
struct BufferParameters
|
||||||
{
|
{
|
||||||
@ -83,6 +84,9 @@ namespace vnotex
|
|||||||
// Get the base path to resolve resources.
|
// Get the base path to resolve resources.
|
||||||
QString getResourcePath() const;
|
QString getResourcePath() const;
|
||||||
|
|
||||||
|
// Return nullptr if not available.
|
||||||
|
QSharedPointer<File> getFile() const;
|
||||||
|
|
||||||
ID getID() const;
|
ID getID() const;
|
||||||
|
|
||||||
// Get buffer content.
|
// Get buffer content.
|
||||||
|
@ -74,6 +74,9 @@ namespace vnotex
|
|||||||
|
|
||||||
virtual bool isReadOnly() const = 0;
|
virtual bool isReadOnly() const = 0;
|
||||||
|
|
||||||
|
// Return nullptr if not available.
|
||||||
|
virtual QSharedPointer<File> getFile() const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual QDateTime getLastModifiedFromFile() const;
|
virtual QDateTime getLastModifiedFromFile() const;
|
||||||
|
|
||||||
|
@ -178,3 +178,8 @@ bool FileBufferProvider::isReadOnly() const
|
|||||||
{
|
{
|
||||||
return m_readOnly;
|
return m_readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSharedPointer<File> FileBufferProvider::getFile() const
|
||||||
|
{
|
||||||
|
return m_file;
|
||||||
|
}
|
||||||
|
@ -65,6 +65,8 @@ namespace vnotex
|
|||||||
|
|
||||||
bool isReadOnly() const Q_DECL_OVERRIDE;
|
bool isReadOnly() const Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
|
QSharedPointer<File> getFile() const Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<File> m_file;
|
QSharedPointer<File> m_file;
|
||||||
|
|
||||||
|
@ -8,6 +8,16 @@
|
|||||||
|
|
||||||
using namespace vnotex;
|
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()
|
FileTypeHelper::FileTypeHelper()
|
||||||
{
|
{
|
||||||
setupBuiltInTypes();
|
setupBuiltInTypes();
|
||||||
@ -21,7 +31,7 @@ void FileTypeHelper::setupBuiltInTypes()
|
|||||||
{
|
{
|
||||||
{
|
{
|
||||||
FileType type;
|
FileType type;
|
||||||
type.m_type = Type::Markdown;
|
type.m_type = FileType::Markdown;
|
||||||
type.m_displayName = Buffer::tr("Markdown");
|
type.m_displayName = Buffer::tr("Markdown");
|
||||||
type.m_typeName = QStringLiteral("Markdown");
|
type.m_typeName = QStringLiteral("Markdown");
|
||||||
type.m_suffixes << QStringLiteral("md")
|
type.m_suffixes << QStringLiteral("md")
|
||||||
@ -33,7 +43,7 @@ void FileTypeHelper::setupBuiltInTypes()
|
|||||||
|
|
||||||
{
|
{
|
||||||
FileType type;
|
FileType type;
|
||||||
type.m_type = Type::Text;
|
type.m_type = FileType::Text;
|
||||||
type.m_typeName = QStringLiteral("Text");
|
type.m_typeName = QStringLiteral("Text");
|
||||||
type.m_displayName = Buffer::tr("Text");
|
type.m_displayName = Buffer::tr("Text");
|
||||||
type.m_suffixes << QStringLiteral("txt") << QStringLiteral("text") << QStringLiteral("log");
|
type.m_suffixes << QStringLiteral("txt") << QStringLiteral("text") << QStringLiteral("log");
|
||||||
@ -42,7 +52,7 @@ void FileTypeHelper::setupBuiltInTypes()
|
|||||||
|
|
||||||
{
|
{
|
||||||
FileType type;
|
FileType type;
|
||||||
type.m_type = Type::Others;
|
type.m_type = FileType::Others;
|
||||||
type.m_typeName = QStringLiteral("Others");
|
type.m_typeName = QStringLiteral("Others");
|
||||||
type.m_displayName = Buffer::tr("Others");
|
type.m_displayName = Buffer::tr("Others");
|
||||||
m_fileTypes.push_back(type);
|
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.
|
// Treat all unknown text files as plain text files.
|
||||||
if (FileUtils::isText(p_filePath)) {
|
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
|
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()) {
|
if (it != m_suffixTypeMap.end()) {
|
||||||
return m_fileTypes.at(it.value());
|
return m_fileTypes.at(it.value());
|
||||||
} else {
|
} else {
|
||||||
return m_fileTypes[Type::Others];
|
return m_fileTypes[FileType::Others];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,8 +107,9 @@ const QVector<FileType> &FileTypeHelper::getAllFileTypes() const
|
|||||||
return m_fileTypes;
|
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];
|
return m_fileTypes[p_type];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,9 +119,9 @@ const FileTypeHelper &FileTypeHelper::getInst()
|
|||||||
return helper;
|
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
|
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);
|
Q_ASSERT(false);
|
||||||
return m_fileTypes[Type::Others];
|
return m_fileTypes[FileType::Others];
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,15 @@ namespace vnotex
|
|||||||
class FileType
|
class FileType
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// FileTypeHelper::Type.
|
// There may be other types after Others.
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
Markdown = 0,
|
||||||
|
Text,
|
||||||
|
Others
|
||||||
|
};
|
||||||
|
|
||||||
|
// Type.
|
||||||
int m_type = -1;
|
int m_type = -1;
|
||||||
|
|
||||||
QString m_typeName;
|
QString m_typeName;
|
||||||
@ -19,25 +27,17 @@ namespace vnotex
|
|||||||
|
|
||||||
QStringList m_suffixes;
|
QStringList m_suffixes;
|
||||||
|
|
||||||
QString preferredSuffix() const
|
QString preferredSuffix() const;
|
||||||
{
|
|
||||||
return m_suffixes.isEmpty() ? QString() : m_suffixes.first();
|
bool isMarkdown() const;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileTypeHelper
|
class FileTypeHelper
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
Markdown = 0,
|
|
||||||
Text,
|
|
||||||
Others
|
|
||||||
};
|
|
||||||
|
|
||||||
const FileType &getFileType(const QString &p_filePath) const;
|
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;
|
const FileType &getFileTypeByName(const QString &p_typeName) const;
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ namespace vnotex
|
|||||||
|
|
||||||
const QVector<FileType> &getAllFileTypes() const;
|
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();
|
static const FileTypeHelper &getInst();
|
||||||
|
|
||||||
|
@ -8,12 +8,13 @@
|
|||||||
|
|
||||||
using namespace vnotex;
|
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),
|
: BufferProvider(p_parent),
|
||||||
m_node(p_node),
|
m_node(p_node),
|
||||||
m_nodeFile(p_node->getContentFile())
|
m_nodeFile(p_file)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_nodeFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer::ProviderType NodeBufferProvider::getType() const
|
Buffer::ProviderType NodeBufferProvider::getType() const
|
||||||
@ -156,3 +157,8 @@ bool NodeBufferProvider::isReadOnly() const
|
|||||||
{
|
{
|
||||||
return m_node->isReadOnly();
|
return m_node->isReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSharedPointer<File> NodeBufferProvider::getFile() const
|
||||||
|
{
|
||||||
|
return m_nodeFile;
|
||||||
|
}
|
||||||
|
@ -15,7 +15,9 @@ namespace vnotex
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
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;
|
Buffer::ProviderType getType() const Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
@ -65,6 +67,8 @@ namespace vnotex
|
|||||||
|
|
||||||
bool isReadOnly() const Q_DECL_OVERRIDE;
|
bool isReadOnly() const Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
|
QSharedPointer<File> getFile() const Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<Node> m_node;
|
QSharedPointer<Node> m_node;
|
||||||
|
|
||||||
|
@ -42,11 +42,11 @@ void BufferMgr::initBufferServer()
|
|||||||
|
|
||||||
// Markdown.
|
// Markdown.
|
||||||
auto markdownFactory = QSharedPointer<MarkdownBufferFactory>::create();
|
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.
|
// Text.
|
||||||
auto textFactory = QSharedPointer<TextBufferFactory>::create();
|
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)
|
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);
|
auto buffer = findBuffer(p_node);
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
auto nodePath = p_node->fetchAbsolutePath();
|
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);
|
auto factory = m_bufferServer->getItem(fileType);
|
||||||
if (!factory) {
|
if (!factory) {
|
||||||
// No factory to open this file type.
|
// No factory to open this file type.
|
||||||
@ -72,7 +74,7 @@ void BufferMgr::open(Node *p_node, const QSharedPointer<FileOpenParameters> &p_p
|
|||||||
}
|
}
|
||||||
|
|
||||||
BufferParameters paras;
|
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);
|
buffer = factory->createBuffer(paras, this);
|
||||||
addBuffer(buffer);
|
addBuffer(buffer);
|
||||||
}
|
}
|
||||||
@ -114,7 +116,8 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointer<FileOpenPar
|
|||||||
auto buffer = findBuffer(p_filePath);
|
auto buffer = findBuffer(p_filePath);
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
// Open it as external file.
|
// 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);
|
auto factory = m_bufferServer->getItem(fileType);
|
||||||
if (!factory) {
|
if (!factory) {
|
||||||
// No factory to open this file type.
|
// No factory to open this file type.
|
||||||
@ -124,7 +127,7 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointer<FileOpenPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
BufferParameters paras;
|
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_nodeAttachedTo,
|
||||||
p_paras->m_readOnly));
|
p_paras->m_readOnly));
|
||||||
buffer = factory->createBuffer(paras, this);
|
buffer = factory->createBuffer(paras, this);
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
using namespace vnotex;
|
using namespace vnotex;
|
||||||
|
|
||||||
#ifndef QT_NO_DEBUG
|
#ifndef QT_NO_DEBUG
|
||||||
// #define VX_DEBUG_WEB
|
#define VX_DEBUG_WEB
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const QString ConfigMgr::c_orgName = "VNote";
|
const QString ConfigMgr::c_orgName = "VNote";
|
||||||
@ -320,6 +320,18 @@ QString ConfigMgr::getUserThemeFolder() const
|
|||||||
return folderPath;
|
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
|
QString ConfigMgr::getAppDocsFolder() const
|
||||||
{
|
{
|
||||||
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("docs"));
|
return PathUtils::concatenateFilePath(m_appConfigFolderPath, QStringLiteral("docs"));
|
||||||
@ -406,3 +418,18 @@ QString ConfigMgr::getApplicationDirPath()
|
|||||||
{
|
{
|
||||||
return PathUtils::parentDirPath(getApplicationFilePath());
|
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 getUserThemeFolder() const;
|
||||||
|
|
||||||
|
QString getAppWebStylesFolder() const;
|
||||||
|
|
||||||
|
QString getUserWebStylesFolder() const;
|
||||||
|
|
||||||
QString getAppDocsFolder() const;
|
QString getAppDocsFolder() const;
|
||||||
|
|
||||||
QString getUserDocsFolder() const;
|
QString getUserDocsFolder() const;
|
||||||
@ -98,6 +102,8 @@ namespace vnotex
|
|||||||
|
|
||||||
static QString getApplicationDirPath();
|
static QString getApplicationDirPath();
|
||||||
|
|
||||||
|
static QString getDocumentOrHomePath();
|
||||||
|
|
||||||
static const QString c_orgName;
|
static const QString c_orgName;
|
||||||
|
|
||||||
static const QString c_appName;
|
static const QString c_appName;
|
||||||
|
@ -32,7 +32,6 @@ SOURCES += \
|
|||||||
$$PWD/widgetconfig.cpp
|
$$PWD/widgetconfig.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
$$PWD/ViewerResource.h \
|
|
||||||
$$PWD/buffermgr.h \
|
$$PWD/buffermgr.h \
|
||||||
$$PWD/configmgr.h \
|
$$PWD/configmgr.h \
|
||||||
$$PWD/coreconfig.h \
|
$$PWD/coreconfig.h \
|
||||||
@ -58,4 +57,5 @@ HEADERS += \
|
|||||||
$$PWD/theme.h \
|
$$PWD/theme.h \
|
||||||
$$PWD/sessionconfig.h \
|
$$PWD/sessionconfig.h \
|
||||||
$$PWD/clipboarddata.h \
|
$$PWD/clipboarddata.h \
|
||||||
|
$$PWD/webresource.h \
|
||||||
$$PWD/widgetconfig.h
|
$$PWD/widgetconfig.h
|
||||||
|
@ -31,6 +31,7 @@ namespace vnotex
|
|||||||
DistributeSplits,
|
DistributeSplits,
|
||||||
RemoveSplitAndWorkspace,
|
RemoveSplitAndWorkspace,
|
||||||
NewWorkspace,
|
NewWorkspace,
|
||||||
|
Export,
|
||||||
MaxShortcut
|
MaxShortcut
|
||||||
};
|
};
|
||||||
Q_ENUM(Shortcut)
|
Q_ENUM(Shortcut)
|
||||||
|
@ -8,6 +8,7 @@ using namespace vnotex;
|
|||||||
ExternalFile::ExternalFile(const QString &p_filePath)
|
ExternalFile::ExternalFile(const QString &p_filePath)
|
||||||
: c_filePath(p_filePath)
|
: c_filePath(p_filePath)
|
||||||
{
|
{
|
||||||
|
setContentType(FileTypeHelper::getInst().getFileType(c_filePath).m_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ExternalFile::read() const
|
QString ExternalFile::read() const
|
||||||
|
@ -7,7 +7,7 @@ const FileType &File::getContentType() const
|
|||||||
return FileTypeHelper::getInst().getFileType(m_contentType);
|
return FileTypeHelper::getInst().getFileType(m_contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::setContentType(FileTypeHelper::Type p_type)
|
void File::setContentType(int p_type)
|
||||||
{
|
{
|
||||||
m_contentType = p_type;
|
m_contentType = p_type;
|
||||||
}
|
}
|
||||||
|
@ -60,10 +60,10 @@ namespace vnotex
|
|||||||
const FileType &getContentType() const;
|
const FileType &getContentType() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setContentType(FileTypeHelper::Type p_type);
|
void setContentType(int p_type);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileTypeHelper::Type m_contentType = FileTypeHelper::Others;
|
int m_contentType = FileType::Others;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include <utils/utils.h>
|
#include <utils/utils.h>
|
||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
#include <utils/pathutils.h>
|
#include <utils/pathutils.h>
|
||||||
|
#include <utils/htmlutils.h>
|
||||||
#include <core/thememgr.h>
|
#include <core/thememgr.h>
|
||||||
#include <core/vnotex.h>
|
#include <core/vnotex.h>
|
||||||
|
|
||||||
@ -14,6 +15,8 @@ using namespace vnotex;
|
|||||||
|
|
||||||
HtmlTemplateHelper::Template HtmlTemplateHelper::s_markdownViewerTemplate;
|
HtmlTemplateHelper::Template HtmlTemplateHelper::s_markdownViewerTemplate;
|
||||||
|
|
||||||
|
static const QString c_globalStylesPlaceholder = "/* VX_GLOBAL_STYLES_PLACEHOLDER */";
|
||||||
|
|
||||||
QString WebGlobalOptions::toJavascriptObject() const
|
QString WebGlobalOptions::toJavascriptObject() const
|
||||||
{
|
{
|
||||||
return QStringLiteral("window.vxOptions = {\n")
|
return QStringLiteral("window.vxOptions = {\n")
|
||||||
@ -26,21 +29,22 @@ QString WebGlobalOptions::toJavascriptObject() const
|
|||||||
+ QString("linkifyEnabled: %1,\n").arg(Utils::boolToString(m_linkifyEnabled))
|
+ QString("linkifyEnabled: %1,\n").arg(Utils::boolToString(m_linkifyEnabled))
|
||||||
+ QString("indentFirstLineEnabled: %1,\n").arg(Utils::boolToString(m_indentFirstLineEnabled))
|
+ QString("indentFirstLineEnabled: %1,\n").arg(Utils::boolToString(m_indentFirstLineEnabled))
|
||||||
+ QString("sectionNumberEnabled: %1,\n").arg(Utils::boolToString(m_sectionNumberEnabled))
|
+ 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)
|
+ QString("sectionNumberBaseLevel: %1\n").arg(m_sectionNumberBaseLevel)
|
||||||
+ QStringLiteral("}");
|
+ 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.
|
// 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;
|
QString styles;
|
||||||
for (const auto &ele : p_resource.m_resources) {
|
for (const auto &ele : p_resource.m_resources) {
|
||||||
if (isGlobalStyles(ele)) {
|
if (ele.isGlobal()) {
|
||||||
if (ele.m_enabled) {
|
if (ele.m_enabled) {
|
||||||
for (const auto &style : ele.m_styles) {
|
for (const auto &style : ele.m_styles) {
|
||||||
// Read the style file content.
|
// 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()) {
|
if (!styles.isEmpty()) {
|
||||||
p_template.replace(QStringLiteral("/* VX_GLOBAL_STYLES_PLACEHOLDER */"),
|
p_template.replace(c_globalStylesPlaceholder, styles);
|
||||||
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());
|
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;
|
QString styles;
|
||||||
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
styles += fillStyleTag(p_webStyleSheetFile);
|
||||||
|
styles += fillStyleTag(p_highlightStyleSheetFile);
|
||||||
styles += fillStyleTag(themeMgr.getFile(Theme::File::WebStyleSheet));
|
|
||||||
styles += fillStyleTag(themeMgr.getFile(Theme::File::HighlightStyleSheet));
|
|
||||||
|
|
||||||
if (!styles.isEmpty()) {
|
if (!styles.isEmpty()) {
|
||||||
p_template.replace(QStringLiteral("<!-- VX_THEME_STYLES_PLACEHOLDER -->"),
|
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.
|
// 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 styles;
|
||||||
QString scripts;
|
QString scripts;
|
||||||
|
|
||||||
for (const auto &ele : p_resource.m_resources) {
|
for (const auto &ele : p_resource.m_resources) {
|
||||||
if (ele.m_enabled && !isGlobalStyles(ele)) {
|
if (ele.m_enabled && !ele.isGlobal()) {
|
||||||
// Styles.
|
// Styles.
|
||||||
for (const auto &style : ele.m_styles) {
|
for (const auto &style : ele.m_styles) {
|
||||||
auto styleFile = ConfigMgr::getInst().getUserOrAppFile(style);
|
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()
|
const QString &HtmlTemplateHelper::getMarkdownViewerTemplate()
|
||||||
{
|
{
|
||||||
return s_markdownViewerTemplate.m_template;
|
return s_markdownViewerTemplate.m_template;
|
||||||
@ -142,16 +175,30 @@ void HtmlTemplateHelper::updateMarkdownViewerTemplate(const MarkdownEditorConfig
|
|||||||
|
|
||||||
s_markdownViewerTemplate.m_revision = p_config.revision();
|
s_markdownViewerTemplate.m_revision = p_config.revision();
|
||||||
|
|
||||||
const auto &viewerResource = p_config.getViewerResource();
|
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
|
||||||
|
s_markdownViewerTemplate.m_template =
|
||||||
{
|
generateMarkdownViewerTemplate(p_config,
|
||||||
auto templateFile = ConfigMgr::getInst().getUserOrAppFile(viewerResource.m_template);
|
themeMgr.getFile(Theme::File::WebStyleSheet),
|
||||||
s_markdownViewerTemplate.m_template = FileUtils::readTextFile(templateFile);
|
themeMgr.getFile(Theme::File::HighlightStyleSheet));
|
||||||
}
|
}
|
||||||
|
|
||||||
fillGlobalStyles(s_markdownViewerTemplate.m_template, viewerResource);
|
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);
|
||||||
|
|
||||||
fillThemeStyles(s_markdownViewerTemplate.m_template);
|
fillGlobalStyles(htmlTemplate, viewerResource, "");
|
||||||
|
|
||||||
|
fillThemeStyles(htmlTemplate, p_webStyleSheetFile, p_highlightStyleSheetFile);
|
||||||
|
|
||||||
{
|
{
|
||||||
WebGlobalOptions opts;
|
WebGlobalOptions opts;
|
||||||
@ -165,8 +212,66 @@ void HtmlTemplateHelper::updateMarkdownViewerTemplate(const MarkdownEditorConfig
|
|||||||
opts.m_autoBreakEnabled = p_config.getAutoBreakEnabled();
|
opts.m_autoBreakEnabled = p_config.getAutoBreakEnabled();
|
||||||
opts.m_linkifyEnabled = p_config.getLinkifyEnabled();
|
opts.m_linkifyEnabled = p_config.getLinkifyEnabled();
|
||||||
opts.m_indentFirstLineEnabled = p_config.getIndentFirstLineEnabled();
|
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;
|
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;
|
QString toJavascriptObject() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,6 +60,29 @@ namespace vnotex
|
|||||||
static const QString &getMarkdownViewerTemplate();
|
static const QString &getMarkdownViewerTemplate();
|
||||||
static void updateMarkdownViewerTemplate(const MarkdownEditorConfig &p_config);
|
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:
|
private:
|
||||||
struct Template
|
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();
|
const auto userObj = p_user.value(m_sessionName).toObject();
|
||||||
|
|
||||||
loadViewerResource(appObj, userObj);
|
loadViewerResource(appObj, userObj);
|
||||||
|
loadExportResource(appObj, userObj);
|
||||||
|
|
||||||
m_webPlantUml = READBOOL(QStringLiteral("web_plantuml"));
|
m_webPlantUml = READBOOL(QStringLiteral("web_plantuml"));
|
||||||
m_webGraphviz = READBOOL(QStringLiteral("web_graphviz"));
|
m_webGraphviz = READBOOL(QStringLiteral("web_graphviz"));
|
||||||
@ -58,6 +59,7 @@ QJsonObject MarkdownEditorConfig::toJson() const
|
|||||||
{
|
{
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj[QStringLiteral("viewer_resource")] = saveViewerResource();
|
obj[QStringLiteral("viewer_resource")] = saveViewerResource();
|
||||||
|
obj[QStringLiteral("export_resource")] = saveExportResource();
|
||||||
obj[QStringLiteral("web_plantuml")] = m_webPlantUml;
|
obj[QStringLiteral("web_plantuml")] = m_webPlantUml;
|
||||||
obj[QStringLiteral("web_graphviz")] = m_webGraphviz;
|
obj[QStringLiteral("web_graphviz")] = m_webGraphviz;
|
||||||
obj[QStringLiteral("prepend_dot_in_relative_link")] = m_prependDotInRelativeLink;
|
obj[QStringLiteral("prepend_dot_in_relative_link")] = m_prependDotInRelativeLink;
|
||||||
@ -122,11 +124,41 @@ QJsonObject MarkdownEditorConfig::saveViewerResource() const
|
|||||||
return m_viewerResource.toJson();
|
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;
|
return m_viewerResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WebResource &MarkdownEditorConfig::getExportResource() const
|
||||||
|
{
|
||||||
|
return m_exportResource;
|
||||||
|
}
|
||||||
|
|
||||||
bool MarkdownEditorConfig::getWebPlantUml() const
|
bool MarkdownEditorConfig::getWebPlantUml() const
|
||||||
{
|
{
|
||||||
return m_webPlantUml;
|
return m_webPlantUml;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "iconfig.h"
|
#include "iconfig.h"
|
||||||
|
|
||||||
#include "viewerresource.h"
|
#include "webresource.h"
|
||||||
|
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
@ -38,15 +38,14 @@ namespace vnotex
|
|||||||
|
|
||||||
QJsonObject toJson() const Q_DECL_OVERRIDE;
|
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;
|
int revision() const Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
TextEditorConfig &getTextEditorConfig();
|
TextEditorConfig &getTextEditorConfig();
|
||||||
const TextEditorConfig &getTextEditorConfig() const;
|
const TextEditorConfig &getTextEditorConfig() const;
|
||||||
|
|
||||||
const ViewerResource &getViewerResource() const;
|
const WebResource &getViewerResource() const;
|
||||||
|
|
||||||
|
const WebResource &getExportResource() const;
|
||||||
|
|
||||||
bool getWebPlantUml() const;
|
bool getWebPlantUml() const;
|
||||||
|
|
||||||
@ -107,9 +106,17 @@ namespace vnotex
|
|||||||
QString sectionNumberStyleToString(SectionNumberStyle p_style) const;
|
QString sectionNumberStyleToString(SectionNumberStyle p_style) const;
|
||||||
SectionNumberStyle stringToSectionNumberStyle(const QString &p_str) 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;
|
QSharedPointer<TextEditorConfig> m_textEditorConfig;
|
||||||
|
|
||||||
ViewerResource m_viewerResource;
|
WebResource m_viewerResource;
|
||||||
|
|
||||||
|
WebResource m_exportResource;
|
||||||
|
|
||||||
// Whether use javascript or external program to render PlantUML.
|
// Whether use javascript or external program to render PlantUML.
|
||||||
bool m_webPlantUml = true;
|
bool m_webPlantUml = true;
|
||||||
|
@ -263,6 +263,10 @@ QDir Node::toDir() const
|
|||||||
|
|
||||||
void Node::load()
|
void Node::load()
|
||||||
{
|
{
|
||||||
|
if (isLoaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
getConfigMgr()->loadNode(this);
|
getConfigMgr()->loadNode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ VXNodeFile::VXNodeFile(const QSharedPointer<VXNode> &p_node)
|
|||||||
: m_node(p_node)
|
: m_node(p_node)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_node && m_node->hasContent());
|
Q_ASSERT(m_node && m_node->hasContent());
|
||||||
|
setContentType(FileTypeHelper::getInst().getFileType(getContentPath()).m_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString VXNodeFile::read() const
|
QString VXNodeFile::read() const
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
SOURCES += \
|
SOURCES += \
|
||||||
$$PWD/nodecontentmediautils.cpp \
|
|
||||||
$$PWD/vxnotebookconfigmgr.cpp \
|
$$PWD/vxnotebookconfigmgr.cpp \
|
||||||
$$PWD/vxnotebookconfigmgrfactory.cpp \
|
$$PWD/vxnotebookconfigmgrfactory.cpp \
|
||||||
$$PWD/inotebookconfigmgr.cpp \
|
$$PWD/inotebookconfigmgr.cpp \
|
||||||
@ -8,7 +7,6 @@ SOURCES += \
|
|||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
$$PWD/inotebookconfigmgr.h \
|
$$PWD/inotebookconfigmgr.h \
|
||||||
$$PWD/nodecontentmediautils.h \
|
|
||||||
$$PWD/vxnotebookconfigmgr.h \
|
$$PWD/vxnotebookconfigmgr.h \
|
||||||
$$PWD/inotebookconfigmgrfactory.h \
|
$$PWD/inotebookconfigmgrfactory.h \
|
||||||
$$PWD/vxnotebookconfigmgrfactory.h \
|
$$PWD/vxnotebookconfigmgrfactory.h \
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
#include <utils/pathutils.h>
|
#include <utils/pathutils.h>
|
||||||
#include <exception.h>
|
#include <exception.h>
|
||||||
|
|
||||||
#include "nodecontentmediautils.h"
|
#include <utils/contentmediautils.h>
|
||||||
|
|
||||||
using namespace vnotex;
|
using namespace vnotex;
|
||||||
|
|
||||||
@ -581,13 +581,13 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileNodeAsChildOf(const QSharedPoi
|
|||||||
getBackend()->copyFile(srcFilePath, destFilePath);
|
getBackend()->copyFile(srcFilePath, destFilePath);
|
||||||
|
|
||||||
// Copy media files fetched from content.
|
// 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.
|
// Copy attachment folder. Rename attachment folder if conflicts.
|
||||||
QString attachmentFolder = p_src->getAttachmentFolder();
|
QString attachmentFolder = p_src->getAttachmentFolder();
|
||||||
if (!attachmentFolder.isEmpty()) {
|
if (!attachmentFolder.isEmpty()) {
|
||||||
auto destAttachmentFolderPath = fetchNodeAttachmentFolder(destFilePath, attachmentFolder);
|
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.
|
// Create a file node.
|
||||||
@ -690,7 +690,7 @@ void VXNotebookConfigMgr::removeFilesOfNode(Node *p_node, bool p_force)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete media files fetched from content.
|
// Delete media files fetched from content.
|
||||||
NodeContentMediaUtils::removeMediaFiles(p_node);
|
ContentMediaUtils::removeMediaFiles(p_node);
|
||||||
|
|
||||||
// Delete node file itself.
|
// Delete node file itself.
|
||||||
auto filePath = p_node->fetchPath();
|
auto filePath = p_node->fetchPath();
|
||||||
@ -787,7 +787,7 @@ QSharedPointer<Node> VXNotebookConfigMgr::copyFileAsChildOf(const QString &p_src
|
|||||||
getBackend()->copyFile(p_srcPath, destFilePath);
|
getBackend()->copyFile(p_srcPath, destFilePath);
|
||||||
|
|
||||||
// Copy media files fetched from content.
|
// Copy media files fetched from content.
|
||||||
NodeContentMediaUtils::copyMediaFiles(p_srcPath, getBackend().data(), destFilePath);
|
ContentMediaUtils::copyMediaFiles(p_srcPath, getBackend().data(), destFilePath);
|
||||||
|
|
||||||
// Create a file node.
|
// Create a file node.
|
||||||
auto currentTime = QDateTime::currentDateTimeUtc();
|
auto currentTime = QDateTime::currentDateTimeUtc();
|
||||||
|
@ -55,9 +55,13 @@ void SessionConfig::init()
|
|||||||
|
|
||||||
loadCore(sessionJobj);
|
loadCore(sessionJobj);
|
||||||
|
|
||||||
|
loadStateAndGeometry(sessionJobj);
|
||||||
|
|
||||||
if (MainConfig::isVersionChanged()) {
|
if (MainConfig::isVersionChanged()) {
|
||||||
doVersionSpecificOverride();
|
doVersionSpecificOverride();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_exportOption.fromJson(sessionJobj[QStringLiteral("export_option")].toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionConfig::loadCore(const QJsonObject &p_session)
|
void SessionConfig::loadCore(const QJsonObject &p_session)
|
||||||
@ -172,6 +176,7 @@ QJsonObject SessionConfig::toJson() const
|
|||||||
obj[QStringLiteral("core")] = saveCore();
|
obj[QStringLiteral("core")] = saveCore();
|
||||||
obj[QStringLiteral("notebooks")] = saveNotebooks();
|
obj[QStringLiteral("notebooks")] = saveNotebooks();
|
||||||
obj[QStringLiteral("state_geometry")] = saveStateAndGeometry();
|
obj[QStringLiteral("state_geometry")] = saveStateAndGeometry();
|
||||||
|
obj[QStringLiteral("export_option")] = m_exportOption.toJson();
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,22 +190,12 @@ QJsonObject SessionConfig::saveStateAndGeometry() const
|
|||||||
|
|
||||||
SessionConfig::MainWindowStateGeometry SessionConfig::getMainWindowStateGeometry() const
|
SessionConfig::MainWindowStateGeometry SessionConfig::getMainWindowStateGeometry() const
|
||||||
{
|
{
|
||||||
auto sessionSettings = getMgr()->getSettings(ConfigMgr::Source::Session);
|
return m_mainWindowStateGeometry;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionConfig::setMainWindowStateGeometry(const SessionConfig::MainWindowStateGeometry &p_state)
|
void SessionConfig::setMainWindowStateGeometry(const SessionConfig::MainWindowStateGeometry &p_state)
|
||||||
{
|
{
|
||||||
m_mainWindowStateGeometry = p_state;
|
updateConfig(m_mainWindowStateGeometry, p_state, this);
|
||||||
++m_revision;
|
|
||||||
writeToSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionConfig::OpenGL SessionConfig::getOpenGLAtBootstrap()
|
SessionConfig::OpenGL SessionConfig::getOpenGLAtBootstrap()
|
||||||
@ -283,3 +278,20 @@ void SessionConfig::doVersionSpecificOverride()
|
|||||||
// In a new version, we may want to change one value by force.
|
// 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.
|
// 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 <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
#include <export/exportdata.h>
|
||||||
|
|
||||||
namespace vnotex
|
namespace vnotex
|
||||||
{
|
{
|
||||||
class SessionConfig : public IConfig
|
class SessionConfig : public IConfig
|
||||||
@ -82,6 +84,9 @@ namespace vnotex
|
|||||||
int getMinimizeToSystemTray() const;
|
int getMinimizeToSystemTray() const;
|
||||||
void setMinimizeToSystemTray(bool p_enabled);
|
void setMinimizeToSystemTray(bool p_enabled);
|
||||||
|
|
||||||
|
const ExportOption &getExportOption() const;
|
||||||
|
void setExportOption(const ExportOption &p_option);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadCore(const QJsonObject &p_session);
|
void loadCore(const QJsonObject &p_session);
|
||||||
|
|
||||||
@ -91,6 +96,8 @@ namespace vnotex
|
|||||||
|
|
||||||
QJsonArray saveNotebooks() const;
|
QJsonArray saveNotebooks() const;
|
||||||
|
|
||||||
|
void loadStateAndGeometry(const QJsonObject &p_session);
|
||||||
|
|
||||||
QJsonObject saveStateAndGeometry() const;
|
QJsonObject saveStateAndGeometry() const;
|
||||||
|
|
||||||
void doVersionSpecificOverride();
|
void doVersionSpecificOverride();
|
||||||
@ -102,8 +109,6 @@ namespace vnotex
|
|||||||
|
|
||||||
QVector<SessionConfig::NotebookItem> m_notebooks;
|
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;
|
MainWindowStateGeometry m_mainWindowStateGeometry;
|
||||||
|
|
||||||
OpenGL m_openGL = OpenGL::None;
|
OpenGL m_openGL = OpenGL::None;
|
||||||
@ -116,6 +121,8 @@ namespace vnotex
|
|||||||
// 0 for disabling minimizing to system tray;
|
// 0 for disabling minimizing to system tray;
|
||||||
// 1 for enabling minimizing to system tray.
|
// 1 for enabling minimizing to system tray.
|
||||||
int m_minimizeToSystemTray = -1;
|
int m_minimizeToSystemTray = -1;
|
||||||
|
|
||||||
|
ExportOption m_exportOption;
|
||||||
};
|
};
|
||||||
} // ns vnotex
|
} // ns vnotex
|
||||||
|
|
||||||
|
@ -368,7 +368,12 @@ bool Theme::isRef(const QString &p_str)
|
|||||||
|
|
||||||
QString Theme::getFile(File p_fileType) const
|
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))) {
|
if (dir.exists(getFileName(p_fileType))) {
|
||||||
return dir.filePath(getFileName(p_fileType));
|
return dir.filePath(getFileName(p_fileType));
|
||||||
} else if (p_fileType == File::MarkdownEditorStyle) {
|
} else if (p_fileType == File::MarkdownEditorStyle) {
|
||||||
|
@ -54,6 +54,8 @@ namespace vnotex
|
|||||||
|
|
||||||
static QPixmap getCover(const QString &p_folder);
|
static QPixmap getCover(const QString &p_folder);
|
||||||
|
|
||||||
|
static QString getFile(const QString &p_themeFolder, File p_fileType);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Metadata
|
struct Metadata
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,8 @@ using namespace vnotex;
|
|||||||
|
|
||||||
QStringList ThemeMgr::s_searchPaths;
|
QStringList ThemeMgr::s_searchPaths;
|
||||||
|
|
||||||
|
QStringList ThemeMgr::s_webStylesSearchPaths;
|
||||||
|
|
||||||
ThemeMgr::ThemeMgr(const QString &p_currentThemeName, QObject *p_parent)
|
ThemeMgr::ThemeMgr(const QString &p_currentThemeName, QObject *p_parent)
|
||||||
: QObject(p_parent)
|
: QObject(p_parent)
|
||||||
{
|
{
|
||||||
@ -203,3 +205,39 @@ void ThemeMgr::refresh()
|
|||||||
{
|
{
|
||||||
loadAvailableThemes();
|
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.
|
// Won't affect current theme since we do not support changing theme real time for now.
|
||||||
void refresh();
|
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 addSearchPath(const QString &p_path);
|
||||||
|
|
||||||
static void addSyntaxHighlightingSearchPaths(const QStringList &p_paths);
|
static void addSyntaxHighlightingSearchPaths(const QStringList &p_paths);
|
||||||
|
|
||||||
|
static void addWebStylesSearchPath(const QString &p_path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadAvailableThemes();
|
void loadAvailableThemes();
|
||||||
|
|
||||||
@ -89,8 +95,11 @@ namespace vnotex
|
|||||||
// Set at runtime, not from the theme config.
|
// Set at runtime, not from the theme config.
|
||||||
QColor m_baseBackground;
|
QColor m_baseBackground;
|
||||||
|
|
||||||
// List of path to search for themes.
|
// List of paths to search for themes.
|
||||||
static QStringList s_searchPaths;
|
static QStringList s_searchPaths;
|
||||||
|
|
||||||
|
// List of paths to search for CSS styles, including CSS syntax highlighting styles.
|
||||||
|
static QStringList s_webStylesSearchPaths;
|
||||||
};
|
};
|
||||||
} // ns vnotex
|
} // ns vnotex
|
||||||
|
|
||||||
|
@ -46,6 +46,8 @@ void VNoteX::initThemeMgr()
|
|||||||
ThemeMgr::addSyntaxHighlightingSearchPaths(
|
ThemeMgr::addSyntaxHighlightingSearchPaths(
|
||||||
QStringList() << configMgr.getUserSyntaxHighlightingFolder()
|
QStringList() << configMgr.getUserSyntaxHighlightingFolder()
|
||||||
<< configMgr.getAppSyntaxHighlightingFolder());
|
<< configMgr.getAppSyntaxHighlightingFolder());
|
||||||
|
ThemeMgr::addWebStylesSearchPath(configMgr.getAppWebStylesFolder());
|
||||||
|
ThemeMgr::addWebStylesSearchPath(configMgr.getUserWebStylesFolder());
|
||||||
m_themeMgr = new ThemeMgr(configMgr.getCoreConfig().getTheme(), this);
|
m_themeMgr = new ThemeMgr(configMgr.getCoreConfig().getTheme(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +97,8 @@ namespace vnotex
|
|||||||
// Requested to locate node in explorer.
|
// Requested to locate node in explorer.
|
||||||
void locateNodeRequested(Node *p_node);
|
void locateNodeRequested(Node *p_node);
|
||||||
|
|
||||||
|
void exportRequested();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit VNoteX(QObject *p_parent = nullptr);
|
explicit VNoteX(QObject *p_parent = nullptr);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#ifndef VIEWERRESOURCE_H
|
#ifndef WEBRESOURCE_H
|
||||||
#define VIEWERRESOURCE_H
|
#define WEBRESOURCE_H
|
||||||
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
namespace vnotex
|
namespace vnotex
|
||||||
{
|
{
|
||||||
// Resource for Web viewer.
|
// Resource for Web.
|
||||||
struct ViewerResource
|
struct WebResource
|
||||||
{
|
{
|
||||||
struct Resource
|
struct Resource
|
||||||
{
|
{
|
||||||
@ -51,6 +51,11 @@ namespace vnotex
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isGlobal() const
|
||||||
|
{
|
||||||
|
return m_name == QStringLiteral("global_styles");
|
||||||
|
}
|
||||||
|
|
||||||
QString m_name;
|
QString m_name;
|
||||||
|
|
||||||
bool m_enabled = true;
|
bool m_enabled = true;
|
||||||
@ -96,4 +101,4 @@ namespace vnotex
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // VIEWERRESOURCE_H
|
#endif // WEBRESOURCE_H
|
@ -24,7 +24,8 @@
|
|||||||
"MaximizeSplit" : "Ctrl+G, Shift+\\",
|
"MaximizeSplit" : "Ctrl+G, Shift+\\",
|
||||||
"DistributeSplits" : "Ctrl+G, =",
|
"DistributeSplits" : "Ctrl+G, =",
|
||||||
"RemoveSplitAndWorkspace" : "Ctrl+G, R",
|
"RemoveSplitAndWorkspace" : "Ctrl+G, R",
|
||||||
"NewWorkspace" : "Ctrl+G, N"
|
"NewWorkspace" : "Ctrl+G, N",
|
||||||
|
"Export" : "Ctrl+G, T"
|
||||||
},
|
},
|
||||||
"toolbar_icon_size" : 16
|
"toolbar_icon_size" : 16
|
||||||
},
|
},
|
||||||
@ -83,7 +84,7 @@
|
|||||||
"markdown_editor" : {
|
"markdown_editor" : {
|
||||||
"override_viewer_resource" : true,
|
"override_viewer_resource" : true,
|
||||||
"viewer_resource" : {
|
"viewer_resource" : {
|
||||||
"template" : "web/markdownviewertemplate.html",
|
"template" : "web/markdown-viewer-template.html",
|
||||||
"resources" : [
|
"resources" : [
|
||||||
{
|
{
|
||||||
"name" : "global_styles",
|
"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",
|
"//comment" : "Whether use javascript or external program to render PlantUML",
|
||||||
"web_plantuml" : true,
|
"web_plantuml" : true,
|
||||||
"//comment" : "Whether use javascript or external program to render Graphviz",
|
"//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/about_vnotex.txt</file>
|
||||||
<file>docs/zh_CN/shortcuts.md</file>
|
<file>docs/zh_CN/shortcuts.md</file>
|
||||||
<file>docs/zh_CN/markdown_guide.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/globalstyles.css</file>
|
||||||
<file>web/css/markdownit.css</file>
|
<file>web/css/markdownit.css</file>
|
||||||
<file>web/css/imageviewer.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/qwebchannel.js</file>
|
||||||
<file>web/js/eventemitter.js</file>
|
<file>web/js/eventemitter.js</file>
|
||||||
<file>web/js/utils.js</file>
|
<file>web/js/utils.js</file>
|
||||||
@ -26,6 +29,7 @@
|
|||||||
<file>web/js/imageviewer.js</file>
|
<file>web/js/imageviewer.js</file>
|
||||||
<file>web/js/easyaccess.js</file>
|
<file>web/js/easyaccess.js</file>
|
||||||
<file>web/js/crosscopy.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-container.min.js</file>
|
||||||
<file>web/js/markdown-it/markdown-it-emoji.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>
|
<file>web/js/markdown-it/markdown-it-footnote.min.js</file>
|
||||||
|
@ -20,15 +20,15 @@ QWidget[DialogCentralWidget="true"] {
|
|||||||
|
|
||||||
/* All widgets */
|
/* All widgets */
|
||||||
*[State="info"] {
|
*[State="info"] {
|
||||||
border: 2px solid @widgets#qwidget#info#border;
|
border: 1px solid @widgets#qwidget#info#border;
|
||||||
}
|
}
|
||||||
|
|
||||||
*[State="warning"] {
|
*[State="warning"] {
|
||||||
border: 2px solid @widgets#qwidget#warning#border;
|
border: 1px solid @widgets#qwidget#warning#border;
|
||||||
}
|
}
|
||||||
|
|
||||||
*[State="error"] {
|
*[State="error"] {
|
||||||
border: 2px solid @widgets#qwidget#error#border;
|
border: 1px solid @widgets#qwidget#error#border;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* QAbstractScrollArea */
|
/* QAbstractScrollArea */
|
||||||
@ -430,6 +430,14 @@ QLineEdit:disabled {
|
|||||||
color: @widgets#qlineedit#disabled#fg;
|
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 */
|
||||||
QTabWidget {
|
QTabWidget {
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
"bg2_9" : "#919cd8",
|
"bg2_9" : "#919cd8",
|
||||||
"fg10" : "#b71c1c",
|
"fg10" : "#b71c1c",
|
||||||
"fg11" : "#ab5683",
|
"fg11" : "#ab5683",
|
||||||
"fg12" : "#283593",
|
"fg12" : "#5768c4",
|
||||||
"fg13" : "#b42b1f",
|
"fg13" : "#b42b1f",
|
||||||
"fg15_3" : "#4f5666",
|
"fg15_3" : "#4f5666",
|
||||||
"fg15_4" : "#60697c",
|
"fg15_4" : "#60697c",
|
||||||
|
@ -10,15 +10,15 @@
|
|||||||
|
|
||||||
/* All widgets */
|
/* All widgets */
|
||||||
*[State="info"] {
|
*[State="info"] {
|
||||||
border: 2px solid @base#info#fg;
|
border: 1px solid @base#info#fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
*[State="warning"] {
|
*[State="warning"] {
|
||||||
border: 2px solid @base#warning#fg;
|
border: 1px solid @base#warning#fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
*[State="error"] {
|
*[State="error"] {
|
||||||
border: 2px solid @base#error#fg;
|
border: 1px solid @base#error#fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ToolBox */
|
/* 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 {
|
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
background-color: #1976d2;
|
background-color: #1976d2;
|
||||||
color: #f1f1f1;
|
color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
background-color: #1976d2;
|
background-color: #1976d2;
|
||||||
color: #f1f1f1;
|
color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
|
@ -20,15 +20,15 @@ QWidget[DialogCentralWidget="true"] {
|
|||||||
|
|
||||||
/* All widgets */
|
/* All widgets */
|
||||||
*[State="info"] {
|
*[State="info"] {
|
||||||
border: 2px solid @widgets#qwidget#info#border;
|
border: 1px solid @widgets#qwidget#info#border;
|
||||||
}
|
}
|
||||||
|
|
||||||
*[State="warning"] {
|
*[State="warning"] {
|
||||||
border: 2px solid @widgets#qwidget#warning#border;
|
border: 1px solid @widgets#qwidget#warning#border;
|
||||||
}
|
}
|
||||||
|
|
||||||
*[State="error"] {
|
*[State="error"] {
|
||||||
border: 2px solid @widgets#qwidget#error#border;
|
border: 1px solid @widgets#qwidget#error#border;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* QAbstractScrollArea */
|
/* QAbstractScrollArea */
|
||||||
@ -430,6 +430,14 @@ QLineEdit:disabled {
|
|||||||
color: @widgets#qlineedit#disabled#fg;
|
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 */
|
||||||
QTabWidget {
|
QTabWidget {
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"bg3_4" : "#dadada",
|
"bg3_4" : "#dadada",
|
||||||
"bg3_41" : "#e0e0e0",
|
"bg3_41" : "#e0e0e0",
|
||||||
"bg3_5" : "#eaeaea",
|
"bg3_5" : "#eaeaea",
|
||||||
"bg3_6" : "#f1f1f1",
|
"bg3_6" : "#f5f5f5",
|
||||||
"fg3_5" : "#222222",
|
"fg3_5" : "#222222",
|
||||||
"fg3_6" : "#646464",
|
"fg3_6" : "#646464",
|
||||||
"fg3_7" : "#7a7a7a",
|
"fg3_7" : "#7a7a7a",
|
||||||
@ -33,7 +33,7 @@
|
|||||||
"bg2_7" : "#e5f3f1",
|
"bg2_7" : "#e5f3f1",
|
||||||
"fg10" : "#b71c1c",
|
"fg10" : "#b71c1c",
|
||||||
"fg11" : "#ab5683",
|
"fg11" : "#ab5683",
|
||||||
"fg12" : "#283593",
|
"fg12" : "#007b6e",
|
||||||
"fg13" : "#b42b1f",
|
"fg13" : "#b42b1f",
|
||||||
"fg15_3" : "#b0b0b0",
|
"fg15_3" : "#b0b0b0",
|
||||||
"fg15_4" : "#7a7a7a",
|
"fg15_4" : "#7a7a7a",
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
"font-family" : "YaHei Consolas Hybrid, Consolas, Monaco, Andale Mono, Monospace, Courier New",
|
"font-family" : "YaHei Consolas Hybrid, Consolas, Monaco, Andale Mono, Monospace, Courier New",
|
||||||
"font-size" : 12,
|
"font-size" : 12,
|
||||||
"text-color" : "#222222",
|
"text-color" : "#222222",
|
||||||
"background-color" : "#f1f1f1",
|
"background-color" : "#f5f5f5",
|
||||||
"selected-text-color" : "#f1f1f1",
|
"selected-text-color" : "#f5f5f5",
|
||||||
"selected-background-color" : "#1976d2"
|
"selected-background-color" : "#1976d2"
|
||||||
},
|
},
|
||||||
"CursorLine" : {
|
"CursorLine" : {
|
||||||
@ -30,7 +30,7 @@
|
|||||||
},
|
},
|
||||||
"IndicatorsBorder" : {
|
"IndicatorsBorder" : {
|
||||||
"text-color" : "#aaaaaa",
|
"text-color" : "#aaaaaa",
|
||||||
"background-color" : "#ededed"
|
"background-color" : "#f1f1f1"
|
||||||
},
|
},
|
||||||
"CurrentLineNumber" : {
|
"CurrentLineNumber" : {
|
||||||
"text-color" : "#222222"
|
"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-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,
|
"font-size" : 12,
|
||||||
"text-color" : "#222222",
|
"text-color" : "#222222",
|
||||||
"background-color" : "#f1f1f1",
|
"background-color" : "#f5f5f5",
|
||||||
"selected-text-color" : "#f1f1f1",
|
"selected-text-color" : "#f5f5f5",
|
||||||
"selected-background-color" : "#1976d2"
|
"selected-background-color" : "#1976d2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,7 @@ body {
|
|||||||
color: #222222;
|
color: #222222;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
background-color: #f1f1f1;
|
background-color: #f5f5f5;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ div.vx-plantuml-graph {
|
|||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background-color: #1976d2;
|
background-color: #1976d2;
|
||||||
color: #f1f1f1;
|
color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-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) ". ";
|
content: counter(section1) "." counter(section2) "." counter(section3) "." counter(section4) "." counter(section5) "." counter(section6) ". ";
|
||||||
}
|
}
|
||||||
|
|
||||||
#vx-content.vx-constrain-image-width img {
|
#vx-content.vx-constrain-image-width img,
|
||||||
max-width: 100%;
|
#vx-content.vx-constrain-image-width div.vx-plantuml-graph > svg,
|
||||||
height: auto;
|
#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 */
|
/* Table of Contents */
|
||||||
@ -133,3 +137,23 @@
|
|||||||
#vx-content.vx-indent-first-line p {
|
#vx-content.vx-indent-first-line p {
|
||||||
text-indent: 2em;
|
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() {
|
registerInternal() {
|
||||||
this.vnotex.on('basicMarkdownRendered', () => {
|
this.vnotex.on('basicMarkdownRendered', () => {
|
||||||
this.reset();
|
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);
|
this.vnotex.getWorker('markdownit').addLangsToSkipHighlight(this.langs);
|
||||||
|
@ -240,10 +240,6 @@ class MarkdownIt extends VxWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerInternal() {
|
registerInternal() {
|
||||||
this.vnotex.on('ready', () => {
|
|
||||||
this.setConstrainImageWidthEnabled(window.vxOptions.constrainImageWidthEnabled);
|
|
||||||
this.setIndentFirstLineEnabled(window.vxOptions.indentFirstLineEnabled);
|
|
||||||
});
|
|
||||||
this.vnotex.on('markdownTextUpdated', (p_text) => {
|
this.vnotex.on('markdownTextUpdated', (p_text) => {
|
||||||
this.render(this.vnotex.contentContainer,
|
this.render(this.vnotex.contentContainer,
|
||||||
p_text,
|
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.
|
// Render Markdown @p_text to HTML in @p_node.
|
||||||
// @p_finishCbStr will be called after finishing loading new content nodes.
|
// @p_finishCbStr will be called after finishing loading new content nodes.
|
||||||
// This could prevent Mermaid Gantt from negative width error.
|
// This could prevent Mermaid Gantt from negative width error.
|
||||||
|
@ -43,6 +43,10 @@ new QWebChannel(qt.webChannelTransport,
|
|||||||
window.vnotex.findText(p_text, p_options);
|
window.vnotex.findText(p_text, p_options);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
adapter.contentRequested.connect(function() {
|
||||||
|
window.vnotex.saveContent();
|
||||||
|
});
|
||||||
|
|
||||||
console.log('QWebChannel has been set up');
|
console.log('QWebChannel has been set up');
|
||||||
if (window.vnotex.initialized) {
|
if (window.vnotex.initialized) {
|
||||||
window.vnotex.kickOffMarkdown();
|
window.vnotex.kickOffMarkdown();
|
||||||
|
@ -18,7 +18,8 @@ window.MathJax = {
|
|||||||
},
|
},
|
||||||
svg: {
|
svg: {
|
||||||
// Make SVG self-contained.
|
// 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() {
|
registerInternal() {
|
||||||
this.vnotex.on('basicMarkdownRendered', () => {
|
this.vnotex.on('basicMarkdownRendered', () => {
|
||||||
this.reset();
|
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);
|
this.vnotex.getWorker('markdownit').addLangsToSkipHighlight(this.langs);
|
||||||
|
@ -113,4 +113,55 @@ class Utils {
|
|||||||
static headingSequenceRegExp() {
|
static headingSequenceRegExp() {
|
||||||
return /^\d{1,3}(?:\.\d+)*\. /;
|
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.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;
|
this.initialized = true;
|
||||||
|
|
||||||
// Signal out.
|
// 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) {
|
registerWorker(p_worker) {
|
||||||
this.workers.set(p_worker.name, p_worker);
|
this.workers.set(p_worker.name, p_worker);
|
||||||
|
|
||||||
@ -79,6 +107,7 @@ class VNoteX extends EventEmitter {
|
|||||||
if (this.numOfOngoingWorkers == 0) {
|
if (this.numOfOngoingWorkers == 0) {
|
||||||
// Signal out anyway.
|
// Signal out anyway.
|
||||||
this.emit('fullMarkdownRendered');
|
this.emit('fullMarkdownRendered');
|
||||||
|
window.vxMarkdownAdapter.setWorkFinished();
|
||||||
|
|
||||||
// Check pending work.
|
// Check pending work.
|
||||||
if (this.pendingData.text) {
|
if (this.pendingData.text) {
|
||||||
@ -211,13 +240,8 @@ class VNoteX extends EventEmitter {
|
|||||||
setSectionNumberEnabled(p_enabled) {
|
setSectionNumberEnabled(p_enabled) {
|
||||||
let sectionClass = 'vx-section-number';
|
let sectionClass = 'vx-section-number';
|
||||||
let sectionLevelClass = 'vx-section-number-' + this.sectionNumberBaseLevel;
|
let sectionLevelClass = 'vx-section-number-' + this.sectionNumberBaseLevel;
|
||||||
if (p_enabled) {
|
this.setContentContainerOption(sectionClass, p_enabled);
|
||||||
this.contentContainer.classList.add(sectionClass);
|
this.setContentContainerOption(sectionLevelClass, p_enabled);
|
||||||
this.contentContainer.classList.add(sectionLevelClass);
|
|
||||||
} else {
|
|
||||||
this.contentContainer.classList.remove(sectionClass);
|
|
||||||
this.contentContainer.classList.remove(sectionLevelClass);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scroll(p_up) {
|
scroll(p_up) {
|
||||||
@ -261,6 +285,28 @@ class VNoteX extends EventEmitter {
|
|||||||
window.vxMarkdownAdapter.setFindText(p_text, p_totalMatches, p_currentMatchIndex);
|
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() {
|
static detectOS() {
|
||||||
let osName="Unknown OS";
|
let osName="Unknown OS";
|
||||||
if (navigator.appVersion.indexOf("Win")!=-1) {
|
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/utils/utils.pri)
|
||||||
|
|
||||||
|
include($$PWD/export/export.pri)
|
||||||
|
|
||||||
include($$PWD/core/core.pri)
|
include($$PWD/core/core.pri)
|
||||||
|
|
||||||
include($$PWD/widgets/widgets.pri)
|
include($$PWD/widgets/widgets.pri)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "nodecontentmediautils.h"
|
#include "contentmediautils.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
@ -15,46 +15,53 @@
|
|||||||
|
|
||||||
#include <utils/pathutils.h>
|
#include <utils/pathutils.h>
|
||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
|
#include <core/file.h>
|
||||||
|
|
||||||
using namespace vnotex;
|
using namespace vnotex;
|
||||||
|
|
||||||
void NodeContentMediaUtils::copyMediaFiles(const Node *p_node,
|
void ContentMediaUtils::copyMediaFiles(Node *p_node,
|
||||||
INotebookBackend *p_backend,
|
INotebookBackend *p_backend,
|
||||||
const QString &p_destFilePath)
|
const QString &p_destFilePath)
|
||||||
{
|
{
|
||||||
Q_ASSERT(p_node->hasContent());
|
Q_ASSERT(p_node->hasContent());
|
||||||
/*
|
auto file = p_node->getContentFile();
|
||||||
const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath());
|
if (file->getContentType().isMarkdown()) {
|
||||||
if (fileType.m_type == FileTypeHelper::Markdown) {
|
copyMarkdownMediaFiles(file->read(),
|
||||||
copyMarkdownMediaFiles(p_node->read(),
|
PathUtils::parentDirPath(file->getContentPath()),
|
||||||
PathUtils::parentDirPath(p_node->fetchContentPath()),
|
|
||||||
p_backend,
|
p_backend,
|
||||||
p_destFilePath);
|
p_destFilePath);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeContentMediaUtils::copyMediaFiles(const QString &p_filePath,
|
void ContentMediaUtils::copyMediaFiles(const QString &p_filePath,
|
||||||
INotebookBackend *p_backend,
|
INotebookBackend *p_backend,
|
||||||
const QString &p_destFilePath)
|
const QString &p_destFilePath)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
const auto &fileType = FileTypeHelper::getInst().getFileType(p_filePath);
|
const auto &fileType = FileTypeHelper::getInst().getFileType(p_filePath);
|
||||||
if (fileType.m_type == FileTypeHelper::Markdown) {
|
if (fileType.isMarkdown()) {
|
||||||
copyMarkdownMediaFiles(FileUtils::readTextFile(p_filePath),
|
copyMarkdownMediaFiles(FileUtils::readTextFile(p_filePath),
|
||||||
PathUtils::parentDirPath(p_filePath),
|
PathUtils::parentDirPath(p_filePath),
|
||||||
p_backend,
|
p_backend,
|
||||||
p_destFilePath);
|
p_destFilePath);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
|
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,
|
const QString &p_basePath,
|
||||||
INotebookBackend *p_backend,
|
INotebookBackend *p_backend,
|
||||||
const QString &p_destFilePath)
|
const QString &p_destFilePath)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
auto content = p_content;
|
auto content = p_content;
|
||||||
|
|
||||||
// Images.
|
// Images.
|
||||||
@ -82,19 +89,20 @@ void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
|
|||||||
handledImages.insert(link.m_path);
|
handledImages.insert(link.m_path);
|
||||||
|
|
||||||
if (!QFileInfo::exists(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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the relative path of the image and apply it to the dest file path.
|
// Get the relative path of the image and apply it to the dest file path.
|
||||||
const auto oldDestFilePath = destDir.filePath(link.m_urlInLink);
|
const auto oldDestFilePath = destDir.filePath(link.m_urlInLink);
|
||||||
destDir.mkpath(PathUtils::parentDirPath(oldDestFilePath));
|
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) {
|
if (oldDestFilePath != destFilePath) {
|
||||||
// Rename happens.
|
// Rename happens.
|
||||||
const auto oldFileName = PathUtils::fileName(oldDestFilePath);
|
const auto oldFileName = PathUtils::fileName(oldDestFilePath);
|
||||||
const auto newFileName = PathUtils::fileName(destFilePath);
|
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.
|
// Update the text content.
|
||||||
auto newUrlInLink(link.m_urlInLink);
|
auto newUrlInLink(link.m_urlInLink);
|
||||||
@ -106,38 +114,41 @@ void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content,
|
|||||||
renamedImages.insert(link.m_path, newUrlInLink);
|
renamedImages.insert(link.m_path, newUrlInLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (p_backend) {
|
||||||
p_backend->copyFile(link.m_path, destFilePath);
|
p_backend->copyFile(link.m_path, destFilePath);
|
||||||
|
} else {
|
||||||
|
FileUtils::copyFile(link.m_path, destFilePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!renamedImages.isEmpty()) {
|
if (!renamedImages.isEmpty()) {
|
||||||
|
if (p_backend) {
|
||||||
p_backend->writeFile(p_destFilePath, content);
|
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->hasContent());
|
||||||
Q_ASSERT(p_node->getType() == Node::Type::File);
|
auto file = p_node->getContentFile();
|
||||||
const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath());
|
if (file->getContentType().isMarkdown()) {
|
||||||
if (fileType.m_type == FileTypeHelper::Markdown) {
|
removeMarkdownMediaFiles(file.data(), p_node->getBackend());
|
||||||
removeMarkdownMediaFiles(p_node);
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeContentMediaUtils::removeMarkdownMediaFiles(const Node *p_node)
|
void ContentMediaUtils::removeMarkdownMediaFiles(const File *p_file, INotebookBackend *p_backend)
|
||||||
{
|
{
|
||||||
/*
|
auto content = p_file->read();
|
||||||
auto content = p_node->read();
|
|
||||||
|
|
||||||
// Images.
|
// Images.
|
||||||
const auto images =
|
const auto images =
|
||||||
vte::MarkdownUtils::fetchImagesFromMarkdownText(content,
|
vte::MarkdownUtils::fetchImagesFromMarkdownText(content,
|
||||||
PathUtils::parentDirPath(p_node->fetchContentPath()),
|
p_file->getResourcePath(),
|
||||||
vte::MarkdownLink::TypeFlag::LocalRelativeInternal);
|
vte::MarkdownLink::TypeFlag::LocalRelativeInternal);
|
||||||
|
|
||||||
auto backend = p_node->getBackend();
|
|
||||||
QSet<QString> handledImages;
|
QSet<QString> handledImages;
|
||||||
for (const auto &link : images) {
|
for (const auto &link : images) {
|
||||||
if (handledImages.contains(link.m_path)) {
|
if (handledImages.contains(link.m_path)) {
|
||||||
@ -150,37 +161,39 @@ void NodeContentMediaUtils::removeMarkdownMediaFiles(const Node *p_node)
|
|||||||
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;
|
continue;
|
||||||
}
|
}
|
||||||
backend->removeFile(link.m_path);
|
p_backend->removeFile(link.m_path);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeContentMediaUtils::copyAttachment(Node *p_node,
|
void ContentMediaUtils::copyAttachment(Node *p_node,
|
||||||
INotebookBackend *p_backend,
|
INotebookBackend *p_backend,
|
||||||
const QString &p_destFilePath,
|
const QString &p_destFilePath,
|
||||||
const QString &p_destAttachmentFolderPath)
|
const QString &p_destAttachmentFolderPath)
|
||||||
{
|
{
|
||||||
/*
|
Q_ASSERT(p_node->hasContent());
|
||||||
Q_ASSERT(p_node->getType() == Node::Type::File);
|
|
||||||
Q_ASSERT(!p_node->getAttachmentFolder().isEmpty());
|
Q_ASSERT(!p_node->getAttachmentFolder().isEmpty());
|
||||||
|
|
||||||
// Copy the whole folder.
|
// Copy the whole folder.
|
||||||
const auto srcAttachmentFolderPath = p_node->fetchAttachmentFolderPath();
|
const auto srcAttachmentFolderPath = p_node->fetchAttachmentFolderPath();
|
||||||
|
if (p_backend) {
|
||||||
p_backend->copyDir(srcAttachmentFolderPath, p_destAttachmentFolderPath);
|
p_backend->copyDir(srcAttachmentFolderPath, p_destAttachmentFolderPath);
|
||||||
|
} else {
|
||||||
|
FileUtils::copyDir(srcAttachmentFolderPath, p_destAttachmentFolderPath);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we need to modify links in content.
|
// Check if we need to modify links in content.
|
||||||
|
// FIXME: check the whole relative path.
|
||||||
if (p_node->getAttachmentFolder() == PathUtils::dirName(p_destAttachmentFolderPath)) {
|
if (p_node->getAttachmentFolder() == PathUtils::dirName(p_destAttachmentFolderPath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath());
|
auto file = p_node->getContentFile();
|
||||||
if (fileType.m_type == FileTypeHelper::Markdown) {
|
if (file->getContentType().isMarkdown()) {
|
||||||
fixMarkdownLinks(srcAttachmentFolderPath, p_backend, p_destFilePath, p_destAttachmentFolderPath);
|
fixMarkdownLinks(srcAttachmentFolderPath, p_backend, p_destFilePath, p_destAttachmentFolderPath);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeContentMediaUtils::fixMarkdownLinks(const QString &p_srcFolderPath,
|
void ContentMediaUtils::fixMarkdownLinks(const QString &p_srcFolderPath,
|
||||||
INotebookBackend *p_backend,
|
INotebookBackend *p_backend,
|
||||||
const QString &p_destFilePath,
|
const QString &p_destFilePath,
|
||||||
const QString &p_destFolderPath)
|
const QString &p_destFolderPath)
|
@ -1,5 +1,5 @@
|
|||||||
#ifndef NODECONTENTMEDIAUTILS_H
|
#ifndef CONTENTMEDIAUTILS_H
|
||||||
#define NODECONTENTMEDIAUTILS_H
|
#define CONTENTMEDIAUTILS_H
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
@ -7,16 +7,17 @@ namespace vnotex
|
|||||||
{
|
{
|
||||||
class INotebookBackend;
|
class INotebookBackend;
|
||||||
class Node;
|
class Node;
|
||||||
|
class File;
|
||||||
|
|
||||||
// Utils to operate on the media files from node's content.
|
// Utils to operate on the media files from node's content.
|
||||||
class NodeContentMediaUtils
|
class ContentMediaUtils
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NodeContentMediaUtils() = delete;
|
ContentMediaUtils() = delete;
|
||||||
|
|
||||||
// Fetch media files from @p_node and copy them to dest folder.
|
// Fetch media files from @p_node and copy them to dest folder.
|
||||||
// @p_destFilePath: @p_node has been copied to @p_destFilePath.
|
// @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,
|
INotebookBackend *p_backend,
|
||||||
const QString &p_destFilePath);
|
const QString &p_destFilePath);
|
||||||
|
|
||||||
@ -25,7 +26,10 @@ namespace vnotex
|
|||||||
INotebookBackend *p_backend,
|
INotebookBackend *p_backend,
|
||||||
const QString &p_destFilePath);
|
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.
|
// Copy attachment folder.
|
||||||
static void copyAttachment(Node *p_node,
|
static void copyAttachment(Node *p_node,
|
||||||
@ -39,7 +43,7 @@ namespace vnotex
|
|||||||
INotebookBackend *p_backend,
|
INotebookBackend *p_backend,
|
||||||
const QString &p_destFilePath);
|
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.
|
// Fix local relative internal links locating in @p_srcFolderPath.
|
||||||
static void fixMarkdownLinks(const QString &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,
|
static QString generateFileNameWithSequence(const QString &p_folderPath,
|
||||||
const QString &p_baseName,
|
const QString &p_baseName,
|
||||||
const QString &p_suffix);
|
const QString &p_suffix = QString());
|
||||||
|
|
||||||
static QTemporaryFile *createTemporaryFile(const QString &p_suffix);
|
static QTemporaryFile *createTemporaryFile(const QString &p_suffix);
|
||||||
|
|
||||||
|
@ -10,3 +10,9 @@ bool HtmlUtils::hasOnlyImgTag(const QString &p_html)
|
|||||||
QRegExp reg(QStringLiteral("<(?:p|span|div) "));
|
QRegExp reg(QStringLiteral("<(?:p|span|div) "));
|
||||||
return !p_html.contains(reg);
|
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;
|
HtmlUtils() = delete;
|
||||||
|
|
||||||
static bool hasOnlyImgTag(const QString &p_html);
|
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;
|
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.
|
// Unindent multi-lines text according to the indentation of the first line.
|
||||||
static QString unindentTextMultiLines(const QString &p_text);
|
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
|
QT += widgets svg
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
$$PWD/contentmediautils.cpp \
|
||||||
$$PWD/docsutils.cpp \
|
$$PWD/docsutils.cpp \
|
||||||
$$PWD/htmlutils.cpp \
|
$$PWD/htmlutils.cpp \
|
||||||
$$PWD/pathutils.cpp \
|
$$PWD/pathutils.cpp \
|
||||||
|
$$PWD/processutils.cpp \
|
||||||
$$PWD/textutils.cpp \
|
$$PWD/textutils.cpp \
|
||||||
$$PWD/urldragdroputils.cpp \
|
$$PWD/urldragdroputils.cpp \
|
||||||
$$PWD/utils.cpp \
|
$$PWD/utils.cpp \
|
||||||
$$PWD/fileutils.cpp \
|
$$PWD/fileutils.cpp \
|
||||||
$$PWD/iconutils.cpp \
|
$$PWD/iconutils.cpp \
|
||||||
|
$$PWD/webutils.cpp \
|
||||||
$$PWD/widgetutils.cpp \
|
$$PWD/widgetutils.cpp \
|
||||||
$$PWD/clipboardutils.cpp
|
$$PWD/clipboardutils.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
$$PWD/contentmediautils.h \
|
||||||
$$PWD/docsutils.h \
|
$$PWD/docsutils.h \
|
||||||
$$PWD/htmlutils.h \
|
$$PWD/htmlutils.h \
|
||||||
$$PWD/pathutils.h \
|
$$PWD/pathutils.h \
|
||||||
|
$$PWD/processutils.h \
|
||||||
$$PWD/textutils.h \
|
$$PWD/textutils.h \
|
||||||
$$PWD/urldragdroputils.h \
|
$$PWD/urldragdroputils.h \
|
||||||
$$PWD/utils.h \
|
$$PWD/utils.h \
|
||||||
$$PWD/fileutils.h \
|
$$PWD/fileutils.h \
|
||||||
$$PWD/iconutils.h \
|
$$PWD/iconutils.h \
|
||||||
|
$$PWD/webutils.h \
|
||||||
$$PWD/widgetutils.h \
|
$$PWD/widgetutils.h \
|
||||||
$$PWD/clipboardutils.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 <QFontDatabase>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFormLayout>
|
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
|
|
||||||
using namespace vnotex;
|
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)
|
void WidgetUtils::selectBaseName(QLineEdit *p_lineEdit)
|
||||||
{
|
{
|
||||||
auto text = p_lineEdit->text();
|
auto text = p_lineEdit->text();
|
||||||
|
@ -17,7 +17,6 @@ class QScrollArea;
|
|||||||
class QListView;
|
class QListView;
|
||||||
class QMenu;
|
class QMenu;
|
||||||
class QShortcut;
|
class QShortcut;
|
||||||
class QFormLayout;
|
|
||||||
class QLineEdit;
|
class QLineEdit;
|
||||||
|
|
||||||
namespace vnotex
|
namespace vnotex
|
||||||
@ -79,8 +78,6 @@ namespace vnotex
|
|||||||
|
|
||||||
static void insertActionAfter(QMenu *p_menu, QAction *p_after, QAction *p_action);
|
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.
|
// Select the base name part of the line edit content.
|
||||||
static void selectBaseName(QLineEdit *p_lineEdit);
|
static void selectBaseName(QLineEdit *p_lineEdit);
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include <utils/widgetutils.h>
|
#include <utils/widgetutils.h>
|
||||||
#include "../propertydefs.h"
|
#include "../propertydefs.h"
|
||||||
|
#include "../widgetsfactory.h"
|
||||||
|
|
||||||
using namespace vnotex;
|
using namespace vnotex;
|
||||||
|
|
||||||
@ -29,6 +30,11 @@ void Dialog::setCentralWidget(QWidget *p_widget)
|
|||||||
m_layout->addWidget(m_centralWidget);
|
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,
|
void Dialog::setDialogButtonBox(QDialogButtonBox::StandardButtons p_buttons,
|
||||||
QDialogButtonBox::StandardButton p_defaultButton)
|
QDialogButtonBox::StandardButton p_defaultButton)
|
||||||
{
|
{
|
||||||
@ -38,7 +44,8 @@ void Dialog::setDialogButtonBox(QDialogButtonBox::StandardButtons p_buttons,
|
|||||||
m_dialogButtonBox = new QDialogButtonBox(p_buttons, this);
|
m_dialogButtonBox = new QDialogButtonBox(p_buttons, this);
|
||||||
connect(m_dialogButtonBox, &QDialogButtonBox::accepted,
|
connect(m_dialogButtonBox, &QDialogButtonBox::accepted,
|
||||||
this, &Dialog::acceptedButtonClicked);
|
this, &Dialog::acceptedButtonClicked);
|
||||||
connect(m_dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
connect(m_dialogButtonBox, &QDialogButtonBox::rejected,
|
||||||
|
this, &Dialog::rejectedButtonClicked);
|
||||||
connect(m_dialogButtonBox, &QDialogButtonBox::clicked,
|
connect(m_dialogButtonBox, &QDialogButtonBox::clicked,
|
||||||
this, [this](QAbstractButton *p_button) {
|
this, [this](QAbstractButton *p_button) {
|
||||||
switch (m_dialogButtonBox->buttonRole(p_button)) {
|
switch (m_dialogButtonBox->buttonRole(p_button)) {
|
||||||
@ -80,13 +87,13 @@ void Dialog::setInformationText(const QString &p_text, InformationLevel p_level)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_infoTextEdit = new QPlainTextEdit(this);
|
m_infoTextEdit = WidgetsFactory::createPlainTextConsole(this);
|
||||||
m_infoTextEdit->setReadOnly(true);
|
|
||||||
m_infoTextEdit->setMaximumHeight(m_infoTextEdit->minimumSizeHint().height());
|
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->setPlainText(p_text);
|
||||||
|
m_infoTextEdit->ensureCursorVisible();
|
||||||
|
|
||||||
const bool visible = !p_text.isEmpty();
|
const bool visible = !p_text.isEmpty();
|
||||||
const bool needResize = visible != m_infoTextEdit->isVisible();
|
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()
|
void Dialog::acceptedButtonClicked()
|
||||||
{
|
{
|
||||||
QDialog::accept();
|
QDialog::accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Dialog::rejectedButtonClicked()
|
||||||
|
{
|
||||||
|
QDialog::reject();
|
||||||
|
}
|
||||||
|
|
||||||
void Dialog::resetButtonClicked()
|
void Dialog::resetButtonClicked()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@ namespace vnotex
|
|||||||
public:
|
public:
|
||||||
explicit Dialog(QWidget *p_parent = nullptr, Qt::WindowFlags p_flags = Qt::WindowFlags());
|
explicit Dialog(QWidget *p_parent = nullptr, Qt::WindowFlags p_flags = Qt::WindowFlags());
|
||||||
|
|
||||||
virtual void setCentralWidget(QWidget *p_widget);
|
|
||||||
|
|
||||||
void setDialogButtonBox(QDialogButtonBox::StandardButtons p_buttons,
|
void setDialogButtonBox(QDialogButtonBox::StandardButtons p_buttons,
|
||||||
QDialogButtonBox::StandardButton p_defaultButton = QDialogButtonBox::NoButton);
|
QDialogButtonBox::StandardButton p_defaultButton = QDialogButtonBox::NoButton);
|
||||||
|
|
||||||
@ -31,6 +29,10 @@ namespace vnotex
|
|||||||
|
|
||||||
void setInformationText(const QString &p_text, InformationLevel p_level = InformationLevel::Info);
|
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);
|
void setButtonEnabled(QDialogButtonBox::StandardButton p_button, bool p_enabled);
|
||||||
|
|
||||||
// Dialog has completed but just stay the GUI to let user know information.
|
// Dialog has completed but just stay the GUI to let user know information.
|
||||||
@ -41,10 +43,17 @@ namespace vnotex
|
|||||||
protected:
|
protected:
|
||||||
virtual void acceptedButtonClicked();
|
virtual void acceptedButtonClicked();
|
||||||
|
|
||||||
|
virtual void rejectedButtonClicked();
|
||||||
|
|
||||||
virtual void resetButtonClicked();
|
virtual void resetButtonClicked();
|
||||||
|
|
||||||
virtual void appliedButtonClicked();
|
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;
|
QBoxLayout *m_layout = nullptr;
|
||||||
|
|
||||||
QWidget *m_centralWidget = 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);
|
auto widget = new QWidget(this);
|
||||||
setCentralWidget(widget);
|
setCentralWidget(widget);
|
||||||
|
|
||||||
auto mainLayout = WidgetUtils::createFormLayout(widget);
|
auto mainLayout = WidgetsFactory::createFormLayout(widget);
|
||||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
const QFileInfo info(m_path);
|
const QFileInfo info(m_path);
|
||||||
|
@ -35,7 +35,7 @@ FolderFilesFilterWidget::FolderFilesFilterWidget(QWidget *p_parent)
|
|||||||
|
|
||||||
void FolderFilesFilterWidget::setupUI()
|
void FolderFilesFilterWidget::setupUI()
|
||||||
{
|
{
|
||||||
auto mainLayout = WidgetUtils::createFormLayout(this);
|
auto mainLayout = WidgetsFactory::createFormLayout(this);
|
||||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ void LinkInsertDialog::setupUI(const QString &p_title,
|
|||||||
auto mainWidget = new QWidget(this);
|
auto mainWidget = new QWidget(this);
|
||||||
setCentralWidget(mainWidget);
|
setCentralWidget(mainWidget);
|
||||||
|
|
||||||
auto mainLayout = WidgetUtils::createFormLayout(mainWidget);
|
auto mainLayout = WidgetsFactory::createFormLayout(mainWidget);
|
||||||
|
|
||||||
m_linkTextEdit = WidgetsFactory::createLineEdit(p_linkText, mainWidget);
|
m_linkTextEdit = WidgetsFactory::createLineEdit(p_linkText, mainWidget);
|
||||||
mainLayout->addRow(tr("&Text:"), m_linkTextEdit);
|
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 createMode = m_mode == Mode::Create;
|
||||||
const bool isNote = p_newNodeFlags & Node::Flag::Content;
|
const bool isNote = p_newNodeFlags & Node::Flag::Content;
|
||||||
|
|
||||||
m_mainLayout = WidgetUtils::createFormLayout(this);
|
m_mainLayout = WidgetsFactory::createFormLayout(this);
|
||||||
|
|
||||||
m_mainLayout->addRow(tr("Notebook:"),
|
m_mainLayout->addRow(tr("Notebook:"),
|
||||||
new QLabel(p_parentNode->getNotebook()->getName(), this));
|
new QLabel(p_parentNode->getNotebook()->getName(), this));
|
||||||
@ -84,7 +84,7 @@ void NodeInfoWidget::setupNameLineEdit(QWidget *p_parent)
|
|||||||
const auto &fileType = FileTypeHelper::getInst().getFileTypeBySuffix(suffix);
|
const auto &fileType = FileTypeHelper::getInst().getFileTypeBySuffix(suffix);
|
||||||
typeName = fileType.m_typeName;
|
typeName = fileType.m_typeName;
|
||||||
} else {
|
} else {
|
||||||
typeName = FileTypeHelper::getInst().getFileType(FileTypeHelper::Others).m_typeName;
|
typeName = FileTypeHelper::getInst().getFileType(FileType::Others).m_typeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
int idx = m_fileTypeComboBox->findData(typeName);
|
int idx = m_fileTypeComboBox->findData(typeName);
|
||||||
|
@ -44,7 +44,7 @@ void NotebookInfoWidget::setupUI()
|
|||||||
QGroupBox *NotebookInfoWidget::setupBasicInfoGroupBox(QWidget *p_parent)
|
QGroupBox *NotebookInfoWidget::setupBasicInfoGroupBox(QWidget *p_parent)
|
||||||
{
|
{
|
||||||
auto box = new QGroupBox(tr("Basic Information"), p_parent);
|
auto box = new QGroupBox(tr("Basic Information"), p_parent);
|
||||||
auto mainLayout = WidgetUtils::createFormLayout(box);
|
auto mainLayout = WidgetsFactory::createFormLayout(box);
|
||||||
|
|
||||||
{
|
{
|
||||||
setupNotebookTypeComboBox(box);
|
setupNotebookTypeComboBox(box);
|
||||||
@ -131,7 +131,7 @@ QLayout *NotebookInfoWidget::setupNotebookRootFolderPath(QWidget *p_parent)
|
|||||||
QGroupBox *NotebookInfoWidget::setupAdvancedInfoGroupBox(QWidget *p_parent)
|
QGroupBox *NotebookInfoWidget::setupAdvancedInfoGroupBox(QWidget *p_parent)
|
||||||
{
|
{
|
||||||
auto box = new QGroupBox(tr("Advanced Information"), p_parent);
|
auto box = new QGroupBox(tr("Advanced Information"), p_parent);
|
||||||
auto mainLayout = WidgetUtils::createFormLayout(box);
|
auto mainLayout = WidgetsFactory::createFormLayout(box);
|
||||||
|
|
||||||
{
|
{
|
||||||
setupConfigMgrComboBox(box);
|
setupConfigMgrComboBox(box);
|
||||||
|
@ -33,6 +33,11 @@ void ScrollDialog::setCentralWidget(QWidget *p_widget)
|
|||||||
m_scrollArea->setWidget(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)
|
void ScrollDialog::showEvent(QShowEvent *p_event)
|
||||||
{
|
{
|
||||||
QDialog::showEvent(p_event);
|
QDialog::showEvent(p_event);
|
||||||
|
@ -13,11 +13,13 @@ namespace vnotex
|
|||||||
public:
|
public:
|
||||||
ScrollDialog(QWidget *p_parent = nullptr, Qt::WindowFlags p_flags = Qt::WindowFlags());
|
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);
|
void resizeToHideScrollBarLater(bool p_vertical, bool p_horizontal);
|
||||||
|
|
||||||
protected:
|
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;
|
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -20,7 +20,7 @@ AppearancePage::AppearancePage(QWidget *p_parent)
|
|||||||
|
|
||||||
void AppearancePage::setupUI()
|
void AppearancePage::setupUI()
|
||||||
{
|
{
|
||||||
auto mainLayout = WidgetUtils::createFormLayout(this);
|
auto mainLayout = WidgetsFactory::createFormLayout(this);
|
||||||
|
|
||||||
{
|
{
|
||||||
const QString label(tr("System title bar"));
|
const QString label(tr("System title bar"));
|
||||||
|
@ -20,7 +20,7 @@ EditorPage::EditorPage(QWidget *p_parent)
|
|||||||
|
|
||||||
void EditorPage::setupUI()
|
void EditorPage::setupUI()
|
||||||
{
|
{
|
||||||
auto mainLayout = WidgetUtils::createFormLayout(this);
|
auto mainLayout = WidgetsFactory::createFormLayout(this);
|
||||||
|
|
||||||
{
|
{
|
||||||
m_autoSavePolicyComboBox = WidgetsFactory::createComboBox(this);
|
m_autoSavePolicyComboBox = WidgetsFactory::createComboBox(this);
|
||||||
|
@ -20,7 +20,7 @@ GeneralPage::GeneralPage(QWidget *p_parent)
|
|||||||
|
|
||||||
void GeneralPage::setupUI()
|
void GeneralPage::setupUI()
|
||||||
{
|
{
|
||||||
auto mainLayout = WidgetUtils::createFormLayout(this);
|
auto mainLayout = WidgetsFactory::createFormLayout(this);
|
||||||
|
|
||||||
{
|
{
|
||||||
m_localeComboBox = WidgetsFactory::createComboBox(this);
|
m_localeComboBox = WidgetsFactory::createComboBox(this);
|
||||||
|
@ -125,7 +125,7 @@ QString MarkdownEditorPage::title() const
|
|||||||
QGroupBox *MarkdownEditorPage::setupReadGroup()
|
QGroupBox *MarkdownEditorPage::setupReadGroup()
|
||||||
{
|
{
|
||||||
auto box = new QGroupBox(tr("Read"), this);
|
auto box = new QGroupBox(tr("Read"), this);
|
||||||
auto layout = WidgetUtils::createFormLayout(box);
|
auto layout = WidgetsFactory::createFormLayout(box);
|
||||||
|
|
||||||
{
|
{
|
||||||
const QString label(tr("Constrain image width"));
|
const QString label(tr("Constrain image width"));
|
||||||
@ -197,7 +197,7 @@ QGroupBox *MarkdownEditorPage::setupReadGroup()
|
|||||||
QGroupBox *MarkdownEditorPage::setupEditGroup()
|
QGroupBox *MarkdownEditorPage::setupEditGroup()
|
||||||
{
|
{
|
||||||
auto box = new QGroupBox(tr("Edit"), this);
|
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"));
|
const QString label(tr("Insert file name as title"));
|
||||||
@ -245,7 +245,7 @@ QGroupBox *MarkdownEditorPage::setupEditGroup()
|
|||||||
QGroupBox *MarkdownEditorPage::setupGeneralGroup()
|
QGroupBox *MarkdownEditorPage::setupGeneralGroup()
|
||||||
{
|
{
|
||||||
auto box = new QGroupBox(tr("General"), this);
|
auto box = new QGroupBox(tr("General"), this);
|
||||||
auto layout = WidgetUtils::createFormLayout(box);
|
auto layout = WidgetsFactory::createFormLayout(box);
|
||||||
|
|
||||||
{
|
{
|
||||||
auto sectionLayout = new QHBoxLayout();
|
auto sectionLayout = new QHBoxLayout();
|
||||||
|
@ -23,7 +23,7 @@ TextEditorPage::TextEditorPage(QWidget *p_parent)
|
|||||||
|
|
||||||
void TextEditorPage::setupUI()
|
void TextEditorPage::setupUI()
|
||||||
{
|
{
|
||||||
auto mainLayout = WidgetUtils::createFormLayout(this);
|
auto mainLayout = WidgetsFactory::createFormLayout(this);
|
||||||
|
|
||||||
{
|
{
|
||||||
m_lineNumberComboBox = WidgetsFactory::createComboBox(this);
|
m_lineNumberComboBox = WidgetsFactory::createComboBox(this);
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include <utils/htmlutils.h>
|
#include <utils/htmlutils.h>
|
||||||
#include <utils/widgetutils.h>
|
#include <utils/widgetutils.h>
|
||||||
#include <utils/textutils.h>
|
#include <utils/textutils.h>
|
||||||
|
#include <utils/webutils.h>
|
||||||
#include <core/exception.h>
|
#include <core/exception.h>
|
||||||
#include <core/markdowneditorconfig.h>
|
#include <core/markdowneditorconfig.h>
|
||||||
#include <core/texteditorconfig.h>
|
#include <core/texteditorconfig.h>
|
||||||
@ -1075,7 +1076,7 @@ void MarkdownEditor::fetchImagesToLocalAndReplace(QString &p_text)
|
|||||||
|
|
||||||
// Only handle absolute file path or network path.
|
// Only handle absolute file path or network path.
|
||||||
QString srcImagePath;
|
QString srcImagePath;
|
||||||
QFileInfo info(TextUtils::purifyUrl(imageUrl));
|
QFileInfo info(WebUtils::purifyUrl(imageUrl));
|
||||||
|
|
||||||
// For network image.
|
// For network image.
|
||||||
QScopedPointer<QTemporaryFile> tmpFile;
|
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)
|
void MarkdownViewerAdapter::setReady(bool p_ready)
|
||||||
{
|
{
|
||||||
if (m_viewerReady == 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);
|
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,
|
const QString &p_text,
|
||||||
int p_lineNumber);
|
int p_lineNumber);
|
||||||
|
|
||||||
|
void setText(const QString &p_text);
|
||||||
|
|
||||||
void scrollToPosition(const Position &p_pos);
|
void scrollToPosition(const Position &p_pos);
|
||||||
|
|
||||||
int getTopLineNumber() const;
|
int getTopLineNumber() const;
|
||||||
@ -119,10 +121,14 @@ namespace vnotex
|
|||||||
|
|
||||||
void findText(const QString &p_text, FindOptions p_options);
|
void findText(const QString &p_text, FindOptions p_options);
|
||||||
|
|
||||||
|
void saveContent();
|
||||||
|
|
||||||
// Functions to be called from web side.
|
// Functions to be called from web side.
|
||||||
public slots:
|
public slots:
|
||||||
void setReady(bool p_ready);
|
void setReady(bool p_ready);
|
||||||
|
|
||||||
|
void setWorkFinished();
|
||||||
|
|
||||||
// The line number at the top.
|
// The line number at the top.
|
||||||
void setTopLineNumber(int p_lineNumber);
|
void setTopLineNumber(int p_lineNumber);
|
||||||
|
|
||||||
@ -161,6 +167,8 @@ namespace vnotex
|
|||||||
|
|
||||||
void setFindText(const QString &p_text, int p_totalMatches, int p_currentMatchIndex);
|
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 to be connected at web side.
|
||||||
signals:
|
signals:
|
||||||
// Current Markdown text is updated.
|
// Current Markdown text is updated.
|
||||||
@ -194,6 +202,9 @@ namespace vnotex
|
|||||||
|
|
||||||
void findTextRequested(const QString &p_text, const QJsonObject &p_options);
|
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 to be connected at cpp side.
|
||||||
signals:
|
signals:
|
||||||
void graphPreviewDataReady(const PreviewData &p_data);
|
void graphPreviewDataReady(const PreviewData &p_data);
|
||||||
@ -202,6 +213,9 @@ namespace vnotex
|
|||||||
|
|
||||||
void viewerReady();
|
void viewerReady();
|
||||||
|
|
||||||
|
// All rendering work has finished.
|
||||||
|
void workFinished();
|
||||||
|
|
||||||
void headingsChanged();
|
void headingsChanged();
|
||||||
|
|
||||||
void currentHeadingChanged();
|
void currentHeadingChanged();
|
||||||
@ -216,6 +230,11 @@ namespace vnotex
|
|||||||
|
|
||||||
void findTextReady(const QString &p_text, int p_totalMatches, int p_currentMatchIndex);
|
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:
|
private:
|
||||||
void scrollToLine(int p_lineNumber);
|
void scrollToLine(int p_lineNumber);
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
#include <core/coreconfig.h>
|
#include <core/coreconfig.h>
|
||||||
#include <core/events.h>
|
#include <core/events.h>
|
||||||
#include <core/fileopenparameters.h>
|
#include <core/fileopenparameters.h>
|
||||||
#include <widgets/dialogs/scrolldialog.h>
|
#include <widgets/dialogs/exportdialog.h>
|
||||||
#include "viewwindow.h"
|
#include "viewwindow.h"
|
||||||
#include "outlineviewer.h"
|
#include "outlineviewer.h"
|
||||||
#include <utils/widgetutils.h>
|
#include <utils/widgetutils.h>
|
||||||
@ -57,6 +57,9 @@ MainWindow::MainWindow(QWidget *p_parent)
|
|||||||
// Note that no user interaction is possible in this state.
|
// Note that no user interaction is possible in this state.
|
||||||
connect(qApp, &QCoreApplication::aboutToQuit,
|
connect(qApp, &QCoreApplication::aboutToQuit,
|
||||||
this, &MainWindow::closeOnQuit);
|
this, &MainWindow::closeOnQuit);
|
||||||
|
|
||||||
|
connect(&VNoteX::getInst(), &VNoteX::exportRequested,
|
||||||
|
this, &MainWindow::exportNotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
@ -582,3 +585,18 @@ void MainWindow::updateTabBarStyle()
|
|||||||
tabBar->setDrawBase(false);
|
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 updateTabBarStyle();
|
||||||
|
|
||||||
|
void exportNotes();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Index in m_docks.
|
// Index in m_docks.
|
||||||
enum DockIndex
|
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