Merge branch 'master' of github.com:cmcaine/tridactyl

This commit is contained in:
Oliver Blanthorn 2017-11-20 17:00:04 +00:00
commit 844feb4edf
No known key found for this signature in database
GPG key ID: 2BB8C36BB504BFF3
23 changed files with 422 additions and 174 deletions

2
package-lock.json generated
View file

@ -8612,7 +8612,7 @@
}
},
"web-ext-types": {
"version": "github:michael-zapata/web-ext-types#d3dac19b70dde1ea0162340a481d63ffb78e5a96",
"version": "github:kelseasy/web-ext-types#30d79e893b7a30d3fbfd8aa0affedaa8dca0d211",
"dev": true
},
"webidl-conversions": {

View file

@ -20,7 +20,7 @@
"uglify-es": "^3.1.5",
"uglifyjs-webpack-plugin": "^1.0.0-rc.0",
"web-ext": "^1.8.1",
"web-ext-types": "github:michael-zapata/web-ext-types",
"web-ext-types": "github:kelseasy/web-ext-types",
"webpack": "^3.8.1"
},
"scripts": {

View file

@ -1,4 +1,4 @@
#!/usr/bin/env bash
shopt -s globstar
sed -i '/<\/body>/s/^/<script src="..\/..\/..\/content.js"><\/script><link rel="stylesheet" href="..\/..\/content.css"><link rel="stylesheet" href="..\/..\/hint.css">/' $1
sed -i '/<\/body>/s@^@<script src="/content.js"></script><link rel="stylesheet" href="/static/content.css"><link rel="stylesheet" href="/static/hint.css">@' $1
#static/docs/modules/_excmds_.html

View file

@ -38,7 +38,10 @@ class Signature:
# Type declaration
if ':' in param:
name, typ = map(str.strip, param.split(':'))
if typ not in ('number', 'boolean', 'string', 'string[]', 'ModeName') and '|' not in typ:
if (typ not in ('number', 'boolean', 'string', 'string[]', 'ModeName')
and '|' not in typ
and typ[0] not in ['"',"'"]
):
raise Exception("Edit me! " + typ + " is not a supported type!")
# Default argument
elif '=' in param:

View file

@ -1,5 +1,5 @@
#!/bin/sh
dest=generated/static/docs
typedoc --out $dest src --ignoreCompilerErrors
./scripts/commandline_injector.sh $dest/modules/_excmds_.html
find $dest -name *.html -exec ./scripts/commandline_injector.sh '{}' \;
cp -r $dest build/static/

View file

@ -5,6 +5,9 @@ const {exec} = require('child_process')
function bump_version(versionstr, component = 2) {
const versionarr = versionstr.split('.')
versionarr[component] = Number(versionarr[component]) + 1
for (let smaller = component + 1; smaller <= 2; smaller++) {
versionarr[smaller] = 0
}
return versionarr.join('.')
}

View file

@ -3,6 +3,7 @@
import * as Controller from "./controller"
import * as keydown_background from "./keydown_background"
import * as CommandLine from "./commandline_background"
import './lib/browser_proxy_background'
// Send keys to controller
keydown_background.onKeydown.addListener(Controller.acceptKey)
@ -24,6 +25,7 @@ import * as itertools from './itertools'
import * as keyseq from './keyseq'
import * as msgsafe from './msgsafe'
import * as state from './state'
import * as webext from './lib/webext'
(window as any).tri = Object.assign(Object.create(null), {
messaging,
@ -38,4 +40,6 @@ import * as state from './state'
keyseq,
msgsafe,
state,
webext,
l: prom => prom.then(console.log).catch(console.error),
})

View file

@ -101,10 +101,16 @@ function process() {
console.log(clInput.value)
sendExstr("hidecmdline")
sendExstr(clInput.value)
if (! browser.extension.inIncognitoContext) {
// Save non-secret commandlines to the history.
const [func,...args] = clInput.value.trim().split(/\s+/)
if (! browser.extension.inIncognitoContext &&
! (func === 'winopen' && args[0] === '-private')
) {
state.cmdHistory = state.cmdHistory.concat([clInput.value])
}
console.log(state.cmdHistory)
completions.innerHTML = ""
clInput.value = ""
cmdline_history_position = 0

View file

@ -3,9 +3,38 @@
// Be careful: typescript elides imports that appear not to be used if they're
// assigned to a name. If you want an import just for its side effects, make
// sure you import it like this:
import "./keydown_content"
import "./commandline_content"
import "./excmds_content"
import "./hinting"
/* import "./keydown_content" */
/* import "./commandline_content" */
/* import "./excmds_content" */
/* import "./hinting" */
console.log("Tridactyl content script loaded, boss!")
// Add various useful modules to the window for debugging
import * as commandline_content from './commandline_content'
import * as convert from './convert'
import * as dom from './dom'
import * as excmds from './excmds_content'
import * as hinting_content from './hinting'
import * as itertools from './itertools'
import * as keydown_content from "./keydown_content"
import * as messaging from './messaging'
import * as msgsafe from './msgsafe'
import state from './state'
import * as webext from './lib/webext'
(window as any).tri = Object.assign(Object.create(null), {
browserBg: webext.browserBg,
commandline_content,
convert,
dom,
excmds,
hinting_content,
itertools,
keydown_content,
messaging,
msgsafe,
state,
webext,
l: prom => prom.then(console.log).catch(console.error),
})

View file

@ -27,7 +27,7 @@ function *ParserController () {
let keypress = keyevent.key
// TODO: think about if this is robust
if (state.mode != "ignore"){
if (state.mode != "ignore" && state.mode != "hint") {
if (isTextEditable(keyevent.target)) {
state.mode = "insert"
} else if (state.mode === 'insert') {
@ -40,7 +40,7 @@ function *ParserController () {
// yet. So drop them. This also drops all modifier keys.
// When we put in handling for other special keys, remember
// to continue to ban modifiers.
if (state.mode === 'normal' && (keypress.length > 1 || keyevent.ctrlKey || keyevent.altKey)) {
if (state.mode === 'normal' && (keypress.length > 1 || keyevent.ctrlKey || keyevent.altKey || keyevent.metaKey)) {
continue
}

View file

@ -1,5 +1,39 @@
// '//#' is a start point for a simple text-replacement-type macro. See excmds_macros.py
/** # Tridactyl help page
Use `:help <excmd>` or scroll down to show [[help]] for a particular excmd.
Tridactyl is in a pretty early stage of development. Please report any issues and make requests for missing features on the GitHub project page [[1]].
Highlighted features:
- Press `b` to bring up a list of open tabs in the current window; you can type the tab ID or part of the title or URL to choose a tab (the buffer list doesn't show which one you've selected yet, but it does work)
- Press `I` to enter ignore mode. `Shift` + `Escape` to return to normal mode.
- Press `f` to start "hint mode", `F` to open in background
- Press `o` to `:open` a different page
- Press `s` if you want to search for something that looks like a domain name or URL
- [[bind]] new commands with e.g. `:bind J tabnext`
- Type `:help` to see a list of available excmds
- Use `yy` to copy the current page URL to your clipboard
- `]]` and `[[` to navigate through the pages of comics, paginated articles, etc
- Pressing `ZZ` will close all tabs and windows, but it will only "save" them if your about:preferences are set to "show your tabs and windows from last time"
There are some caveats common to all webextension vimperator-alikes:
- Do not try to navigate to any about:\* pages using `:open` as it will fail silently
- Firefox will not load Tridactyl on addons.mozilla.org, about:\*, some file:\* URIs, view-source:\*, or data:\*. On these pages Ctrl-L (or F6), Ctrl-Tab and Ctrl-W are your escape hatches
- Tridactyl does not currently support changing/hiding the Firefox GUI, but you can do it yourself by changing your userChrome. There is an example file available on our repository [[2]]
If you want a more fully-featured vimperator-alike, your best option is Firefox ESR [[3]] and Vimperator :)
[1]: https://github.com/cmcaine/tridactyl/issues
[2]: https://github.com/cmcaine/tridactyl/blob/master/src/static/userChrome-minimal.css
[3]: https://www.mozilla.org/en-US/firefox/organizations/
*/
/** ignore this line */
// {{{ setup
import * as Messaging from "./messaging"
@ -32,7 +66,6 @@ import {activeTab, activeTabId} from './lib/webext'
//#background_helper
export const cmd_params = new Map<string, Map<string, string>>()
/** @hidden */
const SEARCH_URLS = new Map<string, string>([
["google","https://www.google.com/search?q="],
["googleuk","https://www.google.co.uk/search?q="],
@ -40,32 +73,65 @@ const SEARCH_URLS = new Map<string, string>([
["duckduckgo","https://duckduckgo.com/?q="],
["yahoo","https://search.yahoo.com/search?p="],
["twitter","https://twitter.com/search?q="],
["wikipedia","https://en.wikipedia.org/wiki/"],
["wikipedia","https://en.wikipedia.org/wiki/Special:Search/"],
["youtube","https://www.youtube.com/results?search_query="],
["amazon","https://www.amazon.com/s/ref=nb_sb_noss?url=search-alias%3Daps&field-keywords="],
["amazonuk","https://www.amazon.co.uk/s/ref=nb_sb_noss?url=search-alias%3Daps&field-keywords="],
["startpage","https://www.startpage.com/do/search?query="],
])
/** @hidden */
function hasScheme(uri: string) {
return uri.match(/^(\w+):/)
return uri.match(/^([\w-]+):/)
}
/** We use this over encodeURIComponent so that '+'s in non queries are not encoded. */
/** @hidden */
function searchURL(provider: string, query: string) {
if (SEARCH_URLS.has(provider)) {
const url = new URL(SEARCH_URLS.get(provider) + query)
// URL constructor doesn't convert +s because they're valid literals in
// the standard it adheres to. But they are special characters in
// x-www-form-urlencoded and e.g. google excepts query parameters in
// that format.
url.search = url.search.replace(/\+/g, '%2B')
return url
} else {
throw new TypeError(`Unknown provider: '${provider}'`)
}
}
/** If maybeURI doesn't have a schema, affix http:// */
/** @hidden */
function forceURI(maybeURI: string) {
if (hasScheme(maybeURI)) {
return maybeURI
function forceURI(maybeURI: string): string {
try {
return new URL(maybeURI).href
} catch (e) {
if (e.name !== 'TypeError') throw e
}
let urlarr = maybeURI.split(" ")
if (SEARCH_URLS.get(urlarr[0]) != null){
return SEARCH_URLS.get(urlarr[0]) + urlarr.slice(1,urlarr.length).join(" ")
} else if (urlarr[0].includes('.')) {
return "http://" + maybeURI
} else {
return SEARCH_URLS.get("google") + maybeURI
// Else if search keyword:
try {
const args = maybeURI.split(' ')
return searchURL(args[0], args.slice(1).join(' ')).href
} catch (e) {
console.log(e)
if (e.name !== 'TypeError') throw e
}
// Else if it's a domain or something
try {
const url = new URL('http://' + maybeURI)
// Ignore unlikely domains
if (url.hostname.includes('.') || url.port || url.password) {
return url.href
}
} catch (e) {
if (e.name !== 'TypeError') throw e
}
// Else search google
return searchURL('google', maybeURI).href
}
/** @hidden */
@ -121,11 +187,13 @@ function history(n: number) {
window.history.go(n)
}
/** Navigate forward one page in history. */
//#content
export function forward(n = 1) {
history(n)
}
/** Navigate back one page in history. */
//#content
export function back(n = 1) {
history(n * -1)
@ -145,18 +213,35 @@ export async function reloadhard(n = 1) {
reload(n, true)
}
/** Open a new page in the current tab.
@param urlarr
- if first word looks like it has a schema, treat as a URI
- 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
*/
//#content
export function open(...urlarr: string[]) {
let url = urlarr.join(" ")
window.location.href = forceURI(url)
}
//#background
export function help(...urlarr: string[]) {
let url = urlarr.join(" ")
// window.location.href = "docs/modules/_excmds_.html#" + url
browser.tabs.create({url: "static/docs/modules/_excmds_.html#" + url})
/** Show this page.
`:help <excmd>` jumps to the entry for that command.
e.g. `:help bind`
*/
//#background
export async function help(excmd?: string) {
const docpage = browser.extension.getURL("static/docs/modules/_excmds_.html#")
if (excmd === undefined) excmd = "tridactyl-help-page"
if ((await activeTab()).url.startsWith(docpage)) {
open(docpage + excmd)
} else {
tabopen(docpage + excmd)
}
}
/** @hidden */
@ -167,7 +252,7 @@ function getlinks(){
/** Find a likely next/previous link and follow it */
//#content
export function clicknext(dir = "next"){
export function clicknext(dir: "next"|"prev" = "next"){
let linkarray = Array.from(getlinks())
let regarray = [/\bnext|^>$|^(>>|»)$|^(>|»)|(>|»)$|\bmore\b/i, /\bprev\b|\bprevious\b|^<$|^(<<|«)$|^(<|«)|(<|«)$/i]
@ -218,8 +303,7 @@ export function tabprev(increment = 1) {
tabnext(increment * -1)
}
// TODO: address should default to some page to which we have access
// and focus the location bar
/** Like [[open]], but in a new tab */
//#background
export async function tabopen(...addressarr: string[]) {
let uri
@ -283,6 +367,7 @@ export async function tabmove(n?: string) {
browser.tabs.move(aTab.id, {index: m})
}
/** Pin the current tab */
//#background
export async function pin() {
let aTab = await activeTab()
@ -293,6 +378,7 @@ export async function pin() {
// {{{ WINDOWS
/** Like [[open]], but in a new window */
//#background
export async function winopen(...args: string[]) {
let address: string
@ -311,6 +397,7 @@ export async function winclose() {
}
/** Close all windows */
// It's unclear if this will leave a session that can be restored.
// We might have to do it ourselves.
//#background
@ -323,18 +410,27 @@ export async function qall(){
// {{{ MISC
/** Deprecated */
//#background
export function suppress(preventDefault?: boolean, stopPropagation?: boolean) {
keydown.suppress(preventDefault, stopPropagation)
mode("ignore")
}
/** Example:
- `mode ignore` to ignore all keys.
*/
//#background
export function mode(mode: ModeName) {
state.mode = mode
// TODO: event emition on mode change.
if (mode === "hint") {
hint()
} else {
state.mode = mode
}
}
//#background
export async function getnexttabs(tabid: number, n?: number) {
//#background_helper
async function getnexttabs(tabid: number, n?: number) {
const curIndex: number = (await browser.tabs.get(tabid)).index
const tabs: browser.tabs.Tab[] = await browser.tabs.query({
currentWindow: true,
@ -377,6 +473,20 @@ export async function getnexttabs(tabid: number, n?: number) {
// {{{ CMDLINE
//#background_helper
import * as controller from './controller'
/** Split `cmds` on pipes (|) and treat each as it's own command.
Workaround: this should clearly be in the parser, but we haven't come up with a good way to deal with |s in URLs, search terms, etc. yet.
*/
//#background
export function composite(...cmds: string[]) {
cmds = cmds.join(" ").split("|")
cmds.forEach(controller.acceptExCmd)
}
/** Don't use this */
// TODO: These two don't really make sense as excmds, they're internal things.
//#content
export function showcmdline() {
@ -384,13 +494,14 @@ export function showcmdline() {
CommandLineContent.focus()
}
/** Don't use this */
//#content
export function hidecmdline() {
CommandLineContent.hide()
CommandLineContent.blur()
}
/** Set the current value of the commandline to string */
/** Set the current value of the commandline to string *with* a trailing space */
//#background
export function fillcmdline(...strarr: string[]) {
let str = strarr.join(" ")
@ -398,6 +509,7 @@ export function fillcmdline(...strarr: string[]) {
messageActiveTab("commandline_frame", "fillcmdline", [str])
}
/** Set the current value of the commandline to string *without* a trailing space */
//#background
export function fillcmdline_notrail(...strarr: string[]) {
let str = strarr.join(" ")
@ -406,13 +518,27 @@ export function fillcmdline_notrail(...strarr: string[]) {
messageActiveTab("commandline_frame", "fillcmdline", [str, trailspace])
}
/** Equivalent to `fillcmdline_notrail <yourargs><current URL>`
See also [[fillcmdline_notrail]]
*/
//#background
export async function current_url(...strarr: string[]){
fillcmdline_notrail(...strarr, (await activeTab()).url)
}
/** Use the system clipboard.
If `excmd == "open"`, call [[open]] with the contents of the clipboard. Similarly for [[tabopen]].
If `excmd == "yank"`, copy the current URL, or if given, the value of toYank, into the system clipboard.
Unfortunately, javascript can only give us the `clipboard` clipboard, not e.g. the X selection clipboard.
*/
//#background
export async function clipboard(excmd = "open", content = ""){
export async function clipboard(excmd: "open"|"yank"|"tabopen" = "open", ...toYank: string[]) {
let content = toYank.join(" ")
let url = ""
switch (excmd) {
case 'yank':
@ -444,7 +570,7 @@ export async function clipboard(excmd = "open", content = ""){
//#background_helper
const DEFAULT_FAVICON = browser.extension.getURL("static/defaultFavicon.svg")
/** Buffer + autocompletions */
/** Soon to be deprecated way of showing buffer completions */
//#background
export async function openbuffer() {
fillcmdline("buffer")
@ -545,24 +671,56 @@ async function listTabs() {
// {{{ SETTINGS
/** Bind a sequence of keys to an excmd.
This is an easier-to-implement bodge while we work on vim-style maps.
Examples:
- `bind G fillcmdline tabopen google`
- `bind D composite tabclose | tabprev`
- `bind j scrollline 20`
- `bind F hint -b`
Use [[composite]] if you want to execute multiple excmds. Use
[[fillcmdline]] to put a string in the cmdline and focus the cmdline
(otherwise the string is executed immediately).
See also:
- [[unbind]]
- [[reset]]
*/
//#background
export async function bind(key: string, ...bindarr: string[]){
let exstring = bindarr.join(" ")
let nmaps = (await browser.storage.sync.get("nmaps"))["nmaps"]
nmaps = (nmaps == undefined) ? {} : nmaps
nmaps = (nmaps == undefined) ? Object.create(null) : nmaps
nmaps[key] = exstring
browser.storage.sync.set({nmaps})
}
/** Unbind a sequence of keys so that they do nothing at all.
See also:
- [[bind]]
- [[reset]]
*/
//#background
export async function unbind(key: string){
bind(key)
bind(key, "")
}
/* Currently, only resets key to default after extension is reloaded */
/** Restores a sequence of keys to their default value.
See also:
- [[bind]]
- [[unbind]]
*/
//#background
export async function reset(key: string){
bind(key)
let nmaps = (await browser.storage.sync.get("nmaps"))["nmaps"]
nmaps = (nmaps == undefined) ? {} : nmaps
delete nmaps[key]
@ -574,12 +732,13 @@ export async function reset(key: string){
// {{{ HINTMODE
//#background_helper
import {hintPageSimple} from './hinting_background'
import * as hinting from './hinting_background'
/** Hint a page. Pass -b as first argument to open hinted page in background. */
//#background
export function hint() {
hintPageSimple()
mode('hint')
export function hint(option?: "-b") {
if (option === '-b') hinting.hintPageOpenInBackground()
else hinting.hintPageSimple()
}

View file

@ -39,6 +39,7 @@ let modeState: HintState = undefined
/** For each hintable element, add a hint */
export function hintPage(hintableElements: Element[], onSelect: HintSelectedCallback) {
state.mode = 'hint'
modeState = new HintState()
for (let [el, name] of izip(hintableElements, hintnames())) {
modeState.hintchars += name
@ -128,7 +129,6 @@ const HINTCHARS = 'hjklasdfgyuiopqwertnmzxcvb'
/** Show only hints prefixed by fstr. Focus first match */
function filter(fstr) {
console.log(fstr)
const active: Hint[] = []
let foundMatch
for (let h of modeState.hints) {
@ -153,6 +153,7 @@ function filter(fstr) {
function reset() {
modeState.destructor()
modeState = undefined
state.mode = 'normal'
}
/** If key is in hintchars, add it to filtstr and filter */
@ -252,23 +253,61 @@ select,
[tabindex]
`
// DEBUGGING
/* hintPage(hintables(), hint=>mouseEvent(hint.target, 'click')) */
/* addEventListener('keydown', pushKey) */
import {activeTab, browserBg, l, firefoxVersionAtLeast} from './lib/webext'
function hintPageSimple() {
console.log("Hinting!")
async function openInBackground(url: string) {
const thisTab = await activeTab()
const options: any = {
active: false,
url,
index: thisTab.index + 1,
}
if (await l(firefoxVersionAtLeast(57))) options.openerTabId = thisTab.id
return browserBg.tabs.create(options)
}
/** if `target === _blank` clicking the link is treated as opening a popup and is blocked. Use webext API to avoid that. */
function simulateClick(target: HTMLElement) {
// target can be set to other stuff, and we'll fail in annoying ways.
// There's no easy way around that while this code executes outside of the
// magic 'short lived event handler' context.
//
// OTOH, hardly anyone uses that functionality any more.
if ((target as HTMLAnchorElement).target === '_blank' ||
(target as HTMLAnchorElement).target === '_new'
) {
browserBg.tabs.create({url: (target as HTMLAnchorElement).href})
} else {
mouseEvent(target, "click")
// Sometimes clicking the element doesn't focus it sufficiently.
target.focus()
}
}
function hintPageOpenInBackground() {
hintPage(hintables(), hint=>{
hint.target.focus()
mouseEvent(hint.target, 'click')
if (hint.target.href) {
// Try to open with the webext API. If that fails, simulate a click on this page anyway.
openInBackground(hint.target.href).catch(()=>simulateClick(hint.target))
} else {
// This is to mirror vimperator behaviour.
simulateClick(hint.target)
}
})
}
function hintPageSimple() {
hintPage(hintables(), hint=>{
simulateClick(hint.target)
})
}
function selectFocusedHint() {
console.log("Selecting hint.", state.mode)
state.mode = 'normal'
modeState.focusedHint.select()
const focused = modeState.focusedHint
reset()
focused.select()
}
import {addListener, attributeCaller} from './messaging'
@ -277,4 +316,5 @@ addListener('hinting_content', attributeCaller({
selectFocusedHint,
reset,
hintPageSimple,
hintPageOpenInBackground,
}))

View file

@ -16,22 +16,27 @@ export async function hintPageSimple() {
return await messageActiveTab('hinting_content', 'hintPageSimple')
}
export async function hintPageOpenInBackground() {
return await messageActiveTab('hinting_content', 'hintPageOpenInBackground')
}
import {MsgSafeKeyboardEvent} from './msgsafe'
/** At some point, this might be turned into a real keyseq parser
if Enter, select focusedHint and reset, or reset on Escape.
else give to the hintfilter
reset and selectFocusedHints are OK candidates for map targets in the
future. pushKey less so, I think.
*/
export function parser(keys: MsgSafeKeyboardEvent[]) {
console.log("hintparser", keys)
const key = keys[0].key
if (key === 'Enter' || key === 'Escape') {
if (key === 'Enter') selectFocusedHint()
if (key === 'Escape') {
reset()
return {keys: [], ex_str: 'mode normal'}
} else if (['Enter', ' '].includes(key)) {
selectFocusedHint()
} else {
pushKey(keys[0])
return {keys: [], ex_str: ''}
}
return {keys: [], ex_str: ''}
}

View file

@ -21,21 +21,6 @@ export function recvEvent(event: MsgSafeKeyboardEvent) {
}
}
// Sledgehammer keyevent suppression
export function suppress(pD?: boolean, sP?: boolean) {
if (pD !== undefined) { preventDefault = pD }
if (sP !== undefined) { stopPropagation = sP }
Messaging.messageAllTabs("keydown_content", "suppress", [preventDefault, stopPropagation])
}
export function state() {
return [preventDefault, stopPropagation]
}
// State
let preventDefault = false
let stopPropagation = false
// Get messages from content
import * as SELF from './keydown_background'
Messaging.addListener('keydown_background', Messaging.attributeCaller(SELF))

View file

@ -17,87 +17,62 @@ function keyeventHandler(ke: KeyboardEvent) {
Messaging.message("keydown_background", "recvEvent", [msgsafe.KeyboardEvent(ke)])
}
/** Choose to suppress a key or not based on module state */
/** Choose to suppress a key or not */
function suppressKey(ke: KeyboardEvent) {
// Silly way
if (preventDefault) ke.preventDefault()
if (stopPropagation) ke.stopPropagation()
// Mode specific suppression
TerribleModeSpecificSuppression(ke)
}
// Else if in known maps.
// StartsWith happens to work for our maps so far. Obviously won't in the future.
if ([...nmaps.keys()].find((map) => map.startsWith(ke.key))) {
ke.preventDefault()
ke.stopPropagation()
// {{{ Shitty key suppression workaround.
import state from './state'
// Keys not to suppress in normal mode.
const normalmodewhitelist = [
'/',
"'",
' ',
]
const hintmodewhitelist = [
'F3',
'F5',
'F12',
]
function TerribleModeSpecificSuppression(ke: KeyboardEvent) {
switch (state.mode) {
case "normal":
// StartsWith happens to work for our maps so far. Obviously won't in the future.
/* if (Object.getOwnPropertyNames(nmaps).find((map) => map.startsWith(ke.key))) { */
if (! ke.ctrlKey && ! ke.metaKey && ! ke.altKey
&& ke.key.length === 1
&& ! normalmodewhitelist.includes(ke.key)
) {
ke.preventDefault()
ke.stopImmediatePropagation()
}
break
case "hint":
if (! hintmodewhitelist.includes(ke.key)) {
ke.preventDefault()
ke.stopImmediatePropagation()
}
break;
case "ignore":
break;
case "insert":
break;
}
}
/** Suppressing all keys from page load means scrolling by arrow key doesn't work until page is reloaded with suppression off. IDK why. Maybe a bug? */
export function suppress(pD?: boolean, sP?: boolean) {
if (pD !== undefined) { preventDefault = pD }
if (sP !== undefined) { stopPropagation = sP }
console.log(pD, sP, preventDefault, stopPropagation)
}
/** UNUSED, hardcoded for now */
/* export function mapState(maps) { */
/* nmaps = maps */
/* } */
// State
let preventDefault = false
let stopPropagation = false
/* let nmaps: Map<string, string> */
const nmaps = new Map<string, string>([
["o", "fillcmdline open"],
["O", "current-url open"],
["w", "fillcmdline winopen"],
["W", "current-url winopen"],
["t", "tabopen"],
//["t", "fillcmdline tabopen"], // for now, use mozilla completion
["]]", "clicknext"],
["[[", "clicknext prev"],
["T", "current-url tab"],
["yy", "clipboard yank"],
["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"],
["gt", "tabnext"],
["gT", "tabprev"],
["gr", "reader"],
[":", "fillcmdline"],
["s", "fillcmdline google"],
["xx", "something"],
["i", "insertmode"],
["b", "openbuffer"],
// Special keys must be prepended with 🄰
// ["🄰Backspace", "something"],
])
// }}}
// Add listeners
window.addEventListener("keydown", keyeventHandler, true)
import * as SELF from './keydown_content'
Messaging.addListener('keydown_content', Messaging.attributeCaller(SELF))
// Get current suppression state
async function init() {
let state = await Messaging.message("keydown_background", "state")
suppress(...state)
}
init()
// Dummy export so that TS treats this as a module.
export {}

13
src/lib/browser_proxy.ts Normal file
View file

@ -0,0 +1,13 @@
import {message} from '../messaging'
const browserProxy = new Proxy(Object.create(null), {
get: function(target, api) {
return new Proxy({}, {
get: function(_, func) {
return (...args) => message('browser_proxy_background', 'shim', [api, func, args])
}
});
}
}) as typeof browser
export default browserProxy

View file

@ -0,0 +1,8 @@
/** Shim to access BG browser APIs from content. */
function shim(api, func, args) {
return browser[api][func](...args)
}
import {addListener, attributeCaller, MessageType} from '../messaging'
addListener('browser_proxy_background' as MessageType, attributeCaller({shim}))

View file

@ -1,3 +1,19 @@
import * as convert from '../convert'
import browserProxy from './browser_proxy'
export function inContentScript() {
return ! ('tabs' in browser)
}
export let browserBg
// Make this library work for both content and background.
if (inContentScript()) {
browserBg = browserProxy
} else {
browserBg = browser
}
/** await a promise and console.error and rethrow if it errors
Errors from promises don't get logged unless you seek them out.
@ -22,10 +38,17 @@ export async function l(promise) {
*/
//#background_helper
export async function activeTab() {
return (await l(browser.tabs.query({active: true, currentWindow: true})))[0]
return (await l(browserBg.tabs.query({active: true, currentWindow: true})))[0]
}
//#background_helper
export async function activeTabId() {
return (await activeTab()).id
}
/** Compare major firefox versions */
export async function firefoxVersionAtLeast(desiredmajor: number) {
const versionstr = (await browserBg.runtime.getBrowserInfo()).version
const actualmajor = convert.toNumber(versionstr.split('.')[0])
return actualmajor >= desiredmajor
}

View file

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Tridactyl",
"version": "1.0.7",
"version": "1.3.1",
"icons": {
"64": "static/logo/Tridactyl_64px.png",
"100": "static/logo/Tridactyl_100px.png",
@ -56,4 +56,4 @@
"strict_min_version": "54.0"
}
}
}
}

View file

@ -8,7 +8,8 @@ export type TabMessageType =
"hinting_content"
export type NonTabMessageType =
"keydown_background" |
"commandline_background"
"commandline_background" |
"browser_proxy_background"
export type MessageType = TabMessageType | NonTabMessageType
export interface Message {

View file

@ -26,7 +26,7 @@ function convertArgs(params, argv) {
for ([type, [i, arg]] of izip(params.values(), enumerate(argv))) {
if (type in conversions) {
typedArgs.push(conversions[type](arg))
} else if (type.includes('|')) {
} else if (type.includes('|') || ["'", '"'].includes(type[0])) {
// Do your own conversions!
typedArgs.push(arg)
} else if (type === "string[]") {
@ -42,7 +42,7 @@ function convertArgs(params, argv) {
// TODO: Pipe to separate commands
// TODO: Abbreviated commands
export function parser(ex_str){
let [func,...args] = ex_str.split(" ")
let [func,...args] = ex_str.trim().split(/\s+/)
if (ExCmds.cmd_params.has(func)) {
try {
let typedArgs = convertArgs(ExCmds.cmd_params.get(func), args)

View file

@ -9,7 +9,7 @@
// TODO: stop stealing keys from "insert mode"
// r -> refresh page is particularly unhelpful
// Can't stringify a map -> just use an object
let nmaps = {
export const DEFAULTNMAPS = {
"o": "fillcmdline open",
"O": "current_url open",
"w": "fillcmdline winopen",
@ -44,18 +44,19 @@ let nmaps = {
"b": "openbuffer",
"ZZ": "qall",
"f": "hint",
"F": "hint -b",
"I": "mode ignore",
// Special keys must be prepended with 🄰
// ["🄰Backspace", "something"],
}
let nmaps = Object.assign(Object.create(null), DEFAULTNMAPS)
// Allow config to be changed in settings
// TODO: make this more general
browser.storage.sync.get("nmaps").then(lazyloadconfig)
async function lazyloadconfig(config_obj){
let nmaps_config = config_obj["nmaps"]
nmaps_config = (nmaps_config == undefined) ? {} : nmaps_config
nmaps = merge_objects(nmaps, nmaps_config)
async function lazyloadconfig(storageResult){
nmaps = Object.assign(Object.create(null), DEFAULTNMAPS, storageResult.nmaps)
console.log(nmaps)
}
@ -66,13 +67,6 @@ browser.storage.onChanged.addListener(
}
})
// Shamelessly copied from https://plainjs.com/javascript/utilities/merge-two-javascript-objects-19/
// It should be possible to replace this with Object.assign, but that doesn't seem to work :/
function merge_objects(obj,src){
Object.keys(src).forEach(function(key) { obj[key] = src[key]; });
return obj;
}
// Split a string into a number prefix and some following keys.
function keys_split_count(keys: string[]){
// Extracts the first number with capturing parentheses
@ -108,7 +102,7 @@ function possible_maps(keys): string[] {
let [count, keystr] = keys_split_count(keys)
// Short circuit or search maps.
if (nmaps.hasOwnProperty(keystr)) {
if (Object.getOwnPropertyNames(nmaps).includes(keystr)) {
return [keystr,]
} else {
// Efficiency: this can be short-circuited.

View file

@ -13,15 +13,15 @@ In the meantime, here are some notes about Tridactyl:
Highlighted features:
- Press `b` to bring up a list of open tabs in the current window; you can type the tab ID or part of the title or URL to choose a tab (the buffer list doesn't show which one you've selected yet, but it does work)
- Press `f` to start "hint mode".
- Press `I` to enter ignore mode. `Shift` + `Escape` to return to normal mode.
- Press `f` to start "hint mode", `F` to open in background
- Press `o` to `:open` a different page
- Press `s` if you want to search for something that looks like a domain name or URL
- Bind new commands with e.g. `:bind J tabprev`
- If you want a bind to insert something on the commandline and wait, use `:bind <whatever> fillcmdline <whatever>` (observe the difference between `:bind t tabopen` and `:bind t fillcmdline tabopen`
- Type `:help` to see a list of available excmds.
- Use `yy` to copy the current page URL to your clipboard.
- Bind new commands with e.g. `:bind J tabprev`. Type `:help bind` to see help on custom binds.
- Type `:help` for online help
- Use `yy` to copy the current page URL to your clipboard
- `]]` and `[[` to navigate through the pages of comics, paginated articles, etc.
- Pressing `ZZ` will close all tabs and windows, but it will only "save" them if your about:preferences are set to "show your tabs and windows from last time".
- Pressing `ZZ` will close all tabs and windows, but it will only "save" them if your about:preferences are set to "show your tabs and windows from last time"
There are some caveats common to all webextension vimperator-alikes: