mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 22:09:52 +08:00
refactor TOC logics
This commit is contained in:
parent
3b011cd8de
commit
97051badf0
@ -83,85 +83,6 @@ var markdownToHtml = function(markdown, needToc) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle wrong title levels, such as '#' followed by '###'
|
|
||||||
var toPerfectToc = function(toc) {
|
|
||||||
var i;
|
|
||||||
var curLevel = 1;
|
|
||||||
var perfToc = [];
|
|
||||||
for (i in toc) {
|
|
||||||
var item = toc[i];
|
|
||||||
while (item.level > curLevel + 1) {
|
|
||||||
curLevel += 1;
|
|
||||||
var tmp = { level: curLevel,
|
|
||||||
anchor: item.anchor,
|
|
||||||
title: '[EMPTY]'
|
|
||||||
};
|
|
||||||
perfToc.push(tmp);
|
|
||||||
}
|
|
||||||
perfToc.push(item);
|
|
||||||
curLevel = item.level;
|
|
||||||
}
|
|
||||||
return perfToc;
|
|
||||||
};
|
|
||||||
|
|
||||||
var itemToHtml = function(item) {
|
|
||||||
return '<a href="#' + item.anchor + '">' + item.title + '</a>';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Turn a perfect toc to a tree using <ul>
|
|
||||||
var tocToTree = function(toc) {
|
|
||||||
var i;
|
|
||||||
var front = '<li>';
|
|
||||||
var ending = ['</li>'];
|
|
||||||
var curLevel = 1;
|
|
||||||
for (i in toc) {
|
|
||||||
var item = toc[i];
|
|
||||||
if (item.level == curLevel) {
|
|
||||||
front += '</li>';
|
|
||||||
front += '<li>';
|
|
||||||
front += itemToHtml(item);
|
|
||||||
} else if (item.level > curLevel) {
|
|
||||||
// assert(item.level - curLevel == 1)
|
|
||||||
front += '<ul>';
|
|
||||||
ending.push('</ul>');
|
|
||||||
front += '<li>';
|
|
||||||
front += itemToHtml(item);
|
|
||||||
ending.push('</li>');
|
|
||||||
curLevel = item.level;
|
|
||||||
} else {
|
|
||||||
while (item.level < curLevel) {
|
|
||||||
var ele = ending.pop();
|
|
||||||
front += ele;
|
|
||||||
if (ele == '</ul>') {
|
|
||||||
curLevel--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
front += '</li>';
|
|
||||||
front += '<li>';
|
|
||||||
front += itemToHtml(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (ending.length > 0) {
|
|
||||||
front += ending.pop();
|
|
||||||
}
|
|
||||||
front = front.replace("<li></li>", "");
|
|
||||||
front = '<ul>' + front + '</ul>';
|
|
||||||
return front;
|
|
||||||
};
|
|
||||||
|
|
||||||
var handleToc = function(needToc) {
|
|
||||||
var tocTree = tocToTree(toPerfectToc(toc));
|
|
||||||
content.setToc(tocTree);
|
|
||||||
|
|
||||||
// Add it to html
|
|
||||||
if (needToc) {
|
|
||||||
var eles = document.getElementsByClassName('vnote-toc');
|
|
||||||
for (var i = 0; i < eles.length; ++i) {
|
|
||||||
eles[i].innerHTML = tocTree;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var updateText = function(text) {
|
var updateText = function(text) {
|
||||||
var needToc = mdHasTocSection(text);
|
var needToc = mdHasTocSection(text);
|
||||||
var html = markdownToHtml(text, needToc);
|
var html = markdownToHtml(text, needToc);
|
||||||
|
@ -41,11 +41,23 @@ new QWebChannel(qt.webChannelTransport,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var g_muteScroll = false;
|
||||||
|
|
||||||
var scrollToAnchor = function(anchor) {
|
var scrollToAnchor = function(anchor) {
|
||||||
|
g_muteScroll = true;
|
||||||
|
if (!anchor) {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
g_muteScroll = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var anc = document.getElementById(anchor);
|
var anc = document.getElementById(anchor);
|
||||||
if (anc != null) {
|
if (anc != null) {
|
||||||
anc.scrollIntoView();
|
anc.scrollIntoView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable scroll temporarily.
|
||||||
|
setTimeout("g_muteScroll = false", 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.onwheel = function(e) {
|
window.onwheel = function(e) {
|
||||||
@ -57,13 +69,19 @@ window.onwheel = function(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.onscroll = function() {
|
window.onscroll = function() {
|
||||||
|
if (g_muteScroll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset;
|
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset;
|
||||||
var eles = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
|
var eles = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
|
||||||
|
|
||||||
if (eles.length == 0) {
|
if (eles.length == 0) {
|
||||||
|
content.setHeader("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var curIdx = 0;
|
|
||||||
|
var curIdx = -1;
|
||||||
var biaScrollTop = scrollTop + 50;
|
var biaScrollTop = scrollTop + 50;
|
||||||
for (var i = 0; i < eles.length; ++i) {
|
for (var i = 0; i < eles.length; ++i) {
|
||||||
if (biaScrollTop >= eles[i].offsetTop) {
|
if (biaScrollTop >= eles[i].offsetTop) {
|
||||||
@ -73,10 +91,12 @@ window.onscroll = function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var curHeader = eles[curIdx].getAttribute("id");
|
var curHeader = null;
|
||||||
if (curHeader != null) {
|
if (curIdx != -1) {
|
||||||
content.setHeader(curHeader);
|
curHeader = eles[curIdx].getAttribute("id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content.setHeader(curHeader ? curHeader : "");
|
||||||
};
|
};
|
||||||
|
|
||||||
document.onkeydown = function(e) {
|
document.onkeydown = function(e) {
|
||||||
@ -326,3 +346,106 @@ var escapeHtml = function(text) {
|
|||||||
|
|
||||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Return the topest level of @toc, starting from 1.
|
||||||
|
var baseLevelOfToc = function(p_toc) {
|
||||||
|
var level = -1;
|
||||||
|
for (i in p_toc) {
|
||||||
|
if (level == -1) {
|
||||||
|
level = p_toc[i].level;
|
||||||
|
} else if (level > p_toc[i].level) {
|
||||||
|
level = p_toc[i].level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level == -1) {
|
||||||
|
level = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return level;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle wrong title levels, such as '#' followed by '###'
|
||||||
|
var toPerfectToc = function(p_toc, p_baseLevel) {
|
||||||
|
var i;
|
||||||
|
var curLevel = p_baseLevel - 1;
|
||||||
|
var perfToc = [];
|
||||||
|
for (i in p_toc) {
|
||||||
|
var item = p_toc[i];
|
||||||
|
|
||||||
|
// Insert empty header.
|
||||||
|
while (item.level > curLevel + 1) {
|
||||||
|
curLevel += 1;
|
||||||
|
var tmp = { level: curLevel,
|
||||||
|
anchor: '',
|
||||||
|
title: '[EMPTY]'
|
||||||
|
};
|
||||||
|
perfToc.push(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
perfToc.push(item);
|
||||||
|
curLevel = item.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
return perfToc;
|
||||||
|
};
|
||||||
|
|
||||||
|
var itemToHtml = function(item) {
|
||||||
|
return '<a href="#' + item.anchor + '">' + item.title + '</a>';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Turn a perfect toc to a tree using <ul>
|
||||||
|
var tocToTree = function(p_toc, p_baseLevel) {
|
||||||
|
var i;
|
||||||
|
var front = '<li>';
|
||||||
|
var ending = ['</li>'];
|
||||||
|
var curLevel = p_baseLevel;
|
||||||
|
for (i in p_toc) {
|
||||||
|
var item = p_toc[i];
|
||||||
|
if (item.level == curLevel) {
|
||||||
|
front += '</li>';
|
||||||
|
front += '<li>';
|
||||||
|
front += itemToHtml(item);
|
||||||
|
} else if (item.level > curLevel) {
|
||||||
|
// assert(item.level - curLevel == 1)
|
||||||
|
front += '<ul>';
|
||||||
|
ending.push('</ul>');
|
||||||
|
front += '<li>';
|
||||||
|
front += itemToHtml(item);
|
||||||
|
ending.push('</li>');
|
||||||
|
curLevel = item.level;
|
||||||
|
} else {
|
||||||
|
while (item.level < curLevel) {
|
||||||
|
var ele = ending.pop();
|
||||||
|
front += ele;
|
||||||
|
if (ele == '</ul>') {
|
||||||
|
curLevel--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
front += '</li>';
|
||||||
|
front += '<li>';
|
||||||
|
front += itemToHtml(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (ending.length > 0) {
|
||||||
|
front += ending.pop();
|
||||||
|
}
|
||||||
|
front = front.replace("<li></li>", "");
|
||||||
|
front = '<ul>' + front + '</ul>';
|
||||||
|
return front;
|
||||||
|
};
|
||||||
|
|
||||||
|
var handleToc = function(needToc) {
|
||||||
|
var baseLevel = baseLevelOfToc(toc);
|
||||||
|
var tocTree = tocToTree(toPerfectToc(toc, baseLevel), baseLevel);
|
||||||
|
content.setToc(tocTree, baseLevel);
|
||||||
|
|
||||||
|
// Add it to html
|
||||||
|
if (needToc) {
|
||||||
|
var eles = document.getElementsByClassName('vnote-toc');
|
||||||
|
for (var i = 0; i < eles.length; ++i) {
|
||||||
|
eles[i].innerHTML = tocTree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@ -36,85 +36,6 @@ var markdownToHtml = function(markdown, needToc) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle wrong title levels, such as '#' followed by '###'
|
|
||||||
var toPerfectToc = function(toc) {
|
|
||||||
var i;
|
|
||||||
var curLevel = 1;
|
|
||||||
var perfToc = [];
|
|
||||||
for (i in toc) {
|
|
||||||
var item = toc[i];
|
|
||||||
while (item.level > curLevel + 1) {
|
|
||||||
curLevel += 1;
|
|
||||||
var tmp = { level: curLevel,
|
|
||||||
anchor: item.anchor,
|
|
||||||
title: '[EMPTY]'
|
|
||||||
};
|
|
||||||
perfToc.push(tmp);
|
|
||||||
}
|
|
||||||
perfToc.push(item);
|
|
||||||
curLevel = item.level;
|
|
||||||
}
|
|
||||||
return perfToc;
|
|
||||||
};
|
|
||||||
|
|
||||||
var itemToHtml = function(item) {
|
|
||||||
return '<a href="#' + item.anchor + '">' + item.title + '</a>';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Turn a perfect toc to a tree using <ul>
|
|
||||||
var tocToTree = function(toc) {
|
|
||||||
var i;
|
|
||||||
var front = '<li>';
|
|
||||||
var ending = ['</li>'];
|
|
||||||
var curLevel = 1;
|
|
||||||
for (i in toc) {
|
|
||||||
var item = toc[i];
|
|
||||||
if (item.level == curLevel) {
|
|
||||||
front += '</li>';
|
|
||||||
front += '<li>';
|
|
||||||
front += itemToHtml(item);
|
|
||||||
} else if (item.level > curLevel) {
|
|
||||||
// assert(item.level - curLevel == 1)
|
|
||||||
front += '<ul>';
|
|
||||||
ending.push('</ul>');
|
|
||||||
front += '<li>';
|
|
||||||
front += itemToHtml(item);
|
|
||||||
ending.push('</li>');
|
|
||||||
curLevel = item.level;
|
|
||||||
} else {
|
|
||||||
while (item.level < curLevel) {
|
|
||||||
var ele = ending.pop();
|
|
||||||
front += ele;
|
|
||||||
if (ele == '</ul>') {
|
|
||||||
curLevel--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
front += '</li>';
|
|
||||||
front += '<li>';
|
|
||||||
front += itemToHtml(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (ending.length > 0) {
|
|
||||||
front += ending.pop();
|
|
||||||
}
|
|
||||||
front = front.replace("<li></li>", "");
|
|
||||||
front = '<ul>' + front + '</ul>';
|
|
||||||
return front;
|
|
||||||
};
|
|
||||||
|
|
||||||
var handleToc = function(needToc) {
|
|
||||||
var tocTree = tocToTree(toPerfectToc(toc));
|
|
||||||
content.setToc(tocTree);
|
|
||||||
|
|
||||||
// Add it to html
|
|
||||||
if (needToc) {
|
|
||||||
var eles = document.getElementsByClassName('vnote-toc');
|
|
||||||
for (var i = 0; i < eles.length; ++i) {
|
|
||||||
eles[i].innerHTML = tocTree;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var mdHasTocSection = function(markdown) {
|
var mdHasTocSection = function(markdown) {
|
||||||
var n = markdown.search(/(\n|^)\[toc\]/i);
|
var n = markdown.search(/(\n|^)\[toc\]/i);
|
||||||
return n != -1;
|
return n != -1;
|
||||||
|
@ -44,85 +44,6 @@ var markdownToHtml = function(markdown, needToc) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle wrong title levels, such as '#' followed by '###'
|
|
||||||
var toPerfectToc = function(toc) {
|
|
||||||
var i;
|
|
||||||
var curLevel = 1;
|
|
||||||
var perfToc = [];
|
|
||||||
for (i in toc) {
|
|
||||||
var item = toc[i];
|
|
||||||
while (item.level > curLevel + 1) {
|
|
||||||
curLevel += 1;
|
|
||||||
var tmp = { level: curLevel,
|
|
||||||
anchor: item.anchor,
|
|
||||||
title: '[EMPTY]'
|
|
||||||
};
|
|
||||||
perfToc.push(tmp);
|
|
||||||
}
|
|
||||||
perfToc.push(item);
|
|
||||||
curLevel = item.level;
|
|
||||||
}
|
|
||||||
return perfToc;
|
|
||||||
};
|
|
||||||
|
|
||||||
var itemToHtml = function(item) {
|
|
||||||
return '<a href="#' + item.anchor + '">' + item.title + '</a>';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Turn a perfect toc to a tree using <ul>
|
|
||||||
var tocToTree = function(toc) {
|
|
||||||
var i;
|
|
||||||
var front = '<li>';
|
|
||||||
var ending = ['</li>'];
|
|
||||||
var curLevel = 1;
|
|
||||||
for (i in toc) {
|
|
||||||
var item = toc[i];
|
|
||||||
if (item.level == curLevel) {
|
|
||||||
front += '</li>';
|
|
||||||
front += '<li>';
|
|
||||||
front += itemToHtml(item);
|
|
||||||
} else if (item.level > curLevel) {
|
|
||||||
// assert(item.level - curLevel == 1)
|
|
||||||
front += '<ul>';
|
|
||||||
ending.push('</ul>');
|
|
||||||
front += '<li>';
|
|
||||||
front += itemToHtml(item);
|
|
||||||
ending.push('</li>');
|
|
||||||
curLevel = item.level;
|
|
||||||
} else {
|
|
||||||
while (item.level < curLevel) {
|
|
||||||
var ele = ending.pop();
|
|
||||||
front += ele;
|
|
||||||
if (ele == '</ul>') {
|
|
||||||
curLevel--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
front += '</li>';
|
|
||||||
front += '<li>';
|
|
||||||
front += itemToHtml(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (ending.length > 0) {
|
|
||||||
front += ending.pop();
|
|
||||||
}
|
|
||||||
front = front.replace("<li></li>", "");
|
|
||||||
front = '<ul>' + front + '</ul>';
|
|
||||||
return front;
|
|
||||||
};
|
|
||||||
|
|
||||||
var handleToc = function(needToc) {
|
|
||||||
var tocTree = tocToTree(toPerfectToc(toc));
|
|
||||||
content.setToc(tocTree);
|
|
||||||
|
|
||||||
// Add it to html
|
|
||||||
if (needToc) {
|
|
||||||
var eles = document.getElementsByClassName('vnote-toc');
|
|
||||||
for (var i = 0; i < eles.length; ++i) {
|
|
||||||
eles[i].innerHTML = tocTree;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var mdHasTocSection = function(markdown) {
|
var mdHasTocSection = function(markdown) {
|
||||||
var n = markdown.search(/(\n|^)\[toc\]/i);
|
var n = markdown.search(/(\n|^)\[toc\]/i);
|
||||||
return n != -1;
|
return n != -1;
|
||||||
|
@ -32,4 +32,6 @@ namespace DirConfig
|
|||||||
static const QString c_imageFolder = "image_folder";
|
static const QString c_imageFolder = "image_folder";
|
||||||
static const QString c_name = "name";
|
static const QString c_name = "name";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const QString c_emptyHeaderName = "[EMPTY]";
|
||||||
#endif
|
#endif
|
||||||
|
@ -14,7 +14,7 @@ void VDocument::updateText()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VDocument::setToc(const QString &toc)
|
void VDocument::setToc(const QString &toc, int /* baseLevel */)
|
||||||
{
|
{
|
||||||
if (toc == m_toc) {
|
if (toc == m_toc) {
|
||||||
return;
|
return;
|
||||||
@ -30,6 +30,8 @@ QString VDocument::getToc()
|
|||||||
|
|
||||||
void VDocument::scrollToAnchor(const QString &anchor)
|
void VDocument::scrollToAnchor(const QString &anchor)
|
||||||
{
|
{
|
||||||
|
m_header = anchor;
|
||||||
|
|
||||||
emit requestScrollToAnchor(anchor);
|
emit requestScrollToAnchor(anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +40,7 @@ void VDocument::setHeader(const QString &anchor)
|
|||||||
if (anchor == m_header) {
|
if (anchor == m_header) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_header = anchor;
|
m_header = anchor;
|
||||||
emit headerChanged(m_header);
|
emit headerChanged(m_header);
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,16 @@ public:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// Will be called in the HTML side
|
// Will be called in the HTML side
|
||||||
void setToc(const QString &toc);
|
|
||||||
|
// @toc: the HTML of the TOC.
|
||||||
|
// @baseLevel: the base level of @toc, starting from 1. It is the top level
|
||||||
|
// in the @toc.
|
||||||
|
void setToc(const QString &toc, int baseLevel);
|
||||||
|
|
||||||
|
// When the Web view has been scrolled, it will signal current header anchor.
|
||||||
|
// Empty @anchor to indicate an invalid header.
|
||||||
void setHeader(const QString &anchor);
|
void setHeader(const QString &anchor);
|
||||||
|
|
||||||
void setLog(const QString &p_log);
|
void setLog(const QString &p_log);
|
||||||
void keyPressEvent(int p_key, bool p_ctrl, bool p_shift);
|
void keyPressEvent(int p_key, bool p_ctrl, bool p_shift);
|
||||||
void updateText();
|
void updateText();
|
||||||
|
@ -195,7 +195,7 @@ void VHtmlTab::discardAndRead()
|
|||||||
readFile();
|
readFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VHtmlTab::scrollToAnchor(const VAnchor &p_anchor)
|
void VHtmlTab::scrollToAnchor(const VAnchor & /* p_anchor */)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ void VHtmlTab::clearSearchedWordHighlight()
|
|||||||
m_editor->clearSearchedWordHighlight();
|
m_editor->clearSearchedWordHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VHtmlTab::zoom(bool p_zoomIn, qreal p_step)
|
void VHtmlTab::zoom(bool /* p_zoomIn */, qreal /* p_step */)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,56 +244,87 @@ void VMdEdit::clearUnusedImages()
|
|||||||
|
|
||||||
void VMdEdit::updateCurHeader()
|
void VMdEdit::updateCurHeader()
|
||||||
{
|
{
|
||||||
int curHeader = 0;
|
if (m_headers.isEmpty()) {
|
||||||
QTextCursor cursor(this->textCursor());
|
return;
|
||||||
int curLine = cursor.block().firstLineNumber();
|
}
|
||||||
|
|
||||||
|
int curLine = textCursor().block().firstLineNumber();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (i = m_headers.size() - 1; i >= 0; --i) {
|
for (i = m_headers.size() - 1; i >= 0; --i) {
|
||||||
|
if (!m_headers[i].isEmpty()) {
|
||||||
if (m_headers[i].lineNumber <= curLine) {
|
if (m_headers[i].lineNumber <= curLine) {
|
||||||
curHeader = m_headers[i].lineNumber;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit curHeaderChanged(curHeader, i == -1 ? 0 : i);
|
}
|
||||||
|
|
||||||
|
if (i == -1) {
|
||||||
|
emit curHeaderChanged(VAnchor(m_file, "", -1, -1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
V_ASSERT(m_headers[i].index == i);
|
||||||
|
|
||||||
|
emit curHeaderChanged(VAnchor(m_file, "", m_headers[i].lineNumber, m_headers[i].index));
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMdEdit::generateEditOutline()
|
void VMdEdit::generateEditOutline()
|
||||||
{
|
{
|
||||||
QTextDocument *doc = document();
|
QTextDocument *doc = document();
|
||||||
|
|
||||||
m_headers.clear();
|
m_headers.clear();
|
||||||
|
|
||||||
|
QVector<VHeader> headers;
|
||||||
|
|
||||||
// Assume that each block contains only one line
|
// Assume that each block contains only one line
|
||||||
// Only support # syntax for now
|
// Only support # syntax for now
|
||||||
QRegExp headerReg("(#{1,6})\\s*(\\S.*)"); // Need to trim the spaces
|
QRegExp headerReg("(#{1,6})\\s*(\\S.*)"); // Need to trim the spaces
|
||||||
int lastLevel = 0;
|
int baseLevel = -1;
|
||||||
for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next()) {
|
for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next()) {
|
||||||
Q_ASSERT(block.lineCount() == 1);
|
V_ASSERT(block.lineCount() == 1);
|
||||||
if ((block.userState() == HighlightBlockState::Normal) &&
|
if ((block.userState() == HighlightBlockState::Normal) &&
|
||||||
headerReg.exactMatch(block.text())) {
|
headerReg.exactMatch(block.text())) {
|
||||||
int level = headerReg.cap(1).length();
|
int level = headerReg.cap(1).length();
|
||||||
VHeader header(level, headerReg.cap(2).trimmed(),
|
VHeader header(level, headerReg.cap(2).trimmed(),
|
||||||
"", block.firstLineNumber());
|
"", block.firstLineNumber(), headers.size());
|
||||||
while (level > lastLevel + 1) {
|
headers.append(header);
|
||||||
// Insert empty level.
|
|
||||||
m_headers.append(VHeader(++lastLevel, "[EMPTY]",
|
if (baseLevel == -1) {
|
||||||
"", block.firstLineNumber()));
|
baseLevel = level;
|
||||||
|
} else if (baseLevel > level) {
|
||||||
|
baseLevel = level;
|
||||||
}
|
}
|
||||||
m_headers.append(header);
|
|
||||||
lastLevel = level;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int curLevel = baseLevel - 1;
|
||||||
|
for (auto & item : headers) {
|
||||||
|
while (item.level > curLevel + 1) {
|
||||||
|
curLevel += 1;
|
||||||
|
|
||||||
|
// Insert empty level which is an invalid header.
|
||||||
|
m_headers.append(VHeader(curLevel, c_emptyHeaderName, "", -1, m_headers.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
item.index = m_headers.size();
|
||||||
|
m_headers.append(item);
|
||||||
|
curLevel = item.level;
|
||||||
|
}
|
||||||
|
|
||||||
emit headersChanged(m_headers);
|
emit headersChanged(m_headers);
|
||||||
|
|
||||||
updateCurHeader();
|
updateCurHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMdEdit::scrollToHeader(int p_headerIndex)
|
void VMdEdit::scrollToHeader(const VAnchor &p_anchor)
|
||||||
{
|
{
|
||||||
Q_ASSERT(p_headerIndex >= 0);
|
if (p_anchor.lineNumber == -1
|
||||||
if (p_headerIndex < m_headers.size()) {
|
|| p_anchor.m_outlineIndex < 0
|
||||||
int line = m_headers[p_headerIndex].lineNumber;
|
|| p_anchor.m_outlineIndex >= m_headers.size()) {
|
||||||
qDebug() << "scroll editor to" << p_headerIndex << "line" << line;
|
return;
|
||||||
scrollToLine(line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollToLine(p_anchor.lineNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString VMdEdit::toPlainTextWithoutImg() const
|
QString VMdEdit::toPlainTextWithoutImg() const
|
||||||
@ -407,3 +438,8 @@ void VMdEdit::resizeEvent(QResizeEvent *p_event)
|
|||||||
|
|
||||||
VEdit::resizeEvent(p_event);
|
VEdit::resizeEvent(p_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QVector<VHeader> &VMdEdit::getHeaders() const
|
||||||
|
{
|
||||||
|
return m_headers;
|
||||||
|
}
|
||||||
|
@ -32,19 +32,27 @@ public:
|
|||||||
// @p_path is the absolute path of the inserted image.
|
// @p_path is the absolute path of the inserted image.
|
||||||
void imageInserted(const QString &p_path);
|
void imageInserted(const QString &p_path);
|
||||||
|
|
||||||
// Scroll to m_headers[p_headerIndex].
|
void scrollToHeader(const VAnchor &p_anchor);
|
||||||
void scrollToHeader(int p_headerIndex);
|
|
||||||
// Like toPlainText(), but remove special blocks containing images.
|
// Like toPlainText(), but remove special blocks containing images.
|
||||||
QString toPlainTextWithoutImg() const;
|
QString toPlainTextWithoutImg() const;
|
||||||
|
|
||||||
|
const QVector<VHeader> &getHeaders() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void headersChanged(const QVector<VHeader> &headers);
|
void headersChanged(const QVector<VHeader> &headers);
|
||||||
void curHeaderChanged(int p_lineNumber, int p_outlineIndex);
|
|
||||||
|
// Signal when current header change.
|
||||||
|
void curHeaderChanged(VAnchor p_anchor);
|
||||||
|
|
||||||
void statusChanged();
|
void statusChanged();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void generateEditOutline();
|
void generateEditOutline();
|
||||||
|
|
||||||
|
// When there is no header in current cursor, will signal an invalid header.
|
||||||
void updateCurHeader();
|
void updateCurHeader();
|
||||||
|
|
||||||
void handleEditStateChanged(KeyState p_state);
|
void handleEditStateChanged(KeyState p_state);
|
||||||
void handleSelectionChanged();
|
void handleSelectionChanged();
|
||||||
void handleClipboardChanged(QClipboard::Mode p_mode);
|
void handleClipboardChanged(QClipboard::Mode p_mode);
|
||||||
|
@ -50,8 +50,8 @@ void VMdTab::setupUI()
|
|||||||
this, &VMdTab::updateTocFromHeaders);
|
this, &VMdTab::updateTocFromHeaders);
|
||||||
connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::statusChanged,
|
connect(dynamic_cast<VMdEdit *>(m_editor), &VMdEdit::statusChanged,
|
||||||
this, &VMdTab::noticeStatusChanged);
|
this, &VMdTab::noticeStatusChanged);
|
||||||
connect(m_editor, SIGNAL(curHeaderChanged(int, int)),
|
connect(m_editor, SIGNAL(curHeaderChanged(VAnchor)),
|
||||||
this, SLOT(updateCurHeader(int, int)));
|
this, SLOT(updateCurHeader(VAnchor)));
|
||||||
connect(m_editor, &VEdit::textChanged,
|
connect(m_editor, &VEdit::textChanged,
|
||||||
this, &VMdTab::handleTextChanged);
|
this, &VMdTab::handleTextChanged);
|
||||||
connect(m_editor, &VEdit::saveAndRead,
|
connect(m_editor, &VEdit::saveAndRead,
|
||||||
@ -91,6 +91,7 @@ void VMdTab::showFileReadMode()
|
|||||||
m_isEditMode = false;
|
m_isEditMode = false;
|
||||||
|
|
||||||
int outlineIndex = m_curHeader.m_outlineIndex;
|
int outlineIndex = m_curHeader.m_outlineIndex;
|
||||||
|
|
||||||
if (m_mdConType == MarkdownConverterType::Hoedown) {
|
if (m_mdConType == MarkdownConverterType::Hoedown) {
|
||||||
viewWebByConverter();
|
viewWebByConverter();
|
||||||
} else {
|
} else {
|
||||||
@ -100,6 +101,7 @@ void VMdTab::showFileReadMode()
|
|||||||
|
|
||||||
m_stacks->setCurrentWidget(m_webViewer);
|
m_stacks->setCurrentWidget(m_webViewer);
|
||||||
clearSearchedWordHighlight();
|
clearSearchedWordHighlight();
|
||||||
|
|
||||||
scrollWebViewToHeader(outlineIndex);
|
scrollWebViewToHeader(outlineIndex);
|
||||||
|
|
||||||
noticeStatusChanged();
|
noticeStatusChanged();
|
||||||
@ -107,14 +109,20 @@ void VMdTab::showFileReadMode()
|
|||||||
|
|
||||||
void VMdTab::scrollWebViewToHeader(int p_outlineIndex)
|
void VMdTab::scrollWebViewToHeader(int p_outlineIndex)
|
||||||
{
|
{
|
||||||
V_ASSERT(p_outlineIndex >= 0);
|
QString anchor;
|
||||||
|
|
||||||
if (p_outlineIndex < m_toc.headers.size()) {
|
m_curHeader = VAnchor(m_file, anchor, -1, p_outlineIndex);
|
||||||
QString anchor = m_toc.headers[p_outlineIndex].anchor;
|
|
||||||
if (!anchor.isEmpty()) {
|
if (p_outlineIndex < m_toc.headers.size() && p_outlineIndex >= 0) {
|
||||||
m_document->scrollToAnchor(anchor.mid(1));
|
QString tmp = m_toc.headers[p_outlineIndex].anchor;
|
||||||
}
|
V_ASSERT(!tmp.isEmpty());
|
||||||
|
m_curHeader.anchor = tmp;
|
||||||
|
anchor = tmp.mid(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_document->scrollToAnchor(anchor);
|
||||||
|
|
||||||
|
emit curHeaderChanged(m_curHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMdTab::viewWebByConverter()
|
void VMdTab::viewWebByConverter()
|
||||||
@ -136,14 +144,28 @@ void VMdTab::showFileEditMode()
|
|||||||
|
|
||||||
m_isEditMode = true;
|
m_isEditMode = true;
|
||||||
|
|
||||||
|
VMdEdit *mdEdit = dynamic_cast<VMdEdit *>(m_editor);
|
||||||
|
V_ASSERT(mdEdit);
|
||||||
|
|
||||||
// beginEdit() may change m_curHeader.
|
// beginEdit() may change m_curHeader.
|
||||||
int outlineIndex = m_curHeader.m_outlineIndex;
|
int outlineIndex = m_curHeader.m_outlineIndex;
|
||||||
|
int lineNumber = -1;
|
||||||
|
auto headers = mdEdit->getHeaders();
|
||||||
|
if (outlineIndex < 0 || outlineIndex >= headers.size()) {
|
||||||
|
lineNumber = -1;
|
||||||
|
outlineIndex = -1;
|
||||||
|
} else {
|
||||||
|
lineNumber = headers[outlineIndex].lineNumber;
|
||||||
|
}
|
||||||
|
|
||||||
m_editor->beginEdit();
|
VAnchor anchor(m_file, "", lineNumber, outlineIndex);
|
||||||
m_stacks->setCurrentWidget(m_editor);
|
|
||||||
|
|
||||||
dynamic_cast<VMdEdit *>(m_editor)->scrollToHeader(outlineIndex);
|
mdEdit->beginEdit();
|
||||||
m_editor->setFocus();
|
m_stacks->setCurrentWidget(mdEdit);
|
||||||
|
|
||||||
|
mdEdit->scrollToHeader(anchor);
|
||||||
|
|
||||||
|
mdEdit->setFocus();
|
||||||
|
|
||||||
noticeStatusChanged();
|
noticeStatusChanged();
|
||||||
}
|
}
|
||||||
@ -368,7 +390,7 @@ static void parseTocLi(QXmlStreamReader &p_xml, QVector<VHeader> &p_headers, int
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
VHeader header(p_level, name, anchor, -1);
|
VHeader header(p_level, name, anchor, -1, p_headers.size());
|
||||||
p_headers.append(header);
|
p_headers.append(header);
|
||||||
} else {
|
} else {
|
||||||
// Error
|
// Error
|
||||||
@ -376,7 +398,7 @@ static void parseTocLi(QXmlStreamReader &p_xml, QVector<VHeader> &p_headers, int
|
|||||||
}
|
}
|
||||||
} else if (p_xml.name() == "ul") {
|
} else if (p_xml.name() == "ul") {
|
||||||
// Such as header 3 under header 1 directly
|
// Such as header 3 under header 1 directly
|
||||||
VHeader header(p_level, "[EMPTY]", "#", -1);
|
VHeader header(p_level, c_emptyHeaderName, "#", -1, p_headers.size());
|
||||||
p_headers.append(header);
|
p_headers.append(header);
|
||||||
parseTocUl(p_xml, p_headers, p_level + 1);
|
parseTocUl(p_xml, p_headers, p_level + 1);
|
||||||
} else {
|
} else {
|
||||||
@ -478,10 +500,9 @@ void VMdTab::scrollToAnchor(const VAnchor &p_anchor)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_curHeader = p_anchor;
|
m_curHeader = p_anchor;
|
||||||
|
|
||||||
if (m_isEditMode) {
|
if (m_isEditMode) {
|
||||||
if (p_anchor.lineNumber > -1) {
|
dynamic_cast<VMdEdit *>(m_editor)->scrollToHeader(p_anchor);
|
||||||
m_editor->scrollToLine(p_anchor.lineNumber);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (!p_anchor.anchor.isEmpty()) {
|
if (!p_anchor.anchor.isEmpty()) {
|
||||||
m_document->scrollToAnchor(p_anchor.anchor.mid(1));
|
m_document->scrollToAnchor(p_anchor.anchor.mid(1));
|
||||||
@ -500,26 +521,31 @@ void VMdTab::updateCurHeader(const QString &p_anchor)
|
|||||||
const QVector<VHeader> &headers = m_toc.headers;
|
const QVector<VHeader> &headers = m_toc.headers;
|
||||||
for (int i = 0; i < headers.size(); ++i) {
|
for (int i = 0; i < headers.size(); ++i) {
|
||||||
if (headers[i].anchor == m_curHeader.anchor) {
|
if (headers[i].anchor == m_curHeader.anchor) {
|
||||||
m_curHeader.m_outlineIndex = i;
|
V_ASSERT(headers[i].index == i);
|
||||||
|
m_curHeader.m_outlineIndex = headers[i].index;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emit curHeaderChanged(m_curHeader);
|
emit curHeaderChanged(m_curHeader);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMdTab::updateCurHeader(int p_lineNumber, int p_outlineIndex)
|
void VMdTab::updateCurHeader(VAnchor p_anchor)
|
||||||
{
|
{
|
||||||
if (!m_isEditMode || m_curHeader.lineNumber == p_lineNumber) {
|
if (m_isEditMode) {
|
||||||
|
if (!p_anchor.anchor.isEmpty() || p_anchor.lineNumber == m_curHeader.lineNumber) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
m_curHeader = VAnchor(m_file, "", p_lineNumber);
|
if (p_anchor.lineNumber != -1 || p_anchor.anchor == m_curHeader.anchor) {
|
||||||
m_curHeader.m_outlineIndex = p_outlineIndex;
|
return;
|
||||||
if (p_lineNumber > -1) {
|
|
||||||
emit curHeaderChanged(m_curHeader);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_curHeader = p_anchor;
|
||||||
|
|
||||||
|
emit curHeaderChanged(m_curHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VMdTab::insertImage()
|
void VMdTab::insertImage()
|
||||||
|
@ -76,7 +76,7 @@ private slots:
|
|||||||
void updateCurHeader(const QString &p_anchor);
|
void updateCurHeader(const QString &p_anchor);
|
||||||
|
|
||||||
// Editor requests to update current header.
|
// Editor requests to update current header.
|
||||||
void updateCurHeader(int p_lineNumber, int p_outlineIndex);
|
void updateCurHeader(VAnchor p_anchor);
|
||||||
|
|
||||||
// Handle key press event in Web view.
|
// Handle key press event in Web view.
|
||||||
void handleWebKeyPressed(int p_key, bool p_ctrl, bool p_shift);
|
void handleWebKeyPressed(int p_key, bool p_ctrl, bool p_shift);
|
||||||
|
116
src/voutline.cpp
116
src/voutline.cpp
@ -1,7 +1,6 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
@ -9,8 +8,10 @@
|
|||||||
#include "vtoc.h"
|
#include "vtoc.h"
|
||||||
#include "utils/vutils.h"
|
#include "utils/vutils.h"
|
||||||
#include "vnote.h"
|
#include "vnote.h"
|
||||||
|
#include "vconfigmanager.h"
|
||||||
|
|
||||||
extern VNote *g_vnote;
|
extern VNote *g_vnote;
|
||||||
|
extern VConfigManager vconfig;
|
||||||
|
|
||||||
VOutline::VOutline(QWidget *parent)
|
VOutline::VOutline(QWidget *parent)
|
||||||
: QTreeWidget(parent), VNavigationMode()
|
: QTreeWidget(parent), VNavigationMode()
|
||||||
@ -23,23 +24,38 @@ VOutline::VOutline(QWidget *parent)
|
|||||||
this, &VOutline::handleCurItemChanged);
|
this, &VOutline::handleCurItemChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VOutline::checkOutline(const VToc &p_toc) const
|
||||||
|
{
|
||||||
|
const QVector<VHeader> &headers = p_toc.headers;
|
||||||
|
|
||||||
|
for (int i = 0; i < headers.size(); ++i) {
|
||||||
|
V_ASSERT(headers[i].index == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void VOutline::updateOutline(const VToc &toc)
|
void VOutline::updateOutline(const VToc &toc)
|
||||||
{
|
{
|
||||||
// Clear current header
|
// Clear current header
|
||||||
curHeader = VAnchor();
|
curHeader = VAnchor();
|
||||||
|
|
||||||
|
checkOutline(toc);
|
||||||
|
|
||||||
outline = toc;
|
outline = toc;
|
||||||
updateTreeFromOutline(outline);
|
|
||||||
|
updateTreeFromOutline();
|
||||||
|
|
||||||
expandTree();
|
expandTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VOutline::updateTreeFromOutline(const VToc &toc)
|
void VOutline::updateTreeFromOutline()
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
if (!toc.valid) {
|
if (!outline.valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const QVector<VHeader> &headers = toc.headers;
|
|
||||||
|
const QVector<VHeader> &headers = outline.headers;
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
updateTreeByLevel(headers, idx, NULL, NULL, 1);
|
updateTreeByLevel(headers, idx, NULL, NULL, 1);
|
||||||
}
|
}
|
||||||
@ -56,13 +72,8 @@ void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index,
|
|||||||
} else {
|
} else {
|
||||||
item = new QTreeWidgetItem(this);
|
item = new QTreeWidgetItem(this);
|
||||||
}
|
}
|
||||||
QJsonObject itemJson;
|
|
||||||
itemJson["anchor"] = header.anchor;
|
fillItem(item, header);
|
||||||
itemJson["line_number"] = header.lineNumber;
|
|
||||||
itemJson["outline_index"] = index;
|
|
||||||
item->setData(0, Qt::UserRole, itemJson);
|
|
||||||
item->setText(0, header.name);
|
|
||||||
item->setToolTip(0, header.name);
|
|
||||||
|
|
||||||
last = item;
|
last = item;
|
||||||
++index;
|
++index;
|
||||||
@ -74,6 +85,17 @@ void VOutline::updateTreeByLevel(const QVector<VHeader> &headers, int &index,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VOutline::fillItem(QTreeWidgetItem *p_item, const VHeader &p_header)
|
||||||
|
{
|
||||||
|
p_item->setData(0, Qt::UserRole, p_header.index);
|
||||||
|
p_item->setText(0, p_header.name);
|
||||||
|
p_item->setToolTip(0, p_header.name);
|
||||||
|
|
||||||
|
if (p_header.isEmpty()) {
|
||||||
|
p_item->setForeground(0, QColor("grey"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void VOutline::expandTree()
|
void VOutline::expandTree()
|
||||||
{
|
{
|
||||||
if (topLevelItemCount() == 0) {
|
if (topLevelItemCount() == 0) {
|
||||||
@ -87,20 +109,22 @@ void VOutline::handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem
|
|||||||
if (!p_curItem) {
|
if (!p_curItem) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QJsonObject itemJson = p_curItem->data(0, Qt::UserRole).toJsonObject();
|
|
||||||
QString anchor = itemJson["anchor"].toString();
|
const VHeader *header = getHeaderFromItem(p_curItem);
|
||||||
int lineNumber = itemJson["line_number"].toInt();
|
if (!header) {
|
||||||
int outlineIndex = itemJson["outline_index"].toInt();
|
return;
|
||||||
VAnchor tmp;
|
}
|
||||||
tmp.m_file = outline.m_file;
|
|
||||||
tmp.anchor = anchor;
|
VAnchor tmp(outline.m_file, header->anchor, header->lineNumber, header->index);
|
||||||
tmp.lineNumber = lineNumber;
|
|
||||||
tmp.m_outlineIndex = outlineIndex;
|
|
||||||
if (tmp == curHeader) {
|
if (tmp == curHeader) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
curHeader = tmp;
|
curHeader = tmp;
|
||||||
|
|
||||||
|
if (!header->isEmpty()) {
|
||||||
emit outlineItemActivated(curHeader);
|
emit outlineItemActivated(curHeader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VOutline::updateCurHeader(const VAnchor &anchor)
|
void VOutline::updateCurHeader(const VAnchor &anchor)
|
||||||
@ -108,17 +132,24 @@ void VOutline::updateCurHeader(const VAnchor &anchor)
|
|||||||
if (anchor == curHeader) {
|
if (anchor == curHeader) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
curHeader = anchor;
|
curHeader = anchor;
|
||||||
if (outline.type == VHeaderType::Anchor) {
|
if (outline.type == VHeaderType::Anchor) {
|
||||||
selectAnchor(anchor.anchor);
|
selectAnchor(anchor.anchor);
|
||||||
} else {
|
} else {
|
||||||
// Select by lineNumber
|
// Select by lineNumber.
|
||||||
selectLineNumber(anchor.lineNumber);
|
selectLineNumber(anchor.lineNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VOutline::selectAnchor(const QString &anchor)
|
void VOutline::selectAnchor(const QString &anchor)
|
||||||
{
|
{
|
||||||
|
setCurrentItem(NULL);
|
||||||
|
|
||||||
|
if (anchor.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int nrTop = topLevelItemCount();
|
int nrTop = topLevelItemCount();
|
||||||
for (int i = 0; i < nrTop; ++i) {
|
for (int i = 0; i < nrTop; ++i) {
|
||||||
if (selectAnchorOne(topLevelItem(i), anchor)) {
|
if (selectAnchorOne(topLevelItem(i), anchor)) {
|
||||||
@ -132,9 +163,13 @@ bool VOutline::selectAnchorOne(QTreeWidgetItem *item, const QString &anchor)
|
|||||||
if (!item) {
|
if (!item) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
|
|
||||||
QString itemAnchor = itemJson["anchor"].toString();
|
const VHeader *header = getHeaderFromItem(item);
|
||||||
if (itemAnchor == anchor) {
|
if (!header) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->anchor == anchor) {
|
||||||
setCurrentItem(item);
|
setCurrentItem(item);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -150,6 +185,12 @@ bool VOutline::selectAnchorOne(QTreeWidgetItem *item, const QString &anchor)
|
|||||||
|
|
||||||
void VOutline::selectLineNumber(int lineNumber)
|
void VOutline::selectLineNumber(int lineNumber)
|
||||||
{
|
{
|
||||||
|
setCurrentItem(NULL);
|
||||||
|
|
||||||
|
if (lineNumber == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int nrTop = topLevelItemCount();
|
int nrTop = topLevelItemCount();
|
||||||
for (int i = 0; i < nrTop; ++i) {
|
for (int i = 0; i < nrTop; ++i) {
|
||||||
if (selectLineNumberOne(topLevelItem(i), lineNumber)) {
|
if (selectLineNumberOne(topLevelItem(i), lineNumber)) {
|
||||||
@ -163,9 +204,13 @@ bool VOutline::selectLineNumberOne(QTreeWidgetItem *item, int lineNumber)
|
|||||||
if (!item) {
|
if (!item) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
|
|
||||||
int itemLineNum = itemJson["line_number"].toInt();
|
const VHeader *header = getHeaderFromItem(item);
|
||||||
if (itemLineNum == lineNumber) {
|
if (!header) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->lineNumber == lineNumber) {
|
||||||
// Select this item
|
// Select this item
|
||||||
setCurrentItem(item);
|
setCurrentItem(item);
|
||||||
return true;
|
return true;
|
||||||
@ -329,3 +374,18 @@ QList<QTreeWidgetItem *> VOutline::getVisibleChildItems(const QTreeWidgetItem *p
|
|||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VHeader *VOutline::getHeaderFromItem(QTreeWidgetItem *p_item) const
|
||||||
|
{
|
||||||
|
const VHeader *header = NULL;
|
||||||
|
|
||||||
|
int index = p_item->data(0, Qt::UserRole).toInt();
|
||||||
|
if (index < 0 || index >= outline.headers.size()) {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
header = &(outline.headers[index]);
|
||||||
|
Q_ASSERT(header->index == index);
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
@ -36,9 +36,13 @@ private slots:
|
|||||||
void handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem *p_preItem);
|
void handleCurItemChanged(QTreeWidgetItem *p_curItem, QTreeWidgetItem *p_preItem);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateTreeFromOutline(const VToc &toc);
|
// Update tree according to outline.
|
||||||
|
void updateTreeFromOutline();
|
||||||
|
|
||||||
|
// @index: the index in @headers.
|
||||||
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);
|
void selectAnchor(const QString &anchor);
|
||||||
bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor);
|
bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor);
|
||||||
@ -47,6 +51,15 @@ private:
|
|||||||
QList<QTreeWidgetItem *> getVisibleItems() const;
|
QList<QTreeWidgetItem *> getVisibleItems() const;
|
||||||
QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const;
|
QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const;
|
||||||
|
|
||||||
|
// Fill the info of @p_item.
|
||||||
|
void fillItem(QTreeWidgetItem *p_item, const VHeader &p_header);
|
||||||
|
|
||||||
|
// Check if @p_toc is valid.
|
||||||
|
void checkOutline(const VToc &p_toc) const;
|
||||||
|
|
||||||
|
// Return NULL if no corresponding header in outline.
|
||||||
|
const VHeader *getHeaderFromItem(QTreeWidgetItem *p_item) const;
|
||||||
|
|
||||||
VToc outline;
|
VToc outline;
|
||||||
VAnchor curHeader;
|
VAnchor curHeader;
|
||||||
|
|
||||||
|
39
src/vtoc.h
39
src/vtoc.h
@ -14,30 +14,53 @@ enum VHeaderType
|
|||||||
|
|
||||||
struct VHeader
|
struct VHeader
|
||||||
{
|
{
|
||||||
VHeader() : level(1), lineNumber(-1) {}
|
VHeader() : level(1), lineNumber(-1), index(-1) {}
|
||||||
VHeader(int level, const QString &name, const QString &anchor, int lineNumber)
|
VHeader(int level, const QString &name, const QString &anchor, int lineNumber, int index)
|
||||||
: level(level), name(name), anchor(anchor), lineNumber(lineNumber) {}
|
: level(level), name(name), anchor(anchor), lineNumber(lineNumber), index(index) {}
|
||||||
int level;
|
int level;
|
||||||
QString name;
|
QString name;
|
||||||
QString anchor;
|
QString anchor;
|
||||||
int lineNumber;
|
int lineNumber;
|
||||||
|
|
||||||
|
// Index in the outline, based on 0.
|
||||||
|
int index;
|
||||||
|
|
||||||
|
// Whether it is an empty (fake) header.
|
||||||
|
bool isEmpty() const
|
||||||
|
{
|
||||||
|
if (anchor.isEmpty()) {
|
||||||
|
return lineNumber == -1;
|
||||||
|
} else {
|
||||||
|
return anchor == "#";
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VAnchor
|
struct VAnchor
|
||||||
{
|
{
|
||||||
VAnchor() : lineNumber(-1), m_outlineIndex(0) {}
|
VAnchor() : m_file(NULL), lineNumber(-1), m_outlineIndex(-1) {}
|
||||||
VAnchor(const VFile *file, const QString &anchor, int lineNumber)
|
|
||||||
: m_file(file), anchor(anchor), lineNumber(lineNumber), m_outlineIndex(0) {}
|
VAnchor(const VFile *file, const QString &anchor, int lineNumber, int outlineIndex = -1)
|
||||||
|
: m_file(file), anchor(anchor), lineNumber(lineNumber), m_outlineIndex(outlineIndex) {}
|
||||||
|
|
||||||
|
// The file this anchor points to.
|
||||||
const VFile *m_file;
|
const VFile *m_file;
|
||||||
|
|
||||||
|
// The string anchor. For Web view.
|
||||||
QString anchor;
|
QString anchor;
|
||||||
|
|
||||||
|
// The line number anchor. For edit view.
|
||||||
int lineNumber;
|
int lineNumber;
|
||||||
// Index of this anchor in VToc outline.
|
|
||||||
|
// Index of the header for this anchor in VToc outline.
|
||||||
|
// Used to translate current header between read and edit mode.
|
||||||
int m_outlineIndex;
|
int m_outlineIndex;
|
||||||
|
|
||||||
bool operator==(const VAnchor &p_anchor) const {
|
bool operator==(const VAnchor &p_anchor) const {
|
||||||
return (p_anchor.m_file == m_file
|
return (p_anchor.m_file == m_file
|
||||||
&& p_anchor.anchor == anchor
|
&& p_anchor.anchor == anchor
|
||||||
&& p_anchor.lineNumber == lineNumber);
|
&& p_anchor.lineNumber == lineNumber
|
||||||
|
&& p_anchor.m_outlineIndex == m_outlineIndex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user