2019-04-02 06:34:31 +02:00
/* tslint:disable:comment-format */
2017-10-12 04:02:01 +01:00
// '//#' is a start point for a simple text-replacement-type macro. See excmds_macros.py
2017-11-19 06:05:15 +00:00
/ * * # T r i d a c t y l h e l p p a g e
2019-02-18 16:11:45 +01:00
Use ` :help <excmd> ` or scroll down to show [ [ help ] ] for a particular excmd . If you ' re still stuck , you might consider reading through the [ : tutor ] ( / s t a t i c / c l i p p y / 1 - t u t o r . h t m l ) a g a i n .
2017-11-19 06:05:15 +00:00
2018-11-16 19:34:30 +01:00
The default keybinds and settings can be found [ here ] ( / s t a t i c / d o c s / c l a s s e s / _ s r c _ l i b _ c o n f i g _ . d e f a u l t _ c o n f i g . h t m l ) a n d a c t i v e b i n d s c a n b e s e e n w i t h ` : v i e w c o n f i g n m a p s ` o r w i t h [ [ b i n d ] ] .
2017-11-19 06:05:15 +00:00
2018-11-08 07:00:35 +01:00
Tridactyl also provides a few functions to manipulate text in the command line or text areas that can be found [ here ] ( / s t a t i c / d o c s / m o d u l e s / _ s r c _ l i b _ e d i t o r _ . h t m l ) . T h e r e a r e a l s o a f e w c o m m a n d s o n l y a v a i l a b l e i n t h e c o m m a n d l i n e w h i c h c a n b e f o u n d [ h e r e ] ( / s t a t i c / d o c s / m o d u l e s / _ s r c _ c o m m a n d l i n e _ f r a m e _ . h t m l ) .
2018-11-01 16:39:16 +01:00
2019-05-28 13:08:48 +01:00
Ex - commands available exclusively in hint mode are listed [ here ] ( / s t a t i c / d o c s / m o d u l e s / _ s r c _ c o n t e n t _ h i n t i n g _ . h t m l )
2018-04-12 22:33:10 +01:00
# # How to use this help page
2018-10-07 16:35:35 +02:00
Every function ( excmd ) on this page can be called via Tridactyl ' s command line which we call "ex" . There is a slight change in syntax , however . Wherever you see :
2018-04-12 22:33:10 +01:00
` function(arg1,arg2) `
You should instead type
` function arg1 arg2 ` into the Tridactyl command line ( accessed via ` : ` )
A "splat" operator ( . . . ) means that the excmd will accept any number of space - delimited arguments into that parameter .
2018-07-26 10:38:54 +01:00
Above each function signature you will see any aliases or key sequences bound to it . The internal names for the various modes are used , which are listed here :
2018-10-15 14:24:31 +08:00
2018-07-26 10:38:54 +01:00
- ` nmaps ` : normal mode binds
- ` imaps ` : insert mode binds
- ` inputmaps ` : input mode binds
- ` ignoremaps ` : ignore mode binds
- ` exaliases ` : aliases in the command mode
2018-11-01 16:39:16 +01:00
- ` exmaps ` : commandline mode binds
2018-07-26 10:38:54 +01:00
2018-05-11 22:09:34 +01:00
At the bottom of each function 's help page, you can click on a link that will take you straight to that function' s definition in our code .
2018-05-04 09:22:57 -05:00
2018-08-16 15:46:24 +01:00
You do not need to worry about types . Return values which are promises will turn into whatever they promise to when used in [ [ composite ] ] .
2018-04-12 22:33:10 +01:00
2018-10-07 16:35:35 +02:00
# # Caveats
2017-11-19 06:05:15 +00:00
There are some caveats common to all webextension vimperator - alikes :
2018-05-18 12:31:44 +01:00
- To make Tridactyl work on addons . mozilla . org and some other Mozilla domains , you need to open ` about:config ` , run [ [ fixamo ] ] or add a new boolean ` privacy.resistFingerprinting.block_mozAddonManager ` with the value ` true ` , and remove the above domains from ` extensions.webextensions.restrictedDomains ` .
- Tridactyl can ' t run on about : \ * , some file : \ * URIs , view - source : \ * , or data : \ * , URIs .
2019-01-28 07:32:09 +01:00
- To change / hide the GUI of Firefox from Tridactyl , you can use [ [ guiset ] ] with the native messenger installed ( see [ [ native ] ] and [ [ installnative ] ] ) . Alternatively , you can edit your userChrome yourself .
2018-10-15 14:24:31 +08:00
2018-10-07 16:35:35 +02:00
# # Getting help
2019-01-30 06:10:32 +01:00
For more information , and FAQs , check out our [ readme ] [ 2 ] and [ troubleshooting guide ] [ 3 ] on github .
2018-10-07 16:35:35 +02:00
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 ] . You can also get in touch using Matrix , Gitter , or IRC chat clients :
[ ! [ Matrix Chat ] [ matrix - badge ] ] [ matrix - link ]
[ ! [ Gitter Chat ] [ gitter - badge ] ] [ gitter - link ]
[ ! [ Freenode Chat ] [ freenode - badge ] ] [ freenode - link ]
All three channels are mirrored together , so it doesn ' t matter which one you use .
2019-01-28 07:32:09 +01:00
[ 1 ] : https : //github.com/tridactyl/tridactyl/issues
[ 2 ] : https : //github.com/tridactyl/tridactyl#readme
2019-01-30 06:10:32 +01:00
[ 3 ] : https : //github.com/tridactyl/tridactyl/blob/master/doc/troubleshooting.md
2017-11-19 06:05:15 +00:00
2017-12-02 23:06:12 +00:00
[ gitter - badge ] : / s t a t i c / b a d g e s / g i t t e r - b a d g e . s v g
[ gitter - link ] : https : //gitter.im/tridactyl/Lobby
[ freenode - badge ] : / s t a t i c / b a d g e s / f r e e n o d e - b a d g e . s v g
2017-12-04 15:41:05 +00:00
[ freenode - link ] : ircs : //chat.freenode.net/tridactyl
2017-12-02 23:06:12 +00:00
[ matrix - badge ] : https : //matrix.to/img/matrix-badge.svg
[ matrix - link ] : https : //riot.im/app/#/room/#tridactyl:matrix.org
2017-11-19 06:05:15 +00:00
* /
/** ignore this line */
2017-10-12 04:02:01 +01:00
// {{{ setup
2018-03-12 22:36:05 +00:00
// Shared
2018-09-29 16:17:52 -07:00
import * as Messaging from "@src/lib/messaging"
2019-01-27 13:04:51 +01:00
import { browserBg , activeTab , activeTabId , activeTabContainerId , openInNewTab , openInNewWindow , openInTab } from "@src/lib/webext"
2018-09-29 17:38:58 -07:00
import * as Container from "@src/lib/containers"
import state from "@src/state"
2018-09-29 16:39:14 -07:00
import { contentState , ModeName } from "@src/content/state_content"
2018-09-29 16:57:37 -07:00
import * as UrlUtil from "@src/lib/url_util"
2018-09-29 15:57:09 -07:00
import * as config from "@src/lib/config"
2018-09-29 15:32:13 -07:00
import * as aliases from "@src/lib/aliases"
2018-09-29 15:30:48 -07:00
import * as Logging from "@src/lib/logging"
2018-08-27 03:32:46 -07:00
import { AutoContain } from "@src/lib/autocontainers"
2018-04-21 23:43:12 +01:00
import * as CSS from "css"
2018-10-04 13:59:19 +01:00
import * as Perf from "@src/perf"
2018-09-29 17:38:58 -07:00
import * as Metadata from "@src/.metadata.generated"
2019-02-20 06:42:45 +01:00
import * as Native from "@src/lib/native"
2019-04-21 01:38:57 -07:00
import * as TTS from "@src/lib/text_to_speech"
import * as excmd_parser from "@src/parsers/exmode"
2019-05-29 15:29:47 +01:00
import * as shell_quote from "node-shell-quote"
export let quote = shell_quote
2019-04-21 01:38:57 -07:00
/ * *
* This is used to drive some excmd handling in ` composite ` .
*
* @hidden
* /
2019-04-26 01:12:48 -07:00
let ALL_EXCMDS
2019-04-21 01:38:57 -07:00
// The entry-point script will make sure this has the right set of
// excmds, so we can use it without futher configuration.
import * as controller from "@src/lib/controller"
2019-05-30 18:21:05 +01:00
//#content_helper
import { generator as KEY_MUNCHER } from "@src/content/controller_content"
2019-04-21 01:38:57 -07:00
/ * *
* Used to store the types of the parameters for each excmd for
* self - documenting functionality .
*
* @hidden
* /
export const cmd_params = new Map < string , Map < string , string > > ( )
/** @hidden */
const logger = new Logging . Logger ( "excmds" )
2017-10-23 09:42:50 +01:00
2018-12-19 08:16:33 +01:00
/** @hidden **/
2018-12-07 00:31:16 +00:00
const TRI_VERSION = "REPLACE_ME_WITH_THE_VERSION_USING_SED"
2017-10-12 04:02:01 +01:00
//#content_helper
2018-03-12 22:36:05 +00:00
// {
2018-09-29 16:23:06 -07:00
import "@src/lib/number.clamp"
2019-04-21 01:38:57 -07:00
import * as CTSELF from "@src/.excmds_content.generated"
2019-04-26 01:12:48 -07:00
import { CmdlineCmds as CtCmdlineCmds } from "@src/background/commandline_cmds"
import { EditorCmds as CtEditorCmds } from "@src/background/editor"
2018-09-29 17:07:29 -07:00
import * as DOM from "@src/lib/dom"
2018-12-20 07:30:53 +01:00
import * as CommandLineContent from "@src/content/commandline_content"
2018-09-29 16:36:57 -07:00
import * as scrolling from "@src/content/scrolling"
2019-02-04 08:18:01 +01:00
import { ownTab } from "@src/lib/webext"
2019-05-30 19:45:46 +00:00
import { wrap_input , getLineAndColNumber , rot13_helper } from "@src/lib/editor_utils"
2019-04-21 01:38:57 -07:00
import * as finding from "@src/content/finding"
import * as toys from "./content/toys"
import * as hinting from "@src/content/hinting"
import * as gobbleMode from "@src/parsers/gobblemode"
2019-04-26 01:12:48 -07:00
ALL_EXCMDS = {
"" : CTSELF ,
"ex" : CtCmdlineCmds ,
"text" : CtEditorCmds ,
}
2018-03-12 22:36:05 +00:00
// }
2019-05-30 18:21:05 +01:00
import { mapstrToKeyseq } from "@src/lib/keyseq"
2017-10-28 05:11:10 +01:00
//#background_helper
2018-03-12 22:36:05 +00:00
// {
2019-04-21 01:38:57 -07:00
// tslint:disable-next-line:no-unused-declaration
import "@src/lib/number.mod"
import * as BGSELF from "@src/.excmds_background.generated"
2019-04-26 01:12:48 -07:00
import { CmdlineCmds as BgCmdlineCmds } from "@src/background/commandline_cmds"
import { EditorCmds as BgEditorCmds } from "@src/background/editor"
2019-04-16 08:30:31 +02:00
import { messageActiveTab } from "@src/lib/messaging"
2019-04-20 23:57:59 +02:00
import { EditorCmds } from "@src/background/editor"
2018-09-29 16:12:02 -07:00
import { flatten } from "@src/lib/itertools"
2019-01-27 13:04:51 +01:00
import { firefoxVersionAtLeast } from "@src/lib/webext"
2018-09-29 15:57:59 -07:00
import * as rc from "@src/background/config_rc"
2019-04-21 01:38:57 -07:00
import * as css_util from "@src/lib/css_util"
2019-04-20 17:22:56 -07:00
import * as Updates from "@src/lib/updates"
2017-10-12 04:02:01 +01:00
2019-04-26 01:12:48 -07:00
ALL_EXCMDS = {
"" : BGSELF ,
"ex" : BgCmdlineCmds ,
"text" : BgEditorCmds ,
}
2017-11-03 19:10:12 +00:00
/** @hidden */
2018-03-12 22:36:05 +00:00
// }
2017-10-12 04:02:01 +01:00
2018-04-20 19:32:09 +01:00
// }}}
2018-04-18 21:49:33 +01:00
// {{{ Native messenger stuff
2018-04-17 18:45:54 +01:00
/** @hidden **/
2018-04-17 18:28:11 +01:00
//#background
export async function getNativeVersion ( ) : Promise < void > {
Native . getNativeMessengerVersion ( )
}
2018-12-27 09:38:56 +01:00
/ * * @ h i d d e n
2018-12-28 08:13:05 +01:00
* This function is used by rssexec and rssexec completions .
2018-12-27 09:38:56 +01:00
* /
//#content
2019-04-11 18:37:29 +02:00
export async function getRssLinks ( ) : Promise < Array < { type : string ; url : string ; title : string } > > {
2019-04-13 09:46:23 +02:00
const seen = new Set < string > ( )
2019-01-21 20:04:26 +01:00
return Array . from ( document . querySelectorAll ( "a, link[rel='alternate']" ) )
2019-04-10 07:21:21 +02:00
. filter ( ( e : any ) = > typeof e . href === "string" )
2019-01-21 20:04:26 +01:00
. reduce ( ( acc , e : any ) = > {
let type = ""
// Start by detecting type because url doesn't necessarily contain the words "rss" or "atom"
if ( e . type ) {
// if type doesn't match either rss or atom, don't include link
if ( e . type . indexOf ( "rss" ) < 0 && e . type . indexOf ( "atom" ) < 0 ) return acc
type = e . type
} else {
// Making sure that we match either a dot or "xml" because "urss" and "atom" are actual words
if ( e . href . match ( /(\.rss)|(rss\.xml)/i ) ) type = "application/rss+xml"
else if ( e . href . match ( /(\.atom)|(atom\.xml)/i ) ) type = "application/atom+xml"
else return acc
}
if ( seen . has ( e . href ) ) return acc
seen . add ( e . href )
return acc . concat ( { type , url : e.href , title : e.title || e . innerText } as { type : string ; url : string ; title : string } )
} , [ ] )
2018-12-27 09:38:56 +01:00
}
/ * *
2018-12-28 08:13:05 +01:00
* Execute [ [ rsscmd ] ] for an rss link .
2018-12-27 09:38:56 +01:00
*
* If ` url ` is undefined , Tridactyl will look for rss links in the current
* page . If it doesn ' t find any , it will display an error message . If it finds
* multiple urls , it will offer completions in order for you to select the link
* you ' re interested in . If a single rss feed is found , it will automatically
* be selected .
* /
2018-12-28 08:13:05 +01:00
//#content
export async function rssexec ( url : string , type ? : string , . . . title : string [ ] ) {
2019-04-10 07:21:21 +02:00
if ( ! url || url === "" ) {
2019-04-13 09:46:23 +02:00
const links = await getRssLinks ( )
2018-12-27 09:38:56 +01:00
switch ( links . length ) {
case 0 :
throw new Error ( "No rss link found on this page." )
break
case 1 :
url = links [ 0 ] . url
title = [ links [ 0 ] . title ]
type = links [ 0 ] . type
break
default :
2018-12-28 08:13:05 +01:00
return fillcmdline ( "rssexec" )
2018-12-27 09:38:56 +01:00
}
}
let rsscmd = config . get ( "rsscmd" )
2018-12-28 08:13:05 +01:00
if ( rsscmd . match ( "%[uty]" ) ) {
rsscmd = rsscmd
. replace ( "%u" , url )
. replace ( "%t" , title . join ( " " ) )
. replace ( "%y" , type || "" )
} else {
rsscmd += " " + url
2018-12-27 09:38:56 +01:00
}
2019-04-21 01:38:57 -07:00
// Need actual excmd parsing here.
return controller . acceptExCmd ( rsscmd )
2018-12-27 09:38:56 +01:00
}
2018-04-17 18:45:54 +01:00
/ * *
2018-06-04 18:51:56 +02:00
* Fills the element matched by ` selector ` with content and falls back to the last used input if the element can 't be found. You probably don' t want this ; it ' s used internally for [ [ editor ] ] .
2018-04-17 18:45:54 +01:00
*
2018-06-04 18:51:56 +02:00
* That said , ` bind gs fillinput null [Tridactyl](https://addons.mozilla.org/en-US/firefox/addon/tridactyl-vim/) is my favourite add-on ` could probably come in handy .
2018-04-17 18:45:54 +01:00
* /
//#content
2018-06-04 07:10:23 +02:00
export async function fillinput ( selector : string , . . . content : string [ ] ) {
let inputToFill = document . querySelector ( selector )
if ( ! inputToFill ) inputToFill = DOM . getLastUsedInput ( )
2018-05-17 21:09:25 +01:00
if ( "value" in inputToFill ) {
2019-04-12 05:54:31 +02:00
( inputToFill as HTMLInputElement ) . value = content . join ( " " )
2018-05-17 21:09:25 +01:00
} else {
inputToFill . textContent = content . join ( " " )
}
2018-04-17 18:45:54 +01:00
}
/** @hidden */
2018-09-26 19:38:50 +02:00
//#content_helper
export function getInput ( e : HTMLElement ) {
2018-04-17 18:45:54 +01:00
// this should probably be subsumed by the focusinput code
2018-09-26 19:38:50 +02:00
if ( "value" in e ) {
return ( e as HTMLInputElement ) . value
2018-05-17 21:09:25 +01:00
} else {
2018-09-26 19:38:50 +02:00
return e . textContent
2018-05-17 21:09:25 +01:00
}
2018-04-17 18:45:54 +01:00
}
2018-09-26 19:38:50 +02:00
/** @hidden */
//#content
export function getinput() {
return getInput ( DOM . getLastUsedInput ( ) )
}
2018-06-04 07:10:23 +02:00
/** @hidden */
//#content
2019-02-15 18:15:07 +01:00
export function getInputSelector() {
2018-06-04 07:10:23 +02:00
return DOM . getSelector ( DOM . getLastUsedInput ( ) )
}
2018-10-10 18:41:08 +02:00
/** @hidden */
//#content
export function addTridactylEditorClass ( selector : string ) {
2019-04-13 09:46:23 +02:00
const elem = document . querySelector ( selector )
2018-10-10 18:41:08 +02:00
elem . className = elem . className + " TridactylEditing "
}
/** @hidden */
//#content
export function removeTridactylEditorClass ( selector : string ) {
2019-04-13 09:46:23 +02:00
const elem = document . querySelector ( selector )
2018-10-10 18:41:08 +02:00
elem . className = elem . className . replace ( " TridactylEditing " , "" )
}
2018-04-17 18:45:54 +01:00
/ * *
* Opens your favourite editor ( which is currently gVim ) and fills the last used input with whatever you write into that file .
2018-04-18 21:49:33 +01:00
* * * Requires that the native messenger is installed , see [ [ native ] ] and [ [ installnative ] ] * * .
*
2019-02-20 06:42:45 +01:00
* Uses the ` editorcmd ` config option , default = ` auto ` looks through a list defined in lib / native . ts try find a sensible combination . If it ' s a bit slow , or chooses the wrong editor , or gives up completely , set editorcmd to something you want . The command must stay in the foreground until the editor exits .
2018-04-18 21:49:33 +01:00
*
2019-04-18 08:22:36 +02:00
* The editorcmd needs to accept a filename , stay in the foreground while it ' s edited , save the file and exit . By default the filename is added to the end of editorcmd , if you require control over the position of that argument , the first occurrence of % f in editorcmd is replaced with the filename . % l , if it exists , is replaced with the line number of the cursor and % c with the column number . For example :
2018-10-08 23:56:35 +00:00
* ` ` `
2019-04-18 08:22:36 +02:00
* set editorcmd terminator - u - e "vim %f -c 'normal %lG%cl'"
2018-10-08 23:56:35 +00:00
* ` ` `
2018-04-17 18:45:54 +01:00
*
2018-07-11 11:56:31 +01:00
* You ' re probably better off using the default insert mode bind of ` <C-i> ` ( Ctrl - i ) to access this .
2018-09-15 08:17:10 +02:00
*
* This function returns a tuple containing the path to the file that was opened by the editor and its content . This enables creating commands such as the following one , which deletes the temporary file created by the editor :
* ` ` `
* alias editor_rm composite editor | jsb - p tri . native . run ( ` rm -f ' ${ JS_ARG [ 0 ] } ' ` )
* bind -- mode = insert < C - i > editor_rm
* bind -- mode = input < C - i > editor_rm
* ` ` `
2018-04-17 18:45:54 +01:00
* /
2019-02-15 18:15:07 +01:00
//#content
2018-04-17 18:45:54 +01:00
export async function editor() {
2019-04-13 09:46:23 +02:00
const elem = DOM . getLastUsedInput ( )
const selector = DOM . getSelector ( elem )
2019-02-15 18:15:07 +01:00
addTridactylEditorClass ( selector )
2019-01-28 07:13:25 +01:00
if ( ! ( await Native . nativegate ( ) ) ) {
2019-02-15 18:15:07 +01:00
removeTridactylEditorClass ( selector )
2019-01-28 07:13:25 +01:00
return undefined
}
try {
2019-04-18 08:22:36 +02:00
let text = ""
let line = 0
let col = 0
2019-04-20 23:57:59 +02:00
wrap_input ( ( t , start , end ) = > {
[ text , line , col ] = getLineAndColNumber ( t , start , end )
2019-04-18 08:22:36 +02:00
return [ null , null , null ]
} ) ( elem )
const file = ( await Native . temp ( text , document . location . hostname ) ) . content
const exec = await Native . editor ( file , line , col )
2019-04-18 07:01:02 +02:00
if ( exec . code == 0 ) {
fillinput ( selector , exec . content )
// TODO: add annoying "This message was written with [Tridactyl](https://addons.mozilla.org/en-US/firefox/addon/tridactyl-vim/)" to everything written using editor
return [ file , exec . content ]
} else {
logger . debug ( ` Editor terminated with non-zero exit code: ${ exec . code } ` )
}
2019-01-28 07:13:25 +01:00
} catch ( e ) {
throw ` :editor failed: ${ e } `
} finally {
2019-02-15 18:15:07 +01:00
removeTridactylEditorClass ( selector )
2019-01-28 07:13:25 +01:00
}
2018-04-17 18:45:54 +01:00
}
2018-07-14 15:18:09 +02:00
/ * *
* Like [ [ guiset ] ] but quieter .
* /
//#background
export async function guiset_quiet ( rule : string , option : string ) {
2018-07-23 11:34:42 +01:00
if ( ! rule || ! option ) throw new Error ( ":guiset requires two arguments. See `:help guiset` for more information." )
2018-07-14 15:18:09 +02:00
// Could potentially fall back to sending minimal example to clipboard if native not installed
// Check for native messenger and make sure we have a plausible profile directory
2018-09-14 10:30:26 +01:00
if ( ! ( await Native . nativegate ( "0.1.1" ) ) ) return
2019-04-13 09:46:23 +02:00
const profile_dir = await Native . getProfileDir ( )
2019-05-29 17:06:38 +01:00
await setpref ( "toolkit.legacyUserProfileCustomizations.stylesheets" , "true" )
2018-07-14 15:18:09 +02:00
// Make backups
await Native . mkdir ( profile_dir + "/chrome" , true )
let cssstr = ( await Native . read ( profile_dir + "/chrome/userChrome.css" ) ) . content
2019-04-13 09:46:23 +02:00
const cssstrOrig = ( await Native . read ( profile_dir + "/chrome/userChrome.orig.css" ) ) . content
2018-07-14 15:18:09 +02:00
if ( cssstrOrig === "" ) await Native . write ( profile_dir + "/chrome/userChrome.orig.css" , cssstr )
await Native . write ( profile_dir + "/chrome/userChrome.css.tri.bak" , cssstr )
// Modify and write new CSS
if ( cssstr === "" ) cssstr = ` @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); `
2019-04-13 09:46:23 +02:00
const stylesheet = CSS . parse ( cssstr )
2018-09-14 10:30:26 +01:00
// Trim due to https://github.com/reworkcss/css/issues/113
2019-04-13 09:46:23 +02:00
const stylesheetDone = CSS . stringify ( css_util . changeCss ( rule , option , stylesheet ) ) . trim ( )
2018-07-14 15:18:09 +02:00
return Native . write ( profile_dir + "/chrome/userChrome.css" , stylesheetDone )
}
2018-04-21 23:43:12 +01:00
/ * *
2018-05-05 13:06:49 +01:00
* Change which parts of the Firefox user interface are shown . * * NB : This feature is experimental and might break stuff . * *
2018-04-21 23:43:12 +01:00
*
2018-05-10 16:27:58 +02:00
* Might mangle your userChrome . Requires native messenger , and you must restart Firefox each time to see any changes ( this can be done using [ [ restart ] ] ) . <!-- (unless you enable addon debugging and refresh using the browser toolbox) -->
2018-04-30 15:57:23 +01:00
*
2019-05-29 17:06:38 +01:00
* Also flips the preference ` toolkit.legacyUserProfileCustomizations.stylesheets ` to true so that FF will read your userChrome .
*
2018-11-08 07:03:58 +01:00
* View available rules and options [ here ] ( / s t a t i c / d o c s / m o d u l e s / _ s r c _ l i b _ c s s _ u t i l _ . h t m l # p o t e n t i a l r u l e s ) a n d [ h e r e ] ( / s t a t i c / d o c s / m o d u l e s / _ s r c _ l i b _ c s s _ u t i l _ . h t m l # m e t a r u l e s ) .
2018-04-30 15:57:23 +01:00
*
* Example usage : ` guiset gui none ` , ` guiset gui full ` , ` guiset tabs autohide ` .
2018-04-21 23:43:12 +01:00
*
2018-05-05 13:06:49 +01:00
* Some of the available options :
*
* - gui
* - full
* - none
*
* - tabs
* - always
* - autohide
*
* - navbar
* - always
* - autohide
2018-06-18 18:18:34 +01:00
* - none
2018-05-05 13:06:49 +01:00
*
* - hoverlink ( the little link that appears when you hover over a link )
* - none
* - left
* - right
* - top - left
* - top - right
*
2019-02-03 08:55:40 +01:00
* - statuspanel ( hoverlink + the indicator that appears when a website is loading )
* - none
* - left
* - right
* - top - left
* - top - right
*
2018-07-14 15:18:09 +02:00
* If you want to use guiset in your tridactylrc , you might want to use [ [ guiset_quiet ] ] instead .
2018-04-21 23:43:12 +01:00
* /
//#background
export async function guiset ( rule : string , option : string ) {
2018-07-14 15:18:09 +02:00
await guiset_quiet ( rule , option )
2019-01-21 19:49:24 +01:00
return fillcmdline_tmp ( 3000 , "userChrome.css written. Please restart Firefox to see the changes." )
2018-04-21 23:43:12 +01:00
}
/** @hidden */
//#background
export function cssparse ( . . . css : string [ ] ) {
console . log ( CSS . parse ( css . join ( " " ) ) )
}
2018-07-29 10:29:38 +02:00
/** @hidden */
//#background
export async function loadtheme ( themename : string ) {
2018-10-08 18:25:37 +01:00
if ( ! ( await Native . nativegate ( "0.1.9" ) ) ) return
2019-04-10 07:21:21 +02:00
const separator = ( await browserBg . runtime . getPlatformInfo ( ) . os ) === "win" ? "\\" : "/"
2018-07-29 10:29:38 +02:00
// remove the "tridactylrc" bit so that we're left with the directory
2018-09-02 10:59:13 +02:00
const path =
( await Native . getrcpath ( ) )
. split ( separator )
. slice ( 0 , - 1 )
. join ( separator ) +
separator +
"themes" +
separator +
2018-09-12 11:51:20 +02:00
themename +
".css"
2018-07-29 10:29:38 +02:00
const file = await Native . read ( path )
2019-04-10 07:21:21 +02:00
if ( file . code !== 0 ) throw new Error ( "Couldn't read theme " + path )
2018-07-29 10:29:38 +02:00
return set ( "customthemes." + themename , file . content )
}
/** @hidden */
//#background
export async function unloadtheme ( themename : string ) {
unset ( "customthemes." + themename )
}
/ * * C h a n g e s t h e c u r r e n t t h e m e .
2018-09-02 10:59:13 +02:00
*
* If THEMENAME is any of the themes that can be found in the [ Tridactyl repo ] ( https : //github.com/tridactyl/tridactyl/tree/master/src/static/themes) (e.g. 'dark'), the theme will be loaded from Tridactyl's internal storage.
2018-07-29 10:29:38 +02:00
*
* If THEMENAME is set to any other value , Tridactyl will attempt to use its native binary ( see [ [ native ] ] ) in order to load a CSS file named THEMENAME from disk . The CSS file has to be in a directory named "themes" and this directory has to be in the same directory as your tridactylrc .
*
2018-09-12 11:51:20 +02:00
* Note that the theme name should NOT contain any dot .
*
* Example : ` :colourscheme mysupertheme `
* On linux , this will load ~ / . c o n f i g / t r i d a c t y l / t h e m e s / m y s u p e r t h e m e . c s s
2018-07-29 10:29:38 +02:00
* /
//#background
export async function colourscheme ( themename : string ) {
// If this is a builtin theme, no need to bother with native messaging stuff
2018-09-02 10:59:13 +02:00
if ( Metadata . staticThemes . includes ( themename ) ) return set ( "theme" , themename )
2018-09-12 11:51:20 +02:00
if ( themename . search ( "\\." ) >= 0 ) throw new Error ( ` Theme name should not contain any dots! (given name: ${ themename } ). ` )
2018-07-29 10:29:38 +02:00
await loadtheme ( themename )
return set ( "theme" , themename )
}
2018-10-12 11:42:31 +02:00
/ * *
* Write a setting to your user . js file .
*
* @param key The key that should be set . Must not be quoted . Must not contain spaces .
* @param value The value the key should take . Quoted if a string , unquoted otherwise .
*
* Note that not all of the keys Firefox uses are suggested by Tridactyl .
*
* e . g . : ` setpref general.warnOnAboutConfig false `
* ` setpref extensions.webextensions.restricterDomains "" `
* /
//#background
2019-01-21 19:49:24 +01:00
export function setpref ( key : string , . . . value : string [ ] ) {
return Native . writePref ( key , value . join ( " " ) )
2018-10-12 11:42:31 +02:00
}
2018-07-03 19:05:20 +01:00
/ * *
* Like [ [ fixamo ] ] but quieter .
* /
//#background
export async function fixamo_quiet() {
2018-10-12 11:42:31 +02:00
await setpref ( "privacy.resistFingerprinting.block_mozAddonManager" , "true" )
return setpref ( "extensions.webextensions.restrictedDomains" , '""' )
2018-07-03 19:05:20 +01:00
}
2018-05-17 22:08:45 +01:00
/ * *
*
* Simply sets
* ` ` ` js
* "privacy.resistFingerprinting.block_mozAddonManager" : true
* "extensions.webextensions.restrictedDomains" : ""
* ` ` `
* in about :config via user . js so that Tridactyl ( and other extensions ! ) can be used on addons . mozilla . org and other sites .
*
2018-06-22 10:14:03 +01:00
* Requires ` native ` and a ` restart ` .
2018-05-17 22:08:45 +01:00
* /
//#background
export async function fixamo() {
2018-07-03 19:05:20 +01:00
await fixamo_quiet ( )
2018-07-30 23:51:08 +01:00
fillcmdline_tmp ( 3000 , "Permissions added to user.js. Please restart Firefox to make them take affect." )
2018-05-17 22:08:45 +01:00
}
2018-04-20 22:06:06 +01:00
/ * *
* Uses the native messenger to open URLs .
*
* * * Be * seriously * careful with this : you can use it to open any URL you can open in the Firefox address bar . * *
*
* You ' ve been warned .
2019-02-13 07:39:39 +01:00
*
* This uses the [ [ browser ] ] setting to know which binary to call . If you need to pass additional arguments to firefox ( e . g . '--new-window' ) , make sure they appear before the url .
2018-04-20 22:06:06 +01:00
* /
//#background
2019-02-13 07:39:39 +01:00
export async function nativeopen ( . . . args : string [ ] ) {
const index = args . findIndex ( arg = > ! arg . startsWith ( "-" ) )
let firefoxArgs = [ ]
if ( index >= 0 ) {
firefoxArgs = args . slice ( 0 , index )
}
const url = args . slice ( firefoxArgs . length ) . join ( " " )
2018-05-09 22:22:47 +02:00
if ( await Native . nativegate ( ) ) {
2018-06-02 20:08:46 +02:00
// First compute where the tab should be
2019-04-13 09:46:23 +02:00
const pos = await config . getAsync ( "tabopenpos" )
2018-06-02 20:08:46 +02:00
let index = ( await activeTab ( ) ) . index + 1
switch ( pos ) {
case "last" :
2018-09-10 07:05:47 +02:00
index = - 1
2018-06-02 20:08:46 +02:00
break
case "related" :
// How do we simulate that?
break
}
// Then make sure the tab is made active and moved to the right place
// when it is opened in the current window
2019-04-13 09:46:23 +02:00
const selecttab = tab = > {
2018-06-02 20:08:46 +02:00
browser . tabs . onCreated . removeListener ( selecttab )
tabSetActive ( tab . id )
browser . tabs . move ( tab . id , { index } )
}
browser . tabs . onCreated . addListener ( selecttab )
2019-01-22 07:05:45 +01:00
try {
if ( ( await browser . runtime . getPlatformInfo ( ) ) . os === "mac" ) {
if ( ( await browser . windows . getCurrent ( ) ) . incognito ) {
throw "nativeopen isn't supported in private mode on OSX. Consider installing Linux or Windows :)."
}
2019-04-13 09:46:23 +02:00
const osascriptArgs = [ "-e 'on run argv'" , "-e 'tell application \"Firefox\" to open location item 1 of argv'" , "-e 'end run'" ]
2019-01-22 07:05:45 +01:00
await Native . run ( "osascript " + osascriptArgs . join ( " " ) + " " + url )
} else {
2019-05-22 18:31:29 +02:00
const os = ( await browser . runtime . getPlatformInfo ( ) ) . os
2019-02-13 07:39:39 +01:00
if ( firefoxArgs . length === 0 ) {
try {
2019-05-19 17:00:52 +02:00
const profile = await Native . getProfile ( )
if ( profile . Name !== undefined ) {
firefoxArgs = [ ` -p ${ profile . Name } ` ]
} else if ( profile . absolutePath !== undefined ) {
2019-05-22 18:31:29 +02:00
if ( os === "win" ) {
firefoxArgs = [ ` --profile " ${ profile . absolutePath } " ` ]
} else {
firefoxArgs = [ ` --profile ' ${ profile . absolutePath } ' ` ]
}
2019-05-19 17:00:52 +02:00
}
2019-02-13 07:39:39 +01:00
} catch ( e ) {
logger . debug ( e )
firefoxArgs = [ ]
}
firefoxArgs . push ( "--new-tab" )
}
2019-03-06 06:49:47 +01:00
let escapedUrl = url
// On linux, we need to quote and escape single quotes in the
// url, otherwise an attacker could create an anchor with a url
// like 'file:// && $(touch /tmp/dead)' and achieve remote code
// execution when the user tries to follow it with `hint -W tabopen`
// But windows treats single quotes as "open this file from the
// user's directory", so we need to use double quotes there
2019-05-22 18:31:29 +02:00
if ( os === "win" ) {
2019-03-06 06:49:47 +01:00
escapedUrl = ` " ${ escapedUrl . replace ( /\\/g , "\\\\" ) . replace ( /"/g , '\\"' ) } " `
} else {
2019-04-03 13:33:03 +02:00
escapedUrl = ` ' ${ escapedUrl . replace ( /'/g , '"\'"' ) } ' `
2019-03-06 06:49:47 +01:00
}
await Native . run ( ` ${ config . get ( "browser" ) } ${ firefoxArgs . join ( " " ) } ${ escapedUrl } ` )
2019-01-22 07:05:45 +01:00
}
setTimeout ( ( ) = > browser . tabs . onCreated . removeListener ( selecttab ) , 100 )
} catch ( e ) {
browser . tabs . onCreated . removeListener ( selecttab )
throw e
2018-05-22 00:49:42 +07:00
}
2018-04-20 22:06:06 +01:00
}
}
2018-04-26 18:24:34 +01:00
/ * *
* Run command in / b i n / s h ( u n l e s s y o u ' r e o n W i n d o w s ) , a n d p r i n t t h e o u t p u t i n t h e c o m m a n d l i n e . N o n - z e r o e x i t c o d e s a n d s t d e r r a r e i g n o r e d , c u r r e n t l y .
*
* Requires the native messenger , obviously .
*
2018-09-01 16:56:28 -07:00
* If you want to use a different shell , just prepend your command with whatever the invocation is and keep in mind that most shells require quotes around the command to be executed , e . g . ` :exclaim xonsh -c "1+2" ` .
2018-04-26 18:24:34 +01:00
*
* Aliased to ` ! ` but the exclamation mark * * must be followed with a space * * .
* /
2018-04-20 19:32:09 +01:00
//#background
export async function exclaim ( . . . str : string [ ] ) {
2018-10-08 18:25:37 +01:00
if ( await Native . nativegate ( ) ) {
fillcmdline ( ( await Native . run ( str . join ( " " ) ) ) . content )
}
2018-04-20 19:47:18 +01:00
} // should consider how to give option to fillcmdline or not. We need flags.
2018-04-20 19:32:09 +01:00
2018-04-26 20:54:32 +01:00
/ * *
* Like exclaim , but without any output to the command line .
* /
//#background
export async function exclaim_quiet ( . . . str : string [ ] ) {
2018-10-08 18:25:37 +01:00
if ( await Native . nativegate ( ) ) {
return ( await Native . run ( str . join ( " " ) ) ) . content
}
2018-04-26 20:54:32 +01:00
}
2018-04-26 18:24:34 +01:00
/ * *
* Tells you if the native messenger is installed and its version .
*
* /
2018-04-17 18:45:54 +01:00
//#background
export async function native() {
2018-04-26 19:35:41 +01:00
const version = await Native . getNativeMessengerVersion ( true )
2018-04-17 18:45:54 +01:00
if ( version !== undefined ) fillcmdline ( "# Native messenger is correctly installed, version " + version )
else fillcmdline ( "# Native messenger not found. Please run `:installnative` and follow the instructions." )
}
/ * *
2018-09-06 10:02:31 -05:00
* Simply copies "curl -fsSl https://raw.githubusercontent.com/tridactyl/tridactyl/master/native/install.sh | bash" to the clipboard and tells the user to run it .
2018-04-17 18:45:54 +01:00
* /
//#background
2018-11-03 08:06:30 +01:00
export async function nativeinstall() {
2019-05-31 15:00:03 +01:00
const tag = TRI_VERSION . includes ( "pre" ) ? "master" : TRI_VERSION
2018-05-14 14:21:07 +10:00
if ( ( await browser . runtime . getPlatformInfo ( ) ) . os === "win" ) {
2019-05-31 15:00:03 +01:00
const installstr = ( await config . get ( "win_nativeinstallcmd" ) ) . replace ( "%WINTAG" , "-Tag " + tag )
2018-07-03 14:41:55 +01:00
await yank ( installstr )
2018-07-11 12:19:04 +10:00
fillcmdline ( "# Installation command copied to clipboard. Please paste and run it from cmd.exe, PowerShell, or MinTTY to install the native messenger." )
2018-05-14 14:21:07 +10:00
} else {
2019-05-31 15:00:03 +01:00
const installstr = ( await config . get ( "nativeinstallcmd" ) ) . replace ( "%TAG" , tag )
2018-07-03 14:41:55 +01:00
await yank ( installstr )
2018-05-14 14:21:07 +10:00
fillcmdline ( "# Installation command copied to clipboard. Please paste and run it in your shell to install the native messenger." )
}
2018-04-24 23:21:26 +01:00
}
2019-05-26 11:29:27 +00:00
/ * * W r i t e s c u r r e n t c o n f i g t o a f i l e .
TODO : Write documentation .
Available flags :
- ` --force ` will overwrite the config file if it exists .
@param string [ ] argArr an optional string of arguments to be parsed .
@returns the parsed config .
* /
//#background
export async function mktridactylrc ( . . . argArr : string [ ] ) {
let overwrite = false
const argParse = ( args : string [ ] ) : string [ ] = > {
if ( args [ 0 ] === "--force" ) {
overwrite = true
args . shift ( )
argParse ( args )
}
return args
}
const file = argParse ( argArr ) . join ( " " ) || undefined
//TODO: Add actual native messenger code here.
//if (await Native.nativegate("0.1.3")) if (!await rc.source(file)) logger.error("Could not find RC file")
const conf = config . parseConfig ( )
console . log ( conf )
console . log ( file )
console . log ( overwrite )
return conf
}
2018-05-10 21:22:14 +01:00
/ * *
* Runs an RC file from disk .
*
2019-05-08 17:35:42 +02:00
* If no argument given , it will try to open ~ /.tridactylrc, ~/ . config / tridactyl / tridactylrc or $XDG_CONFIG_HOME / tridactyl / tridactylrc in reverse order . You may use a ` _ ` in place of a leading ` . ` if you wish , e . g , if you use Windows .
2018-07-30 17:56:05 +01:00
*
* On Windows , the ` ~ ` expands to ` %USERPROFILE% ` .
2018-05-10 21:22:14 +01:00
*
2018-06-08 21:26:18 +01:00
* The RC file is just a bunch of Tridactyl excmds ( i . e , the stuff on this help page ) . Settings persist in local storage ; add ` sanitise tridactyllocal tridactylsync ` to make it more Vim like . There ' s an [ example file ] ( https : //raw.githubusercontent.com/cmcaine/tridactyl/master/.tridactylrc) if you want it.
2018-05-10 22:25:31 +01:00
*
2018-05-10 21:37:13 +01:00
* @param fileArr the file to open . Must be an absolute path , but can contain environment variables and things like ~ .
2018-05-10 21:22:14 +01:00
* /
//#background
export async function source ( . . . fileArr : string [ ] ) {
const file = fileArr . join ( " " ) || undefined
2019-04-03 07:14:57 +02:00
if ( ( await Native . nativegate ( "0.1.3" ) ) && ! ( await rc . source ( file ) ) ) {
2019-04-03 07:03:03 +02:00
logger . error ( "Could not find RC file" )
}
2018-05-10 21:22:14 +01:00
}
/ * *
* Same as [ [ source ] ] but suppresses all errors
* /
//#background
export async function source_quiet ( . . . fileArr : string [ ] ) {
try {
const file = fileArr . join ( " " ) || undefined
if ( await Native . nativegate ( "0.1.3" , false ) ) rc . source ( file )
} catch ( e ) {
logger . info ( "Automatic loading of RC file failed." )
}
}
2018-04-26 18:24:34 +01:00
/ * *
* Updates the native messenger if it is installed , using our GitHub repo . This is run every time Tridactyl is updated .
*
* If you want to disable this , or point it to your own native messenger , edit the ` nativeinstallcmd ` setting .
* /
2018-04-24 23:21:26 +01:00
//#background
2018-04-25 12:50:15 +01:00
export async function updatenative ( interactive = true ) {
2019-05-31 15:00:03 +01:00
const tag = TRI_VERSION . includes ( "pre" ) ? "master" : TRI_VERSION
2018-05-09 22:22:47 +02:00
if ( await Native . nativegate ( "0" , interactive ) ) {
2018-04-28 18:06:18 +01:00
if ( ( await browser . runtime . getPlatformInfo ( ) ) . os === "mac" ) {
if ( interactive ) logger . error ( "Updating the native messenger on OSX is broken. Please use `:installnative` instead." )
return
}
2018-05-14 14:21:07 +10:00
if ( ( await browser . runtime . getPlatformInfo ( ) ) . os === "win" ) {
2019-05-31 15:00:03 +01:00
await Native . run ( ( await config . get ( "win_nativeinstallcmd" ) ) . replace ( "%WINTAG" , "-Tag " + tag ) )
2018-05-14 14:21:07 +10:00
} else {
2019-05-31 15:00:03 +01:00
await Native . run ( ( await config . get ( "nativeinstallcmd" ) ) . replace ( "%TAG" , tag ) )
2018-05-14 14:21:07 +10:00
}
2018-04-25 12:50:15 +01:00
if ( interactive ) native ( )
2018-04-24 23:21:26 +01:00
}
2018-04-17 18:45:54 +01:00
}
2018-04-18 21:49:33 +01:00
2018-05-10 16:27:58 +02:00
/ * *
* Restarts firefox with the same commandline arguments .
*
* Warning : This can kill your tabs , especially if you :restart several times
* in a row
* /
//#background
export async function restart() {
2018-05-28 00:32:30 +10:00
const profiledir = await Native . getProfileDir ( )
const browsercmd = await config . get ( "browser" )
2018-05-23 18:07:32 +10:00
if ( ( await browser . runtime . getPlatformInfo ( ) ) . os === "win" ) {
2019-04-13 09:46:23 +02:00
const reply = await Native . winFirefoxRestart ( profiledir , browsercmd )
2018-05-24 13:11:02 +10:00
logger . info ( "[+] win_firefox_restart 'reply' = " + JSON . stringify ( reply ) )
2019-04-03 19:09:49 +02:00
if ( Number ( reply . code ) === 0 ) {
fillcmdline ( "#" + reply . content )
2018-05-23 18:07:32 +10:00
qall ( )
} else {
2019-04-03 19:09:49 +02:00
fillcmdline ( "#" + reply . error )
2018-05-23 18:07:32 +10:00
}
} else {
2018-10-04 10:04:20 +02:00
const firefox = ( await Native . ff_cmdline ( ) ) . join ( " " )
2018-05-23 18:07:32 +10:00
// Wait for the lock to disappear, then wait a bit more, then start firefox
2018-05-28 00:32:30 +10:00
Native . run ( ` while readlink ${ profiledir } /lock ; do sleep 1 ; done ; sleep 1 ; ${ firefox } ` )
2018-05-23 18:07:32 +10:00
qall ( )
}
2018-05-10 16:27:58 +02:00
}
2018-09-27 17:53:17 +02:00
/ * * D o w n l o a d t h e c u r r e n t d o c u m e n t .
*
* If you have the native messenger v >= 0.1 . 9 installed , the function accepts one optional argument , filename , which can be :
* - An absolute path
* - A path starting with ~ , which will be expanded to your home directory
* - A relative path , relative to the native messenger executable ( e . g . ~ / . l o c a l / s h a r e / t r i d a c t y l o n l i n u x ) .
2018-09-30 18:56:56 +01:00
* If filename is not given , a download dialogue will be opened . If filename is a directory , the file will be saved inside of it , its name being inferred from the URL . If the directories mentioned in the path do not exist or if a file already exists at this path , the file will be kept in your downloads folder and an error message will be given .
*
* * * NB * * : if a non - default save location is chosen , Firefox ' s download manager will say the file is missing . It is not - it is where you asked it to be saved .
2018-09-27 17:53:17 +02:00
*
* @param filename The name the file should be saved as .
* /
//#content
export async function saveas ( . . . filename : string [ ] ) {
if ( filename . length > 0 ) {
return Messaging . message ( "download_background" , "downloadUrlAs" , [ window . location . href , filename . join ( " " ) ] )
} else {
return Messaging . message ( "download_background" , "downloadUrl" , [ window . location . href , true ] )
}
}
2018-04-18 21:49:33 +01:00
// }}}
2017-11-03 19:10:12 +00:00
/** @hidden */
2017-10-12 04:02:01 +01:00
//#background_helper
function tabSetActive ( id : number ) {
2018-07-10 19:35:21 +02:00
return browser . tabs . update ( id , { active : true } )
2017-10-12 04:02:01 +01:00
}
// }}}
// {{{ PAGE CONTEXT
2018-06-03 11:08:45 +02:00
/** @hidden */
2018-03-11 15:25:28 +01:00
//#content_helper
2018-06-03 11:08:45 +02:00
let JUMPED : boolean
2018-05-27 15:59:20 +02:00
2018-06-03 10:06:51 +02:00
/ * * T h i s i s u s e d a s a n I D f o r t h e c u r r e n t p a g e i n t h e j u m p l i s t .
2018-06-03 11:08:45 +02:00
It has a potentially confusing behavior : if you visit site A , then site B , then visit site A again , the jumplist that was created for your first visit on A will be re - used for your second visit .
An ideal solution would be to have a counter that is incremented every time a new page is visited within the tab and use that as the return value for getJumpPageId but this doesn ' t seem to be trivial to implement .
@hidden
2018-06-03 10:06:51 +02:00
* /
2018-03-11 15:25:28 +01:00
//#content_helper
2018-05-27 15:59:20 +02:00
export function getJumpPageId() {
return document . location . href
}
2018-06-03 11:08:45 +02:00
/** @hidden */
2018-03-11 15:25:28 +01:00
//#content_helper
2018-05-27 15:59:20 +02:00
export async function saveJumps ( jumps ) {
browserBg . sessions . setTabValue ( await activeTabId ( ) , "jumps" , jumps )
}
2018-06-03 11:08:45 +02:00
/ * * R e t u r n s a p r o m i s e f o r a n o b j e c t c o n t a i n i n g t h e j u m p l i s t o f a l l p a g e s a c c e s s e d i n t h e c u r r e n t t a b .
The keys of the object currently are the page ' s URL , however this might change some day . Use [ [ getJumpPageId ] ] to access the jumplist of a specific page .
@hidden
* /
2018-03-11 15:25:28 +01:00
//#content_helper
2018-05-27 15:59:20 +02:00
export async function curJumps() {
2019-04-13 09:46:23 +02:00
const tabid = await activeTabId ( )
2018-05-27 15:59:20 +02:00
let jumps = await browserBg . sessions . getTabValue ( tabid , "jumps" )
if ( ! jumps ) jumps = { }
2018-06-03 10:06:51 +02:00
// This makes sure that `key` exists in `obj`, setting it to `def` if it doesn't
2019-04-13 09:46:23 +02:00
const ensure = ( obj , key , def ) = > {
2018-05-27 15:59:20 +02:00
if ( obj [ key ] === null || obj [ key ] === undefined ) obj [ key ] = def
}
2019-04-13 09:46:23 +02:00
const page = getJumpPageId ( )
2018-05-27 15:59:20 +02:00
ensure ( jumps , page , { } )
2019-04-13 09:46:23 +02:00
const dummy = new UIEvent ( "scroll" )
2018-06-03 10:06:51 +02:00
ensure ( jumps [ page ] , "list" , [ { x : dummy.pageX , y : dummy.pageY } ] )
2018-05-27 15:59:20 +02:00
ensure ( jumps [ page ] , "cur" , 0 )
saveJumps ( jumps )
return jumps
}
2018-03-11 15:25:28 +01:00
2018-06-03 11:08:45 +02:00
/** Calls [[jumpprev]](-n) */
2018-03-11 15:25:28 +01:00
//#content
export function jumpnext ( n = 1 ) {
2018-05-05 18:12:01 +02:00
jumpprev ( - n )
2018-03-11 15:25:28 +01:00
}
/ * * S i m i l a r t o P e n t a d a c t y l o r v i m ' s j u m p l i s t .
2018-10-07 16:35:35 +02:00
*
* When you scroll on a page , either by using the mouse or Tridactyl 's key bindings, your position in the page will be saved after jumpdelay milliseconds (`:get jumpdelay` to know how many milliseconds that is). If you scroll again, you' ll be able to go back to your previous position by using ` :jumpprev 1 ` . If you need to go forward in the jumplist , use ` :jumpprev -1 ` .
*
* Known bug : Tridactyl will use the same jumplist for multiple visits to a same website in the same tab , see [ github issue 834 ] ( https : //github.com/tridactyl/tridactyl/issues/834).
2018-06-03 11:08:45 +02:00
* /
2018-03-11 15:25:28 +01:00
//#content
2018-05-05 18:12:01 +02:00
export function jumpprev ( n = 1 ) {
2018-05-27 15:59:20 +02:00
curJumps ( ) . then ( alljumps = > {
2019-04-13 09:46:23 +02:00
const jumps = alljumps [ getJumpPageId ( ) ]
const current = jumps . cur - n
2018-05-27 15:59:20 +02:00
if ( current < 0 ) {
jumps . cur = 0
saveJumps ( alljumps )
return back ( - current )
} else if ( current >= jumps . list . length ) {
jumps . cur = jumps . list . length - 1
saveJumps ( alljumps )
return forward ( current - jumps . list . length + 1 )
}
jumps . cur = current
2019-04-13 09:46:23 +02:00
const p = jumps . list [ jumps . cur ]
2018-05-27 15:59:20 +02:00
saveJumps ( alljumps )
JUMPED = true
window . scrollTo ( p . x , p . y )
} )
2018-03-11 15:25:28 +01:00
}
/ * * C a l l e d o n ' s c r o l l ' e v e n t s .
If you want to have a function that moves within the page but doesn ' t add a
location to the jumplist , make sure to set JUMPED to true before moving
around .
The setTimeout call is required because sometimes a user wants to move
somewhere by pressing 'j' multiple times and we don ' t want to add the
in - between locations to the jump list
2018-06-03 11:08:45 +02:00
@hidden
2018-03-11 15:25:28 +01:00
* /
//#content_helper
export function addJump ( scrollEvent : UIEvent ) {
if ( JUMPED ) {
JUMPED = false
return
}
2019-04-13 09:46:23 +02:00
const pageX = scrollEvent . pageX
const pageY = scrollEvent . pageY
2018-05-27 15:59:20 +02:00
// Get config for current page
curJumps ( ) . then ( alljumps = > {
2019-04-13 09:46:23 +02:00
const jumps = alljumps [ getJumpPageId ( ) ]
2018-05-27 15:59:20 +02:00
// Prevent pending jump from being registered
clearTimeout ( jumps . timeoutid )
// Schedule the registering of the current jump
jumps . timeoutid = setTimeout ( ( ) = > {
2019-04-13 09:46:23 +02:00
const list = jumps . list
2018-05-27 15:59:20 +02:00
// if the page hasn't moved, stop
2019-04-10 07:21:21 +02:00
if ( list [ jumps . cur ] . x === pageX && list [ jumps . cur ] . y === pageY ) return
2018-05-27 15:59:20 +02:00
// Store the new jump
// Could removing all jumps from list[cur] to list[list.length] be
// a better/more intuitive behavior?
list . push ( { x : pageX , y : pageY } )
jumps . cur = jumps . list . length - 1
saveJumps ( alljumps )
2018-09-25 18:26:06 +02:00
} , config . get ( "jumpdelay" ) )
2018-05-27 15:59:20 +02:00
} )
2018-03-11 15:25:28 +01:00
}
//#content_helper
2018-05-05 18:12:01 +02:00
document . addEventListener ( "scroll" , addJump )
2018-05-27 15:59:20 +02:00
// Try to restore the previous jump position every time a page is loaded
//#content_helper
2018-06-24 05:49:52 +02:00
document . addEventListener ( "load" , ( ) = > curJumps ( ) . then ( ( ) = > jumpprev ( 0 ) ) )
2018-03-11 15:25:28 +01:00
2017-11-09 21:01:57 +00:00
/** Blur (unfocus) the active element */
//#content
export function unfocus() {
2019-04-12 05:54:31 +02:00
( document . activeElement as HTMLInputElement ) . blur ( )
Janky proof of concept for per-tab state, incl mode and parsers
I've been programming on pure instinct for like five hours and I'm not
sure what exactly I did any more. I *think* that what I did was
something like:
* Split `state.ts` into `state.ts` and `content_state.ts`, then
removed mode from state and moved it to content_state.
* Made the types in content_state.ts a hair more powerful
* Fixed all errors resulting from those two changes, in the process
swapping state out for content_state wherever appropriate.
* Moved a couple excmds from background into content since they
depended heavily on the mode and should live with the mode
* Split the controller in half, moving the parser logic to the content
script and leaving acceptExCmds in the background
version. content_controller forwards excmds to background script
using messaging.
* Nuked the keydown_* code, since we no longer need to forward keys to
the background.
* Went around fixing everything resulting from *those* changes, mostly
the various perversions of keydown listeners
* Various tweaks here and there that probably didn't do anything but I
was just changing things at random a third of the time and I really
need to go through this and clean it up.
Things that work:
* excmds are nice now. each tab has its own commandline; they stay
open when you navigate between tabs and you can come back to them
after doing stuff in another tab and finish your command line input.
* keybinds that don't don't involve mode switching: `[[`, `]]`, `j`,
`k`, `r`, etc, all still functional.
* You can turn on hinting mode and, again, navigate to another tab
which will *not* be in hinting mode, do whatever you need to do
there including excmds or more hinting, and then come back to finish
the hint you started in the first tab
* You can exit hint mode by pressing escape. :P
Things that I broke:
* ...Actually quite a bunch of stuff, I think.
* I don't seem to be able to *finish* a hint any more. Sometimes I
can't even get one key in.
2018-09-01 02:58:21 -07:00
contentState . mode = "normal"
2017-11-09 21:01:57 +00:00
}
2018-06-03 11:08:45 +02:00
/ * * S c r o l l s t h e w i n d o w o r a n y s c r o l l a b l e c h i l d e l e m e n t b y a p i x e l s o n t h e h o r i z o n t a l a x i s a n d b p i x e l s o n t h e v e r t i c a l a x i s .
* /
2017-10-12 04:02:01 +01:00
//#content
2018-05-26 17:31:27 +02:00
export async function scrollpx ( a : number , b : number ) {
2018-09-14 10:30:26 +01:00
if ( ! ( await scrolling . scroll ( a , b , document . documentElement ) ) ) scrolling . recursiveScroll ( a , b )
2017-10-12 04:02:01 +01:00
}
2017-12-08 21:11:40 -08:00
/ * * I f t w o n u m b e r s a r e g i v e n , t r e a t a s x a n d y v a l u e s t o g i v e t o w i n d o w . s c r o l l T o
2018-10-17 21:05:19 +01:00
If one number is given , scroll to that percentage along a chosen axis , defaulting to the y - axis . If the number has 'c' appended to it , it will be interpreted in radians .
2018-06-03 11:08:45 +02:00
Note that if ` a ` is 0 or 100 and if the document is not scrollable in the given direction , Tridactyl will attempt to scroll the first scrollable element until it reaches the very bottom of that element .
2018-10-17 21:05:19 +01:00
Examples :
2018-11-20 11:23:34 -08:00
- ` scrollto 50 ` - > scroll halfway down the page .
- ` scrollto 3.14c ` - > scroll approximately 49.97465213 % of the way down the page .
2017-10-12 04:02:01 +01:00
* /
//#content
2018-10-11 13:52:28 +01:00
export function scrollto ( a : number | string , b : number | "x" | "y" = "y" ) {
2019-04-10 07:21:21 +02:00
if ( typeof a === "string" && a . match ( /c$/i ) ) {
2018-10-11 13:52:28 +01:00
a = ( Number ( a . replace ( /c$/ , "" ) ) * 100 ) / ( 2 * Math . PI )
}
2017-10-12 04:02:01 +01:00
a = Number ( a )
2019-04-13 09:46:23 +02:00
const elem = window . document . scrollingElement || window . document . documentElement
const percentage = a . clamp ( 0 , 100 )
2017-12-08 21:11:40 -08:00
if ( b === "y" ) {
2019-04-13 09:46:23 +02:00
const top = elem . getClientRects ( ) [ 0 ] . top
2018-09-14 10:30:26 +01:00
window . scrollTo ( window . scrollX , ( percentage * elem . scrollHeight ) / 100 )
2019-04-10 07:21:21 +02:00
if ( top === elem . getClientRects ( ) [ 0 ] . top && ( percentage === 0 || percentage === 100 ) ) {
2018-04-22 18:20:37 +02:00
// scrollTo failed, if the user wants to go to the top/bottom of
2018-05-26 17:31:27 +02:00
// the page try scrolling.recursiveScroll instead
2019-04-10 07:21:21 +02:00
scrolling . recursiveScroll ( window . scrollX , 1073741824 * ( percentage === 0 ? - 1 : 1 ) , document . documentElement )
2018-04-22 18:20:37 +02:00
}
2018-04-13 19:28:03 +01:00
} else if ( b === "x" ) {
2019-04-13 09:46:23 +02:00
const left = elem . getClientRects ( ) [ 0 ] . left
2018-09-14 10:30:26 +01:00
window . scrollTo ( ( percentage * elem . scrollWidth ) / 100 , window . scrollY )
2019-04-10 07:21:21 +02:00
if ( left === elem . getClientRects ( ) [ 0 ] . left && ( percentage === 0 || percentage === 100 ) ) {
scrolling . recursiveScroll ( 1073741824 * ( percentage === 0 ? - 1 : 1 ) , window . scrollX , document . documentElement )
2018-04-22 18:20:37 +02:00
}
2017-12-08 21:11:40 -08:00
} else {
window . scrollTo ( a , Number ( b ) ) // a,b numbers
}
2017-10-12 04:02:01 +01:00
}
2018-06-03 11:08:45 +02:00
/** @hidden */
2018-05-29 19:02:46 +02:00
//#content_helper
let lineHeight = null
2018-06-03 11:08:45 +02:00
/ * * S c r o l l s t h e d o c u m e n t o f i t s f i r s t s c r o l l a b l e c h i l d e l e m e n t b y n l i n e s .
2018-04-11 23:49:36 +02:00
*
2018-06-03 11:08:45 +02:00
* The height of a line is defined by the site 's CSS. If Tridactyl can' t get it , it ' ll default to 22 pixels .
2018-04-13 19:28:03 +01:00
* /
2017-10-12 04:02:01 +01:00
//#content
export function scrollline ( n = 1 ) {
2018-05-29 19:02:46 +02:00
if ( lineHeight === null ) {
2019-04-13 09:46:23 +02:00
const getLineHeight = elem = > {
2018-07-06 07:06:05 +02:00
// Get line height
const cssHeight = window . getComputedStyle ( elem ) . getPropertyValue ( "line-height" )
// Remove the "px" at the end
2019-04-05 13:16:04 +02:00
return parseInt ( cssHeight . substr ( 0 , cssHeight . length - 2 ) , 10 )
2018-07-06 07:06:05 +02:00
}
lineHeight = getLineHeight ( document . documentElement )
if ( ! lineHeight ) lineHeight = getLineHeight ( document . body )
2018-05-29 19:02:46 +02:00
// Is there a better way to compute a fallback? Maybe fetch from about:preferences?
if ( ! lineHeight ) lineHeight = 22
2018-04-11 08:23:11 +02:00
}
2018-05-29 20:43:31 +02:00
scrolling . recursiveScroll ( 0 , lineHeight * n )
2017-10-12 04:02:01 +01:00
}
2018-02-27 22:30:58 +01:00
2018-06-03 11:08:45 +02:00
/ * * S c r o l l s t h e d o c u m e n t b y n p a g e s .
*
* The height of a page is the current height of the window .
* /
2017-10-12 04:02:01 +01:00
//#content
export function scrollpage ( n = 1 ) {
2018-02-27 22:30:58 +01:00
scrollpx ( 0 , window . innerHeight * n )
2017-10-12 04:02:01 +01:00
}
2019-01-22 15:26:15 +00:00
/ * *
* Rudimentary find mode , left unbound by default as we don ' t currently support ` incsearch ` . Suggested binds :
*
* bind / fillcmdline find
* bind ? fillcmdline find - ?
* bind n findnext 1
* bind N findnext - 1
* bind , < Space > nohlsearch
2018-04-12 22:33:10 +01:00
*
2018-06-24 16:36:49 +02:00
* Argument : A string you want to search for .
2018-04-13 19:28:03 +01:00
*
2018-06-24 16:36:49 +02:00
* This function accepts two flags : ` -? ` to search from the bottom rather than the top and ` -: n ` to jump directly to the nth match .
*
* The behavior of this function is affected by the following setting :
2018-04-12 22:33:10 +01:00
*
2019-01-22 15:26:15 +00:00
* ` findcase ` : either "smart" , "sensitive" or "insensitive" . If "smart" , find will be case - sensitive if the pattern contains uppercase letters .
2018-04-13 19:28:03 +01:00
*
2019-01-22 15:26:15 +00:00
* Known bugs : find will currently happily jump to a non - visible element , and pressing n or N without having searched for anything will cause an error .
2018-04-12 22:33:10 +01:00
* /
Janky proof of concept for per-tab state, incl mode and parsers
I've been programming on pure instinct for like five hours and I'm not
sure what exactly I did any more. I *think* that what I did was
something like:
* Split `state.ts` into `state.ts` and `content_state.ts`, then
removed mode from state and moved it to content_state.
* Made the types in content_state.ts a hair more powerful
* Fixed all errors resulting from those two changes, in the process
swapping state out for content_state wherever appropriate.
* Moved a couple excmds from background into content since they
depended heavily on the mode and should live with the mode
* Split the controller in half, moving the parser logic to the content
script and leaving acceptExCmds in the background
version. content_controller forwards excmds to background script
using messaging.
* Nuked the keydown_* code, since we no longer need to forward keys to
the background.
* Went around fixing everything resulting from *those* changes, mostly
the various perversions of keydown listeners
* Various tweaks here and there that probably didn't do anything but I
was just changing things at random a third of the time and I really
need to go through this and clean it up.
Things that work:
* excmds are nice now. each tab has its own commandline; they stay
open when you navigate between tabs and you can come back to them
after doing stuff in another tab and finish your command line input.
* keybinds that don't don't involve mode switching: `[[`, `]]`, `j`,
`k`, `r`, etc, all still functional.
* You can turn on hinting mode and, again, navigate to another tab
which will *not* be in hinting mode, do whatever you need to do
there including excmds or more hinting, and then come back to finish
the hint you started in the first tab
* You can exit hint mode by pressing escape. :P
Things that I broke:
* ...Actually quite a bunch of stuff, I think.
* I don't seem to be able to *finish* a hint any more. Sometimes I
can't even get one key in.
2018-09-01 02:58:21 -07:00
//#content
2018-06-24 13:36:06 +02:00
export function find ( . . . args : string [ ] ) {
2019-05-28 23:12:04 +02:00
const flagpos = args . indexOf ( "-?" )
2019-04-13 09:46:23 +02:00
const reverse = flagpos >= 0
2018-06-24 13:36:06 +02:00
if ( reverse ) args . splice ( flagpos , 1 )
2019-02-07 08:33:30 +01:00
const searchQuery = args . join ( " " )
2019-05-28 23:12:04 +02:00
return finding . jumpToMatch ( searchQuery , reverse )
2018-03-18 15:08:09 +00:00
}
2018-06-24 16:36:49 +02:00
/ * * J u m p t o t h e n e x t s e a r c h e d p a t t e r n .
2018-04-12 22:33:10 +01:00
*
* @param number - number of words to advance down the page ( use 1 for next word , - 1 for previous )
2018-04-13 19:28:03 +01:00
*
2018-04-12 22:33:10 +01:00
* /
Janky proof of concept for per-tab state, incl mode and parsers
I've been programming on pure instinct for like five hours and I'm not
sure what exactly I did any more. I *think* that what I did was
something like:
* Split `state.ts` into `state.ts` and `content_state.ts`, then
removed mode from state and moved it to content_state.
* Made the types in content_state.ts a hair more powerful
* Fixed all errors resulting from those two changes, in the process
swapping state out for content_state wherever appropriate.
* Moved a couple excmds from background into content since they
depended heavily on the mode and should live with the mode
* Split the controller in half, moving the parser logic to the content
script and leaving acceptExCmds in the background
version. content_controller forwards excmds to background script
using messaging.
* Nuked the keydown_* code, since we no longer need to forward keys to
the background.
* Went around fixing everything resulting from *those* changes, mostly
the various perversions of keydown listeners
* Various tweaks here and there that probably didn't do anything but I
was just changing things at random a third of the time and I really
need to go through this and clean it up.
Things that work:
* excmds are nice now. each tab has its own commandline; they stay
open when you navigate between tabs and you can come back to them
after doing stuff in another tab and finish your command line input.
* keybinds that don't don't involve mode switching: `[[`, `]]`, `j`,
`k`, `r`, etc, all still functional.
* You can turn on hinting mode and, again, navigate to another tab
which will *not* be in hinting mode, do whatever you need to do
there including excmds or more hinting, and then come back to finish
the hint you started in the first tab
* You can exit hint mode by pressing escape. :P
Things that I broke:
* ...Actually quite a bunch of stuff, I think.
* I don't seem to be able to *finish* a hint any more. Sometimes I
can't even get one key in.
2018-09-01 02:58:21 -07:00
//#content
2019-02-07 07:52:47 +01:00
export function findnext ( n = 1 ) {
return finding . jumpToNextMatch ( n )
2018-06-24 13:36:06 +02:00
}
2018-06-24 17:58:11 +02:00
//#content
2018-06-24 13:36:06 +02:00
export function clearsearchhighlight() {
2019-02-07 07:52:47 +01:00
return finding . removeHighlighting ( )
2017-10-12 04:02:01 +01:00
}
2017-11-03 19:10:12 +00:00
/** @hidden */
2017-10-12 04:02:01 +01:00
//#content_helper
function history ( n : number ) {
window . history . go ( n )
}
2017-11-19 06:05:15 +00:00
/** Navigate forward one page in history. */
2017-10-12 04:02:01 +01:00
//#content
export function forward ( n = 1 ) {
history ( n )
}
2017-11-19 06:05:15 +00:00
/** Navigate back one page in history. */
2017-10-12 04:02:01 +01:00
//#content
export function back ( n = 1 ) {
history ( n * - 1 )
}
/** Reload the next n tabs, starting with activeTab, possibly bypassingCache */
//#background
export async function reload ( n = 1 , hard = false ) {
2019-04-13 09:46:23 +02:00
const tabstoreload = await getnexttabs ( await activeTabId ( ) , n )
const reloadProperties = { bypassCache : hard }
2019-04-03 18:15:32 +02:00
tabstoreload . forEach ( n = > browser . tabs . reload ( n , reloadProperties ) )
2017-10-12 04:02:01 +01:00
}
2017-11-22 16:59:58 +00:00
/** Reloads all tabs, bypassing the cache if hard is set to true */
//#background
2018-04-13 19:28:03 +01:00
export async function reloadall ( hard = false ) {
2019-04-13 09:46:23 +02:00
const tabs = await browser . tabs . query ( { currentWindow : true } )
const reloadprops = { bypassCache : hard }
2019-04-03 18:15:32 +02:00
tabs . forEach ( tab = > browser . tabs . reload ( tab . id , reloadprops ) )
2017-11-22 16:59:58 +00:00
}
2018-10-19 12:39:48 +01:00
/** Reloads all tabs except the current one, bypassing the cache if hard is set to true */
//#background
export async function reloadallbut ( hard = false ) {
let tabs = await browser . tabs . query ( { currentWindow : true } )
2019-04-13 09:46:23 +02:00
const currId = await activeTabId ( )
2019-04-10 07:21:21 +02:00
tabs = tabs . filter ( tab = > tab . id !== currId )
2019-04-13 09:46:23 +02:00
const reloadprops = { bypassCache : hard }
2019-04-03 18:15:32 +02:00
tabs . forEach ( tab = > browser . tabs . reload ( tab . id , reloadprops ) )
2018-10-19 12:39:48 +01:00
}
2017-10-12 04:02:01 +01:00
/** Reload the next n tabs, starting with activeTab. bypass cache for all */
//#background
export async function reloadhard ( n = 1 ) {
reload ( n , true )
}
2018-05-04 23:05:00 +01:00
// I went through the whole list https://developer.mozilla.org/en-US/Firefox/The_about_protocol
// about:blank is even more special
/** @hidden */
2019-01-27 13:04:51 +01:00
export const ABOUT_WHITELIST = [ "about:license" , "about:logo" , "about:rights" , "about:blank" ]
2018-05-04 23:05:00 +01:00
2019-01-28 16:32:07 +00:00
/ * *
* Open a new page in the current tab .
2018-04-27 21:43:58 +01:00
*
2019-01-28 16:32:07 +00:00
* @param urlarr
2018-05-05 12:49:45 +01:00
*
2019-01-28 16:32:07 +00:00
* - 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 [ [ searchengine ] ]
2018-05-05 12:49:45 +01:00
*
2019-01-28 16:32:07 +00:00
* Related settings : [ [ searchengine ] ] , [ [ historyresults ] ]
2018-06-14 15:56:06 -04:00
*
2018-04-27 21:43:58 +01:00
* Can only open about : * or file : * URLs if you have the native messenger installed , and on OSX you must set ` browser ` to something that will open Firefox from a terminal pass it commmand line options .
*
* /
2017-10-12 04:02:01 +01:00
//#content
2018-04-19 16:35:10 +01:00
export async function open ( . . . urlarr : string [ ] ) {
2019-04-13 09:46:23 +02:00
const url = urlarr . join ( " " )
2019-01-27 13:04:51 +01:00
let p = Promise . resolve ( )
2018-04-19 16:35:10 +01:00
// Setting window.location to about:blank results in a page we can't access, tabs.update works.
2019-01-27 13:04:51 +01:00
if ( ! ABOUT_WHITELIST . includes ( url ) && url . match ( /^(about|file):.*/ ) ) {
2018-07-20 13:39:59 +01:00
// Open URLs that firefox won't let us by running `firefox <URL>` on the command line
2019-04-21 01:38:57 -07:00
p = nativeopen ( url )
2018-10-15 14:24:31 +08:00
} else if ( url . match ( /^javascript:/ ) ) {
2019-04-13 09:46:23 +02:00
const bookmarklet = url . replace ( /^javascript:/ , "" )
2019-04-12 05:54:31 +02:00
; ( document . body as any ) . append (
2019-02-15 15:33:25 +01:00
html `
< script >
$ { bookmarklet }
< / script >
` ,
)
2019-01-27 13:04:51 +01:00
} else {
2019-02-04 08:18:01 +01:00
p = activeTab ( ) . then ( tab = > openInTab ( tab , { } , urlarr ) )
2018-04-19 16:35:10 +01:00
}
2019-01-27 13:04:51 +01:00
return p
2017-10-12 04:02:01 +01:00
}
2018-10-08 09:53:20 +02:00
/ * *
* Works exactly like [ [ open ] ] , but only suggests bookmarks .
*
* @param opt Optional . Has to be ` -t ` in order to make bmarks open your bookmarks in a new tab .
* @param urlarr any argument accepted by [ [ open ] ] , or [ [ tabopen ] ] if opt is "-t"
* /
//#background
export async function bmarks ( opt : string , . . . urlarr : string [ ] ) {
2019-04-10 07:21:21 +02:00
if ( opt === "-t" ) return tabopen ( . . . urlarr )
2018-10-08 09:53:20 +02:00
else return open ( opt , . . . urlarr )
}
2018-05-29 11:27:45 +01:00
/ * *
* Like [ [ open ] ] but doesn ' t make a new entry in history .
* /
//#content
export async function open_quiet ( . . . urlarr : string [ ] ) {
2019-04-13 09:46:23 +02:00
const url = urlarr . join ( " " )
2018-05-29 11:27:45 +01:00
2019-02-04 08:18:01 +01:00
if ( ! ABOUT_WHITELIST . includes ( url ) && url . match ( /^(about|file):.*/ ) ) {
2019-04-21 01:38:57 -07:00
return nativeopen ( url )
2018-05-29 11:27:45 +01:00
}
2019-02-04 08:18:01 +01:00
return ownTab ( ) . then ( tab = > openInTab ( tab , { loadReplace : true } , urlarr ) )
2018-05-29 11:27:45 +01:00
}
2018-06-27 07:16:06 +02:00
/ * *
* If the url of the current document matches one of your search engines , will convert it to a list of arguments that open / tabopen will understand . If the url doesn ' t match any search engine , returns the url without modifications .
*
* For example , if you have searchurls . gi set to "https://www.google.com/search?q=%s&tbm=isch" , using this function on a page you opened using "gi butterflies" will return "gi butterflies" .
*
* This is useful when combined with fillcmdline , for example like this : ` bind O composite url2args | fillcmdline open ` .
*
* Note that this might break with search engines that redirect you to other pages / add GET parameters that do not exist in your searchurl .
* /
//#content
export async function url2args() {
2019-04-13 09:46:23 +02:00
const url = document . location . href
const searchurls = await config . getAsync ( "searchurls" )
2018-06-27 07:16:06 +02:00
let result = url
2019-04-13 09:46:23 +02:00
for ( const engine of Object . keys ( searchurls ) ) {
const [ beginning , end ] = [ . . . searchurls [ engine ] . split ( "%s" ) , "" ]
2018-06-27 07:16:06 +02:00
if ( url . startsWith ( beginning ) && url . endsWith ( end ) ) {
// Get the string matching %s
2018-06-28 06:48:54 +02:00
let encodedArgs = url . substring ( beginning . length )
encodedArgs = encodedArgs . substring ( 0 , encodedArgs . length - end . length )
// Remove any get parameters that might have been added by the search engine
// This works because if the user's query contains an "&", it will be encoded as %26
2019-04-13 09:46:23 +02:00
const amperpos = encodedArgs . search ( "&" )
2018-07-02 06:07:37 +02:00
if ( amperpos > 0 ) encodedArgs = encodedArgs . substring ( 0 , amperpos )
2018-06-28 06:48:54 +02:00
// Do transformations depending on the search engine
if ( beginning . search ( "duckduckgo" ) > 0 ) encodedArgs = encodedArgs . replace ( /\+/g , " " )
else if ( beginning . search ( "wikipedia" ) > 0 ) encodedArgs = encodedArgs . replace ( /_/g , " " )
2019-04-13 09:46:23 +02:00
const args = engine + " " + decodeURIComponent ( encodedArgs )
2018-06-27 07:16:06 +02:00
if ( args . length < result . length ) result = args
}
}
return result
}
2018-04-12 22:33:10 +01:00
/** @hidden */
2018-03-08 22:21:48 +01:00
//#content_helper
2019-04-10 06:47:46 +02:00
let sourceElement
2018-04-28 07:54:22 +02:00
/** @hidden */
//#content_helper
function removeSource() {
if ( sourceElement ) {
sourceElement . remove ( )
sourceElement = undefined
}
}
2018-10-07 16:35:35 +02:00
2018-04-28 07:54:22 +02:00
/ * * D i s p l a y t h e ( H T M L ) s o u r c e o f t h e c u r r e n t p a g e .
Behaviour can be changed by the 'viewsource' setting .
If the 'viewsource' setting is set to 'default' rather than 'tridactyl' ,
the url the source of which should be displayed can be given as argument .
Otherwise , the source of the current document will be displayed .
* /
2018-03-08 19:01:55 +01:00
//#content
export function viewsource ( url = "" ) {
2018-04-13 19:28:03 +01:00
if ( url === "" ) url = window . location . href
2018-03-08 22:21:48 +01:00
if ( config . get ( "viewsource" ) === "default" ) {
2018-03-08 22:32:11 +01:00
window . location . href = "view-source:" + url
2018-03-08 22:21:48 +01:00
return
}
if ( ! sourceElement ) {
2018-12-20 07:30:53 +01:00
sourceElement = CommandLineContent . executeWithoutCommandLine ( ( ) = > {
2019-04-13 09:46:23 +02:00
const pre = document . createElement ( "pre" )
2018-03-08 22:21:48 +01:00
pre . id = "TridactylViewsourceElement"
2018-03-17 12:31:27 +01:00
pre . className = "cleanslate " + config . get ( "theme" )
2018-03-08 22:21:48 +01:00
pre . innerText = document . documentElement . innerHTML
document . documentElement . appendChild ( pre )
2018-04-28 07:54:22 +02:00
window . addEventListener ( "popstate" , removeSource )
2018-03-08 22:21:48 +01:00
return pre
} )
} else {
sourceElement . parentNode . removeChild ( sourceElement )
sourceElement = undefined
2018-04-28 07:54:22 +02:00
window . removeEventListener ( "popstate" , removeSource )
2018-03-08 22:21:48 +01:00
}
2018-03-08 19:01:55 +01:00
}
2018-05-25 16:24:24 +01:00
/ * *
2018-11-04 17:20:01 +01:00
* Go to the homepages you have set with ` set homepages ["url1", "url2"] ` .
2018-05-25 16:24:24 +01:00
*
* @param all
2018-12-19 08:12:26 +01:00
* - if "true" , opens all homepages in new tabs
* - if "false" or not given , opens the last homepage in the current tab
2018-05-25 16:24:24 +01:00
*
* /
2017-11-30 13:50:10 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function home ( all : "false" | "true" = "false" ) {
2019-04-13 09:46:23 +02:00
const homepages = config . get ( "homepages" )
2018-04-13 19:28:03 +01:00
if ( homepages . length > 0 ) {
2017-12-02 12:30:04 +01:00
if ( all === "false" ) open ( homepages [ homepages . length - 1 ] )
2017-11-30 13:50:10 +00:00
else {
2018-04-13 19:28:03 +01:00
homepages . map ( t = > tabopen ( t ) )
2017-11-30 13:50:10 +00:00
}
}
}
2017-11-19 06:05:15 +00:00
/ * * S h o w t h i s p a g e .
2018-10-06 20:42:25 +02:00
` :help something ` jumps to the entry for something . Something can be an excmd , an alias for an excmd , a binding or a setting .
2018-06-07 12:52:11 +02:00
2018-10-06 20:42:25 +02:00
On the ex command page , the "nmaps" list is a list of all the bindings for the command you ' re seeing and the "exaliases" list lists all its aliases .
2018-06-07 12:52:11 +02:00
2019-02-08 07:40:24 +01:00
If there 's a conflict (e.g. you have a "go" binding that does something, a "go" excmd that does something else and a "go" setting that does a third thing), the binding is chosen first, then the setting, then the excmd. In such situations, if you want to let Tridactyl know you' re looking for something specfic , you can specify the following flags as first arguments :
` -a ` : look for an alias
` -b ` : look for a binding
` -e ` : look for an ex command
` -s ` : look for a setting
2018-06-07 12:52:11 +02:00
2018-08-30 13:47:35 +01:00
If the keyword you gave to ` :help ` is actually an alias for a composite command ( see [ [ composite ] ] ) , you will be taken to the help section for the first command of the pipeline . You will be able to see the whole pipeline by hovering your mouse over the alias in the "exaliases" list . Unfortunately there currently is no way to display these HTML tooltips from the keyboard .
2017-10-23 23:44:07 +01:00
2017-11-19 06:05:15 +00:00
e . g . ` :help bind `
* /
//#background
2019-02-08 07:40:24 +01:00
export async function help ( . . . helpItems : string [ ] ) {
const flags = {
// -a: look for an alias
"-a" : ( settings , helpItem ) = > {
2019-04-13 09:46:23 +02:00
const aliases = settings . exaliases
2019-02-08 07:40:24 +01:00
// As long as helpItem is an alias, try to resolve this alias to a real helpItem
2019-04-13 09:46:23 +02:00
const resolved = [ ]
2019-02-08 07:40:24 +01:00
while ( aliases [ helpItem ] ) {
resolved . push ( helpItem )
helpItem = aliases [ helpItem ] . split ( " " )
2019-04-10 07:21:21 +02:00
helpItem = helpItem [ 0 ] === "composite" ? helpItem [ 1 ] : helpItem [ 0 ]
2019-02-08 07:40:24 +01:00
// Prevent infinite loops
if ( resolved . includes ( helpItem ) ) break
2018-10-06 20:42:25 +02:00
}
2019-02-08 07:40:24 +01:00
if ( resolved . length > 0 ) {
2019-05-26 22:03:34 -07:00
return browser . runtime . getURL ( "static/docs/modules/_src_excmds_.html" ) + "#" + helpItem
2019-02-08 07:40:24 +01:00
}
return ""
} ,
// -b: look for a binding
"-b" : ( settings , helpItem ) = > {
2019-04-13 09:46:23 +02:00
for ( const mode of [ "nmaps" , "imaps" , "inputmaps" , "ignoremaps" ] ) {
const bindings = settings [ mode ]
2019-02-08 07:40:24 +01:00
// If 'helpItem' matches a binding, replace 'helpItem' with
// the command that would be executed when pressing the key
// sequence referenced by 'helpItem' and don't check other
// modes
if ( helpItem in bindings ) {
helpItem = bindings [ helpItem ] . split ( " " )
helpItem = [ "composite" , "fillcmdline" ] . includes ( helpItem [ 0 ] ) ? helpItem [ 1 ] : helpItem [ 0 ]
2019-05-26 22:03:34 -07:00
return browser . runtime . getURL ( "static/docs/modules/_src_excmds_.html" ) + "#" + helpItem
2019-02-08 07:40:24 +01:00
}
}
return ""
} ,
// -e: look for an excmd
2019-05-26 22:03:34 -07:00
"-e" : ( settings , helpItem ) = > browser . runtime . getURL ( "static/docs/modules/_src_excmds_.html" ) + "#" + helpItem ,
2019-02-08 07:40:24 +01:00
// -s: look for a setting
"-s" : ( settings , helpItem ) = > {
2018-10-06 20:42:25 +02:00
let subSettings = settings
2019-04-13 09:46:23 +02:00
const settingNames = helpItem . split ( "." )
2018-10-06 20:42:25 +02:00
let settingHelpAnchor = ""
// Try to match each piece of the path, this is done so that a correct object (e.g. followpagepatterns) with an incorrect key (e.g. nextt) will still match the parent object.
2019-04-13 09:46:23 +02:00
for ( const settingName of settingNames ) {
2018-10-06 20:42:25 +02:00
if ( settingName in subSettings ) {
settingHelpAnchor += settingName + "."
subSettings = subSettings [ settingName ]
}
}
2019-04-10 07:21:21 +02:00
if ( settingHelpAnchor !== "" ) {
2019-05-26 22:03:34 -07:00
return browser . runtime . getURL ( "static/docs/classes/_src_lib_config_.default_config.html" ) + "#" + settingHelpAnchor . slice ( 0 , - 1 )
2018-10-06 20:42:25 +02:00
}
2019-02-08 07:40:24 +01:00
return ""
} ,
}
let flag = ""
2019-04-03 07:03:03 +02:00
if ( helpItems . length > 0 && Object . keys ( flags ) . includes ( helpItems [ 0 ] ) ) {
flag = helpItems [ 0 ]
helpItems . splice ( 0 , 1 )
2018-06-03 10:21:05 +02:00
}
2018-10-06 20:42:25 +02:00
2019-02-08 07:40:24 +01:00
const subject = helpItems . join ( " " )
const settings = await config . getAsync ( )
let url = ""
// If the user did specify what they wanted, specifically look for it
2019-04-10 07:21:21 +02:00
if ( flag !== "" ) {
2019-02-08 07:40:24 +01:00
url = flags [ flag ] ( settings , subject )
}
// Otherwise or if it couldn't be found, try all possible items
2019-04-10 07:21:21 +02:00
if ( url === "" ) {
2019-02-08 07:40:24 +01:00
url = [ "-b" , "-s" , "-a" , "-e" ] . reduce ( ( acc , curFlag ) = > {
2019-04-10 07:21:21 +02:00
if ( acc !== "" ) return acc
2019-02-08 07:40:24 +01:00
return flags [ curFlag ] ( settings , subject )
} , "" )
}
2019-05-26 22:03:34 -07:00
if ( ( await activeTab ( ) ) . url . startsWith ( browser . runtime . getURL ( "static/docs/" ) ) ) {
2019-02-08 07:40:24 +01:00
open ( url )
2017-11-19 06:05:15 +00:00
} else {
2019-02-08 07:40:24 +01:00
tabopen ( url )
2017-11-19 06:05:15 +00:00
}
2017-10-23 23:44:07 +01:00
}
2018-04-13 23:30:53 +01:00
/ * * S t a r t t h e t u t o r i a l
2018-04-14 22:37:16 +01:00
* @param newtab - whether to start the tutorial in a newtab . Defaults to current tab .
2018-04-14 22:14:16 +01:00
* /
2018-04-13 23:30:53 +01:00
//#background
2018-04-14 22:37:16 +01:00
export async function tutor ( newtab? : string ) {
2019-05-26 22:03:34 -07:00
const tutor = browser . runtime . getURL ( "static/clippy/1-tutor.html" )
2018-04-14 22:37:16 +01:00
if ( newtab ) tabopen ( tutor )
else open ( tutor )
2018-04-13 23:30:53 +01:00
}
2018-05-23 15:39:40 +01:00
/ * *
* Display Tridactyl ' s contributors in order of commits in a user - friendly fashion
* /
//#background
2019-05-31 15:13:28 +02:00
export async function credits() {
2019-05-26 22:03:34 -07:00
const creditspage = browser . runtime . getURL ( "static/authors.html" )
2018-05-23 15:39:40 +01:00
tabopen ( creditspage )
}
2018-12-17 17:30:58 +00:00
/ * *
* Cover the current page in an overlay to prevent clicking on links with the mouse to force yourself to use hint mode . Get rid of it by reloading the page .
*
* Suggested usage : ` autocmd DocLoad .* no_mouse_mode `
*
* "There is no mouse" .
2019-05-28 17:08:13 +01:00
* /
//#content
export function no_mouse_mode() {
toys . no_mouse ( )
}
/ * *
* Matrix variant of [ [ no_mouse_mode ] ]
*
* "There is no mouse" .
2018-12-17 17:30:58 +00:00
*
* Coincidentally added to Tridactyl at the same time as we reached 1337 stars on GitHub .
* /
//#content
2019-05-28 17:08:13 +01:00
export function neo_mouse_mode() {
2018-12-17 17:30:58 +00:00
toys . jack_in ( )
}
2018-12-20 22:32:24 +00:00
/ * *
* Christmas variant of [ [ no_mouse_mode ] ] ( if you live in $DEFAULT hemisphere ) .
* /
//#content
export function snow_mouse_mode() {
toys . snow ( )
}
2017-11-03 19:10:12 +00:00
/** @hidden */
2017-11-22 22:21:46 +00:00
// Find clickable next-page/previous-page links whose text matches the supplied pattern,
// and return the last such link.
//
2018-02-18 18:21:59 +00:00
// If no matching link is found, return undefined.
2017-11-22 22:21:46 +00:00
//
// We return the last link that matches because next/prev buttons tend to be at the end of the page
// whereas lots of blogs have "VIEW MORE" etc. plastered all over their pages.
2018-02-18 18:21:59 +00:00
//#content_helper
2017-11-22 22:21:46 +00:00
function findRelLink ( pattern : RegExp ) : HTMLAnchorElement | null {
2018-02-18 18:21:59 +00:00
// querySelectorAll returns a "non-live NodeList" which is just a shit array without working reverse() or find() calls, so convert it.
2019-04-04 07:09:44 +02:00
/* tslint:disable:no-useless-cast */
2019-04-03 06:33:13 +02:00
const links = Array . from ( document . querySelectorAll ( "a[href]" ) as NodeListOf < HTMLAnchorElement > )
2017-11-22 22:21:46 +00:00
2018-02-18 18:21:59 +00:00
// Find the last link that matches the test
return links . reverse ( ) . find ( link = > pattern . test ( link . innerText ) )
2017-11-22 22:21:46 +00:00
2018-02-18 18:21:59 +00:00
// Note:
// `innerText` gives better (i.e. less surprising) results than `textContent`
// at the expense of being much slower, but that shouldn't be an issue here
// as it's a one-off operation that's only performed when we're leaving a page
2017-10-12 04:02:01 +01:00
}
2017-11-22 22:21:46 +00:00
/** @hidden */
// Return the last element in the document matching the supplied selector,
// or null if there are no matches.
function selectLast ( selector : string ) : HTMLElement | null {
2019-04-04 07:09:44 +02:00
/* tslint:disable:no-useless-cast */
2019-04-03 06:33:13 +02:00
const nodes = document . querySelectorAll ( selector ) as NodeListOf < HTMLElement >
2017-11-22 22:21:46 +00:00
return nodes . length ? nodes [ nodes . length - 1 ] : null
}
/ * * F i n d a l i k e l y n e x t / p r e v i o u s l i n k a n d f o l l o w i t
2018-02-18 18:23:21 +00:00
2018-02-19 01:34:15 +00:00
If a link or anchor element with rel = rel exists , use that , otherwise fall back to :
2018-03-14 21:34:30 +08:00
2018-02-19 01:34:15 +00:00
1 ) find the last anchor on the page with innerText matching the appropriate ` followpagepattern ` .
2 ) call [ [ urlincrement ] ] with 1 or - 1
2018-02-18 18:23:21 +00:00
If you want to support e . g . French :
` ` `
set followpagepatterns . next ^ ( next | newer | prochain ) \ b | » | >>
2019-01-02 16:51:12 +01:00
set followpagepatterns . prev ^ ( prev ( ious ) ? | older | précédent ) \ b | « | <<
2018-02-18 18:23:21 +00:00
` ` `
@param rel the relation of the target page to the current page : "next" or "prev"
* /
2017-10-12 04:02:01 +01:00
//#content
2018-04-13 19:28:03 +01:00
export function followpage ( rel : "next" | "prev" = "next" ) {
2019-04-03 06:33:13 +02:00
const link = selectLast ( ` link[rel~= ${ rel } ][href] ` ) as HTMLLinkElement
2017-10-12 04:02:01 +01:00
2017-11-22 22:21:46 +00:00
if ( link ) {
window . location . href = link . href
return
}
2017-10-12 04:02:01 +01:00
2019-04-03 06:33:13 +02:00
const anchor = ( selectLast ( ` a[rel~= ${ rel } ][href] ` ) || findRelLink ( new RegExp ( config . get ( "followpagepatterns" , rel ) , "i" ) ) ) as HTMLAnchorElement
2017-10-12 04:02:01 +01:00
2017-11-22 22:21:46 +00:00
if ( anchor ) {
2018-02-18 18:23:21 +00:00
DOM . mouseEvent ( anchor , "click" )
2018-02-19 01:34:15 +00:00
} else {
urlincrement ( rel === "next" ? 1 : - 1 )
2017-11-22 22:21:46 +00:00
}
2017-10-12 04:02:01 +01:00
}
2017-11-15 12:40:26 +00:00
2017-11-18 16:52:23 +00:00
/ * * I n c r e m e n t t h e c u r r e n t t a b U R L
*
* @param count the increment step , can be positive or negative
2018-04-13 19:28:03 +01:00
* /
2017-11-18 16:52:23 +00:00
//#content
2018-04-13 19:28:03 +01:00
export function urlincrement ( count = 1 ) {
2019-04-13 09:46:23 +02:00
const newUrl = UrlUtil . incrementUrl ( window . location . href , count )
2017-11-18 16:52:23 +00:00
if ( newUrl !== null ) {
2018-06-20 08:30:42 +02:00
// This might throw an error when using incrementurl on a moz-extension:// page if the page we're trying to access doesn't exist
try {
window . location . href = newUrl
} catch ( e ) {
logger . info ( ` urlincrement: Impossible to navigate to ${ newUrl } ` )
}
2017-11-18 16:52:23 +00:00
}
}
2017-11-19 16:55:18 +00:00
/ * * G o t o t h e r o o t d o m a i n o f t h e c u r r e n t U R L
* /
//#content
2018-04-13 19:28:03 +01:00
export function urlroot() {
2019-04-13 09:46:23 +02:00
const rootUrl = UrlUtil . getUrlRoot ( window . location )
2017-11-19 16:55:18 +00:00
if ( rootUrl !== null ) {
window . location . href = rootUrl . href
}
}
2017-11-21 10:52:30 +00:00
/ * * G o t o t h e p a r e n t U R L o f t h e c u r r e n t t a b ' s U R L
* /
//#content
2018-04-13 19:28:03 +01:00
export function urlparent ( count = 1 ) {
2019-04-13 09:46:23 +02:00
const parentUrl = UrlUtil . getUrlParent ( window . location , count )
2017-11-21 10:52:30 +00:00
if ( parentUrl !== null ) {
window . location . href = parentUrl . href
}
}
2017-12-04 03:12:19 +00:00
/ * *
* Open a URL made by modifying the current URL
*
2018-01-28 16:49:05 +00:00
* There are several modes :
*
* * Text replace mode : ` urlmodify -t <old> <new> `
*
* Replaces the first instance of the text ` old ` with ` new ` .
* * ` http://example.com ` - > ( ` -t exa peta ` ) - > ` http://petample.com `
*
* * Regex replacment mode : ` urlmodify -r <regexp> <new> [flags] `
*
* Replaces the first match of the ` regexp ` with ` new ` . You can use
* flags ` i ` and ` g ` to match case - insensitively and to match
* all instances respectively
* * ` http://example.com ` - > ( ` -r [ea] X g ` ) - > ` http://XxXmplX.com `
*
2019-05-24 22:59:12 +10:00
* * Query set mode : ` urlmodify -s <query> <value> `
*
* Sets the value of a query to be a specific one . If the query already
* exists , it will be replaced .
* * ` http://e.com?id=abc ` - > ( ` -s foo bar ` ) - > ` http://e.com?id=abc&foo=bar
*
2018-01-28 16:49:05 +00:00
* * Query replace mode : ` urlmodify -q <query> <new_val> `
*
* Replace the value of a query with a new one :
* * ` http://e.com?id=foo ` - > ( ` -q id bar ` ) - > ` http://e.com?id=bar
*
* * Query delete mode : ` urlmodify -Q <query> `
*
* Deletes the given query ( and the value if any ) :
* * ` http://e.com?id=foo&page=1 ` - > ( ` -Q id ` ) - > ` http://e.com?page=1 `
*
* * Graft mode : ` urlmodify -g <graft_point> <new_path_tail> `
*
* "Grafts" a new tail on the URL path , possibly removing some of the old
* tail . Graft point indicates where the old URL is truncated before adding
* the new path .
*
* * ` graft_point ` >= 0 counts path levels , starting from the left
* ( beginning ) . 0 will append from the "root" , and no existing path will
* remain , 1 will keep one path level , and so on .
* * ` graft_point ` < 0 counts from the right ( i . e . the end of the current
* path ) . - 1 will append to the existing path , - 2 will remove the last path
* level , and so on .
*
* ` ` ` text
* http : //website.com/this/is/the/path/component
* Graft point : ^ ^ ^ ^ ^ ^
* From left : 0 1 2 3 4 5
* From right : - 6 - 5 - 4 - 3 - 2 - 1
* ` ` `
*
* Examples :
*
* * ` http://e.com/issues/42 ` - > ( ` -g 0 foo ` ) - > ` http://e.com/foo `
* * ` http://e.com/issues/42 ` - > ( ` -g 1 foo ` ) - > ` http://e.com/issues/foo `
* * ` http://e.com/issues/42 ` - > ( ` -g -1 foo ` ) - > ` http://e.com/issues/42/foo `
* * ` http://e.com/issues/42 ` - > ( ` -g -2 foo ` ) - > ` http://e.com/issues/foo `
*
* @param mode The replace mode :
* * - t text replace
* * - r regexp replace
2019-05-24 22:59:12 +10:00
* * - s set the value of the given query
2018-01-28 16:49:05 +00:00
* * - q replace the value of the given query
* * - Q delete the given query
* * - g graft a new path onto URL or parent path of it
* @param replacement the replacement arguments ( depends on mode ) :
* * - t < old > < new >
* * - r < regexp > < new > [ flags ]
2019-05-24 22:59:12 +10:00
* * - s < query > < value >
2018-01-28 16:49:05 +00:00
* * - q < query > < new_val >
* * - Q < query >
* * - g < graftPoint > < newPathTail >
2017-12-04 03:12:19 +00:00
* /
//#content
2019-05-24 22:59:12 +10:00
export function urlmodify ( mode : "-t" | "-r" | "-s" | "-q" | "-Q" | "-g" , . . . args : string [ ] ) {
2019-04-13 09:46:23 +02:00
const oldUrl = new URL ( window . location . href )
2019-04-10 06:47:46 +02:00
let newUrl
2017-12-04 03:12:19 +00:00
2018-04-13 19:28:03 +01:00
switch ( mode ) {
2017-12-04 03:12:19 +00:00
case "-t" :
if ( args . length !== 2 ) {
2018-04-13 19:28:03 +01:00
throw new Error ( "Text replacement needs 2 arguments:" + "<old> <new>" )
2017-12-04 03:12:19 +00:00
}
newUrl = oldUrl . href . replace ( args [ 0 ] , args [ 1 ] )
break
case "-r" :
if ( args . length < 2 || args . length > 3 ) {
2018-04-13 19:28:03 +01:00
throw new Error ( "RegExp replacement takes 2 or 3 arguments: " + "<regexp> <new> [flags]" )
2017-12-04 03:12:19 +00:00
}
2018-04-13 19:28:03 +01:00
if ( args [ 2 ] && args [ 2 ] . search ( /^[gi]+$/ ) === - 1 ) {
throw new Error ( "RegExp replacement flags can only include 'g', 'i'" + ", Got '" + args [ 2 ] + "'" )
2017-12-04 03:12:19 +00:00
}
2019-04-13 09:46:23 +02:00
const regexp = new RegExp ( args [ 0 ] , args [ 2 ] )
2017-12-04 03:12:19 +00:00
newUrl = oldUrl . href . replace ( regexp , args [ 1 ] )
break
2019-05-24 22:59:12 +10:00
case "-s" :
if ( args . length !== 2 ) {
throw new Error ( "Query setting needs 2 arguments:" + "<query> <value>" )
}
2019-05-25 09:08:44 +10:00
newUrl = UrlUtil . setQueryValue ( oldUrl , args [ 0 ] , args [ 1 ] )
2019-05-24 22:59:12 +10:00
break
2017-12-04 03:12:19 +00:00
case "-q" :
if ( args . length !== 2 ) {
2018-04-13 19:28:03 +01:00
throw new Error ( "Query replacement needs 2 arguments:" + "<query> <new_val>" )
2017-12-04 03:12:19 +00:00
}
2018-04-13 19:28:03 +01:00
newUrl = UrlUtil . replaceQueryValue ( oldUrl , args [ 0 ] , args [ 1 ] )
2017-12-04 03:12:19 +00:00
break
case "-Q" :
if ( args . length !== 1 ) {
2018-04-13 19:28:03 +01:00
throw new Error ( "Query deletion needs 1 argument:" + "<query>" )
2017-12-04 03:12:19 +00:00
}
newUrl = UrlUtil . deleteQuery ( oldUrl , args [ 0 ] )
break
case "-g" :
if ( args . length !== 2 ) {
2018-04-13 19:28:03 +01:00
throw new Error ( "URL path grafting needs 2 arguments:" + "<graft point> <new path tail>" )
2017-12-04 03:12:19 +00:00
}
newUrl = UrlUtil . graftUrlPath ( oldUrl , args [ 1 ] , Number ( args [ 0 ] ) )
break
}
2018-05-29 10:51:26 +01:00
// TODO: once we have an arg parser, have a quiet flag that prevents the page from being added to history
2017-12-04 03:12:19 +00:00
if ( newUrl && newUrl !== oldUrl ) {
2018-05-29 10:51:26 +01:00
window . location . replace ( newUrl )
2017-12-04 03:12:19 +00:00
}
}
2017-11-27 19:48:49 +01:00
/ * * R e t u r n s t h e u r l o f l i n k s t h a t h a v e a m a t c h i n g r e l .
2017-11-28 00:05:12 +00:00
Don 't bind to this: it' s an internal function .
@hidden
2017-11-27 19:48:49 +01:00
* /
//#content
2018-04-13 19:28:03 +01:00
export function geturlsforlinks ( reltype = "rel" , rel : string ) {
2019-04-13 09:46:23 +02:00
const elems = document . querySelectorAll ( "link[" + reltype + "='" + rel + "']" )
2018-04-13 19:28:03 +01:00
if ( elems ) return Array . prototype . map . call ( elems , x = > x . href )
2017-11-27 19:48:49 +01:00
return [ ]
}
2018-10-10 15:01:02 +00:00
/ * * S e t s t h e c u r r e n t p a g e ' s z o o m l e v e l a n y w h e r e b e t w e e n 3 0 % a n d 3 0 0 % .
*
* If you overshoot the level while using relative adjustments i . e . level > 300 % or level < 30 %
* the zoom level will be set to it ' s maximum or minimum position .
*
* @param level - The zoom level to set .
2018-10-10 15:13:24 +00:00
* Expects percentages when changing the absolute zoom value and percentage points when making relative adjustments .
2018-10-10 15:01:02 +00:00
* @param rel - Set the zoom adjustment to be relative to current zoom level .
* /
2017-11-15 12:40:26 +00:00
//#background
2018-04-13 19:28:03 +01:00
export async function zoom ( level = 0 , rel = "false" ) {
2017-11-15 12:40:26 +00:00
level = level > 3 ? level / 100 : level
2019-04-03 07:03:03 +02:00
if ( rel === "false" && ( level > 3 || level < 0.3 ) ) {
throw new Error ( ` [zoom] level out of range: ${ level } ` )
2018-10-10 15:01:02 +00:00
}
if ( rel === "true" ) {
level += await browser . tabs . getZoom ( )
// Handle overshooting of zoom level.
if ( level > 3 ) level = 3
if ( level < 0.3 ) level = 0.3
}
2017-11-15 12:40:26 +00:00
browser . tabs . setZoom ( level )
}
2018-04-12 22:33:10 +01:00
/ * * O p e n s t h e c u r r e n t p a g e i n F i r e f o x ' s r e a d e r m o d e .
* You currently cannot use Tridactyl while in reader mode .
* /
2017-11-21 19:10:42 +00:00
//#background
export async function reader() {
2018-06-30 13:35:35 +02:00
if ( await firefoxVersionAtLeast ( 58 ) ) {
2019-04-13 09:46:23 +02:00
const aTab = await activeTab ( )
2018-04-13 19:28:03 +01:00
if ( aTab . isArticle ) {
browser . tabs . toggleReaderMode ( )
} // else {
// // once a statusbar exists an error can be displayed there
// }
2017-11-21 19:10:42 +00:00
}
}
2018-12-19 08:16:33 +01:00
/** @hidden **/
2018-01-31 19:51:08 +00:00
//#content_helper
2018-07-18 14:36:35 +01:00
// {
2018-06-01 08:39:43 +02:00
loadaucmds ( "DocStart" )
2018-06-02 05:35:48 +02:00
window . addEventListener ( "pagehide" , ( ) = > loadaucmds ( "DocEnd" ) )
2018-07-18 14:36:35 +01:00
window . addEventListener ( "DOMContentLoaded" , ( ) = > loadaucmds ( "DocLoad" ) )
2018-10-25 22:59:47 -06:00
/** @hidden */
2018-10-25 22:40:25 -06:00
const fullscreenhandler = ( ) = > {
loadaucmds ( "FullscreenChange" )
if ( document . fullscreenElement || ( document as any ) . mozFullScreenElement ) {
loadaucmds ( "FullscreenEnter" )
} else {
loadaucmds ( "FullscreenLeft" )
}
}
2018-12-19 08:16:33 +01:00
/** @hidden **/
2018-10-25 22:54:25 -06:00
const fullscreenApiIsPrefixed = "mozFullScreenEnabled" in document
2018-10-25 22:40:25 -06:00
// Until firefox removes vendor prefix for this api (in FF64), we must also use mozfullscreenchange
2018-10-25 22:54:25 -06:00
if ( fullscreenApiIsPrefixed ) {
2018-10-25 22:40:25 -06:00
document . addEventListener ( "mozfullscreenchange" , fullscreenhandler )
} else if ( "fullscreenEnabled" in document ) {
document . addEventListener ( "fullscreenchange" , fullscreenhandler )
}
2018-07-18 14:36:35 +01:00
// }
2018-01-31 19:51:08 +00:00
2018-04-12 22:33:10 +01:00
/** @hidden */
2018-01-31 19:51:08 +00:00
//#content
2018-10-25 22:40:25 -06:00
export async function loadaucmds ( cmdType : "DocStart" | "DocLoad" | "DocEnd" | "TabEnter" | "TabLeft" | "FullscreenEnter" | "FullscreenLeft" | "FullscreenChange" ) {
2019-04-13 09:46:23 +02:00
const aucmds = await config . getAsync ( "autocmds" , cmdType )
2018-01-31 19:51:08 +00:00
const ausites = Object . keys ( aucmds )
2018-06-01 09:57:40 +01:00
const aukeyarr = ausites . filter ( e = > window . document . location . href . search ( e ) >= 0 )
2019-04-13 09:46:23 +02:00
for ( const aukey of aukeyarr ) {
2019-04-21 01:38:57 -07:00
controller . acceptExCmd ( aucmds [ aukey ] )
2018-01-31 19:51:08 +00:00
}
}
2017-11-23 23:57:04 +00:00
/ * * T h e k i n d s o f i n p u t e l e m e n t s t h a t w e w a n t t o b e i n c l u d e d i n t h e " f o c u s i n p u t "
* command ( gi )
2018-04-12 22:33:10 +01:00
* @hidden
2017-11-23 23:57:04 +00:00
* /
export const INPUTTAGS_selectors = `
input :not ( [ disabled ] ) : not ( [ readonly ] ) : - moz - any (
: not ( [ type ] ) ,
[ type = 'text' ] ,
[ type = 'search' ] ,
[ type = 'password' ] ,
[ type = 'datetime' ] ,
[ type = 'datetime-local' ] ,
[ type = 'date' ] ,
[ type = 'month' ] ,
[ type = 'time' ] ,
[ type = 'week' ] ,
[ type = 'number' ] ,
[ type = 'range' ] ,
[ type = 'email' ] ,
[ type = 'url' ] ,
[ type = 'tel' ] ,
[ type = 'color' ]
) ,
textarea :not ( [ disabled ] ) : not ( [ readonly ] ) ,
object ,
[ role = 'application' ]
`
2018-04-13 19:28:03 +01:00
/ * * P a s s w o r d f i e l d s e l e c t o r s
* @hidden
2018-04-12 22:33:10 +01:00
* /
2017-11-23 23:57:04 +00:00
const INPUTPASSWORD_selectors = `
input [ type = 'password' ]
`
/ * * F o c u s t h e l a s t u s e d i n p u t o n t h e p a g e
*
* @param nth focus the nth input on the page , or "special" inputs :
* "-l" : last focussed input
2017-12-02 23:54:37 +08:00
* "-n" : input after last focussed one
* "-N" : input before last focussed one
2017-11-23 23:57:04 +00:00
* "-p" : first password field
* "-b" : biggest input field
* /
//#content
2018-04-13 19:28:03 +01:00
export function focusinput ( nth : number | string ) {
2017-11-23 23:57:04 +00:00
let inputToFocus : HTMLElement = null
// set to false to avoid falling back on the first available input
// if a special finder fails
let fallbackToNumeric = true
// nth = "-l" -> use the last used input for this page
if ( nth === "-l" ) {
// try to recover the last used input stored as a
// DOM node, which should be exactly the one used before (or null)
2018-03-03 23:42:46 +01:00
if ( DOM . getLastUsedInput ( ) ) {
inputToFocus = DOM . getLastUsedInput ( )
2017-12-05 22:07:23 +00:00
} else {
// Pick the first input in the DOM.
2018-04-13 19:28:03 +01:00
inputToFocus = DOM . getElemsBySelector ( INPUTTAGS_selectors , [ DOM . isSubstantial ] ) [ 0 ] as HTMLElement
2017-11-23 23:57:04 +00:00
2017-12-05 22:07:23 +00:00
// We could try to save the last used element on page exit, but
// that seems like a lot of faff for little gain.
}
2018-04-13 19:28:03 +01:00
} else if ( nth === "-n" || nth === "-N" ) {
2017-12-02 23:54:37 +08:00
// attempt to find next/previous input
2019-04-13 09:46:23 +02:00
const inputs = DOM . getElemsBySelector ( INPUTTAGS_selectors , [ DOM . isSubstantial ] ) as HTMLElement [ ]
2017-12-02 23:54:37 +08:00
if ( inputs . length ) {
2018-03-03 23:42:46 +01:00
let index = inputs . indexOf ( DOM . getLastUsedInput ( ) )
if ( DOM . getLastUsedInput ( ) ) {
2017-12-05 22:07:23 +00:00
if ( nth === "-n" ) {
index ++
} else {
index --
}
index = index . mod ( inputs . length )
2017-12-02 23:54:37 +08:00
} else {
2017-12-05 22:07:23 +00:00
index = 0
2017-12-02 23:54:37 +08:00
}
2017-12-05 22:07:23 +00:00
inputToFocus = inputs [ index ]
2017-12-02 23:54:37 +08:00
}
2018-04-13 19:28:03 +01:00
} else if ( nth === "-p" ) {
2017-11-23 23:57:04 +00:00
// attempt to find a password input
fallbackToNumeric = false
2019-04-13 09:46:23 +02:00
const inputs = DOM . getElemsBySelector ( INPUTPASSWORD_selectors , [ DOM . isSubstantial ] )
2017-11-23 23:57:04 +00:00
if ( inputs . length ) {
2019-04-03 06:33:13 +02:00
inputToFocus = inputs [ 0 ] as HTMLElement
2017-11-23 23:57:04 +00:00
}
2018-04-13 19:28:03 +01:00
} else if ( nth === "-b" ) {
2019-04-13 09:46:23 +02:00
const inputs = DOM . getElemsBySelector ( INPUTTAGS_selectors , [ DOM . isSubstantial ] ) as HTMLElement [ ]
2019-04-03 18:27:50 +02:00
inputs . sort ( DOM . compareElementArea )
inputToFocus = inputs [ inputs . length - 1 ]
2017-11-23 23:57:04 +00:00
}
// either a number (not special) or we failed to find a special input when
// asked and falling back is acceptable
2018-05-01 15:10:32 +02:00
if ( ( ! inputToFocus || ! document . contains ( inputToFocus ) ) && fallbackToNumeric ) {
2019-04-13 09:46:23 +02:00
const index = isNaN ( nth as number ) ? 0 : ( nth as number )
2018-04-13 19:28:03 +01:00
inputToFocus = DOM . getNthElement ( INPUTTAGS_selectors , index , [ DOM . isSubstantial ] )
2017-11-23 23:57:04 +00:00
}
2017-12-02 23:54:37 +08:00
if ( inputToFocus ) {
2018-04-02 14:50:06 +02:00
DOM . focus ( inputToFocus )
Janky proof of concept for per-tab state, incl mode and parsers
I've been programming on pure instinct for like five hours and I'm not
sure what exactly I did any more. I *think* that what I did was
something like:
* Split `state.ts` into `state.ts` and `content_state.ts`, then
removed mode from state and moved it to content_state.
* Made the types in content_state.ts a hair more powerful
* Fixed all errors resulting from those two changes, in the process
swapping state out for content_state wherever appropriate.
* Moved a couple excmds from background into content since they
depended heavily on the mode and should live with the mode
* Split the controller in half, moving the parser logic to the content
script and leaving acceptExCmds in the background
version. content_controller forwards excmds to background script
using messaging.
* Nuked the keydown_* code, since we no longer need to forward keys to
the background.
* Went around fixing everything resulting from *those* changes, mostly
the various perversions of keydown listeners
* Various tweaks here and there that probably didn't do anything but I
was just changing things at random a third of the time and I really
need to go through this and clean it up.
Things that work:
* excmds are nice now. each tab has its own commandline; they stay
open when you navigate between tabs and you can come back to them
after doing stuff in another tab and finish your command line input.
* keybinds that don't don't involve mode switching: `[[`, `]]`, `j`,
`k`, `r`, etc, all still functional.
* You can turn on hinting mode and, again, navigate to another tab
which will *not* be in hinting mode, do whatever you need to do
there including excmds or more hinting, and then come back to finish
the hint you started in the first tab
* You can exit hint mode by pressing escape. :P
Things that I broke:
* ...Actually quite a bunch of stuff, I think.
* I don't seem to be able to *finish* a hint any more. Sometimes I
can't even get one key in.
2018-09-01 02:58:21 -07:00
if ( config . get ( "gimode" ) === "nextinput" && contentState . mode !== "input" ) {
contentState . mode = "input"
2017-12-05 22:07:23 +00:00
}
2017-12-02 23:54:37 +08:00
}
2017-11-23 23:57:04 +00:00
}
2018-04-20 12:24:15 +01:00
/ * *
* Focus the tab which contains the last focussed input element . If you ' re lucky , it will focus the right input , too .
*
* Currently just goes to the last focussed input ; being able to jump forwards and backwards is planned .
* /
//#background
export async function changelistjump ( n? : number ) {
2019-04-13 09:46:23 +02:00
const tail = state . prevInputs [ state . prevInputs . length - 1 ]
const jumppos = tail . jumppos ? tail.jumppos : state.prevInputs.length - 1
2018-04-20 12:24:15 +01:00
const input = state . prevInputs [ jumppos ]
await browser . tabs . update ( input . tab , { active : true } )
const id = input . inputId
// Not all elements have an ID, so this will do for now.
if ( id ) focusbyid ( input . inputId )
else focusinput ( "-l" )
// Really want to bin the input we just focussed ^ and edit the real last input to tell us where to jump to next.
// It doesn't work in practice as the focus events get added after we try to delete them.
// Even editing focusbyid/focusinput doesn't work to try to delete their own history doesn't work.
// I'm bored of working on it for now, though.
// Probable solution: add an event listener to state.prevInputs changing, delete the focussed element, then delete event listener.
//
// let arr = state.prevInputs
// arr.splice(-2,2)
// tail.jumppos = jumppos - 1
// arr = arr.concat(tail)
// state.prevInputs = arr
}
2018-06-03 11:08:45 +02:00
/** @hidden */
2018-04-20 12:24:15 +01:00
//#content
export function focusbyid ( id : string ) {
document . getElementById ( id ) . focus ( )
}
2017-10-12 04:02:01 +01:00
// }}}
// {{{ TABS
2017-11-27 22:42:50 +11:00
/ * * S w i t c h t o t h e t a b b y i n d e x ( p o s i t i o n o n t a b b a r ) , w r a p p i n g r o u n d .
2017-11-28 00:01:41 +00:00
@param index
1 - based index of the tab to target . Wraps such that 0 = last tab , - 1 =
penultimate tab , etc .
if undefined , return activeTabId ( )
* /
2017-11-27 17:27:17 +11:00
/** @hidden */
//#background_helper
2018-04-13 19:28:03 +01:00
async function tabIndexSetActive ( index : number | string ) {
2018-07-10 19:35:21 +02:00
return tabSetActive ( await idFromIndex ( index ) )
2017-10-12 04:02:01 +01:00
}
2017-11-27 17:27:17 +11:00
/ * * S w i t c h t o t h e n e x t t a b , w r a p p i n g r o u n d .
If increment is specified , move that many tabs forwards .
* /
//#background
export async function tabnext ( increment = 1 ) {
2018-11-06 07:39:55 +01:00
return tabprev ( - increment )
2017-11-27 17:27:17 +11:00
}
2017-11-28 16:16:41 +00:00
/ * * S w i t c h t o t h e n e x t t a b , w r a p p i n g r o u n d .
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 )
}
}
2017-11-27 17:27:17 +11:00
/ * * S w i t c h t o t h e p r e v i o u s t a b , w r a p p i n g r o u n d .
If increment is specified , move that many tabs backwards .
* /
//#background
export async function tabprev ( increment = 1 ) {
2018-11-06 07:39:55 +01:00
// Proper way:
// return tabIndexSetActive((await activeTab()).index - increment + 1)
// Kludge until https://bugzilla.mozilla.org/show_bug.cgi?id=1504775 is fixed:
2018-12-04 12:55:56 +01:00
return browser . tabs . query ( { currentWindow : true , hidden : false } ) . then ( tabs = > {
2019-04-03 18:34:19 +02:00
tabs . sort ( ( t1 , t2 ) = > t1 . index - t2 . index )
2019-04-13 09:46:23 +02:00
const prevTab = ( tabs . findIndex ( t = > t . active ) - increment + tabs . length ) % tabs . length
2018-11-06 12:42:45 +00:00
return browser . tabs . update ( tabs [ prevTab ] . id , { active : true } )
} )
2017-11-27 17:27:17 +11:00
}
2018-02-18 16:05:38 +00:00
/ * * L i k e [ [ o p e n ] ] , b u t i n a n e w t a b . I f n o a d d r e s s i s g i v e n , i t w i l l o p e n t h e n e w t a b p a g e , w h i c h c a n b e s e t w i t h ` s e t n e w t a b [ u r l ] `
2018-08-27 03:32:46 -07:00
Use the ` -c ` flag followed by a container name to open a tab in said container . Tridactyl will try to fuzzy match a name if an exact match is not found . If any autocontainer directives are configured and - c is not set , Tridactyl will try to use the right container automatically using your configurations .
2018-05-10 14:23:31 +00:00
Use the ` -b ` flag to open the tab in the background .
These two can be combined in any order , but need to be placed as the first arguments .
2018-04-16 11:26:16 +01:00
2018-02-18 16:05:38 +00:00
Unlike Firefox ' s Ctrl - t shortcut , this opens tabs immediately after the
currently active tab rather than at the end of the tab list because that is
2018-04-15 19:08:42 +01:00
the authors ' preference .
2018-03-12 22:37:37 +00:00
If you would rather the Firefox behaviour ` set tabopenpos last ` . This
preference also affects the clipboard , quickmarks , home , help , etc .
If you would rather the URL be opened as if you ' d middle clicked it , ` set
tabopenpos related ` .
2018-04-15 19:08:42 +01:00
Hinting is controlled by ` relatedopenpos `
2018-03-12 22:37:37 +00:00
2018-12-19 07:53:11 +01:00
Also see the [ [ searchengine ] ] and [ [ searchurls ] ] settings .
2018-02-18 16:05:38 +00:00
* /
2018-10-08 22:00:16 +01:00
//#background
2017-10-24 17:40:00 +01:00
export async function tabopen ( . . . addressarr : string [ ] ) {
2018-04-16 11:26:16 +01:00
let active
2018-05-10 14:23:31 +00:00
let container
2018-08-27 03:32:46 -07:00
const win = await browser . windows . getCurrent ( )
2018-05-10 14:23:31 +00:00
// Lets us pass both -b and -c in no particular order as long as they are up front.
2018-05-24 11:27:09 +00:00
async function argParse ( args ) : Promise < string [ ] > {
2018-05-10 14:23:31 +00:00
if ( args [ 0 ] === "-b" ) {
active = false
args . shift ( )
argParse ( args )
} else if ( args [ 0 ] === "-c" ) {
2018-05-25 10:46:18 +00:00
// Ignore the -c flag if incognito as containers are disabled.
2019-04-03 19:09:49 +02:00
if ( ! win . incognito ) container = await Container . fuzzyMatch ( args [ 1 ] )
2018-05-25 10:46:18 +00:00
else logger . error ( "[tabopen] can't open a container in a private browsing window." )
2018-05-24 21:37:19 +00:00
args . shift ( )
args . shift ( )
2018-05-10 14:23:31 +00:00
argParse ( args )
}
return args
2018-04-16 11:26:16 +01:00
}
2019-04-13 09:46:23 +02:00
const query = await argParse ( addressarr )
2018-03-12 22:37:37 +00:00
2019-04-13 09:46:23 +02:00
const address = query . join ( " " )
2018-05-04 23:05:00 +01:00
if ( ! ABOUT_WHITELIST . includes ( address ) && address . match ( /^(about|file):.*/ ) ) {
2019-01-22 07:05:45 +01:00
return nativeopen ( address )
2019-01-27 13:04:51 +01:00
}
2018-03-12 22:37:37 +00:00
2018-08-27 03:32:46 -07:00
const aucon = new AutoContain ( )
if ( ! container && aucon . autocontainConfigured ( ) ) {
const autoContainer = await aucon . getAuconForUrl ( address )
if ( autoContainer && autoContainer !== "firefox-default" ) {
container = autoContainer
logger . debug ( "tabopen setting container automatically using autocontain directive" )
}
}
2019-01-22 07:05:45 +01:00
return activeTabContainerId ( ) . then ( containerId = > {
2019-04-13 09:46:23 +02:00
const args = { active } as any
2018-05-24 11:27:09 +00:00
// Ensure -c has priority.
2019-01-22 07:05:45 +01:00
if ( container ) {
2019-01-27 13:04:51 +01:00
args . cookieStoreId = container
2019-01-22 07:05:45 +01:00
} else if ( containerId && config . get ( "tabopencontaineraware" ) === "true" ) {
2019-01-27 13:04:51 +01:00
args . cookieStoreId = containerId
2019-01-22 07:05:45 +01:00
}
2019-02-04 18:43:16 +01:00
return openInNewTab ( null , args ) . then ( tab = > openInTab ( tab , { loadReplace : true } , query ) )
2018-05-08 12:27:30 +00:00
} )
2017-10-12 04:02:01 +01:00
}
2017-11-28 00:01:41 +00:00
/ * * R e s o l v e a t a b i n d e x t o t h e t a b i d o f t h e c o r r e s p o n d i n g t a b i n t h i s w i n d o w .
@param index
1 - based index of the tab to target . Wraps such that 0 = last tab , - 1 =
penultimate tab , etc .
2018-04-12 13:17:17 +01:00
also supports # for previous tab , % for current tab .
2017-11-28 00:01:41 +00:00
if undefined , return activeTabId ( )
@hidden
* /
//#background_helper
2018-04-13 19:28:03 +01:00
async function idFromIndex ( index? : number | "%" | "#" | string ) : Promise < number > {
2018-04-12 13:17:17 +01:00
if ( index === "#" ) {
// Support magic previous/current tab syntax everywhere
2018-10-25 07:52:17 +02:00
const tabs = await getSortedWinTabs ( )
if ( tabs . length < 2 ) {
// In vim, '#' is the id of the previous buffer even if said buffer has been wiped
// However, firefox doesn't store tab ids for closed tabs
// Since vim makes '#' default to the current buffer if only one buffer has ever been opened for the current session, it seems reasonable to return the id of the current tab if only one tab is opened in firefox
return activeTabId ( )
}
return tabs [ 1 ] . id
2018-04-13 19:28:03 +01:00
} else if ( index !== undefined && index !== "%" ) {
2017-11-28 15:47:12 +00:00
// Wrap
2018-04-12 13:17:17 +01:00
index = Number ( index )
2018-06-30 13:35:35 +02:00
index = ( index - 1 ) . mod ( ( await browser . tabs . query ( { currentWindow : true } ) ) . length ) + 1
2017-11-28 00:01:41 +00:00
// Return id of tab with that index.
2018-06-30 13:35:35 +02:00
return ( await browser . tabs . query ( {
currentWindow : true ,
index : index - 1 ,
} ) ) [ 0 ] . id
2017-11-28 00:01:41 +00:00
} else {
2018-12-25 10:00:01 +01:00
return activeTabId ( )
2017-11-28 00:01:41 +00:00
}
}
2017-11-29 00:36:53 +00:00
/** Close all other tabs in this window */
2017-11-29 02:46:11 +08:00
//#background
export async function tabonly() {
2017-11-29 00:36:53 +00:00
const tabs = await browser . tabs . query ( {
pinned : false ,
active : false ,
2018-04-13 19:28:03 +01:00
currentWindow : true ,
2017-11-29 07:19:02 +08:00
} )
2017-11-29 00:36:53 +00:00
const tabsIds = tabs . map ( tab = > tab . id )
2017-11-29 02:46:11 +08:00
browser . tabs . remove ( tabsIds )
}
2017-11-28 00:01:41 +00:00
/ * * D u p l i c a t e a t a b .
@param index
The 1 - based index of the tab to target . index < 1 wraps . If omitted , this tab .
* /
2017-10-12 04:02:01 +01:00
//#background
2017-11-28 00:01:41 +00:00
export async function tabduplicate ( index? : number ) {
browser . tabs . duplicate ( await idFromIndex ( index ) )
2017-10-12 04:02:01 +01:00
}
2017-11-28 00:01:41 +00:00
/ * * D e t a c h a t a b , o p e n i n g i t i n a n e w w i n d o w .
@param index
The 1 - based index of the tab to target . index < 1 wraps . If omitted , this tab .
* /
2017-10-12 04:02:01 +01:00
//#background
2017-11-28 00:01:41 +00:00
export async function tabdetach ( index? : number ) {
2018-04-13 19:28:03 +01:00
browser . windows . create ( { tabId : await idFromIndex ( index ) } )
2017-10-12 04:02:01 +01:00
}
2018-04-12 13:32:58 +01:00
/ * * G e t l i s t o f t a b s s o r t e d b y m o s t r e c e n t u s e
@hidden
* /
2018-03-14 21:34:30 +08:00
//#background_helper
async function getSortedWinTabs ( ) : Promise < browser.tabs.Tab [ ] > {
2018-04-13 19:28:03 +01:00
const tabs = await browser . tabs . query ( { currentWindow : true } )
tabs . sort ( ( a , b ) = > ( a . lastAccessed < b . lastAccessed ? 1 : - 1 ) )
2018-03-14 21:34:30 +08:00
return tabs
}
2018-05-04 09:22:57 -05:00
/ * * T o g g l e f u l l s c r e e n s t a t e
2018-04-11 18:19:29 +01:00
* /
//#background
export async function fullscreen() {
// Could easily extend this to fullscreen / minimise any window but seems like that would be a tiny use-case.
const currwin = await browser . windows . getCurrent ( )
const wid = currwin . id
// This might have odd behaviour on non-tiling window managers, but no-one uses those, right?
2019-04-10 07:21:21 +02:00
const state = currwin . state === "fullscreen" ? "normal" : "fullscreen"
2018-04-13 19:28:03 +01:00
browser . windows . update ( wid , { state } )
2018-04-11 18:19:29 +01:00
}
2017-11-28 00:01:41 +00:00
/ * * C l o s e a t a b .
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 .
* /
2017-10-12 04:02:01 +01:00
//#background
2017-11-28 00:01:41 +00:00
export async function tabclose ( . . . indexes : string [ ] ) {
if ( indexes . length > 0 ) {
2018-03-14 21:34:30 +08:00
let ids : number [ ]
2018-04-12 13:17:17 +01:00
ids = await Promise . all ( indexes . map ( index = > idFromIndex ( index ) ) )
2018-03-14 21:34:30 +08:00
browser . tabs . remove ( ids )
2017-10-12 04:02:01 +01:00
} else {
2017-11-28 00:01:41 +00:00
// Close current tab
2017-11-09 00:41:07 +00:00
browser . tabs . remove ( await activeTabId ( ) )
2017-10-12 04:02:01 +01:00
}
}
2018-05-03 17:07:24 -03:00
/ * * C l o s e a l l t a b s t o t h e r i g h t o f t h e c u r r e n t o n e
*
* /
//#background
2018-05-04 11:38:15 +01:00
export async function tabclosealltoright() {
2018-05-03 17:07:24 -03:00
const tabs = await browser . tabs . query ( {
pinned : false ,
currentWindow : true ,
} )
const atab = await activeTab ( )
2019-04-13 09:46:23 +02:00
const ids = tabs . filter ( tab = > tab . index > atab . index ) . map ( tab = > tab . id )
2018-05-03 17:07:24 -03:00
browser . tabs . remove ( ids )
}
/ * * C l o s e a l l t a b s t o t h e l e f t o f t h e c u r r e n t o n e
*
* /
//#background
2018-05-04 11:38:15 +01:00
export async function tabclosealltoleft() {
2018-05-03 17:07:24 -03:00
const tabs = await browser . tabs . query ( {
pinned : false ,
currentWindow : true ,
} )
const atab = await activeTab ( )
2019-04-13 09:46:23 +02:00
const ids = tabs . filter ( tab = > tab . index < atab . index ) . map ( tab = > tab . id )
2018-05-03 17:07:24 -03:00
browser . tabs . remove ( ids )
}
2018-10-30 20:06:04 +00:00
/ * * R e s t o r e t h e m o s t r e c e n t l y c l o s e d i t e m .
The default behaviour is to restore the most recently closed tab in the
current window unless the most recently closed item is a window .
Supplying either "tab" or "window" as an argument will specifically only
restore an item of the specified type .
@param item
The type of item to restore . Valid inputs are "recent" , "tab" and "window" .
@return
The tab or window id of the restored item . Returns - 1 if no items are found .
* /
2017-10-12 04:02:01 +01:00
//#background
2019-04-01 12:53:08 +02:00
export async function undo ( item = "recent" ) : Promise < number > {
2018-04-13 19:28:03 +01:00
const current_win_id : number = ( await browser . windows . getCurrent ( ) ) . id
2018-11-01 13:32:12 +00:00
const sessions = await browser . sessions . getRecentlyClosed ( )
2018-08-19 11:56:51 +00:00
2018-10-30 20:06:04 +00:00
if ( item === "tab" ) {
2018-11-04 12:28:31 +00:00
const lastSession = sessions . find ( s = > {
if ( s . tab ) return true
} )
2018-10-30 20:06:04 +00:00
if ( lastSession ) {
2018-08-19 11:56:51 +00:00
browser . sessions . restore ( lastSession . tab . sessionId )
2018-10-30 20:06:04 +00:00
return lastSession . tab . id
}
2018-11-04 12:28:31 +00:00
} else if ( item === "window" ) {
const lastSession = sessions . find ( s = > {
if ( s . window ) return true
} )
2018-10-30 20:06:04 +00:00
if ( lastSession ) {
2018-08-19 11:56:51 +00:00
browser . sessions . restore ( lastSession . window . sessionId )
2018-10-30 20:06:04 +00:00
return lastSession . window . id
2017-10-21 12:54:48 +02:00
}
2018-11-04 12:28:31 +00:00
} else if ( item === "recent" ) {
2018-10-30 20:06:04 +00:00
// The first session object that's a window or a tab from this window. Or undefined if sessions is empty.
const lastSession = sessions . find ( s = > {
if ( s . window ) {
return true
} else if ( s . tab && s . tab . windowId === current_win_id ) {
return true
} else {
return false
}
} )
if ( lastSession ) {
if ( lastSession . tab ) {
browser . sessions . restore ( lastSession . tab . sessionId )
return lastSession . tab . id
} else if ( lastSession . window ) {
browser . sessions . restore ( lastSession . window . sessionId )
return lastSession . window . id
}
2017-10-21 12:54:48 +02:00
}
2019-04-05 13:16:04 +02:00
} else if ( ! isNaN ( parseInt ( item , 10 ) ) ) {
2019-01-19 18:06:05 +01:00
const sessionId = item
2019-04-10 07:21:21 +02:00
const session = sessions . find ( s = > ( s . tab || s . window ) . sessionId === sessionId )
2019-01-19 18:06:05 +01:00
if ( session ) {
browser . sessions . restore ( sessionId )
return ( session . tab || session . window ) . id
}
2018-11-01 13:32:12 +00:00
} else {
throw new Error ( ` [undo] Invalid argument: ${ item } . Must be one of "tab", "window", "recent" ` )
2017-10-21 12:54:48 +02:00
}
2018-10-30 20:06:04 +00:00
return - 1
2017-10-12 04:02:01 +01:00
}
2017-11-28 00:01:41 +00:00
/ * * M o v e t h e c u r r e n t t a b t o b e j u s t i n f r o n t o f t h e i n d e x s p e c i f i e d .
2018-07-17 16:18:48 +01:00
Known bug : This supports relative movement with ` tabmove +pos ` and ` tabmove -pos ` , but autocomplete doesn ' t know that yet and will override positive and negative indexes .
2017-11-28 00:01:41 +00:00
2018-07-17 16:18:48 +01:00
Put a space in front of tabmove if you want to disable completion and have the relative indexes at the command line .
2017-11-28 00:01:41 +00:00
Binds are unaffected .
@param index
New index for the current tab .
2018-07-30 14:51:06 +01:00
1 , start , ^ are aliases for the first index . 0 , end , $ are aliases for the last index .
2017-11-28 00:01:41 +00:00
* /
2017-10-12 04:02:01 +01:00
//#background
2018-08-04 17:48:53 +03:00
export async function tabmove ( index = "$" ) {
2017-11-28 00:01:41 +00:00
const aTab = await activeTab ( )
2018-08-04 14:52:12 +03:00
const windowTabs = await browser . tabs . query ( { currentWindow : true } )
const windowPinnedTabs = await browser . tabs . query ( { currentWindow : true , pinned : true } )
const maxPinnedIndex = windowPinnedTabs . length - 1
let minindex : number
let maxindex : number
if ( aTab . pinned ) {
minindex = 0
maxindex = maxPinnedIndex
} else {
minindex = maxPinnedIndex + 1
maxindex = windowTabs . length - 1
}
2017-11-28 00:01:41 +00:00
let newindex : number
2018-08-04 14:52:12 +03:00
let relative = false
2018-07-29 17:26:49 +03:00
2018-08-04 19:11:21 +03:00
if ( index . startsWith ( "+" ) || index . startsWith ( "-" ) ) {
2018-08-04 14:52:12 +03:00
relative = true
2018-07-29 17:26:49 +03:00
newindex = Number ( index ) + aTab . index
2018-08-04 18:01:19 +03:00
} else if ( [ "end" , "$" , "0" ] . includes ( index ) ) {
2018-07-30 14:51:06 +01:00
newindex = maxindex
} else if ( [ "start" , "^" ] . includes ( index ) ) {
newindex = 0
2018-08-04 14:52:12 +03:00
} else {
2018-08-09 06:21:00 +02:00
newindex = Number ( index ) + minindex - 1
2018-08-04 14:52:12 +03:00
}
if ( newindex > maxindex ) {
if ( relative ) {
while ( newindex > maxindex ) {
newindex -= maxindex - minindex + 1
}
} else newindex = maxindex
}
if ( newindex < minindex ) {
if ( relative ) {
while ( newindex < minindex ) {
newindex += maxindex - minindex + 1
}
} else newindex = minindex
}
2018-04-13 19:28:03 +01:00
browser . tabs . move ( aTab . id , { index : newindex } )
2017-10-12 04:02:01 +01:00
}
2017-11-19 06:05:15 +00:00
/** Pin the current tab */
2017-10-12 04:02:01 +01:00
//#background
export async function pin() {
2019-04-13 09:46:23 +02:00
const aTab = await activeTab ( )
2018-04-13 19:28:03 +01:00
browser . tabs . update ( aTab . id , { pinned : ! aTab . pinned } )
2017-10-12 04:02:01 +01:00
}
2018-07-15 23:46:13 +00:00
/ * * M u t e c u r r e n t t a b o r a l l t a b s .
Passing "all" to the excmd will operate on the mute state of all tabs .
2018-07-16 09:28:50 +00:00
Passing "unmute" to the excmd will unmute .
2018-07-15 23:46:13 +00:00
Passing "toggle" to the excmd will toggle the state of ` browser.tabs.tab.MutedInfo `
2018-10-15 14:24:31 +08:00
@param string [ ] muteArgs
2018-07-15 23:46:13 +00:00
* /
//#background
export async function mute ( . . . muteArgs : string [ ] ) : Promise < void > {
let mute = true
let toggle = false
let all = false
2019-04-13 09:46:23 +02:00
const argParse = ( args : string [ ] ) = > {
2019-04-10 07:21:21 +02:00
if ( args === null ) {
2018-07-17 16:18:48 +01:00
return
}
2018-07-15 23:46:13 +00:00
if ( args [ 0 ] === "all" ) {
all = true
args . shift ( )
argParse ( args )
2018-07-17 16:18:48 +01:00
}
2018-07-16 09:28:50 +00:00
if ( args [ 0 ] === "unmute" ) {
2018-07-15 23:46:13 +00:00
mute = false
args . shift ( )
argParse ( args )
}
if ( args [ 0 ] === "toggle" ) {
toggle = true
args . shift ( )
argParse ( args )
}
}
argParse ( muteArgs )
2019-04-13 09:46:23 +02:00
const updateObj = { muted : false }
2018-07-17 16:18:48 +01:00
if ( mute ) {
updateObj . muted = true
}
if ( all ) {
2019-04-13 09:46:23 +02:00
const tabs = await browser . tabs . query ( { currentWindow : true } )
for ( const tab of tabs ) {
2018-07-17 16:18:48 +01:00
if ( toggle ) {
updateObj . muted = ! tab . mutedInfo . muted
}
browser . tabs . update ( tab . id , updateObj )
2018-07-15 23:46:13 +00:00
}
} else {
2019-04-13 09:46:23 +02:00
const tab = await activeTab ( )
2018-07-17 16:18:48 +01:00
if ( toggle ) {
updateObj . muted = ! tab . mutedInfo . muted
}
browser . tabs . update ( tab . id , updateObj )
2018-07-15 23:46:13 +00:00
}
}
2017-10-12 04:02:01 +01:00
// }}}
// {{{ WINDOWS
2019-02-15 15:33:25 +01:00
/ * *
* Like [ [ tabopen ] ] , but in a new window .
*
* ` winopen -private [...] ` will open the result in a private window ( and won ' t store the command in your ex - history ; ) ) .
*
* ` winopen -popup [...] ` will open it in a popup window . You can combine the two for a private popup .
*
* Example : ` winopen -popup -private ddg.gg `
* /
2017-10-12 04:02:01 +01:00
//#background
export async function winopen ( . . . args : string [ ] ) {
2019-04-03 19:09:49 +02:00
const createData = { } as any
2018-04-20 22:06:06 +01:00
let firefoxArgs = "--new-window"
2019-02-15 15:33:25 +01:00
let done = false
while ( ! done ) {
switch ( args [ 0 ] ) {
case "-private" :
2019-04-03 19:09:49 +02:00
createData . incognito = true
2019-02-15 15:33:25 +01:00
args = args . slice ( 1 , args . length )
firefoxArgs = "--private-window"
break
case "-popup" :
2019-04-03 19:09:49 +02:00
createData . type = "popup"
2019-02-15 15:33:25 +01:00
args = args . slice ( 1 , args . length )
break
default :
done = true
break
}
2019-02-04 08:00:51 +01:00
}
2019-04-13 09:46:23 +02:00
const address = args . join ( " " )
2018-05-04 23:05:00 +01:00
if ( ! ABOUT_WHITELIST . includes ( address ) && address . match ( /^(about|file):.*/ ) ) {
2019-03-26 08:27:38 +01:00
return nativeopen ( firefoxArgs , address )
2018-04-20 22:06:06 +01:00
}
2019-02-04 08:00:51 +01:00
2019-02-04 18:43:16 +01:00
return browser . windows . create ( createData ) . then ( win = > openInTab ( win . tabs [ 0 ] , { loadReplace : true } , address . split ( " " ) ) )
2017-10-12 04:02:01 +01:00
}
2019-02-03 08:55:40 +01:00
/ * *
2019-01-31 08:13:05 +01:00
* Close a tab .
*
* @param id - The window id . Defaults to the id of the current window .
*
2019-02-03 15:45:11 +00:00
* Example : ` winclose `
2019-01-31 08:13:05 +01:00
* /
2017-10-12 04:02:01 +01:00
//#background
2019-01-31 08:13:05 +01:00
export async function winclose ( . . . ids : string [ ] ) {
2019-04-10 07:21:21 +02:00
if ( ids . length === 0 ) {
2019-01-31 08:13:05 +01:00
ids . push ( ` ${ ( await browser . windows . getCurrent ( ) ) . id } ` )
}
2019-04-05 13:16:04 +02:00
return Promise . all ( ids . map ( id = > browser . windows . remove ( parseInt ( id , 10 ) ) ) )
2017-10-12 04:02:01 +01:00
}
2017-11-19 06:05:15 +00:00
/** Close all windows */
2017-11-02 19:36:44 +00:00
// It's unclear if this will leave a session that can be restored.
// We might have to do it ourselves.
//#background
2018-04-13 19:28:03 +01:00
export async function qall() {
2019-04-13 09:46:23 +02:00
const windows = await browser . windows . getAll ( )
2019-04-03 18:15:32 +02:00
windows . forEach ( window = > browser . windows . remove ( window . id ) )
2017-11-02 19:36:44 +00:00
}
2017-10-12 04:02:01 +01:00
// }}}
2018-05-09 12:29:46 +00:00
// {{{ CONTAINERS
2018-05-12 17:13:52 +00:00
2018-05-09 12:29:46 +00:00
/ * * C l o s e s a l l t a b s o p e n i n t h e s a m e c o n t a i n e r a c r o s s a l l w i n d o w s .
2018-06-14 15:25:38 +00:00
@param name The container name .
2018-05-09 12:29:46 +00:00
* /
//#background
2018-06-14 14:09:59 +00:00
export async function containerclose ( name : string ) {
2019-04-13 09:46:23 +02:00
const containerId = await Container . getId ( name )
2018-06-13 09:04:54 +00:00
browser . tabs . query ( { cookieStoreId : containerId } ) . then ( tabs = > {
2018-05-09 12:29:46 +00:00
browser . tabs . remove (
tabs . map ( tab = > {
return tab . id
} ) ,
)
} )
}
2018-06-19 13:41:29 +00:00
/ * * C r e a t e s a n e w c o n t a i n e r . N o t e t h a t c o n t a i n e r n a m e s m u s t b e u n i q u e a n d t h a t t h e c h e c k s a r e c a s e - i n s e n s i t i v e .
2018-06-14 15:25:38 +00:00
2018-06-19 13:41:29 +00:00
Further reading https : //developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contextualIdentities/ContextualIdentity
2018-06-14 15:25:38 +00:00
2018-06-19 13:41:29 +00:00
Example usage :
- ` :containercreate tridactyl green dollar `
@param name The container name . Must be unique .
@param color The container color . Valid colors are : "blue" , "turquoise" , "green" , "yellow" , "orange" , "red" , "pink" , "purple" . If no color is chosen a random one will be selected from the list of valid colors .
@param icon The container icon . Valid icons are : "fingerprint" , "briefcase" , "dollar" , "cart" , "circle" , "gift" , "vacation" , "food" , "fruit" , "pet" , "tree" , "chill" . If no icon is chosen , it defaults to "fingerprint" .
2018-06-14 13:47:43 +00:00
* /
2017-12-06 05:37:18 -05:00
//#background
2018-06-14 13:47:43 +00:00
export async function containercreate ( name : string , color? : string , icon? : string ) {
2018-06-13 08:19:38 +00:00
await Container . create ( name , color , icon )
2017-12-06 05:37:18 -05:00
}
2018-06-19 13:41:29 +00:00
/ * * D e l e t e a c o n t a i n e r . C l o s e s a l l t a b s a s s o c i a t e d w i t h t h a t c o n t a i n e r b e f o r e h a n d . N o t e : c o n t a i n e r n a m e s a r e c a s e - i n s e n s i t i v e .
2018-06-14 15:25:38 +00:00
@param name The container name .
2018-06-13 09:04:54 +00:00
* /
2018-06-13 08:19:38 +00:00
//#background
2018-07-20 11:12:03 +00:00
export async function containerdelete ( name : string ) {
2018-06-14 14:09:59 +00:00
await containerclose ( name )
2018-06-13 08:19:38 +00:00
await Container . remove ( name )
}
2018-06-19 13:41:29 +00:00
/ * * U p d a t e a c o n t a i n e r ' s i n f o r m a t i o n . N o t e t h a t n o n e o f t h e p a r a m e t e r s a r e o p t i o n a l a n d t h a t c o n t a i n e r n a m e s a r e c a s e - i n s e n s i t i v e .
2018-06-14 15:25:38 +00:00
Example usage :
2018-06-19 13:41:29 +00:00
- Changing the container name : ` :containerupdate banking blockchain green dollar `
2018-06-14 15:25:38 +00:00
2018-06-19 13:41:29 +00:00
- Changing the container icon : ` :containerupdate banking banking green briefcase `
2017-10-12 04:02:01 +01:00
2018-06-19 13:41:29 +00:00
- Changing the container color : ` :containerupdate banking banking purple dollar `
2018-06-14 15:25:38 +00:00
@param name The container name .
@param uname The new container name . Must be unique .
@param ucolor The new container color . Valid colors are : "blue" , "turquoise" , "green" , "yellow" , "orange" , "red" , "pink" , "purple" . If no color is chosen a random one will be selected from the list of valid colors .
@param uicon The new container icon . Valid icons are : "fingerprint" , "briefcase" , "dollar" , "cart" , "circle" , "gift" , "vacation" , "food" , "fruit" , "pet" , "tree" , "chill" .
2018-06-14 13:47:43 +00:00
* /
2018-06-13 08:19:38 +00:00
//#background
export async function containerupdate ( name : string , uname : string , ucolor : string , uicon : string ) {
logger . debug ( "containerupdate parameters: " + name + ", " + uname + ", " + ucolor + ", " + uicon )
2019-04-13 09:46:23 +02:00
const containerId = await Container . fuzzyMatch ( name )
const containerObj = Container . fromString ( uname , ucolor , uicon )
2019-04-04 13:03:31 +02:00
await Container . update ( containerId , containerObj )
2018-05-12 17:13:52 +00:00
}
2018-06-17 21:27:11 +00:00
/ * * S h o w s a l i s t o f t h e c u r r e n t c o n t a i n e r s i n F i r e f o x ' s n a t i v e J S O N v i e w e r i n t h e c u r r e n t t a b .
NB : Tridactyl cannot run on this page !
* /
//#content
export async function viewcontainers() {
// # and white space don't agree with FF's JSON viewer.
// Probably other symbols too.
2019-04-13 09:46:23 +02:00
const containers = await browserBg . contextualIdentities . query ( { } ) // Can't access src/lib/containers.ts from a content script.
2018-06-17 21:27:11 +00:00
window . location . href =
"data:application/json," +
JSON . stringify ( containers )
. replace ( /#/g , "%23" )
. replace ( / /g , "%20" )
}
2018-07-03 11:32:16 +00:00
2018-05-09 12:29:46 +00:00
// }}}
//
2017-10-12 04:02:01 +01:00
// {{{ MISC
2017-11-26 00:06:02 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function version() {
2018-12-07 00:31:16 +00:00
fillcmdline_notrail ( TRI_VERSION )
2017-11-26 00:06:02 +00:00
}
2019-01-29 19:51:40 +01:00
/ * *
* Switch mode .
*
* For now you probably shouldn 't manually switch to other modes than `normal` and `ignore`. Make sure you' re aware of the key bindings ( ignoremaps ) that will allow you to go come back to normal mode from ignore mode before you run ` :mode ignore ` otherwise you ' re going to have a hard time re - enabling Tridactyl .
*
* Example :
* - ` mode ignore ` to ignore almost all keys .
*
* If you 're looking for a way to temporarily disable Tridactyl, `mode ignore` might be what you' re looking for .
*
* Note that when in ignore mode , Tridactyl will not switch to insert mode when focusing text areas / inputs . This is by design .
2019-05-30 15:37:06 +01:00
*
* * * New feature : * * you can add modes as simply as adding binds with ` bind --mode=[newmodename] ` and then enter the mode with ` mode [newmodename] ` .
2019-01-29 19:51:40 +01:00
* /
Janky proof of concept for per-tab state, incl mode and parsers
I've been programming on pure instinct for like five hours and I'm not
sure what exactly I did any more. I *think* that what I did was
something like:
* Split `state.ts` into `state.ts` and `content_state.ts`, then
removed mode from state and moved it to content_state.
* Made the types in content_state.ts a hair more powerful
* Fixed all errors resulting from those two changes, in the process
swapping state out for content_state wherever appropriate.
* Moved a couple excmds from background into content since they
depended heavily on the mode and should live with the mode
* Split the controller in half, moving the parser logic to the content
script and leaving acceptExCmds in the background
version. content_controller forwards excmds to background script
using messaging.
* Nuked the keydown_* code, since we no longer need to forward keys to
the background.
* Went around fixing everything resulting from *those* changes, mostly
the various perversions of keydown listeners
* Various tweaks here and there that probably didn't do anything but I
was just changing things at random a third of the time and I really
need to go through this and clean it up.
Things that work:
* excmds are nice now. each tab has its own commandline; they stay
open when you navigate between tabs and you can come back to them
after doing stuff in another tab and finish your command line input.
* keybinds that don't don't involve mode switching: `[[`, `]]`, `j`,
`k`, `r`, etc, all still functional.
* You can turn on hinting mode and, again, navigate to another tab
which will *not* be in hinting mode, do whatever you need to do
there including excmds or more hinting, and then come back to finish
the hint you started in the first tab
* You can exit hint mode by pressing escape. :P
Things that I broke:
* ...Actually quite a bunch of stuff, I think.
* I don't seem to be able to *finish* a hint any more. Sometimes I
can't even get one key in.
2018-09-01 02:58:21 -07:00
//#content
2017-11-09 00:41:07 +00:00
export function mode ( mode : ModeName ) {
2017-11-19 06:05:15 +00:00
// TODO: event emition on mode change.
2017-11-19 07:57:30 +00:00
if ( mode === "hint" ) {
2017-11-19 06:05:15 +00:00
hint ( )
} else {
Janky proof of concept for per-tab state, incl mode and parsers
I've been programming on pure instinct for like five hours and I'm not
sure what exactly I did any more. I *think* that what I did was
something like:
* Split `state.ts` into `state.ts` and `content_state.ts`, then
removed mode from state and moved it to content_state.
* Made the types in content_state.ts a hair more powerful
* Fixed all errors resulting from those two changes, in the process
swapping state out for content_state wherever appropriate.
* Moved a couple excmds from background into content since they
depended heavily on the mode and should live with the mode
* Split the controller in half, moving the parser logic to the content
script and leaving acceptExCmds in the background
version. content_controller forwards excmds to background script
using messaging.
* Nuked the keydown_* code, since we no longer need to forward keys to
the background.
* Went around fixing everything resulting from *those* changes, mostly
the various perversions of keydown listeners
* Various tweaks here and there that probably didn't do anything but I
was just changing things at random a third of the time and I really
need to go through this and clean it up.
Things that work:
* excmds are nice now. each tab has its own commandline; they stay
open when you navigate between tabs and you can come back to them
after doing stuff in another tab and finish your command line input.
* keybinds that don't don't involve mode switching: `[[`, `]]`, `j`,
`k`, `r`, etc, all still functional.
* You can turn on hinting mode and, again, navigate to another tab
which will *not* be in hinting mode, do whatever you need to do
there including excmds or more hinting, and then come back to finish
the hint you started in the first tab
* You can exit hint mode by pressing escape. :P
Things that I broke:
* ...Actually quite a bunch of stuff, I think.
* I don't seem to be able to *finish* a hint any more. Sometimes I
can't even get one key in.
2018-09-01 02:58:21 -07:00
contentState . mode = mode
2017-11-19 06:05:15 +00:00
}
2017-10-12 04:02:01 +01:00
}
2018-04-14 22:14:16 +01:00
/** @hidden */
2017-11-19 06:05:15 +00:00
//#background_helper
async function getnexttabs ( tabid : number , n? : number ) {
2017-10-12 04:02:01 +01:00
const curIndex : number = ( await browser . tabs . get ( tabid ) ) . index
const tabs : browser.tabs.Tab [ ] = await browser . tabs . query ( {
currentWindow : true ,
} )
const indexFilter = ( ( tab : browser.tabs.Tab ) = > {
2018-04-13 19:28:03 +01:00
return curIndex <= tab . index && ( n ? tab . index < curIndex + Number ( n ) : true )
2017-10-12 04:02:01 +01:00
} ) . bind ( n )
return tabs . filter ( indexFilter ) . map ( ( tab : browser.tabs.Tab ) = > {
return tab . id
} )
}
// Moderately slow; should load in results as they arrive, perhaps
// Todo: allow jumping to buffers once they are found
// Consider adding to buffers with incremental search
// maybe only if no other results in URL etc?
// Find out how to return context of each result
2019-04-21 01:38:57 -07:00
// //#background
2017-10-12 04:02:01 +01:00
/* export async function findintabs(query: string) { */
/* const tabs = await browser.tabs.query({currentWindow: true}) */
/* console.log(query) */
/* const findintab = async tab => */
/* await browser.find.find(query, {tabId: tab.id}) */
/* let results = [] */
/* for (let tab of tabs) { */
/* let result = await findintab(tab) */
/* if (result.count > 0) { */
/* results.push({tab, result}) */
/* } */
/* } */
/* results.sort(r => r.result.count) */
/* console.log(results) */
/* return results */
/* } */
// }}}
// {{{ CMDLINE
2017-12-02 12:08:30 +01:00
/ * * R e p e a t s a ` c m d ` ` n ` t i m e s .
2019-04-21 11:16:13 -07:00
If ` cmd ` doesn ' t exist , re - executes the last exstr that was executed in the tab .
2017-12-02 12:08:30 +01:00
Executes the command once if ` n ` isn ' t defined either .
2019-04-21 01:38:57 -07:00
2019-04-21 11:16:13 -07:00
This re - executes the last * exstr * , not the last * excmd * . Some excmds operate internally by constructing and evaluating exstrs , others by directly invoking excmds without going through the exstr parser . For example , aucmds and keybindings evaluate exstrs and are repeatable , while commands like ` :bmarks ` directly invoke ` :tabopen ` and you ' ll repeat the ` :bmarks ` rather than the internal ` :tabopen ` .
It ' s difficult to execute this in the background script ( ` :jsb ` , ` :run_excmd ` , ` :autocmd TriStart ` , ` :source ` ) , but if you you do , it will re - execute the last exstr that was executed in the background script . What this may have been is unpredictable and not precisely encouraged .
2017-12-02 12:08:30 +01:00
* /
2019-04-21 01:38:57 -07:00
//#both
2019-05-04 14:49:28 -07:00
export async function repeat ( n = 1 , . . . exstr : string [ ] ) {
{excmds,controller}.ts: Fix race condition in state.mode synchronization
This fixes https://github.com/cmcaine/tridactyl/issues/613.
This was a really fun bug. What happened was this:
- First, the content script set state.mode to "hint", which was then
synchronized
- The event listener in the background script noticed the update and set
state.mode to "hint" in the background script
- Then, the content script translated the hint selection into an excmd
that needed to be executed in the background script and sent said
command to the controller, which lives in the background script
- The content script then set state.mode to "normal"
- The background script executed the command and saved it in
state.last_ex_str, which was then synchronized
- The event listener in the content script noticed the update and set
last_ex_str to the last executed str and "state.mode" to "hint"
So basically, the problem was that the background script didn't notice
the state.mode update right after it happened. I fixed this by moving
last_ex_str out of "state" since it doesn't need to be synchronized with
the content script.
This means that the same kind of race condition can still happen. I'm
not sure how to fix this. We could just kill state completely and
instead have state be updated through message passing, but this probably
wouldn't be very ergonomic.
Another solution, the one envisioned for Tridactylv2, is to move to the
content script entirely. This is probably the best option.
2018-06-05 21:13:29 +02:00
let cmd = controller . last_ex_str
2018-04-13 19:28:03 +01:00
if ( exstr . length > 0 ) cmd = exstr . join ( " " )
2017-12-30 00:46:26 +00:00
logger . debug ( "repeating " + cmd + " " + n + " times" )
2019-04-21 01:38:57 -07:00
for ( let i = 0 ; i < n ; i ++ ) {
2019-05-04 14:49:28 -07:00
await controller . acceptExCmd ( cmd )
2019-04-21 01:38:57 -07:00
}
2017-12-02 12:08:30 +01:00
}
2018-05-05 14:14:02 +01:00
/ * *
2018-05-26 18:45:18 +02:00
* Split ` cmds ` on pipes ( | ) and treat each as its own command . Return values are passed as the last argument of the next ex command , e . g ,
2018-05-16 17:12:29 +01:00
*
* ` composite echo yes | fillcmdline ` becomes ` fillcmdline yes ` . A more complicated example is the ex alias , ` command current_url composite get_current_url | fillcmdline_notrail ` , which is used in , e . g . ` bind T current_url tabopen ` .
2018-05-05 14:14:02 +01:00
*
* 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 .
2018-05-16 17:46:25 +01:00
*
* ` cmds ` are also split with semicolons ( ; ) and don ' t pass things along to each other .
*
* The behaviour of combining ; and | in the same composite command is left as an exercise for the reader .
2018-05-05 14:14:02 +01:00
* /
2019-04-21 01:38:57 -07:00
//#both
2018-04-22 17:40:55 +01:00
export async function composite ( . . . cmds : string [ ] ) {
2018-05-26 21:14:30 +02:00
try {
2019-03-12 23:45:08 +00:00
return (
cmds
. join ( " " )
// Semicolons delimit pipelines
. split ( ";" )
// For each pipeline, wait for previous pipeline to finish, then
// execute each cmd in pipeline in order, passing the result of the
// previous cmd as the last argument to the next command.
. reduce (
async ( prev_pipeline , cmd ) = > {
await prev_pipeline
2019-04-13 09:46:23 +02:00
const cmds = cmd . split ( "|" )
2019-03-12 23:45:08 +00:00
2019-04-21 01:38:57 -07:00
// Compute the first piped value.
//
// We could invoke controller.acceptExCmd, but
// that would cause our pipeline section to be
// stored as the last executed command for the
// purposes of :repeat, which would be
// nonsense. So we copy-paste the important
// parts of the body of that function instead.
2019-04-26 01:12:48 -07:00
const [ fn , args ] = excmd_parser . parser ( cmds [ 0 ] , ALL_EXCMDS )
2019-04-13 09:46:23 +02:00
const first_value = fn . call ( { } , . . . args )
2019-03-12 23:45:08 +00:00
// Exec the rest of the pipe in sequence.
return cmds . slice ( 1 ) . reduce ( async ( pipedValue , cmd ) = > {
2019-04-26 01:12:48 -07:00
const [ fn , args ] = excmd_parser . parser ( cmd , ALL_EXCMDS )
2019-03-12 23:45:08 +00:00
return fn . call ( { } , . . . args , await pipedValue )
} , first_value )
} ,
null as any ,
)
)
2018-05-26 21:14:30 +02:00
} catch ( e ) {
logger . error ( e )
2018-04-22 17:40:55 +01:00
}
}
2019-05-29 15:29:47 +01:00
/ * *
* Escape command for safe use in shell with composite . E . g : ` composite js MALICIOUS_WEBSITE_FUNCTION() | shellescape | exclaim ls `
* /
export async function shellescape ( . . . quoteme : string [ ] ) {
return shell_quote . quote ( quoteme )
}
2018-06-03 11:08:45 +02:00
/ * * S l e e p t i m e _ m s m i l l i s e c o n d s .
* This is probably only useful for composite commands that need to wait until the previous asynchronous command has finished running .
* /
2019-04-21 01:38:57 -07:00
//#both
2018-04-22 17:40:55 +01:00
export async function sleep ( time_ms : number ) {
await new Promise ( resolve = > setTimeout ( resolve , time_ms ) )
2017-11-18 13:47:10 +00:00
}
2018-04-12 22:33:10 +01:00
/** @hidden */
2018-12-20 07:30:53 +01:00
//#content
export function showcmdline ( focus = true ) {
CommandLineContent . show ( )
let done = Promise . resolve ( )
if ( focus ) {
CommandLineContent . focus ( )
done = Messaging . messageOwnTab ( "commandline_frame" , "focus" )
}
return done
2017-10-12 04:02:01 +01:00
}
2018-07-02 07:06:26 +02:00
/** @hidden */
2018-12-20 07:41:02 +01:00
//#content
2018-07-02 07:06:26 +02:00
export function hidecmdline() {
2018-12-20 07:41:02 +01:00
CommandLineContent . hide_and_blur ( )
2018-07-02 07:06:26 +02:00
}
2017-11-19 06:05:15 +00:00
/** Set the current value of the commandline to string *with* a trailing space */
2018-12-20 08:15:44 +01:00
//#content
2017-10-24 12:51:04 +01:00
export function fillcmdline ( . . . strarr : string [ ] ) {
2019-04-13 09:46:23 +02:00
const str = strarr . join ( " " )
2017-10-12 04:02:01 +01:00
showcmdline ( )
2018-12-20 08:15:44 +01:00
return Messaging . messageOwnTab ( "commandline_frame" , "fillcmdline" , [ str ] )
2017-10-12 04:02:01 +01:00
}
2017-11-19 06:05:15 +00:00
/** Set the current value of the commandline to string *without* a trailing space */
2018-12-20 08:15:44 +01:00
//#content
2017-11-09 15:30:09 +00:00
export function fillcmdline_notrail ( . . . strarr : string [ ] ) {
2019-04-13 09:46:23 +02:00
const str = strarr . join ( " " )
2017-11-09 15:30:09 +00:00
showcmdline ( )
2018-12-20 08:15:44 +01:00
return Messaging . messageOwnTab ( "commandline_frame" , "fillcmdline" , [ str , false ] )
2017-11-09 15:30:09 +00:00
}
2018-06-30 16:59:10 +02:00
/** Show and fill the command line without focusing it */
2018-12-20 08:15:44 +01:00
//#content
2018-06-30 16:59:10 +02:00
export function fillcmdline_nofocus ( . . . strarr : string [ ] ) {
showcmdline ( false )
2018-12-20 08:15:44 +01:00
return Messaging . messageOwnTab ( "commandline_frame" , "fillcmdline" , [ strarr . join ( " " ) , false , false ] )
2018-06-30 16:59:10 +02:00
}
2018-07-02 19:36:34 +01:00
/** Shows str in the command line for ms milliseconds. Recommended duration: 3000ms. */
2018-12-20 08:15:44 +01:00
//#content
2018-07-30 23:51:08 +01:00
export async function fillcmdline_tmp ( ms : number , . . . strarr : string [ ] ) {
2019-04-13 09:46:23 +02:00
const str = strarr . join ( " " )
2018-07-01 18:17:35 +02:00
showcmdline ( false )
2018-12-20 08:15:44 +01:00
Messaging . messageOwnTab ( "commandline_frame" , "fillcmdline" , [ strarr . join ( " " ) , false , false ] )
2018-06-30 16:59:10 +02:00
return new Promise ( resolve = >
setTimeout ( async ( ) = > {
2019-04-10 07:21:21 +02:00
if ( ( await Messaging . messageOwnTab ( "commandline_frame" , "getContent" , [ ] ) ) === str ) {
2018-12-20 08:15:44 +01:00
CommandLineContent . hide_and_blur ( )
resolve ( Messaging . messageOwnTab ( "commandline_frame" , "clear" , [ true ] ) )
2018-07-05 19:50:32 +02:00
}
2018-06-30 16:59:10 +02:00
resolve ( )
2018-07-30 23:51:08 +01:00
} , ms ) ,
2018-06-30 16:59:10 +02:00
)
}
2018-05-20 22:29:59 -07:00
/ * *
2018-07-03 14:41:55 +01:00
* Copy content to clipboard without feedback . Use ` clipboard yank ` for interactive use .
2018-05-20 22:29:59 -07:00
* /
//#background
2018-11-07 06:13:43 +01:00
export function yank ( . . . content : string [ ] ) {
return setclip ( content . join ( " " ) )
2018-05-20 22:29:59 -07:00
}
2018-07-26 10:45:11 +01:00
/ * *
* Copies a string to the clipboard / selection buffer depending on the user ' s preferences
2018-06-12 06:15:28 +02:00
*
2018-07-26 10:45:11 +01:00
* @hidden
2018-06-12 06:15:28 +02:00
* /
//#background_helper
2018-07-03 06:44:25 +02:00
async function setclip ( str ) {
2018-06-12 06:15:28 +02:00
// Functions to avoid retyping everything everywhere
2018-10-05 10:36:37 +02:00
// Note: We're using fillcmdline here because exceptions are somehow not caught. We're rethrowing because otherwise the error message will be overwritten with the "yank successful" message.
2019-04-13 09:46:23 +02:00
const s = ( ) = > Native . clipboard ( "set" , str )
const c = ( ) = > messageActiveTab ( "commandline_frame" , "setClipboard" , [ str ] )
2018-06-12 06:15:28 +02:00
let promises = [ ]
2018-07-03 19:52:22 +02:00
switch ( await config . getAsync ( "yankto" ) ) {
case "selection" :
2018-06-12 06:15:28 +02:00
promises = [ s ( ) ]
2018-06-13 08:21:38 +02:00
break
2018-07-03 19:52:22 +02:00
case "clipboard" :
2018-06-12 06:15:28 +02:00
promises = [ c ( ) ]
2018-06-13 08:21:38 +02:00
break
2018-07-03 19:52:22 +02:00
case "both" :
promises = [ s ( ) , c ( ) ]
break
2018-06-12 06:15:28 +02:00
}
2018-11-07 06:13:43 +01:00
return Promise . all ( promises )
2018-06-12 06:15:28 +02:00
}
2018-07-26 10:45:11 +01:00
/ * *
* Fetches the content of the clipboard / selection buffer depending on user ' s preferences
2018-06-12 06:15:28 +02:00
*
2019-05-22 10:51:05 +01:00
* Exposed for use with [ [ composite ] ] , e . g . ` composite getclip | fillcmdline `
2018-06-12 06:15:28 +02:00
* /
2019-05-22 10:51:05 +01:00
//#background
export async function getclip ( fromm ? : "clipboard" | "selection" ) {
2019-04-10 07:21:21 +02:00
if ( fromm === undefined ) fromm = await config . getAsync ( "putfrom" )
if ( fromm === "clipboard" ) {
2018-07-17 16:18:48 +01:00
return messageActiveTab ( "commandline_frame" , "getClipboard" )
2018-07-03 19:52:22 +02:00
} else {
2018-11-07 06:13:43 +01:00
return Native . clipboard ( "get" , "" )
2018-07-03 06:44:25 +02:00
}
2018-05-20 22:29:59 -07:00
}
2017-11-19 06:05:15 +00:00
/ * * U s e t h e s y s t e m c l i p b o a r d .
2019-04-10 07:21:21 +02:00
If ` excmd === "open" ` , call [ [ open ] ] with the contents of the clipboard . Similarly for [ [ tabopen ] ] .
2017-11-19 06:05:15 +00:00
2019-04-10 07:21:21 +02:00
If ` excmd === "yank" ` , copy the current URL , or if given , the value of toYank , into the system clipboard .
2017-11-19 06:05:15 +00:00
2019-04-10 07:21:21 +02:00
If ` excmd === "yankcanon" ` , copy the canonical URL of the current page if it exists , otherwise copy the current URL .
2017-11-27 19:15:04 +00:00
2019-04-10 07:21:21 +02:00
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/.
2017-11-27 19:15:04 +00:00
2019-04-10 07:21:21 +02:00
If ` excmd === "yanktitle" ` , copy the title of the open page .
2018-04-11 19:05:30 +01:00
2019-04-10 07:21:21 +02:00
If ` excmd === "yankmd" ` , copy the title and url of the open page formatted in Markdown for easy use on sites such as reddit .
2018-04-11 19:05:30 +01:00
2018-07-03 19:52:22 +02:00
If you ' re on Linux and the native messenger is installed , Tridactyl will call an external binary ( either xclip or xsel ) to read or write to your X selection buffer . If you want another program to be used , set "externalclipboardcmd" to its name and make sure it has the same interface as xsel / xclip ( "-i" / "-o" and reading from stdin ) .
2018-06-12 06:15:28 +02:00
2018-07-03 19:52:22 +02:00
When doing a read operation ( i . e . open or tabopen ) , if "putfrom" is set to "selection" , the X selection buffer will be read instead of the clipboard . Set "putfrom" to "clipboard" to use the clipboard .
2018-06-12 06:15:28 +02:00
2018-07-03 19:52:22 +02:00
When doing a write operation , if "yankto" is set to "selection" , only the X selection buffer will be written to . If "yankto" is set to "both" , both the X selection and the clipboard will be written to . If "yankto" is set to "clipboard" , only the clipboard will be written to .
2017-11-19 06:05:15 +00:00
* /
2017-10-28 19:20:31 +08:00
//#background
2018-10-19 00:36:53 +02:00
export async function clipboard ( excmd : "open" | "yank" | "yankshort" | "yankcanon" | "yanktitle" | "yankmd" | "xselpaste" | "tabopen" = "open" , . . . toYank : string [ ] ) {
2017-11-19 06:05:15 +00:00
let content = toYank . join ( " " )
2017-11-16 19:58:33 +00:00
let url = ""
2017-11-27 19:48:49 +01:00
let urls = [ ]
2018-11-07 06:13:43 +01:00
switch ( excmd ) {
case "yankshort" :
urls = await geturlsforlinks ( "rel" , "shortlink" )
2019-04-10 07:21:21 +02:00
if ( urls . length === 0 ) {
2018-11-07 06:13:43 +01:00
urls = await geturlsforlinks ( "rev" , "canonical" )
}
if ( urls . length > 0 ) {
await yank ( urls [ 0 ] )
fillcmdline_tmp ( 3000 , "# " + urls [ 0 ] + " copied to clipboard." )
2018-07-03 06:44:25 +02:00
break
2018-11-07 06:13:43 +01:00
}
// Trying yankcanon if yankshort failed...
case "yankcanon" :
urls = await geturlsforlinks ( "rel" , "canonical" )
if ( urls . length > 0 ) {
await yank ( urls [ 0 ] )
fillcmdline_tmp ( 3000 , "# " + urls [ 0 ] + " copied to clipboard." )
2018-07-03 06:44:25 +02:00
break
2018-11-07 06:13:43 +01:00
}
// Trying yank if yankcanon failed...
case "yank" :
2019-04-10 07:21:21 +02:00
content = content === "" ? ( await activeTab ( ) ) . url : content
2018-11-07 06:13:43 +01:00
await yank ( content )
fillcmdline_tmp ( 3000 , "# " + content + " copied to clipboard." )
break
case "yanktitle" :
content = ( await activeTab ( ) ) . title
await yank ( content )
fillcmdline_tmp ( 3000 , "# " + content + " copied to clipboard." )
break
case "yankmd" :
content = "[" + ( await activeTab ( ) ) . title + "](" + ( await activeTab ( ) ) . url + ")"
await yank ( content )
fillcmdline_tmp ( 3000 , "# " + content + " copied to clipboard." )
break
case "open" :
url = await getclip ( )
2019-04-04 06:54:11 +02:00
if ( url ) {
open ( url )
}
2018-11-07 06:13:43 +01:00
break
case "tabopen" :
url = await getclip ( )
2019-04-04 06:54:11 +02:00
if ( url ) {
tabopen ( url )
}
2018-11-07 06:13:43 +01:00
break
2018-10-19 00:36:53 +02:00
case "xselpaste" :
content = await getclip ( "selection" )
if ( content . length > 0 ) {
2019-04-20 23:57:59 +02:00
EditorCmds . insert_text ( content )
2018-10-19 00:36:53 +02:00
}
break
2018-11-07 06:13:43 +01:00
default :
// todo: maybe we should have some common error and error handler
throw new Error ( ` [clipboard] unknown excmd: ${ excmd } ` )
2017-10-19 20:15:01 +01:00
}
}
2017-11-27 22:42:50 +11:00
/ * * C h a n g e a c t i v e t a b .
2017-11-27 19:54:45 +00:00
@param index
2018-06-17 06:21:10 +02:00
Starts at 1 . 0 refers to last tab of the current window , - 1 to penultimate tab , etc .
2017-11-27 19:54:45 +00:00
"#" means the tab that was last accessed in this window
2018-06-17 06:21:10 +02:00
2018-11-19 07:14:08 +01:00
This is different from [ [ taball ] ] because ` index ` is the position of the tab in the current window .
2017-11-27 22:42:50 +11:00
* /
2017-10-12 04:02:01 +01:00
//#background
2018-11-19 07:14:08 +01:00
export async function tab ( index : number | "#" ) {
2018-04-12 13:17:17 +01:00
tabIndexSetActive ( index )
2017-10-12 04:02:01 +01:00
}
2018-06-17 06:21:10 +02:00
/ * * C h a n g e a c t i v e t a b .
@param id
2018-06-19 19:42:35 +02:00
A string following the following format : "[0-9]+.[0-9]+" , the first number being the index of the window that should be selected and the second one being the index of the tab within that window .
2018-06-17 06:21:10 +02:00
* /
//#background
2018-11-19 07:14:08 +01:00
export async function taball ( id : string ) {
2019-04-13 09:46:23 +02:00
const windows = ( await browser . windows . getAll ( ) ) . map ( w = > w . id ) . sort ( ( a , b ) = > a - b )
2018-06-19 19:42:35 +02:00
if ( id === null || id === undefined || ! id . match ( /\d+\.\d+/ ) ) {
const tab = await activeTab ( )
2019-04-13 09:46:23 +02:00
const prevId = id
2018-06-19 19:42:35 +02:00
id = windows . indexOf ( tab . windowId ) + "." + ( tab . index + 1 )
2018-11-19 07:14:08 +01:00
logger . info ( ` taball: Bad tab id: ${ prevId } , defaulting to ${ id } ` )
2018-06-18 07:55:46 +02:00
}
2019-05-27 10:08:23 +02:00
const [ winindex , tabindex_string ] = id . split ( "." )
const winid = windows [ parseInt ( winindex , 10 ) - 1 ]
const tabindex_number = parseInt ( tabindex_string , 10 ) - 1
const tabid = ( await browser . tabs . query ( { windowId : winid , index : tabindex_number } ) ) [ 0 ] . id
await browser . windows . update ( winid , { focused : true } )
return browser . tabs . update ( tabid , { active : true } )
2018-06-17 06:21:10 +02:00
}
2017-10-12 04:02:01 +01:00
// }}}
// }}}
2017-11-05 14:10:11 +00:00
2017-11-09 00:41:07 +00:00
// {{{ SETTINGS
2018-01-28 15:09:12 +00:00
/ * *
2018-01-09 17:34:15 +08:00
* Similar to vim ' s ` :command ` . Maps one ex - mode command to another .
2018-01-09 00:07:06 +08:00
* If command already exists , this will override it , and any new commands
* added in a future release will be SILENTLY overridden . Aliases are
* expanded recursively .
2018-01-28 15:09:12 +00:00
*
2017-12-31 16:53:35 +08:00
* Examples :
* - ` command t tabopen `
* - ` command tn tabnext_gt `
2018-01-09 00:07:06 +08:00
* = ` command hello t ` This will expand recursively into 'hello' - > 'tabopen'
2018-01-28 15:09:12 +00:00
*
2017-12-31 16:53:35 +08:00
* Note that this is only for excmd - > excmd mappings . To map a normal - mode
* command to an excommand , see [ [ bind ] ] .
2018-01-28 15:09:12 +00:00
*
2018-01-09 17:34:15 +08:00
* See also :
* - [ [ comclear ] ]
2017-12-31 16:53:35 +08:00
* /
//#background
export function command ( name : string , . . . definition : string [ ] ) {
2018-01-28 17:57:46 +00:00
// Test if alias creates an alias loop.
2018-01-09 17:34:15 +08:00
try {
2018-01-28 17:57:46 +00:00
const def = definition . join ( " " )
2018-01-28 18:13:21 +00:00
aliases . expandExstr ( name )
2019-01-21 19:49:24 +01:00
return config . set ( "exaliases" , name , def )
2018-04-13 19:28:03 +01:00
} catch ( e ) {
2018-01-28 17:57:46 +00:00
config . unset ( "exaliases" , name )
2019-01-21 19:49:24 +01:00
throw ` Alias not set. ${ e } `
2018-01-09 17:34:15 +08:00
}
2017-12-31 16:53:35 +08:00
}
2018-01-09 00:20:18 +08:00
/ * *
* Similar to vim ' s ` comclear ` command . Clears an excmd alias defined by
2018-01-28 15:09:12 +00:00
* ` command ` .
*
* For example : ` comclear helloworld ` will reverse any changes caused
2018-01-09 00:20:18 +08:00
* by ` command helloworld xxx `
2018-01-28 15:09:12 +00:00
*
2018-01-09 17:34:15 +08:00
* See also :
* - [ [ command ] ]
2018-01-09 00:20:18 +08:00
* /
//#background
export function comclear ( name : string ) {
config . unset ( "exaliases" , name )
}
2018-08-09 06:21:00 +02:00
/** @hidden */
2018-08-03 06:50:04 +02:00
//#background_helper
2019-04-02 18:22:54 +02:00
interface bind_args {
mode : string
configName : string
key : string
excmd : string
}
2018-08-03 06:50:04 +02:00
2018-08-09 06:21:00 +02:00
/** @hidden */
2018-08-03 06:50:04 +02:00
//#background_helper
function parse_bind_args ( . . . args : string [ ] ) : bind_args {
2019-04-10 07:21:21 +02:00
if ( args . length === 0 ) throw new Error ( "Invalid bind/unbind arguments." )
2018-08-03 06:50:04 +02:00
2019-04-13 09:46:23 +02:00
const result = { } as bind_args
2018-08-03 06:50:04 +02:00
result . mode = "normal"
2018-09-01 16:56:28 -07:00
// TODO: This mapping is copy-pasted in controller_content.ts,
// where it constructs the list of parsers. it should be
// centralized, possibly as part of rewrite for content-local maps
// and similar.
2019-05-28 12:29:53 +02:00
const mode2maps = new Map ( [ [ "normal" , "nmaps" ] , [ "ignore" , "ignoremaps" ] , [ "insert" , "imaps" ] , [ "input" , "inputmaps" ] , [ "ex" , "exmaps" ] , [ "hint" , "hintmaps" ] ] )
2018-08-03 06:50:04 +02:00
if ( args [ 0 ] . startsWith ( "--mode=" ) ) {
result . mode = args . shift ( ) . replace ( "--mode=" , "" )
}
2019-05-30 15:37:06 +01:00
if ( ! mode2maps . has ( result . mode ) ) {
result . configName = result . mode + "maps"
} else {
result . configName = mode2maps . get ( result . mode )
}
2018-08-03 06:50:04 +02:00
2019-04-13 09:46:23 +02:00
const key = args . shift ( )
2018-08-03 06:50:04 +02:00
// Convert key to internal representation
result . key = mapstrToKeyseq ( key )
. map ( k = > k . toMapstr ( ) )
. join ( "" )
result . excmd = args . join ( " " )
return result
}
2018-02-19 01:12:58 +00:00
/ * * B i n d a s e q u e n c e o f k e y s t o a n e x c m d o r v i e w b o u n d s e q u e n c e .
2017-11-19 06:05:15 +00:00
This is an easier - to - implement bodge while we work on vim - style maps .
Examples :
- ` bind G fillcmdline tabopen google `
2018-11-20 11:23:34 -08:00
- ` bind D composite tabclose | tab # ` - > close current tab and switch to most recent previous tab
2017-11-19 06:05:15 +00:00
- ` bind j scrollline 20 `
- ` bind F hint -b `
2018-02-19 01:12:58 +00:00
You can view binds by omitting the command line :
- ` bind j `
- ` bind k `
2018-05-07 12:54:53 +01:00
You can bind to modifiers and special keys by enclosing them with angle brackets , for example ` bind <C- \ >z fullscreen ` , ` unbind <F1> ` ( a favourite of people who use TreeStyleTabs : ) ) , or ` bind <Backspace> forward ` .
2018-04-16 12:45:40 +01:00
Modifiers are truncated to a single character , so Ctrl - > C , Alt - > A , and Shift - > S . Shift is a bit special as it is only required if Shift does not change the key inputted , e . g . ` <S-ArrowDown> ` is OK , but ` <S-a> ` should just be ` A ` .
You can view all special key names here : https : //developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
2017-11-19 06:05:15 +00:00
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 ) .
2019-05-28 13:08:48 +01:00
You can bind to other modes with ` bind --mode={insert|ignore|normal|input|ex|hint} ... ` , e . g , ` bind --mode=insert emacs qall ` ( NB : unlike vim , all preceeding characters will not be input ) , or ` bind --mode=hint <C-[> hint.reset ` .
2018-07-30 18:27:08 +01:00
2017-11-19 06:05:15 +00:00
See also :
- [ [ unbind ] ]
- [ [ reset ] ]
* /
2017-11-05 14:10:11 +00:00
//#background
2018-07-30 18:27:08 +01:00
export function bind ( . . . args : string [ ] ) {
2019-04-13 09:46:23 +02:00
const args_obj = parse_bind_args ( . . . args )
2019-01-21 19:49:24 +01:00
let p = Promise . resolve ( )
2019-04-10 07:21:21 +02:00
if ( args_obj . excmd !== "" ) {
2019-02-06 15:21:58 +00:00
for ( let i = 0 ; i < args_obj . key . length ; i ++ ) {
// Check if any initial subsequence of the key exists and will shadow the new binding
2019-04-13 09:46:23 +02:00
const key_sub = args_obj . key . slice ( 0 , i )
2019-05-28 12:49:45 +01:00
if ( config . getDynamic ( args_obj . configName , key_sub ) ) {
2019-02-06 15:21:58 +00:00
fillcmdline_notrail ( "# Warning: bind `" + key_sub + "` exists and will shadow `" + args_obj . key + "`. Try running `:unbind --mode=" + args_obj . mode + " " + key_sub + "`" )
break
2019-02-05 16:37:03 +01:00
}
}
2019-01-21 19:49:24 +01:00
p = config . set ( args_obj . configName , args_obj . key , args_obj . excmd )
2018-08-03 06:50:04 +02:00
} else if ( args_obj . key . length ) {
2018-02-19 01:12:58 +00:00
// Display the existing bind
2019-05-28 12:49:45 +01:00
p = fillcmdline_notrail ( "#" , args_obj . key , "=" , config . getDynamic ( args_obj . configName , args_obj . key ) )
2018-02-19 01:12:58 +00:00
}
2019-01-21 19:49:24 +01:00
return p
2017-11-05 14:10:11 +00:00
}
2018-10-03 18:29:50 +02:00
/ * *
* Like [ [ bind ] ] but for a specific url pattern ( also see [ [ seturl ] ] ) .
*
* @param pattern Mandatory . The pattern on which the binding should take effect .
* @param mode Optional . The mode the binding should be in ( e . g . normal , insert , ignore , input ) . Defaults to normal .
* @param keys Mandatory . The keys that should be bound .
* @param excmd Optional . Without it , will display what ` keys ` are bound to in ` mode ` .
*
* /
//#background
export function bindurl ( pattern : string , mode : string , keys : string , . . . excmd : string [ ] ) {
2019-04-13 09:46:23 +02:00
const args_obj = parse_bind_args ( mode , keys , . . . excmd )
2019-01-21 19:49:24 +01:00
let p = Promise . resolve ( )
2019-04-10 07:21:21 +02:00
if ( args_obj . excmd !== "" ) {
2019-01-21 19:49:24 +01:00
p = config . setURL ( pattern , args_obj . configName , args_obj . key , args_obj . excmd )
2018-10-03 18:29:50 +02:00
} else if ( args_obj . key . length ) {
// Display the existing bind
2019-01-21 19:49:24 +01:00
p = fillcmdline_notrail ( "#" , args_obj . key , "=" , config . getURL ( pattern , [ args_obj . configName , args_obj . key ] ) )
2018-10-03 18:29:50 +02:00
}
2019-01-21 19:49:24 +01:00
return p
2018-10-03 18:29:50 +02:00
}
2018-09-05 14:34:10 +01:00
/ * *
* Makes one key equivalent to another for the purposes of most of our parsers . Useful for international keyboard layouts .
*
* e . g ,
* keymap ę e
2019-05-29 18:11:02 +01:00
*
* See ` :help keytranslatemodes ` to enable keymaps in modes other than normal mode .
2018-09-05 14:34:10 +01:00
* /
//#background
export function keymap ( source : string , target : string ) {
set ( "keytranslatemap." + source , target )
}
2018-02-18 16:05:38 +00:00
/ * *
2019-02-04 18:35:54 +01:00
* @hidden
* /
2017-12-03 11:30:44 +00:00
//#background
2019-02-04 18:35:54 +01:00
export function searchsetkeyword() {
throw ":searchsetkeyword has been deprecated. Use `set searchurls.KEYWORD URL` instead."
2018-02-01 23:39:23 +00:00
}
Implement site-specific settings
This PR implements site-specific settings. It requires multiple changes
and new features.
First, I needed to create a new value in `window.tri` named
`contentLocation`. This contentLocation object is either a Location or
URL object containing the URL of the currently selected tab of the
currently selected window. This is required because config.get() is not
asynchronous and we probably want to keep it that way, thus we can't
message the content script just to get the current url.
Then, we create a new object, URLCONFIGS, in content.ts. It behaves
exactly the same as USERCONFIG, except it has a level of indirection,
matching url patterns to config objects. This requires creating new
seturl, geturl and unseturl functions which behave mostly the same as
set, get and unset.
Last, we create a `seturl` ex command in order to interact with this new
object.
2018-09-10 18:51:31 +02:00
/ * *
* Validates arguments for set / seturl
* @hidden
* /
function validateSetArgs ( key : string , values : string [ ] ) {
2018-10-07 13:52:48 +02:00
const target : any [ ] = key . split ( "." )
Implement site-specific settings
This PR implements site-specific settings. It requires multiple changes
and new features.
First, I needed to create a new value in `window.tri` named
`contentLocation`. This contentLocation object is either a Location or
URL object containing the URL of the currently selected tab of the
currently selected window. This is required because config.get() is not
asynchronous and we probably want to keep it that way, thus we can't
message the content script just to get the current url.
Then, we create a new object, URLCONFIGS, in content.ts. It behaves
exactly the same as USERCONFIG, except it has a level of indirection,
matching url patterns to config objects. This requires creating new
seturl, geturl and unseturl functions which behave mostly the same as
set, get and unset.
Last, we create a `seturl` ex command in order to interact with this new
object.
2018-09-10 18:51:31 +02:00
2019-04-05 08:59:21 +02:00
let value
2019-04-13 09:46:23 +02:00
const file = Metadata . everything . getFile ( "src/lib/config.ts" )
const default_config = file . getClass ( "default_config" )
const md = default_config . getMember ( target [ 0 ] )
2019-04-05 08:59:21 +02:00
if ( md !== undefined ) {
2018-11-04 17:20:01 +01:00
const strval = values . join ( " " )
// Note: the conversion will throw if strval can't be converted to the right type
2019-04-10 07:21:21 +02:00
if ( md . type . kind === "object" && target . length > 1 ) {
2019-04-05 08:59:21 +02:00
value = ( md as any ) . type . convertMember ( target . slice ( 1 ) , strval )
2019-01-21 19:15:00 +01:00
} else {
value = md . type . convert ( strval )
}
Implement site-specific settings
This PR implements site-specific settings. It requires multiple changes
and new features.
First, I needed to create a new value in `window.tri` named
`contentLocation`. This contentLocation object is either a Location or
URL object containing the URL of the currently selected tab of the
currently selected window. This is required because config.get() is not
asynchronous and we probably want to keep it that way, thus we can't
message the content script just to get the current url.
Then, we create a new object, URLCONFIGS, in content.ts. It behaves
exactly the same as USERCONFIG, except it has a level of indirection,
matching url patterns to config objects. This requires creating new
seturl, geturl and unseturl functions which behave mostly the same as
set, get and unset.
Last, we create a `seturl` ex command in order to interact with this new
object.
2018-09-10 18:51:31 +02:00
} else {
2018-11-04 17:20:01 +01:00
// If we don't have metadata, fall back to the old way
logger . warning ( "Could not fetch setting metadata. Falling back to type of current value." )
const currentValue = config . get ( . . . target )
if ( Array . isArray ( currentValue ) ) {
// Do nothing
} else if ( currentValue === undefined || typeof currentValue === "string" ) {
value = values . join ( " " )
} else {
throw "Unsupported setting type!"
2018-09-22 12:34:26 +02:00
}
Implement site-specific settings
This PR implements site-specific settings. It requires multiple changes
and new features.
First, I needed to create a new value in `window.tri` named
`contentLocation`. This contentLocation object is either a Location or
URL object containing the URL of the currently selected tab of the
currently selected window. This is required because config.get() is not
asynchronous and we probably want to keep it that way, thus we can't
message the content script just to get the current url.
Then, we create a new object, URLCONFIGS, in content.ts. It behaves
exactly the same as USERCONFIG, except it has a level of indirection,
matching url patterns to config objects. This requires creating new
seturl, geturl and unseturl functions which behave mostly the same as
set, get and unset.
Last, we create a `seturl` ex command in order to interact with this new
object.
2018-09-10 18:51:31 +02:00
}
2018-10-07 13:52:48 +02:00
target . push ( value )
return target
Implement site-specific settings
This PR implements site-specific settings. It requires multiple changes
and new features.
First, I needed to create a new value in `window.tri` named
`contentLocation`. This contentLocation object is either a Location or
URL object containing the URL of the currently selected tab of the
currently selected window. This is required because config.get() is not
asynchronous and we probably want to keep it that way, thus we can't
message the content script just to get the current url.
Then, we create a new object, URLCONFIGS, in content.ts. It behaves
exactly the same as USERCONFIG, except it has a level of indirection,
matching url patterns to config objects. This requires creating new
seturl, geturl and unseturl functions which behave mostly the same as
set, get and unset.
Last, we create a `seturl` ex command in order to interact with this new
object.
2018-09-10 18:51:31 +02:00
}
/ * *
* Usage : ` seturl [pattern] key values `
*
2018-09-12 11:12:17 +02:00
* @param pattern The URL pattern the setting should be set for , e . g . ` en.wikipedia.org ` or ` /index.html ` . Defaults to the current url if ` values ` is a single word .
Implement site-specific settings
This PR implements site-specific settings. It requires multiple changes
and new features.
First, I needed to create a new value in `window.tri` named
`contentLocation`. This contentLocation object is either a Location or
URL object containing the URL of the currently selected tab of the
currently selected window. This is required because config.get() is not
asynchronous and we probably want to keep it that way, thus we can't
message the content script just to get the current url.
Then, we create a new object, URLCONFIGS, in content.ts. It behaves
exactly the same as USERCONFIG, except it has a level of indirection,
matching url patterns to config objects. This requires creating new
seturl, geturl and unseturl functions which behave mostly the same as
set, get and unset.
Last, we create a `seturl` ex command in order to interact with this new
object.
2018-09-10 18:51:31 +02:00
* @param key The name of the setting you want to set , e . g . ` followpagepatterns.next `
* @param values The value you wish for , e . g . ` next `
*
* Example :
2018-11-20 11:23:34 -08:00
* - ` seturl .* \ .fr followpagepatterns.next suivant `
* - ` seturl website.fr followpagepatterns.next next `
Implement site-specific settings
This PR implements site-specific settings. It requires multiple changes
and new features.
First, I needed to create a new value in `window.tri` named
`contentLocation`. This contentLocation object is either a Location or
URL object containing the URL of the currently selected tab of the
currently selected window. This is required because config.get() is not
asynchronous and we probably want to keep it that way, thus we can't
message the content script just to get the current url.
Then, we create a new object, URLCONFIGS, in content.ts. It behaves
exactly the same as USERCONFIG, except it has a level of indirection,
matching url patterns to config objects. This requires creating new
seturl, geturl and unseturl functions which behave mostly the same as
set, get and unset.
Last, we create a `seturl` ex command in order to interact with this new
object.
2018-09-10 18:51:31 +02:00
*
2018-09-12 11:12:17 +02:00
* When multiple patterns can apply to a same URL , the pattern that has the highest priority is used . You can set the priority of a pattern by using ` :seturl pattern priority 10 ` . By default every pattern has a priority of 10 .
Implement site-specific settings
This PR implements site-specific settings. It requires multiple changes
and new features.
First, I needed to create a new value in `window.tri` named
`contentLocation`. This contentLocation object is either a Location or
URL object containing the URL of the currently selected tab of the
currently selected window. This is required because config.get() is not
asynchronous and we probably want to keep it that way, thus we can't
message the content script just to get the current url.
Then, we create a new object, URLCONFIGS, in content.ts. It behaves
exactly the same as USERCONFIG, except it has a level of indirection,
matching url patterns to config objects. This requires creating new
seturl, geturl and unseturl functions which behave mostly the same as
set, get and unset.
Last, we create a `seturl` ex command in order to interact with this new
object.
2018-09-10 18:51:31 +02:00
*
* Note that the patterns a regex - like , not glob - like . This means that if you want to match everything , you need to use ` .* ` instead of ` * ` .
* /
//#content
export function seturl ( pattern : string , key : string , . . . values : string [ ] ) {
2019-04-10 07:21:21 +02:00
if ( values . length === 0 && key ) {
Implement site-specific settings
This PR implements site-specific settings. It requires multiple changes
and new features.
First, I needed to create a new value in `window.tri` named
`contentLocation`. This contentLocation object is either a Location or
URL object containing the URL of the currently selected tab of the
currently selected window. This is required because config.get() is not
asynchronous and we probably want to keep it that way, thus we can't
message the content script just to get the current url.
Then, we create a new object, URLCONFIGS, in content.ts. It behaves
exactly the same as USERCONFIG, except it has a level of indirection,
matching url patterns to config objects. This requires creating new
seturl, geturl and unseturl functions which behave mostly the same as
set, get and unset.
Last, we create a `seturl` ex command in order to interact with this new
object.
2018-09-10 18:51:31 +02:00
values = [ key ]
key = pattern
pattern = window . location . href
}
if ( ! pattern || ! key || ! values . length ) {
throw "seturl syntax: [pattern] key value"
}
2019-01-21 19:49:24 +01:00
return config . setURL ( pattern , . . . validateSetArgs ( key , values ) )
Implement site-specific settings
This PR implements site-specific settings. It requires multiple changes
and new features.
First, I needed to create a new value in `window.tri` named
`contentLocation`. This contentLocation object is either a Location or
URL object containing the URL of the currently selected tab of the
currently selected window. This is required because config.get() is not
asynchronous and we probably want to keep it that way, thus we can't
message the content script just to get the current url.
Then, we create a new object, URLCONFIGS, in content.ts. It behaves
exactly the same as USERCONFIG, except it has a level of indirection,
matching url patterns to config objects. This requires creating new
seturl, geturl and unseturl functions which behave mostly the same as
set, get and unset.
Last, we create a `seturl` ex command in order to interact with this new
object.
2018-09-10 18:51:31 +02:00
}
2018-02-01 23:39:23 +00:00
/ * * S e t a k e y v a l u e p a i r i n c o n f i g .
2018-11-16 19:34:30 +01:00
Use to set any string values found [ here ] ( / s t a t i c / d o c s / c l a s s e s / _ s r c _ l i b _ c o n f i g _ . d e f a u l t _ c o n f i g . h t m l ) .
2018-02-01 23:39:23 +00:00
e . g .
set searchurls . google https : //www.google.com/search?q=
set logging . messaging info
2018-06-03 11:08:45 +02:00
If no value is given , the value of the of the key will be displayed
2018-02-01 23:39:23 +00:00
* /
//#background
2018-02-02 13:06:30 +00:00
export function set ( key : string , . . . values : string [ ] ) {
2018-05-19 14:58:06 -07:00
if ( ! key ) {
throw "Key must be provided!"
} else if ( ! values [ 0 ] ) {
2019-01-21 19:49:24 +01:00
return get ( key )
2018-02-01 23:39:23 +00:00
}
2019-04-10 07:21:21 +02:00
if ( key === "noiframeon" ) {
2019-04-13 09:46:23 +02:00
const noiframes = config . get ( "noiframeon" )
2018-11-28 07:55:37 +01:00
// unset previous settings
2018-12-08 05:59:49 +01:00
if ( noiframes ) noiframes . forEach ( url = > seturl ( url , "noiframe" , "false" ) )
2018-11-28 07:55:37 +01:00
// store new settings
values . forEach ( url = > seturl ( url , "noiframe" , "true" ) )
// save as deprecated setting for compatibility
config . set ( "noiframeon" , values )
throw "Warning: `noiframeon $url1 $url2` has been deprecated in favor of `:seturl $url1 noiframe true`. The right seturl calls have been made for you but from now on please use `:seturl`."
}
2019-01-21 19:49:24 +01:00
return config . set ( . . . validateSetArgs ( key , values ) )
2017-12-03 11:30:44 +00:00
}
2018-07-12 16:51:25 +01:00
/** @hidden */
//#background_helper
2019-04-13 09:46:23 +02:00
const AUCMDS = [ "DocStart" , "DocLoad" , "DocEnd" , "TriStart" , "TabEnter" , "TabLeft" , "FullscreenChange" , "FullscreenEnter" , "FullscreenLeft" ]
2018-02-02 14:53:58 +00:00
/ * * S e t a u t o c m d s t o r u n w h e n c e r t a i n e v e n t s h a p p e n .
2018-10-25 22:40:25 -06:00
@param event Curently , 'TriStart' , 'DocStart' , 'DocLoad' , 'DocEnd' , 'TabEnter' , 'TabLeft' , 'FullscreenChange' , 'FullscreenEnter' , and 'FullscreenLeft' are supported
2018-02-02 14:53:58 +00:00
2018-06-02 05:35:48 +02:00
@param url For DocStart , DocEnd , TabEnter , and TabLeft : a fragment of the URL on which the events will trigger , or a JavaScript regex ( e . g , ` /www \ .amazon \ .co.* \ / ` )
2018-05-19 14:55:11 +01:00
We just use [ URL . search ] ( https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search).
2018-05-15 07:00:56 +02:00
For TriStart : A regular expression that matches the hostname of the computer
the autocmd should be run on . This requires the native messenger to be
installed , except for the ".*" regular expression which will always be
triggered , even without the native messenger .
2018-02-02 14:53:58 +00:00
@param excmd The excmd to run ( use [ [ composite ] ] to run multiple commands )
* /
2018-01-31 19:51:08 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function autocmd ( event : string , url : string , . . . excmd : string [ ] ) {
2018-02-02 14:53:58 +00:00
// rudimentary run time type checking
// TODO: Decide on autocmd event names
2018-07-12 16:51:25 +01:00
if ( ! AUCMDS . includes ( event ) ) throw event + " is not a supported event."
2018-02-02 13:06:30 +00:00
config . set ( "autocmds" , event , url , excmd . join ( " " ) )
2018-01-31 19:51:08 +00:00
}
2018-07-24 17:40:36 +01:00
/ * * A u t o m a t i c a l l y o p e n a d o m a i n a n d a l l i t s s u b d o m a i n s i n a s p e c i f i e d c o n t a i n e r .
2018-07-25 12:32:53 +00:00
*
* For declaring containers that do not yet exist , consider using ` auconscreatecontainer true ` in your tridactylrc .
* This allows tridactyl to automatically create containers from your autocontain directives . Note that they will be random icons and colors .
*
* * * NB : This is an experimental feature , if you encounter issues please create an issue on github . * *
*
* The domain is passed through as a regular expression so there are a few gotchas to be aware of :
* * Unescaped periods will match * anything * . ` autocontain google.co.uk work ` will match ` google!co $ uk ` . Escape your periods or accept that you might get some false positives .
* * You can use regex in your domain pattern . ` autocontain google \ ,(co \ .uk|com) work ` will match either ` google.co.uk ` or ` google.com ` .
*
2019-03-21 22:19:03 -07:00
* This * should * now peacefully coexist with the Temporary Containers and Multi - Account Containers addons . Do not trust this claim . If a fight starts the participants will try to open infinite tabs . It is * strongly * recommended that you use a tridactylrc so that you can abort a sorceror ' s - apprentice scenario by killing firefox , commenting out all of autocontainer directives in your rc file , and restarting firefox to clean up the mess . There are a number of strange behaviors resulting from limited coordination between extensions . Redirects can be particularly surprising ; for example , with ` :autocontain will-redirect.example.org example ` set and ` will-redirect.example.org ` redirecting to ` redirected.example.org ` , navigating to ` will-redirect.example.org ` will result in the new tab being in the ` example ` container under some conditions and in the ` firefox-default ` container under others .
Teach autocontainers to coexist with other container-management exts
This has a few moving parts.
First, we need a bit of code for keeping track of what other
extensions are installed and enabled. This isn't completely trivial
because we need to listen for "on {en,dis}abled" and "on
{,un}installed" events. This requires a new permission, `management`,
which is _annoying_, but not having this permission would require
extension detection to be done using kludgy messaging hacks that would
be on the critical path for essentially every navigation operation.
Second, we need to write code to talk to the other addons and ask them
if they're handling things. Thankfully they do provide public APIs and
and we can use their sample code to do exactly what we need to do.
Third, it turns out some important chunks of the firefox webext API
aren't handled by the web-ext-browser ts declaration we're
using. They *are* handled by a PR on @types/firefox-webext-browser,
though, so we can copy and paste that to make TS happy.
Fourth, and finally, we need to add some code to the autocontainer
logic to use the compatibility code. This is pretty easy, but
autocontainer logic starts taking a noticeable amount of time becuase
of all the sequences awaits we're doing, so I also have to tweak
things to do all of the async stuff in parallel.
2018-08-27 03:27:02 -07:00
*
2018-07-25 12:32:53 +00:00
* @param domain The domain which will trigger the autoContain directive . Includes all subdomains .
* @param container The container to open the url in .
2018-07-07 11:18:02 +00:00
* /
//#background
2018-07-07 18:38:06 +00:00
export function autocontain ( domain : string , container : string ) {
config . set ( "autocontain" , domain , container )
2018-07-07 11:18:02 +00:00
}
2018-07-02 22:53:04 -07:00
/ * * R e m o v e a u t o c m d s
2018-10-25 22:40:25 -06:00
@param event Curently , 'TriStart' , 'DocStart' , 'DocLoad' , 'DocEnd' , 'TabEnter' , 'TabLeft' , 'FullscreenChange' , 'FullscreenEnter' , and 'FullscreenLeft' are supported
2018-07-02 22:53:04 -07:00
@param url For DocStart , DocEnd , TabEnter , and TabLeft : a fragment of the URL on which the events will trigger , or a JavaScript regex ( e . g , ` /www \ .amazon \ .co.* \ / ` )
* /
//#background
2018-07-12 16:51:25 +01:00
export function autocmddelete ( event : string , url : string ) {
if ( ! AUCMDS . includes ( event ) ) throw event + " is not a supported event."
2018-07-02 22:53:04 -07:00
config . unset ( "autocmds" , event , url )
}
2018-01-31 19:51:08 +00:00
2018-06-01 09:50:24 +01:00
/ * *
* Helper function to put Tridactyl into ignore mode on the provided URL .
*
2018-09-01 16:29:59 -07:00
* Simply creates a DocStart [ [ autocmd ] ] that runs ` mode ignore ` .
2018-06-01 09:50:24 +01:00
*
* Due to a Tridactyl bug , the only way to remove these rules once they are set is to delete all of your autocmds with ` unset autocmds ` .
*
2019-01-29 19:51:40 +01:00
* If you 're looking for a way to temporarily disable Tridactyl, this might be what you' re looking for .
*
2018-06-01 09:50:24 +01:00
* <!-- this should probably be moved to an ex alias once configuration has better help - - !>
*
* /
//#background
export function blacklistadd ( url : string ) {
2018-09-01 16:29:59 -07:00
autocmd ( "DocStart" , url , "mode ignore" )
2018-06-01 09:50:24 +01:00
}
2017-11-19 06:05:15 +00:00
/ * * U n b i n d a s e q u e n c e o f k e y s s o t h a t t h e y d o n o t h i n g a t a l l .
See also :
- [ [ bind ] ]
- [ [ reset ] ]
* /
2017-11-05 14:10:11 +00:00
//#background
2018-08-03 06:50:04 +02:00
export async function unbind ( . . . args : string [ ] ) {
2019-04-13 09:46:23 +02:00
const args_obj = parse_bind_args ( . . . args )
2019-04-10 07:21:21 +02:00
if ( args_obj . excmd !== "" ) throw new Error ( "unbind syntax: `unbind key`" )
2018-08-03 06:50:04 +02:00
2019-01-21 19:49:24 +01:00
return config . set ( args_obj . configName , args_obj . key , "" )
2017-11-05 14:10:11 +00:00
}
2018-10-03 18:29:50 +02:00
/ * *
* Unbind a sequence of keys you have set with [ [ bindurl ] ] . Note that this * * kills * * a bind , which means Tridactyl will pass it to the page on ` pattern ` . If instead you want to use the default setting again , use [ [ reseturl ] ] .
*
* @param pattern the url on which the key should be unbound
* @param mode Optional . The mode in which the key should be unbound . Defaults to normal .
* @param keys The keybinding that should be unbound
*
* example : ` unbindurl jupyter --mode=ignore I `
*
* This unbinds ` I ` in ignore mode on every website the URL of which contains ` jupyter ` , while keeping the binding active everywhere else .
*
* Also see [ [ bind ] ] , [ [ bindurl ] ] , [ [ seturl ] ] , [ [ unbind ] ] , [ [ unseturl ] ]
* /
//#background
export async function unbindurl ( pattern : string , mode : string , keys : string ) {
2019-04-13 09:46:23 +02:00
const args_obj = parse_bind_args ( mode , keys )
2017-11-19 06:05:15 +00:00
2019-01-21 19:49:24 +01:00
return config . setURL ( pattern , args_obj . configName , args_obj . key , "" )
2018-10-03 18:29:50 +02:00
}
2017-11-19 06:05:15 +00:00
2018-10-03 18:29:50 +02:00
/ * *
* Restores a sequence of keys to their default value .
*
* @param mode Optional . The mode the key should be reset in . Defaults to normal .
* @param key The key that should be reset .
*
2019-01-02 16:53:40 +01:00
* See also :
2018-12-19 08:12:26 +01:00
* - [ [ bind ] ]
* - [ [ unbind ] ]
2018-10-03 18:29:50 +02:00
* /
2017-11-05 14:10:11 +00:00
//#background
2018-10-03 18:29:50 +02:00
export async function reset ( mode : string , key : string ) {
2019-04-13 09:46:23 +02:00
const args_obj = parse_bind_args ( mode , key )
2019-01-21 19:49:24 +01:00
return config . unset ( args_obj . configName , args_obj . key )
2018-10-03 18:29:50 +02:00
}
2017-11-29 19:51:18 +00:00
2018-10-03 18:29:50 +02:00
/ * *
* Restores a sequence of keys to their value in the global config for a specific URL .
*
2019-01-02 16:53:40 +01:00
* See also :
2018-12-19 08:12:26 +01:00
* - [ [ bind ] ]
* - [ [ unbind ] ]
* - [ [ reset ] ]
* - [ [ bindurl ] ]
* - [ [ unbindurl ] ]
* - [ [ seturl ] ]
* - [ [ unseturl ] ]
2018-10-03 18:29:50 +02:00
* /
//#background
export async function reseturl ( pattern : string , mode : string , key : string ) {
2019-04-13 09:46:23 +02:00
const args_obj = parse_bind_args ( mode , key )
2019-01-21 19:49:24 +01:00
return config . unsetURL ( pattern , args_obj . configName , args_obj . key )
2017-11-05 14:10:11 +00:00
}
2017-12-02 00:20:32 +01:00
/ * * D e l e t e s v a r i o u s p r i v a c y - r e l a t e d i t e m s .
The list of possible arguments can be found here :
https : //developer.mozilla.org/en-US/Add-ons/WebExtensions/API/browsingData/DataTypeSet
Additional , tridactyl - specific arguments are :
- commandline : Removes the in - memory commandline history .
2017-12-02 13:27:02 +00:00
- tridactyllocal : Removes all tridactyl storage local to this machine . Use it with
2017-12-02 00:20:32 +01:00
commandline if you want to delete your commandline history .
2017-12-02 13:27:02 +00:00
- tridactylsync : Removes all tridactyl storage associated with your Firefox Account ( i . e , all user configuration , by default ) .
2017-12-02 00:20:32 +01:00
These arguments aren ' t affected by the timespan parameter .
Timespan parameter :
- t [ 0 - 9 ] + ( m | h | d | w )
Examples :
2018-04-15 14:11:00 +01:00
- ` sanitise all ` - > Deletes everything
- ` sanitise history ` - > Deletes all history
- ` sanitise commandline tridactyllocal tridactylsync ` - > Deletes every bit of data Tridactyl holds
- ` sanitise cookies -t 3d ` - > Deletes cookies that were set during the last three days .
2017-12-02 00:20:32 +01:00
* /
//#background
2018-04-12 22:33:10 +01:00
export async function sanitise ( . . . args : string [ ] ) {
2019-04-13 09:46:23 +02:00
const flagpos = args . indexOf ( "-t" )
2017-12-02 00:20:32 +01:00
let since = { }
// If the -t flag has been given and there is an arg after it
if ( flagpos > - 1 ) {
if ( flagpos < args . length - 1 ) {
2019-04-13 09:46:23 +02:00
const match = args [ flagpos + 1 ] . match ( "^([0-9])+(m|h|d|w)$" )
2018-04-12 22:33:10 +01:00
// If the arg of the flag matches Pentadactyl's sanitisetimespan format
2019-04-10 07:21:21 +02:00
if ( match !== null && match . length === 3 ) {
2017-12-02 00:20:32 +01:00
// Compute the timespan in milliseconds and get a Date object
2019-04-05 13:16:04 +02:00
let millis = parseInt ( match [ 1 ] , 10 ) * 1000
2017-12-02 00:20:32 +01:00
switch ( match [ 2 ] ) {
2018-04-13 19:28:03 +01:00
case "w" :
millis *= 7
case "d" :
millis *= 24
case "h" :
millis *= 60
case "m" :
millis *= 60
2017-12-02 00:20:32 +01:00
}
2018-04-13 19:28:03 +01:00
since = { since : new Date ( ) . getTime ( ) - millis }
2017-12-02 00:20:32 +01:00
} else {
2018-04-13 19:28:03 +01:00
throw new Error ( ":sanitise error: expected time format: ^([0-9])+(m|h|d|w)$, given format:" + args [ flagpos + 1 ] )
2017-12-02 00:20:32 +01:00
}
} else {
2018-04-12 22:33:10 +01:00
throw new Error ( ":sanitise error: -t given but no following arguments" )
2017-12-02 00:20:32 +01:00
}
}
2019-04-13 09:46:23 +02:00
const dts = {
2018-04-13 19:28:03 +01:00
cache : false ,
cookies : false ,
downloads : false ,
formData : false ,
history : false ,
localStorage : false ,
passwords : false ,
serviceWorkers : false ,
2017-12-02 00:20:32 +01:00
// These are Tridactyl-specific
2018-04-13 19:28:03 +01:00
commandline : false ,
tridactyllocal : false ,
tridactylsync : false ,
2017-12-02 00:20:32 +01:00
/ * W h e n t h i s o n e i s a c t i v a t e d , a l o t o f e r r o r s s e e m t o p o p u p i n
the console . Keeping it disabled is probably a good idea .
"pluginData" : false ,
* /
/ * T h e s e 3 a r e s u p p o r t e d b y C h r o m e a n d O p e r a b u t n o t b y F i r e f o x y e t .
"fileSystems" : false ,
"indexedDB" : false ,
"serverBoundCertificates" : false ,
* /
}
2019-04-10 07:21:21 +02:00
if ( args . find ( x = > x === "all" ) !== undefined ) {
2019-04-13 09:46:23 +02:00
for ( const attr in dts ) dts [ attr ] = true
2017-12-02 00:20:32 +01:00
} else {
// We bother checking if dts[x] is false because
// browser.browsingData.remove() is very strict on the format of the
// object it expects
2019-04-03 18:15:32 +02:00
args . forEach ( x = > {
2018-04-13 19:28:03 +01:00
if ( dts [ x ] === false ) dts [ x ] = true
} )
2017-12-02 00:20:32 +01:00
}
// Tridactyl-specific items
2018-04-13 19:28:03 +01:00
if ( dts . commandline === true ) state . cmdHistory = [ ]
2017-12-02 00:20:32 +01:00
delete dts . commandline
2018-07-05 08:05:30 +02:00
if ( dts . tridactyllocal === true ) await browser . storage . local . clear ( )
2017-12-02 00:20:32 +01:00
delete dts . tridactyllocal
2018-07-05 08:05:30 +02:00
if ( dts . tridactylsync === true ) await browser . storage . sync . clear ( )
2017-12-02 00:20:32 +01:00
delete dts . tridactylsync
// Global items
browser . browsingData . remove ( since , dts )
}
2018-04-12 22:33:10 +01:00
/ * * B i n d a q u i c k m a r k f o r t h e c u r r e n t U R L o r s p a c e - s e p a r a t e d l i s t o f U R L s t o a k e y o n t h e k e y b o a r d .
2017-11-20 23:32:24 +00:00
Afterwards use go [ key ] , gn [ key ] , or gw [ key ] to [ [ open ] ] , [ [ tabopen ] ] , or
[ [ winopen ] ] the URL respectively .
2017-12-08 21:11:40 -08:00
2018-11-20 11:23:34 -08:00
Example :
- ` quickmark m https://mail.google.com/mail/u/0/#inbox `
2017-11-20 23:32:24 +00:00
* /
2017-11-19 13:45:18 +01:00
//#background
2017-12-08 11:56:21 +00:00
export async function quickmark ( key : string , . . . addressarr : string [ ] ) {
2017-11-19 13:45:18 +01:00
// ensure we're binding to a single key
if ( key . length !== 1 ) {
return
}
2017-12-08 11:56:21 +00:00
if ( addressarr . length <= 1 ) {
2019-04-13 09:46:23 +02:00
const address = addressarr . length === 0 ? ( await activeTab ( ) ) . url : addressarr [ 0 ]
2017-12-08 11:56:21 +00:00
// Have to await these or they race!
await bind ( "gn" + key , "tabopen" , address )
await bind ( "go" + key , "open" , address )
await bind ( "gw" + key , "winopen" , address )
} else {
2019-04-13 09:46:23 +02:00
const compstring = addressarr . join ( " | tabopen " )
const compstringwin = addressarr . join ( " | winopen " )
2017-12-08 11:56:21 +00:00
await bind ( "gn" + key , "composite tabopen" , compstring )
await bind ( "go" + key , "composite open" , compstring )
await bind ( "gw" + key , "composite winopen" , compstringwin )
}
2017-11-19 13:45:18 +01:00
}
2018-02-19 01:12:58 +00:00
/ * * P u t s t h e c o n t e n t s o f c o n f i g v a l u e w i t h k e y s ` k e y s ` i n t o t h e c o m m a n d l i n e a n d t h e b a c k g r o u n d p a g e c o n s o l e
It 's a bit rubbish, but we don' t have a good way to provide feedback to the commandline yet .
You can view the log entry in the browser console ( Ctrl - Shift - j ) .
2018-04-12 22:33:10 +01:00
For example , you might try ` get nmaps ` to see all of your current binds .
2018-02-19 01:12:58 +00:00
* /
2017-11-29 16:28:06 +00:00
//#background
2018-02-19 00:37:42 +00:00
export function get ( . . . keys : string [ ] ) {
2018-04-13 19:28:03 +01:00
const target = keys . join ( "." ) . split ( "." )
2019-05-28 12:49:45 +01:00
const value = config . getDynamic ( . . . target )
2018-02-19 01:12:58 +00:00
console . log ( value )
if ( typeof value === "object" ) {
2018-04-13 19:28:03 +01:00
fillcmdline_notrail ( ` # ${ keys . join ( "." ) } = ${ JSON . stringify ( value ) } ` )
2018-02-19 01:12:58 +00:00
} else {
2018-04-13 19:28:03 +01:00
fillcmdline_notrail ( ` # ${ keys . join ( "." ) } = ${ value } ` )
2018-02-19 01:12:58 +00:00
}
2017-11-29 16:28:06 +00:00
}
2018-04-15 18:00:14 +01:00
/ * * O p e n s t h e c u r r e n t c o n f i g u r a t i o n i n F i r e f o x ' s n a t i v e J S O N v i e w e r i n t h e c u r r e n t t a b .
*
* NB : Tridactyl cannot run on this page !
*
* @param key - The specific key you wish to view ( e . g , nmaps ) .
*
* /
//#content
export function viewconfig ( key? : string ) {
// # and white space don't agree with FF's JSON viewer.
// Probably other symbols too.
if ( ! key )
window . location . href =
"data:application/json," +
JSON . stringify ( config . get ( ) )
. replace ( /#/g , "%23" )
. replace ( / /g , "%20" )
// I think JS casts key to the string "undefined" if it isn't given.
else
window . location . href =
"data:application/json," +
2019-05-28 12:49:45 +01:00
JSON . stringify ( config . getDynamic ( key ) )
2018-04-15 18:00:14 +01:00
. replace ( /#/g , "%23" )
. replace ( / /g , "%20" )
// base 64 encoding is a cleverer way of doing this, but it doesn't seem to work for the whole config.
//window.location.href = "data:application/json;base64," + btoa(JSON.stringify(config.get()))
}
2018-09-11 06:34:36 +02:00
/ * *
* Reset a site - specific setting .
*
* usage : ` unseturl [pattern] key `
*
* @param pattern The pattern the setting should be unset on , e . g . ` .*wiki.* ` . Defaults to the current url .
* @param key The key that should be unset .
*
* Example : ` unseturl youtube.com gimode `
*
* Note that this removes a setting from the site - specific config , it doesn ' t "invert" it . This means that if you have a setting set to ` false ` in your global config and the same setting set to ` false ` in a site - specific setting , using ` unseturl ` will result in the setting still being set to ` false ` .
*
* Also note that ` pattern ` should match exactly the one that was used when using ` seturl ` .
* /
//#content
export function unseturl ( pattern : string , key : string ) {
if ( ! key ) {
key = pattern
pattern = window . location . href
}
config . unsetURL ( pattern , key . split ( "." ) )
}
2018-09-04 13:00:32 +01:00
/ * *
* Reset a config setting to default
* /
2017-11-29 18:57:04 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function unset ( . . . keys : string [ ] ) {
const target = keys . join ( "." ) . split ( "." )
if ( target === undefined ) throw "You must define a target!"
2018-02-02 14:12:47 +00:00
config . unset ( . . . target )
2017-11-29 18:57:04 +00:00
}
2017-11-09 00:41:07 +00:00
// }}}
// {{{ HINTMODE
2017-11-24 13:01:44 +00:00
/ * * H i n t a p a g e .
2017-11-28 02:12:07 +00:00
@param option
2019-02-22 12:48:47 +01:00
- - t open in a new foreground tab
2017-11-28 02:12:07 +00:00
- - b open in background
- - y copy ( yank ) link ' s target to clipboard
- - p copy an element ' s text to the clipboard
2018-05-18 18:15:13 +03:00
- - P copy an element ' s title / alt text to the clipboard
2017-11-30 04:11:49 +00:00
- - r read an element ' s text with text - to - speech
2017-11-28 02:12:07 +00:00
- - i view an image
- - I view an image in a new tab
2017-11-28 22:51:53 +00:00
- - k delete an element from the page
2017-12-24 11:35:39 +00:00
- - s save ( download ) the linked resource
- - S save the linked image
- - a save - as the linked resource
- - A save - as the linked image
2017-11-28 02:12:07 +00:00
- - ; focus an element
2017-11-28 22:51:53 +00:00
- - # yank an element ' s anchor URL to clipboard
2017-11-29 13:23:30 +00:00
- - c [ selector ] hint links that match the css selector
- ` bind ;c hint -c [class*="expand"],[class="togg"] ` works particularly well on reddit and HN
2019-05-26 11:44:36 +01:00
- - f [ text ] hint links and inputs that display the given text
2019-05-25 19:23:21 +02:00
- ` bind <c-e> hint -f Edit `
2019-05-26 16:14:58 +02:00
- - fr [ text ] use RegExp to hint the links and inputs
2018-03-12 17:07:42 +00:00
- - w open in new window
2018-08-17 07:59:00 +02:00
- - wp open in new private window
2019-02-18 16:35:59 -06:00
- - z scroll an element to the top of the viewport
2018-10-04 19:37:39 +02:00
- ` -pipe selector key ` e . g , ` -pipe a href ` returns the key . Only makes sense with ` composite ` , e . g , ` composite hint -pipe * textContent | yank ` . If you don ' t select a hint ( i . e . press < Esc > ) , will return an empty string .
2018-08-17 07:59:00 +02:00
- ` -W excmd... ` append hint href to excmd and execute , e . g , ` hint -W exclaim mpv ` to open YouTube videos .
- - q * quick ( or rapid ) hints mode . Stay in hint mode until you press < Esc > , e . g . ` :hint -qb ` to open multiple hints in the background or ` :hint -qW excmd ` to execute excmd once for each hint . This will return an array containing all elements or the result of executed functions ( e . g . ` hint -qpipe a href ` will return an array of links ) .
2019-01-29 14:38:39 -05:00
- - J * disable javascript hints . Don ' t generate hints related to javascript events . This is particularly useful when used with the ` -c ` option when you want to generate only hints for the specified css selectors . Also useful on sites with plenty of useless javascript elements such as google . com
2019-01-29 15:03:58 -05:00
- For example , use ` bind ;jg hint -Jc .rc > .r > a ` on google . com to generate hints only for clickable search results of a given query
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
- - br deprecated , use ` -qb ` instead
2018-07-21 16:44:11 +01:00
2018-08-19 13:13:11 +01:00
Excepting the custom selector mode and background hint mode , each of these hint modes is available by default as ` ;<option character> ` , so e . g . ` ;y ` to yank a link ' s target ; ` ;g<option character> ` starts rapid hint mode for all modes where it makes sense , and some others .
2017-12-24 11:35:39 +00:00
To open a hint in the background , the default bind is ` F ` .
2019-05-28 13:08:48 +01:00
Ex - commands available exclusively in hint mode are listed [ here ] ( / s t a t i c / d o c s / m o d u l e s / _ s r c _ c o n t e n t _ h i n t i n g _ . h t m l )
2017-11-29 20:13:40 +00:00
Related settings :
2018-06-14 15:56:06 -04:00
- "hintchars" : "hjklasdfgyuiopqwertnmzxcvb"
- "hintfiltermode" : "simple" | "vimperator" | "vimperator-reflow"
- "relatedopenpos" : "related" | "next" | "last"
2018-08-01 14:04:47 +01:00
- "hintuppercase" : "true" | "false"
2018-03-17 00:40:23 -04:00
- "hintnames" : "short" | "uniform" | "numeric"
2018-03-17 00:37:39 -04:00
With "short" names , Tridactyl will generate short hints that
are never prefixes of each other . With "uniform" , Tridactyl
will generate hints of uniform length . In either case , the
hints are generated from the set in "hintchars" .
2018-03-17 00:40:23 -04:00
With "numeric" names , hints are always assigned using
sequential integers , and "hintchars" is ignored . This has the
disadvantage that some hints are prefixes of others ( and you
need to hit space or enter to select such a hint ) . But it has
the advantage that the hints tend to be more predictable
( e . g . , a news site will have the same hints for its
boilerplate each time you visit it , even if the number of
links in the main body changes ) .
2017-11-28 02:12:07 +00:00
* /
2018-07-21 13:02:50 +01:00
//#content
2019-04-03 07:45:09 +02:00
export async function hint ( option? : string , selectors? : string , . . . rest : string [ ] ) : Promise < any > {
2018-08-16 17:22:30 +02:00
if ( ! option ) option = ""
2018-07-21 16:44:11 +01:00
2019-04-10 07:21:21 +02:00
if ( option === "-br" ) option = "-qb"
2018-07-21 16:44:11 +01:00
2019-02-22 12:48:47 +01:00
// extract flags
2019-02-26 06:24:18 +01:00
// Note: we need to process 'pipe' separately because it could be interpreted as -p -i -e otherwise
const pipeIndex = option . indexOf ( "pipe" )
if ( pipeIndex >= 0 ) {
option = option . slice ( 0 , pipeIndex ) + option . slice ( pipeIndex + 1 )
}
2019-02-22 12:48:47 +01:00
const options = new Set ( option . length ? option . slice ( 1 ) . split ( "" ) : [ ] )
const rapid = options . delete ( "q" )
2019-02-27 06:17:15 +01:00
const jshints = ! options . delete ( "J" )
2019-02-22 12:48:47 +01:00
const withSelectors = options . delete ( "c" )
2019-01-29 14:38:39 -05:00
2019-02-22 12:48:47 +01:00
option = "-" + Array . from ( options ) . join ( "" )
2019-02-26 06:24:18 +01:00
if ( pipeIndex >= 0 ) {
option = "-pipe"
}
2018-07-21 16:44:11 +01:00
2019-04-03 07:45:09 +02:00
let selectHints
2019-04-13 09:46:23 +02:00
const hintTabOpen = async ( href , active = ! rapid ) = > {
const containerId = await activeTabContainerId ( )
2018-08-04 09:38:19 +02:00
if ( containerId ) {
2018-12-25 10:00:01 +01:00
return openInNewTab ( href , {
2018-08-04 09:38:19 +02:00
active ,
related : true ,
cookieStoreId : containerId ,
} )
} else {
2018-12-25 10:00:01 +01:00
return openInNewTab ( href , {
2018-08-04 09:38:19 +02:00
active ,
related : true ,
} )
2018-08-03 22:49:42 +02:00
}
2018-07-21 16:44:11 +01:00
}
2018-08-04 10:55:16 +02:00
switch ( option ) {
2019-05-26 16:14:58 +02:00
case "-f" : // Filter links by text
case "-fr" : // Filter links by regex
let match : string | RegExp
match = [ selectors , . . . rest ] . join ( " " )
if ( option == "-fr" ) {
match = new RegExp ( match ) ;
}
2019-05-25 19:23:21 +02:00
selectHints = hinting . pipe_elements (
2019-05-26 16:14:58 +02:00
hinting . hintByText ( match ) ,
2019-05-25 19:23:21 +02:00
elem = > {
DOM . simulateClick ( elem as HTMLElement )
return elem
} ,
rapid ,
)
break
2019-02-22 12:48:47 +01:00
case "-b" : // Open in background
case "-t" : // Open in foreground
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe (
2019-02-22 12:48:47 +01:00
withSelectors ? [ selectors , . . . rest ] . join ( " " ) : DOM . HINTTAGS_selectors ,
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
async link = > {
link . focus ( )
if ( link . href ) {
2019-02-22 12:48:47 +01:00
hintTabOpen ( link . href , option === "-t" ) . catch ( ( ) = > DOM . simulateClick ( link ) )
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
} else {
DOM . simulateClick ( link )
}
return link
} ,
rapid ,
2019-01-24 15:50:02 -05:00
jshints ,
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
)
2018-08-04 10:55:16 +02:00
break
2018-07-21 16:44:11 +01:00
2018-08-04 10:55:16 +02:00
case "-y" :
// Yank link
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe (
DOM . HINTTAGS_selectors ,
elem = > {
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
2019-04-03 19:09:49 +02:00
run_exstr ( "yank " + elem . href )
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
return elem
} ,
rapid ,
2019-01-24 15:50:02 -05:00
jshints ,
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
)
2018-08-04 10:55:16 +02:00
break
2018-07-21 16:44:11 +01:00
2018-08-04 10:55:16 +02:00
case "-p" :
// Yank text content
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
DOM . elementsWithText ( ) ,
elem = > {
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
2019-04-03 19:09:49 +02:00
run_exstr ( "yank " + elem . textContent )
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
return elem
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
2018-07-21 16:44:11 +01:00
2018-08-04 10:55:16 +02:00
case "-P" :
// Yank link alt text
// ???: Neither anchors nor links posses an "alt" attribute. I'm assuming that the person who wrote this code also wanted to select the alt text of images
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
DOM . getElemsBySelector ( "[title], [alt]" , [ DOM . isVisible ] ) ,
link = > {
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
run_exstr ( "yank " + ( link . title ? link.title : link.alt ) )
return link
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
2018-07-21 16:44:11 +01:00
2018-08-04 10:55:16 +02:00
case "-#" :
// Yank anchor
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
DOM . anchors ( ) ,
link = > {
2019-04-13 09:46:23 +02:00
const anchorUrl = new URL ( window . location . href )
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
// ???: What purpose does selecting elements with a name attribute have? Selecting values that only have meaning in forms doesn't seem very useful.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
anchorUrl . hash = link . id || link . name
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
run_exstr ( "yank " + anchorUrl . href )
return link
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
2018-07-21 16:44:11 +01:00
2018-08-04 10:55:16 +02:00
case "-W" :
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe (
DOM . HINTTAGS_selectors ,
elem = > {
// /!\ RACY RACY RACY!
run_exstr ( selectors + " " + rest . join ( " " ) + " " + elem )
return elem
} ,
rapid ,
2019-01-24 15:50:02 -05:00
jshints ,
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
)
2018-08-04 10:55:16 +02:00
break
case "-pipe" :
2018-09-14 10:30:26 +01:00
selectHints = hinting . pipe (
selectors ,
elem = > elem [ rest . join ( " " ) ] ,
rapid ,
2019-01-24 15:50:02 -05:00
jshints ,
2018-09-14 10:30:26 +01:00
)
2018-08-04 10:55:16 +02:00
break
case "-i" :
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
hinting . hintableImages ( ) ,
elem = > {
open ( new URL ( elem . getAttribute ( "src" ) , window . location . href ) . href )
return elem
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
case "-I" :
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
hinting . hintableImages ( ) ,
async elem = > {
await hintTabOpen ( new URL ( elem . getAttribute ( "src" ) , window . location . href ) . href )
return elem
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
case "-k" :
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
hinting . killables ( ) ,
elem = > {
elem . remove ( )
return elem
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
case "-s" :
case "-a" :
2018-08-04 11:59:36 +02:00
case "-S" :
2018-08-04 10:55:16 +02:00
case "-A" :
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
let elems = [ ]
2018-08-04 11:59:36 +02:00
// s: don't ask the user where to save the file
// a: ask the user where to save the file
let saveAs = true
2019-04-10 07:21:21 +02:00
if ( option [ 1 ] . toLowerCase ( ) === "s" ) saveAs = false
2018-08-04 11:59:36 +02:00
// Lowercase: anchors
// Uppercase: images
let attr = "href"
2019-04-10 07:21:21 +02:00
if ( option [ 1 ] . toLowerCase ( ) === option [ 1 ] ) {
2018-08-04 11:59:36 +02:00
attr = "href"
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
elems = hinting . saveableElements ( )
2018-08-04 11:59:36 +02:00
} else {
attr = "src"
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
elems = hinting . hintableImages ( )
2018-08-04 12:08:29 +02:00
}
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
elems ,
elem = > {
Messaging . message ( "download_background" , "downloadUrl" , [ new URL ( elem [ attr ] , window . location . href ) . href , saveAs ] )
return elem
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
case "-;" :
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
hinting . hintables ( selectors ) ,
elem = > {
elem . focus ( )
return elem
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
case "-r" :
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
DOM . elementsWithText ( ) ,
elem = > {
TTS . readText ( elem . textContent )
return elem
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
case "-w" :
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
hinting . hintables ( ) ,
elem = > {
elem . focus ( )
if ( elem . href ) openInNewWindow ( { url : new URL ( elem . href , window . location . href ) . href } )
else DOM . simulateClick ( elem )
return elem
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
2019-02-22 13:25:17 +01:00
case "-wp" :
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe_elements (
hinting . hintables ( ) ,
elem = > {
elem . focus ( )
if ( elem . href ) return openInNewWindow ( { url : elem.href , incognito : true } )
} ,
rapid ,
)
2018-08-04 10:55:16 +02:00
break
2019-02-18 16:35:59 -06:00
case "-z" :
selectHints = hinting . pipe_elements (
DOM . elementsWithText ( ) ,
elem = > {
elem . scrollIntoView ( true )
return elem
} ,
rapid ,
)
break
2018-08-04 10:55:16 +02:00
default :
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
selectHints = hinting . pipe (
2019-02-22 12:48:47 +01:00
withSelectors ? [ selectors , . . . rest ] . join ( " " ) : DOM . HINTTAGS_selectors ,
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
elem = > {
DOM . simulateClick ( elem as HTMLElement )
return elem
} ,
rapid ,
2019-01-24 15:50:02 -05:00
jshints ,
Make rapid hint mode less janky, enable hint mode for every action
Making hint mode less janky is achieved by not resetting hint mode when
a hint has been selected. New command line options, `-q*` where `*`
stands for any alread-existing flag, are added.
The global hint flow is as following:
- User goes into hint mode
- hint mode promise is created
- When a hint is selected, the corresponding action is performed (e.g.
opening a link, killing an element...)
- The result of the action is saved in the hint
- If we're not in rapid hint mode, modeState is reset, and the hint mode
promise is resolved with the result of the action as parameter
- If we're in rapid hint mode, modeState is not reset. Instead its
filter is reset and all its hints are displayed again.
- Rapid hint mode is only left on escape, and this means that the
promise will be rejected.
Rejecting the promise means that we can't pipe elements selected in hint
rapid hint mode to other commands. This makes sense because pursuing
execution of a pipe several times in parallel, on top of being possibly
hard to implement, would probably be confusing.
Instead of using a pipe in order to execute arbitrary commands in hint
mode, users can use `-qW excmd`. Contrary to the pipe, this makes clear that
the excmd will be executed multiple times, once per focused hint.
This way of handling rapid hint mode has the advantage of simplifying
the `hint` function. Now, instead of having to return a tuple with the
number of available hints, the function can just return the selected
element or whatever the command line arguments specifies it should
return.
2018-08-04 20:18:43 +02:00
)
2018-08-03 22:49:42 +02:00
}
2018-08-17 07:59:00 +02:00
return selectHints
2018-07-21 16:44:11 +01:00
}
// how 2 crash pc
////#content
//export async function rapid(...commands: string[]){
// while(true){
// await run_exstr(...commands)
// }
//}
2019-05-30 19:45:46 +00:00
/ * * P e r f o r m r o t 1 3 .
*
* Transforms all text nodes in the current tab via rot13 . Only characters in
* the ASCII range are considered .
* /
//#content
export function rot13() {
const body = document . createTreeWalker (
document . body ,
NodeFilter . SHOW_TEXT ,
{ acceptNode :
( node ) = > NodeFilter . FILTER_ACCEPT
}
)
while ( body . nextNode ( ) ) {
const t = body . currentNode . textContent
body . currentNode . textContent = rot13_helper ( t )
}
}
2018-07-21 16:44:11 +01:00
/ * *
* Hacky ex string parser .
*
* Use it for fire - and - forget running of background commands in content .
* /
2019-02-05 08:27:59 +01:00
//#content
2018-07-21 17:03:44 +01:00
export function run_exstr ( . . . commands : string [ ] ) {
2019-02-26 08:41:19 +01:00
return Messaging . message ( "controller_background" , "acceptExCmd" , commands )
2017-11-09 00:41:07 +00:00
}
// }}}
2017-11-19 13:45:18 +01:00
// {{{ GOBBLE mode
2017-11-20 23:32:24 +00:00
/ * * I n i t i a l i z e g o b b l e m o d e .
It will read ` nChars ` input keys , append them to ` endCmd ` and execute that
string .
* /
2018-09-05 03:15:12 -07:00
//#content
2017-11-19 13:45:18 +01:00
export async function gobble ( nChars : number , endCmd : string ) {
gobbleMode . init ( nChars , endCmd )
}
// }}}
2017-11-22 11:54:17 +00:00
2017-11-30 04:11:49 +00:00
// {{{TEXT TO SPEECH
/ * *
* Read text content of elements matching the given selector
*
* @param selector the selector to match elements
* /
//#content_helper
function tssReadFromCss ( selector : string ) : void {
2019-04-13 09:46:23 +02:00
const elems = DOM . getElemsBySelector ( selector , [ ] )
2017-11-30 04:11:49 +00:00
2018-04-13 19:28:03 +01:00
elems . forEach ( e = > {
2017-11-30 04:11:49 +00:00
TTS . readText ( e . textContent )
} )
}
/ * *
* Read the given text using the browser ' s text to speech functionality and
* the settings currently set
*
* @param mode the command mode
* - t read the following args as text
* - c read the content of elements matching the selector
* /
//#content
export async function ttsread ( mode : "-t" | "-c" , . . . args : string [ ] ) {
if ( mode === "-t" ) {
// really should quote args, but for now, join
TTS . readText ( args . join ( " " ) )
2018-04-13 19:28:03 +01:00
} else if ( mode === "-c" ) {
2017-11-30 04:11:49 +00:00
if ( args . length > 0 ) {
tssReadFromCss ( args [ 0 ] )
} else {
2017-12-29 23:55:39 +00:00
throw "Error: no CSS selector supplied"
2017-11-30 04:11:49 +00:00
}
} else {
2017-12-29 23:55:39 +00:00
throw "Unknown mode for ttsread command: " + mode
2017-11-30 04:11:49 +00:00
}
}
/ * *
* Show a list of the voices available to the TTS system . These can be
* set in the config using ` ttsvoice `
* /
//#background
export async function ttsvoices() {
2019-04-13 09:46:23 +02:00
const voices = TTS . listVoices ( )
2019-04-03 18:27:50 +02:00
voices . sort ( )
2017-11-30 04:11:49 +00:00
// need a better way to show this to the user
2019-04-03 18:27:50 +02:00
fillcmdline_notrail ( "#" , voices . join ( ", " ) )
2017-11-30 04:11:49 +00:00
}
/ * *
* Cancel current reading and clear pending queue
*
* Arguments :
* - stop : cancel current and pending utterances
* /
//#content
export async function ttscontrol ( action : string ) {
// only pause seems to be working, so only provide access to that
// to avoid exposing users to things that won't work
2019-04-10 07:21:21 +02:00
if ( action !== "stop" ) {
2017-12-29 23:55:39 +00:00
throw new Error ( "Unknown text-to-speech action: " + action )
2017-11-30 04:11:49 +00:00
}
2019-04-03 13:49:38 +02:00
TTS . doAction ( action as TTS . Action )
2017-11-30 04:11:49 +00:00
}
//}}}
2018-09-03 03:13:42 -07:00
// {{{ PERFORMANCE LOGGING
/ * *
* Build a set of FilterConfigs from a list of human - input filter
* specs .
*
* @hidden
* /
//#background_helper
export function buildFilterConfigs ( filters : string [ ] ) : Perf . StatsFilterConfig [ ] {
return filters . map (
( filter : string ) : Perf . StatsFilterConfig = > {
if ( filter . endsWith ( "/" ) ) {
2018-09-05 00:13:25 -07:00
return { kind : "ownerName" , ownerName : filter.slice ( 0 , - 1 ) }
2018-09-03 03:13:42 -07:00
} else if ( filter === ":start" ) {
2018-09-05 00:13:25 -07:00
return { kind : "eventType" , eventType : "start" }
2018-09-03 03:13:42 -07:00
} else if ( filter === ":end" ) {
2018-09-05 00:13:25 -07:00
return { kind : "eventType" , eventType : "end" }
2018-09-03 03:13:42 -07:00
} else if ( filter === ":measure" ) {
2018-09-05 00:13:25 -07:00
return { kind : "eventType" , eventType : "measure" }
2018-09-03 03:13:42 -07:00
} else {
2018-09-05 00:13:25 -07:00
return { kind : "functionName" , functionName : name }
2018-09-03 03:13:42 -07:00
}
} ,
)
}
/ * *
* Dump the raw json for our performance counters . Filters with
* trailing slashes are class names , : start | : end | : measure specify
* what type of sample to pass through , and all others are function
* names . All filters must match for a sample to be dumped .
*
* Tridactyl does not collect performance information by default . To
* get this data you ' ll have to set the configuration option
* ` perfcounters ` to ` "true" ` . You may also want to examine the value
* of ` perfsamples ` .
* /
//#background
2018-09-05 01:18:09 -07:00
export async function perfdump ( . . . filters : string [ ] ) {
2019-04-13 09:46:23 +02:00
const filterconfigs = buildFilterConfigs ( filters )
2018-09-03 03:13:42 -07:00
const entries = window . tri . statsLogger . getEntries ( . . . filterconfigs )
console . log ( filterconfigs )
open ( "data:application/json;charset=UTF-8," + JSON . stringify ( entries ) )
}
/ * *
* Pretty - print a histogram of execution durations for you . Arguments
* are as above , with the addition that this automatically filters to
* counter samples of type : measure .
*
* Note that this will display its output by opening a data : url with
* text in the place of your current tab .
* /
//#background
export async function perfhistogram ( . . . filters : string [ ] ) {
2019-04-13 09:46:23 +02:00
const filterconfigs = buildFilterConfigs ( filters )
2018-09-05 00:13:25 -07:00
filterconfigs . push ( { kind : "eventType" , eventType : "measure" } )
2018-09-03 03:13:42 -07:00
const entries = window . tri . statsLogger . getEntries ( . . . filterconfigs )
2019-04-10 07:21:21 +02:00
if ( entries . length === 0 ) {
2018-10-04 13:59:19 +01:00
fillcmdline_tmp ( 3000 , "perfhistogram: No samples found." )
2018-09-26 00:59:11 -07:00
return
}
2018-09-03 03:13:42 -07:00
const histogram = Perf . renderStatsHistogram ( entries )
console . log ( histogram )
open ( "data:text/plain;charset=UTF-8;base64," + btoa ( histogram ) )
}
// }}}
2017-11-22 11:54:17 +00:00
// unsupported on android
2018-04-16 21:12:25 +01:00
/ * *
* Add or remove a bookmark .
2018-04-13 19:28:03 +01:00
*
* Optionally , you may give the bookmark a title . If no URL is given , a bookmark is added for the current page .
*
2018-04-16 21:12:25 +01:00
* If a bookmark already exists for the URL , it is removed , even if a title is given .
*
* Does not support creation of folders : you ' ll need to use the Firefox menus for that .
*
* @param titlearr Title for the bookmark ( can include spaces but not forward slashes , as these are interpreted as folders ) . If you want to put the bookmark in a folder , you can :
* - Specify it exactly : ` /Bookmarks Menu/Mozilla Firefox/My New Bookmark Title `
* - Specify it by a subset : ` Firefox/My New Bookmark Title `
* - and leave out the title if you want : ` Firefox/ `
2018-04-13 19:28:03 +01:00
* /
2017-11-22 11:54:17 +00:00
//#background
2018-04-13 19:28:03 +01:00
export async function bmark ( url? : string , . . . titlearr : string [ ] ) {
2019-05-27 14:47:47 +01:00
const auto_url = ( url == undefined )
2019-05-30 16:37:32 +01:00
url = url === undefined ? ( await activeTab ( ) ) . url : ( _ = > {
try {
return ( new URL ( url ) . href )
} catch ( e ) {
return ( new URL ( "http://" + url ) . href )
}
} ) ( )
2017-12-08 21:11:40 -08:00
let title = titlearr . join ( " " )
2018-04-16 21:12:25 +01:00
// if titlearr is given and we have duplicates, we probably want to give an error here.
const dupbmarks = await browser . bookmarks . search ( { url } )
2019-04-03 18:15:32 +02:00
dupbmarks . forEach ( bookmark = > browser . bookmarks . remove ( bookmark . id ) )
2019-04-10 07:21:21 +02:00
if ( dupbmarks . length !== 0 ) return
2018-04-16 21:12:25 +01:00
const path = title . substring ( 0 , title . lastIndexOf ( "/" ) + 1 )
2018-07-28 17:08:55 +10:00
// if title is blank, get it from the current page.
// technically could race condition if someone switched tabs REALLY quick after
// bookmarking, but too unlikely to bother with for now
2019-05-27 14:47:47 +01:00
if ( title == "" && auto_url ) {
2018-07-28 17:08:55 +10:00
//retrieve title from current tab
title = ( await activeTab ( ) ) . title
}
if ( path != "" ) {
2018-04-16 21:12:25 +01:00
const tree = ( await browser . bookmarks . getTree ( ) ) [ 0 ] // Why would getTree return a tree? Obviously it returns an array of unit length.
// I hate recursion.
const treeClimber = ( tree , treestr ) = > {
if ( tree . type !== "folder" ) return { }
treestr += tree . title + "/"
if ( ! ( "children" in tree ) || tree . children . length === 0 ) return { path : treestr , id : tree.id }
return [ { path : treestr , id : tree.id } , tree . children . map ( child = > treeClimber ( child , treestr ) ) ]
}
const validpaths = flatten ( treeClimber ( tree , "" ) ) . filter ( x = > "path" in x )
title = title . substring ( title . lastIndexOf ( "/" ) + 1 )
2019-04-10 07:21:21 +02:00
let pathobj = validpaths . find ( p = > p . path === path )
2018-04-16 21:12:25 +01:00
// If strict look doesn't find it, be a bit gentler
if ( pathobj === undefined ) pathobj = validpaths . find ( p = > p . path . includes ( path ) )
2018-07-28 17:08:55 +10:00
//technically an initial title string like `Firefox/` can give us a blank title
//once we remove the path, so let's fix that
2019-05-27 14:47:47 +01:00
if ( title == "" && auto_url ) {
2018-07-28 17:08:55 +10:00
//retrieve title from current tab
const currTitle = ( await activeTab ( ) ) . title
title = currTitle
}
2018-04-16 21:12:25 +01:00
if ( pathobj !== undefined ) {
browser . bookmarks . create ( { url , title , parentId : pathobj.id } )
return
} // otherwise, give the user an error, probably with [v.path for v in validpaths]
2018-04-13 19:28:03 +01:00
}
2018-04-16 21:12:25 +01:00
browser . bookmarks . create ( { url , title } )
2017-11-22 11:54:17 +00:00
}
2017-11-05 14:10:11 +00:00
2018-05-05 14:14:02 +01:00
//#background
export async function echo ( . . . str : string [ ] ) {
return str . join ( " " )
}
/ * *
* Lets you execute JavaScript in the page context . If you want to get the result back , use ` composite js ... | fillcmdline `
*
2018-05-16 17:31:33 +01:00
* Some of Tridactyl 's functions are accessible here via the `tri` object. Just do `console.log(tri)` in the web console on the new tab page to see what' s available .
2018-05-05 14:14:02 +01:00
*
2018-05-27 06:29:40 +02:00
* If you want to pipe an argument to ` js ` , you need to use the "-p" flag and then use the JS_ARG global variable , e . g :
*
* ` composite get_current_url | js -p alert(JS_ARG) `
2018-05-05 14:14:02 +01:00
* /
2019-04-15 19:47:41 +02:00
/* tslint:disable:no-identical-functions */
2018-05-05 14:14:02 +01:00
//#content
export async function js ( . . . str : string [ ] ) {
2018-05-27 06:29:40 +02:00
if ( str [ 0 ] . startsWith ( "-p" ) ) {
2019-04-16 08:30:31 +02:00
/* tslint:disable:no-unused-declaration */
2019-04-03 07:45:09 +02:00
/* tslint:disable:no-dead-store */
2019-04-13 09:46:23 +02:00
const JS_ARG = str [ str . length - 1 ]
2018-05-27 06:29:40 +02:00
return eval ( str . slice ( 1 , - 1 ) . join ( " " ) )
} else {
return eval ( str . join ( " " ) )
}
2018-05-05 14:14:02 +01:00
}
2018-05-17 16:45:55 +01:00
/ * *
* Lets you execute JavaScript in the background context . All the help from [ [ js ] ] applies . Gives you a different ` tri ` object .
* /
2019-04-15 19:47:41 +02:00
/* tslint:disable:no-identical-functions */
2018-05-17 16:45:55 +01:00
//#background
export async function jsb ( . . . str : string [ ] ) {
2018-05-27 06:29:40 +02:00
if ( str [ 0 ] . startsWith ( "-p" ) ) {
2019-04-16 08:30:31 +02:00
/* tslint:disable:no-unused-declaration */
2019-04-03 07:45:09 +02:00
/* tslint:disable:no-dead-store */
2019-04-13 09:46:23 +02:00
const JS_ARG = str [ str . length - 1 ]
2018-05-27 06:29:40 +02:00
return eval ( str . slice ( 1 , - 1 ) . join ( " " ) )
} else {
return eval ( str . join ( " " ) )
}
2018-05-17 16:45:55 +01:00
}
2019-04-08 09:07:02 +02:00
/ * *
* Opens a new tab the url of which is "https://github.com/tridactyl/tridactyl/issues/new" and automatically fill add tridactyl , firefox and os version to the issue .
* /
//#content
export async function issue() {
const newIssueUrl = "https://github.com/tridactyl/tridactyl/issues/new"
if ( window . location . href !== newIssueUrl ) {
2019-04-21 01:38:57 -07:00
return tabopen ( newIssueUrl )
2019-04-08 09:07:02 +02:00
}
const textarea = document . getElementById ( "issue_body" )
if ( ! ( textarea instanceof HTMLTextAreaElement ) ) {
logger . warning ( "issue(): Couldn't find textarea element in github issue page." )
return
}
2019-05-26 22:03:34 -07:00
let template = await ( fetch ( browser . runtime . getURL ( "issue_template.md" ) )
2019-04-08 09:07:02 +02:00
. then ( resp = > resp . body . getReader ( ) )
. then ( reader = > reader . read ( ) )
. then ( r = > ( new TextDecoder ( "utf-8" ) ) . decode ( r . value ) ) )
if ( textarea . value !== template ) {
logger . debug ( "issue(): Textarea value differs from template, exiting early." )
return
}
const platform = await browserBg . runtime . getPlatformInfo ( ) ;
// Remove the bit asking the user
template = template . replace ( "* Operating system:\n" , "" )
// Add this piece of information to the top of the template
template = ` Operating system: ${ platform . os } \ n ` + template
const info = await browserBg . runtime . getBrowserInfo ( )
template = template . replace ( "* Firefox version (Top right menu > Help > About Firefox):\n\n" , "" )
template = ` Firefox version: ${ info . vendor } ${ info . name } ${ info . version } \ n ` + template
template = template . replace ( "* Tridactyl version (`:version`):\n\n" , "" )
template = ` Tridactyl version: ${ TRI_VERSION } \ n ` + template
textarea . value = template
}
2018-12-07 15:40:20 +00:00
/ * *
* Checks if there are any stable updates available for Tridactyl .
*
* Related settings :
*
2019-04-21 03:03:16 -07:00
* - ` update.nag = true | false ` - checks for updates on Tridactyl start .
* - ` update.nagwait = 7 ` - waits 7 days before nagging you to update .
* - ` update.checkintervalsecs = 86400 ` - waits 24 hours between checking for an update .
2018-12-07 15:40:20 +00:00
*
* /
2018-12-07 00:31:16 +00:00
//#background
2019-04-20 17:22:56 -07:00
export async function updatecheck ( source : "manual" | "auto_polite" | "auto_impolite" = "manual" ) {
const forceCheck = source == "manual"
const highestKnownVersion = await Updates . getLatestVersion ( forceCheck )
if ( ! highestKnownVersion ) {
return false
}
if ( ! Updates . shouldNagForVersion ( highestKnownVersion ) ) {
if ( source == "manual" ) {
fillcmdline_tmp ( 30000 , "You're up to date! Tridactyl version " + highestKnownVersion . version + "." )
2018-12-07 00:31:16 +00:00
}
2019-04-20 17:22:56 -07:00
return false
}
const notify = ( ) = > {
fillcmdline_tmp ( 30000 , "Tridactyl " + highestKnownVersion . version + " is available (you're on " + Updates . getInstalledVersion ( ) + "). Visit about:addons, right click Tridactyl, click 'Find Updates'. Restart Firefox once it has downloaded." )
}
// A bit verbose, but I figured it was important to have the logic
// right when it comes to automatically nagging users about the
// version they're on.
if ( source == "manual" ) {
notify ( )
} else if ( source == "auto_impolite" ) {
logger . debug ( "Impolitely nagging user to update. Installed, latest: " ,
Updates . getInstalledVersion ( ) , highestKnownVersion )
notify ( )
Updates . updateLatestNaggedVersion ( highestKnownVersion )
} else if ( source == "auto_polite" && ! Updates . naggedForVersion ( highestKnownVersion ) ) {
logger . debug ( "Politely nagging user to update. Installed, latest: " ,
Updates . getInstalledVersion ( ) , highestKnownVersion )
notify ( )
Updates . updateLatestNaggedVersion ( highestKnownVersion )
}
2018-12-07 00:31:16 +00:00
}
2019-05-30 18:21:05 +01:00
/ * *
* Feed some keys to Tridactyl ' s parser . E . g . ` keyfeed jkjkjkjkjkjkjkjkjkjkjkjkjkjkjkjkjkjkjkjkjkjjkj ` .
*
* NB :
*
* - Does _not_ function like Vim ' s noremap - ` bind j keyfeed j ` will cause an infinite loop .
* - Doesn 't work in exmode - i.e. `keyfeed t<CR>` won' t work .
*
* /
//#content
export async function keyfeed ( mapstr : string ) {
const keyseq = mapstrToKeyseq ( mapstr )
for ( const k of keyseq ) {
KEY_MUNCHER . next ( k )
await sleep ( 10 )
}
}
2018-04-12 22:33:10 +01:00
/ * * O p e n a w e l c o m e p a g e o n f i r s t i n s t a l l .
*
* @hidden
* /
//#background_helper
2018-04-13 19:28:03 +01:00
browser . runtime . onInstalled . addListener ( details = > {
2019-04-10 07:21:21 +02:00
if ( details . reason === "install" ) tutor ( "newtab" )
else if ( ( details as any ) . temporary !== true && details . reason === "update" ) updatenative ( false )
2018-04-13 19:28:03 +01:00
// could add elif "update" and show a changelog. Hide it behind a setting to make it less annoying?
2018-04-12 22:33:10 +01:00
} )
2017-10-21 12:54:15 +02:00
// vim: tabstop=4 shiftwidth=4 expandtab