From 5cc8d6c8f1825f7a05cea5f16a551a1449457737 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Sun, 30 Sep 2018 23:00:58 +0800 Subject: [PATCH] MdEditor: support downloading images to local when Parse&Paste --- src/resources/vnote.ini | 3 ++ src/utils/vutils.cpp | 10 ++-- src/utils/vutils.h | 8 +-- src/vconfigmanager.cpp | 1 + src/vconfigmanager.h | 10 ++++ src/vmdeditoperations.cpp | 62 +++++++++++++++------ src/vmdeditoperations.h | 19 +++++-- src/vmdeditor.cpp | 110 +++++++++++++++++++++++++++++++++++++- src/vmdeditor.h | 2 + 9 files changed, 196 insertions(+), 29 deletions(-) diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index 23b85ef5..1ad03c71 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -296,6 +296,9 @@ vim_leader_key=" " ; Enable tab highlight enable_tab_highlight=false +; Download images in parsed HTML text +parse_paste_local_image=true + [export] ; Path of the wkhtmltopdf tool wkhtmltopdf=wkhtmltopdf diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 946b8038..4c559ff5 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -42,10 +42,12 @@ extern VConfigManager *g_config; QVector> VUtils::s_availableLanguages; -const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(\\s*([^\\)\"'\\s]+)\\s*" - "((\"[^\"\\)\\n]*\")|('[^'\\)\\n]*'))?\\s*" - "(=(\\d*)x(\\d*))?\\s*" - "\\)"); +const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]" + "\\(\\s*" + "([^\\)\"'\\s]+)" + "(\\s*(\"[^\"\\)\\n]*\")|('[^'\\)\\n]*'))?" + "(\\s*=(\\d*)x(\\d*))?" + "\\s*\\)"); const QString VUtils::c_imageTitleRegExp = QString("[^\\[\\]]*"); diff --git a/src/utils/vutils.h b/src/utils/vutils.h index 0b6dcbfb..bfd2e195 100644 --- a/src/utils/vutils.h +++ b/src/utils/vutils.h @@ -133,6 +133,9 @@ public: static QVector fetchImagesFromMarkdownFile(VFile *p_file, ImageLink::ImageLinkType p_type = ImageLink::All); + // Use PegParser to parse @p_content to get all image link regions. + static QVector fetchImageRegionsUsingParser(const QString &p_content); + // Return the absolute path of @p_url according to @p_basePath. static QString linkUrlToPath(const QString &p_basePath, const QString &p_url); @@ -389,7 +392,7 @@ public: // 3. Image Optional Title with double quotes or quotes; // 4. Unused; // 5. Unused; - // 6. Unused; + // 6. Width and height text; // 7. Width; // 8. Height; static const QString c_imageLinkRegExp; @@ -440,9 +443,6 @@ private: static void initAvailableLanguage(); - // Use PegParser to parse @p_content to get all image link regions. - static QVector fetchImageRegionsUsingParser(const QString &p_content); - // Delete file/directory specified by @p_path by moving it to the recycle bin // folder @p_recycleBinFolderPath. static bool deleteFile(const QString &p_recycleBinFolderPath, diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index ff464063..eeb2bb2a 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -356,6 +356,7 @@ void VConfigManager::initEditorConfigs() m_enableTabHighlight = getConfigFromSettings("editor", "enable_tab_highlight").toBool(); + m_parsePasteLocalImage = getConfigFromSettings("editor", "parse_paste_local_image").toBool(); } void VConfigManager::initSettings() diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index 542090bd..93d6cf50 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -575,6 +575,8 @@ public: QList getKeyboardLayoutMappingKeys() const; + bool getParsePasteLocalImage() const; + private: // Look up a config from user and default settings. QVariant getConfigFromSettings(const QString §ion, const QString &key) const; @@ -1027,6 +1029,9 @@ private: // Whether insert new note in front. bool m_insertNewNoteInFront; + // Whether download image from parse and paste. + bool m_parsePasteLocalImage; + // The name of the config file in each directory. static const QString c_dirConfigFile; @@ -2684,4 +2689,9 @@ inline QList VConfigManager::getKeyboardLayoutMappingKeys() const return keys; } + +inline bool VConfigManager::getParsePasteLocalImage() const +{ + return m_parsePasteLocalImage; +} #endif // VCONFIGMANAGER_H diff --git a/src/vmdeditoperations.cpp b/src/vmdeditoperations.cpp index dddab771..46c42100 100644 --- a/src/vmdeditoperations.cpp +++ b/src/vmdeditoperations.cpp @@ -101,20 +101,44 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, mdEditor->imageInserted(filePath, url); } -void VMdEditOperations::insertImageFromPath(const QString &title, const QString &path, - const QString &folderInLink, const QString &oriImagePath) +void VMdEditOperations::insertImageFromPath(const QString &p_title, + const QString &p_folderPath, + const QString &p_folderInLink, + const QString &p_srcImagePath) { - QString fileName = VUtils::generateImageFileName(path, title, QFileInfo(oriImagePath).suffix()); - QString filePath = QDir(path).filePath(fileName); + insertImageFromPath(p_title, + p_folderPath, + p_folderInLink, + p_srcImagePath, + true, + QString(), + QString()); +} + +void VMdEditOperations::insertImageFromPath(const QString &p_title, + const QString &p_folderPath, + const QString &p_folderInLink, + const QString &p_srcImagePath, + bool p_insertText, + QString &p_destImagePath, + QString &p_urlInLink) +{ + p_destImagePath.clear(); + p_urlInLink.clear(); + + QString fileName = VUtils::generateImageFileName(p_folderPath, + p_title, + QFileInfo(p_srcImagePath).suffix()); + QString filePath = QDir(p_folderPath).filePath(fileName); V_ASSERT(!QFile(filePath).exists()); QString errStr; - bool ret = VUtils::makePath(path); + bool ret = VUtils::makePath(p_folderPath); if (!ret) { errStr = tr("Fail to create image folder %2.") - .arg(g_config->c_dataTextStyle).arg(path); + .arg(g_config->c_dataTextStyle).arg(p_folderPath); } else { - ret = QFile::copy(oriImagePath, filePath); + ret = QFile::copy(p_srcImagePath, filePath); if (!ret) { errStr = tr("Fail to copy image %2.") .arg(g_config->c_dataTextStyle).arg(filePath); @@ -122,8 +146,10 @@ void VMdEditOperations::insertImageFromPath(const QString &title, const QString } if (!ret) { - VUtils::showMessage(QMessageBox::Warning, tr("Warning"), - tr("Fail to insert image %2.").arg(g_config->c_dataTextStyle).arg(title), + VUtils::showMessage(QMessageBox::Warning, + tr("Warning"), + tr("Fail to insert image %2.") + .arg(g_config->c_dataTextStyle).arg(p_title), errStr, QMessageBox::Ok, QMessageBox::Ok, @@ -131,15 +157,19 @@ void VMdEditOperations::insertImageFromPath(const QString &title, const QString return; } - QString url = QString("%1/%2").arg(folderInLink).arg(fileName); - QString md = QString("![%1](%2)").arg(title).arg(url); - insertText(md); + p_urlInLink = QString("%1/%2").arg(p_folderInLink).arg(fileName); + p_destImagePath = filePath; - qDebug() << "insert image" << title << filePath; + if (p_insertText) { + QString md = QString("![%1](%2)").arg(p_title).arg(p_urlInLink); + insertText(md); + } - VMdEditor *mdEditor = dynamic_cast(m_editor); + qDebug() << "insert image" << p_title << filePath; + + VMdEditor *mdEditor = static_cast(m_editor); Q_ASSERT(mdEditor); - mdEditor->imageInserted(filePath, url); + mdEditor->imageInserted(filePath, p_urlInLink); } bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl) @@ -177,7 +207,7 @@ bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl) VDownloader *downloader = new VDownloader(&dialog); connect(downloader, &VDownloader::downloadFinished, &dialog, &VInsertImageDialog::imageDownloaded); - downloader->download(imageUrl.toString()); + downloader->download(imageUrl); } if (dialog.exec() == QDialog::Accepted) { if (isLocal) { diff --git a/src/vmdeditoperations.h b/src/vmdeditoperations.h index cb4f2c1b..ca697b48 100644 --- a/src/vmdeditoperations.h +++ b/src/vmdeditoperations.h @@ -34,11 +34,22 @@ public: bool insertImageLink(const QString &p_linkText, const QString &p_linkUrl); + // @p_urlInLink and @p_destImagePath will be empty on failure. + void insertImageFromPath(const QString &p_title, + const QString &p_folderPath, + const QString &p_folderInLink, + const QString &p_srcImagePath, + bool p_insertText, + QString &p_destImagePath, + QString &p_urlInLink); + private: - // Insert image from @oriImagePath as @path. - // @folderInLink: the folder part in the image link. - void insertImageFromPath(const QString &title, const QString &path, - const QString &folderInLink, const QString &oriImagePath); + // Insert image from @p_srcImagePath as to @p_folderPath. + // @p_folderInLink: the folder part in the image link. + void insertImageFromPath(const QString &p_title, + const QString &p_folderPath, + const QString &p_folderInLink, + const QString &p_srcImagePath); // @title: title of the inserted image; // @path: the image folder path to insert the image in; diff --git a/src/vmdeditor.cpp b/src/vmdeditor.cpp index 9893caf5..6488f803 100644 --- a/src/vmdeditor.cpp +++ b/src/vmdeditor.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include "vdocument.h" #include "utils/veditutils.h" @@ -29,6 +31,7 @@ #include "vplantumlhelper.h" #include "vgraphvizhelper.h" #include "vmdtab.h" +#include "vdownloader.h" extern VWebUtils *g_webUtils; @@ -1169,7 +1172,15 @@ void VMdEditor::htmlToTextFinished(int p_id, int p_timeStamp, const QString &p_t { Q_UNUSED(p_id); if (m_copyTimeStamp == p_timeStamp && !p_text.isEmpty()) { - m_editOps->insertText(p_text); + emit m_object->statusMessage(tr("Inserting parsed Markdown text")); + + QString text(p_text); + if (g_config->getParsePasteLocalImage()) { + // May take long time. + replaceTextWithLocalImages(text); + } + + m_editOps->insertText(text); emit m_object->statusMessage(tr("Parsed Markdown text inserted")); } } @@ -1991,3 +2002,100 @@ bool VMdEditor::processTextFromMimeData(const QMimeData *p_source) Q_ASSERT(p_source->hasText()); return false; } + +void VMdEditor::replaceTextWithLocalImages(QString &p_text) +{ + QVector regs = VUtils::fetchImageRegionsUsingParser(p_text); + if (regs.isEmpty()) { + return; + } + + // Sort it in ascending order. + std::sort(regs.begin(), regs.end()); + + QProgressDialog proDlg(tr("Fetching images to local..."), + tr("Abort"), + 0, + regs.size(), + this); + proDlg.setWindowModality(Qt::WindowModal); + proDlg.setWindowTitle(tr("Fetching Images To Local")); + + QRegExp regExp(VUtils::c_imageLinkRegExp); + for (int i = regs.size() - 1; i >= 0; --i) { + proDlg.setValue(regs.size() - 1 - i); + if (proDlg.wasCanceled()) { + break; + } + + const VElementRegion ® = regs[i]; + QString linkText = p_text.mid(reg.m_startPos, reg.m_endPos - reg.m_startPos); + if (regExp.indexIn(linkText) == -1) { + continue; + } + + QString imageTitle = regExp.cap(1).trimmed(); + QString imageUrl = regExp.cap(2).trimmed(); + + proDlg.setLabelText(tr("Fetching image: %1").arg(imageUrl)); + + QString destImagePath, urlInLink; + + // Only handle absolute file path or network path. + QString srcImagePath; + QFileInfo info(imageUrl); + + // For network image. + QString suffix = info.suffix(); + QScopedPointer tmpFile; + + if (info.exists() && info.isAbsolute()) { + // Absolute local path. + srcImagePath = info.absoluteFilePath(); + } else { + // Network path. + QByteArray data = VDownloader::downloadSync(QUrl(imageUrl)); + if (!data.isEmpty()) { + QString xx = suffix.isEmpty() ? "XXXXXX" : "XXXXXX."; + tmpFile.reset(new QTemporaryFile(QDir::tempPath() + + QDir::separator() + + xx + + suffix)); + if (tmpFile->open() && tmpFile->write(data) > -1) { + srcImagePath = tmpFile->fileName(); + } + } + } + + if (srcImagePath.isEmpty()) { + continue; + } + + // Insert image without inserting text. + auto ops = static_cast(m_editOps); + ops->insertImageFromPath(imageTitle, + m_file->fetchImageFolderPath(), + m_file->getImageFolderInLink(), + srcImagePath, + false, + destImagePath, + urlInLink); + if (urlInLink.isEmpty()) { + continue; + } + + // Replace URL in link. + QString newLink = QString("![%1](%2%3%4)") + .arg(imageTitle) + .arg(urlInLink) + .arg(regExp.cap(3)) + .arg(regExp.cap(6)); + p_text.replace(reg.m_startPos, + reg.m_endPos - reg.m_startPos, + newLink); + + qDebug() << "replace link" << linkText << "to" << newLink; + } + + proDlg.setValue(regs.size()); +} diff --git a/src/vmdeditor.h b/src/vmdeditor.h index a44f773b..cce3897d 100644 --- a/src/vmdeditor.h +++ b/src/vmdeditor.h @@ -315,6 +315,8 @@ private: bool processTextFromMimeData(const QMimeData *p_source); + void replaceTextWithLocalImages(QString &p_text); + PegMarkdownHighlighter *m_pegHighlighter; VCodeBlockHighlightHelper *m_cbHighlighter;