mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 22:09:52 +08:00
web-view: support title navigation shortcuts
Like Vim mode, support following title navigtion shortcuts: [[, ]], [], ][, [{, ]}.
This commit is contained in:
parent
5ad99ee9b8
commit
fea491d938
@ -33,6 +33,13 @@ Zoom in/out the page.
|
|||||||
Zoom in/out the page through the mouse scroll.
|
Zoom in/out the page through the mouse scroll.
|
||||||
- `Ctrl+0`
|
- `Ctrl+0`
|
||||||
Recover the page zoom factor to 100%.
|
Recover the page zoom factor to 100%.
|
||||||
|
- Jump between titles
|
||||||
|
- `[[`: jump to previous title;
|
||||||
|
- `]]`: jump to next title;
|
||||||
|
- `[]`: jump to previous title at the same level;
|
||||||
|
- `][`: jump to next title at the same level;
|
||||||
|
- `[{`: jump to previous title at a higher level;
|
||||||
|
- `]}`: jump to next title at a higher level;
|
||||||
|
|
||||||
### Edit Mode
|
### Edit Mode
|
||||||
- `Ctrl+S`
|
- `Ctrl+S`
|
||||||
|
@ -33,6 +33,13 @@
|
|||||||
鼠标滚轮实现放大/缩小页面。
|
鼠标滚轮实现放大/缩小页面。
|
||||||
- `Ctrl+0`
|
- `Ctrl+0`
|
||||||
恢复页面大小为100%。
|
恢复页面大小为100%。
|
||||||
|
- 标题跳转
|
||||||
|
- `[[`:跳转到上一个标题;
|
||||||
|
- `]]`: 跳转到下一个标题;
|
||||||
|
- `[]`:跳转到上一个同层级的标题;
|
||||||
|
- `][`:跳转到下一个同层级的标题;
|
||||||
|
- `[{`:跳转到上一个高一层级的标题;
|
||||||
|
- `]}`:跳转到下一个高一层级的标题;
|
||||||
|
|
||||||
### 编辑模式
|
### 编辑模式
|
||||||
- `Ctrl+S`
|
- `Ctrl+S`
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
var content;
|
var content;
|
||||||
var keyState = 0;
|
|
||||||
|
// Current header index in all headers.
|
||||||
|
var currentHeaderIdx = -1;
|
||||||
|
|
||||||
|
// Pending keys for keydown.
|
||||||
|
var pendingKeys = [];
|
||||||
|
|
||||||
var VMermaidDivClass = 'mermaid-diagram';
|
var VMermaidDivClass = 'mermaid-diagram';
|
||||||
var VFlowchartDivClass = 'flowchart-diagram';
|
var VFlowchartDivClass = 'flowchart-diagram';
|
||||||
@ -50,6 +55,7 @@ var g_muteScroll = false;
|
|||||||
|
|
||||||
var scrollToAnchor = function(anchor) {
|
var scrollToAnchor = function(anchor) {
|
||||||
g_muteScroll = true;
|
g_muteScroll = true;
|
||||||
|
currentHeaderIdx = -1;
|
||||||
if (!anchor) {
|
if (!anchor) {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
g_muteScroll = false;
|
g_muteScroll = false;
|
||||||
@ -59,6 +65,13 @@ var scrollToAnchor = function(anchor) {
|
|||||||
var anc = document.getElementById(anchor);
|
var anc = document.getElementById(anchor);
|
||||||
if (anc != null) {
|
if (anc != null) {
|
||||||
anc.scrollIntoView();
|
anc.scrollIntoView();
|
||||||
|
var headers = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
|
||||||
|
for (var i = 0; i < headers.length; ++i) {
|
||||||
|
if (headers[i] == anc) {
|
||||||
|
currentHeaderIdx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable scroll temporarily.
|
// Disable scroll temporarily.
|
||||||
@ -78,6 +91,7 @@ window.onscroll = function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentHeaderIdx = -1;
|
||||||
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");
|
||||||
|
|
||||||
@ -86,25 +100,30 @@ window.onscroll = function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
curIdx = i;
|
currentHeaderIdx = i;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var curHeader = null;
|
var curHeader = null;
|
||||||
if (curIdx != -1) {
|
if (currentHeaderIdx != -1) {
|
||||||
curHeader = eles[curIdx].getAttribute("id");
|
curHeader = eles[currentHeaderIdx].getAttribute("id");
|
||||||
}
|
}
|
||||||
|
|
||||||
content.setHeader(curHeader ? curHeader : "");
|
content.setHeader(curHeader ? curHeader : "");
|
||||||
};
|
};
|
||||||
|
|
||||||
document.onkeydown = function(e) {
|
document.onkeydown = function(e) {
|
||||||
|
// Need to clear pending kyes.
|
||||||
|
var clear = true;
|
||||||
|
|
||||||
|
// This even has been handled completely. No need to call the default handler.
|
||||||
|
var accept = true;
|
||||||
|
|
||||||
e = e || window.event;
|
e = e || window.event;
|
||||||
var key;
|
var key;
|
||||||
var shift;
|
var shift;
|
||||||
@ -114,72 +133,182 @@ document.onkeydown = function(e) {
|
|||||||
} else {
|
} else {
|
||||||
key = e.keyCode;
|
key = e.keyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
shift = !!e.shiftKey;
|
shift = !!e.shiftKey;
|
||||||
ctrl = !!e.ctrlKey;
|
ctrl = !!e.ctrlKey;
|
||||||
switch (key) {
|
switch (key) {
|
||||||
|
// Skip Ctrl, Shift, Alt, Supper.
|
||||||
|
case 16:
|
||||||
|
case 17:
|
||||||
|
case 18:
|
||||||
|
case 91:
|
||||||
|
clear = false;
|
||||||
|
break;
|
||||||
|
|
||||||
case 74: // J
|
case 74: // J
|
||||||
window.scrollBy(0, 100);
|
window.scrollBy(0, 100);
|
||||||
keyState = 0;
|
break;
|
||||||
break;
|
|
||||||
|
|
||||||
case 75: // K
|
case 75: // K
|
||||||
window.scrollBy(0, -100);
|
window.scrollBy(0, -100);
|
||||||
keyState = 0;
|
break;
|
||||||
break;
|
|
||||||
|
|
||||||
case 72: // H
|
case 72: // H
|
||||||
window.scrollBy(-100, 0);
|
window.scrollBy(-100, 0);
|
||||||
keyState = 0;
|
break;
|
||||||
break;
|
|
||||||
|
|
||||||
case 76: // L
|
case 76: // L
|
||||||
window.scrollBy(100, 0);
|
window.scrollBy(100, 0);
|
||||||
keyState = 0;
|
break;
|
||||||
break;
|
|
||||||
|
|
||||||
case 71: // G
|
case 71: // G
|
||||||
if (shift) {
|
if (shift) {
|
||||||
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft || window.pageXOffset;
|
if (pendingKeys.length == 0) {
|
||||||
var scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
|
|
||||||
window.scrollTo(scrollLeft, scrollHeight);
|
|
||||||
keyState = 0;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
if (keyState == 0) {
|
|
||||||
keyState = 1;
|
|
||||||
} else if (keyState == 1) {
|
|
||||||
keyState = 0;
|
|
||||||
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft || window.pageXOffset;
|
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft || window.pageXOffset;
|
||||||
window.scrollTo(scrollLeft, 0);
|
var scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
|
||||||
|
window.scrollTo(scrollLeft, scrollHeight);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pendingKeys.length == 0) {
|
||||||
|
// First g, pend it.
|
||||||
|
pendingKeys.push({
|
||||||
|
key: key,
|
||||||
|
ctrl: ctrl,
|
||||||
|
shift: shift
|
||||||
|
});
|
||||||
|
|
||||||
|
clear = false;
|
||||||
|
break;
|
||||||
|
} else if (pendingKeys.length == 1) {
|
||||||
|
var pendKey = pendingKeys[0];
|
||||||
|
if (pendKey.key == key && !pendKey.shift && !pendKey.ctrl) {
|
||||||
|
var scrollLeft = document.documentElement.scrollLeft
|
||||||
|
|| document.body.scrollLeft
|
||||||
|
|| window.pageXOffset;
|
||||||
|
window.scrollTo(scrollLeft, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
|
accept = false;
|
||||||
|
break;
|
||||||
|
|
||||||
case 85: // U
|
case 85: // U
|
||||||
keyState = 0;
|
|
||||||
if (ctrl) {
|
if (ctrl) {
|
||||||
var clientHeight = document.documentElement.clientHeight;
|
var clientHeight = document.documentElement.clientHeight;
|
||||||
window.scrollBy(0, -clientHeight / 2);
|
window.scrollBy(0, -clientHeight / 2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
|
accept = false;
|
||||||
|
break;
|
||||||
|
|
||||||
case 68: // D
|
case 68: // D
|
||||||
keyState = 0;
|
|
||||||
if (ctrl) {
|
if (ctrl) {
|
||||||
var clientHeight = document.documentElement.clientHeight;
|
var clientHeight = document.documentElement.clientHeight;
|
||||||
window.scrollBy(0, clientHeight / 2);
|
window.scrollBy(0, clientHeight / 2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
|
accept = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 219: // [ or {
|
||||||
|
if (shift) {
|
||||||
|
// {
|
||||||
|
if (pendingKeys.length == 1) {
|
||||||
|
var pendKey = pendingKeys[0];
|
||||||
|
if (pendKey.key == key && !pendKey.shift && !pendKey.ctrl) {
|
||||||
|
// [{, jump to previous title at a higher level.
|
||||||
|
jumpTitle(false, -1, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// [
|
||||||
|
if (pendingKeys.length == 0) {
|
||||||
|
// First [, pend it.
|
||||||
|
pendingKeys.push({
|
||||||
|
key: key,
|
||||||
|
ctrl: ctrl,
|
||||||
|
shift: shift
|
||||||
|
});
|
||||||
|
|
||||||
|
clear = false;
|
||||||
|
break;
|
||||||
|
} else if (pendingKeys.length == 1) {
|
||||||
|
var pendKey = pendingKeys[0];
|
||||||
|
if (pendKey.key == key && !pendKey.shift && !pendKey.ctrl) {
|
||||||
|
// [[, jump to previous title.
|
||||||
|
jumpTitle(false, 1, 1);
|
||||||
|
break;
|
||||||
|
} else if (pendKey.key == 221 && !pendKey.shift && !pendKey.ctrl) {
|
||||||
|
// ][, jump to next title at the same level.
|
||||||
|
jumpTitle(true, 0, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accept = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 221: // ] or }
|
||||||
|
if (shift) {
|
||||||
|
// }
|
||||||
|
if (pendingKeys.length == 1) {
|
||||||
|
var pendKey = pendingKeys[0];
|
||||||
|
if (pendKey.key == key && !pendKey.shift && !pendKey.ctrl) {
|
||||||
|
// ]}, jump to next title at a higher level.
|
||||||
|
jumpTitle(true, -1, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ]
|
||||||
|
if (pendingKeys.length == 0) {
|
||||||
|
// First ], pend it.
|
||||||
|
pendingKeys.push({
|
||||||
|
key: key,
|
||||||
|
ctrl: ctrl,
|
||||||
|
shift: shift
|
||||||
|
});
|
||||||
|
|
||||||
|
clear = false;
|
||||||
|
break;
|
||||||
|
} else if (pendingKeys.length == 1) {
|
||||||
|
var pendKey = pendingKeys[0];
|
||||||
|
if (pendKey.key == key && !pendKey.shift && !pendKey.ctrl) {
|
||||||
|
// ]], jump to next title.
|
||||||
|
jumpTitle(true, 1, 1);
|
||||||
|
break;
|
||||||
|
} else if (pendKey.key == 219 && !pendKey.shift && !pendKey.ctrl) {
|
||||||
|
// [], jump to previous title at the same level.
|
||||||
|
jumpTitle(false, 0, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accept = false;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
content.keyPressEvent(key, ctrl, shift);
|
accept = false;
|
||||||
keyState = 0;
|
break;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
if (clear) {
|
||||||
|
pendingKeys = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accept) {
|
||||||
|
e.preventDefault();
|
||||||
|
} else {
|
||||||
|
content.keyPressEvent(key, ctrl, shift);
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var mermaidParserErr = false;
|
var mermaidParserErr = false;
|
||||||
@ -581,3 +710,81 @@ window.onmousemove = function(e) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @forward: jump forward or backward.
|
||||||
|
// @relativeLevel: 0 for the same level as current header;
|
||||||
|
// negative value for upper level;
|
||||||
|
// positive value is ignored.
|
||||||
|
var jumpTitle = function(forward, relativeLevel, repeat) {
|
||||||
|
var headers = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
|
||||||
|
if (headers.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentHeaderIdx == -1) {
|
||||||
|
// At the beginning, before any headers.
|
||||||
|
if (relativeLevel < 0 || !forward) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetIdx = -1;
|
||||||
|
// -1: skip level check.
|
||||||
|
var targetLevel = 0;
|
||||||
|
|
||||||
|
var delta = 1;
|
||||||
|
if (!forward) {
|
||||||
|
delta = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset;
|
||||||
|
|
||||||
|
var firstHeader = true;
|
||||||
|
for (targetIdx = (currentHeaderIdx == -1 ? 0 : currentHeaderIdx);
|
||||||
|
targetIdx >= 0 && targetIdx < headers.length;
|
||||||
|
targetIdx += delta) {
|
||||||
|
var header = headers[targetIdx];
|
||||||
|
var level = parseInt(header.tagName.substr(1));
|
||||||
|
if (targetLevel == 0) {
|
||||||
|
targetLevel = level;
|
||||||
|
if (relativeLevel < 0) {
|
||||||
|
targetLevel += relativeLevel;
|
||||||
|
if (targetLevel < 1) {
|
||||||
|
// Invalid level.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (relativeLevel > 0) {
|
||||||
|
targetLevel = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetLevel == -1 || level == targetLevel) {
|
||||||
|
if (targetIdx == currentHeaderIdx) {
|
||||||
|
// If current header is visible, skip it.
|
||||||
|
content.setLog("scroll " + scrollTop + " " + headers[targetIdx].offsetTop);
|
||||||
|
if (forward || scrollTop <= headers[targetIdx].offsetTop) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (--repeat == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (level < targetLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstHeader = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetIdx < 0 || targetIdx >= headers.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable scroll temporarily.
|
||||||
|
g_muteScroll = true;
|
||||||
|
headers[targetIdx].scrollIntoView();
|
||||||
|
currentHeaderIdx = targetIdx;
|
||||||
|
content.setHeader(headers[targetIdx].getAttribute("id"));
|
||||||
|
setTimeout("g_muteScroll = false", 100);
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user