mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 22:09:52 +08:00
refactor local image folder
Treat those images which have relative path and locate in directories that have the same parent directory as the file as internal images. VNote will only manage the internal images.
This commit is contained in:
parent
7a4d86eca9
commit
e5021f4501
@ -17,14 +17,14 @@
|
|||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
#include "vconfigmanager.h"
|
#include "vfile.h"
|
||||||
|
|
||||||
extern VConfigManager vconfig;
|
extern VConfigManager vconfig;
|
||||||
|
|
||||||
const QVector<QPair<QString, QString>> VUtils::c_availableLanguages = {QPair<QString, QString>("en_US", "Englisth(US)"),
|
const QVector<QPair<QString, QString>> VUtils::c_availableLanguages = {QPair<QString, QString>("en_US", "Englisth(US)"),
|
||||||
QPair<QString, QString>("zh_CN", "Chinese")};
|
QPair<QString, QString>("zh_CN", "Chinese")};
|
||||||
|
|
||||||
const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"]+)\\s*(\".*\")?\\s*\\)");
|
const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"]+)\\s*(\"(\\\\.|[^\"\\)])*\")?\\s*\\)");
|
||||||
|
|
||||||
VUtils::VUtils()
|
VUtils::VUtils()
|
||||||
{
|
{
|
||||||
@ -113,71 +113,120 @@ void VUtils::processStyle(QString &style, const QVector<QPair<QString, QString>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VUtils::isMarkdown(const QString &name)
|
bool VUtils::isMarkdown(const QString &p_fileName)
|
||||||
{
|
{
|
||||||
const QVector<QString> mdPostfix({"md", "markdown", "mkd"});
|
const QVector<QString> mdPostfix({"md", "markdown", "mkd"});
|
||||||
|
|
||||||
QStringList list = name.split('.', QString::SkipEmptyParts);
|
QFileInfo info(p_fileName);
|
||||||
if (list.isEmpty()) {
|
QString suffix = info.suffix();
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const QString &postfix = list.last();
|
|
||||||
for (int i = 0; i < mdPostfix.size(); ++i) {
|
for (int i = 0; i < mdPostfix.size(); ++i) {
|
||||||
if (postfix == mdPostfix[i]) {
|
if (suffix == mdPostfix[i]) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString VUtils::fileNameFromPath(const QString &path)
|
QString VUtils::fileNameFromPath(const QString &p_path)
|
||||||
{
|
{
|
||||||
if (path.isEmpty()) {
|
if (p_path.isEmpty()) {
|
||||||
return path;
|
return p_path;
|
||||||
}
|
}
|
||||||
return QFileInfo(QDir::cleanPath(path)).fileName();
|
|
||||||
|
return QFileInfo(QDir::cleanPath(p_path)).fileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString VUtils::basePathFromPath(const QString &path)
|
QString VUtils::basePathFromPath(const QString &p_path)
|
||||||
{
|
{
|
||||||
return QFileInfo(path).path();
|
if (p_path.isEmpty()) {
|
||||||
|
return p_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QFileInfo(QDir::cleanPath(p_path)).path();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect image links like 
|
QVector<ImageLink> VUtils::fetchImagesFromMarkdownFile(VFile *p_file,
|
||||||
QVector<QString> VUtils::imagesFromMarkdownFile(const QString &filePath)
|
ImageLink::ImageLinkType p_type)
|
||||||
{
|
{
|
||||||
Q_ASSERT(isMarkdown(filePath));
|
V_ASSERT(p_file->getDocType() == DocType::Markdown);
|
||||||
QVector<QString> images;
|
QVector<ImageLink> images;
|
||||||
if (filePath.isEmpty()) {
|
|
||||||
|
bool isOpened = p_file->isOpened();
|
||||||
|
if (!isOpened && !p_file->open()) {
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
QString basePath = basePathFromPath(filePath);
|
|
||||||
QString text = readFileFromDisk(filePath);
|
|
||||||
QRegExp regExp("\\!\\[[^\\]]*\\]\\((images/[^/\\)]+)\\)");
|
|
||||||
int pos = 0;
|
|
||||||
|
|
||||||
|
const QString &text = p_file->getContent();
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
if (!isOpened) {
|
||||||
|
p_file->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegExp regExp(c_imageLinkRegExp);
|
||||||
|
QString basePath = p_file->retriveBasePath();
|
||||||
|
int pos = 0;
|
||||||
while (pos < text.size() && (pos = regExp.indexIn(text, pos)) != -1) {
|
while (pos < text.size() && (pos = regExp.indexIn(text, pos)) != -1) {
|
||||||
Q_ASSERT(regExp.captureCount() == 1);
|
QString imageUrl = regExp.capturedTexts()[2].trimmed();
|
||||||
qDebug() << regExp.capturedTexts()[0] << regExp.capturedTexts()[1];
|
|
||||||
images.append(QDir(basePath).filePath(regExp.capturedTexts()[1]));
|
ImageLink link;
|
||||||
|
QFileInfo info(basePath, imageUrl);
|
||||||
|
if (info.exists()) {
|
||||||
|
if (info.isNativePath()) {
|
||||||
|
// Local file.
|
||||||
|
link.m_path = QDir::cleanPath(info.absoluteFilePath());
|
||||||
|
|
||||||
|
if (QDir::isRelativePath(imageUrl)) {
|
||||||
|
link.m_type = p_file->isInternalImageFolder(VUtils::basePathFromPath(link.m_path)) ?
|
||||||
|
ImageLink::LocalRelativeInternal : ImageLink::LocalRelativeExternal;
|
||||||
|
} else {
|
||||||
|
link.m_type = ImageLink::LocalAbsolute;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
link.m_type = ImageLink::Resource;
|
||||||
|
link.m_path = imageUrl;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QUrl url(imageUrl);
|
||||||
|
link.m_path = url.toString();
|
||||||
|
link.m_type = ImageLink::Remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link.m_type & p_type) {
|
||||||
|
images.push_back(link);
|
||||||
|
qDebug() << "fetch one image:" << link.m_type << link.m_path;
|
||||||
|
}
|
||||||
|
|
||||||
pos += regExp.matchedLength();
|
pos += regExp.matchedLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isOpened) {
|
||||||
|
p_file->close();
|
||||||
|
}
|
||||||
|
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VUtils::makeDirectory(const QString &path)
|
bool VUtils::makePath(const QString &p_path)
|
||||||
{
|
{
|
||||||
if (path.isEmpty()) {
|
if (p_path.isEmpty()) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mkdir will return false if it already exists
|
bool ret = true;
|
||||||
QString basePath = basePathFromPath(path);
|
QDir dir;
|
||||||
QString dirName = directoryNameFromPath(path);
|
if (dir.mkpath(p_path)) {
|
||||||
QDir dir(basePath);
|
qDebug() << "make path" << p_path;
|
||||||
if (dir.mkdir(dirName)) {
|
} else {
|
||||||
qDebug() << "mkdir" << path;
|
qWarning() << "fail to make path" << p_path;
|
||||||
|
ret = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClipboardOpType VUtils::opTypeInClipboard()
|
ClipboardOpType VUtils::opTypeInClipboard()
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "vconstants.h"
|
#include "vconstants.h"
|
||||||
|
|
||||||
class QKeyEvent;
|
class QKeyEvent;
|
||||||
|
class VFile;
|
||||||
|
|
||||||
#if !defined(V_ASSERT)
|
#if !defined(V_ASSERT)
|
||||||
#define V_ASSERT(cond) ((!(cond)) ? qt_assert(#cond, __FILE__, __LINE__) : qt_noop())
|
#define V_ASSERT(cond) ((!(cond)) ? qt_assert(#cond, __FILE__, __LINE__) : qt_noop())
|
||||||
@ -22,6 +23,22 @@ enum class MessageBoxType
|
|||||||
Danger = 1
|
Danger = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ImageLink
|
||||||
|
{
|
||||||
|
enum ImageLinkType
|
||||||
|
{
|
||||||
|
LocalRelativeInternal = 0x1,
|
||||||
|
LocalRelativeExternal = 0x2,
|
||||||
|
LocalAbsolute = 0x4,
|
||||||
|
Resource = 0x8,
|
||||||
|
Remote = 0x10,
|
||||||
|
All = 0xffff
|
||||||
|
};
|
||||||
|
|
||||||
|
QString m_path;
|
||||||
|
ImageLinkType m_type;
|
||||||
|
};
|
||||||
|
|
||||||
class VUtils
|
class VUtils
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -36,12 +53,29 @@ public:
|
|||||||
static QString generateCopiedFileName(const QString &p_dirPath, const QString &p_fileName);
|
static QString generateCopiedFileName(const QString &p_dirPath, const QString &p_fileName);
|
||||||
static QString generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName);
|
static QString generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName);
|
||||||
static void processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap);
|
static void processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap);
|
||||||
static bool isMarkdown(const QString &fileName);
|
static bool isMarkdown(const QString &p_fileName);
|
||||||
static inline QString directoryNameFromPath(const QString& path);
|
|
||||||
static QString fileNameFromPath(const QString &path);
|
// Return the last directory name of @p_path.
|
||||||
static QString basePathFromPath(const QString &path);
|
static inline QString directoryNameFromPath(const QString& p_path);
|
||||||
static QVector<QString> imagesFromMarkdownFile(const QString &filePath);
|
|
||||||
static void makeDirectory(const QString &path);
|
// Return the file name of @p_path.
|
||||||
|
// /home/tamlok/abc, /home/tamlok/abc/ will both return abc.
|
||||||
|
static QString fileNameFromPath(const QString &p_path);
|
||||||
|
|
||||||
|
// Return the base path of @p_path.
|
||||||
|
// /home/tamlok/abc, /home/tamlok/abc/ will both return /home/tamlok.
|
||||||
|
static QString basePathFromPath(const QString &p_path);
|
||||||
|
|
||||||
|
// Fetch all the image links (including those in code blocks) in markdown file p_file.
|
||||||
|
// @p_type to filter the links returned.
|
||||||
|
// Need to open p_file and will close it if it is originally closed.
|
||||||
|
static QVector<ImageLink> fetchImagesFromMarkdownFile(VFile *p_file,
|
||||||
|
ImageLink::ImageLinkType p_type = ImageLink::All);
|
||||||
|
|
||||||
|
// Create directories along the @p_path.
|
||||||
|
// @p_path could be /home/tamlok/abc, /home/tamlok/abc/.
|
||||||
|
static bool makePath(const QString &p_path);
|
||||||
|
|
||||||
static ClipboardOpType opTypeInClipboard();
|
static ClipboardOpType opTypeInClipboard();
|
||||||
static bool copyFile(const QString &p_srcFilePath, const QString &p_destFilePath, bool p_isCut);
|
static bool copyFile(const QString &p_srcFilePath, const QString &p_destFilePath, bool p_isCut);
|
||||||
static bool copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, bool p_isCut);
|
static bool copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, bool p_isCut);
|
||||||
@ -59,11 +93,12 @@ public:
|
|||||||
static QString getLocale();
|
static QString getLocale();
|
||||||
|
|
||||||
// Regular expression for image link.
|
// Regular expression for image link.
|
||||||
// 
|
// 
|
||||||
// Captured texts (need to be trimmed):
|
// Captured texts (need to be trimmed):
|
||||||
// 1. Image Alt Text (Title);
|
// 1. Image Alt Text (Title);
|
||||||
// 2. Image URL;
|
// 2. Image URL;
|
||||||
// 3. Image Optional Title with double quotes;
|
// 3. Image Optional Title with double quotes;
|
||||||
|
// 4. Unused;
|
||||||
static const QString c_imageLinkRegExp;
|
static const QString c_imageLinkRegExp;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -71,9 +106,9 @@ private:
|
|||||||
static const QVector<QPair<QString, QString>> c_availableLanguages;
|
static const QVector<QPair<QString, QString>> c_availableLanguages;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline QString VUtils::directoryNameFromPath(const QString &path)
|
inline QString VUtils::directoryNameFromPath(const QString &p_path)
|
||||||
{
|
{
|
||||||
return fileNameFromPath(path);
|
return fileNameFromPath(p_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // VUTILS_H
|
#endif // VUTILS_H
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
#include "vfile.h"
|
#include "vfile.h"
|
||||||
#include "utils/vutils.h"
|
#include "utils/vutils.h"
|
||||||
|
|
||||||
|
extern VConfigManager vconfig;
|
||||||
|
|
||||||
VDirectory::VDirectory(VNotebook *p_notebook,
|
VDirectory::VDirectory(VNotebook *p_notebook,
|
||||||
const QString &p_name, QObject *p_parent)
|
const QString &p_name, QObject *p_parent)
|
||||||
: QObject(p_parent), m_notebook(p_notebook), m_name(p_name), m_opened(false),
|
: QObject(p_parent), m_notebook(p_notebook), m_name(p_name), m_opened(false),
|
||||||
@ -472,9 +474,10 @@ VFile *VDirectory::copyFile(VDirectory *p_destDir, const QString &p_destName,
|
|||||||
DocType docType = p_srcFile->getDocType();
|
DocType docType = p_srcFile->getDocType();
|
||||||
DocType newDocType = VUtils::isMarkdown(destPath) ? DocType::Markdown : DocType::Html;
|
DocType newDocType = VUtils::isMarkdown(destPath) ? DocType::Markdown : DocType::Html;
|
||||||
|
|
||||||
QVector<QString> images;
|
QVector<ImageLink> images;
|
||||||
if (docType == DocType::Markdown) {
|
if (docType == DocType::Markdown) {
|
||||||
images = VUtils::imagesFromMarkdownFile(srcPath);
|
images = VUtils::fetchImagesFromMarkdownFile(p_srcFile,
|
||||||
|
ImageLink::LocalRelativeInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the file
|
// Copy the file
|
||||||
@ -505,36 +508,72 @@ VFile *VDirectory::copyFile(VDirectory *p_destDir, const QString &p_destName,
|
|||||||
destFile->convert(docType, newDocType);
|
destFile->convert(docType, newDocType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to copy images when it is still markdown
|
// We need to copy internal images when it is still markdown.
|
||||||
if (!images.isEmpty()) {
|
if (!images.isEmpty()) {
|
||||||
if (newDocType == DocType::Markdown) {
|
if (newDocType == DocType::Markdown) {
|
||||||
QString dirPath = destFile->retriveImagePath();
|
QString parentPath = destFile->retriveBasePath();
|
||||||
VUtils::makeDirectory(dirPath);
|
|
||||||
int nrPasted = 0;
|
int nrPasted = 0;
|
||||||
for (int i = 0; i < images.size(); ++i) {
|
for (int i = 0; i < images.size(); ++i) {
|
||||||
if (!QFile(images[i]).exists()) {
|
const ImageLink &link = images[i];
|
||||||
|
if (!QFileInfo::exists(link.m_path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString destImagePath = QDir(dirPath).filePath(VUtils::fileNameFromPath(images[i]));
|
QString errStr;
|
||||||
|
bool ret = true;
|
||||||
|
|
||||||
|
QString imageFolder = VUtils::directoryNameFromPath(VUtils::basePathFromPath(link.m_path));
|
||||||
|
QString destImagePath = QDir(parentPath).filePath(imageFolder);
|
||||||
|
ret = VUtils::makePath(destImagePath);
|
||||||
|
if (!ret) {
|
||||||
|
errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
|
||||||
|
.arg(vconfig.c_dataTextStyle).arg(destImagePath);
|
||||||
|
} else {
|
||||||
|
destImagePath = QDir(destImagePath).filePath(VUtils::fileNameFromPath(link.m_path));
|
||||||
|
|
||||||
// Copy or Cut the images accordingly.
|
// Copy or Cut the images accordingly.
|
||||||
if (VUtils::copyFile(images[i], destImagePath, p_cut)) {
|
if (destImagePath == link.m_path) {
|
||||||
|
ret = false;
|
||||||
|
} else {
|
||||||
|
ret = VUtils::copyFile(link.m_path, destImagePath, p_cut);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
qDebug() << (p_cut ? "Cut" : "Copy") << "image"
|
||||||
|
<< link.m_path << "->" << destImagePath;
|
||||||
|
|
||||||
nrPasted++;
|
nrPasted++;
|
||||||
} else {
|
} else {
|
||||||
|
errStr = tr("Please check if there already exists a file <span style=\"%1\">%2</span> "
|
||||||
|
"and then manually copy it and modify the note accordingly.")
|
||||||
|
.arg(vconfig.c_dataTextStyle).arg(destImagePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
||||||
tr("Fail to copy image %1.").arg(images[i]),
|
tr("Fail to copy image <span style=\"%1\">%2</span> while "
|
||||||
tr("Please check if there already exists a file with the same name and then manually copy it."),
|
"%5 note <span style=\"%3\">%4</span>.")
|
||||||
QMessageBox::Ok, QMessageBox::Ok, NULL);
|
.arg(vconfig.c_dataTextStyle).arg(link.m_path)
|
||||||
|
.arg(vconfig.c_dataTextStyle).arg(srcPath)
|
||||||
|
.arg(p_cut ? tr("moving") : tr("copying")),
|
||||||
|
errStr, QMessageBox::Ok, QMessageBox::Ok, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qDebug() << "pasted" << nrPasted << "images sucessfully";
|
|
||||||
|
qDebug() << "pasted" << nrPasted << "images";
|
||||||
} else {
|
} else {
|
||||||
// Delete the images
|
// Delete the images.
|
||||||
|
int deleted = 0;
|
||||||
for (int i = 0; i < images.size(); ++i) {
|
for (int i = 0; i < images.size(); ++i) {
|
||||||
QFile file(images[i]);
|
QFile file(images[i].m_path);
|
||||||
file.remove();
|
if (file.remove()) {
|
||||||
|
++deleted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDebug() << "delete" << deleted << "images since it is not Markdown any more for" << srcPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return destFile;
|
return destFile;
|
||||||
|
@ -38,10 +38,12 @@ public:
|
|||||||
VDirectory *addSubDirectory(const QString &p_name, int p_index);
|
VDirectory *addSubDirectory(const QString &p_name, int p_index);
|
||||||
void deleteFile(VFile *p_file);
|
void deleteFile(VFile *p_file);
|
||||||
bool rename(const QString &p_name);
|
bool rename(const QString &p_name);
|
||||||
|
|
||||||
// Copy @p_srcFile to @p_destDir, setting new name to @p_destName.
|
// Copy @p_srcFile to @p_destDir, setting new name to @p_destName.
|
||||||
// @p_cut: copy or cut. Returns the dest VFile.
|
// @p_cut: copy or cut. Returns the dest VFile.
|
||||||
static VFile *copyFile(VDirectory *p_destDir, const QString &p_destName,
|
static VFile *copyFile(VDirectory *p_destDir, const QString &p_destName,
|
||||||
VFile *p_srcFile, bool p_cut);
|
VFile *p_srcFile, bool p_cut);
|
||||||
|
|
||||||
static VDirectory *copyDirectory(VDirectory *p_destDir, const QString &p_destName,
|
static VDirectory *copyDirectory(VDirectory *p_destDir, const QString &p_destName,
|
||||||
VDirectory *p_srcDir, bool p_cut);
|
VDirectory *p_srcDir, bool p_cut);
|
||||||
|
|
||||||
|
@ -45,9 +45,9 @@ void VFile::close()
|
|||||||
|
|
||||||
void VFile::deleteDiskFile()
|
void VFile::deleteDiskFile()
|
||||||
{
|
{
|
||||||
Q_ASSERT(parent());
|
V_ASSERT(parent());
|
||||||
|
|
||||||
// Delete local images in ./images if it is Markdown
|
// Delete local images if it is Markdown.
|
||||||
if (m_docType == DocType::Markdown) {
|
if (m_docType == DocType::Markdown) {
|
||||||
deleteLocalImages();
|
deleteLocalImages();
|
||||||
}
|
}
|
||||||
@ -97,17 +97,19 @@ void VFile::setModified(bool p_modified)
|
|||||||
|
|
||||||
void VFile::deleteLocalImages()
|
void VFile::deleteLocalImages()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_docType == DocType::Markdown);
|
V_ASSERT(m_docType == DocType::Markdown);
|
||||||
QString filePath = retrivePath();
|
|
||||||
QVector<QString> images = VUtils::imagesFromMarkdownFile(filePath);
|
QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(this,
|
||||||
|
ImageLink::LocalRelativeInternal);
|
||||||
int deleted = 0;
|
int deleted = 0;
|
||||||
for (int i = 0; i < images.size(); ++i) {
|
for (int i = 0; i < images.size(); ++i) {
|
||||||
QFile file(images[i]);
|
QFile file(images[i].m_path);
|
||||||
if (file.remove()) {
|
if (file.remove()) {
|
||||||
++deleted;
|
++deleted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qDebug() << "delete" << deleted << "images for" << filePath;
|
|
||||||
|
qDebug() << "delete" << deleted << "images for" << retrivePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VFile::setName(const QString &p_name)
|
void VFile::setName(const QString &p_name)
|
||||||
@ -151,6 +153,11 @@ QString VFile::getNotebookName() const
|
|||||||
return getDirectory()->getNotebookName();
|
return getDirectory()->getNotebookName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VNotebook *VFile::getNotebook() const
|
||||||
|
{
|
||||||
|
return getDirectory()->getNotebook();
|
||||||
|
}
|
||||||
|
|
||||||
VNotebook *VFile::getNotebook()
|
VNotebook *VFile::getNotebook()
|
||||||
{
|
{
|
||||||
return getDirectory()->getNotebook();
|
return getDirectory()->getNotebook();
|
||||||
@ -175,7 +182,7 @@ QString VFile::retriveBasePath() const
|
|||||||
|
|
||||||
QString VFile::retriveImagePath() const
|
QString VFile::retriveImagePath() const
|
||||||
{
|
{
|
||||||
return QDir(retriveBasePath()).filePath("images");
|
return QDir(retriveBasePath()).filePath(getNotebook()->getImageFolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VFile::setContent(const QString &p_content)
|
void VFile::setContent(const QString &p_content)
|
||||||
@ -202,3 +209,8 @@ FileType VFile::getType() const
|
|||||||
{
|
{
|
||||||
return m_type;
|
return m_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VFile::isInternalImageFolder(const QString &p_path) const
|
||||||
|
{
|
||||||
|
return VUtils::basePathFromPath(p_path) == getDirectory()->retrivePath();
|
||||||
|
}
|
||||||
|
@ -28,6 +28,7 @@ public:
|
|||||||
DocType getDocType() const;
|
DocType getDocType() const;
|
||||||
const QString &getContent() const;
|
const QString &getContent() const;
|
||||||
virtual void setContent(const QString &p_content);
|
virtual void setContent(const QString &p_content);
|
||||||
|
virtual const VNotebook *getNotebook() const;
|
||||||
virtual VNotebook *getNotebook();
|
virtual VNotebook *getNotebook();
|
||||||
virtual QString getNotebookName() const;
|
virtual QString getNotebookName() const;
|
||||||
virtual QString retrivePath() const;
|
virtual QString retrivePath() const;
|
||||||
@ -39,13 +40,19 @@ public:
|
|||||||
bool isOpened() const;
|
bool isOpened() const;
|
||||||
FileType getType() const;
|
FileType getType() const;
|
||||||
|
|
||||||
|
// Whether the directory @p_path is an internal image folder of this file.
|
||||||
|
// It is true only when the folder is in the same directory as the parent
|
||||||
|
// directory of this file.
|
||||||
|
virtual bool isInternalImageFolder(const QString &p_path) const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setModified(bool p_modified);
|
void setModified(bool p_modified);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Delete the file and corresponding images
|
// Delete the file and corresponding images
|
||||||
void deleteDiskFile();
|
void deleteDiskFile();
|
||||||
// Delete local images in ./images of DocType::Markdown
|
|
||||||
|
// Delete local images of DocType::Markdown.
|
||||||
void deleteLocalImages();
|
void deleteLocalImages();
|
||||||
|
|
||||||
QString m_name;
|
QString m_name;
|
||||||
|
@ -195,7 +195,7 @@ QString VImagePreviewer::fetchImagePathToPreview(const QString &p_text)
|
|||||||
if (info.exists()) {
|
if (info.exists()) {
|
||||||
if (info.isNativePath()) {
|
if (info.isNativePath()) {
|
||||||
// Local file.
|
// Local file.
|
||||||
imagePath = info.absoluteFilePath();
|
imagePath = QDir::cleanPath(info.absoluteFilePath());
|
||||||
} else {
|
} else {
|
||||||
imagePath = imageUrl;
|
imagePath = imageUrl;
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ void VMdEdit::saveFile()
|
|||||||
void VMdEdit::reloadFile()
|
void VMdEdit::reloadFile()
|
||||||
{
|
{
|
||||||
const QString &content = m_file->getContent();
|
const QString &content = m_file->getContent();
|
||||||
Q_ASSERT(content.indexOf(QChar::ObjectReplacementCharacter) == -1);
|
V_ASSERT(content.indexOf(QChar::ObjectReplacementCharacter) == -1);
|
||||||
setPlainText(content);
|
setPlainText(content);
|
||||||
setModified(false);
|
setModified(false);
|
||||||
}
|
}
|
||||||
@ -171,61 +171,74 @@ void VMdEdit::insertFromMimeData(const QMimeData *source)
|
|||||||
VEdit::insertFromMimeData(source);
|
VEdit::insertFromMimeData(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMdEdit::imageInserted(const QString &p_name)
|
void VMdEdit::imageInserted(const QString &p_path)
|
||||||
{
|
{
|
||||||
m_insertedImages.append(p_name);
|
ImageLink link;
|
||||||
|
link.m_path = p_path;
|
||||||
|
link.m_type = ImageLink::LocalRelativeInternal;
|
||||||
|
|
||||||
|
m_insertedImages.append(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMdEdit::initInitImages()
|
void VMdEdit::initInitImages()
|
||||||
{
|
{
|
||||||
m_initImages = VUtils::imagesFromMarkdownFile(m_file->retrivePath());
|
m_initImages = VUtils::fetchImagesFromMarkdownFile(m_file,
|
||||||
|
ImageLink::LocalRelativeInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMdEdit::clearUnusedImages()
|
void VMdEdit::clearUnusedImages()
|
||||||
{
|
{
|
||||||
QVector<QString> images = VUtils::imagesFromMarkdownFile(m_file->retrivePath());
|
QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file,
|
||||||
|
ImageLink::LocalRelativeInternal);
|
||||||
|
|
||||||
if (!m_insertedImages.isEmpty()) {
|
if (!m_insertedImages.isEmpty()) {
|
||||||
QVector<QString> imageNames(images.size());
|
|
||||||
for (int i = 0; i < imageNames.size(); ++i) {
|
|
||||||
imageNames[i] = VUtils::fileNameFromPath(images[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir dir = QDir(m_file->retriveImagePath());
|
|
||||||
for (int i = 0; i < m_insertedImages.size(); ++i) {
|
for (int i = 0; i < m_insertedImages.size(); ++i) {
|
||||||
QString name = m_insertedImages[i];
|
const ImageLink &link = m_insertedImages[i];
|
||||||
|
|
||||||
|
V_ASSERT(link.m_type == ImageLink::LocalRelativeInternal);
|
||||||
|
|
||||||
int j;
|
int j;
|
||||||
for (j = 0; j < imageNames.size(); ++j) {
|
for (j = 0; j < images.size(); ++j) {
|
||||||
if (name == imageNames[j]) {
|
if (link.m_path == images[j].m_path) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete it
|
// This inserted image is no longer in the file.
|
||||||
if (j == imageNames.size()) {
|
if (j == images.size()) {
|
||||||
QString imagePath = dir.filePath(name);
|
if (!QFile(link.m_path).remove()) {
|
||||||
QFile(imagePath).remove();
|
qWarning() << "fail to delete unused inserted image" << link.m_path;
|
||||||
qDebug() << "delete inserted image" << imagePath;
|
} else {
|
||||||
|
qDebug() << "delete unused inserted image" << link.m_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_insertedImages.clear();
|
m_insertedImages.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < m_initImages.size(); ++i) {
|
for (int i = 0; i < m_initImages.size(); ++i) {
|
||||||
QString imagePath = m_initImages[i];
|
const ImageLink &link = m_initImages[i];
|
||||||
|
|
||||||
|
V_ASSERT(link.m_type == ImageLink::LocalRelativeInternal);
|
||||||
|
|
||||||
int j;
|
int j;
|
||||||
for (j = 0; j < images.size(); ++j) {
|
for (j = 0; j < images.size(); ++j) {
|
||||||
if (imagePath == images[j]) {
|
if (link.m_path == images[j].m_path) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete it
|
// Original local relative image is no longer in the file.
|
||||||
if (j == images.size()) {
|
if (j == images.size()) {
|
||||||
QFile(imagePath).remove();
|
if (!QFile(link.m_path).remove()) {
|
||||||
qDebug() << "delete existing image" << imagePath;
|
qWarning() << "fail to delete unused original image" << link.m_path;
|
||||||
|
} else {
|
||||||
|
qDebug() << "delete unused original image" << link.m_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_initImages.clear();
|
m_initImages.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "vtoc.h"
|
#include "vtoc.h"
|
||||||
#include "veditoperations.h"
|
#include "veditoperations.h"
|
||||||
#include "vconfigmanager.h"
|
#include "vconfigmanager.h"
|
||||||
|
#include "utils/vutils.h"
|
||||||
|
|
||||||
class HGMarkdownHighlighter;
|
class HGMarkdownHighlighter;
|
||||||
class VCodeBlockHighlightHelper;
|
class VCodeBlockHighlightHelper;
|
||||||
@ -27,8 +28,9 @@ public:
|
|||||||
void saveFile() Q_DECL_OVERRIDE;
|
void saveFile() Q_DECL_OVERRIDE;
|
||||||
void reloadFile() Q_DECL_OVERRIDE;
|
void reloadFile() Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
// An image has been inserted.
|
// An image has been inserted. The image is relative.
|
||||||
void imageInserted(const QString &p_name);
|
// @p_path is the absolute path of the inserted image.
|
||||||
|
void imageInserted(const QString &p_path);
|
||||||
|
|
||||||
// Scroll to m_headers[p_headerIndex].
|
// Scroll to m_headers[p_headerIndex].
|
||||||
void scrollToHeader(int p_headerIndex);
|
void scrollToHeader(int p_headerIndex);
|
||||||
@ -67,8 +69,13 @@ private:
|
|||||||
HGMarkdownHighlighter *m_mdHighlighter;
|
HGMarkdownHighlighter *m_mdHighlighter;
|
||||||
VCodeBlockHighlightHelper *m_cbHighlighter;
|
VCodeBlockHighlightHelper *m_cbHighlighter;
|
||||||
VImagePreviewer *m_imagePreviewer;
|
VImagePreviewer *m_imagePreviewer;
|
||||||
QVector<QString> m_insertedImages;
|
|
||||||
QVector<QString> m_initImages;
|
// Image links inserted while editing.
|
||||||
|
QVector<ImageLink> m_insertedImages;
|
||||||
|
|
||||||
|
// Image links right at the beginning of the edit.
|
||||||
|
QVector<ImageLink> m_initImages;
|
||||||
|
|
||||||
QVector<VHeader> m_headers;
|
QVector<VHeader> m_headers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,49 +55,80 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
|
|||||||
const QImage &image)
|
const QImage &image)
|
||||||
{
|
{
|
||||||
QString fileName = VUtils::generateImageFileName(path, title);
|
QString fileName = VUtils::generateImageFileName(path, title);
|
||||||
qDebug() << "insert image" << path << title << fileName;
|
|
||||||
QString filePath = QDir(path).filePath(fileName);
|
QString filePath = QDir(path).filePath(fileName);
|
||||||
Q_ASSERT(!QFile(filePath).exists());
|
V_ASSERT(!QFile(filePath).exists());
|
||||||
VUtils::makeDirectory(path);
|
|
||||||
bool ret = image.save(filePath);
|
QString errStr;
|
||||||
|
bool ret = VUtils::makePath(path);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Fail to save image %1.").arg(filePath),
|
errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
|
||||||
QMessageBox::Ok, (QWidget *)m_editor);
|
.arg(vconfig.c_dataTextStyle).arg(path);
|
||||||
msgBox.exec();
|
} else {
|
||||||
|
ret = image.save(filePath);
|
||||||
|
if (!ret) {
|
||||||
|
errStr = tr("Fail to save image <span style=\"%1\">%2</span>.")
|
||||||
|
.arg(vconfig.c_dataTextStyle).arg(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
||||||
|
tr("Fail to insert image <span style=\"%1\">%2</span>.").arg(vconfig.c_dataTextStyle).arg(title),
|
||||||
|
errStr,
|
||||||
|
QMessageBox::Ok,
|
||||||
|
QMessageBox::Ok,
|
||||||
|
(QWidget *)m_editor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString md = QString("").arg(title).arg(fileName);
|
QString md = QString("").arg(title).arg(VUtils::directoryNameFromPath(path)).arg(fileName);
|
||||||
insertTextAtCurPos(md);
|
insertTextAtCurPos(md);
|
||||||
|
|
||||||
|
qDebug() << "insert image" << title << filePath;
|
||||||
|
|
||||||
VMdEdit *mdEditor = dynamic_cast<VMdEdit *>(m_editor);
|
VMdEdit *mdEditor = dynamic_cast<VMdEdit *>(m_editor);
|
||||||
Q_ASSERT(mdEditor);
|
Q_ASSERT(mdEditor);
|
||||||
mdEditor->imageInserted(fileName);
|
mdEditor->imageInserted(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMdEditOperations::insertImageFromPath(const QString &title,
|
void VMdEditOperations::insertImageFromPath(const QString &title,
|
||||||
const QString &path, const QString &oriImagePath)
|
const QString &path, const QString &oriImagePath)
|
||||||
{
|
{
|
||||||
QString fileName = VUtils::generateImageFileName(path, title, QFileInfo(oriImagePath).suffix());
|
QString fileName = VUtils::generateImageFileName(path, title, QFileInfo(oriImagePath).suffix());
|
||||||
qDebug() << "insert image" << path << title << fileName << oriImagePath;
|
|
||||||
QString filePath = QDir(path).filePath(fileName);
|
QString filePath = QDir(path).filePath(fileName);
|
||||||
Q_ASSERT(!QFile(filePath).exists());
|
V_ASSERT(!QFile(filePath).exists());
|
||||||
VUtils::makeDirectory(path);
|
|
||||||
bool ret = QFile::copy(oriImagePath, filePath);
|
QString errStr;
|
||||||
|
bool ret = VUtils::makePath(path);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
qWarning() << "fail to copy" << oriImagePath << "to" << filePath;
|
errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
|
||||||
QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Fail to save image %1.").arg(filePath),
|
.arg(vconfig.c_dataTextStyle).arg(path);
|
||||||
QMessageBox::Ok, (QWidget *)m_editor);
|
} else {
|
||||||
msgBox.exec();
|
ret = QFile::copy(oriImagePath, filePath);
|
||||||
|
if (!ret) {
|
||||||
|
errStr = tr("Fail to copy image <span style=\"%1\">%2</span>.")
|
||||||
|
.arg(vconfig.c_dataTextStyle).arg(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
||||||
|
tr("Fail to insert image <span style=\"%1\">%2</span>.").arg(vconfig.c_dataTextStyle).arg(title),
|
||||||
|
errStr,
|
||||||
|
QMessageBox::Ok,
|
||||||
|
QMessageBox::Ok,
|
||||||
|
(QWidget *)m_editor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString md = QString("").arg(title).arg(fileName);
|
QString md = QString("").arg(title).arg(VUtils::directoryNameFromPath(path)).arg(fileName);
|
||||||
insertTextAtCurPos(md);
|
insertTextAtCurPos(md);
|
||||||
|
|
||||||
|
qDebug() << "insert image" << title << filePath;
|
||||||
|
|
||||||
VMdEdit *mdEditor = dynamic_cast<VMdEdit *>(m_editor);
|
VMdEdit *mdEditor = dynamic_cast<VMdEdit *>(m_editor);
|
||||||
Q_ASSERT(mdEditor);
|
Q_ASSERT(mdEditor);
|
||||||
mdEditor->imageInserted(fileName);
|
mdEditor->imageInserted(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
|
bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
|
||||||
|
@ -26,7 +26,12 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void insertImageFromPath(const QString &title, const QString &path, const QString &oriImagePath);
|
void insertImageFromPath(const QString &title, const QString &path, const QString &oriImagePath);
|
||||||
|
|
||||||
|
// @title: title of the inserted image;
|
||||||
|
// @path: the image folder path to insert the image in;
|
||||||
|
// @image: the image to be inserted;
|
||||||
void insertImageFromQImage(const QString &title, const QString &path, const QImage &image);
|
void insertImageFromQImage(const QString &title, const QString &path, const QImage &image);
|
||||||
|
|
||||||
void setKeyState(KeyState p_state);
|
void setKeyState(KeyState p_state);
|
||||||
|
|
||||||
// Key press handlers.
|
// Key press handlers.
|
||||||
|
@ -6,9 +6,12 @@
|
|||||||
#include "vconfigmanager.h"
|
#include "vconfigmanager.h"
|
||||||
#include "vfile.h"
|
#include "vfile.h"
|
||||||
|
|
||||||
|
const QString VNotebook::c_defaultImageFolder = "_v_images";
|
||||||
|
|
||||||
VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent)
|
VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent)
|
||||||
: QObject(parent), m_name(name), m_path(path)
|
: QObject(parent), m_name(name), m_imageFolder(c_defaultImageFolder)
|
||||||
{
|
{
|
||||||
|
m_path = QDir::cleanPath(path);
|
||||||
m_rootDir = new VDirectory(this, VUtils::directoryNameFromPath(path));
|
m_rootDir = new VDirectory(this, VUtils::directoryNameFromPath(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,3 +119,8 @@ bool VNotebook::containsFile(const VFile *p_file) const
|
|||||||
{
|
{
|
||||||
return m_rootDir->containsFile(p_file);
|
return m_rootDir->containsFile(p_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString &VNotebook::getImageFolder() const
|
||||||
|
{
|
||||||
|
return m_imageFolder;
|
||||||
|
}
|
||||||
|
@ -30,12 +30,22 @@ public:
|
|||||||
QObject *p_parent = 0);
|
QObject *p_parent = 0);
|
||||||
static bool deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles);
|
static bool deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles);
|
||||||
|
|
||||||
|
const QString &getImageFolder() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void contentChanged();
|
void contentChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_name;
|
QString m_name;
|
||||||
QString m_path;
|
QString m_path;
|
||||||
|
|
||||||
|
// Folder name to store images.
|
||||||
|
// VNote will store images in this folder within the same directory of the note.
|
||||||
|
QString m_imageFolder;
|
||||||
|
|
||||||
|
// Default folder name to store images of all the notes within this notebook.
|
||||||
|
static const QString c_defaultImageFolder;
|
||||||
|
|
||||||
// Parent is NULL for root directory
|
// Parent is NULL for root directory
|
||||||
VDirectory *m_rootDir;
|
VDirectory *m_rootDir;
|
||||||
};
|
};
|
||||||
|
@ -87,3 +87,8 @@ void VOrphanFile::setContent(const QString & /* p_content */)
|
|||||||
{
|
{
|
||||||
V_ASSERT(false);
|
V_ASSERT(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VOrphanFile::isInternalImageFolder(const QString &p_path) const
|
||||||
|
{
|
||||||
|
return VUtils::basePathFromPath(p_path) == VUtils::basePathFromPath(m_path);
|
||||||
|
}
|
||||||
|
@ -10,21 +10,22 @@ class VOrphanFile : public VFile
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
VOrphanFile(const QString &p_path, QObject *p_parent);
|
VOrphanFile(const QString &p_path, QObject *p_parent);
|
||||||
bool open();
|
bool open() Q_DECL_OVERRIDE;
|
||||||
QString retrivePath() const;
|
QString retrivePath() const Q_DECL_OVERRIDE;
|
||||||
QString retriveRelativePath() const;
|
QString retriveRelativePath() const Q_DECL_OVERRIDE;
|
||||||
QString retriveBasePath() const;
|
QString retriveBasePath() const Q_DECL_OVERRIDE;
|
||||||
VDirectory *getDirectory();
|
VDirectory *getDirectory() Q_DECL_OVERRIDE;
|
||||||
const VDirectory *getDirectory() const;
|
const VDirectory *getDirectory() const Q_DECL_OVERRIDE;
|
||||||
QString getNotebookName() const;
|
QString getNotebookName() const Q_DECL_OVERRIDE;
|
||||||
VNotebook *getNotebook();
|
VNotebook *getNotebook() Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool save();
|
bool save() Q_DECL_OVERRIDE;
|
||||||
void convert(DocType p_curType, DocType p_targetType);
|
void convert(DocType p_curType, DocType p_targetType) Q_DECL_OVERRIDE;
|
||||||
void setName(const QString &p_name);
|
void setName(const QString &p_name) Q_DECL_OVERRIDE;
|
||||||
QString retriveImagePath() const;
|
QString retriveImagePath() const Q_DECL_OVERRIDE;
|
||||||
void setContent(const QString &p_content);
|
void setContent(const QString &p_content) Q_DECL_OVERRIDE;
|
||||||
|
bool isInternalImageFolder(const QString &p_path) const Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
QString m_path;
|
QString m_path;
|
||||||
friend class VDirectory;
|
friend class VDirectory;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user