Browse Source

support synchronization between preview page and outline

Signed-off-by: Le Tan <[email protected]>
Le Tan 9 years ago
parent
commit
849fdf05bd

+ 23 - 0
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);
+    }
+  }
   </script>

+ 23 - 0
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);
+    }
+  }
   </script>
 </body>
 </html>

+ 9 - 0
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);
+}

+ 3 - 0
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

+ 10 - 0
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

+ 2 - 0
src/veditarea.h

@@ -26,6 +26,7 @@ signals:
     void curTabStatusChanged(const QString &notebook, 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();

+ 14 - 1
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));
+    }
+}

+ 3 - 0
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

+ 16 - 0
src/veditwindow.cpp

@@ -167,6 +167,8 @@ int VEditWindow::openFileInTab(const QString &notebook, 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();

+ 2 - 0
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();

+ 2 - 0
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);

+ 52 - 0
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<VHeader> &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<QTreeWidgetItem *> 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;
+}

+ 4 - 0
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<VHeader> &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

+ 1 - 1
src/vtoc.cpp

@@ -1,6 +1,6 @@
 #include "vtoc.h"
 
 VToc::VToc()
-    : curHeaderIndex(0), type(VHeaderType::Anchor), valid(false)
+    : type(VHeaderType::Anchor), valid(false)
 {
 }

+ 1 - 1
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<VHeader> headers;
-    int curHeaderIndex;
     int type;
     QString filePath;
     bool valid;