|
"use strict"; |
|
/** |
|
* :nav コマンドでテキトウに補完を使いましょう |
|
* :nnoremap v :nav<Space> とかすると快適かも |
|
*/ |
|
|
|
var selector = "h1, h2, h3, h4, main, [role=main], [role=navigation], [role=search]"; |
|
var cache = new WeakMap; |
|
|
|
function getJumpList (force) { |
|
var browser = gBrowser.selectedBrowser, |
|
doc = browser.contentDocument, |
|
win = browser.contentWindow; |
|
|
|
if (!force && cache.has(win)) |
|
return cache.get(win); |
|
|
|
var items = []; |
|
var wX = win.scrollX, |
|
wY = win.scrollY, |
|
index = 1; |
|
|
|
for (let elm of doc.querySelectorAll(selector)) { |
|
let id = elm.getAttribute("id") || "", |
|
role = elm.getAttribute("role") || "", |
|
rect = elm.getBoundingClientRect(); |
|
|
|
let localName = elm.localName, |
|
key = index + ":" + localName + ":", |
|
desc = elm.textContent.substring(0, 50).replace(/\s+/g, " "); |
|
|
|
if (id) |
|
key += "#" + id + ":"; |
|
if (role) |
|
key += "[" + role + "]:" |
|
|
|
items.push({ |
|
id: id, |
|
key: key + desc, |
|
desc: localName + ": " + desc, |
|
x: wX + rect.left, |
|
y: wY + rect.top, |
|
node: elm, |
|
}); |
|
index++; |
|
} |
|
cache.set(win, items); |
|
return items; |
|
} |
|
|
|
function jumpTo (item) { |
|
var win = gBrowser.contentWindow; |
|
if (item.id) |
|
win.location.hash = "#" + item.id; |
|
else |
|
win.scrollTo(Math.min(item.x, win.scrollMaxX), Math.min(item.y, win.scrollMaxY)); |
|
|
|
item.node.focus(); |
|
flashNode(item.node); |
|
cache.delete(win); |
|
} |
|
|
|
function flashNode (node) { |
|
var count = 7, |
|
originalOUtline = node.style.outline, |
|
win = node.ownerDocument.defaultView; |
|
|
|
var id = win.setInterval(function(){ |
|
if (count <= 0) { |
|
win.clearInterval(id); |
|
node.style.outline = originalOUtline; |
|
return; |
|
} |
|
node.style.outline = count % 2 ? |
|
"2px solid red": |
|
"0 solid transparent"; |
|
--count; |
|
}, 200); |
|
} |
|
|
|
commands.addUserCommand(["nav"], "page jump", |
|
function (args) { |
|
var list = getJumpList(); |
|
var arg = args.string; |
|
var m = arg.match(/^(\d+):?/); |
|
if (m) { |
|
let index = parseInt(m[1], 10) - 1; |
|
if (index in list) { |
|
jumpTo(list[index]); |
|
return; |
|
} |
|
} |
|
var item = list.filter(function (item) { |
|
return item.key.contains(arg); |
|
})[0]; |
|
if (item) |
|
jumpTo(item); |
|
}, { |
|
literal: 0, |
|
completer: function (context) { |
|
context.title = ["Page Jump List"]; |
|
context.compare = CompletionContext.Sort.unsorted; |
|
context.anchored = false; |
|
context.completions = [[item.key, item.desc] for (item of getJumpList(true))]; |
|
}, |
|
}, true); |