mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
support synchronization between preview page and outline
Signed-off-by: Le Tan <tamlokveer@gmail.com>
This commit is contained in:
parent
fe3d16ba3b
commit
849fdf05bd
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,6 +1,6 @@
|
||||
#include "vtoc.h"
|
||||
|
||||
VToc::VToc()
|
||||
: curHeaderIndex(0), type(VHeaderType::Anchor), valid(false)
|
||||
: type(VHeaderType::Anchor), valid(false)
|
||||
{
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user