Browse Source

bug-fix: fix images when cutting files

1. De-duplicate the images occur multiple times in the note;
2. Refresh the read mode and edit mode preview after the directory path
change;
3. Update init images and inserted images after the directory path
change;
Le Tan 8 years ago
parent
commit
03122a24db

+ 3 - 2
src/resources/markdown-it.js

@@ -51,7 +51,8 @@ var mdit = window.markdownit({
                 return hljs.highlightAuto(str).value;
             }
         } else {
-            return str;
+            // Use external default escaping.
+            return '';
         }
     }
 });
@@ -67,7 +68,7 @@ mdit = mdit.use(window.markdownitHeadingAnchor, {
         toc.push({
             level: getHeadingLevel(openToken.tag),
             anchor: anchor,
-            title: escapeHtml(inlineToken.content)
+            title: mdit.utils.escapeHtml(inlineToken.content)
         });
     }
 });

+ 27 - 2
src/utils/vutils.cpp

@@ -216,6 +216,9 @@ QVector<ImageLink> VUtils::fetchImagesFromMarkdownFile(VFile *p_file,
         return images;
     }
 
+    // Used to de-duplicate the links. Url as the key.
+    QSet<QString> fetchedLinks;
+
     QVector<VElementRegion> regions = fetchImageRegionsUsingParser(text);
     QRegExp regExp(c_imageLinkRegExp);
     QString basePath = p_file->fetchBasePath();
@@ -231,6 +234,7 @@ QVector<ImageLink> VUtils::fetchImagesFromMarkdownFile(VFile *p_file,
         QString imageUrl = regExp.capturedTexts()[2].trimmed();
 
         ImageLink link;
+        link.m_url = imageUrl;
         QFileInfo info(basePath, imageUrl);
         if (info.exists()) {
             if (info.isNativePath()) {
@@ -254,8 +258,11 @@ QVector<ImageLink> VUtils::fetchImagesFromMarkdownFile(VFile *p_file,
         }
 
         if (link.m_type & p_type) {
-            images.push_back(link);
-            qDebug() << "fetch one image:" << link.m_type << link.m_path;
+            if (!fetchedLinks.contains(link.m_url)) {
+                fetchedLinks.insert(link.m_url);
+                images.push_back(link);
+                qDebug() << "fetch one image:" << link.m_type << link.m_path << link.m_url;
+            }
         }
     }
 
@@ -266,6 +273,24 @@ QVector<ImageLink> VUtils::fetchImagesFromMarkdownFile(VFile *p_file,
     return images;
 }
 
+QString VUtils::imageLinkUrlToPath(const QString &p_basePath, const QString &p_url)
+{
+    QString path;
+    QFileInfo info(p_basePath, p_url);
+    if (info.exists()) {
+        if (info.isNativePath()) {
+            // Local file.
+            path = QDir::cleanPath(info.absoluteFilePath());
+        } else {
+            path = p_url;
+        }
+    } else {
+        path = QUrl(p_url).toString();
+    }
+
+    return path;
+}
+
 bool VUtils::makePath(const QString &p_path)
 {
     if (p_path.isEmpty()) {

+ 7 - 0
src/utils/vutils.h

@@ -67,6 +67,10 @@ struct ImageLink
     };
 
     QString m_path;
+
+    // The url text in the link.
+    QString m_url;
+
     ImageLinkType m_type;
 };
 
@@ -121,6 +125,9 @@ public:
     static QVector<ImageLink> fetchImagesFromMarkdownFile(VFile *p_file,
                                                           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);
+
     // Create directories along the @p_path.
     // @p_path could be /home/tamlok/abc, /home/tamlok/abc/.
     static bool makePath(const QString &p_path);

+ 9 - 0
src/vconstants.h

@@ -128,4 +128,13 @@ enum class CursorBlock
     LeftSide
 };
 
+enum class UpdateAction
+{
+    // The info of a file/directory has been changed.
+    InfoChanged = 0,
+
+    // The file/directory has been moved.
+    Moved
+};
+
 #endif

+ 2 - 2
src/vdirectorytree.cpp

@@ -602,7 +602,7 @@ void VDirectoryTree::editDirectoryInfo()
 
         fillTreeItem(curItem, curDir);
 
-        emit directoryUpdated(curDir);
+        emit directoryUpdated(curDir, UpdateAction::InfoChanged);
     }
 }
 
@@ -856,7 +856,7 @@ void VDirectoryTree::pasteDirectories(VDirectory *p_destDir,
             }
 
             // Broadcast this update
-            emit directoryUpdated(destDir);
+            emit directoryUpdated(destDir, p_isCut ? UpdateAction::Moved : UpdateAction::InfoChanged);
         }
     }
 

+ 2 - 1
src/vdirectorytree.h

@@ -11,6 +11,7 @@
 #include "vdirectory.h"
 #include "vnotebook.h"
 #include "vnavigationmode.h"
+#include "vconstants.h"
 
 class VEditArea;
 class QLabel;
@@ -35,7 +36,7 @@ public:
 signals:
     void currentDirectoryChanged(VDirectory *p_directory);
 
-    void directoryUpdated(const VDirectory *p_directory);
+    void directoryUpdated(const VDirectory *p_directory, UpdateAction p_act);
 
 public slots:
     // Set directory tree to display a given notebook @p_notebook.

+ 4 - 4
src/veditarea.cpp

@@ -521,19 +521,19 @@ bool VEditArea::isFileOpened(const VFile *p_file)
     return !findTabsByFile(p_file).isEmpty();
 }
 
-void VEditArea::handleFileUpdated(const VFile *p_file)
+void VEditArea::handleFileUpdated(const VFile *p_file, UpdateAction p_act)
 {
     int nrWin = splitter->count();
     for (int i = 0; i < nrWin; ++i) {
-        getWindow(i)->updateFileInfo(p_file);
+        getWindow(i)->updateFileInfo(p_file, p_act);
     }
 }
 
-void VEditArea::handleDirectoryUpdated(const VDirectory *p_dir)
+void VEditArea::handleDirectoryUpdated(const VDirectory *p_dir, UpdateAction p_act)
 {
     int nrWin = splitter->count();
     for (int i = 0; i < nrWin; ++i) {
-        getWindow(i)->updateDirectoryInfo(p_dir);
+        getWindow(i)->updateDirectoryInfo(p_dir, p_act);
     }
 }
 

+ 4 - 2
src/veditarea.h

@@ -115,8 +115,10 @@ public slots:
     // Scroll current tab to @p_header.
     void scrollToHeader(const VHeaderPointer &p_header);
 
-    void handleFileUpdated(const VFile *p_file);
-    void handleDirectoryUpdated(const VDirectory *p_dir);
+    void handleFileUpdated(const VFile *p_file, UpdateAction p_act);
+
+    void handleDirectoryUpdated(const VDirectory *p_dir, UpdateAction p_act);
+
     void handleNotebookUpdated(const VNotebook *p_notebook);
 
 private slots:

+ 6 - 0
src/vedittab.cpp

@@ -188,3 +188,9 @@ void VEditTab::reloadFromDisk()
 void VEditTab::writeBackupFile()
 {
 }
+
+void VEditTab::handleFileOrDirectoryChange(bool p_isFile, UpdateAction p_act)
+{
+    Q_UNUSED(p_isFile);
+    Q_UNUSED(p_act);
+}

+ 3 - 0
src/vedittab.h

@@ -111,6 +111,9 @@ public:
     // Reload file from disk and reload the editor.
     void reloadFromDisk();
 
+    // Handle the change of file or directory, such as the file has been moved.
+    virtual void handleFileOrDirectoryChange(bool p_isFile, UpdateAction p_act);
+
 public slots:
     // Enter edit mode
     virtual void editFile() = 0;

+ 5 - 2
src/veditwindow.cpp

@@ -793,18 +793,20 @@ void VEditWindow::handleTabVimStatusUpdated(const VVim *p_vim)
     }
 }
 
-void VEditWindow::updateFileInfo(const VFile *p_file)
+void VEditWindow::updateFileInfo(const VFile *p_file, UpdateAction p_act)
 {
     if (!p_file) {
         return;
     }
+
     int idx = findTabByFile(p_file);
     if (idx > -1) {
         updateTabStatus(idx);
+        getTab(idx)->handleFileOrDirectoryChange(true, p_act);
     }
 }
 
-void VEditWindow::updateDirectoryInfo(const VDirectory *p_dir)
+void VEditWindow::updateDirectoryInfo(const VDirectory *p_dir, UpdateAction p_act)
 {
     if (!p_dir) {
         return;
@@ -816,6 +818,7 @@ void VEditWindow::updateDirectoryInfo(const VDirectory *p_dir)
         QPointer<VFile> file = editor->getFile();
         if (p_dir->containsFile(file)) {
             updateTabStatus(i);
+            editor->handleFileOrDirectoryChange(false, p_act);
         }
     }
 }

+ 4 - 2
src/veditwindow.h

@@ -43,8 +43,10 @@ public:
     // Scroll current tab to header @p_header.
     void scrollToHeader(const VHeaderPointer &p_header);
 
-    void updateFileInfo(const VFile *p_file);
-    void updateDirectoryInfo(const VDirectory *p_dir);
+    void updateFileInfo(const VFile *p_file, UpdateAction p_act);
+
+    void updateDirectoryInfo(const VDirectory *p_dir, UpdateAction p_act);
+
     void updateNotebookInfo(const VNotebook *p_notebook);
 
     VEditTab *getCurrentTab() const;

+ 2 - 2
src/vfilelist.cpp

@@ -247,7 +247,7 @@ void VFileList::fileInfo(VNoteFile *p_file)
             fillItem(item, p_file);
         }
 
-        emit fileUpdated(p_file);
+        emit fileUpdated(p_file, UpdateAction::InfoChanged);
     }
 }
 
@@ -800,7 +800,7 @@ void VFileList::pasteFiles(VDirectory *p_destDir,
 
         if (destFile) {
             ++nrPasted;
-            emit fileUpdated(destFile);
+            emit fileUpdated(destFile, p_isCut ? UpdateAction::Moved : UpdateAction::InfoChanged);
         }
     }
 

+ 1 - 1
src/vfilelist.h

@@ -69,7 +69,7 @@ signals:
                      OpenFileMode p_mode = OpenFileMode::Read,
                      bool p_forceMode = false);
 
-    void fileUpdated(const VNoteFile *p_file);
+    void fileUpdated(const VNoteFile *p_file, UpdateAction p_act);
 
 private slots:
     void contextMenuRequested(QPoint pos);

+ 6 - 4
src/vmdeditoperations.cpp

@@ -86,14 +86,15 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
         return;
     }
 
-    QString md = QString("![%1](%2/%3)").arg(title).arg(folderInLink).arg(fileName);
+    QString url = QString("%1/%2").arg(folderInLink).arg(fileName);
+    QString md = QString("![%1](%2)").arg(title).arg(url);
     insertTextAtCurPos(md);
 
     qDebug() << "insert image" << title << filePath;
 
     VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
     Q_ASSERT(mdEditor);
-    mdEditor->imageInserted(filePath);
+    mdEditor->imageInserted(filePath, url);
 }
 
 void VMdEditOperations::insertImageFromPath(const QString &title, const QString &path,
@@ -126,14 +127,15 @@ void VMdEditOperations::insertImageFromPath(const QString &title, const QString
         return;
     }
 
-    QString md = QString("![%1](%2/%3)").arg(title).arg(folderInLink).arg(fileName);
+    QString url = QString("%1/%2").arg(folderInLink).arg(fileName);
+    QString md = QString("![%1](%2)").arg(title).arg(url);
     insertTextAtCurPos(md);
 
     qDebug() << "insert image" << title << filePath;
 
     VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
     Q_ASSERT(mdEditor);
-    mdEditor->imageInserted(filePath);
+    mdEditor->imageInserted(filePath, url);
 }
 
 bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)

+ 93 - 10
src/vmdeditor.cpp

@@ -125,6 +125,7 @@ void VMdEditor::beginEdit()
 void VMdEditor::endEdit()
 {
     setReadOnlyAndHighlightCurrentLine(true);
+
     clearUnusedImages();
 }
 
@@ -138,6 +139,10 @@ void VMdEditor::saveFile()
 
     m_file->setContent(toPlainText());
     setModified(false);
+
+    clearUnusedImages();
+
+    initInitImages();
 }
 
 void VMdEditor::reloadFile()
@@ -544,7 +549,7 @@ void VMdEditor::clearUnusedImages()
     QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file,
                                                                     ImageLink::LocalRelativeInternal);
 
-    QVector<QString> unusedImages;
+    QSet<QString> unusedImages;
 
     if (!m_insertedImages.isEmpty()) {
         for (int i = 0; i < m_insertedImages.size(); ++i) {
@@ -563,7 +568,7 @@ void VMdEditor::clearUnusedImages()
 
             // This inserted image is no longer in the file.
             if (j == images.size()) {
-                unusedImages.push_back(link.m_path);
+                unusedImages.insert(link.m_path);
             }
         }
 
@@ -584,7 +589,7 @@ void VMdEditor::clearUnusedImages()
 
         // Original local relative image is no longer in the file.
         if (j == images.size()) {
-            unusedImages.push_back(link.m_path);
+            unusedImages.insert(link.m_path);
         }
     }
 
@@ -621,27 +626,27 @@ void VMdEditor::clearUnusedImages()
                 g_config->setConfirmImagesCleanUp(dialog.getAskAgainEnabled());
 
                 for (auto const & item : items) {
-                    unusedImages.push_back(item.m_name);
+                    unusedImages.insert(item.m_name);
                 }
             }
         }
 
-        for (int i = 0; i < unusedImages.size(); ++i) {
+        for (auto const & item : unusedImages) {
             bool ret = false;
             if (m_file->getType() == FileType::Note) {
                 const VNoteFile *tmpFile = dynamic_cast<const VNoteFile *>((VFile *)m_file);
-                ret = VUtils::deleteFile(tmpFile->getNotebook(), unusedImages[i], false);
+                ret = VUtils::deleteFile(tmpFile->getNotebook(), item, false);
             } else if (m_file->getType() == FileType::Orphan) {
                 const VOrphanFile *tmpFile = dynamic_cast<const VOrphanFile *>((VFile *)m_file);
-                ret = VUtils::deleteFile(tmpFile, unusedImages[i], false);
+                ret = VUtils::deleteFile(tmpFile, item, false);
             } else {
                 Q_ASSERT(false);
             }
 
             if (!ret) {
-                qWarning() << "fail to delete unused original image" << unusedImages[i];
+                qWarning() << "fail to delete unused original image" << item;
             } else {
-                qDebug() << "delete unused image" << unusedImages[i];
+                qDebug() << "delete unused image" << item;
             }
         }
     }
@@ -731,10 +736,11 @@ void VMdEditor::insertFromMimeData(const QMimeData *p_source)
     VTextEdit::insertFromMimeData(p_source);
 }
 
-void VMdEditor::imageInserted(const QString &p_path)
+void VMdEditor::imageInserted(const QString &p_path, const QString &p_url)
 {
     ImageLink link;
     link.m_path = p_path;
+    link.m_url = p_url;
     if (m_file->useRelativeImageFolder()) {
         link.m_type = ImageLink::LocalRelativeInternal;
     } else {
@@ -907,3 +913,80 @@ void VMdEditor::setContent(const QString &p_content, bool p_modified)
         setPlainText(p_content);
     }
 }
+
+void VMdEditor::refreshPreview()
+{
+    m_previewMgr->refreshPreview();
+}
+
+void VMdEditor::updateInitAndInsertedImages(bool p_fileChanged, UpdateAction p_act)
+{
+    if (p_fileChanged && p_act == UpdateAction::InfoChanged) {
+        return;
+    }
+
+    if (!isModified()) {
+        Q_ASSERT(m_insertedImages.isEmpty());
+        m_insertedImages.clear();
+
+        if (!m_initImages.isEmpty()) {
+            // Re-generate init images.
+            initInitImages();
+        }
+
+        return;
+    }
+
+    // Update init images.
+    QVector<ImageLink> tmp = m_initImages;
+    initInitImages();
+    Q_ASSERT(tmp.size() == m_initImages.size());
+
+    QDir dir(m_file->fetchBasePath());
+
+    // File has been moved.
+    if (p_fileChanged) {
+        // Since we clear unused images once user save the note, all images
+        // in m_initImages now are moved already.
+
+        // Update inserted images.
+        // Inserted images should be moved manually here. Then update all the
+        // paths.
+        for (auto & link : m_insertedImages) {
+            if (link.m_type == ImageLink::LocalAbsolute) {
+                continue;
+            }
+
+            QString newPath = QDir::cleanPath(dir.absoluteFilePath(link.m_url));
+            if (VUtils::equalPath(link.m_path, newPath)) {
+                continue;
+            }
+
+            if (!VUtils::copyFile(link.m_path, newPath, true)) {
+                VUtils::showMessage(QMessageBox::Warning,
+                                    tr("Warning"),
+                                    tr("Fail to move unsaved inserted image %1 to %2.")
+                                    .arg(link.m_path)
+                                    .arg(newPath),
+                                    tr("Please check it manually to avoid image loss."),
+                                    QMessageBox::Ok,
+                                    QMessageBox::Ok,
+                                    this);
+                continue;
+            }
+
+            link.m_path = newPath;
+        }
+    } else {
+        // Directory changed.
+        // Update inserted images.
+        for (auto & link : m_insertedImages) {
+            if (link.m_type == ImageLink::LocalAbsolute) {
+                continue;
+            }
+
+            QString newPath = QDir::cleanPath(dir.absoluteFilePath(link.m_url));
+            link.m_path = newPath;
+        }
+    }
+}

+ 7 - 2
src/vmdeditor.h

@@ -45,7 +45,8 @@ public:
 
     // An image has been inserted. The image is relative.
     // @p_path is the absolute path of the inserted image.
-    void imageInserted(const QString &p_path);
+    // @p_url is the URL text within ().
+    void imageInserted(const QString &p_path, const QString &p_url);
 
     // Scroll to header @p_blockNumber.
     // Return true if @p_blockNumber is valid to scroll to.
@@ -59,6 +60,11 @@ public:
 
     void setContent(const QString &p_content, bool p_modified = false) Q_DECL_OVERRIDE;
 
+    void refreshPreview();
+
+    // Update m_initImages and m_insertedImages to handle the change of the note path.
+    void updateInitAndInsertedImages(bool p_fileChanged, UpdateAction p_act);
+
 public slots:
     bool jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat) Q_DECL_OVERRIDE;
 
@@ -217,5 +223,4 @@ private:
 
     bool m_freshEdit;
 };
-
 #endif // VMDEDITOR_H

+ 15 - 0
src/vmdtab.cpp

@@ -982,3 +982,18 @@ void VMdTab::updateCursorStatus()
 {
     emit statusUpdated(fetchTabInfo(VEditTabInfo::InfoType::Cursor));
 }
+
+void VMdTab::handleFileOrDirectoryChange(bool p_isFile, UpdateAction p_act)
+{
+    // Reload the web view with new base URL.
+    m_headerFromEditMode = m_currentHeader;
+    m_webViewer->setHtml(VUtils::generateHtmlTemplate(m_mdConType, false),
+                         m_file->getBaseUrl());
+
+    if (m_editor) {
+        m_editor->updateInitAndInsertedImages(p_isFile, p_act);
+
+        // Refresh the previewed images in edit mode.
+        m_editor->refreshPreview();
+    }
+}

+ 2 - 0
src/vmdtab.h

@@ -85,6 +85,8 @@ public:
 
     void reload() Q_DECL_OVERRIDE;
 
+    void handleFileOrDirectoryChange(bool p_isFile, UpdateAction p_act) Q_DECL_OVERRIDE;
+
 public slots:
     // Enter edit mode.
     void editFile() Q_DECL_OVERRIDE;

+ 7 - 0
src/vnotefile.cpp

@@ -511,8 +511,15 @@ bool VNoteFile::copyFile(VDirectory *p_destDir,
 
     // Copy images.
     QDir parentDir(destFile->fetchBasePath());
+    QSet<QString> processedImages;
     for (int i = 0; i < images.size(); ++i) {
         const ImageLink &link = images[i];
+        if (processedImages.contains(link.m_path)) {
+            continue;
+        }
+
+        processedImages.insert(link.m_path);
+
         if (!QFileInfo::exists(link.m_path)) {
             VUtils::addErrMsg(p_errMsg, tr("Source image %1 does not exist.")
                                           .arg(link.m_path));

+ 11 - 0
src/vpreviewmanager.cpp

@@ -375,3 +375,14 @@ void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp,
 
     m_editor->relayout(affectedBlocks);
 }
+
+void VPreviewManager::refreshPreview()
+{
+    if (!m_previewEnabled) {
+        return;
+    }
+
+    clearPreview();
+
+    requestUpdateImageLinks();
+}

+ 3 - 0
src/vpreviewmanager.h

@@ -26,6 +26,9 @@ public:
     // Clear all the preview.
     void clearPreview();
 
+    // Refresh all the preview.
+    void refreshPreview();
+
 public slots:
     // Image links were updated from the highlighter.
     void imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions);