From 849fdf05bd416164791d80a10529e0074cb093ae Mon Sep 17 00:00:00 2001 From: Le Tan Date: Tue, 8 Nov 2016 23:21:22 +0800 Subject: [PATCH] support synchronization between preview page and outline Signed-off-by: Le Tan --- src/resources/pre_template.html | 23 +++++++++++++++ src/resources/template.html | 23 +++++++++++++++ src/vdocument.cpp | 9 ++++++ src/vdocument.h | 3 ++ src/veditarea.cpp | 10 +++++++ src/veditarea.h | 2 ++ src/vedittab.cpp | 15 +++++++++- src/vedittab.h | 3 ++ src/veditwindow.cpp | 16 ++++++++++ src/veditwindow.h | 2 ++ src/vmainwindow.cpp | 2 ++ src/voutline.cpp | 52 +++++++++++++++++++++++++++++++++ src/voutline.h | 4 +++ src/vtoc.cpp | 2 +- src/vtoc.h | 2 +- 15 files changed, 165 insertions(+), 3 deletions(-) diff --git a/src/resources/pre_template.html b/src/resources/pre_template.html index f3a69794..83f9b24e 100644 --- a/src/resources/pre_template.html +++ b/src/resources/pre_template.html @@ -26,4 +26,27 @@ content.requestScrollToAnchor.connect(scrollToAnchor); } ); + + window.onscroll = function() { + var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset; + var eles = document.querySelectorAll("h1, h2, h3, h4, h5, h6"); + + if (eles.length == 0) { + return; + } + var curIdx = 0; + var biaScrollTop = scrollTop + 20; + for (var i = 0; i < eles.length; ++i) { + if (biaScrollTop >= eles[i].offsetTop) { + curIdx = i; + } else { + break; + } + } + + var curHeader = eles[curIdx].getAttribute("id"); + if (curHeader != null) { + content.setHeader(curHeader); + } + } diff --git a/src/resources/template.html b/src/resources/template.html index 942ed8a8..3f2ed8cb 100644 --- a/src/resources/template.html +++ b/src/resources/template.html @@ -157,6 +157,29 @@ content.requestScrollToAnchor.connect(scrollToAnchor); } ); + + window.onscroll = function() { + var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset; + var eles = document.querySelectorAll("h1, h2, h3, h4, h5, h6"); + + if (eles.length == 0) { + return; + } + var curIdx = 0; + var biaScrollTop = scrollTop + 20; + for (var i = 0; i < eles.length; ++i) { + if (biaScrollTop >= eles[i].offsetTop) { + curIdx = i; + } else { + break; + } + } + + var curHeader = eles[curIdx].getAttribute("id"); + if (curHeader != null) { + content.setHeader(curHeader); + } + } diff --git a/src/vdocument.cpp b/src/vdocument.cpp index 9e3c0f8f..0cacc925 100644 --- a/src/vdocument.cpp +++ b/src/vdocument.cpp @@ -43,3 +43,12 @@ void VDocument::scrollToAnchor(const QString &anchor) { emit requestScrollToAnchor(anchor); } + +void VDocument::setHeader(const QString &anchor) +{ + if (anchor == m_header) { + return; + } + m_header = anchor; + emit headerChanged(m_header); +} diff --git a/src/vdocument.h b/src/vdocument.h index 88982a23..d3f9558b 100644 --- a/src/vdocument.h +++ b/src/vdocument.h @@ -21,15 +21,18 @@ public: public slots: // Will be called in the HTML side void setToc(const QString &toc); + void setHeader(const QString &anchor); signals: void textChanged(const QString &text); void tocChanged(const QString &toc); void requestScrollToAnchor(const QString &anchor); + void headerChanged(const QString &anchor); private: QString m_text; QString m_toc; + QString m_header; }; #endif // VDOCUMENT_H diff --git a/src/veditarea.cpp b/src/veditarea.cpp index 806a9a58..00bdbe0f 100644 --- a/src/veditarea.cpp +++ b/src/veditarea.cpp @@ -39,6 +39,8 @@ void VEditArea::insertSplitWindow(int idx) this, &VEditArea::handleWindowFocused); connect(win, &VEditWindow::outlineChanged, this, &VEditArea::handleOutlineChanged); + connect(win, &VEditWindow::curHeaderChanged, + this, &VEditArea::handleCurHeaderChanged); int nrWin = splitter->count(); if (nrWin == 1) { @@ -290,6 +292,14 @@ void VEditArea::handleOutlineChanged(const VToc &toc) } } +void VEditArea::handleCurHeaderChanged(const VAnchor &anchor) +{ + QObject *winObject = sender(); + if (splitter->widget(curWindowIndex) == winObject) { + emit curHeaderChanged(anchor); + } +} + void VEditArea::handleOutlineItemActivated(const VAnchor &anchor) { // Notice current window diff --git a/src/veditarea.h b/src/veditarea.h index bbecb613..c3f2007b 100644 --- a/src/veditarea.h +++ b/src/veditarea.h @@ -26,6 +26,7 @@ signals: void curTabStatusChanged(const QString ¬ebook, const QString &relativePath, bool editMode, bool modifiable); void outlineChanged(const VToc &toc); + void curHeaderChanged(const VAnchor &anchor); protected: void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; @@ -47,6 +48,7 @@ private slots: void handleRemoveSplitRequest(VEditWindow *curWindow); void handleWindowFocused(); void handleOutlineChanged(const VToc &toc); + void handleCurHeaderChanged(const VAnchor &anchor); private: void setupUI(); diff --git a/src/vedittab.cpp b/src/vedittab.cpp index 37f04fdf..ca0a8e2b 100644 --- a/src/vedittab.cpp +++ b/src/vedittab.cpp @@ -221,6 +221,8 @@ void VEditTab::setupMarkdownPreview() channel->registerObject(QStringLiteral("content"), &document); connect(&document, &VDocument::tocChanged, this, &VEditTab::updateTocFromHtml); + connect(&document, &VDocument::headerChanged, + this, &VEditTab::updateCurHeader); page->setWebChannel(channel); if (mdConverterType == MarkdownConverterType::Marked) { @@ -263,7 +265,6 @@ void VEditTab::updateTocFromHtml(const QString &tocHtml) return; } - tableOfContent.curHeaderIndex = 0; tableOfContent.filePath = QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName)); tableOfContent.valid = true; @@ -349,3 +350,15 @@ void VEditTab::scrollToAnchor(const VAnchor &anchor) } } } + +void VEditTab::updateCurHeader(const QString &anchor) +{ + if (curHeader == anchor) { + return; + } + curHeader = anchor; + if (!anchor.isEmpty()) { + emit curHeaderChanged(VAnchor(QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName)), + "#" + anchor, -1)); + } +} diff --git a/src/vedittab.h b/src/vedittab.h index 46be504a..f5cab51a 100644 --- a/src/vedittab.h +++ b/src/vedittab.h @@ -39,10 +39,12 @@ public: signals: void getFocused(); void outlineChanged(const VToc &toc); + void curHeaderChanged(const VAnchor &anchor); private slots: void handleFocusChanged(QWidget *old, QWidget *now); void updateTocFromHtml(const QString &tocHtml); + void updateCurHeader(const QString &anchor); private: bool isMarkdown(const QString &name); @@ -63,6 +65,7 @@ private: VDocument document; MarkdownConverterType mdConverterType; VToc tableOfContent; + QString curHeader; }; inline bool VEditTab::getIsEditMode() const diff --git a/src/veditwindow.cpp b/src/veditwindow.cpp index 4f467987..a84bff33 100644 --- a/src/veditwindow.cpp +++ b/src/veditwindow.cpp @@ -167,6 +167,8 @@ int VEditWindow::openFileInTab(const QString ¬ebook, const QString &relativeP this, &VEditWindow::getFocused); connect(editor, &VEditTab::outlineChanged, this, &VEditWindow::handleOutlineChanged); + connect(editor, &VEditTab::curHeaderChanged, + this, &VEditWindow::handleCurHeaderChanged); QJsonObject tabJson; tabJson["notebook"] = notebook; @@ -384,6 +386,20 @@ void VEditWindow::handleOutlineChanged(const VToc &toc) } } +void VEditWindow::handleCurHeaderChanged(const VAnchor &anchor) +{ + // Only propagate it if it is current tab + int idx = currentIndex(); + QJsonObject tabJson = tabBar()->tabData(idx).toJsonObject(); + Q_ASSERT(!tabJson.isEmpty()); + QString path = vnote->getNotebookPath(tabJson["notebook"].toString()); + path = QDir::cleanPath(QDir(path).filePath(tabJson["relative_path"].toString())); + + if (anchor.filePath == path) { + emit curHeaderChanged(anchor); + } +} + void VEditWindow::scrollCurTab(const VAnchor &anchor) { int idx = currentIndex(); diff --git a/src/veditwindow.h b/src/veditwindow.h index 76886cba..c6f4bbf9 100644 --- a/src/veditwindow.h +++ b/src/veditwindow.h @@ -50,6 +50,7 @@ signals: // This widget or its children get the focus void getFocused(); void outlineChanged(const VToc &toc); + void curHeaderChanged(const VAnchor &anchor); private slots: bool handleTabCloseRequest(int index); @@ -59,6 +60,7 @@ private slots: void contextMenuRequested(QPoint pos); void tabListJump(QAction *action); void handleOutlineChanged(const VToc &toc); + void handleCurHeaderChanged(const VAnchor &anchor); private: void setupCornerWidget(); diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 748fddcb..ddd0ff3e 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -347,6 +347,8 @@ void VMainWindow::initDockWindows() outline, &VOutline::updateOutline); connect(outline, &VOutline::outlineItemActivated, editArea, &VEditArea::handleOutlineItemActivated); + connect(editArea, &VEditArea::curHeaderChanged, + outline, &VOutline::updateCurHeader); toolBox->addItem(outline, QIcon(":/resources/icons/outline.svg"), tr("Outline")); dock->setWidget(toolBox); addDockWidget(Qt::RightDockWidgetArea, dock); diff --git a/src/voutline.cpp b/src/voutline.cpp index 70867d4f..6b45e91b 100644 --- a/src/voutline.cpp +++ b/src/voutline.cpp @@ -10,6 +10,7 @@ VOutline::VOutline(QWidget *parent) { setColumnCount(1); setHeaderHidden(true); + setSelectionMode(QAbstractItemView::SingleSelection); connect(this, &VOutline::itemClicked, this, &VOutline::handleItemClicked); @@ -64,7 +65,11 @@ void VOutline::updateTreeByLevel(const QVector &headers, int &index, void VOutline::expandTree() { + if (topLevelItemCount() == 0) { + return; + } expandAll(); + setItemSelected(topLevelItem(0), true); } @@ -77,3 +82,50 @@ void VOutline::handleItemClicked(QTreeWidgetItem *item, int column) qDebug() << "click anchor" << anchor << lineNumber; emit outlineItemActivated(VAnchor(outline.filePath, anchor, lineNumber)); } + +void VOutline::updateCurHeader(const VAnchor &anchor) +{ + curHeader = anchor; + if (outline.type == VHeaderType::Anchor) { + selectAnchor(anchor.anchor); + } else { + // Select by lineNumber + } +} + +void VOutline::selectAnchor(const QString &anchor) +{ + QList selected = selectedItems(); + foreach (QTreeWidgetItem *item, selected) { + setItemSelected(item, false); + } + + int nrTop = topLevelItemCount(); + for (int i = 0; i < nrTop; ++i) { + if (selectAnchorOne(topLevelItem(i), anchor)) { + return; + } + } +} + +bool VOutline::selectAnchorOne(QTreeWidgetItem *item, const QString &anchor) +{ + if (!item) { + return false; + } + QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject(); + QString itemAnchor = itemJson["anchor"].toString(); + if (itemAnchor == anchor) { + // Select this item + setItemSelected(item, true); + return true; + } + + int nrChild = item->childCount(); + for (int i = 0; i < nrChild; ++i) { + if (selectAnchorOne(item->child(i), anchor)) { + return true; + } + } + return false; +} diff --git a/src/voutline.h b/src/voutline.h index 466161d1..bd775710 100644 --- a/src/voutline.h +++ b/src/voutline.h @@ -15,6 +15,7 @@ signals: public slots: void updateOutline(const VToc &toc); + void updateCurHeader(const VAnchor &anchor); private slots: void handleItemClicked(QTreeWidgetItem *item, int column); @@ -24,8 +25,11 @@ private: void updateTreeByLevel(const QVector &headers, int &index, QTreeWidgetItem *parent, QTreeWidgetItem *last, int level); void expandTree(); + void selectAnchor(const QString &anchor); + bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor); VToc outline; + VAnchor curHeader; }; #endif // VOUTLINE_H diff --git a/src/vtoc.cpp b/src/vtoc.cpp index 1ee5dac7..5bacae14 100644 --- a/src/vtoc.cpp +++ b/src/vtoc.cpp @@ -1,6 +1,6 @@ #include "vtoc.h" VToc::VToc() - : curHeaderIndex(0), type(VHeaderType::Anchor), valid(false) + : type(VHeaderType::Anchor), valid(false) { } diff --git a/src/vtoc.h b/src/vtoc.h index daba74cf..287c68cd 100644 --- a/src/vtoc.h +++ b/src/vtoc.h @@ -23,6 +23,7 @@ struct VHeader struct VAnchor { + VAnchor() : lineNumber(-1) {} VAnchor(const QString filePath, const QString &anchor, int lineNumber) : filePath(filePath), anchor(anchor), lineNumber(lineNumber) {} QString filePath; @@ -36,7 +37,6 @@ public: VToc(); QVector headers; - int curHeaderIndex; int type; QString filePath; bool valid;