From dd1365f269480cac60bb65108aa4cfd974aac056 Mon Sep 17 00:00:00 2001 From: glacambre Date: Mon, 27 Nov 2017 19:48:49 +0100 Subject: [PATCH 01/45] excmds.ts: Add yankshort and yankcanon. --- src/excmds.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/excmds.ts b/src/excmds.ts index e86d1e56..8b13f099 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -360,6 +360,17 @@ export function urlparent (){ } } +/** Returns the url of links that have a matching rel. + */ +//#content +export function geturlsforlinks(rel: string){ + let elems = document.querySelectorAll("link[rel='" + rel + "']") as NodeListOf + console.log(rel, elems) + if (elems) + return Array.prototype.map.call(elems, x => x.href) + return [] +} + //#background export function zoom(level=0){ level = level > 3 ? level / 100 : level @@ -739,10 +750,23 @@ export async function current_url(...strarr: string[]){ */ //#background -export async function clipboard(excmd: "open"|"yank"|"tabopen" = "open", ...toYank: string[]) { +export async function clipboard(excmd: "open"|"yank"|"yankshort"|"yankcanon"|"tabopen" = "open", ...toYank: string[]) { let content = toYank.join(" ") let url = "" + let urls = [] switch (excmd) { + case 'yankshort': + urls = await messageActiveTab("excmd_content", "geturlsforlinks", ["shortlink"]); + if (urls.length > 0) { + messageActiveTab("commandline_frame", "setClipboard", [urls[0]]) + break + } + case 'yankcanon': + urls = await messageActiveTab("excmd_content", "geturlsforlinks", ["canonical"]); + if (urls.length > 0) { + messageActiveTab("commandline_frame", "setClipboard", [urls[0]]) + break + } case 'yank': await messageActiveTab("commandline_content", "focus") content = (content == "") ? (await activeTab()).url : content From eef773f44ac9ee4d6ecf6f6158c50048b1af73a2 Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Mon, 27 Nov 2017 18:45:53 +0000 Subject: [PATCH 02/45] Add binding to yankcanon, yankshort --- src/parsers/normalmode.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/parsers/normalmode.ts b/src/parsers/normalmode.ts index 856f9760..411445c2 100755 --- a/src/parsers/normalmode.ts +++ b/src/parsers/normalmode.ts @@ -22,6 +22,8 @@ export const DEFAULTNMAPS = { "]c": "urlincrement 1", "T": "current_url tabopen", "yy": "clipboard yank", + "ys": "clipboard yankshort", + "yc": "clipboard yankcanon", "p": "clipboard open", "P": "clipboard tabopen", "j": "scrollline 10", From 6f244162abd8554e79beec022f934ab2f664add5 Mon Sep 17 00:00:00 2001 From: Robbie McMichael Date: Mon, 27 Nov 2017 17:01:45 +1100 Subject: [PATCH 03/45] Fix builds when using BSD sed --- scripts/commandline_injector.sh | 6 ++++-- scripts/newtab.md.sh | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/commandline_injector.sh b/scripts/commandline_injector.sh index 1af6e7f4..ce730117 100755 --- a/scripts/commandline_injector.sh +++ b/scripts/commandline_injector.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash -shopt -s globstar -sed -i '/<\/body>/s@^@@' $1 + +sed -i.bak '/<\/body>/s@^@@' "$1" +rm "$1.bak" + #static/docs/modules/_excmds_.html diff --git a/scripts/newtab.md.sh b/scripts/newtab.md.sh index c82cd90b..6464909e 100755 --- a/scripts/newtab.md.sh +++ b/scripts/newtab.md.sh @@ -3,4 +3,9 @@ # Combine newtab markdown and template cd src/static -sed "/REPLACETHIS/s/REPLACETHIS/marked newtab.md/e" newtab.template.html > ../../generated/static/newtab.html + +newtab="../../generated/static/newtab.html" + +sed "/REPLACETHIS/,$ d" newtab.template.html > "$newtab" +marked newtab.md >> "$newtab" +sed "1,/REPLACETHIS/ d" newtab.template.html >> "$newtab" From f9ee2a3e9a3397fd9ec2fc9001f91c9d443aa44d Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Mon, 27 Nov 2017 19:15:04 +0000 Subject: [PATCH 04/45] Add documentation for yankshort,canon --- src/excmds.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/excmds.ts b/src/excmds.ts index 8b13f099..da407a1e 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -746,6 +746,10 @@ export async function current_url(...strarr: string[]){ If `excmd == "yank"`, copy the current URL, or if given, the value of toYank, into the system clipboard. + If `excmd == "yankcanon"`, copy the canonical URL of the current page if it exists, otherwise copy the current URL. + + If `excmd == "yankshort"`, copy the shortlink version of the current URL, and fall back to the canonical then actual URL. Known to work on https://yankshort.neocities.org/. + Unfortunately, javascript can only give us the `clipboard` clipboard, not e.g. the X selection clipboard. */ From 87aa3ee225c8b5735f9e39f44ae45219aed698e0 Mon Sep 17 00:00:00 2001 From: Robbie McMichael Date: Mon, 27 Nov 2017 17:27:17 +1100 Subject: [PATCH 05/45] Add tabfirst and tablast commands --- src/excmds.ts | 44 ++++++++++++++++++++++++++++----------- src/parsers/normalmode.ts | 2 ++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index da407a1e..8e90b269 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -493,29 +493,49 @@ document.addEventListener("focusin",e=>{if (DOM.isTextEditable(e.target as HTMLE // {{{ TABS -/** Switch to the next tab by index (position on tab bar), wrapping round. - - optional increment is number of tabs forwards to move. - */ -//#background -export async function tabnext(increment = 1) { +/** Switch to the tab by index (position on tab bar), wrapping round. */ +/** @hidden */ +//#background_helper +async function tabindex(index: number) { // Get an array of tabs in the current window let current_window = await browser.windows.getCurrent() let tabs = await browser.tabs.query({windowId: current_window.id}) - // Derive the index we want - let desiredIndex = ((await activeTab()).index + increment).mod(tabs.length) - // Find and switch to the tab with that index let desiredTab = tabs.find((tab: any) => { - return tab.index === desiredIndex + return tab.index === index.mod(tabs.length) }) tabSetActive(desiredTab.id) } +/** Switch to the next tab, wrapping round. + + If increment is specified, move that many tabs forwards. + */ //#background -export function tabprev(increment = 1) { - tabnext(increment * -1) +export async function tabnext(increment = 1) { + tabindex((await activeTab()).index + increment) +} + +/** Switch to the previous tab, wrapping round. + + If increment is specified, move that many tabs backwards. + */ +//#background +export async function tabprev(increment = 1) { + tabindex((await activeTab()).index - increment) +} + +/** Switch to the first tab. */ +//#background +export async function tabfirst() { + tabindex(0) +} + +/** Switch to the last tab. */ +//#background +export async function tablast() { + tabindex(-1) } /** Like [[open]], but in a new tab */ diff --git a/src/parsers/normalmode.ts b/src/parsers/normalmode.ts index 411445c2..ef9189a0 100755 --- a/src/parsers/normalmode.ts +++ b/src/parsers/normalmode.ts @@ -41,6 +41,8 @@ export const DEFAULTNMAPS = { "gi": "focusinput -l", "gt": "tabnext", "gT": "tabprev", + "g^": "tabfirst", + "g$": "tablast", "gr": "reader", "gu": "urlparent", "gU": "urlroot", From 53ee803bc99172135bc5022f15bc41f8fc93dfc0 Mon Sep 17 00:00:00 2001 From: Robbie McMichael Date: Mon, 27 Nov 2017 22:42:50 +1100 Subject: [PATCH 06/45] Reduce code duplication in buffer function --- src/excmds.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 8e90b269..e2469f57 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -493,7 +493,10 @@ document.addEventListener("focusin",e=>{if (DOM.isTextEditable(e.target as HTMLE // {{{ TABS -/** Switch to the tab by index (position on tab bar), wrapping round. */ +/** Switch to the tab by index (position on tab bar), wrapping round. + + Note: all internal indices should start at 0. + */ /** @hidden */ //#background_helper async function tabindex(index: number) { @@ -825,24 +828,27 @@ export async function buffers() { tabs() } -/** Change active tab */ +/** Change active tab. + + The buffer index starts at 1. + */ //#background export async function buffer(n?: number | string) { - if (!n || Number(n) == 0) return // Vimperator index starts at 1 + if (!n) + return + if (n === "#") { - n = + // Switch to the most recently accessed buffer + tabindex( (await browser.tabs.query({currentWindow: true})).sort((a, b) => { return a.lastAccessed < b.lastAccessed ? 1 : -1 - })[1].index + 1 - } - if (Number.isInteger(Number(n))) { - tabSetActive( - (await browser.tabs.query({ - currentWindow: true, - index: Number(n) - 1, - }))[0].id + })[1].index ) } + else if (Number.isInteger(Number(n))) { + // Internal indices start at 0. + tabindex(Number(n) - 1) + } } /*/1** Set tab with index of n belonging to window with id of m to active *1/ */ From 9b68e94d942631c3a642093e8f4e3e58dc2c4001 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Mon, 27 Nov 2017 19:10:20 +0000 Subject: [PATCH 07/45] excmd: arg to buffer is not optional --- src/excmds.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index e2469f57..ef10f891 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -833,10 +833,7 @@ export async function buffers() { The buffer index starts at 1. */ //#background -export async function buffer(n?: number | string) { - if (!n) - return - +export async function buffer(n: number | '#') { if (n === "#") { // Switch to the most recently accessed buffer tabindex( @@ -844,8 +841,7 @@ export async function buffer(n?: number | string) { return a.lastAccessed < b.lastAccessed ? 1 : -1 })[1].index ) - } - else if (Number.isInteger(Number(n))) { + } else if (Number.isInteger(Number(n))) { // Internal indices start at 0. tabindex(Number(n) - 1) } From edd53f197b6a6fccc37e65cd8cccb75ff0b49e24 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Mon, 27 Nov 2017 19:47:25 +0000 Subject: [PATCH 08/45] completions: Support numbers for `buffer` --- src/completions.ts | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/completions.ts b/src/completions.ts index bf5d0ac1..a6de189b 100644 --- a/src/completions.ts +++ b/src/completions.ts @@ -399,7 +399,7 @@ export class HistoryCompletionSource extends CompletionSourceFuse { class BufferCompletionOption extends CompletionOptionHTML implements CompletionOptionFuse { public fuseKeys = [] - constructor(public value: string, tab: browser.tabs.Tab, isAlternative = false) { + constructor(public value: string, tab: browser.tabs.Tab, public isAlternative = false) { super() // Two character buffer properties prefix let pre = "" @@ -477,7 +477,38 @@ export class BufferCompletionSource extends CompletionSourceFuse { // will be for other things. this.updateOptions() } - setStateFromScore(scoredOpts: ScoredOption[]){super.setStateFromScore(scoredOpts, true)} + + setStateFromScore(scoredOpts: ScoredOption[]){ + super.setStateFromScore(scoredOpts, true) + } + + /** Score with fuse unless query is an integer or a single # */ + scoredOptions(query: string, options = this.options): ScoredOption[] { + const args = query.split(/\s+/gu) + if (args.length === 1) { + if (Number.isInteger(Number(args[0]))) { + const index = (Number(args[0]) - 1).mod(options.length) + return [{ + index, + option: options[index], + score: 0, + }] + } else if (args[0] === '#') { + for (const [index, option] of enumerate(options)) { + if (option.isAlternative) { + return [{ + index, + option, + score: 0, + }] + } + } + } + } + + // If not yet returned... + return super.scoredOptions(query, options) + } } // {{{ UNUSED: MANAGING ASYNC CHANGES From 1c8e88693c8286f086eed052900f1c07927b723a Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Mon, 27 Nov 2017 19:54:45 +0000 Subject: [PATCH 09/45] excmd: document new `buffer` functions --- src/excmds.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index ef10f891..53a1bc78 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -830,20 +830,23 @@ export async function buffers() { /** Change active tab. - The buffer index starts at 1. + @param index + Starts at 1. 0 refers to last tab, -1 to penultimate tab, etc. + + "#" means the tab that was last accessed in this window */ //#background -export async function buffer(n: number | '#') { - if (n === "#") { +export async function buffer(index: number | '#') { + if (index === "#") { // Switch to the most recently accessed buffer tabindex( (await browser.tabs.query({currentWindow: true})).sort((a, b) => { return a.lastAccessed < b.lastAccessed ? 1 : -1 })[1].index ) - } else if (Number.isInteger(Number(n))) { + } else if (Number.isInteger(Number(index))) { // Internal indices start at 0. - tabindex(Number(n) - 1) + tabindex(Number(index) - 1) } } From 56da023ee650aff5fec1576b443ab2116c7b6c89 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Mon, 27 Nov 2017 19:56:13 +0000 Subject: [PATCH 10/45] build: Fix make_docs on some shells --- scripts/make_docs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/make_docs.sh b/scripts/make_docs.sh index 7301c4a6..7da22049 100755 --- a/scripts/make_docs.sh +++ b/scripts/make_docs.sh @@ -1,5 +1,5 @@ #!/bin/sh dest=generated/static/docs typedoc --out $dest src --ignoreCompilerErrors -find $dest -name *.html -exec ./scripts/commandline_injector.sh '{}' \; +find $dest -name \*.html -exec ./scripts/commandline_injector.sh '{}' \; cp -r $dest build/static/ From ca9e3b25f0b1c5a5a136a0f6cd17b5517093cac2 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 00:01:41 +0000 Subject: [PATCH 11/45] excmd: improve tab commands --- src/excmds.ts | 143 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 98 insertions(+), 45 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 53a1bc78..ba8cff58 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -495,20 +495,16 @@ document.addEventListener("focusin",e=>{if (DOM.isTextEditable(e.target as HTMLE /** Switch to the tab by index (position on tab bar), wrapping round. - Note: all internal indices should start at 0. - */ + @param index + 1-based index of the tab to target. Wraps such that 0 = last tab, -1 = + penultimate tab, etc. + + if undefined, return activeTabId() +*/ /** @hidden */ //#background_helper -async function tabindex(index: number) { - // Get an array of tabs in the current window - let current_window = await browser.windows.getCurrent() - let tabs = await browser.tabs.query({windowId: current_window.id}) - - // Find and switch to the tab with that index - let desiredTab = tabs.find((tab: any) => { - return tab.index === index.mod(tabs.length) - }) - tabSetActive(desiredTab.id) +async function tabIndexSetActive(index: number) { + tabSetActive(await idFromIndex(index)) } /** Switch to the next tab, wrapping round. @@ -517,7 +513,7 @@ async function tabindex(index: number) { */ //#background export async function tabnext(increment = 1) { - tabindex((await activeTab()).index + increment) + tabIndexSetActive((await activeTab()).index + increment + 1) } /** Switch to the previous tab, wrapping round. @@ -526,19 +522,19 @@ export async function tabnext(increment = 1) { */ //#background export async function tabprev(increment = 1) { - tabindex((await activeTab()).index - increment) + tabIndexSetActive((await activeTab()).index - increment + 1) } /** Switch to the first tab. */ //#background export async function tabfirst() { - tabindex(0) + tabIndexSetActive(1) } /** Switch to the last tab. */ //#background export async function tablast() { - tabindex(-1) + tabIndexSetActive(0) } /** Like [[open]], but in a new tab */ @@ -550,24 +546,70 @@ export async function tabopen(...addressarr: string[]) { browser.tabs.create({url: uri}) } -//#background -export async function tabduplicate(id?: number){ - id = id ? id : (await activeTabId()) - browser.tabs.duplicate(id) -} +/** Resolve a tab index to the tab id of the corresponding tab in this window. -//#background -export async function tabdetach(id?: number){ - id = id ? id : (await activeTabId()) - browser.windows.create({tabId: id}) -} + @param index + 1-based index of the tab to target. Wraps such that 0 = last tab, -1 = + penultimate tab, etc. -//#background -export async function tabclose(ids?: number[] | number) { - if (ids !== undefined) { - browser.tabs.remove(ids) + if undefined, return activeTabId() + + @hidden +*/ +//#background_helper +async function idFromIndex(index?: number): Promise { + if (index) { + // Wrap if required + if (index <= 0) { + index = (index - 1).mod( + (await browser.tabs.query({currentWindow: true})).length) + + 1 + } + + // Return id of tab with that index. + return (await browser.tabs.query({ + currentWindow: true, + index: index - 1, + }))[0].id } else { - // Close the current tab + return await activeTabId() + } +} + +/** Duplicate a tab. + + @param index + The 1-based index of the tab to target. index < 1 wraps. If omitted, this tab. +*/ +//#background +export async function tabduplicate(index?: number) { + browser.tabs.duplicate(await idFromIndex(index)) +} + +/** Detach a tab, opening it in a new window. + + @param index + The 1-based index of the tab to target. index < 1 wraps. If omitted, this tab. +*/ +//#background +export async function tabdetach(index?: number) { + browser.windows.create({tabId: await idFromIndex(index)}) +} + +/** Close a tab. + + Known bug: autocompletion will make it impossible to close more than one tab at once if the list of numbers looks enough like an open tab's title or URL. + + @param indexes + The 1-based indexes of the tabs to target. indexes < 1 wrap. If omitted, this tab. +*/ +//#background +export async function tabclose(...indexes: string[]) { + if (indexes.length > 0) { + const idsPromise = indexes.map(index => idFromIndex(Number(index))) + browser.tabs.remove(await Promise.all(idsPromise)) + } else { + // Close current tab browser.tabs.remove(await activeTabId()) } } @@ -592,17 +634,29 @@ export async function undo(){ } } +/** Move the current tab to be just in front of the index specified. + + Known bug: This supports relative movement, but autocomple doesn't know + that yet and will override positive and negative indexes. + + Put a space in front of tabmove if you want to disable completion and have + the relative indexes at the command line. + + Binds are unaffected. + + @param index + New index for the current tab. + + 1 is the first index. 0 is the last index. -1 is the penultimate, etc. +*/ //#background -export async function tabmove(n?: string) { - let aTab = await activeTab(), - m: number - if (!n) { - browser.tabs.move(aTab.id, {index: -1}) - return - } else if (n.startsWith("+") || n.startsWith("-")) { - m = Math.max(0, Number(n) + aTab.index) - } else m = Number(n) - browser.tabs.move(aTab.id, {index: m}) +export async function tabmove(index = "0") { + const aTab = await activeTab() + let newindex: number + if (index.startsWith("+") || index.startsWith("-")) { + newindex = Math.max(0, Number(index) + aTab.index) + } else newindex = Number(index) - 1 + browser.tabs.move(aTab.id, {index: newindex}) } /** Pin the current tab */ @@ -839,14 +893,13 @@ export async function buffers() { export async function buffer(index: number | '#') { if (index === "#") { // Switch to the most recently accessed buffer - tabindex( + tabIndexSetActive( (await browser.tabs.query({currentWindow: true})).sort((a, b) => { return a.lastAccessed < b.lastAccessed ? 1 : -1 - })[1].index + })[1].index + 1 ) } else if (Number.isInteger(Number(index))) { - // Internal indices start at 0. - tabindex(Number(index) - 1) + tabIndexSetActive(Number(index)) } } From 7dda663a474ff66395361c052f0738be768390a3 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 00:05:12 +0000 Subject: [PATCH 12/45] excmd: Simplify IPC for geturlsforlinks This is more about maybe expanding the macro system in the future. Need to look at how other projects do it. --- scripts/excmds_macros.py | 2 +- src/excmds.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/excmds_macros.py b/scripts/excmds_macros.py index 9d7b60fe..598ed720 100755 --- a/scripts/excmds_macros.py +++ b/scripts/excmds_macros.py @@ -93,7 +93,7 @@ def content(lines, context): sig = Signature(block.split('\n')[0]) return "cmd_params.set('{sig.name}', ".format(**locals()) + dict_to_js(sig.params) + """) {sig.raw} - messageActiveTab( + return messageActiveTab( "excmd_content", "{sig.name}", """.format(**locals()) + str(list(sig.params.keys())).replace("'","") + """, diff --git a/src/excmds.ts b/src/excmds.ts index ba8cff58..8feac1f5 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -361,6 +361,10 @@ export function urlparent (){ } /** Returns the url of links that have a matching rel. + + Don't bind to this: it's an internal function. + + @hidden */ //#content export function geturlsforlinks(rel: string){ @@ -837,13 +841,13 @@ export async function clipboard(excmd: "open"|"yank"|"yankshort"|"yankcanon"|"ta let urls = [] switch (excmd) { case 'yankshort': - urls = await messageActiveTab("excmd_content", "geturlsforlinks", ["shortlink"]); + urls = await geturlsforlinks("shortlink") if (urls.length > 0) { messageActiveTab("commandline_frame", "setClipboard", [urls[0]]) break } case 'yankcanon': - urls = await messageActiveTab("excmd_content", "geturlsforlinks", ["canonical"]); + urls = await geturlsforlinks("canonical") if (urls.length > 0) { messageActiveTab("commandline_frame", "setClipboard", [urls[0]]) break From cd82433633b629c870ffdd973230799e9e063719 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 00:20:23 +0000 Subject: [PATCH 13/45] excmd: Document tabs and buffers --- src/excmds.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 8feac1f5..632bec76 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -875,12 +875,20 @@ export async function clipboard(excmd: "open"|"yank"|"yankshort"|"yankcanon"|"ta } // {{{ Buffer/completion stuff -// TODO: Move autocompletions out of excmds. -/** Ported from Vimperator. */ + +/** Equivalent to `fillcmdline buffer` + + Sort of Vimperator alias +*/ //#background export async function tabs() { fillcmdline("buffer") } + +/** Equivalent to `fillcmdline buffer` + + Sort of Vimperator alias +*/ //#background export async function buffers() { tabs() @@ -907,12 +915,6 @@ export async function buffer(index: number | '#') { } } -/*/1** Set tab with index of n belonging to window with id of m to active *1/ */ -/*//#background */ -/*export async function bufferall(m?: number, n?: number) { */ -/* // TODO */ -/*} */ - // }}} // }}} From c57a829aa8aaa5422bed81a2124b450cd7284b77 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 02:12:07 +0000 Subject: [PATCH 14/45] excmd: document hint --- src/excmds.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 632bec76..69b0e65b 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -1005,12 +1005,17 @@ export async function quickmark(key: string) { import * as hinting from './hinting_background' /** Hint a page. -* -* Pass -b as first argument to open hinted page in background. -* -y copies the link's target to the clipboard. -* -p copies an element's text to the clipboard.*/ + + @param option + - -b open in background + - -y copy (yank) link's target to clipboard + - -p copy an element's text to the clipboard + - -i view an image + - -I view an image in a new tab + - -; focus an element +*/ //#background -export function hint(option?: "-b") { +export function hint(option?: string) { if (option === '-b') hinting.hintPageOpenInBackground() else if (option === "-y") hinting.hintPageYank() else if (option === "-p") hinting.hintPageTextYank() From 074393332f58b6ccf09e44a6ba2ae74e622bcc84 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 02:45:15 +0000 Subject: [PATCH 15/45] release 1.6.0 New features: - auto-completion for `:open`, `:tabopen`, etc. - Tab selects autocompletion options - New hint modes! ;i - open image ;I - open image in new tab ;; - focus - Better commandline history completion (substring matching!) - Better completion for `:buffer`, `:tabmove`, etc. - Focus inputs gi -- last input focusinput -p -- first password input focusinput -b -- largest input - Better documentation! - More search URLs - Yank the short or canonical link (see :help clipboard) Contributors to this release: John Beard - Image hinting - Focus input Olie Blanthorn - Both types of history completion - Merging a bunch of PRs - Some documentation Colin Caine - Improved buffer completion - Documentation Olle Fredriksson - Make searches including hashes work. Jake Beazley - CSS @chocolateboy - Improved pagination [[,]] @glacambre - More search URLs Robbie McMichael - Fix build for BSD sed (OSX) - tabfirst, tablast emanresusername - Select more elements in hints --- src/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.json b/src/manifest.json index acacc8bf..365cb3d0 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Tridactyl", - "version": "1.5.1", + "version": "1.6.0", "icons": { "64": "static/logo/Tridactyl_64px.png", "100": "static/logo/Tridactyl_100px.png", From 4e9355e5ce4dd9623ad92bdbbc4b0d4012eb19c1 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 02:52:10 +0000 Subject: [PATCH 16/45] build: Helper for stable releases --- scripts/version.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/version.js b/scripts/version.js index 6fc0249d..ccad7656 100755 --- a/scripts/version.js +++ b/scripts/version.js @@ -20,6 +20,12 @@ async function add_beta(versionstr) { }) } +function save_manifest(filename, manifest) { + // Save file + const fs = require('fs') + fs.writeFileSync(filename, JSON.stringify(manifest, null, 4)) +} + async function main() { let filename, manifest switch (process.argv[2]) { @@ -28,19 +34,18 @@ async function main() { filename = './src/manifest.json' manifest = require('.' + filename) manifest.version = bump_version(manifest.version, Number(process.argv[3])) + save_manifest(filename, manifest) + exec(`git add ${filename} && git commit -m 'release ${manifest.version}' && git tag ${manifest.version}`) break case 'beta': filename = './build/manifest.json' manifest = require('.' + filename) manifest.version = await add_beta(manifest.version) + save_manifest(filename, manifest) break default: throw "Unknown command!" } - - // Save file - const fs = require('fs') - fs.writeFile(filename, JSON.stringify(manifest, null, 4), err=>err && console.error(err)) } main() From 758c91a5f4bd5d42c62f4a28e508d5d528fe5f0b Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Tue, 28 Nov 2017 13:53:22 +0000 Subject: [PATCH 17/45] Fix #150: tab{next,prev} now wrap again --- src/excmds.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 69b0e65b..343f6b26 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -562,13 +562,11 @@ export async function tabopen(...addressarr: string[]) { */ //#background_helper async function idFromIndex(index?: number): Promise { - if (index) { + if (index !== undefined) { // Wrap if required - if (index <= 0) { - index = (index - 1).mod( - (await browser.tabs.query({currentWindow: true})).length) - + 1 - } + let tabs = (await browser.tabs.query({currentWindow: true})) + if (index < 0) index += tabs.length + index = (index - 1).mod(tabs.length) + 1 // Return id of tab with that index. return (await browser.tabs.query({ From c255d1a2e931f3ee1fd05f871dc8180ec30d890a Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Tue, 28 Nov 2017 14:17:51 +0000 Subject: [PATCH 18/45] Fix #151: version is now Colin-proof and does not rely on tags --- scripts/git_version.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/git_version.sh b/scripts/git_version.sh index 0d68aea4..8f77401b 100755 --- a/scripts/git_version.sh +++ b/scripts/git_version.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash -version=$(git describe --tags) +gitversion=$(git describe --tags | cut -d"-" -f2-) +manversion=$(grep '"version":' ./src/manifest.json | cut -d":" -f2 | tr -d \" | tr -d , | cut -d" " -f2) +version=$manversion-$gitversion sed -i 's/REPLACE_ME_WITH_THE_VERSION_USING_SED/'$version'/' ./build/background.js From de84cd42f5d6787b9115c9f61943b61ed9e47de3 Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Tue, 28 Nov 2017 14:28:20 +0000 Subject: [PATCH 19/45] Fix #134: [count]gt now goes to nth tab Known issues: count is ignored on gT --- src/excmds.ts | 9 +++++++++ src/parsers/normalmode.ts | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 343f6b26..0c9251a4 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -529,6 +529,15 @@ export async function tabprev(increment = 1) { tabIndexSetActive((await activeTab()).index - increment + 1) } +//#background +export async function tabchoose(n: number | string = "next"){ + if (n == "next") tabnext() + else if (n == "prev") tabprev() + else { + tabIndexSetActive(n as number) + } +} + /** Switch to the first tab. */ //#background export async function tabfirst() { diff --git a/src/parsers/normalmode.ts b/src/parsers/normalmode.ts index ef9189a0..4d977f0d 100755 --- a/src/parsers/normalmode.ts +++ b/src/parsers/normalmode.ts @@ -39,8 +39,8 @@ export const DEFAULTNMAPS = { "r": "reload", "R": "reloadhard", "gi": "focusinput -l", - "gt": "tabnext", - "gT": "tabprev", + "gt": "tabchoose", + "gT": "tabchoose prev", "g^": "tabfirst", "g$": "tablast", "gr": "reader", From fc02d21da736da47b8137082f7898fa6a3901550 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 15:47:12 +0000 Subject: [PATCH 20/45] excmd: fix wrapping on tabnext/prev --- src/excmds.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 0c9251a4..9502faaa 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -572,16 +572,16 @@ export async function tabopen(...addressarr: string[]) { //#background_helper async function idFromIndex(index?: number): Promise { if (index !== undefined) { - // Wrap if required - let tabs = (await browser.tabs.query({currentWindow: true})) - if (index < 0) index += tabs.length - index = (index - 1).mod(tabs.length) + 1 + // Wrap + index = (index - 1).mod( + (await l(browser.tabs.query({currentWindow: true}))).length) + + 1 // Return id of tab with that index. - return (await browser.tabs.query({ + return (await l(browser.tabs.query({ currentWindow: true, index: index - 1, - }))[0].id + })))[0].id } else { return await activeTabId() } From baa5a9b861193bdbf6a770a494410fd185ab3631 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 16:00:34 +0000 Subject: [PATCH 21/45] excmd: fix :version --- scripts/git_version.sh | 7 ------- src/excmds.ts | 6 ++++-- src/tridactyl.d.ts | 6 ++++++ 3 files changed, 10 insertions(+), 9 deletions(-) delete mode 100755 scripts/git_version.sh diff --git a/scripts/git_version.sh b/scripts/git_version.sh deleted file mode 100755 index 8f77401b..00000000 --- a/scripts/git_version.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -gitversion=$(git describe --tags | cut -d"-" -f2-) -manversion=$(grep '"version":' ./src/manifest.json | cut -d":" -f2 | tr -d \" | tr -d , | cut -d" " -f2) -version=$manversion-$gitversion - -sed -i 's/REPLACE_ME_WITH_THE_VERSION_USING_SED/'$version'/' ./build/background.js diff --git a/src/excmds.ts b/src/excmds.ts index 9502faaa..1c37dc67 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -719,10 +719,12 @@ export function suppress(preventDefault?: boolean, stopPropagation?: boolean) { mode("ignore") } +import manifest from './manifest.json' + //#background export function version(){ - clipboard("yank","REPLACE_ME_WITH_THE_VERSION_USING_SED") - fillcmdline_notrail("REPLACE_ME_WITH_THE_VERSION_USING_SED") + clipboard("yank", manifest.version) + fillcmdline_notrail(manifest.version) } diff --git a/src/tridactyl.d.ts b/src/tridactyl.d.ts index 55405c33..280383b8 100644 --- a/src/tridactyl.d.ts +++ b/src/tridactyl.d.ts @@ -35,3 +35,9 @@ declare namespace browser.tabs { // html-tagged-template.js declare function html(strings: TemplateStringsArray, ...values: any[]): HTMLElement + +// Stub declaration to allow importing JSON. +declare module "*.json" { + const value: any; + export default value; +} From e933c100d3fc2b446481c55b3649c63e90c1f9f9 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 16:16:41 +0000 Subject: [PATCH 22/45] excmd: Replace tabchoose with tabprev_gt The gT and gt maps in vim aren't symmetric, this better preserves those semantics while removing a confusingly named excmd --- src/excmds.ts | 25 ++++++++++++++++--------- src/parsers/normalmode.ts | 4 ++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 1c37dc67..fe12a9f7 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -520,6 +520,22 @@ export async function tabnext(increment = 1) { tabIndexSetActive((await activeTab()).index + increment + 1) } +/** Switch to the next tab, wrapping round. + + If an index is specified, go to the tab with that number (this mimics the + behaviour of `{count}gt` in vim, except that this command will accept a + count that is out of bounds (and will mod it so that it is within bounds as + per [[tabmove]], etc)). + */ +//#background +export async function tabnext_gt(index?: number) { + if (index === undefined) { + tabnext() + } else { + tabIndexSetActive(index) + } +} + /** Switch to the previous tab, wrapping round. If increment is specified, move that many tabs backwards. @@ -529,15 +545,6 @@ export async function tabprev(increment = 1) { tabIndexSetActive((await activeTab()).index - increment + 1) } -//#background -export async function tabchoose(n: number | string = "next"){ - if (n == "next") tabnext() - else if (n == "prev") tabprev() - else { - tabIndexSetActive(n as number) - } -} - /** Switch to the first tab. */ //#background export async function tabfirst() { diff --git a/src/parsers/normalmode.ts b/src/parsers/normalmode.ts index 4d977f0d..131fec96 100755 --- a/src/parsers/normalmode.ts +++ b/src/parsers/normalmode.ts @@ -39,8 +39,8 @@ export const DEFAULTNMAPS = { "r": "reload", "R": "reloadhard", "gi": "focusinput -l", - "gt": "tabchoose", - "gT": "tabchoose prev", + "gt": "tabnext_gt", + "gT": "tabprev", "g^": "tabfirst", "g$": "tablast", "gr": "reader", From 4a3a25751c1a0188131366f14afdb465b0e44a6f Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 17:19:50 +0000 Subject: [PATCH 23/45] hinting: Fix #136. Hints still don't work for images, but I don't see any way we can detect that they're clickable, besides special casing instagram. --- src/hinting.ts | 2 +- src/static/hint.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hinting.ts b/src/hinting.ts index 74ad6455..f021b5e6 100644 --- a/src/hinting.ts +++ b/src/hinting.ts @@ -20,7 +20,7 @@ import {messageActiveTab} from './messaging' /** Simple container for the state of a single frame's hints. */ class HintState { public focusedHint: Hint - readonly hintHost = document.createElement('div') + readonly hintHost = html`
` readonly hints: Hint[] = [] public filter = '' public hintchars = '' diff --git a/src/static/hint.css b/src/static/hint.css index dcca5e1e..0afefacb 100644 --- a/src/static/hint.css +++ b/src/static/hint.css @@ -23,3 +23,7 @@ span.TridactylHint { .TridactylHintElem { background-color: yellow; } .TridactylHintActive { background-color: #88FF00; } + +div.TridactylHintHost { + position: static !important; +} From 3cae9a53349291944f5a7d269173ad3367b84531 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 17:23:16 +0000 Subject: [PATCH 24/45] Revert "excmd: fix :version" This reverts commit baa5a9b861193bdbf6a770a494410fd185ab3631. --- scripts/git_version.sh | 7 +++++++ src/excmds.ts | 6 ++---- src/tridactyl.d.ts | 6 ------ 3 files changed, 9 insertions(+), 10 deletions(-) create mode 100755 scripts/git_version.sh diff --git a/scripts/git_version.sh b/scripts/git_version.sh new file mode 100755 index 00000000..8f77401b --- /dev/null +++ b/scripts/git_version.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +gitversion=$(git describe --tags | cut -d"-" -f2-) +manversion=$(grep '"version":' ./src/manifest.json | cut -d":" -f2 | tr -d \" | tr -d , | cut -d" " -f2) +version=$manversion-$gitversion + +sed -i 's/REPLACE_ME_WITH_THE_VERSION_USING_SED/'$version'/' ./build/background.js diff --git a/src/excmds.ts b/src/excmds.ts index fe12a9f7..327847d2 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -726,12 +726,10 @@ export function suppress(preventDefault?: boolean, stopPropagation?: boolean) { mode("ignore") } -import manifest from './manifest.json' - //#background export function version(){ - clipboard("yank", manifest.version) - fillcmdline_notrail(manifest.version) + clipboard("yank","REPLACE_ME_WITH_THE_VERSION_USING_SED") + fillcmdline_notrail("REPLACE_ME_WITH_THE_VERSION_USING_SED") } diff --git a/src/tridactyl.d.ts b/src/tridactyl.d.ts index 280383b8..55405c33 100644 --- a/src/tridactyl.d.ts +++ b/src/tridactyl.d.ts @@ -35,9 +35,3 @@ declare namespace browser.tabs { // html-tagged-template.js declare function html(strings: TemplateStringsArray, ...values: any[]): HTMLElement - -// Stub declaration to allow importing JSON. -declare module "*.json" { - const value: any; - export default value; -} From 4a271805bb1f87ab84106d9c42bd089dcb893a40 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 17:36:40 +0000 Subject: [PATCH 25/45] build: BSD compatibility for git_version.sh --- scripts/git_version.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/git_version.sh b/scripts/git_version.sh index 8f77401b..72f01cc3 100755 --- a/scripts/git_version.sh +++ b/scripts/git_version.sh @@ -4,4 +4,5 @@ gitversion=$(git describe --tags | cut -d"-" -f2-) manversion=$(grep '"version":' ./src/manifest.json | cut -d":" -f2 | tr -d \" | tr -d , | cut -d" " -f2) version=$manversion-$gitversion -sed -i 's/REPLACE_ME_WITH_THE_VERSION_USING_SED/'$version'/' ./build/background.js +sed -i.bak 's/REPLACE_ME_WITH_THE_VERSION_USING_SED/'$version'/' ./build/background.js +rm ./build/background.js.bak From 2be3646b213f5814c48cf53b9e1c09baaa71e8da Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Tue, 28 Nov 2017 18:37:39 +0000 Subject: [PATCH 26/45] release 1.6.1 --- src/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.json b/src/manifest.json index 365cb3d0..f7e038d1 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Tridactyl", - "version": "1.6.0", + "version": "1.6.1", "icons": { "64": "static/logo/Tridactyl_64px.png", "100": "static/logo/Tridactyl_100px.png", From a781c4bdd445fb9784170ff8d7f9bca5648547be Mon Sep 17 00:00:00 2001 From: Alex Kir Date: Wed, 29 Nov 2017 02:46:11 +0800 Subject: [PATCH 27/45] Added `tabonly` command Executing `tabonly` will close all tabs but active and pinned --- src/excmds.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/excmds.ts b/src/excmds.ts index 327847d2..312b726c 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -594,6 +594,18 @@ async function idFromIndex(index?: number): Promise { } } +/** Close all tabs but current */ +//#background +export async function tabonly() { + let tabs = browser.tabs.query({pinned: false, active: false}) + let tabsIds = [] + for (let id of await tabs) { + tabsIds.push(id.id) + } + browser.tabs.remove(tabsIds) +} + + /** Duplicate a tab. @param index From 23f845eb3f688b7090be4e18596311c3470d159b Mon Sep 17 00:00:00 2001 From: Alex Kir Date: Wed, 29 Nov 2017 07:19:02 +0800 Subject: [PATCH 28/45] Now looking for tabs to close only in current window --- src/excmds.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/excmds.ts b/src/excmds.ts index 312b726c..9bfce35c 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -597,7 +597,11 @@ async function idFromIndex(index?: number): Promise { /** Close all tabs but current */ //#background export async function tabonly() { - let tabs = browser.tabs.query({pinned: false, active: false}) + let tabs = browser.tabs.query({ + pinned: false, + active: false, + currentWindow: true + }) let tabsIds = [] for (let id of await tabs) { tabsIds.push(id.id) From b4a5edd20ca5bf101d533db7d51faa1e2d7afbf3 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Wed, 29 Nov 2017 00:36:53 +0000 Subject: [PATCH 29/45] excmd: remove tabs. Simplify tabonly --- src/excmds.ts | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 9bfce35c..46d06c6c 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -384,12 +384,12 @@ export function zoom(level=0){ //#background export async function reader() { if (await l(firefoxVersionAtLeast(58))) { - let aTab = await activeTab() - if (aTab.isArticle) { - browser.tabs.toggleReaderMode() - } // else { - // // once a statusbar exists an error can be displayed there - // } + let aTab = await activeTab() + if (aTab.isArticle) { + browser.tabs.toggleReaderMode() + } // else { + // // once a statusbar exists an error can be displayed there + // } } } @@ -594,18 +594,15 @@ async function idFromIndex(index?: number): Promise { } } -/** Close all tabs but current */ +/** Close all other tabs in this window */ //#background export async function tabonly() { - let tabs = browser.tabs.query({ - pinned: false, - active: false, - currentWindow: true + const tabs = await browser.tabs.query({ + pinned: false, + active: false, + currentWindow: true }) - let tabsIds = [] - for (let id of await tabs) { - tabsIds.push(id.id) - } + const tabsIds = tabs.map(tab => tab.id) browser.tabs.remove(tabsIds) } From a3ca5cd2c17be76ff3693698a5f73c3bf2a74698 Mon Sep 17 00:00:00 2001 From: John Beard Date: Tue, 28 Nov 2017 22:51:53 +0000 Subject: [PATCH 30/45] Add yank anchor hint submode (;#) THis yanks the page URL with the hinted elements id or title as a hash fragment, which can be used to link to the page at that element's location. For example: https://en.wikipedia.org/wiki/Vim_(text_editor)#History ;# nmap added, from Vimperator --- src/excmds.ts | 2 ++ src/hinting.ts | 28 ++++++++++++++++++++++++++++ src/hinting_background.ts | 5 +++++ src/parsers/normalmode.ts | 1 + 4 files changed, 36 insertions(+) diff --git a/src/excmds.ts b/src/excmds.ts index 327847d2..ca3300d8 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -1027,6 +1027,7 @@ import * as hinting from './hinting_background' - -i view an image - -I view an image in a new tab - -; focus an element + - -# yank an element's anchor URL to clipboard */ //#background export function hint(option?: string) { @@ -1036,6 +1037,7 @@ export function hint(option?: string) { else if (option === "-i") hinting.hintImage(false) else if (option === "-I") hinting.hintImage(true) else if (option === "-;") hinting.hintFocus() + else if (option === "-#") hinting.hintPageAnchorYank() else hinting.hintPageSimple() } diff --git a/src/hinting.ts b/src/hinting.ts index f021b5e6..6415ada2 100644 --- a/src/hinting.ts +++ b/src/hinting.ts @@ -211,6 +211,14 @@ function hintableImages() { isVisible) } +/** Get arrat of "anchors": elements which have id or name and can be addressed + * with the hash/fragment in the URL + */ +function anchors() { + return Array.from(document.querySelectorAll(HINTTAGS_anchor_selectors)) + .filter(isVisible) +} + // CSS selectors. More readable for web developers. Not dead. Leaves browser to care about XML. const HINTTAGS_selectors = ` input:not([type=hidden]):not([disabled]), @@ -265,6 +273,11 @@ img, [src] ` +const HINTTAGS_anchor_selectors = ` +[id], +[name] +` + import {activeTab, browserBg, l, firefoxVersionAtLeast} from './lib/webext' async function openInBackground(url: string) { @@ -327,6 +340,20 @@ function hintPageYank() { }) } +/** Hint anchors and yank the URL on selection + */ +function hintPageAnchorYank() { + + hintPage(anchors(), hint=>{ + + let anchorUrl = new URL(window.location.href) + + anchorUrl.hash = hint.target.id || hint.target.name; + + messageActiveTab("commandline_frame", "setClipboard", [anchorUrl.href]) + }) +} + /** Hint images, opening in the same tab, or in a background tab * * @param inBackground opens the image source URL in a background tab, @@ -366,6 +393,7 @@ addListener('hinting_content', attributeCaller({ hintPageSimple, hintPageYank, hintPageTextYank, + hintPageAnchorYank, hintPageOpenInBackground, hintImage, hintFocus, diff --git a/src/hinting_background.ts b/src/hinting_background.ts index f3c0f8d1..ac05b8a9 100644 --- a/src/hinting_background.ts +++ b/src/hinting_background.ts @@ -19,6 +19,11 @@ export async function hintPageYank() { export async function hintPageTextYank() { return await messageActiveTab('hinting_content', 'hintPageTextYank') } + +export async function hintPageAnchorYank() { + return await messageActiveTab('hinting_content', 'hintPageAnchorYank') +} + export async function hintPageSimple() { return await messageActiveTab('hinting_content', 'hintPageSimple') } diff --git a/src/parsers/normalmode.ts b/src/parsers/normalmode.ts index 131fec96..5027ecb2 100755 --- a/src/parsers/normalmode.ts +++ b/src/parsers/normalmode.ts @@ -61,6 +61,7 @@ export const DEFAULTNMAPS = { ";y": "hint -y", ";p": "hint -p", ";;": "hint -;", + ";#": "hint -#", "I": "mode ignore", "a": "current_url bmark", "A": "bmark", From f0dfe1d5da5f99ddde3e29e4be0c36720b89dc2d Mon Sep 17 00:00:00 2001 From: John Beard Date: Tue, 28 Nov 2017 23:20:42 +0000 Subject: [PATCH 31/45] Refactor document CSS selector queries with dom.ts The DOM.getElemsBySelector function makes it a bit less verbose to construct an array of filtered elements matching a selector. Also, this function is expanded to take multiple filters, applied sequentially. --- src/dom.ts | 20 ++++++++++++++------ src/excmds.ts | 6 +++--- src/hinting.ts | 24 +++++++++++------------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/dom.ts b/src/dom.ts index 075aa0bc..94f13693 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -183,23 +183,31 @@ export function isVisible (element: Element) { /** Get all elements that match the given selector * * @param selector `the CSS selector to choose elements with - * @param filter filter to use to further chose items, or null for all + * @param filters filter to use (in thre given order) to further chose + * items, or [] for all */ -export function getElemsBySelector(selector: string, filter: ElementFilter) { +export function getElemsBySelector(selector: string, + filters: Array) { + let elems = Array.from(document.querySelectorAll(selector)) - return filter ? elems.filter(filter) : elems + for (let filter of filters) { + elems = elems.filter(filter) + } + + return elems } /** Get the nth input element on a page * * @param nth the element index, can be negative to start at the end - * @param filter filter to use to further chose items, or null for all + * @param filters filter to use (in thre given order) to further chose + * items, or [] for all */ export function getNthElement(selectors: string, nth: number, - filter: ElementFilter) { + filters: Array): HTMLElement { - let inputs = getElemsBySelector(selectors, filter) + let inputs = getElemsBySelector(selectors, filters) if (inputs.length) { let index = Number(nth).clamp(-inputs.length, inputs.length - 1) diff --git a/src/excmds.ts b/src/excmds.ts index ca3300d8..caf352d3 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -463,7 +463,7 @@ export function focusinput(nth: number|string) { fallbackToNumeric = false let inputs = DOM.getElemsBySelector(INPUTPASSWORD_selectors, - DOM.isSubstantial) + [DOM.isSubstantial]) if (inputs.length) { inputToFocus = inputs[0] @@ -472,7 +472,7 @@ export function focusinput(nth: number|string) { else if (nth === "-b") { let inputs = DOM.getElemsBySelector(INPUTTAGS_selectors, - DOM.isSubstantial) as HTMLElement[] + [DOM.isSubstantial]) as HTMLElement[] inputToFocus = inputs.sort(DOM.compareElementArea).slice(-1)[0] } @@ -483,7 +483,7 @@ export function focusinput(nth: number|string) { let index = isNaN(nth) ? 0 : nth inputToFocus = DOM.getNthElement(INPUTTAGS_selectors, - index, DOM.isSubstantial) + index, [DOM.isSubstantial]) } if (inputToFocus) inputToFocus.focus() diff --git a/src/hinting.ts b/src/hinting.ts index 6415ada2..ffad363a 100644 --- a/src/hinting.ts +++ b/src/hinting.ts @@ -10,7 +10,7 @@ Redraw on reflow */ -import {elementsByXPath, isVisible, mouseEvent} from './dom' +import * as DOM from './dom' import {log} from './math' import {permutationsWithReplacement, islice, izip, map} from './itertools' import {hasModifiers} from './keyseq' @@ -192,31 +192,29 @@ function pushKey(ke) { 2. Not hidden by another element */ function hintables() { - return Array.from(document.querySelectorAll(HINTTAGS_selectors)).filter(isVisible) + return DOM.getElemsBySelector(HINTTAGS_selectors, [DOM.isVisible]) } function elementswithtext() { - return Array.from(document.querySelectorAll(HINTTAGS_text_selectors)).filter( - isVisible - ).filter(hint => { - return hint.textContent != "" - }) + + return DOM.getElemsBySelector(HINTTAGS_text_selectors, + [DOM.isVisible, hint => { + return hint.textContent != "" + }] + ) } /** Get array of images in the viewport */ function hintableImages() { - /* return [...elementsByXPath(HINTTAGS)].filter(isVisible) as any as Element[] */ - return Array.from(document.querySelectorAll(HINTTAGS_img_selectors)).filter( - isVisible) + return DOM.getElemsBySelector(HINTTAGS_img_selectors, [DOM.isVisible]) } /** Get arrat of "anchors": elements which have id or name and can be addressed * with the hash/fragment in the URL */ function anchors() { - return Array.from(document.querySelectorAll(HINTTAGS_anchor_selectors)) - .filter(isVisible) + return DOM.getElemsBySelector(HINTTAGS_anchor_selectors, [DOM.isVisible]) } // CSS selectors. More readable for web developers. Not dead. Leaves browser to care about XML. @@ -303,7 +301,7 @@ function simulateClick(target: HTMLElement) { ) { browserBg.tabs.create({url: (target as HTMLAnchorElement).href}) } else { - mouseEvent(target, "click") + DOM.mouseEvent(target, "click") // Sometimes clicking the element doesn't focus it sufficiently. target.focus() } From dc7b5717db74bdaa1e01cfd153de44cd9608cf34 Mon Sep 17 00:00:00 2001 From: John Beard Date: Wed, 29 Nov 2017 02:55:04 +0000 Subject: [PATCH 32/45] Hint CSS: make easier to see stacked hints This has two major aspects: - Add a border so the extent of a hint is clear - Make hint BG translucent, so stacked hints can be discerned Also used more Tango-ish colours to soften the intense yellow. --- src/static/hint.css | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/static/hint.css b/src/static/hint.css index 0afefacb..b8a0ba10 100644 --- a/src/static/hint.css +++ b/src/static/hint.css @@ -5,7 +5,7 @@ span.TridactylHint { font-weight: bold; text-transform: uppercase; color: white; - background-color: red; + background-color: #CC0000; border-color: ButtonShadow; border-width: 0px; min-width: .75em; @@ -21,8 +21,15 @@ span.TridactylHint { transition: unset; } -.TridactylHintElem { background-color: yellow; } -.TridactylHintActive { background-color: #88FF00; } +.TridactylHintElem { + background-color: rgba(255, 240, 0, 0.5); + outline: 1px solid #8F5902; +} + +.TridactylHintActive { + background-color: #88FF00; + outline: 1px solid #CC0000; +} div.TridactylHintHost { position: static !important; From e42a173762ba86e4fc5545200575dc4ad2cedd4c Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 11:33:33 +0000 Subject: [PATCH 33/45] Revert to Vimperator colours, reduce opacity --- src/static/hint.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/hint.css b/src/static/hint.css index b8a0ba10..3a9ee0fe 100644 --- a/src/static/hint.css +++ b/src/static/hint.css @@ -5,7 +5,7 @@ span.TridactylHint { font-weight: bold; text-transform: uppercase; color: white; - background-color: #CC0000; + background-color: red; border-color: ButtonShadow; border-width: 0px; min-width: .75em; @@ -22,7 +22,7 @@ span.TridactylHint { } .TridactylHintElem { - background-color: rgba(255, 240, 0, 0.5); + background-color: rgba(255, 255, 0, 0.25); outline: 1px solid #8F5902; } From 6cd93b233d61a6c1f944d1468ed2961bb656f924 Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 12:09:37 +0000 Subject: [PATCH 34/45] Allow ;p to yank any element which contains text --- src/hinting.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/hinting.ts b/src/hinting.ts index ffad363a..fee6d72f 100644 --- a/src/hinting.ts +++ b/src/hinting.ts @@ -197,7 +197,7 @@ function hintables() { function elementswithtext() { - return DOM.getElemsBySelector(HINTTAGS_text_selectors, + return DOM.getElemsBySelector("*", [DOM.isVisible, hint => { return hint.textContent != "" }] @@ -252,20 +252,6 @@ select, [tabindex] ` -const HINTTAGS_text_selectors = ` -input:not([type=hidden]):not([disabled]), -a, -area, -iframe, -textarea, -button, -p, -div, -pre, -code, -span -` - const HINTTAGS_img_selectors = ` img, [src] From b9901257928bf860f061d806581acfd26980d9ed Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 13:23:30 +0000 Subject: [PATCH 35/45] Add `hint -c [selector]`: add hints that match selector It's particularly good on Hacker News and Reddit with `bind ;c hint -c [class*="expand"],[class="togg"]` for minimising comments and expanding images. --- src/excmds.ts | 5 ++++- src/hinting.ts | 8 ++++---- src/hinting_background.ts | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index e187c494..99a8f648 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -1041,9 +1041,11 @@ import * as hinting from './hinting_background' - -I view an image in a new tab - -; focus an element - -# yank an element's anchor URL to clipboard + - -c [selector] hint links that match the css selector + - `bind ;c hint -c [class*="expand"],[class="togg"]` works particularly well on reddit and HN */ //#background -export function hint(option?: string) { +export function hint(option?: string, selectors="") { if (option === '-b') hinting.hintPageOpenInBackground() else if (option === "-y") hinting.hintPageYank() else if (option === "-p") hinting.hintPageTextYank() @@ -1051,6 +1053,7 @@ export function hint(option?: string) { else if (option === "-I") hinting.hintImage(true) else if (option === "-;") hinting.hintFocus() else if (option === "-#") hinting.hintPageAnchorYank() + else if (option === "-c") hinting.hintPageSimple(selectors) else hinting.hintPageSimple() } diff --git a/src/hinting.ts b/src/hinting.ts index fee6d72f..85ea32b6 100644 --- a/src/hinting.ts +++ b/src/hinting.ts @@ -191,8 +191,8 @@ function pushKey(ke) { 1. Within viewport 2. Not hidden by another element */ -function hintables() { - return DOM.getElemsBySelector(HINTTAGS_selectors, [DOM.isVisible]) +function hintables(selectors=HINTTAGS_selectors) { + return DOM.getElemsBySelector(selectors, [DOM.isVisible]) } function elementswithtext() { @@ -306,8 +306,8 @@ function hintPageOpenInBackground() { }) } -function hintPageSimple() { - hintPage(hintables(), hint=>{ +function hintPageSimple(selectors=HINTTAGS_selectors) { + hintPage(hintables(selectors), hint=>{ simulateClick(hint.target) }) } diff --git a/src/hinting_background.ts b/src/hinting_background.ts index ac05b8a9..28752764 100644 --- a/src/hinting_background.ts +++ b/src/hinting_background.ts @@ -24,8 +24,8 @@ export async function hintPageAnchorYank() { return await messageActiveTab('hinting_content', 'hintPageAnchorYank') } -export async function hintPageSimple() { - return await messageActiveTab('hinting_content', 'hintPageSimple') +export async function hintPageSimple(selectors?) { + return await messageActiveTab('hinting_content', 'hintPageSimple',[selectors]) } export async function hintPageOpenInBackground() { From da68d5f39834a7587e2b26e74a99b23947f65118 Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 16:28:06 +0000 Subject: [PATCH 36/45] Add basic configuration with set Currently does not import previous binds --- src/config.ts | 120 ++++++++++++++++++++++++++++++++++++++ src/excmds.ts | 19 ++++++ src/parsers/normalmode.ts | 88 ++-------------------------- 3 files changed, 144 insertions(+), 83 deletions(-) create mode 100644 src/config.ts diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 00000000..9a8ab73d --- /dev/null +++ b/src/config.ts @@ -0,0 +1,120 @@ +// Sketch +// +// Need an easy way of getting and setting settings +// If a setting is not set, the default should probably be returned. +// That probably means that binds etc. should be per-key? +// +// We should probably store all settings in memory, and only load from storage on startup and when we set it +// +// Really, we'd like a way of just letting things use the variables +// +const CONFIGNAME = "userconfig" + +type StorageMap = browser.storage.StorageMap + +// make a naked object +function o(object){ + return Object.assign(Object.create(null),object) +} + +let USERCONFIG = o({}) +const DEFAULTS = o({ + "nmaps": o({ + "o": "fillcmdline open", + "O": "current_url open", + "w": "fillcmdline winopen", + "W": "current_url winopen", + "t": "fillcmdline tabopen", + //["t": "fillcmdline tabopen", // for now, use mozilla completion + "]]": "followpage next", + "[[": "followpage prev", + "[c": "urlincrement -1", + "]c": "urlincrement 1", + "T": "current_url tabopen", + "yy": "clipboard yank", + "ys": "clipboard yankshort", + "yc": "clipboard yankcanon", + "p": "clipboard open", + "P": "clipboard tabopen", + "j": "scrollline 10", + "k": "scrollline -10", + "h": "scrollpx -50", + "l": "scrollpx 50", + "G": "scrollto 100", + "gg": "scrollto 0", + "H": "back", + "L": "forward", + "d": "tabclose", + "u": "undo", + "r": "reload", + "R": "reloadhard", + "gi": "focusinput -l", + "gt": "tabnext_gt", + "gT": "tabprev", + "g^": "tabfirst", + "g$": "tablast", + "gr": "reader", + "gu": "urlparent", + "gU": "urlroot", + ":": "fillcmdline", + "s": "fillcmdline open google", + "S": "fillcmdline tabopen google", + "M": "gobble 1 quickmark", + "xx": "something", + // "B": "fillcmdline bufferall", + "b": "fillcmdline buffer", + "ZZ": "qall", + "f": "hint", + "F": "hint -b", + ";i": "hint -i", + ";I": "hint -I", + ";y": "hint -y", + ";p": "hint -p", + ";;": "hint -;", + ";#": "hint -#", + "I": "mode ignore", + "a": "current_url bmark", + "A": "bmark", + }) +}) + +// currently only supports 2D or 1D storage +export function get(target, property?){ + console.log(DEFAULTS) + if (property !== undefined){ + if (USERCONFIG[target] !== undefined){ + return USERCONFIG[target][property] || DEFAULTS[target][property] + } + else return DEFAULTS[target][property] + } + // god knows what happens if it isn't an object + if (typeof DEFAULTS[target] === "object") return Object.assign(DEFAULTS[target],USERCONFIG[target]) + else return DEFAULTS[target] || USERCONFIG[target] +} + +// if you don't specify a property and you should, this will wipe everything +export function set(target, value, property?){ + if (property !== undefined){ + if (USERCONFIG[target] === undefined) USERCONFIG[target] = o({}) + return USERCONFIG[target][property] = value + } + USERCONFIG[target] = value +} + +export async function save(storage: "local" | "sync" = "sync"){ + let storageobj = storage == "local" ? browser.storage.local : browser.storage.sync + storageobj.set({CONFIGNAME: USERCONFIG}).then(schlepp) +} + + +// Read all user configuration on start +// Local storage overrides sync +browser.storage.sync.get(CONFIGNAME).then(settings => { + schlepp(settings) + browser.storage.local.get(CONFIGNAME).then(schlepp) +}) + +function schlepp(settings){ + // "Import" is a reserved so this will have to do + Object.assign(USERCONFIG,settings[CONFIGNAME]) +} diff --git a/src/excmds.ts b/src/excmds.ts index 99a8f648..1b8e76d2 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -1024,6 +1024,25 @@ export async function quickmark(key: string) { await bind("gw" + key, "winopen", address) } +//#background_helper +import * as config from './config' + +//#background +export function get(target: string, property?: string){ + console.log(config.get(target,property)) +} + +//#background +export function set(target: string, value: string){ + config.set(target,value) +} + +//#background +export function bind2(key: string, ...bindarr: string[]){ + let exstring = bindarr.join(" ") + config.set("nmaps",exstring,key) +} + // }}} // {{{ HINTMODE diff --git a/src/parsers/normalmode.ts b/src/parsers/normalmode.ts index 5027ecb2..ca05cc52 100755 --- a/src/parsers/normalmode.ts +++ b/src/parsers/normalmode.ts @@ -3,88 +3,10 @@ differs from Vim in that no map may be a prefix of another map (e.g. 'g' and 'gg' cannot both be maps). This simplifies the parser. */ -// Normal-mode mappings. -// keystr -> ex_str -// TODO: Move these into a tridactyl-wide state namespace -// TODO: stop stealing keys from "insert mode" -// r -> refresh page is particularly unhelpful -// Can't stringify a map -> just use an object -export const DEFAULTNMAPS = { - "o": "fillcmdline open", - "O": "current_url open", - "w": "fillcmdline winopen", - "W": "current_url winopen", - "t": "fillcmdline tabopen", - //["t": "fillcmdline tabopen", // for now, use mozilla completion - "]]": "followpage next", - "[[": "followpage prev", - "[c": "urlincrement -1", - "]c": "urlincrement 1", - "T": "current_url tabopen", - "yy": "clipboard yank", - "ys": "clipboard yankshort", - "yc": "clipboard yankcanon", - "p": "clipboard open", - "P": "clipboard tabopen", - "j": "scrollline 10", - "k": "scrollline -10", - "h": "scrollpx -50", - "l": "scrollpx 50", - "G": "scrollto 100", - "gg": "scrollto 0", - "H": "back", - "L": "forward", - "d": "tabclose", - "u": "undo", - "r": "reload", - "R": "reloadhard", - "gi": "focusinput -l", - "gt": "tabnext_gt", - "gT": "tabprev", - "g^": "tabfirst", - "g$": "tablast", - "gr": "reader", - "gu": "urlparent", - "gU": "urlroot", - ":": "fillcmdline", - "s": "fillcmdline open google", - "S": "fillcmdline tabopen google", - "M": "gobble 1 quickmark", - "xx": "something", - // "B": "fillcmdline bufferall", - "b": "fillcmdline buffer", - "ZZ": "qall", - "f": "hint", - "F": "hint -b", - ";i": "hint -i", - ";I": "hint -I", - ";y": "hint -y", - ";p": "hint -p", - ";;": "hint -;", - ";#": "hint -#", - "I": "mode ignore", - "a": "current_url bmark", - "A": "bmark", - // Special keys must be prepended with 🄰 - // ["🄰Backspace", "something"], -} +import * as config from '../config' -let nmaps = Object.assign(Object.create(null), DEFAULTNMAPS) +let nmaps = config.get("nmaps") -// Allow config to be changed in settings -// TODO: make this more general -browser.storage.sync.get("nmaps").then(lazyloadconfig) -async function lazyloadconfig(storageResult){ - nmaps = Object.assign(Object.create(null), DEFAULTNMAPS, storageResult.nmaps) - console.log(nmaps) -} - -browser.storage.onChanged.addListener( - (changes, areaname) => { - if (areaname == "sync") { - browser.storage.sync.get("nmaps").then(lazyloadconfig) - } - }) // Split a string into a number prefix and some following keys. function keys_split_count(keys: string[]){ @@ -101,7 +23,7 @@ function keys_split_count(keys: string[]){ // Given a valid keymap, resolve it to an ex_str function resolve_map(map) { // TODO: This needs to become recursive to allow maps to be defined in terms of other maps. - return nmaps[map] + return config.get("nmaps")[map] } // Valid keystr to ex_str by splitting count, resolving keystr and appending count as final argument. @@ -121,7 +43,7 @@ function possible_maps(keys): string[] { let [count, keystr] = keys_split_count(keys) // Short circuit or search maps. - if (Object.getOwnPropertyNames(nmaps).includes(keystr)) { + if (Object.getOwnPropertyNames(config.get("nmaps")).includes(keystr)) { return [keystr,] } else { // Efficiency: this can be short-circuited. @@ -131,7 +53,7 @@ function possible_maps(keys): string[] { // A list of maps that start with the fragment. export function completions(fragment): string[] { - let posskeystrs = Array.from(Object.keys(nmaps)) + let posskeystrs = Array.from(Object.keys(config.get("nmaps"))) return posskeystrs.filter((key)=>key.startsWith(fragment)) } From f23118e166ec10dc64b104d3244c9b466f541167 Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 16:40:16 +0000 Subject: [PATCH 37/45] Fix #60: allow users to change default search engine --- src/config.ts | 11 ++++++----- src/excmds.ts | 10 +++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/config.ts b/src/config.ts index 9a8ab73d..5adc022f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -57,8 +57,8 @@ const DEFAULTS = o({ "gu": "urlparent", "gU": "urlroot", ":": "fillcmdline", - "s": "fillcmdline open google", - "S": "fillcmdline tabopen google", + "s": "fillcmdline open search", + "S": "fillcmdline tabopen search", "M": "gobble 1 quickmark", "xx": "something", // "B": "fillcmdline bufferall", @@ -75,7 +75,8 @@ const DEFAULTS = o({ "I": "mode ignore", "a": "current_url bmark", "A": "bmark", - }) + }), + "search_engine": "google", }) // currently only supports 2D or 1D storage @@ -89,7 +90,7 @@ export function get(target, property?){ } // god knows what happens if it isn't an object if (typeof DEFAULTS[target] === "object") return Object.assign(DEFAULTS[target],USERCONFIG[target]) - else return DEFAULTS[target] || USERCONFIG[target] + else return USERCONFIG[target] || DEFAULTS[target] } // if you don't specify a property and you should, this will wipe everything @@ -115,6 +116,6 @@ browser.storage.sync.get(CONFIGNAME).then(settings => { }) function schlepp(settings){ - // "Import" is a reserved so this will have to do + // "Import" is a reserved word so this will have to do Object.assign(USERCONFIG,settings[CONFIGNAME]) } diff --git a/src/excmds.ts b/src/excmds.ts index 1b8e76d2..15748648 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -68,6 +68,8 @@ import * as CommandLineBackground from './commandline_background' //#content_helper import * as DOM from './dom' +import * as config from './config' + /** @hidden */ //#background_helper @@ -106,6 +108,7 @@ function hasScheme(uri: string) { /** @hidden */ function searchURL(provider: string, query: string) { + if (provider == "search") provider = config.get("search_engine") if (SEARCH_URLS.has(provider)) { const url = new URL(SEARCH_URLS.get(provider) + encodeURIComponent(query)) // URL constructor doesn't convert +s because they're valid literals in @@ -148,8 +151,8 @@ function forceURI(maybeURI: string): string { if (e.name !== 'TypeError') throw e } - // Else search google - return searchURL('google', maybeURI).href + // Else search $search_engine + return searchURL('search', maybeURI).href } /** @hidden */ @@ -1024,9 +1027,6 @@ export async function quickmark(key: string) { await bind("gw" + key, "winopen", address) } -//#background_helper -import * as config from './config' - //#background export function get(target: string, property?: string){ console.log(config.get(target,property)) From 712037ae79eee23c767e4ab0722a69ad3fa34e44 Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 16:56:56 +0000 Subject: [PATCH 38/45] Allow users to save configuration via command line --- src/config.ts | 10 +++++++--- src/excmds.ts | 14 +++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/config.ts b/src/config.ts index 5adc022f..d3568e14 100644 --- a/src/config.ts +++ b/src/config.ts @@ -77,6 +77,7 @@ const DEFAULTS = o({ "A": "bmark", }), "search_engine": "google", + "storage_location": "sync", }) // currently only supports 2D or 1D storage @@ -103,11 +104,14 @@ export function set(target, value, property?){ } export async function save(storage: "local" | "sync" = "sync"){ - let storageobj = storage == "local" ? browser.storage.local : browser.storage.sync - storageobj.set({CONFIGNAME: USERCONFIG}).then(schlepp) + // let storageobj = storage == "local" ? browser.storage.local : browser.storage.sync + // storageobj.set({CONFIGNAME: USERCONFIG}) + let settingsobj = o({}) + settingsobj[CONFIGNAME] = USERCONFIG + if (storage == "local") browser.storage.local.set(settingsobj) + else browser.storage.sync.set(settingsobj) } - // Read all user configuration on start // Local storage overrides sync browser.storage.sync.get(CONFIGNAME).then(settings => { diff --git a/src/excmds.ts b/src/excmds.ts index 15748648..9f16ea59 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -1034,7 +1034,8 @@ export function get(target: string, property?: string){ //#background export function set(target: string, value: string){ - config.set(target,value) + // We don't support setting objects yet + if (target != "nmaps") config.set(target,value) } //#background @@ -1043,6 +1044,17 @@ export function bind2(key: string, ...bindarr: string[]){ config.set("nmaps",exstring,key) } +//#background +export function saveconfig(){ + config.save(config.get("storage_location")) +} + +//#background +export function mktridactylrc(){ + saveconfig() +} + + // }}} // {{{ HINTMODE From 6ae704876c23876c51975ed973fc85b5eb24ba8b Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 18:57:04 +0000 Subject: [PATCH 39/45] Add unset for resetting a bind to default --- src/config.ts | 9 +++++++-- src/excmds.ts | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/config.ts b/src/config.ts index d3568e14..7cc4c265 100644 --- a/src/config.ts +++ b/src/config.ts @@ -89,8 +89,7 @@ export function get(target, property?){ } else return DEFAULTS[target][property] } - // god knows what happens if it isn't an object - if (typeof DEFAULTS[target] === "object") return Object.assign(DEFAULTS[target],USERCONFIG[target]) + if (typeof DEFAULTS[target] === "object") return Object.assign(o({}),DEFAULTS[target],USERCONFIG[target]) else return USERCONFIG[target] || DEFAULTS[target] } @@ -103,6 +102,12 @@ export function set(target, value, property?){ USERCONFIG[target] = value } +export function unset(target, property?){ + if (property !== undefined){ + delete USERCONFIG[target][property] + } else delete USERCONFIG[target] +} + export async function save(storage: "local" | "sync" = "sync"){ // let storageobj = storage == "local" ? browser.storage.local : browser.storage.sync // storageobj.set({CONFIGNAME: USERCONFIG}) diff --git a/src/excmds.ts b/src/excmds.ts index 9f16ea59..c246db67 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -1044,6 +1044,11 @@ export function bind2(key: string, ...bindarr: string[]){ config.set("nmaps",exstring,key) } +//#background +export function unset(target: string, property?: string){ + config.unset(target,property) +} + //#background export function saveconfig(){ config.save(config.get("storage_location")) From 7713d5e5ff95a3f77dce14772761a96964d670aa Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 19:51:18 +0000 Subject: [PATCH 40/45] Make all changes to config persistent, import legacy binds --- src/config.ts | 30 ++++++++++++++++++++---------- src/excmds.ts | 37 ++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/config.ts b/src/config.ts index 7cc4c265..ad094699 100644 --- a/src/config.ts +++ b/src/config.ts @@ -76,8 +76,8 @@ const DEFAULTS = o({ "a": "current_url bmark", "A": "bmark", }), - "search_engine": "google", - "storage_location": "sync", + "searchengine": "google", + "storageloc": "sync", }) // currently only supports 2D or 1D storage @@ -97,15 +97,17 @@ export function get(target, property?){ export function set(target, value, property?){ if (property !== undefined){ if (USERCONFIG[target] === undefined) USERCONFIG[target] = o({}) - return USERCONFIG[target][property] = value - } - USERCONFIG[target] = value + USERCONFIG[target][property] = value + } else USERCONFIG[target] = value + // Always save + save(get("storageloc")) } export function unset(target, property?){ if (property !== undefined){ delete USERCONFIG[target][property] } else delete USERCONFIG[target] + save(get("storageloc")) } export async function save(storage: "local" | "sync" = "sync"){ @@ -118,13 +120,21 @@ export async function save(storage: "local" | "sync" = "sync"){ } // Read all user configuration on start -// Local storage overrides sync -browser.storage.sync.get(CONFIGNAME).then(settings => { - schlepp(settings) - browser.storage.local.get(CONFIGNAME).then(schlepp) +// Legacy config gets loaded first +let legacy_nmaps = {} +browser.storage.sync.get("nmaps").then(nmaps => { + legacy_nmaps = nmaps["nmaps"] + browser.storage.sync.get(CONFIGNAME).then(settings => { + schlepp(settings[CONFIGNAME]) + // Local storage overrides sync + browser.storage.local.get(CONFIGNAME).then(settings => { + schlepp(settings[CONFIGNAME]) + USERCONFIG["nmaps"] = Object.assign(legacy_nmaps, USERCONFIG["nmaps"]) + }) + }) }) function schlepp(settings){ // "Import" is a reserved word so this will have to do - Object.assign(USERCONFIG,settings[CONFIGNAME]) + Object.assign(USERCONFIG,settings) } diff --git a/src/excmds.ts b/src/excmds.ts index c246db67..2cb63f37 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -108,7 +108,7 @@ function hasScheme(uri: string) { /** @hidden */ function searchURL(provider: string, query: string) { - if (provider == "search") provider = config.get("search_engine") + if (provider == "search") provider = config.get("searchengine") if (SEARCH_URLS.has(provider)) { const url = new URL(SEARCH_URLS.get(provider) + encodeURIComponent(query)) // URL constructor doesn't convert +s because they're valid literals in @@ -151,7 +151,7 @@ function forceURI(maybeURI: string): string { if (e.name !== 'TypeError') throw e } - // Else search $search_engine + // Else search $searchengine return searchURL('search', maybeURI).href } @@ -972,12 +972,9 @@ export async function buffer(index: number | '#') { - [[reset]] */ //#background -export async function bind(key: string, ...bindarr: string[]){ +export function bind(key: string, ...bindarr: string[]){ let exstring = bindarr.join(" ") - let nmaps = (await browser.storage.sync.get("nmaps"))["nmaps"] - nmaps = (nmaps == undefined) ? Object.create(null) : nmaps - nmaps[key] = exstring - browser.storage.sync.set({nmaps}) + config.set("nmaps",exstring,key) } /** Unbind a sequence of keys so that they do nothing at all. @@ -1001,6 +998,9 @@ export async function unbind(key: string){ */ //#background export async function reset(key: string){ + config.unset("nmaps",key) + + // Code for dealing with legacy binds let nmaps = (await browser.storage.sync.get("nmaps"))["nmaps"] nmaps = (nmaps == undefined) ? {} : nmaps delete nmaps[key] @@ -1038,26 +1038,21 @@ export function set(target: string, value: string){ if (target != "nmaps") config.set(target,value) } -//#background -export function bind2(key: string, ...bindarr: string[]){ - let exstring = bindarr.join(" ") - config.set("nmaps",exstring,key) -} - //#background export function unset(target: string, property?: string){ config.unset(target,property) } -//#background -export function saveconfig(){ - config.save(config.get("storage_location")) -} +// not required as we automatically save all config +////#background +//export function saveconfig(){ +// config.save(config.get("storageloc")) +//} -//#background -export function mktridactylrc(){ - saveconfig() -} +////#background +//export function mktridactylrc(){ +// saveconfig() +//} // }}} From bd6d3d4bb6cffa11c4f05ee010e14e76c06bca6e Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 20:13:40 +0000 Subject: [PATCH 41/45] Add configuration options for hinting and documentation --- src/config.ts | 5 +++-- src/excmds.ts | 19 +++++++++++++++++-- src/hinting.ts | 18 +++++++++++------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/config.ts b/src/config.ts index ad094699..d7cb947f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,6 +17,7 @@ function o(object){ return Object.assign(Object.create(null),object) } +// TODO: have list of possibilities for settings, e.g. hintmode: reverse | normal let USERCONFIG = o({}) const DEFAULTS = o({ "nmaps": o({ @@ -25,7 +26,6 @@ const DEFAULTS = o({ "w": "fillcmdline winopen", "W": "current_url winopen", "t": "fillcmdline tabopen", - //["t": "fillcmdline tabopen", // for now, use mozilla completion "]]": "followpage next", "[[": "followpage prev", "[c": "urlincrement -1", @@ -78,11 +78,12 @@ const DEFAULTS = o({ }), "searchengine": "google", "storageloc": "sync", + "hintchars": "hjklasdfgyuiopqwertnmzxcvb", + "hintorder": "normal", }) // currently only supports 2D or 1D storage export function get(target, property?){ - console.log(DEFAULTS) if (property !== undefined){ if (USERCONFIG[target] !== undefined){ return USERCONFIG[target][property] || DEFAULTS[target][property] diff --git a/src/excmds.ts b/src/excmds.ts index 2cb63f37..91352ea6 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -250,6 +250,9 @@ export async function reloadhard(n = 1) { - else if the first word contains a dot, treat as a domain name - else if the first word is a key of [[SEARCH_URLS]], treat all following terms as search parameters for that provider - else treat as search parameters for google + + Related settings: + "searchengine": "google" or any of [[SEARCH_URLS]] */ //#content export function open(...urlarr: string[]) { @@ -1032,10 +1035,18 @@ export function get(target: string, property?: string){ console.log(config.get(target,property)) } +/** Set a setting to a value + + Currently, this only supports string settings without any whitespace + (i.e. not nmaps.) + + It can be used on any string <-> string settings found [here](/static/docs/modules/_config_.html#defaults) + +*/ //#background -export function set(target: string, value: string){ +export function set(setting: string, value: string){ // We don't support setting objects yet - if (target != "nmaps") config.set(target,value) + if (setting != "nmaps") config.set(setting,value) } //#background @@ -1074,6 +1085,10 @@ import * as hinting from './hinting_background' - -# yank an element's anchor URL to clipboard - -c [selector] hint links that match the css selector - `bind ;c hint -c [class*="expand"],[class="togg"]` works particularly well on reddit and HN + + Related settings: + "hintchars": "hjklasdfgyuiopqwertnmzxcvb" + "hintorder": "normal" or "reverse" */ //#background export function hint(option?: string, selectors="") { diff --git a/src/hinting.ts b/src/hinting.ts index 85ea32b6..25756310 100644 --- a/src/hinting.ts +++ b/src/hinting.ts @@ -16,6 +16,7 @@ import {permutationsWithReplacement, islice, izip, map} from './itertools' import {hasModifiers} from './keyseq' import state from './state' import {messageActiveTab} from './messaging' +import * as config from './config' /** Simple container for the state of a single frame's hints. */ class HintState { @@ -63,16 +64,19 @@ export function hintPage( } /** vimperator-style minimal hint names */ -function* hintnames(hintchars = HINTCHARS): IterableIterator { +function* hintnames(hintchars = config.get("hintchars")): IterableIterator { let taglen = 1 while (true) { - yield* map(permutationsWithReplacement(hintchars, taglen), e=>e.join('')) + yield* map(permutationsWithReplacement(hintchars, taglen), e=>{ + if (config.get("hintorder") == "reverse") e = e.reverse() + return e.join('') + }) taglen++ } } /** Uniform length hintnames */ -function* hintnames_uniform(n: number, hintchars = HINTCHARS): IterableIterator { +function* hintnames_uniform(n: number, hintchars = config.get("hintchars")): IterableIterator { if (n <= hintchars.length) yield* islice(hintchars[Symbol.iterator](), n) else { @@ -80,7 +84,10 @@ function* hintnames_uniform(n: number, hintchars = HINTCHARS): IterableIterator< const taglen = Math.ceil(log(n, hintchars.length)) // And return first n permutations yield* map(islice(permutationsWithReplacement(hintchars, taglen), n), - perm => perm.join('')) + perm => { + if (config.get("hintorder") == "reverse") perm = perm.reverse() + return perm.join('') + }) } } @@ -136,9 +143,6 @@ class Hint { } } -const HINTCHARS = 'hjklasdfgyuiopqwertnmzxcvb' -/* const HINTCHARS = 'asdf' */ - /** Show only hints prefixed by fstr. Focus first match */ function filter(fstr) { const active: Hint[] = [] From 2acfa3fbb0af975688133f40939461d3d2ef799d Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 21:38:30 +0000 Subject: [PATCH 42/45] set [setting] without a value will inform you of the current value --- src/excmds.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/excmds.ts b/src/excmds.ts index 91352ea6..b7a93e73 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -1044,9 +1044,13 @@ export function get(target: string, property?: string){ */ //#background -export function set(setting: string, value: string){ +export function set(setting: string, value?: string){ // We don't support setting objects yet - if (setting != "nmaps") config.set(setting,value) + if (setting != "nmaps"){ + if (value !== undefined){ + config.set(setting,value) + } else fillcmdline_notrail("set " + setting + " " + config.get(setting)) + } } //#background From 19d03e66b36553d9f6c297dcce1c112d95788e86 Mon Sep 17 00:00:00 2001 From: Oliver Blanthorn Date: Wed, 29 Nov 2017 21:48:46 +0000 Subject: [PATCH 43/45] Fix bug where some settings would not change until page reload --- src/config.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/config.ts b/src/config.ts index d7cb947f..4fd097bb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -139,3 +139,11 @@ function schlepp(settings){ // "Import" is a reserved word so this will have to do Object.assign(USERCONFIG,settings) } + +browser.storage.onChanged.addListener( + (changes, areaname) => { + if (CONFIGNAME in changes) { + Object.assign(USERCONFIG, changes[CONFIGNAME].newValue) + } + } +) From 733477771444ce3777c3a375d080f17941fab080 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Wed, 29 Nov 2017 23:47:40 +0000 Subject: [PATCH 44/45] completions: backport improved history --- src/completions.ts | 97 ++++++++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/src/completions.ts b/src/completions.ts index a6de189b..bbef1635 100644 --- a/src/completions.ts +++ b/src/completions.ts @@ -13,6 +13,7 @@ import * as Fuse from 'fuse.js' import {enumerate} from './itertools' import {toNumber} from './convert' import * as Messaging from './messaging' +import {browserBg} from './lib/webext' const DEFAULT_FAVICON = browser.extension.getURL("static/defaultFavicon.svg") @@ -55,9 +56,7 @@ export abstract class CompletionSource { return this._state } - next(inc = 1): boolean { - return false - } + abstract next(inc?: number): boolean prev(inc = 1): boolean { return this.next(-1*inc) @@ -319,8 +318,9 @@ class HistoryCompletionOption extends CompletionOptionHTML implements Completion constructor(public value: string, page: browser.history.HistoryItem) { super() - // Two character buffer properties prefix - // Push prefix before padding so we don't match on whitespace + if (! page.title) { + page.title = new URL(page.url).host + } // Push properties we want to fuzmatch on this.fuseKeys.push(page.title, page.url) // weight by page.visitCount @@ -346,11 +346,6 @@ function sleep(ms: number) { export class HistoryCompletionSource extends CompletionSourceFuse { public options: HistoryCompletionOption[] - // TODO: - // - store the exstr and trigger redraws on user or data input without - // callback faffery - // - sort out the element redrawing. - constructor(private _parent) { super( [ @@ -361,38 +356,74 @@ export class HistoryCompletionSource extends CompletionSourceFuse { "HistoryCompletionSource", "History" ) - this.updateOptions() this._parent.appendChild(this.node) } - private async updateOptions(exstr?: string) { - /* console.log('updateOptions', this.optionContainer) */ - // this sleep stops input from being blocked, but also breaks :open until something is typed - // await sleep(0) - const history: browser.history.HistoryItem[] = - await Messaging.message("commandline_background", "history") + public async filter(exstr: string) { + this.lastExstr = exstr + const [prefix, query] = this.splitOnPrefix(exstr) - const options = [] - - // Get alternative tab, defined as last accessed tab. - history.sort((a, b) => { return a.lastVisitTime < b.lastVisitTime ? 1 : -1 }) - - for (const page of history) { - options.push(new HistoryCompletionOption( - page.url, - page, - )) + // Hide self and stop if prefixes don't match + if (prefix) { + // Show self if prefix and currently hidden + if (this.state === 'hidden') { + this.state = 'normal' + } + } else { + this.state = 'hidden' + return } - /* console.log('updateOptions end', this.waiting, this.optionContainer) */ - this.options = options + this.options = (await this.scoreOptions(query, 10)).map( + page => new HistoryCompletionOption(page.url, page) + ) + this.updateChain() } - async onInput(exstr) { - // Schedule an update, if you like. Not very useful for buffers, but - // will be for other things. - this.updateOptions() + updateChain() { + // Options are pre-trimmed to the right length. + this.options.forEach(option => option.state = 'normal') + + // Call concrete class + this.updateDisplay() + } + + onInput() {} + + private frecency(item: browser.history.HistoryItem) { + // Doesn't actually care about recency yet. + return item.visitCount * -1 + } + + private async scoreOptions(query: string, n: number) { + if (! query) { + return (await browserBg.topSites.get()).slice(0, n) + } else { + // Search history, dedupe and sort by frecency + let history = await browserBg.history.search({ + text: query, + maxResults: 500, + startTime: 0 + }) + + // Remove entries with duplicate URLs + const dedupe = new Map() + for (const page of history) { + if (dedupe.has(page.url)) { + if (dedupe.get(page.url).title.length < page.title.length) { + dedupe.set(page.url, page) + } + } else { + dedupe.set(page.url, page) + } + } + history = [...dedupe.values()] + + history.sort((a, b) => this.frecency(a) - this.frecency(b)) + + return history.slice(0, n) + } } } From 51d725827352c4e877e7e6c447d2350c4e6aeea4 Mon Sep 17 00:00:00 2001 From: Colin Caine Date: Thu, 30 Nov 2017 00:24:00 +0000 Subject: [PATCH 45/45] completions: improve css Stop long titles appearing after URLs --- src/static/commandline.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/static/commandline.css b/src/static/commandline.css index 7fb1fff0..2975c46d 100644 --- a/src/static/commandline.css +++ b/src/static/commandline.css @@ -66,6 +66,10 @@ input { overflow: hidden; } +#completions table tr td { + overflow: hidden; +} + #completions img { display: inline; vertical-align: middle;