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
Use ` :help <excmd> ` or scroll down to show [ [ help ] ] for a particular excmd .
2018-07-21 18:29:19 +01:00
The default keybinds can be found [ here ] ( / s t a t i c / d o c s / m o d u l e s / _ s r c _ c o n f i g _ . h t m l # d e f a u l t s ) o r a l l 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 ` .
2018-02-19 01:12:58 +00:00
You can also view them with [ [ bind ] ] . Try ` bind j ` .
2017-11-22 12:13:25 +00:00
2018-03-08 22:34:22 +00:00
For more information , and FAQs , check out our [ readme ] [ 4 ] on github .
2017-12-02 23:06:12 +00:00
Tridactyl is in a pretty early stage of development . Please report any
2018-01-23 21:07:36 -08:00
issues and make requests for missing features on the GitHub [ project page ] [ 1 ] .
2017-12-04 15:41:05 +00:00
You can also get in touch using Matrix , Gitter , or IRC chat clients :
2017-11-19 06:05:15 +00:00
2017-12-02 23:06:12 +00:00
[ ! [ Matrix Chat ] [ matrix - badge ] ] [ matrix - link ]
[ ! [ Gitter Chat ] [ gitter - badge ] ] [ gitter - link ]
2017-12-04 15:41:05 +00:00
[ ! [ Freenode Chat ] [ freenode - badge ] ] [ freenode - link ]
All three channels are mirrored together , so it doesn ' t matter which one you use .
2017-11-19 06:05:15 +00:00
2018-04-12 22:33:10 +01:00
# # How to use this help page
We 've hackily re-purposed TypeDoc which is designed for internal documentation. 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 :
` 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-05-16 17:12:29 +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-07-21 18:29:19 +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 . This is especially recommended for browsing the [ config ] ( / s t a t i c / d o c s / m o d u l e s / _ s r c _ c o n f i g _ . h t m l # d e f a u l t s ) w h i c h i s n i g h - o n u n r e a d a b l e o n t h e s e p a g e s .
2018-05-04 09:22:57 -05:00
2018-04-12 22:33:10 +01:00
2017-12-02 23:06:12 +00:00
# # Highlighted features :
- Press ` b ` to bring up a list of open tabs in the current window ; you can
2018-01-06 20:28:37 +03:00
type the tab ID or part of the title or URL to choose a tab
2018-05-04 09:22:57 -05:00
- Press ` Shift ` + ` Insert ` to enter "ignore mode" . Press ` Shift ` + ` Insert `
2018-05-25 09:24:02 +01:00
again to return to "normal mode" . ` <C-A-backtick> ` also works both ways .
2018-05-23 11:22:50 -06:00
- Press ` f ` to start "hint mode" , ` F ` to open in background ( note : hint
characters should be typed in lowercase )
2017-11-19 06:05:15 +00:00
- Press ` o ` to ` :open ` a different page
2017-12-02 23:06:12 +00:00
- Press ` s ` if you want to search for something that looks like a domain
name or URL
2017-11-19 06:05:15 +00:00
- [ [ bind ] ] new commands with e . g . ` :bind J tabnext `
- Type ` :help ` to see a list of available excmds
- Use ` yy ` to copy the current page URL to your clipboard
2018-05-18 12:31:44 +01:00
- ` [[ ` and ` ]] ` to navigate through the pages of comics , paginated
2017-12-02 23:06:12 +00:00
articles , etc
- Pressing ` ZZ ` will close all tabs and windows , but it will only "save"
them if your about :preferences are set to " show your tabs and windows
from last time "
2018-07-11 11:56:31 +01:00
- Press Ctrl - i in a text box to edit in an external editor ( e . g . vim ) . Requires native messenger .
2018-05-20 18:17:17 +01:00
- Change theme with ` colours default|dark|greenmat|shydactyl `
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 .
2018-05-17 21:50:01 +02: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 .
There is an [ example file ] ( 2 ) available in our repository .
2017-11-19 06:05:15 +00:00
2017-12-02 23:06:12 +00:00
If you want a more fully - featured vimperator - alike , your best option is
2018-03-08 22:34:22 +00:00
[ Firefox ESR ] [ 3 ] and Vimperator : )
2017-11-19 06:05:15 +00:00
[ 1 ] : https : //github.com/cmcaine/tridactyl/issues
[ 2 ] : https : //github.com/cmcaine/tridactyl/blob/master/src/static/userChrome-minimal.css
2018-05-24 15:44:16 +01:00
[ 3 ] : https : //www.mozilla.org/en-GB/firefox/organizations/all/#legacy
2018-03-08 22:34:22 +00:00
[ 4 ] : https : //github.com/cmcaine/tridactyl#readme
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
2017-10-23 09:42:50 +01:00
import * as Messaging from "./messaging"
2018-07-21 16:44:11 +01:00
import { browserBg , activeTabId , activeTabContainerId , openInNewTab } from "./lib/webext"
2018-06-13 08:19:38 +00:00
import * as Container from "./lib/containers"
2017-12-05 22:07:23 +00:00
import state from "./state"
2018-03-12 22:36:05 +00:00
import * as UrlUtil from "./url_util"
2018-04-13 19:28:03 +01:00
import * as config from "./config"
import * as aliases from "./aliases"
2018-03-12 22:36:05 +00:00
import * as Logging from "./logging"
2018-04-12 22:33:10 +01:00
/** @hidden */
2018-04-13 19:28:03 +01:00
const logger = new Logging . Logger ( "excmds" )
import Mark from "mark.js"
2018-04-21 23:43:12 +01:00
import * as CSS from "css"
2017-10-23 09:42:50 +01:00
2017-10-12 04:02:01 +01:00
//#content_helper
2018-03-12 22:36:05 +00:00
// {
import "./number.clamp"
2018-04-22 17:15:40 +01:00
import * as SELF from "./.excmds_content.generated"
2018-04-13 19:28:03 +01:00
Messaging . addListener ( "excmd_content" , Messaging . attributeCaller ( SELF ) )
import * as DOM from "./dom"
2018-03-08 22:21:48 +01:00
import { executeWithoutCommandLine } from "./commandline_content"
2018-05-26 17:31:27 +02:00
import * as scrolling from "./scrolling"
2018-03-12 22:36:05 +00:00
// }
2017-10-28 05:11:10 +01:00
//#background_helper
2018-03-12 22:36:05 +00:00
// {
/** Message excmds_content.ts in the active tab of the currentWindow */
2018-07-01 18:17:35 +02:00
import { messageTab , messageActiveTab } from "./messaging"
2018-04-16 21:12:25 +01:00
import { flatten } from "./itertools"
2017-10-12 04:02:01 +01:00
import "./number.mod"
2018-04-13 19:28:03 +01:00
import { ModeName } from "./state"
2017-10-23 09:42:50 +01:00
import * as keydown from "./keydown_background"
2018-07-21 16:44:11 +01:00
import { activeTab , firefoxVersionAtLeast } from "./lib/webext"
2018-04-13 19:28:03 +01:00
import * as CommandLineBackground from "./commandline_background"
2018-05-10 21:22:14 +01:00
import * as rc from "./config_rc"
2018-05-26 18:45:18 +02:00
import * as excmd_parser from "./parsers/exmode"
2018-07-01 18:24:37 +02:00
import { mapstrToKeyseq } from "./keyseq"
2017-10-12 04:02:01 +01:00
2018-04-17 18:28:11 +01:00
//#background_helper
import * as Native from "./native_background"
2017-11-03 19:10:12 +00:00
/** @hidden */
2017-10-23 09:42:50 +01:00
export const cmd_params = new Map < string , Map < string , string > > ( )
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-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 ) {
; ( inputToFill as HTMLInputElement ) . value = content . join ( " " )
} else {
inputToFill . textContent = content . join ( " " )
}
2018-04-17 18:45:54 +01:00
}
/** @hidden */
//#content
export async function getinput() {
// this should probably be subsumed by the focusinput code
2018-05-17 21:09:25 +01:00
let input = DOM . getLastUsedInput ( )
if ( "value" in input ) {
return ( input as HTMLInputElement ) . value
} else {
return input . textContent
}
2018-04-17 18:45:54 +01:00
}
2018-06-04 07:10:23 +02:00
/** @hidden */
//#content
export async function getInputSelector() {
return DOM . getSelector ( DOM . getLastUsedInput ( ) )
}
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 ] ] * * .
*
2018-04-26 18:24:34 +01:00
* Uses the ` editorcmd ` config option , default = ` auto ` looks through a list defined in native_background . 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
*
* The editorcmd needs to accept a filename , stay in the foreground while it ' s edited , save the file and exit .
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-04-17 18:45:54 +01:00
* /
//#background
export async function editor() {
2018-06-04 07:10:23 +02:00
let tab = await activeTab ( )
let selector = await Messaging . messageTab ( tab . id , "excmd_content" , "getInputSelector" , [ ] )
let url = new URL ( tab . url )
2018-05-21 20:58:31 +01:00
if ( ! await Native . nativegate ( ) ) return
2018-05-11 10:20:22 -04:00
const file = ( await Native . temp ( await getinput ( ) , url . hostname ) ) . content
2018-06-04 18:51:56 +02:00
// We're using Messaging.messageTab instead of `fillinput()` because fillinput() will execute in the currently active tab, which might not be the tab the user spawned the editor in
2018-06-04 07:10:23 +02:00
Messaging . messageTab ( tab . id , "excmd_content" , "fillinput" , [ selector , ( await Native . editor ( file ) ) . content ] )
2018-04-17 18:45:54 +01:00
// TODO: add annoying "This message was written with [Tridactyl](https://addons.mozilla.org/en-US/firefox/addon/tridactyl-vim/)"
// to everything written using editor
}
2018-04-21 23:43:12 +01:00
//#background_helper
import * as css_util from "./css_util"
2018-07-14 15:18:09 +02:00
/ * *
* Like [ [ guiset ] ] but quieter .
* /
//#background
export async function guiset_quiet ( rule : string , option : string ) {
2018-07-22 18:35:14 +02: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
if ( ! await Native . nativegate ( "0.1.1" ) ) return
let profile_dir = ""
if ( config . get ( "profiledir" ) === "auto" && [ "linux" , "openbsd" , "mac" ] . includes ( ( await browser . runtime . getPlatformInfo ( ) ) . os ) ) {
try {
profile_dir = await Native . getProfileDir ( )
} catch ( e ) { }
} else {
profile_dir = config . get ( "profiledir" )
}
if ( profile_dir == "" ) {
fillcmdline ( "Please set your profile directory (found on about:support) via `set profiledir [profile directory]`" )
return
}
// Make backups
await Native . mkdir ( profile_dir + "/chrome" , true )
let cssstr = ( await Native . read ( profile_dir + "/chrome/userChrome.css" ) ) . content
let cssstrOrig = ( await Native . read ( profile_dir + "/chrome/userChrome.orig.css" ) ) . content
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"); `
let stylesheet = CSS . parse ( cssstr )
// Trim due to https://github.com/reworkcss/css/issues/114
let stylesheetDone = CSS . stringify ( css_util . changeCss ( rule , option , stylesheet ) ) . trim ( )
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
*
2018-07-21 18:29:19 +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 _ 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 _ 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
*
* - titlebar
* - hide
* - show
*
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 )
2018-07-02 19:36:34 +01:00
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-03 19:05:20 +01:00
/ * *
* Like [ [ fixamo ] ] but quieter .
* /
//#background
export async function fixamo_quiet() {
await Native . writePref ( "privacy.resistFingerprinting.block_mozAddonManager" , true )
await Native . writePref ( "extensions.webextensions.restrictedDomains" , "" )
}
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-02 19:36:34 +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 .
* /
//#background
export async function nativeopen ( url : string , . . . firefoxArgs : string [ ] ) {
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
let pos = await config . getAsync ( "tabopenpos" )
let index = ( await activeTab ( ) ) . index + 1
switch ( pos ) {
case "last" :
index = 99999
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
let selecttab = tab = > {
browser . tabs . onCreated . removeListener ( selecttab )
tabSetActive ( tab . id )
browser . tabs . move ( tab . id , { index } )
}
browser . tabs . onCreated . addListener ( selecttab )
2018-05-22 00:49:42 +07:00
if ( ( await browser . runtime . getPlatformInfo ( ) ) . os === "mac" ) {
let osascriptArgs = [ "-e 'on run argv'" , "-e 'tell application \"Firefox\" to open location item 1 of argv'" , "-e 'end run'" ]
2018-06-07 13:17:41 +02:00
await Native . run ( "osascript " + osascriptArgs . join ( " " ) + " " + url )
2018-05-22 00:49:42 +07:00
} else {
if ( firefoxArgs . length === 0 ) firefoxArgs = [ "--new-tab" ]
2018-06-07 13:17:41 +02:00
await Native . run ( config . get ( "browser" ) + " " + firefoxArgs . join ( " " ) + " " + url )
2018-05-22 00:49:42 +07:00
}
2018-06-07 13:17:41 +02:00
setTimeout ( ( ) = > browser . tabs . onCreated . removeListener ( selecttab ) , 100 )
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 .
*
* If you want to use a different shell , just prepend your command with whatever the invocation is and keep in mode that most shells require quotes around the command to be executed , e . g . ` :exclaim xonsh -c "1+2" ` .
*
* 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 [ ] ) {
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-05-05 15:07:21 +01:00
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-04-18 21:49:33 +01:00
* Simply copies "curl -fsSl https://raw.githubusercontent.com/cmcaine/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
export async function installnative() {
2018-05-14 14:21:07 +10:00
if ( ( await browser . runtime . getPlatformInfo ( ) ) . os === "win" ) {
2018-07-11 12:19:04 +10:00
const installstr = await config . get ( "win_nativeinstallcmd" )
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 {
const installstr = await config . get ( "nativeinstallcmd" )
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
}
2018-05-10 21:22:14 +01:00
/ * *
* Runs an RC file from disk .
*
* If no argument given , it will try to open ~ /.tridactylrc, ~/ . config / tridactylrc or $XDG_CONFIG_HOME / tridactyl / tridactylrc in reverse order .
*
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
2018-05-21 20:58:31 +01:00
if ( await Native . nativegate ( "0.1.3" ) ) if ( ! await rc . source ( file ) ) 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 ) {
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" ) {
2018-07-11 12:19:04 +10:00
await Native . run ( await config . get ( "win_nativeinstallcmd" ) )
2018-05-14 14:21:07 +10:00
} else {
await Native . run ( await config . get ( "nativeinstallcmd" ) )
}
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" ) {
2018-05-28 00:32:30 +10:00
let reply = await Native . winFirefoxRestart ( profiledir , browsercmd )
2018-05-24 13:11:02 +10:00
logger . info ( "[+] win_firefox_restart 'reply' = " + JSON . stringify ( reply ) )
2018-05-23 18:07:32 +10:00
if ( Number ( reply [ "code" ] ) === 0 ) {
fillcmdline ( "#" + reply [ "content" ] )
qall ( )
} else {
fillcmdline ( "#" + reply [ "error" ] )
}
} else {
const firefox = ( await Native . ffargs ( ) ) . join ( " " )
// 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-04-18 21:49:33 +01:00
// }}}
2017-11-03 19:10:12 +00:00
/** @hidden */
2017-10-12 04:02:01 +01:00
function hasScheme ( uri : string ) {
2017-11-19 06:05:15 +00:00
return uri . match ( /^([\w-]+):/ )
2017-10-12 04:02:01 +01:00
}
2017-11-20 01:11:38 +00:00
/** @hidden */
function searchURL ( provider : string , query : string ) {
2017-11-29 19:51:18 +00:00
if ( provider == "search" ) provider = config . get ( "searchengine" )
2017-12-26 10:26:27 +01:00
const searchurlprovider = config . get ( "searchurls" , provider )
2018-04-13 19:28:03 +01:00
if ( searchurlprovider === undefined ) {
2017-11-20 01:11:38 +00:00
throw new TypeError ( ` Unknown provider: ' ${ provider } ' ` )
}
2017-12-26 10:26:27 +01:00
2018-02-01 16:42:24 +00:00
return UrlUtil . interpolateSearchItem ( new URL ( searchurlprovider ) , query )
2017-11-20 01:11:38 +00:00
}
2018-04-19 17:35:05 +01:00
/** Take a string and find a way to interpret it as a URI or search query. */
2017-11-03 19:10:12 +00:00
/** @hidden */
2018-03-03 09:56:05 +01:00
export function forceURI ( maybeURI : string ) : string {
2017-12-06 14:59:00 +00:00
// Need undefined to be able to open about:newtab
if ( maybeURI == "" ) return undefined
2018-04-19 17:35:05 +01:00
// If the uri looks like it might contain a schema and a domain, try url()
// test for a non-whitespace, non-colon character after the colon to avoid
// false positives like "error: can't reticulate spline" and "std::map".
//
// These heuristics mean that very unusual URIs will be coerced to
// something else by this function.
if ( /^[a-zA-Z0-9+.-]+:[^\s:]/ . test ( maybeURI ) ) {
try {
return new URL ( maybeURI ) . href
} catch ( e ) {
if ( e . name !== "TypeError" ) throw e
}
2017-11-09 07:38:24 +00:00
}
2017-11-15 00:55:51 +02:00
2017-11-20 01:11:38 +00:00
// Else if search keyword:
try {
2018-04-13 19:28:03 +01:00
const args = maybeURI . split ( " " )
return searchURL ( args [ 0 ] , args . slice ( 1 ) . join ( " " ) ) . href
2017-11-20 01:11:38 +00:00
} catch ( e ) {
2018-04-13 19:28:03 +01:00
if ( e . name !== "TypeError" ) throw e
2017-11-20 01:11:38 +00:00
}
// Else if it's a domain or something
try {
2018-04-13 19:28:03 +01:00
const url = new URL ( "http://" + maybeURI )
2017-11-20 01:11:38 +00:00
// Ignore unlikely domains
2018-04-13 19:28:03 +01:00
if ( url . hostname . includes ( "." ) || url . port || url . password ) {
2017-11-20 01:11:38 +00:00
return url . href
}
} catch ( e ) {
2018-04-13 19:28:03 +01:00
if ( e . name !== "TypeError" ) throw e
2017-10-12 04:02:01 +01:00
}
2017-11-20 01:11:38 +00:00
2017-11-29 19:51:18 +00:00
// Else search $searchengine
2018-04-13 19:28:03 +01:00
return searchURL ( "search" , maybeURI ) . href
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
//#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
}
// }}}
2017-12-04 14:14:18 +00:00
// {{{ INTERNAL/DEBUG
/ * *
* Set the logging level for a given logging module .
*
* @param logModule the logging module to set the level on
* @param level the level to log at : in increasing verbosity , one of
* "never" , "error" , "warning" , "info" , "debug"
* /
//#background
export function loggingsetlevel ( logModule : string , level : string ) {
const map = {
2018-04-13 19:28:03 +01:00
never : Logging . LEVEL . NEVER ,
error : Logging.LEVEL.ERROR ,
warning : Logging.LEVEL.WARNING ,
info : Logging.LEVEL.INFO ,
debug : Logging.LEVEL.DEBUG ,
2017-12-04 14:14:18 +00:00
}
2017-12-29 23:58:23 +00:00
let newLevel = map [ level . toLowerCase ( ) ]
2017-12-04 14:14:18 +00:00
if ( newLevel !== undefined ) {
2018-02-01 23:39:23 +00:00
config . set ( "logging" , logModule , newLevel )
} else {
throw "Bad log level!"
2017-12-04 14:14:18 +00:00
}
}
// }}}
2017-11-22 18:05:54 +00:00
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() {
let tabid = await activeTabId ( )
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
2018-05-27 15:59:20 +02:00
let ensure = ( obj , key , def ) = > {
if ( obj [ key ] === null || obj [ key ] === undefined ) obj [ key ] = def
}
let page = getJumpPageId ( )
ensure ( jumps , page , { } )
2018-06-03 10:06:51 +02:00
let dummy = new UIEvent ( "scroll" )
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-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 = > {
let jumps = alljumps [ getJumpPageId ( ) ]
let current = jumps . cur - n
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
let p = jumps . list [ jumps . cur ]
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
}
2018-05-27 15:59:20 +02:00
let pageX = scrollEvent . pageX
let pageY = scrollEvent . pageY
// Get config for current page
curJumps ( ) . then ( alljumps = > {
let jumps = alljumps [ getJumpPageId ( ) ]
// Prevent pending jump from being registered
clearTimeout ( jumps . timeoutid )
// Schedule the registering of the current jump
jumps . timeoutid = setTimeout ( ( ) = > {
let list = jumps . list
// if the page hasn't moved, stop
if ( list [ jumps . cur ] . x == pageX && list [ jumps . cur ] . y == pageY ) return
// 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 )
} , config . get ( "jumpdelay" ) )
} )
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() {
2018-04-13 19:28:03 +01:00
; ( document . activeElement as HTMLInputElement ) . blur ( )
2018-04-29 07:15:54 +02:00
state . 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-05-29 20:43:31 +02: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-06-03 11:08:45 +02:00
If one number is given , scroll to that percentage along a chosen axis , defaulting to the y - axis
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 .
2017-10-12 04:02:01 +01:00
* /
//#content
2017-12-08 21:11:40 -08:00
export function scrollto ( a : number , b : number | "x" | "y" = "y" ) {
2017-10-12 04:02:01 +01:00
a = Number ( a )
2018-07-06 07:06:05 +02:00
let elem = window . document . scrollingElement || window . document . documentElement
2018-04-22 18:20:37 +02:00
let percentage = a . clamp ( 0 , 100 )
2017-12-08 21:11:40 -08:00
if ( b === "y" ) {
2018-04-22 18:20:37 +02:00
let top = elem . getClientRects ( ) [ 0 ] . top
window . scrollTo ( window . scrollX , percentage * elem . scrollHeight / 100 )
if ( top == elem . getClientRects ( ) [ 0 ] . top && ( percentage == 0 || percentage == 100 ) ) {
// 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
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" ) {
2018-04-22 18:20:37 +02:00
let left = elem . getClientRects ( ) [ 0 ] . left
window . scrollTo ( percentage * elem . scrollWidth / 100 , window . scrollY )
if ( left == elem . getClientRects ( ) [ 0 ] . left && ( percentage == 0 || percentage == 100 ) ) {
2018-05-26 17:31:27 +02:00
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 ) {
2018-07-06 07:06:05 +02:00
let getLineHeight = elem = > {
// Get line height
const cssHeight = window . getComputedStyle ( elem ) . getPropertyValue ( "line-height" )
// Remove the "px" at the end
return parseInt ( cssHeight . substr ( 0 , cssHeight . length - 2 ) )
}
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
}
2018-01-28 21:18:29 +00:00
//#background_helper
2018-04-13 19:28:03 +01:00
import * as finding from "./finding_background"
2018-01-28 21:18:29 +00:00
2018-04-12 22:33:10 +01:00
/ * * S t a r t f i n d m o d e . W o r k i n p r o g r e s s .
*
* @param direction - the direction to search in : 1 is forwards , - 1 is backwards .
2018-04-13 19:28:03 +01:00
*
2018-04-12 22:33:10 +01:00
* /
2018-01-28 21:18:29 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function find ( direction? : number ) {
2018-03-18 15:08:09 +00:00
if ( direction === undefined ) direction = 1
finding . findPage ( direction )
}
2018-04-12 22:33:10 +01:00
/ * * H i g h l i g h t t h e n e x t o c c u r e n c e o f t h e p r e v i o u s l y s e a r c h e d f o r w o r d .
*
* @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
* /
2018-03-18 15:08:09 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function findnext ( n : number ) {
2018-03-18 15:08:09 +00:00
finding . findPageNavigate ( n )
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 ) {
2017-11-09 00:41:07 +00:00
let tabstoreload = await getnexttabs ( await activeTabId ( ) , n )
2018-04-13 19:28:03 +01:00
let reloadProperties = { bypassCache : hard }
2017-10-12 04:02:01 +01:00
tabstoreload . map ( n = > browser . tabs . reload ( n , reloadProperties ) )
}
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 ) {
let tabs = await browser . tabs . query ( { currentWindow : true } )
let reloadprops = { bypassCache : hard }
2017-11-22 16:59:58 +00:00
tabs . map ( tab = > browser . tabs . reload ( tab . id , reloadprops ) )
}
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 */
export const ABOUT_WHITELIST = [ "about:home" , "about:license" , "about:logo" , "about:rights" ]
2017-11-19 06:05:15 +00:00
/ * * O p e n a n e w p a g e i n t h e c u r r e n t t a b .
2018-04-27 21:43:58 +01:00
*
* @param urlarr
* - if first word looks like it has a schema , treat as a URI
* - else if the first word contains a dot , treat as a domain name
* - else if the first word is a key of [ [ SEARCH_URLS ] ] , treat all following terms as search parameters for that provider
* - else treat as search parameters for google
2018-05-05 12:49:45 +01:00
*
2018-04-27 21:43:58 +01:00
* Related settings :
2018-06-14 15:56:06 -04:00
* - "searchengine" : "google" or any of [ [ SEARCH_URLS ] ]
* - "historyresults" : the n - most - recent results to ask Firefox for before they are sorted by frequency . Reduce this number if you find your results are bad .
*
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 [ ] ) {
2017-11-09 15:30:09 +00:00
let url = urlarr . join ( " " )
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.
2018-07-20 13:39:59 +01:00
if ( url === "about:blank" ) {
2018-04-19 16:35:10 +01:00
browserBg . tabs . update ( await activeTabId ( ) , { url } )
2018-05-04 23:05:00 +01:00
} else 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
2018-04-20 22:06:06 +01:00
Messaging . message ( "commandline_background" , "recvExStr" , [ "nativeopen " + url ] )
2018-04-26 09:10:25 +01:00
} else if ( url !== "" ) {
2018-04-19 16:35:10 +01:00
window . location . href = forceURI ( url )
}
2017-10-12 04:02:01 +01:00
}
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 [ ] ) {
let url = urlarr . join ( " " )
// Setting window.location to about:blank results in a page we can't access, tabs.update works.
if ( [ "about:blank" ] . includes ( url ) ) {
url = url || undefined
browserBg . tabs . update ( await activeTabId ( ) , { url } )
// Open URLs that firefox won't let us by running `firefox <URL>` on the command line
} else if ( ! ABOUT_WHITELIST . includes ( url ) && url . match ( /^(about|file):.*/ ) ) {
Messaging . message ( "commandline_background" , "recvExStr" , [ "nativeopen " + url ] )
} else if ( url !== "" ) {
document . location . replace ( forceURI ( url ) )
}
}
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() {
let url = document . location . href
let searchurls = await config . getAsync ( "searchurls" )
let result = url
for ( let engine in searchurls ) {
let [ beginning , end ] = [ . . . searchurls [ engine ] . split ( "%s" ) , "" ]
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
2018-07-02 06:07:37 +02:00
let amperpos = encodedArgs . search ( "&" )
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 , " " )
let 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
let sourceElement = undefined
2018-04-28 07:54:22 +02:00
/** @hidden */
//#content_helper
function removeSource() {
if ( sourceElement ) {
sourceElement . remove ( )
sourceElement = undefined
}
}
/ * * 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 ) {
sourceElement = executeWithoutCommandLine ( ( ) = > {
let pre = document . createElement ( "pre" )
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
/ * *
* Go to the homepages you have set with ` set home [url1] [url2] ` .
*
* @param all
* - if "true" , opens all homepages in new tabs
* - if "false" or not given , opens the last homepage in the current tab
*
* /
2017-11-30 13:50:10 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function home ( all : "false" | "true" = "false" ) {
2017-11-30 13:50:10 +00:00
let 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-07-11 12:19:04 +10:00
` :help something ` jumps to the entry for something . Something can be an excmd , an alias for an excmd or a binding .
2018-06-07 12:52:11 +02:00
The "nmaps" list is a list of all the bindings for the command you ' re seeing and the "exaliases" list lists all its aliases .
If there ' s a conflict ( e . g . you have a "go" binding that does something and also a "go" excmd that does something else ) , the binding has higher priority .
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 now 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
export async function help ( excmd? : string ) {
2018-07-21 18:29:19 +01:00
const docpage = browser . extension . getURL ( "static/docs/modules/_src_excmds_.html" )
2018-05-01 14:02:53 +01:00
if ( excmd === undefined ) excmd = ""
2018-06-03 10:21:05 +02:00
else {
2018-06-03 17:37:17 +02:00
let bindings = await config . getAsync ( "nmaps" )
2018-06-07 12:52:11 +02:00
// If 'excmd' matches a binding, replace 'excmd' with the command that would be executed when pressing the key sequence referenced by 'excmd'
2018-06-03 17:37:17 +02:00
if ( excmd in bindings ) {
excmd = bindings [ excmd ] . split ( " " )
excmd = [ "composite" , "fillcmdline" ] . includes ( excmd [ 0 ] ) ? excmd [ 1 ] : excmd [ 0 ]
}
2018-06-07 12:52:11 +02:00
2018-06-03 10:21:05 +02:00
let aliases = await config . getAsync ( "exaliases" )
2018-06-07 12:52:11 +02:00
// As long as excmd is an alias, try to resolve this alias to a real excmd
let resolved = [ ]
2018-06-03 17:32:04 +02:00
while ( aliases [ excmd ] ) {
2018-06-07 12:52:11 +02:00
resolved . push ( excmd )
2018-06-03 17:32:04 +02:00
excmd = aliases [ excmd ] . split ( " " )
excmd = excmd [ 0 ] == "composite" ? excmd [ 1 ] : excmd [ 0 ]
2018-06-07 12:52:11 +02:00
// Prevent infinite loops
if ( resolved . includes ( excmd ) ) break
2018-06-03 17:32:04 +02:00
}
2018-06-03 10:21:05 +02:00
}
2017-11-19 06:05:15 +00:00
if ( ( await activeTab ( ) ) . url . startsWith ( docpage ) ) {
2018-04-15 11:07:48 +01:00
open ( docpage + "#" + excmd )
2017-11-19 06:05:15 +00:00
} else {
2018-04-15 11:07:48 +01:00
tabopen ( docpage + "#" + excmd )
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 ) {
2018-04-13 23:30:53 +01:00
const tutor = browser . extension . getURL ( "static/clippy/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
export async function credits ( excmd? : string ) {
const creditspage = browser . extension . getURL ( "static/authors.html" )
tabopen ( creditspage )
}
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.
2018-04-13 19:28:03 +01:00
const links = Array . from ( < NodeListOf < HTMLAnchorElement > > document . querySelectorAll ( "a[href]" ) )
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 {
const nodes = < NodeListOf < HTMLElement > > document . querySelectorAll ( selector )
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 | » | >>
set followpagepatterns . prev ^ ( prev ( ious ) ? | older | précédent ) \ b | » | >>
` ` `
@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" ) {
2017-11-22 22:21:46 +00:00
const link = < HTMLLinkElement > selectLast ( ` link[rel~= ${ rel } ][href] ` )
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
2018-04-13 19:28:03 +01:00
const anchor = < HTMLAnchorElement > selectLast ( ` a[rel~= ${ rel } ][href] ` ) || findRelLink ( new RegExp ( config . get ( "followpagepatterns" , rel ) , "i" ) )
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 ) {
2017-12-04 03:12:19 +00:00
let 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() {
2017-12-04 03:12:19 +00:00
let 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 ) {
2017-12-04 03:12:19 +00:00
let 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 `
*
* * 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
* * - 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 ]
* * - q < query > < new_val >
* * - Q < query >
* * - g < graftPoint > < newPathTail >
2017-12-04 03:12:19 +00:00
* /
//#content
export function urlmodify ( mode : "-t" | "-r" | "-q" | "-Q" | "-g" , . . . args : string [ ] ) {
let oldUrl = new URL ( window . location . href )
let newUrl = undefined
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
}
let regexp = new RegExp ( args [ 0 ] , args [ 2 ] )
newUrl = oldUrl . href . replace ( regexp , args [ 1 ] )
break
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 ) {
2017-11-30 18:04:16 +01:00
let elems = document . querySelectorAll ( "link[" + reltype + "='" + rel + "']" ) as NodeListOf < HTMLLinkElement >
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 [ ]
}
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
2018-04-13 19:28:03 +01:00
if ( rel == "true" ) level += await browser . tabs . getZoom ( )
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 ) ) {
2018-04-13 19:28:03 +01:00
let aTab = await activeTab ( )
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-01-31 19:51:08 +00:00
//@hidden
//#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-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-07-18 14:36:35 +01:00
export async function loadaucmds ( cmdType : "DocStart" | "DocLoad" | "DocEnd" | "TabEnter" | "TabLeft" ) {
2018-06-01 08:39:43 +02:00
let 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 )
for ( let aukey of aukeyarr ) {
2018-01-31 19:51:08 +00:00
Messaging . message ( "commandline_background" , "recvExStr" , [ aucmds [ aukey ] ] )
}
}
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
2018-04-13 19:28:03 +01:00
let 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
2018-04-13 19:28:03 +01:00
let inputs = DOM . getElemsBySelector ( INPUTPASSWORD_selectors , [ DOM . isSubstantial ] )
2017-11-23 23:57:04 +00:00
if ( inputs . length ) {
inputToFocus = < HTMLElement > inputs [ 0 ]
}
2018-04-13 19:28:03 +01:00
} else if ( nth === "-b" ) {
let inputs = DOM . getElemsBySelector ( INPUTTAGS_selectors , [ DOM . isSubstantial ] ) as HTMLElement [ ]
2017-11-23 23:57:04 +00:00
inputToFocus = inputs . sort ( DOM . compareElementArea ) . slice ( - 1 ) [ 0 ]
}
// 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 ) {
2017-11-23 23:57:04 +00:00
let index = isNaN ( < number > nth ) ? 0 : < number > nth
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 )
2018-04-13 19:28:03 +01:00
if ( config . get ( "gimode" ) === "nextinput" && state . mode !== "input" ) {
state . 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 ) {
let tail = state . prevInputs [ state . prevInputs . length - 1 ]
let jumppos = tail . jumppos ? tail.jumppos : state.prevInputs.length - 1
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 ) {
2017-11-28 00:01:41 +00:00
tabIndexSetActive ( ( await activeTab ( ) ) . index + increment + 1 )
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-07-10 19:35:21 +02:00
return tabIndexSetActive ( ( await activeTab ( ) ) . index - increment + 1 )
2017-11-27 17:27:17 +11:00
}
/** Switch to the first tab. */
//#background
export async function tabfirst() {
2017-11-28 00:01:41 +00:00
tabIndexSetActive ( 1 )
2017-11-27 17:27:17 +11:00
}
/** Switch to the last tab. */
2017-10-12 04:02:01 +01:00
//#background
2017-11-27 17:27:17 +11:00
export async function tablast() {
2017-11-28 00:01:41 +00:00
tabIndexSetActive ( 0 )
2017-10-12 04:02:01 +01: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-06-14 15:25:38 +00: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 .
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-02-18 16:05:38 +00:00
* /
2017-10-12 04:02:01 +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
// 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.
let win = await browser . windows . getCurrent ( )
2018-06-13 08:19:38 +00: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
}
2018-03-12 22:37:37 +00:00
let url : string
2018-05-24 21:37:19 +00:00
let address = ( await argParse ( addressarr ) ) . join ( " " )
2018-03-12 22:37:37 +00:00
2018-05-04 23:05:00 +01:00
if ( ! ABOUT_WHITELIST . includes ( address ) && address . match ( /^(about|file):.*/ ) ) {
2018-05-25 01:42:33 +07:00
if ( ( await browser . runtime . getPlatformInfo ( ) ) . os === "mac" && ( await browser . windows . getCurrent ( ) ) . incognito ) {
2018-05-25 09:32:20 +01:00
fillcmdline_notrail ( "# nativeopen isn't supported in private mode on OSX. Consider installing Linux or Windows :)." )
2018-05-25 01:42:33 +07:00
return
} else {
nativeopen ( address )
return
}
2018-04-20 22:06:06 +01:00
} else if ( address != "" ) url = forceURI ( address )
2018-03-12 22:37:37 +00:00
else url = forceURI ( config . get ( "newtab" ) )
2018-05-08 12:27:30 +00:00
activeTabContainerId ( ) . then ( containerId = > {
2018-05-24 11:27:09 +00:00
// Ensure -c has priority.
2018-06-13 08:19:38 +00:00
if ( container ) openInNewTab ( url , { active : active , cookieStoreId : container } )
2018-05-24 11:27:09 +00:00
else if ( containerId && config . get ( "tabopencontaineraware" ) === "true" ) openInNewTab ( url , { active : active , cookieStoreId : containerId } )
2018-05-08 12:27:30 +00:00
else openInNewTab ( url , { active } )
} )
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
return ( await getSortedWinTabs ( ) ) [ 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 {
return await activeTabId ( )
}
}
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?
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 ( )
let ids = tabs . filter ( tab = > tab . index > atab . index ) . map ( tab = > tab . id )
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 ( )
let ids = tabs . filter ( tab = > tab . index < atab . index ) . map ( tab = > tab . id )
browser . tabs . remove ( ids )
}
2017-10-21 12:54:48 +02:00
/** restore most recently closed tab in this window unless the most recently closed item was a window */
2017-10-12 04:02:01 +01:00
//#background
2018-04-13 19:28:03 +01:00
export async function undo() {
const current_win_id : number = ( await browser . windows . getCurrent ( ) ) . id
2017-10-21 12:54:48 +02:00
const sessions = await browser . sessions . getRecentlyClosed ( )
// The first session object that's a window or a tab from this window. Or undefined if sessions is empty.
2018-04-13 19:28:03 +01:00
let closed = sessions . find ( s = > {
return "window" in s || ( s . tab && s . tab . windowId == current_win_id )
2017-10-21 12:54:48 +02:00
} )
if ( closed ) {
if ( closed . tab ) {
browser . sessions . restore ( closed . tab . sessionId )
2018-04-13 19:28:03 +01:00
} else if ( closed . window ) {
2017-10-21 12:54:48 +02:00
browser . sessions . restore ( closed . window . sessionId )
}
}
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 .
1 is the first index . 0 is the last index . - 1 is the penultimate , etc .
* /
2017-10-12 04:02:01 +01:00
//#background
2017-11-28 00:01:41 +00:00
export async function tabmove ( index = "0" ) {
const aTab = await activeTab ( )
let newindex : number
if ( index . startsWith ( "+" ) || index . startsWith ( "-" ) ) {
newindex = Math . max ( 0 , Number ( index ) + aTab . index )
} else newindex = Number ( index ) - 1
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() {
let 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 `
@param string [ ] muteArgs
* /
//#background
export async function mute ( . . . muteArgs : string [ ] ) : Promise < void > {
let mute = true
let toggle = false
let all = false
let argParse = ( args : string [ ] ) = > {
2018-07-17 16:18:48 +01:00
if ( args == null ) {
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 )
2018-07-17 16:18:48 +01:00
let updateObj = { muted : false }
if ( mute ) {
updateObj . muted = true
}
if ( all ) {
let tabs = await browser . tabs . query ( { currentWindow : true } )
2018-07-15 23:46:13 +00:00
for ( let 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 {
let 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
2017-11-30 11:40:01 +00:00
/** Like [[tabopen]], but in a new window */
2017-10-12 04:02:01 +01:00
//#background
export async function winopen ( . . . args : string [ ] ) {
let address : string
const createData = { }
2018-04-20 22:06:06 +01:00
let firefoxArgs = "--new-window"
2017-10-12 04:02:01 +01:00
if ( args [ 0 ] === "-private" ) {
createData [ "incognito" ] = true
2018-04-13 19:28:03 +01:00
address = args . slice ( 1 , args . length ) . join ( " " )
2018-04-20 22:06:06 +01:00
firefoxArgs = "--private-window"
2018-04-13 19:28:03 +01:00
} else address = args . join ( " " )
2017-11-30 11:40:01 +00:00
createData [ "url" ] = address != "" ? forceURI ( address ) : forceURI ( config . get ( "newtab" ) )
2018-05-04 23:05:00 +01:00
if ( ! ABOUT_WHITELIST . includes ( address ) && address . match ( /^(about|file):.*/ ) ) {
2018-05-25 01:42:33 +07:00
if ( ( await browser . runtime . getPlatformInfo ( ) ) . os === "mac" ) {
2018-05-25 09:32:20 +01:00
fillcmdline_notrail ( "# nativeopen isn't supported for winopen on OSX. Consider installing Linux or Windows :)." )
2018-05-25 01:42:33 +07:00
return
} else {
nativeopen ( address , firefoxArgs )
return
}
2018-04-20 22:06:06 +01:00
}
2017-10-12 04:02:01 +01:00
browser . windows . create ( createData )
}
//#background
export async function winclose() {
browser . windows . remove ( ( await browser . windows . getCurrent ( ) ) . id )
}
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() {
2017-11-02 19:36:44 +00:00
let windows = await browser . windows . getAll ( )
2018-04-13 19:28:03 +01:00
windows . map ( 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 ) {
let 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
export async function containerremove ( 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 )
try {
let containerId = await Container . fuzzyMatch ( name )
let containerObj = Container . fromString ( uname , ucolor , uicon )
2018-06-13 12:20:27 +00:00
await Container . update ( containerId , containerObj )
2018-06-13 08:19:38 +00:00
} catch ( e ) {
throw e
}
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.
2018-06-20 20:26:47 +00:00
let 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-05-09 12:29:46 +00:00
// }}}
//
2017-10-12 04:02:01 +01:00
// {{{ MISC
2018-04-12 22:33:10 +01:00
/ * * D e p r e c a t e d
* @hidden
* /
2017-10-23 09:42:50 +01:00
//#background
export function suppress ( preventDefault? : boolean , stopPropagation? : boolean ) {
2017-11-19 03:22:59 +00:00
mode ( "ignore" )
2017-10-23 09:42:50 +01:00
}
2017-11-26 00:06:02 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function version() {
2017-11-28 17:23:16 +00:00
fillcmdline_notrail ( "REPLACE_ME_WITH_THE_VERSION_USING_SED" )
2017-11-26 00:06:02 +00:00
}
2017-11-19 06:05:15 +00:00
/ * * E x a m p l e :
- ` mode ignore ` to ignore all keys .
* /
2017-10-12 04:02:01 +01:00
//#background
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 ( )
2018-01-28 21:18:29 +00:00
} else if ( mode === "find" ) {
find ( )
2017-11-19 06:05:15 +00:00
} else {
state . mode = mode
}
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
//#background
/* 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-11-18 13:47:10 +00:00
//#background_helper
2018-04-13 19:28:03 +01:00
import * as controller from "./controller"
2017-11-18 13:47:10 +00:00
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 .
Falls back to the last executed command if ` cmd ` doesn ' t exist .
Executes the command once if ` n ` isn ' t defined either .
* /
//#background
2017-12-04 06:16:29 +01:00
export 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" )
2018-04-13 19:28:03 +01:00
for ( let i = 0 ; i < n ; i ++ ) controller . acceptExCmd ( cmd )
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
* /
2017-11-18 13:47:10 +00:00
//#background
2018-04-22 17:40:55 +01:00
export async function composite ( . . . cmds : string [ ] ) {
2018-05-26 21:14:30 +02:00
try {
return cmds
. join ( " " )
. split ( ";" )
. reduce (
async ( _ , cmd ) = > {
2018-05-28 19:48:35 +02:00
await _
let cmds = cmd . split ( "|" )
2018-06-08 09:04:02 +02:00
let [ fn , args ] = excmd_parser . parser ( cmds [ 0 ] )
2018-05-28 19:48:35 +02:00
return cmds . slice ( 1 ) . reduce ( async ( pipedValue , cmd ) = > {
let [ fn , args ] = excmd_parser . parser ( cmd )
return fn . call ( { } , . . . args , await pipedValue )
} , fn . call ( { } , . . . args ) )
2018-05-26 21:14:30 +02:00
} ,
null as any ,
)
} catch ( e ) {
logger . error ( e )
2018-04-22 17:40:55 +01:00
}
}
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 .
* /
2018-04-22 17:40:55 +01:00
//#background
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 */
2017-11-22 18:05:54 +00:00
//#background
2018-06-30 16:59:10 +02:00
function showcmdline ( focus = true ) {
CommandLineBackground . show ( focus )
2017-10-12 04:02:01 +01:00
}
2018-07-02 07:06:26 +02:00
/** @hidden */
//#background
export function hidecmdline() {
CommandLineBackground . hide ( )
}
2017-11-19 06:05:15 +00:00
/** Set the current value of the commandline to string *with* a trailing space */
2017-10-12 04:02:01 +01:00
//#background
2017-10-24 12:51:04 +01:00
export function fillcmdline ( . . . strarr : string [ ] ) {
let str = strarr . join ( " " )
2017-10-12 04:02:01 +01:00
showcmdline ( )
2017-10-28 05:11:10 +01:00
messageActiveTab ( "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 */
2017-11-09 15:30:09 +00:00
//#background
export function fillcmdline_notrail ( . . . strarr : string [ ] ) {
let str = strarr . join ( " " )
let trailspace = false
showcmdline ( )
messageActiveTab ( "commandline_frame" , "fillcmdline" , [ str , trailspace ] )
}
2018-06-30 16:59:10 +02:00
/** Show and fill the command line without focusing it */
//#background
export function fillcmdline_nofocus ( . . . strarr : string [ ] ) {
showcmdline ( false )
return messageActiveTab ( "commandline_frame" , "fillcmdline" , [ strarr . join ( " " ) , false , false ] )
}
2018-07-02 19:36:34 +01:00
/** Shows str in the command line for ms milliseconds. Recommended duration: 3000ms. */
2018-06-30 16:59:10 +02:00
//#background
export async function fillcmdline_tmp ( ms : string , . . . strarr : string [ ] ) {
let milliseconds = parseInt ( ms )
2018-07-01 18:17:35 +02:00
let str = strarr . join ( " " )
let tabId = await activeTabId ( )
showcmdline ( false )
messageTab ( tabId , "commandline_frame" , "fillcmdline" , [ strarr . join ( " " ) , false , false ] )
2018-06-30 16:59:10 +02:00
return new Promise ( resolve = >
setTimeout ( async ( ) = > {
2018-07-05 19:50:32 +02:00
if ( ( await messageTab ( tabId , "commandline_frame" , "getContent" , [ ] ) ) == str ) {
CommandLineBackground . hide ( tabId )
await messageTab ( tabId , "commandline_frame" , "clear" , [ true ] )
}
2018-06-30 16:59:10 +02:00
resolve ( )
} , milliseconds ) ,
)
}
2018-05-16 17:12:29 +01:00
/ * *
* Returns the current URL . For use with [ [ composite ] ] .
* /
2017-11-09 15:30:09 +00:00
//#background
2018-05-16 17:12:29 +01:00
export async function get_current_url() {
return ( await activeTab ( ) ) . url
2017-11-09 15:30:09 +00: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-07-01 20:43:44 +01:00
export async function yank ( . . . content : string [ ] ) {
2018-07-03 06:44:25 +02:00
await setclip ( content . join ( " " ) )
2018-05-20 22:29:59 -07:00
}
2018-07-03 06:44:25 +02:00
/ * * C o p i e s a s t r i n g t o t h e c l i p b o a r d / s e l e c t i o n b u f f e r d e p e n d i n g o n t h e u s e r ' s p r e f e r e n c e s
2018-06-12 06:15:28 +02:00
*
* @hidden
* /
//#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
let s = ( ) = > Native . clipboard ( "set" , str )
let c = ( ) = > messageActiveTab ( "commandline_frame" , "setClipboard" , [ str ] )
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
}
return Promise . all ( promises )
}
2018-07-03 06:44:25 +02:00
/ * * F e t c h e s t h e c o n t e n t o f t h e c l i p b o a r d / s e l e c t i o n b u f f e r d e p e n d i n g o n u s e r ' s p r e f e r e n c e s
2018-06-12 06:15:28 +02:00
*
* @hidden
* /
//#background_helper
2018-07-03 06:44:25 +02:00
async function getclip() {
2018-07-17 18:00:46 +01:00
if ( ( await config . getAsync ( "putfrom" ) ) == "clipboard" ) {
2018-07-17 16:18:48 +01:00
return messageActiveTab ( "commandline_frame" , "getClipboard" )
2018-07-03 19:52:22 +02:00
} else {
2018-07-17 16:18:48 +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 .
If ` excmd == "open" ` , call [ [ open ] ] with the contents of the clipboard . Similarly for [ [ tabopen ] ] .
If ` excmd == "yank" ` , copy the current URL , or if given , the value of toYank , into the system clipboard .
2017-11-27 19:15:04 +00:00
If ` excmd == "yankcanon" ` , copy the canonical URL of the current page if it exists , otherwise copy the current URL .
If ` excmd == "yankshort" ` , copy the shortlink version of the current URL , and fall back to the canonical then actual URL . Known to work on https : //yankshort.neocities.org/.
2018-04-11 19:05:30 +01:00
If ` excmd == "yanktitle" ` , copy the title of the open page .
If ` excmd == "yankmd" ` , copy the title and url of the open page formatted in Markdown for easy use on sites such as reddit .
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-04-13 19:28:03 +01:00
export async function clipboard ( excmd : "open" | "yank" | "yankshort" | "yankcanon" | "yanktitle" | "yankmd" | "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-07-03 06:44:25 +02:00
try {
switch ( excmd ) {
case "yankshort" :
urls = await geturlsforlinks ( "rel" , "shortlink" )
if ( urls . length == 0 ) {
urls = await geturlsforlinks ( "rev" , "canonical" )
}
if ( urls . length > 0 ) {
await yank ( urls [ 0 ] )
fillcmdline_tmp ( "3000" , "# " + urls [ 0 ] + " copied to clipboard." )
break
}
// 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." )
break
}
// Trying yank if yankcanon failed...
case "yank" :
content = content == "" ? ( await activeTab ( ) ) . url : content
await yank ( content )
fillcmdline_tmp ( "3000" , "# " + content + " copied to clipboard." )
2017-11-27 19:48:49 +01:00
break
2018-07-03 06:44:25 +02:00
case "yanktitle" :
content = ( await activeTab ( ) ) . title
await yank ( content )
fillcmdline_tmp ( "3000" , "# " + content + " copied to clipboard." )
2017-11-27 19:48:49 +01:00
break
2018-07-03 06:44:25 +02:00
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 ( )
url && open ( url )
break
case "tabopen" :
url = await getclip ( )
url && tabopen ( url )
break
default :
// todo: maybe we should have some common error and error handler
throw new Error ( ` [clipboard] unknown excmd: ${ excmd } ` )
}
} catch ( e ) {
logger . error ( e )
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
This is different from [ [ bufferall ] ] because ` index ` is the position of the tab in the window .
2017-11-27 22:42:50 +11:00
* /
2017-10-12 04:02:01 +01:00
//#background
2018-04-13 19:28:03 +01:00
export async function buffer ( 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-06-19 19:42:35 +02:00
export async function bufferall ( id : string ) {
let windows = ( await browser . windows . getAll ( ) ) . map ( w = > w . id ) . sort ( )
if ( id === null || id === undefined || ! id . match ( /\d+\.\d+/ ) ) {
const tab = await activeTab ( )
let prevId = id
id = windows . indexOf ( tab . windowId ) + "." + ( tab . index + 1 )
logger . info ( ` bufferall: Bad tab id: ${ prevId } , defaulting to ${ id } ` )
2018-06-18 07:55:46 +02:00
}
2018-06-19 19:42:35 +02:00
let [ winindex , tabindex ] = id . split ( "." )
await browser . windows . update ( windows [ parseInt ( winindex ) - 1 ] , { focused : true } )
return browser . tabs . update ( await idFromIndex ( tabindex ) , { 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 ( " " )
// Set alias
2018-02-01 23:39:23 +00:00
config . set ( "exaliases" , name , def )
2018-01-28 18:13:21 +00:00
aliases . expandExstr ( name )
2018-04-13 19:28:03 +01:00
} catch ( e ) {
2018-01-28 17:57:46 +00:00
// Warn user about infinite loops
2018-04-13 19:28:03 +01:00
fillcmdline_notrail ( e , " Alias unset." )
2018-01-28 17:57:46 +00:00
config . unset ( "exaliases" , name )
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-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 `
2017-11-26 14:56:09 +00:00
- ` bind D composite tabclose | buffer # `
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 ) .
See also :
- [ [ unbind ] ]
- [ [ reset ] ]
* /
2017-11-05 14:10:11 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function bind ( key : string , . . . bindarr : string [ ] ) {
2018-07-01 18:24:37 +02:00
// Convert key to internal representation
2018-07-01 20:43:44 +01:00
key = mapstrToKeyseq ( key )
. map ( k = > k . toMapstr ( ) )
. join ( "" )
2018-02-19 01:12:58 +00:00
if ( bindarr . length ) {
let exstring = bindarr . join ( " " )
config . set ( "nmaps" , key , exstring )
2018-02-19 01:23:21 +00:00
} else if ( key . length ) {
2018-02-19 01:12:58 +00:00
// Display the existing bind
fillcmdline_notrail ( "#" , key , "=" , config . get ( "nmaps" , key ) )
}
2017-11-05 14:10:11 +00:00
}
2018-02-18 16:05:38 +00:00
/ * *
2018-02-01 16:42:24 +00:00
* Set a search engine keyword for use with * open or ` set searchengine `
*
2018-02-02 15:00:10 +00:00
* @deprecated use ` set searchurls.KEYWORD URL ` instead
*
2018-02-01 16:42:24 +00:00
* @param keyword the keyword to use for this search ( e . g . 'esa' )
* @param url the URL to interpolate the query into . If % s is found in
* the URL , the query is inserted there , else it is appended .
* If the insertion point is in the "query string" of the URL ,
* the query is percent - encoded , else it is verbatim .
2018-02-02 15:00:10 +00:00
* * /
2017-12-03 11:30:44 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function searchsetkeyword ( keyword : string , url : string ) {
2018-02-01 23:39:23 +00:00
config . set ( "searchurls" , keyword , forceURI ( url ) )
}
/ * * 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-07-21 18:29:19 +01:00
Use to set any string values found [ here ] ( / s t a t i c / d o c s / m o d u l e s / _ s r c _ c o n f i g _ . h t m l # d e f a u l t s )
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 ] ) {
get ( key )
return
2018-02-01 23:39:23 +00:00
}
2018-04-13 19:28:03 +01:00
const target = key . split ( "." )
2018-02-01 23:39:23 +00:00
// Special case conversions
// TODO: Should we do any special case shit here?
switch ( target [ 0 ] ) {
case "logging" :
const map = {
2018-04-13 19:28:03 +01:00
never : Logging . LEVEL . NEVER ,
error : Logging.LEVEL.ERROR ,
warning : Logging.LEVEL.WARNING ,
info : Logging.LEVEL.INFO ,
debug : Logging.LEVEL.DEBUG ,
2018-02-01 23:39:23 +00:00
}
let level = map [ values [ 0 ] . toLowerCase ( ) ]
if ( level === undefined ) throw "Bad log level!"
else config . set ( . . . target , level )
return
}
const currentValue = config . get ( . . . target )
if ( Array . isArray ( currentValue ) ) {
config . set ( . . . target , values )
2018-03-02 17:26:40 +00:00
} else if ( currentValue === undefined || typeof currentValue === "string" ) {
2018-04-13 19:28:03 +01:00
config . set ( . . . target , values . join ( " " ) )
2018-02-01 23:39:23 +00:00
} else {
throw "Unsupported setting type!"
}
2017-12-03 11:30:44 +00:00
}
2018-07-12 16:51:25 +01:00
/** @hidden */
//#background_helper
2018-07-18 14:36:35 +01:00
let AUCMDS = [ "DocStart" , "DocLoad" , "DocEnd" , "TriStart" , "TabEnter" , "TabLeft" ]
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-07-18 14:36:35 +01:00
@param event Curently , 'TriStart' , 'DocStart' , 'DocLoad' , 'DocEnd' , 'TabEnter' and 'TabLeft' 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-02 22:53:04 -07:00
/ * * R e m o v e a u t o c m d s
2018-07-18 14:36:35 +01:00
@param event Curently , 'TriStart' , 'DocStart' , 'DocLoad' , 'DocEnd' , 'TabEnter' and 'TabLeft' 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 .
*
* Simply creates a DocStart and TabEnter [ [ autocmd ] ] that runs ` mode ignore ` .
*
* 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 ` .
*
* <!-- this should probably be moved to an ex alias once configuration has better help - - !>
*
* /
//#background
export function blacklistadd ( url : string ) {
; [ "DocStart" , "TabEnter" ] . map ( e = > autocmd ( e , url , "mode ignore" ) )
}
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-04-13 19:28:03 +01:00
export async function unbind ( key : string ) {
2018-07-01 18:24:37 +02:00
// Convert key to internal representation
2018-07-01 20:43:44 +01:00
key = mapstrToKeyseq ( key )
. map ( k = > k . toMapstr ( ) )
. join ( "" )
2018-02-19 15:59:56 +00:00
config . set ( "nmaps" , key , "" )
2017-11-05 14:10:11 +00:00
}
2017-11-19 06:05:15 +00:00
/ * * R e s t o r e s a s e q u e n c e o f k e y s t o t h e i r d e f a u l t v a l u e .
See also :
- [ [ bind ] ]
- [ [ unbind ] ]
* /
2017-11-05 14:10:11 +00:00
//#background
2018-04-13 19:28:03 +01:00
export async function reset ( key : string ) {
config . unset ( "nmaps" , key )
2017-11-29 19:51:18 +00:00
// Code for dealing with legacy binds
2017-11-05 14:10:11 +00:00
let nmaps = ( await browser . storage . sync . get ( "nmaps" ) ) [ "nmaps" ]
2018-04-13 19:28:03 +01:00
nmaps = nmaps == undefined ? { } : nmaps
2017-11-05 14:10:11 +00:00
delete nmaps [ key ]
2018-04-13 19:28:03 +01:00
browser . storage . sync . set ( { nmaps } )
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 [ ] ) {
2017-12-02 00:20:32 +01:00
let flagpos = args . indexOf ( "-t" )
let since = { }
// If the -t flag has been given and there is an arg after it
if ( flagpos > - 1 ) {
if ( flagpos < args . length - 1 ) {
2018-04-13 19:28:03 +01:00
let 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
2017-12-02 00:20:32 +01:00
if ( match !== null && match . length == 3 ) {
// Compute the timespan in milliseconds and get a Date object
let millis = parseInt ( match [ 1 ] ) * 1000
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
}
}
let 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 ,
* /
}
if ( args . find ( x = > x == "all" ) !== undefined ) {
2018-04-13 19:28:03 +01:00
for ( let 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
2018-04-13 19:28:03 +01:00
args . map ( x = > {
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
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 ) {
let address = addressarr . length == 0 ? ( await activeTab ( ) ) . url : addressarr [ 0 ]
// 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 {
let compstring = addressarr . join ( " | tabopen " )
let compstringwin = addressarr . join ( " | winopen " )
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 ( "." )
2018-02-19 01:12:58 +00:00
const value = config . get ( . . . target )
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," +
JSON . stringify ( config . get ( key ) )
. 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()))
}
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-29 19:51:18 +00:00
// not required as we automatically save all config
////#background
//export function saveconfig(){
// config.save(config.get("storageloc"))
//}
2017-11-29 16:56:56 +00:00
2017-11-29 19:51:18 +00:00
////#background
//export function mktridactylrc(){
// saveconfig()
//}
2017-11-29 16:56:56 +00:00
2017-11-09 00:41:07 +00:00
// }}}
// {{{ HINTMODE
2018-07-21 13:02:50 +01:00
//#content_helper
import * as hinting from "./hinting"
2017-11-09 00:41:07 +00:00
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
- - b open in background
2018-07-21 16:44:11 +01:00
- - br repeatedly open in background
2017-11-28 02:12:07 +00:00
- - 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
2018-03-12 17:07:42 +00:00
- - w open in new window
- wp open in new private window
2018-07-21 16:44:11 +01:00
- ` -pipe selector key ` e . g , ` -pipe * href ` returns the key . Only makes sense with ` composite ` , e . g , ` composite hint -pipe * textContent | yank ` .
- * * DEPRECATED * * ` -W excmd... ` append hint href to excmd and execute , e . g , ` hint -W exclaim mpv ` to open YouTube videos . Use ` composite hint -pipe | [excmd] ` instead .
2017-11-29 20:13:40 +00:00
2017-12-24 11:35:39 +00: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 .
To open a hint in the background , the default bind is ` F ` .
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-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
export async function hint ( option? : string , selectors? : string , . . . rest : string [ ] ) {
2018-07-21 16:44:11 +01:00
// NB: if you want something to work with rapid hinting, make it return something
// Open in background
2018-07-21 17:03:44 +01:00
if ( option === "-b" ) {
2018-07-21 16:44:11 +01:00
let link = await hinting . pipe ( DOM . HINTTAGS_selectors )
link . focus ( )
2018-07-21 17:03:44 +01:00
if ( link . href ) {
2018-07-21 16:44:11 +01:00
let containerId = await activeTabContainerId ( )
if ( containerId ) {
2018-07-21 17:03:44 +01:00
openInNewTab ( link . href , {
2018-07-21 16:44:11 +01:00
active : false ,
related : true ,
cookieStoreId : containerId ,
2018-07-21 17:03:44 +01:00
} ) . catch ( ( ) = > DOM . simulateClick ( link ) )
2018-07-21 16:44:11 +01:00
} else {
2018-07-21 17:03:44 +01:00
openInNewTab ( link . href , {
2018-07-21 16:44:11 +01:00
active : false ,
related : true ,
2018-07-21 17:03:44 +01:00
} ) . catch ( ( ) = > DOM . simulateClick ( link ) )
2018-07-21 16:44:11 +01:00
}
} else {
2018-07-21 17:03:44 +01:00
DOM . simulateClick ( link )
2018-07-21 16:44:11 +01:00
}
return link . href
}
// Yank link
else if ( option === "-y" ) {
2018-07-21 17:03:44 +01:00
run_exstr ( "yank " + ( await hinting . pipe ( DOM . HINTTAGS_selectors ) ) [ "href" ] )
2018-07-21 16:44:11 +01:00
}
// Yank text content
else if ( option === "-p" ) {
2018-07-21 17:03:44 +01:00
run_exstr ( "yank " + ( await hinting . pipe_elements ( DOM . elementsWithText ( ) ) ) [ "textContent" ] )
2018-07-21 16:44:11 +01:00
}
// Yank link alt text
2018-07-21 17:03:44 +01:00
else if ( option === "-P" ) {
2018-07-21 16:44:11 +01:00
let link = await hinting . pipe_elements ( DOM . getElemsBySelector ( "[title], [alt]" , [ DOM . isVisible ] ) )
run_exstr ( "yank " + ( link . title ? link.title : link.alt ) )
}
// Yank anchor
2018-07-21 17:03:44 +01:00
else if ( option === "-#" ) {
2018-07-21 16:44:11 +01:00
let anchorUrl = new URL ( window . location . href )
let link = await hinting . pipe_elements ( DOM . anchors ( ) )
anchorUrl . hash = link . id || link . name
run_exstr ( "yank " + anchorUrl . href )
2018-07-21 17:03:44 +01:00
} else if ( option === "-c" ) DOM . simulateClick ( await hinting . pipe ( selectors ) )
2018-07-21 16:44:11 +01:00
// Deprecated: hint exstr
else if ( option === "-W" ) run_exstr ( selectors + " " + rest . join ( " " ) + " " + ( await hinting . pipe ( DOM . HINTTAGS_selectors ) ) )
else if ( option === "-pipe" ) return ( await hinting . pipe ( selectors ) ) [ rest . join ( " " ) ]
2018-07-21 17:03:44 +01:00
else if ( option === "-br" ) {
while ( true ) {
2018-07-21 16:44:11 +01:00
await hint ( "-b" )
}
}
// TODO: port these to new fangled way
2017-11-22 20:38:02 +00:00
else if ( option === "-i" ) hinting . hintImage ( false )
else if ( option === "-I" ) hinting . hintImage ( true )
2017-11-28 22:51:53 +00:00
else if ( option === "-k" ) hinting . hintKill ( )
2017-11-27 14:43:01 +00:00
else if ( option === "-s" ) hinting . hintSave ( "link" , false )
else if ( option === "-S" ) hinting . hintSave ( "img" , false )
else if ( option === "-a" ) hinting . hintSave ( "link" , true )
else if ( option === "-A" ) hinting . hintSave ( "img" , true )
2018-05-18 15:25:11 +01:00
else if ( option === "-;" ) hinting . hintFocus ( selectors )
2017-11-30 04:11:49 +00:00
else if ( option === "-r" ) hinting . hintRead ( )
2018-03-12 17:07:42 +00:00
else if ( option === "-w" ) hinting . hintPageWindow ( )
else if ( option === "-wp" ) hinting . hintPageWindowPrivate ( )
2018-07-21 16:44:11 +01:00
else DOM . simulateClick ( await hinting . pipe ( DOM . HINTTAGS_selectors ) )
}
// how 2 crash pc
////#content
//export async function rapid(...commands: string[]){
// while(true){
// await run_exstr(...commands)
// }
//}
/ * *
* Hacky ex string parser .
*
* Use it for fire - and - forget running of background commands in content .
*
* @hidden
* /
//#content_helper
2018-07-21 17:03:44 +01:00
export function run_exstr ( . . . commands : string [ ] ) {
2018-07-21 16:44:11 +01:00
Messaging . message ( "commandline_background" , "recvExStr" , commands )
2017-11-09 00:41:07 +00:00
}
// }}}
2017-11-19 13:45:18 +01:00
// {{{ GOBBLE mode
//#background_helper
2018-04-13 19:28:03 +01:00
import * as gobbleMode from "./parsers/gobblemode"
2017-11-19 13:45:18 +01:00
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 .
* /
2017-11-19 13:45:18 +01:00
//#background
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
2018-04-13 19:28:03 +01:00
import * as TTS from "./text_to_speech"
2017-11-30 04:11:49 +00:00
/ * *
* Read text content of elements matching the given selector
*
* @param selector the selector to match elements
* /
//#content_helper
function tssReadFromCss ( selector : string ) : void {
let elems = DOM . getElemsBySelector ( selector , [ ] )
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() {
let voices = TTS . listVoices ( )
// need a better way to show this to the user
2018-02-19 01:12:58 +00:00
fillcmdline_notrail ( "#" , voices . sort ( ) . 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 ) {
let ttsAction : TTS.Action = null
// convert user input to TTS.Action
// only pause seems to be working, so only provide access to that
// to avoid exposing users to things that won't work
switch ( action ) {
case "stop" :
ttsAction = "stop"
break
}
if ( ttsAction ) {
TTS . doAction ( ttsAction )
} else {
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
}
}
//}}}
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 [ ] ) {
2017-11-22 11:54:17 +00:00
url = url === undefined ? ( await activeTab ( ) ) . url : url
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 } )
2018-04-13 19:28:03 +01:00
dupbmarks . map ( bookmark = > browser . bookmarks . remove ( bookmark . id ) )
2018-04-16 21:12:25 +01:00
if ( dupbmarks . length != 0 ) return
const path = title . substring ( 0 , title . lastIndexOf ( "/" ) + 1 )
// TODO: if title is blank, get it from the page.
if ( path != "" ) {
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 )
let pathobj = validpaths . find ( p = > p . path == path )
// If strict look doesn't find it, be a bit gentler
if ( pathobj === undefined ) pathobj = validpaths . find ( p = > p . path . includes ( path ) )
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-16 17:31:33 +01:00
* Aliased to ` !js `
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
* /
//#content
export async function js ( . . . str : string [ ] ) {
2018-05-27 06:29:40 +02:00
if ( str [ 0 ] . startsWith ( "-p" ) ) {
let JS_ARG = str [ str . length - 1 ]
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 .
* /
//#background
export async function jsb ( . . . str : string [ ] ) {
2018-05-27 06:29:40 +02:00
if ( str [ 0 ] . startsWith ( "-p" ) ) {
let JS_ARG = str [ str . length - 1 ]
return eval ( str . slice ( 1 , - 1 ) . join ( " " ) )
} else {
return eval ( str . join ( " " ) )
}
2018-05-17 16:45:55 +01:00
}
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 = > {
2018-04-14 22:37:16 +01:00
if ( details . reason == "install" ) tutor ( "newtab" )
2018-04-26 12:32:10 +01:00
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