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);
|
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>
|
</script>
|
||||||
|
@ -157,6 +157,29 @@
|
|||||||
content.requestScrollToAnchor.connect(scrollToAnchor);
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -43,3 +43,12 @@ void VDocument::scrollToAnchor(const QString &anchor)
|
|||||||
{
|
{
|
||||||
emit requestScrollToAnchor(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:
|
public slots:
|
||||||
// Will be called in the HTML side
|
// Will be called in the HTML side
|
||||||
void setToc(const QString &toc);
|
void setToc(const QString &toc);
|
||||||
|
void setHeader(const QString &anchor);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void textChanged(const QString &text);
|
void textChanged(const QString &text);
|
||||||
void tocChanged(const QString &toc);
|
void tocChanged(const QString &toc);
|
||||||
void requestScrollToAnchor(const QString &anchor);
|
void requestScrollToAnchor(const QString &anchor);
|
||||||
|
void headerChanged(const QString &anchor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_text;
|
QString m_text;
|
||||||
QString m_toc;
|
QString m_toc;
|
||||||
|
QString m_header;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VDOCUMENT_H
|
#endif // VDOCUMENT_H
|
||||||
|
@ -39,6 +39,8 @@ void VEditArea::insertSplitWindow(int idx)
|
|||||||
this, &VEditArea::handleWindowFocused);
|
this, &VEditArea::handleWindowFocused);
|
||||||
connect(win, &VEditWindow::outlineChanged,
|
connect(win, &VEditWindow::outlineChanged,
|
||||||
this, &VEditArea::handleOutlineChanged);
|
this, &VEditArea::handleOutlineChanged);
|
||||||
|
connect(win, &VEditWindow::curHeaderChanged,
|
||||||
|
this, &VEditArea::handleCurHeaderChanged);
|
||||||
|
|
||||||
int nrWin = splitter->count();
|
int nrWin = splitter->count();
|
||||||
if (nrWin == 1) {
|
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)
|
void VEditArea::handleOutlineItemActivated(const VAnchor &anchor)
|
||||||
{
|
{
|
||||||
// Notice current window
|
// Notice current window
|
||||||
|
@ -26,6 +26,7 @@ signals:
|
|||||||
void curTabStatusChanged(const QString ¬ebook, const QString &relativePath,
|
void curTabStatusChanged(const QString ¬ebook, const QString &relativePath,
|
||||||
bool editMode, bool modifiable);
|
bool editMode, bool modifiable);
|
||||||
void outlineChanged(const VToc &toc);
|
void outlineChanged(const VToc &toc);
|
||||||
|
void curHeaderChanged(const VAnchor &anchor);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
|
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
|
||||||
@ -47,6 +48,7 @@ private slots:
|
|||||||
void handleRemoveSplitRequest(VEditWindow *curWindow);
|
void handleRemoveSplitRequest(VEditWindow *curWindow);
|
||||||
void handleWindowFocused();
|
void handleWindowFocused();
|
||||||
void handleOutlineChanged(const VToc &toc);
|
void handleOutlineChanged(const VToc &toc);
|
||||||
|
void handleCurHeaderChanged(const VAnchor &anchor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupUI();
|
void setupUI();
|
||||||
|
@ -221,6 +221,8 @@ void VEditTab::setupMarkdownPreview()
|
|||||||
channel->registerObject(QStringLiteral("content"), &document);
|
channel->registerObject(QStringLiteral("content"), &document);
|
||||||
connect(&document, &VDocument::tocChanged,
|
connect(&document, &VDocument::tocChanged,
|
||||||
this, &VEditTab::updateTocFromHtml);
|
this, &VEditTab::updateTocFromHtml);
|
||||||
|
connect(&document, &VDocument::headerChanged,
|
||||||
|
this, &VEditTab::updateCurHeader);
|
||||||
page->setWebChannel(channel);
|
page->setWebChannel(channel);
|
||||||
|
|
||||||
if (mdConverterType == MarkdownConverterType::Marked) {
|
if (mdConverterType == MarkdownConverterType::Marked) {
|
||||||
@ -263,7 +265,6 @@ void VEditTab::updateTocFromHtml(const QString &tocHtml)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tableOfContent.curHeaderIndex = 0;
|
|
||||||
tableOfContent.filePath = QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName));
|
tableOfContent.filePath = QDir::cleanPath(QDir(noteFile->basePath).filePath(noteFile->fileName));
|
||||||
tableOfContent.valid = true;
|
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:
|
signals:
|
||||||
void getFocused();
|
void getFocused();
|
||||||
void outlineChanged(const VToc &toc);
|
void outlineChanged(const VToc &toc);
|
||||||
|
void curHeaderChanged(const VAnchor &anchor);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleFocusChanged(QWidget *old, QWidget *now);
|
void handleFocusChanged(QWidget *old, QWidget *now);
|
||||||
void updateTocFromHtml(const QString &tocHtml);
|
void updateTocFromHtml(const QString &tocHtml);
|
||||||
|
void updateCurHeader(const QString &anchor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isMarkdown(const QString &name);
|
bool isMarkdown(const QString &name);
|
||||||
@ -63,6 +65,7 @@ private:
|
|||||||
VDocument document;
|
VDocument document;
|
||||||
MarkdownConverterType mdConverterType;
|
MarkdownConverterType mdConverterType;
|
||||||
VToc tableOfContent;
|
VToc tableOfContent;
|
||||||
|
QString curHeader;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool VEditTab::getIsEditMode() const
|
inline bool VEditTab::getIsEditMode() const
|
||||||
|
@ -167,6 +167,8 @@ int VEditWindow::openFileInTab(const QString ¬ebook, const QString &relativeP
|
|||||||
this, &VEditWindow::getFocused);
|
this, &VEditWindow::getFocused);
|
||||||
connect(editor, &VEditTab::outlineChanged,
|
connect(editor, &VEditTab::outlineChanged,
|
||||||
this, &VEditWindow::handleOutlineChanged);
|
this, &VEditWindow::handleOutlineChanged);
|
||||||
|
connect(editor, &VEditTab::curHeaderChanged,
|
||||||
|
this, &VEditWindow::handleCurHeaderChanged);
|
||||||
|
|
||||||
QJsonObject tabJson;
|
QJsonObject tabJson;
|
||||||
tabJson["notebook"] = notebook;
|
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)
|
void VEditWindow::scrollCurTab(const VAnchor &anchor)
|
||||||
{
|
{
|
||||||
int idx = currentIndex();
|
int idx = currentIndex();
|
||||||
|
@ -50,6 +50,7 @@ signals:
|
|||||||
// This widget or its children get the focus
|
// This widget or its children get the focus
|
||||||
void getFocused();
|
void getFocused();
|
||||||
void outlineChanged(const VToc &toc);
|
void outlineChanged(const VToc &toc);
|
||||||
|
void curHeaderChanged(const VAnchor &anchor);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
bool handleTabCloseRequest(int index);
|
bool handleTabCloseRequest(int index);
|
||||||
@ -59,6 +60,7 @@ private slots:
|
|||||||
void contextMenuRequested(QPoint pos);
|
void contextMenuRequested(QPoint pos);
|
||||||
void tabListJump(QAction *action);
|
void tabListJump(QAction *action);
|
||||||
void handleOutlineChanged(const VToc &toc);
|
void handleOutlineChanged(const VToc &toc);
|
||||||
|
void handleCurHeaderChanged(const VAnchor &anchor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupCornerWidget();
|
void setupCornerWidget();
|
||||||
|
@ -347,6 +347,8 @@ void VMainWindow::initDockWindows()
|
|||||||
outline, &VOutline::updateOutline);
|
outline, &VOutline::updateOutline);
|
||||||
connect(outline, &VOutline::outlineItemActivated,
|
connect(outline, &VOutline::outlineItemActivated,
|
||||||
editArea, &VEditArea::handleOutlineItemActivated);
|
editArea, &VEditArea::handleOutlineItemActivated);
|
||||||
|
connect(editArea, &VEditArea::curHeaderChanged,
|
||||||
|
outline, &VOutline::updateCurHeader);
|
||||||
toolBox->addItem(outline, QIcon(":/resources/icons/outline.svg"), tr("Outline"));
|
toolBox->addItem(outline, QIcon(":/resources/icons/outline.svg"), tr("Outline"));
|
||||||
dock->setWidget(toolBox);
|
dock->setWidget(toolBox);
|
||||||
addDockWidget(Qt::RightDockWidgetArea, dock);
|
addDockWidget(Qt::RightDockWidgetArea, dock);
|
||||||
|
@ -10,6 +10,7 @@ VOutline::VOutline(QWidget *parent)
|
|||||||
{
|
{
|
||||||
setColumnCount(1);
|
setColumnCount(1);
|
||||||
setHeaderHidden(true);
|
setHeaderHidden(true);
|
||||||
|
setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
|
||||||
connect(this, &VOutline::itemClicked,
|
connect(this, &VOutline::itemClicked,
|
||||||
this, &VOutline::handleItemClicked);
|
this, &VOutline::handleItemClicked);
|
||||||
@ -64,7 +65,11 @@ void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index,
|
|||||||
|
|
||||||
void VOutline::expandTree()
|
void VOutline::expandTree()
|
||||||
{
|
{
|
||||||
|
if (topLevelItemCount() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
expandAll();
|
expandAll();
|
||||||
|
setItemSelected(topLevelItem(0), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -77,3 +82,50 @@ void VOutline::handleItemClicked(QTreeWidgetItem *item, int column)
|
|||||||
qDebug() << "click anchor" << anchor << lineNumber;
|
qDebug() << "click anchor" << anchor << lineNumber;
|
||||||
emit outlineItemActivated(VAnchor(outline.filePath, 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:
|
public slots:
|
||||||
void updateOutline(const VToc &toc);
|
void updateOutline(const VToc &toc);
|
||||||
|
void updateCurHeader(const VAnchor &anchor);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleItemClicked(QTreeWidgetItem *item, int column);
|
void handleItemClicked(QTreeWidgetItem *item, int column);
|
||||||
@ -24,8 +25,11 @@ private:
|
|||||||
void updateTreeByLevel(const QVector<VHeader> &headers, int &index, QTreeWidgetItem *parent,
|
void updateTreeByLevel(const QVector<VHeader> &headers, int &index, QTreeWidgetItem *parent,
|
||||||
QTreeWidgetItem *last, int level);
|
QTreeWidgetItem *last, int level);
|
||||||
void expandTree();
|
void expandTree();
|
||||||
|
void selectAnchor(const QString &anchor);
|
||||||
|
bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor);
|
||||||
|
|
||||||
VToc outline;
|
VToc outline;
|
||||||
|
VAnchor curHeader;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VOUTLINE_H
|
#endif // VOUTLINE_H
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include "vtoc.h"
|
#include "vtoc.h"
|
||||||
|
|
||||||
VToc::VToc()
|
VToc::VToc()
|
||||||
: curHeaderIndex(0), type(VHeaderType::Anchor), valid(false)
|
: type(VHeaderType::Anchor), valid(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ struct VHeader
|
|||||||
|
|
||||||
struct VAnchor
|
struct VAnchor
|
||||||
{
|
{
|
||||||
|
VAnchor() : lineNumber(-1) {}
|
||||||
VAnchor(const QString filePath, const QString &anchor, int lineNumber)
|
VAnchor(const QString filePath, const QString &anchor, int lineNumber)
|
||||||
: filePath(filePath), anchor(anchor), lineNumber(lineNumber) {}
|
: filePath(filePath), anchor(anchor), lineNumber(lineNumber) {}
|
||||||
QString filePath;
|
QString filePath;
|
||||||
@ -36,7 +37,6 @@ public:
|
|||||||
VToc();
|
VToc();
|
||||||
|
|
||||||
QVector<VHeader> headers;
|
QVector<VHeader> headers;
|
||||||
int curHeaderIndex;
|
|
||||||
int type;
|
int type;
|
||||||
QString filePath;
|
QString filePath;
|
||||||
bool valid;
|
bool valid;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user