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;
This commit is contained in:
Le Tan 2017-12-20 20:48:16 +08:00
parent 7fd5ec26e5
commit 03122a24db
22 changed files with 223 additions and 36 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -521,19 +521,19 @@ bool VEditArea::isFileOpened(const VFile *p_file)
return !findTabsByFile(p_file).isEmpty(); 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(); int nrWin = splitter->count();
for (int i = 0; i < nrWin; ++i) { 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(); int nrWin = splitter->count();
for (int i = 0; i < nrWin; ++i) { for (int i = 0; i < nrWin; ++i) {
getWindow(i)->updateDirectoryInfo(p_dir); getWindow(i)->updateDirectoryInfo(p_dir, p_act);
} }
} }

View File

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

View File

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

View File

@ -111,6 +111,9 @@ public:
// Reload file from disk and reload the editor. // Reload file from disk and reload the editor.
void reloadFromDisk(); 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: public slots:
// Enter edit mode // Enter edit mode
virtual void editFile() = 0; virtual void editFile() = 0;

View File

@ -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) { if (!p_file) {
return; return;
} }
int idx = findTabByFile(p_file); int idx = findTabByFile(p_file);
if (idx > -1) { if (idx > -1) {
updateTabStatus(idx); 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) { if (!p_dir) {
return; return;
@ -816,6 +818,7 @@ void VEditWindow::updateDirectoryInfo(const VDirectory *p_dir)
QPointer<VFile> file = editor->getFile(); QPointer<VFile> file = editor->getFile();
if (p_dir->containsFile(file)) { if (p_dir->containsFile(file)) {
updateTabStatus(i); updateTabStatus(i);
editor->handleFileOrDirectoryChange(false, p_act);
} }
} }
} }

View File

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

View File

@ -247,7 +247,7 @@ void VFileList::fileInfo(VNoteFile *p_file)
fillItem(item, 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) { if (destFile) {
++nrPasted; ++nrPasted;
emit fileUpdated(destFile); emit fileUpdated(destFile, p_isCut ? UpdateAction::Moved : UpdateAction::InfoChanged);
} }
} }

View File

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

View File

@ -86,14 +86,15 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
return; 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); insertTextAtCurPos(md);
qDebug() << "insert image" << title << filePath; qDebug() << "insert image" << title << filePath;
VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor); VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
Q_ASSERT(mdEditor); Q_ASSERT(mdEditor);
mdEditor->imageInserted(filePath); mdEditor->imageInserted(filePath, url);
} }
void VMdEditOperations::insertImageFromPath(const QString &title, const QString &path, void VMdEditOperations::insertImageFromPath(const QString &title, const QString &path,
@ -126,14 +127,15 @@ void VMdEditOperations::insertImageFromPath(const QString &title, const QString
return; 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); insertTextAtCurPos(md);
qDebug() << "insert image" << title << filePath; qDebug() << "insert image" << title << filePath;
VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor); VMdEditor *mdEditor = dynamic_cast<VMdEditor *>(m_editor);
Q_ASSERT(mdEditor); Q_ASSERT(mdEditor);
mdEditor->imageInserted(filePath); mdEditor->imageInserted(filePath, url);
} }
bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl) bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)

View File

@ -125,6 +125,7 @@ void VMdEditor::beginEdit()
void VMdEditor::endEdit() void VMdEditor::endEdit()
{ {
setReadOnlyAndHighlightCurrentLine(true); setReadOnlyAndHighlightCurrentLine(true);
clearUnusedImages(); clearUnusedImages();
} }
@ -138,6 +139,10 @@ void VMdEditor::saveFile()
m_file->setContent(toPlainText()); m_file->setContent(toPlainText());
setModified(false); setModified(false);
clearUnusedImages();
initInitImages();
} }
void VMdEditor::reloadFile() void VMdEditor::reloadFile()
@ -544,7 +549,7 @@ void VMdEditor::clearUnusedImages()
QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file, QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file,
ImageLink::LocalRelativeInternal); ImageLink::LocalRelativeInternal);
QVector<QString> unusedImages; QSet<QString> unusedImages;
if (!m_insertedImages.isEmpty()) { if (!m_insertedImages.isEmpty()) {
for (int i = 0; i < m_insertedImages.size(); ++i) { 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. // This inserted image is no longer in the file.
if (j == images.size()) { 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. // Original local relative image is no longer in the file.
if (j == images.size()) { 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()); g_config->setConfirmImagesCleanUp(dialog.getAskAgainEnabled());
for (auto const & item : items) { 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; bool ret = false;
if (m_file->getType() == FileType::Note) { if (m_file->getType() == FileType::Note) {
const VNoteFile *tmpFile = dynamic_cast<const VNoteFile *>((VFile *)m_file); 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) { } else if (m_file->getType() == FileType::Orphan) {
const VOrphanFile *tmpFile = dynamic_cast<const VOrphanFile *>((VFile *)m_file); const VOrphanFile *tmpFile = dynamic_cast<const VOrphanFile *>((VFile *)m_file);
ret = VUtils::deleteFile(tmpFile, unusedImages[i], false); ret = VUtils::deleteFile(tmpFile, item, false);
} else { } else {
Q_ASSERT(false); Q_ASSERT(false);
} }
if (!ret) { if (!ret) {
qWarning() << "fail to delete unused original image" << unusedImages[i]; qWarning() << "fail to delete unused original image" << item;
} else { } 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); VTextEdit::insertFromMimeData(p_source);
} }
void VMdEditor::imageInserted(const QString &p_path) void VMdEditor::imageInserted(const QString &p_path, const QString &p_url)
{ {
ImageLink link; ImageLink link;
link.m_path = p_path; link.m_path = p_path;
link.m_url = p_url;
if (m_file->useRelativeImageFolder()) { if (m_file->useRelativeImageFolder()) {
link.m_type = ImageLink::LocalRelativeInternal; link.m_type = ImageLink::LocalRelativeInternal;
} else { } else {
@ -907,3 +913,80 @@ void VMdEditor::setContent(const QString &p_content, bool p_modified)
setPlainText(p_content); 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;
}
}
}

View File

@ -45,7 +45,8 @@ public:
// An image has been inserted. The image is relative. // An image has been inserted. The image is relative.
// @p_path is the absolute path of the inserted image. // @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. // Scroll to header @p_blockNumber.
// Return true if @p_blockNumber is valid to scroll to. // 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 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: 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;
@ -217,5 +223,4 @@ private:
bool m_freshEdit; bool m_freshEdit;
}; };
#endif // VMDEDITOR_H #endif // VMDEDITOR_H

View File

@ -982,3 +982,18 @@ void VMdTab::updateCursorStatus()
{ {
emit statusUpdated(fetchTabInfo(VEditTabInfo::InfoType::Cursor)); 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();
}
}

View File

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

View File

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

View File

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

View File

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