#include #include #include #include #include #include "vedittab.h" #include "vedit.h" #include "vdocument.h" #include "vnote.h" #include "utils/vutils.h" #include "vpreviewpage.h" #include "hgmarkdownhighlighter.h" #include "vconfigmanager.h" #include "vmarkdownconverter.h" #include "vnotebook.h" #include "vtoc.h" #include "vmdedit.h" #include "dialog/vfindreplacedialog.h" extern VConfigManager vconfig; VEditTab::VEditTab(VFile *p_file, OpenFileMode p_mode, QWidget *p_parent) : QStackedWidget(p_parent), m_file(p_file), isEditMode(false), mdConverterType(vconfig.getMdConverterType()), m_fileModified(false) { tableOfContent.filePath = p_file->retrivePath(); curHeader.filePath = p_file->retrivePath(); Q_ASSERT(!m_file->isOpened()); m_file->open(); setupUI(); if (p_mode == OpenFileMode::Edit) { showFileEditMode(); } else { showFileReadMode(); } connect(qApp, &QApplication::focusChanged, this, &VEditTab::handleFocusChanged); } VEditTab::~VEditTab() { if (m_file) { m_file->close(); } } void VEditTab::setupUI() { switch (m_file->getDocType()) { case DocType::Markdown: m_textEditor = new VMdEdit(m_file, this); connect(dynamic_cast(m_textEditor), &VMdEdit::headersChanged, this, &VEditTab::updateTocFromHeaders); connect(dynamic_cast(m_textEditor), &VMdEdit::statusChanged, this, &VEditTab::noticeStatusChanged); connect(m_textEditor, SIGNAL(curHeaderChanged(int, int)), this, SLOT(updateCurHeader(int, int))); connect(m_textEditor, &VEdit::textChanged, this, &VEditTab::handleTextChanged); m_textEditor->reloadFile(); addWidget(m_textEditor); setupMarkdownPreview(); break; case DocType::Html: m_textEditor = new VEdit(m_file, this); connect(m_textEditor, &VEdit::textChanged, this, &VEditTab::handleTextChanged); m_textEditor->reloadFile(); addWidget(m_textEditor); webPreviewer = NULL; break; default: qWarning() << "error: unknown doc type" << int(m_file->getDocType()); Q_ASSERT(false); } } void VEditTab::handleTextChanged() { if (m_fileModified) { return; } noticeStatusChanged(); } void VEditTab::noticeStatusChanged() { m_fileModified = m_file->isModified(); emit statusChanged(); } void VEditTab::showFileReadMode() { qDebug() << "read" << m_file->getName(); isEditMode = false; int outlineIndex = curHeader.m_outlineIndex; switch (m_file->getDocType()) { case DocType::Html: m_textEditor->setReadOnly(true); break; case DocType::Markdown: if (mdConverterType == MarkdownConverterType::Marked) { document.setText(m_file->getContent()); updateTocFromHtml(document.getToc()); } else { previewByConverter(); } setCurrentWidget(webPreviewer); clearFindSelectionInWebView(); scrollPreviewToHeader(outlineIndex); break; default: qWarning() << "error: unknown doc type" << int(m_file->getDocType()); Q_ASSERT(false); } noticeStatusChanged(); } void VEditTab::scrollPreviewToHeader(int p_outlineIndex) { Q_ASSERT(p_outlineIndex >= 0); if (p_outlineIndex < tableOfContent.headers.size()) { QString anchor = tableOfContent.headers[p_outlineIndex].anchor; qDebug() << "scroll preview to" << p_outlineIndex << anchor; if (!anchor.isEmpty()) { document.scrollToAnchor(anchor.mid(1)); } } } void VEditTab::previewByConverter() { VMarkdownConverter mdConverter; QString &content = m_file->getContent(); QString html = mdConverter.generateHtml(content, vconfig.getMarkdownExtensions()); QRegularExpression tocExp("

\\[TOC\\]<\\/p>", QRegularExpression::CaseInsensitiveOption); QString toc = mdConverter.generateToc(content, vconfig.getMarkdownExtensions()); processHoedownToc(toc); html.replace(tocExp, toc); document.setHtml(html); // Hoedown will add '\n' while Marked does not updateTocFromHtml(toc); } void VEditTab::processHoedownToc(QString &p_toc) { // Hoedown will add '\n'. p_toc.replace("\n", ""); // Hoedown will translate `_` in title to ``. p_toc.replace("", "_"); p_toc.replace("", "_"); } void VEditTab::showFileEditMode() { isEditMode = true; // beginEdit() may change curHeader. int outlineIndex = curHeader.m_outlineIndex; m_textEditor->beginEdit(); setCurrentWidget(m_textEditor); if (m_file->getDocType() == DocType::Markdown) { dynamic_cast(m_textEditor)->scrollToHeader(outlineIndex); } m_textEditor->setFocus(); noticeStatusChanged(); } bool VEditTab::closeFile(bool p_forced) { if (p_forced && isEditMode) { // Discard buffer content m_textEditor->reloadFile(); m_textEditor->endEdit(); showFileReadMode(); } else { readFile(); } return !isEditMode; } void VEditTab::editFile() { if (isEditMode) { return; } showFileEditMode(); } void VEditTab::readFile() { if (!isEditMode) { return; } if (m_textEditor->isModified()) { // Prompt to save the changes int ret = VUtils::showMessage(QMessageBox::Information, tr("Information"), QString("Note %1 has been modified.").arg(m_file->getName()), tr("Do you want to save your changes?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Save, this); switch (ret) { case QMessageBox::Save: saveFile(); // Fall through case QMessageBox::Discard: m_textEditor->reloadFile(); break; case QMessageBox::Cancel: // Nothing to do if user cancel this action return; default: qWarning() << "error: wrong return value from QMessageBox:" << ret; return; } } m_textEditor->endEdit(); showFileReadMode(); } bool VEditTab::saveFile() { bool ret; if (!isEditMode || !m_textEditor->isModified()) { return true; } // Make sure the file already exists. Temporary deal with cases when user delete or move // a file. QString filePath = m_file->retrivePath(); if (!QFile(filePath).exists()) { qWarning() << filePath << "being written has been removed"; VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to save note"), QString("%1 being written has been removed.").arg(filePath), QMessageBox::Ok, QMessageBox::Ok, this); return false; } m_textEditor->saveFile(); ret = m_file->save(); if (!ret) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to save note"), QString("Fail to write to disk when saving a note. Please try it again."), QMessageBox::Ok, QMessageBox::Ok, this); m_textEditor->setModified(true); } noticeStatusChanged(); return ret; } void VEditTab::setupMarkdownPreview() { webPreviewer = new QWebEngineView(this); VPreviewPage *page = new VPreviewPage(this); webPreviewer->setPage(page); QWebChannel *channel = new QWebChannel(this); channel->registerObject(QStringLiteral("content"), &document); connect(&document, &VDocument::tocChanged, this, &VEditTab::updateTocFromHtml); connect(&document, SIGNAL(headerChanged(const QString&)), this, SLOT(updateCurHeader(const QString &))); page->setWebChannel(channel); if (mdConverterType == MarkdownConverterType::Marked) { webPreviewer->setHtml(VNote::templateHtml, QUrl::fromLocalFile(m_file->retriveBasePath() + QDir::separator())); } else { webPreviewer->setHtml(VNote::preTemplateHtml + VNote::postTemplateHtml, QUrl::fromLocalFile(m_file->retriveBasePath() + QDir::separator())); } addWidget(webPreviewer); } void VEditTab::focusTab() { currentWidget()->setFocus(); emit getFocused(); } void VEditTab::handleFocusChanged(QWidget * /* old */, QWidget *now) { if (isChild(now)) { emit getFocused(); } } void VEditTab::updateTocFromHtml(const QString &tocHtml) { if (isEditMode) { return; } tableOfContent.type = VHeaderType::Anchor; QVector &headers = tableOfContent.headers; headers.clear(); if (!tocHtml.isEmpty()) { QXmlStreamReader xml(tocHtml); if (xml.readNextStartElement()) { if (xml.name() == "ul") { parseTocUl(xml, headers, 1); } else { qWarning() << "error: TOC HTML does not start with