MdEditor: aware of links and images in edit mode

This commit is contained in:
Le Tan 2018-08-17 22:42:36 +08:00
parent dc1f1c4535
commit f98c2f5382
9 changed files with 320 additions and 80 deletions

View File

@ -42,6 +42,8 @@ public:
const QTextDocument *getDocument() const;
const QVector<VElementRegion> &getImageRegions() const;
public slots:
// Parse and rehighlight immediately.
void updateHighlight();
@ -215,6 +217,11 @@ inline const QVector<VElementRegion> &PegMarkdownHighlighter::getHeaderRegions()
return m_result->m_headerRegions;
}
inline const QVector<VElementRegion> &PegMarkdownHighlighter::getImageRegions() const
{
return m_result->m_imageRegions;
}
inline const QSet<int> &PegMarkdownHighlighter::getPossiblePreviewBlocks() const
{
return m_possiblePreviewBlocks;

View File

@ -163,3 +163,42 @@ void VClipboardUtils::setMimeDataLoop(QClipboard *p_clipboard,
VUtils::sleepWait(100 /* ms */);
}
}
QMimeData *linkMimeData(const QString &p_link)
{
QList<QUrl> urls;
urls.append(VUtils::pathToUrl(p_link));
QMimeData *data = new QMimeData();
data->setUrls(urls);
QString text = urls[0].toEncoded();
#if defined(Q_OS_WIN)
if (urls[0].isLocalFile()) {
text = urls[0].toString(QUrl::EncodeSpaces);
}
#endif
data->setText(text);
return data;
}
void VClipboardUtils::setLinkToClipboard(QClipboard *p_clipboard,
const QString &p_link,
QClipboard::Mode p_mode)
{
VClipboardUtils::setMimeDataToClipboard(p_clipboard,
linkMimeData(p_link),
p_mode);
}
void VClipboardUtils::setImageAndLinkToClipboard(QClipboard *p_clipboard,
const QImage &p_image,
const QString &p_link,
QClipboard::Mode p_mode)
{
QMimeData *data = linkMimeData(p_link);
data->setImageData(p_image);
VClipboardUtils::setMimeDataToClipboard(p_clipboard,
data,
p_mode);
}

View File

@ -14,12 +14,21 @@ public:
const QImage &p_image,
QClipboard::Mode p_mode = QClipboard::Clipboard);
static void setImageAndLinkToClipboard(QClipboard *p_clipboard,
const QImage &p_image,
const QString &p_link,
QClipboard::Mode p_mode = QClipboard::Clipboard);
static void setMimeDataToClipboard(QClipboard *p_clipboard,
QMimeData *p_mimeData,
QClipboard::Mode p_mode = QClipboard::Clipboard);
static QMimeData *cloneMimeData(const QMimeData *p_mimeData);
static void setLinkToClipboard(QClipboard *p_clipboard,
const QString &p_link,
QClipboard::Mode p_mode = QClipboard::Clipboard);
private:
VClipboardUtils()
{

View File

@ -42,13 +42,17 @@ extern VConfigManager *g_config;
QVector<QPair<QString, QString>> VUtils::s_availableLanguages;
const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"'\\s]+)\\s*"
const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(\\s*([^\\)\"'\\s]+)\\s*"
"((\"[^\"\\)\\n]*\")|('[^'\\)\\n]*'))?\\s*"
"(=(\\d*)x(\\d*))?\\s*"
"\\)");
const QString VUtils::c_imageTitleRegExp = QString("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]*");
const QString VUtils::c_linkRegExp = QString("\\[([^\\]]*)\\]\\(\\s*([^\\)\"'\\s]+)\\s*"
"((\"[^\"\\)\\n]*\")|('[^'\\)\\n]*'))?\\s*"
"\\)");
const QString VUtils::c_fileNameRegExp = QString("(?:[^\\\\/:\\*\\?\"<>\\|\\s]| )*");
const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]*)\\s*[^`]*$");
@ -276,22 +280,39 @@ QVector<ImageLink> VUtils::fetchImagesFromMarkdownFile(VFile *p_file,
return images;
}
QString VUtils::imageLinkUrlToPath(const QString &p_basePath, const QString &p_url)
QString VUtils::linkUrlToPath(const QString &p_basePath, const QString &p_url)
{
QString path;
QString fullPath;
QFileInfo info(p_basePath, p_url);
if (info.exists()) {
if (info.isNativePath()) {
// Local file.
path = QDir::cleanPath(info.absoluteFilePath());
fullPath = QDir::cleanPath(info.absoluteFilePath());
} else {
path = p_url;
fullPath = p_url;
}
} else {
path = QUrl(p_url).toString();
QString decodedUrl(p_url);
VUtils::decodeUrl(decodedUrl);
QFileInfo dinfo(p_basePath, decodedUrl);
if (dinfo.exists()) {
if (dinfo.isNativePath()) {
// Local file.
fullPath = QDir::cleanPath(dinfo.absoluteFilePath());
} else {
fullPath = p_url;
}
} else {
QUrl url(p_url);
if (url.isLocalFile()) {
fullPath = url.toLocalFile();
} else {
fullPath = url.toString();
}
}
}
return path;
return fullPath;
}
bool VUtils::makePath(const QString &p_path)
@ -1678,3 +1699,74 @@ QPixmap VUtils::svgToPixmap(const QByteArray &p_content,
renderer.render(&painter);
return pm;
}
QString VUtils::fetchImageLinkUrlToPreview(const QString &p_text, int &p_width, int &p_height)
{
QRegExp regExp(VUtils::c_imageLinkRegExp);
p_width = p_height = -1;
int index = regExp.indexIn(p_text);
if (index == -1) {
return QString();
}
int lastIndex = regExp.lastIndexIn(p_text);
if (lastIndex != index) {
return QString();
}
QString tmp(regExp.cap(7));
if (!tmp.isEmpty()) {
p_width = tmp.toInt();
if (p_width <= 0) {
p_width = -1;
}
}
tmp = regExp.cap(8);
if (!tmp.isEmpty()) {
p_height = tmp.toInt();
if (p_height <= 0) {
p_height = -1;
}
}
return regExp.cap(2).trimmed();
}
QString VUtils::fetchImageLinkUrl(const QString &p_link)
{
QRegExp regExp(VUtils::c_imageLinkRegExp);
int index = regExp.indexIn(p_link);
if (index == -1) {
return QString();
}
return regExp.cap(2).trimmed();
}
QString VUtils::fetchLinkUrl(const QString &p_link)
{
QRegExp regExp(VUtils::c_linkRegExp);
int index = regExp.indexIn(p_link);
if (index == -1) {
return QString();
}
return regExp.cap(2).trimmed();
}
QUrl VUtils::pathToUrl(const QString &p_path)
{
QUrl url;
if (QFileInfo::exists(p_path)) {
url = QUrl::fromLocalFile(p_path);
} else {
url = QUrl(p_path);
}
return url;
}

View File

@ -133,7 +133,7 @@ public:
ImageLink::ImageLinkType p_type = ImageLink::All);
// Return the absolute path of @p_url according to @p_basePath.
static QString imageLinkUrlToPath(const QString &p_basePath, const QString &p_url);
static QString linkUrlToPath(const QString &p_basePath, const QString &p_url);
// Create directories along the @p_path.
// @p_path could be /home/tamlok/abc, /home/tamlok/abc/.
@ -365,6 +365,15 @@ public:
const QString &p_background,
qreal p_factor);
// Fetch the image link's URL if there is only one link.
static QString fetchImageLinkUrlToPreview(const QString &p_text, int &p_width, int &p_height);
static QString fetchImageLinkUrl(const QString &p_link);
static QString fetchLinkUrl(const QString &p_link);
static QUrl pathToUrl(const QString &p_path);
// Regular expression for image link.
// ![image title]( http://github.com/tamlok/vnote.jpg "alt text" =200x100)
// Captured texts (need to be trimmed):
@ -381,6 +390,16 @@ public:
// Regular expression for image title.
static const QString c_imageTitleRegExp;
// Regular expression for link.
// [link text]( http://github.com/tamlok "alt text")
// Captured texts (need to be trimmed):
// 1. Link Alt Text (Title);
// 2. Link URL;
// 3. Link Optional Title with double quotes or quotes;
// 4. Unused;
// 5. Unused;
static const QString c_linkRegExp;
// Regular expression for file/directory name.
// Forbidden char: \/:*?"<>| and whitespaces except space.
static const QString c_fileNameRegExp;

View File

@ -24,6 +24,7 @@
#include "dialog/vcopytextashtmldialog.h"
#include "utils/vwebutils.h"
#include "dialog/vinsertlinkdialog.h"
#include "utils/vclipboardutils.h"
extern VWebUtils *g_webUtils;
@ -302,6 +303,8 @@ void VMdEditor::contextMenuEvent(QContextMenuEvent *p_event)
if (textCursor().hasSelection()) {
initCopyAsMenu(actions.isEmpty() ? NULL : actions.last(), menu.data());
} else {
initLinkMenu(actions.isEmpty() ? NULL : actions[0], menu.data(), p_event->pos());
QAction *saveExitAct = new QAction(VIconUtils::menuIcon(":/resources/icons/save_exit.svg"),
tr("&Save Changes And Read"),
menu.data());
@ -1458,3 +1461,142 @@ int VMdEditor::lineNumberAreaWidth() const
{
return VTextEdit::lineNumberAreaWidth();
}
void VMdEditor::initLinkMenu(QAction *p_before, QMenu *p_menu, const QPoint &p_pos)
{
QTextCursor cursor = cursorForPosition(p_pos);
int pos = cursor.position();
QTextBlock block = cursor.block();
const QString text(block.text());
// Image.
QRegExp regExp(VUtils::c_imageLinkRegExp);
if (regExp.indexIn(text) > -1) {
const QVector<VElementRegion> &imgRegs = m_pegHighlighter->getImageRegions();
for (auto const & reg : imgRegs) {
if (!reg.contains(pos)) {
continue;
}
if (reg.m_endPos > block.position() + text.length()) {
return;
}
QString linkText = text.mid(reg.m_startPos - block.position(),
reg.m_endPos - reg.m_startPos);
QString surl = VUtils::fetchImageLinkUrl(linkText);
if (surl.isEmpty()) {
return;
}
QString imgPath = VUtils::linkUrlToPath(m_file->fetchBasePath(), surl);
bool isLocalFile = QFileInfo::exists(imgPath);
QAction *viewImageAct = new QAction(tr("View Image"), p_menu);
connect(viewImageAct, &QAction::triggered,
this, [this, imgPath]() {
QDesktopServices::openUrl(VUtils::pathToUrl(imgPath));
});
p_menu->insertAction(p_before, viewImageAct);
QAction *copyImageLinkAct = new QAction(tr("Copy Image URL"), p_menu);
connect(copyImageLinkAct, &QAction::triggered,
this, [this, imgPath]() {
QClipboard *clipboard = QApplication::clipboard();
VClipboardUtils::setLinkToClipboard(clipboard,
imgPath,
QClipboard::Clipboard);
});
p_menu->insertAction(p_before, copyImageLinkAct);
if (isLocalFile) {
QAction *copyImagePathAct = new QAction(tr("Copy Image Path"), p_menu);
connect(copyImagePathAct, &QAction::triggered,
this, [this, imgPath]() {
QClipboard *clipboard = QApplication::clipboard();
QMimeData *data = new QMimeData();
data->setText(imgPath);
VClipboardUtils::setMimeDataToClipboard(clipboard,
data,
QClipboard::Clipboard);
});
p_menu->insertAction(p_before, copyImagePathAct);
QAction *copyImageAct = new QAction(tr("Copy Image"), p_menu);
connect(copyImageAct, &QAction::triggered,
this, [this, imgPath]() {
QClipboard *clipboard = QApplication::clipboard();
clipboard->clear();
QImage img = VUtils::imageFromFile(imgPath);
if (!img.isNull()) {
VClipboardUtils::setImageAndLinkToClipboard(clipboard,
img,
imgPath,
QClipboard::Clipboard);
}
});
p_menu->insertAction(p_before, copyImageAct);
}
p_menu->insertSeparator(p_before ? p_before : NULL);
return;
}
}
// Link.
QRegExp regExp2(VUtils::c_linkRegExp);
QString linkText;
int p = 0;
while (p < text.size()) {
int idx = text.indexOf(regExp2, p);
if (idx == -1) {
break;
}
p = idx + regExp2.matchedLength();
if (pos >= idx && pos < p) {
linkText = regExp2.cap(2);
break;
}
}
if (linkText.isEmpty()) {
return;
}
QString linkUrl = VUtils::linkUrlToPath(m_file->fetchBasePath(), linkText);
bool isLocalFile = QFileInfo::exists(linkUrl);
QAction *viewLinkAct = new QAction(tr("View Link"), p_menu);
connect(viewLinkAct, &QAction::triggered,
this, [this, linkUrl]() {
QDesktopServices::openUrl(VUtils::pathToUrl(linkUrl));
});
p_menu->insertAction(p_before, viewLinkAct);
QAction *copyLinkAct = new QAction(tr("Copy Link URL"), p_menu);
connect(copyLinkAct, &QAction::triggered,
this, [this, linkUrl]() {
QClipboard *clipboard = QApplication::clipboard();
VClipboardUtils::setLinkToClipboard(clipboard,
linkUrl,
QClipboard::Clipboard);
});
p_menu->insertAction(p_before, copyLinkAct);
if (isLocalFile) {
QAction *copyLinkPathAct = new QAction(tr("Copy Link Path"), p_menu);
connect(copyLinkPathAct, &QAction::triggered,
this, [this, linkUrl]() {
QClipboard *clipboard = QApplication::clipboard();
QMimeData *data = new QMimeData();
data->setText(linkUrl);
VClipboardUtils::setMimeDataToClipboard(clipboard,
data,
QClipboard::Clipboard);
});
p_menu->insertAction(p_before, copyLinkPathAct);
}
p_menu->insertSeparator(p_before ? p_before : NULL);
}

View File

@ -277,6 +277,8 @@ private:
void initPasteAsBlockQuoteMenu(QMenu *p_menu);
void initLinkMenu(QAction *p_before, QMenu *p_menu, const QPoint &p_pos);
void insertImageLink(const QString &p_text, const QString &p_url);
void setFontPointSizeByStyleSheet(int p_ptSize);

View File

@ -180,44 +180,9 @@ void VPreviewManager::fetchImageLinksFromRegions(QVector<VElementRegion> p_image
}
}
QString VPreviewManager::fetchImageUrlToPreview(const QString &p_text, int &p_width, int &p_height)
{
QRegExp regExp(VUtils::c_imageLinkRegExp);
p_width = p_height = -1;
int index = regExp.indexIn(p_text);
if (index == -1) {
return QString();
}
int lastIndex = regExp.lastIndexIn(p_text);
if (lastIndex != index) {
return QString();
}
QString tmp(regExp.cap(7));
if (!tmp.isEmpty()) {
p_width = tmp.toInt();
if (p_width <= 0) {
p_width = -1;
}
}
tmp = regExp.cap(8);
if (!tmp.isEmpty()) {
p_height = tmp.toInt();
if (p_height <= 0) {
p_height = -1;
}
}
return regExp.cap(2).trimmed();
}
void VPreviewManager::fetchImageInfoToPreview(const QString &p_text, ImageLinkInfo &p_info)
{
QString surl = fetchImageUrlToPreview(p_text, p_info.m_width, p_info.m_height);
QString surl = VUtils::fetchImageLinkUrlToPreview(p_text, p_info.m_width, p_info.m_height);
p_info.m_linkShortUrl = surl;
if (surl.isEmpty()) {
p_info.m_linkUrl = surl;
@ -225,39 +190,7 @@ void VPreviewManager::fetchImageInfoToPreview(const QString &p_text, ImageLinkIn
}
const VFile *file = m_editor->getFile();
QString imagePath;
QFileInfo info(file->fetchBasePath(), surl);
if (info.exists()) {
if (info.isNativePath()) {
// Local file.
imagePath = QDir::cleanPath(info.absoluteFilePath());
} else {
imagePath = surl;
}
} else {
QString decodedUrl(surl);
VUtils::decodeUrl(decodedUrl);
QFileInfo dinfo(file->fetchBasePath(), decodedUrl);
if (dinfo.exists()) {
if (dinfo.isNativePath()) {
// Local file.
imagePath = QDir::cleanPath(dinfo.absoluteFilePath());
} else {
imagePath = surl;
}
} else {
QUrl url(surl);
if (url.isLocalFile()) {
imagePath = url.toLocalFile();
} else {
imagePath = url.toString();
}
}
}
p_info.m_linkUrl = imagePath;
p_info.m_linkUrl = VUtils::linkUrlToPath(file->fetchBasePath(), surl);
}
QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)

View File

@ -180,9 +180,6 @@ private:
void fetchImageLinksFromRegions(QVector<VElementRegion> p_imageRegions,
QVector<ImageLinkInfo> &p_imageLinks);
// Fetch the image link's URL if there is only one link.
QString fetchImageUrlToPreview(const QString &p_text, int &p_width, int &p_height);
// Fetch the image's full path and size.
void fetchImageInfoToPreview(const QString &p_text, ImageLinkInfo &p_info);