mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 05:49:53 +08:00
MdEditor: aware of links and images in edit mode
This commit is contained in:
parent
dc1f1c4535
commit
f98c2f5382
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
// 
|
||||
// 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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user