VMdEditor: support copy selected text as HTML

This commit is contained in:
Le Tan 2018-01-03 19:49:41 +08:00
parent ce48c80cd0
commit 2f1971476d
26 changed files with 611 additions and 71 deletions

View File

@ -0,0 +1,81 @@
#include "vcopytextashtmldialog.h"
#include <QtWidgets>
#include <QWebEngineView>
#include <QClipboard>
#include <QMimeData>
#include <QApplication>
#include "utils/vutils.h"
#include "utils/vclipboardutils.h"
#include "utils/vwebutils.h"
#include "vconfigmanager.h"
extern VConfigManager *g_config;
VCopyTextAsHtmlDialog::VCopyTextAsHtmlDialog(const QString &p_text, QWidget *p_parent)
: QDialog(p_parent), m_text(p_text)
{
setupUI();
}
void VCopyTextAsHtmlDialog::setupUI()
{
QLabel *textLabel = new QLabel(tr("Text:"));
m_textEdit = new QPlainTextEdit(m_text);
m_textEdit->setReadOnly(true);
m_textEdit->setProperty("LineEdit", true);
m_htmlLabel = new QLabel(tr("HTML:"));
m_htmlViewer = VUtils::getWebEngineView();
m_htmlViewer->setContextMenuPolicy(Qt::NoContextMenu);
m_htmlViewer->setMinimumSize(600, 400);
m_infoLabel = new QLabel(tr("Converting text to HTML ..."));
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok);
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
m_btnBox->button(QDialogButtonBox::Ok)->setProperty("SpecialBtn", true);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addWidget(textLabel);
mainLayout->addWidget(m_textEdit);
mainLayout->addWidget(m_htmlLabel);
mainLayout->addWidget(m_htmlViewer);
mainLayout->addWidget(m_infoLabel);
mainLayout->addStretch();
mainLayout->addWidget(m_btnBox);
setLayout(mainLayout);
setWindowTitle(tr("Copy Text As HTML"));
setHtmlVisible(false);
}
void VCopyTextAsHtmlDialog::setHtmlVisible(bool p_visible)
{
m_htmlLabel->setVisible(p_visible);
m_htmlViewer->setVisible(p_visible);
}
void VCopyTextAsHtmlDialog::setConvertedHtml(const QUrl &p_baseUrl,
const QString &p_html)
{
QString html = QString("<html><body>%1</body></html>").arg(p_html);
m_htmlViewer->setHtml(html, p_baseUrl);
setHtmlVisible(true);
// Fix image source.
if (g_config->getFixImageSrcInWebWhenCopied()) {
VWebUtils::fixImageSrcInHtml(p_baseUrl, html);
}
QClipboard *clipboard = QApplication::clipboard();
QMimeData *data = new QMimeData();
data->setText(m_text);
data->setHtml(html);
VClipboardUtils::setMimeDataToClipboard(clipboard, data, QClipboard::Clipboard);
QTimer::singleShot(3000, this, &VCopyTextAsHtmlDialog::accept);
m_infoLabel->setText(tr("HTML has been copied. Will be closed in 3 seconds."));
}

View File

@ -0,0 +1,46 @@
#ifndef VCOPYTEXTASHTMLDIALOG_H
#define VCOPYTEXTASHTMLDIALOG_H
#include <QDialog>
#include <QUrl>
class QPlainTextEdit;
class QWebEngineView;
class QDialogButtonBox;
class VWaitingWidget;
class QLabel;
class VCopyTextAsHtmlDialog : public QDialog
{
Q_OBJECT
public:
VCopyTextAsHtmlDialog(const QString &p_text, QWidget *p_parent = nullptr);
void setConvertedHtml(const QUrl &p_baseUrl, const QString &p_html);
const QString &getText() const;
private:
void setupUI();
void setHtmlVisible(bool p_visible);
QPlainTextEdit *m_textEdit;
QLabel *m_htmlLabel;
QWebEngineView *m_htmlViewer;
QLabel *m_infoLabel;
QDialogButtonBox *m_btnBox;
QString m_text;
};
inline const QString &VCopyTextAsHtmlDialog::getText() const
{
return m_text;
}
#endif // VCOPYTEXTASHTMLDIALOG_H

View File

@ -69,3 +69,15 @@ var highlightText = function(text, id, timeStamp) {
var html = marked(text); var html = marked(text);
content.highlightTextCB(html, id, timeStamp); content.highlightTextCB(html, id, timeStamp);
} }
var textToHtml = function(text) {
var html = marked(text);
var container = document.getElementById('text-to-html-div');
container.innerHTML = html;
html = getHtmlWithInlineStyles(container);
container.innerHTML = "";
content.textToHtmlCB(text, html);
}

View File

@ -124,3 +124,14 @@ var highlightText = function(text, id, timeStamp) {
content.highlightTextCB(html, id, timeStamp); content.highlightTextCB(html, id, timeStamp);
} }
var textToHtml = function(text) {
var html = mdit.render(text);
var container = document.getElementById('text-to-html-div');
container.innerHTML = html;
html = getHtmlWithInlineStyles(container);
container.innerHTML = "";
content.textToHtmlCB(text, html);
}

View File

@ -31,5 +31,7 @@
</head> </head>
<body> <body>
<div id="placeholder"></div> <div id="placeholder"></div>
<div id="text-to-html-div" style="display:none;"></div>
</body> </body>
</html> </html>

View File

@ -28,6 +28,10 @@ if (typeof VEnableHighlightLineNumber == 'undefined') {
VEnableHighlightLineNumber = false; VEnableHighlightLineNumber = false;
} }
if (typeof VStylesToInline == 'undefined') {
VStylesToInline = '';
}
// Add a caption (using alt text) under the image. // Add a caption (using alt text) under the image.
var VImageCenterClass = 'img-center'; var VImageCenterClass = 'img-center';
var VImageCaptionClass = 'img-caption'; var VImageCaptionClass = 'img-caption';
@ -53,6 +57,11 @@ new QWebChannel(qt.webChannelTransport,
content.requestHighlightText.connect(highlightText); content.requestHighlightText.connect(highlightText);
content.noticeReadyToHighlightText(); content.noticeReadyToHighlightText();
} }
if (typeof textToHtml == "function") {
content.requestTextToHtml.connect(textToHtml);
content.noticeReadyToTextToHtml();
}
}); });
var VHighlightedAnchorClass = 'highlighted-anchor'; var VHighlightedAnchorClass = 'highlighted-anchor';
@ -853,4 +862,68 @@ var listContainsRegex = function(strs, exp) {
} }
return false; return false;
} };
var StylesToInline = null;
var initStylesToInline = function() {
console.log('initStylesToInline');
StylesToInline = new Map();
if (VStylesToInline.length == 0) {
return;
}
var rules = VStylesToInline.split(',');
for (var i = 0; i < rules.length; ++i) {
var vals = rules[i].split('$');
if (vals.length != 2) {
continue;
}
var tags = vals[0].split(':');
var pros = vals[1].split(':');
for (var j = 0; j < tags.length; ++j) {
StylesToInline.set(tags[j].toLowerCase(), pros);
}
}
};
// Embed the CSS styles of @ele and all its children.
var embedInlineStyles = function(ele) {
var tagName = ele.tagName.toLowerCase();
var props = StylesToInline.get(tagName);
if (!props) {
props = StylesToInline.get('all');
if (!props) {
return;
}
}
// Embed itself.
var style = window.getComputedStyle(ele, null);
for (var i = 0; i < props.length; ++i) {
var pro = props[i];
ele.style.setProperty(pro, style.getPropertyValue(pro));
}
// Embed children.
var children = ele.children;
for (var i = 0; i < children.length; ++i) {
embedInlineStyles(children[i]);
}
};
var getHtmlWithInlineStyles = function(container) {
if (!StylesToInline) {
initStylesToInline();
}
var children = container.children;
for (var i = 0; i < children.length; ++i) {
embedInlineStyles(children[i]);
}
return container.innerHTML;
};

View File

@ -75,3 +75,14 @@ var highlightText = function(text, id, timeStamp) {
content.highlightTextCB(html, id, timeStamp); content.highlightTextCB(html, id, timeStamp);
} }
var textToHtml = function(text) {
var html = marked(text);
var container = document.getElementById('text-to-html-div');
container.innerHTML = html;
html = getHtmlWithInlineStyles(container);
container.innerHTML = "";
content.textToHtmlCB(text, html);
}

View File

@ -109,3 +109,23 @@ var highlightText = function(text, id, timeStamp) {
content.highlightTextCB(html, id, timeStamp); content.highlightTextCB(html, id, timeStamp);
} }
var textToHtml = function(text) {
var html = renderer.makeHtml(text);
var parser = new DOMParser();
var htmlDoc = parser.parseFromString("<div id=\"showdown-container\">" + html + "</div>", 'text/html');
highlightCodeBlocks(htmlDoc, false, false);
html = htmlDoc.getElementById('showdown-container').innerHTML;
delete parser;
var container = document.getElementById('text-to-html-div');
container.innerHTML = html;
html = getHtmlWithInlineStyles(container);
container.innerHTML = "";
content.textToHtmlCB(text, html);
}

View File

@ -196,6 +196,15 @@ mathjax_javascript=https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.
; Fix local relative image source when copied ; Fix local relative image source when copied
fix_img_src_when_copied=true fix_img_src_when_copied=true
; Styles to be removed when copied in read mode
; style1,style2,style3
styles_to_remove_when_copied=margin,margin-left,margin-right,padding,padding-left,padding-right
; CSS properties to embed as inline styles when copied in edit mode
; tag1:tag2:tag3$property1:property2:property3,tag4:tag5$property2:property3
; "all" for all tags not specified explicitly
styles_to_inline_when_copied=all$border:color:display:font-family:font-size:font-style:white-space:word-spacing:line-height:text-align:text-indent:padding-top:padding-bottom:margin-top:margin-bottom,code$font-family:font-size:line-height:color:display:overfow-x,li$line-height,a$color:vertical-align,pre$display:overflow-y:overflow-x:color:font-size:font-style:font-weight:letter-spacing:text-align:text-indent:word-spacing
[shortcuts] [shortcuts]
; Define shortcuts here, with each item in the form "operation=keysequence". ; Define shortcuts here, with each item in the form "operation=keysequence".
; Leave keysequence empty to disable the shortcut of an operation. ; Leave keysequence empty to disable the shortcut of an operation.

View File

@ -102,7 +102,10 @@ SOURCES += main.cpp\
vbuttonmenuitem.cpp \ vbuttonmenuitem.cpp \
utils/viconutils.cpp \ utils/viconutils.cpp \
lineeditdelegate.cpp \ lineeditdelegate.cpp \
dialog/vtipsdialog.cpp dialog/vtipsdialog.cpp \
dialog/vcopytextashtmldialog.cpp \
vwaitingwidget.cpp \
utils/vwebutils.cpp
HEADERS += vmainwindow.h \ HEADERS += vmainwindow.h \
vdirectorytree.h \ vdirectorytree.h \
@ -191,7 +194,10 @@ HEADERS += vmainwindow.h \
vbuttonmenuitem.h \ vbuttonmenuitem.h \
utils/viconutils.h \ utils/viconutils.h \
lineeditdelegate.h \ lineeditdelegate.h \
dialog/vtipsdialog.h dialog/vtipsdialog.h \
dialog/vcopytextashtmldialog.h \
vwaitingwidget.h \
utils/vwebutils.h
RESOURCES += \ RESOURCES += \
vnote.qrc \ vnote.qrc \

View File

@ -664,6 +664,8 @@ QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType, bool p_exp
"<script>var VEnableHighlightLineNumber = true;</script>\n"; "<script>var VEnableHighlightLineNumber = true;</script>\n";
} }
extraFile += "<script>var VStylesToInline = '" + g_config->getStylesToInlineWhenCopied() + "';</script>\n";
QString htmlTemplate; QString htmlTemplate;
if (p_exportPdf) { if (p_exportPdf) {
htmlTemplate = VNote::s_markdownTemplatePDF; htmlTemplate = VNote::s_markdownTemplatePDF;

View File

@ -6321,9 +6321,7 @@ void VVim::handleMouseMoved(QMouseEvent *p_event)
void VVim::handleMouseReleased(QMouseEvent *p_event) void VVim::handleMouseReleased(QMouseEvent *p_event)
{ {
Q_UNUSED(p_event); if (checkMode(VimMode::Normal) && p_event->button() == Qt::LeftButton) {
if (checkMode(VimMode::Normal)) {
QTextCursor cursor = m_editor->textCursorW(); QTextCursor cursor = m_editor->textCursorW();
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
return; return;

58
src/utils/vwebutils.cpp Normal file
View File

@ -0,0 +1,58 @@
#include "vwebutils.h"
#include <QRegExp>
#include <QFileInfo>
#include <QDebug>
VWebUtils::VWebUtils()
{
}
bool VWebUtils::fixImageSrcInHtml(const QUrl &p_baseUrl, QString &p_html)
{
bool changed = false;
#if defined(Q_OS_WIN)
QUrl::ComponentFormattingOption strOpt = QUrl::EncodeSpaces;
#else
QUrl::ComponentFormattingOption strOpt = QUrl::FullyEncoded;
#endif
QRegExp reg("(<img src=\")([^\"]+)\"");
int pos = 0;
while (pos < p_html.size()) {
int idx = p_html.indexOf(reg, pos);
if (idx == -1) {
break;
}
QString urlStr = reg.cap(2);
QUrl imgUrl(urlStr);
QString fixedStr;
if (imgUrl.isRelative()) {
fixedStr = p_baseUrl.resolved(imgUrl).toString(strOpt);
} else if (imgUrl.isLocalFile()) {
fixedStr = imgUrl.toString(strOpt);
} else if (imgUrl.scheme() != "https" && imgUrl.scheme() != "http") {
QString tmp = imgUrl.toString();
if (QFileInfo::exists(tmp)) {
fixedStr = QUrl::fromLocalFile(tmp).toString(strOpt);
}
}
pos = idx + reg.matchedLength();
if (!fixedStr.isEmpty() && urlStr != fixedStr) {
qDebug() << "fix img url" << urlStr << fixedStr;
// Insert one more space to avoid fix the url twice.
pos = pos + fixedStr.size() + 1 - urlStr.size();
p_html.replace(idx,
reg.matchedLength(),
QString("<img src=\"%1\"").arg(fixedStr));
changed = true;
}
}
return changed;
}

18
src/utils/vwebutils.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef VWEBUTILS_H
#define VWEBUTILS_H
#include <QUrl>
#include <QString>
class VWebUtils
{
public:
// Fix <img src> in @p_html.
static bool fixImageSrcInHtml(const QUrl &p_baseUrl, QString &p_html);
private:
VWebUtils();
};
#endif // VWEBUTILS_H

View File

@ -287,6 +287,12 @@ void VConfigManager::initialize()
m_fixImageSrcInWebWhenCopied = getConfigFromSettings("web", m_fixImageSrcInWebWhenCopied = getConfigFromSettings("web",
"fix_img_src_when_copied").toBool(); "fix_img_src_when_copied").toBool();
m_stylesToRemoveWhenCopied = getConfigFromSettings("web",
"styles_to_remove_when_copied").toStringList();
m_stylesToInlineWhenCopied = getConfigFromSettings("web",
"styles_to_inline_when_copied").toStringList().join(",");
} }
void VConfigManager::initSettings() void VConfigManager::initSettings()

View File

@ -428,6 +428,10 @@ public:
bool getFixImageSrcInWebWhenCopied() const; bool getFixImageSrcInWebWhenCopied() const;
const QStringList &getStylesToRemoveWhenCopied() const;
const QString &getStylesToInlineWhenCopied() const;
private: private:
// Look up a config from user and default settings. // Look up a config from user and default settings.
QVariant getConfigFromSettings(const QString &section, const QString &key) const; QVariant getConfigFromSettings(const QString &section, const QString &key) const;
@ -821,6 +825,12 @@ private:
// Whether fix the local relative image src in read mode when copied. // Whether fix the local relative image src in read mode when copied.
bool m_fixImageSrcInWebWhenCopied; bool m_fixImageSrcInWebWhenCopied;
// Styles to be removed when copied in read mode.
QStringList m_stylesToRemoveWhenCopied;
// The string containing styles to inline when copied in edit mode.
QString m_stylesToInlineWhenCopied;
// The name of the config file in each directory, obsolete. // The name of the config file in each directory, obsolete.
// Use c_dirConfigFile instead. // Use c_dirConfigFile instead.
static const QString c_obsoleteDirConfigFile; static const QString c_obsoleteDirConfigFile;
@ -1993,4 +2003,13 @@ inline bool VConfigManager::getFixImageSrcInWebWhenCopied() const
return m_fixImageSrcInWebWhenCopied; return m_fixImageSrcInWebWhenCopied;
} }
inline const QStringList &VConfigManager::getStylesToRemoveWhenCopied() const
{
return m_stylesToRemoveWhenCopied;
}
inline const QString &VConfigManager::getStylesToInlineWhenCopied() const
{
return m_stylesToInlineWhenCopied;
}
#endif // VCONFIGMANAGER_H #endif // VCONFIGMANAGER_H

View File

@ -77,12 +77,27 @@ void VDocument::highlightTextCB(const QString &p_html, int p_id, int p_timeStamp
emit textHighlighted(p_html, p_id, p_timeStamp); emit textHighlighted(p_html, p_id, p_timeStamp);
} }
void VDocument::textToHtmlAsync(const QString &p_text)
{
emit requestTextToHtml(p_text);
}
void VDocument::textToHtmlCB(const QString &p_text, const QString &p_html)
{
emit textToHtmlFinished(p_text, p_html);
}
void VDocument::noticeReadyToHighlightText() void VDocument::noticeReadyToHighlightText()
{ {
m_readyToHighlight = true; m_readyToHighlight = true;
emit readyToHighlightText(); emit readyToHighlightText();
} }
void VDocument::noticeReadyToTextToHtml()
{
m_readyToTextToHtml = true;
}
void VDocument::setFile(const VFile *p_file) void VDocument::setFile(const VFile *p_file)
{ {
m_file = p_file; m_file = p_file;

View File

@ -29,10 +29,15 @@ public:
// Use p_id to identify the result. // Use p_id to identify the result.
void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp); void highlightTextAsync(const QString &p_text, int p_id, int p_timeStamp);
// Request to convert @p_text to HTML.
void textToHtmlAsync(const QString &p_text);
void setFile(const VFile *p_file); void setFile(const VFile *p_file);
bool isReadyToHighlight() const; bool isReadyToHighlight() const;
bool isReadyToTextToHtml() const;
public slots: public slots:
// Will be called in the HTML side // Will be called in the HTML side
@ -49,9 +54,15 @@ public slots:
void setLog(const QString &p_log); void setLog(const QString &p_log);
void keyPressEvent(int p_key, bool p_ctrl, bool p_shift); void keyPressEvent(int p_key, bool p_ctrl, bool p_shift);
void updateText(); void updateText();
void highlightTextCB(const QString &p_html, int p_id, int p_timeStamp); void highlightTextCB(const QString &p_html, int p_id, int p_timeStamp);
void noticeReadyToHighlightText(); void noticeReadyToHighlightText();
void textToHtmlCB(const QString &p_text, const QString &p_html);
void noticeReadyToTextToHtml();
// Web-side handle logics (MathJax etc.) is finished. // Web-side handle logics (MathJax etc.) is finished.
// But the page may not finish loading, such as images. // But the page may not finish loading, such as images.
void finishLogics(); void finishLogics();
@ -65,14 +76,25 @@ signals:
// @anchor is the id of that anchor, without '#'. // @anchor is the id of that anchor, without '#'.
void headerChanged(const QString &anchor); void headerChanged(const QString &anchor);
void htmlChanged(const QString &html); void htmlChanged(const QString &html);
void logChanged(const QString &p_log); void logChanged(const QString &p_log);
void keyPressed(int p_key, bool p_ctrl, bool p_shift); void keyPressed(int p_key, bool p_ctrl, bool p_shift);
void requestHighlightText(const QString &p_text, int p_id, int p_timeStamp); void requestHighlightText(const QString &p_text, int p_id, int p_timeStamp);
void textHighlighted(const QString &p_html, int p_id, int p_timeStamp); void textHighlighted(const QString &p_html, int p_id, int p_timeStamp);
void readyToHighlightText(); void readyToHighlightText();
void logicsFinished(); void logicsFinished();
void requestTextToHtml(const QString &p_text);
void textToHtmlFinished(const QString &p_text, const QString &p_html);
private: private:
QString m_toc; QString m_toc;
QString m_header; QString m_header;
@ -87,10 +109,18 @@ private:
// Whether the web side is ready to handle highlight text request. // Whether the web side is ready to handle highlight text request.
bool m_readyToHighlight; bool m_readyToHighlight;
// Whether the web side is ready to convert text to html.
bool m_readyToTextToHtml;
}; };
inline bool VDocument::isReadyToHighlight() const inline bool VDocument::isReadyToHighlight() const
{ {
return m_readyToHighlight; return m_readyToHighlight;
} }
inline bool VDocument::isReadyToTextToHtml() const
{
return m_readyToTextToHtml;
}
#endif // VDOCUMENT_H #endif // VDOCUMENT_H

View File

@ -19,6 +19,7 @@
#include "vnotefile.h" #include "vnotefile.h"
#include "vpreviewmanager.h" #include "vpreviewmanager.h"
#include "utils/viconutils.h" #include "utils/viconutils.h"
#include "dialog/vcopytextashtmldialog.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -29,7 +30,8 @@ VMdEditor::VMdEditor(VFile *p_file,
: VTextEdit(p_parent), : VTextEdit(p_parent),
VEditor(p_file, this), VEditor(p_file, this),
m_mdHighlighter(NULL), m_mdHighlighter(NULL),
m_freshEdit(true) m_freshEdit(true),
m_textToHtmlDialog(NULL)
{ {
Q_ASSERT(p_file->getDocType() == DocType::Markdown); Q_ASSERT(p_file->getDocType() == DocType::Markdown);
@ -266,12 +268,19 @@ void VMdEditor::contextMenuEvent(QContextMenuEvent *p_event)
QMenu *menu = createStandardContextMenu(); QMenu *menu = createStandardContextMenu();
menu->setToolTipsVisible(true); menu->setToolTipsVisible(true);
const QList<QAction *> actions = menu->actions();
if (!textCursor().hasSelection()) {
VEditTab *editTab = dynamic_cast<VEditTab *>(parent()); VEditTab *editTab = dynamic_cast<VEditTab *>(parent());
Q_ASSERT(editTab); Q_ASSERT(editTab);
if (editTab->isEditMode()) { if (editTab->isEditMode()) {
const QList<QAction *> actions = menu->actions();
if (textCursor().hasSelection()) {
QAction *copyAsHtmlAct = new QAction(tr("Copy As &HTML without Background"), menu);
copyAsHtmlAct->setToolTip(tr("Copy selected contents as HTML without background styles"));
connect(copyAsHtmlAct, &QAction::triggered,
this, &VMdEditor::handleCopyAsHtmlAction);
menu->insertAction(actions.isEmpty() ? NULL : actions[0], copyAsHtmlAct);
} else {
QAction *saveExitAct = new QAction(VIconUtils::menuIcon(":/resources/icons/save_exit.svg"), QAction *saveExitAct = new QAction(VIconUtils::menuIcon(":/resources/icons/save_exit.svg"),
tr("&Save Changes And Read"), tr("&Save Changes And Read"),
menu); menu);
@ -292,11 +301,12 @@ void VMdEditor::contextMenuEvent(QContextMenuEvent *p_event)
menu->insertAction(actions.isEmpty() ? NULL : actions[0], discardExitAct); menu->insertAction(actions.isEmpty() ? NULL : actions[0], discardExitAct);
menu->insertAction(discardExitAct, saveExitAct); menu->insertAction(discardExitAct, saveExitAct);
}
if (!actions.isEmpty()) { if (!actions.isEmpty()) {
menu->insertSeparator(actions[0]); menu->insertSeparator(actions[0]);
} }
} }
}
menu->exec(p_event->globalPos()); menu->exec(p_event->globalPos());
delete menu; delete menu;
@ -990,3 +1000,32 @@ void VMdEditor::updateInitAndInsertedImages(bool p_fileChanged, UpdateAction p_a
} }
} }
} }
void VMdEditor::handleCopyAsHtmlAction()
{
QTextCursor cursor = textCursor();
Q_ASSERT(cursor.hasSelection());
QString text = VEditUtils::selectedText(cursor);
Q_ASSERT(!text.isEmpty());
Q_ASSERT(!m_textToHtmlDialog);
m_textToHtmlDialog = new VCopyTextAsHtmlDialog(text, this);
// For Hoedown, we use marked.js to convert the text to have a general interface.
emit requestTextToHtml(text);
m_textToHtmlDialog->exec();
delete m_textToHtmlDialog;
m_textToHtmlDialog = NULL;
}
void VMdEditor::textToHtmlFinished(const QString &p_text,
const QUrl &p_baseUrl,
const QString &p_html)
{
if (m_textToHtmlDialog && m_textToHtmlDialog->getText() == p_text) {
m_textToHtmlDialog->setConvertedHtml(p_baseUrl, p_html);
}
}

View File

@ -5,6 +5,7 @@
#include <QString> #include <QString>
#include <QClipboard> #include <QClipboard>
#include <QImage> #include <QImage>
#include <QUrl>
#include "vtextedit.h" #include "vtextedit.h"
#include "veditor.h" #include "veditor.h"
@ -17,6 +18,7 @@ class HGMarkdownHighlighter;
class VCodeBlockHighlightHelper; class VCodeBlockHighlightHelper;
class VDocument; class VDocument;
class VPreviewManager; class VPreviewManager;
class VCopyTextAsHtmlDialog;
class VMdEditor : public VTextEdit, public VEditor class VMdEditor : public VTextEdit, public VEditor
{ {
@ -68,6 +70,8 @@ public:
public slots: public slots:
bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE; bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
void textToHtmlFinished(const QString &p_text, const QUrl &p_baseUrl, const QString &p_html);
// Wrapper functions for QPlainTextEdit/QTextEdit. // Wrapper functions for QPlainTextEdit/QTextEdit.
public: public:
void setExtraSelectionsW(const QList<QTextEdit::ExtraSelection> &p_selections) Q_DECL_OVERRIDE void setExtraSelectionsW(const QList<QTextEdit::ExtraSelection> &p_selections) Q_DECL_OVERRIDE
@ -165,6 +169,9 @@ signals:
// Will be emitted by VImagePreviewer for now. // Will be emitted by VImagePreviewer for now.
void statusChanged(); void statusChanged();
// Request to convert @p_text to Html.
void requestTextToHtml(const QString &p_text);
protected: protected:
void updateFontAndPalette() Q_DECL_OVERRIDE; void updateFontAndPalette() Q_DECL_OVERRIDE;
@ -191,6 +198,9 @@ private slots:
// When there is no header in current cursor, will signal an invalid header. // When there is no header in current cursor, will signal an invalid header.
void updateCurrentHeader(); void updateCurrentHeader();
// Copy selected text as HTML.
void handleCopyAsHtmlAction();
private: private:
// Update the config of VTextEdit according to global configurations. // Update the config of VTextEdit according to global configurations.
void updateTextEditConfig(); void updateTextEditConfig();
@ -222,5 +232,7 @@ private:
QVector<VTableOfContentItem> m_headers; QVector<VTableOfContentItem> m_headers;
bool m_freshEdit; bool m_freshEdit;
VCopyTextAsHtmlDialog *m_textToHtmlDialog;
}; };
#endif // VMDEDITOR_H #endif // VMDEDITOR_H

View File

@ -413,6 +413,11 @@ void VMdTab::setupMarkdownViewer()
tabIsReady(TabReady::ReadMode); tabIsReady(TabReady::ReadMode);
}); });
connect(m_document, &VDocument::textToHtmlFinished,
this, [this](const QString &p_text, const QString &p_html) {
Q_ASSERT(m_editor);
m_editor->textToHtmlFinished(p_text, m_webViewer->url(), p_html);
});
page->setWebChannel(channel); page->setWebChannel(channel);
@ -464,6 +469,8 @@ void VMdTab::setupMarkdownEditor()
tabIsReady(TabReady::EditMode); tabIsReady(TabReady::EditMode);
}); });
connect(m_editor, &VMdEditor::requestTextToHtml,
this, &VMdTab::textToHtmlViaWebView);
enableHeadingSequence(m_enableHeadingSequence); enableHeadingSequence(m_enableHeadingSequence);
m_editor->reloadFile(); m_editor->reloadFile();
@ -1006,3 +1013,20 @@ void VMdTab::handleFileOrDirectoryChange(bool p_isFile, UpdateAction p_act)
m_editor->refreshPreview(); m_editor->refreshPreview();
} }
} }
void VMdTab::textToHtmlViaWebView(const QString &p_text)
{
int maxRetry = 50;
while (!m_document->isReadyToTextToHtml() && maxRetry > 0) {
qDebug() << "wait for web side ready to convert text to HTML";
VUtils::sleepWait(100);
--maxRetry;
}
if (maxRetry == 0) {
qWarning() << "web side is not ready to convert text to HTML";
return;
}
m_document->textToHtmlAsync(p_text);
}

View File

@ -184,6 +184,8 @@ private:
// updateStatus() with only cursor position information. // updateStatus() with only cursor position information.
void updateCursorStatus(); void updateCursorStatus();
void textToHtmlViaWebView(const QString &p_text);
VMdEditor *m_editor; VMdEditor *m_editor;
VWebView *m_webViewer; VWebView *m_webViewer;
VDocument *m_document; VDocument *m_document;

33
src/vwaitingwidget.cpp Normal file
View File

@ -0,0 +1,33 @@
#include "vwaitingwidget.h"
#include <QLabel>
#include <QPixmap>
#include <QVBoxLayout>
#include <QHBoxLayout>
VWaitingWidget::VWaitingWidget(QWidget *p_parent)
: QWidget(p_parent)
{
setupUI();
}
void VWaitingWidget::setupUI()
{
QSize imgSize(64, 64);
QLabel *logoLabel = new QLabel();
logoLabel->setPixmap(QPixmap(":/resources/icons/vnote.svg").scaled(imgSize, Qt::KeepAspectRatio));
QHBoxLayout *layout = new QHBoxLayout();
layout->addStretch();
layout->addWidget(logoLabel);
layout->addStretch();
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addStretch();
mainLayout->addLayout(layout);
mainLayout->addStretch();
mainLayout->setContentsMargins(0, 0, 0, 0);
setLayout(mainLayout);
}

19
src/vwaitingwidget.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef VWAITINGWIDGET_H
#define VWAITINGWIDGET_H
#include <QWidget>
class QLabel;
class VWaitingWidget : public QWidget
{
Q_OBJECT
public:
explicit VWaitingWidget(QWidget *p_parent = nullptr);
private:
void setupUI();
};
#endif // VWAITINGWIDGET_H

View File

@ -10,12 +10,12 @@
#include <QMimeData> #include <QMimeData>
#include <QApplication> #include <QApplication>
#include <QImage> #include <QImage>
#include <QRegExp>
#include <QFileInfo> #include <QFileInfo>
#include "vfile.h" #include "vfile.h"
#include "utils/vclipboardutils.h" #include "utils/vclipboardutils.h"
#include "utils/viconutils.h" #include "utils/viconutils.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "utils/vwebutils.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
@ -231,12 +231,50 @@ void VWebView::hideUnusedActions(QMenu *p_menu)
static bool removeBackgroundColor(QString &p_html) static bool removeBackgroundColor(QString &p_html)
{ {
QRegExp reg("(\\s|\")background(-color)?:[^;]+;"); QRegExp reg("(<[^>]+\\sstyle=[^>]*(\\s|\"))background(-color)?:[^;]+;([^>]*>)");
int size = p_html.size(); int size = p_html.size();
p_html.replace(reg, "\\1"); p_html.replace(reg, "\\1\\4");
return p_html.size() != size; return p_html.size() != size;
} }
bool VWebView::removeStyles(QString &p_html)
{
bool changed = false;
const QStringList &styles = g_config->getStylesToRemoveWhenCopied();
if (styles.isEmpty()) {
return changed;
}
QRegExp tagReg("(<[^>]+\\sstyle=[^>]*>)");
int pos = 0;
while (pos < p_html.size()) {
int idx = p_html.indexOf(tagReg, pos);
if (idx == -1) {
break;
}
QString styleStr = tagReg.cap(1);
QString alteredStyleStr = styleStr;
QString regPatt("(\\s|\")%1:[^;]+;");
for (auto const & sty : styles) {
QRegExp reg(regPatt.arg(sty));
alteredStyleStr.replace(reg, "\\1");
}
pos = idx + tagReg.matchedLength();
if (styleStr != alteredStyleStr) {
pos = pos + alteredStyleStr.size() - styleStr.size();
p_html.replace(idx, tagReg.matchedLength(), alteredStyleStr);
changed = true;
}
}
return changed;
}
void VWebView::handleCopyWithoutBackgroundAction() void VWebView::handleCopyWithoutBackgroundAction()
{ {
m_needRemoveBackground = true; m_needRemoveBackground = true;
@ -280,56 +318,6 @@ void VWebView::handleClipboardChanged(QClipboard::Mode p_mode)
} }
} }
bool VWebView::fixImgSrc(QString &p_html)
{
bool changed = false;
#if defined(Q_OS_WIN)
QUrl::ComponentFormattingOption strOpt = QUrl::EncodeSpaces;
#else
QUrl::ComponentFormattingOption strOpt = QUrl::FullyEncoded;
#endif
QRegExp reg("(<img src=\")([^\"]+)\"");
QUrl baseUrl(url());
int pos = 0;
while (pos < p_html.size()) {
int idx = p_html.indexOf(reg, pos);
if (idx == -1) {
break;
}
QString urlStr = reg.cap(2);
QUrl imgUrl(urlStr);
QString fixedStr;
if (imgUrl.isRelative()) {
fixedStr = baseUrl.resolved(imgUrl).toString(strOpt);
} else if (imgUrl.isLocalFile()) {
fixedStr = imgUrl.toString(strOpt);
} else if (imgUrl.scheme() != "https" && imgUrl.scheme() != "http") {
QString tmp = imgUrl.toString();
if (QFileInfo::exists(tmp)) {
fixedStr = QUrl::fromLocalFile(tmp).toString(strOpt);
}
}
pos = idx + reg.matchedLength();
if (!fixedStr.isEmpty() && urlStr != fixedStr) {
qDebug() << "fix img url" << urlStr << fixedStr;
// Insert one more space to avoid fix the url twice.
pos = pos + fixedStr.size() + 1 - urlStr.size();
p_html.replace(idx,
reg.matchedLength(),
QString("<img src=\"%1\"").arg(fixedStr));
changed = true;
}
}
return changed;
}
void VWebView::alterHtmlMimeData(QClipboard *p_clipboard, void VWebView::alterHtmlMimeData(QClipboard *p_clipboard,
const QMimeData *p_mimeData, const QMimeData *p_mimeData,
bool p_removeBackground) bool p_removeBackground)
@ -353,7 +341,12 @@ void VWebView::alterHtmlMimeData(QClipboard *p_clipboard,
} }
// Fix local relative images. // Fix local relative images.
if (m_fixImgSrc && fixImgSrc(html)) { if (m_fixImgSrc && VWebUtils::fixImageSrcInHtml(url(), html)) {
altered = true;
}
// Fix margin and padding.
if (removeStyles(html)) {
altered = true; altered = true;
} }

View File

@ -3,6 +3,7 @@
#include <QWebEngineView> #include <QWebEngineView>
#include <QClipboard> #include <QClipboard>
#include <QRegExp>
class VFile; class VFile;
class QMenu; class QMenu;
@ -42,11 +43,11 @@ private:
const QMimeData *p_mimeData, const QMimeData *p_mimeData,
bool p_removeBackground); bool p_removeBackground);
bool fixImgSrc(QString &p_html);
void removeHtmlFromImageData(QClipboard *p_clipboard, void removeHtmlFromImageData(QClipboard *p_clipboard,
const QMimeData *p_mimeData); const QMimeData *p_mimeData);
bool removeStyles(QString &p_html);
VFile *m_file; VFile *m_file;
// Whether this view has hooked the Copy Image Url action. // Whether this view has hooked the Copy Image Url action.