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-05-23 20:29:45 +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-05-23 20:29:45 +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-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-05-08 12:27:30 +00:00
import { l , browserBg , activeTabId , activeTabContainerId } 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-04-13 19:28:03 +01:00
import { 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-04-19 16:35:10 +01:00
import { activeTab , firefoxVersionAtLeast , openInNewTab } 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"
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-04-28 14:19:07 +01:00
* You ' re probably better off using the default insert mode bind of < C - 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-09 22:22:47 +02: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-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-05-23 20:29:45 +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-04-21 23:43:12 +01:00
* /
//#background
export async function guiset ( rule : string , option : string ) {
// Could potentially fall back to sending minimal example to clipboard if native not installed
2018-04-30 15:57:23 +01:00
// Check for native messenger and make sure we have a plausible profile directory
2018-05-09 22:22:47 +02:00
if ( ! await Native . nativegate ( "0.1.1" ) ) return
2018-04-22 19:59:41 +01:00
let profile_dir = ""
2018-05-09 06:20:25 +02:00
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" )
}
2018-04-30 15:57:23 +01:00
if ( profile_dir == "" ) {
2018-05-08 13:09:53 +02:00
fillcmdline ( "Please set your profile directory (found on about:support) via `set profiledir [profile directory]`" )
2018-04-30 15:57:23 +01:00
return
}
// Make backups
2018-04-28 00:08:39 +01:00
await Native . mkdir ( profile_dir + "/chrome" , true )
let cssstr = ( await Native . read ( profile_dir + "/chrome/userChrome.css" ) ) . content
2018-04-30 15:57:23 +01:00
let cssstrOrig = ( await Native . read ( profile_dir + "/chrome/userChrome.orig.css" ) ) . content
if ( cssstrOrig === "" ) await Native . write ( profile_dir + "/chrome/userChrome.orig.css" , cssstr )
2018-04-21 23:43:12 +01:00
await Native . write ( profile_dir + "/chrome/userChrome.css.tri.bak" , cssstr )
2018-04-30 15:57:23 +01:00
// Modify and write new CSS
2018-04-28 00:08:39 +01:00
if ( cssstr === "" ) cssstr = ` @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); `
2018-04-21 23:43:12 +01:00
let stylesheet = CSS . parse ( cssstr )
2018-05-09 12:48:51 +01:00
// Trim due to https://github.com/reworkcss/css/issues/114
let stylesheetDone = CSS . stringify ( css_util . changeCss ( rule , option , stylesheet ) ) . trim ( )
2018-04-21 23:43:12 +01:00
Native . write ( profile_dir + "/chrome/userChrome.css" , stylesheetDone )
}
/** @hidden */
//#background
export function cssparse ( . . . css : string [ ] ) {
console . log ( CSS . parse ( css . join ( " " ) ) )
}
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() {
await Native . writePref ( "privacy.resistFingerprinting.block_mozAddonManager" , true )
await Native . writePref ( "extensions.webextensions.restrictedDomains" , "" )
}
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-05-16 07:53:48 +10:00
const installstr = await config . get ( "win_powershell_nativeinstallcmd" )
2018-05-14 14:21:07 +10:00
await clipboard ( "yank" , installstr )
2018-05-16 07:53:48 +10:00
fillcmdline ( "# Installation command copied to clipboard. Please paste and run it in Windows Powershell to install the native messenger." )
2018-05-14 14:21:07 +10:00
} else {
const installstr = await config . get ( "nativeinstallcmd" )
await clipboard ( "yank" , installstr )
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-11 21:33:10 +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-05-16 07:53:48 +10:00
await Native . run ( await config . get ( "win_cmdexe_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-04-13 19:28:03 +01:00
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
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-05-02 19:34:13 +02:00
let elem = window . document . scrollingElement || window . document . body
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 .
*
* The height of a line is defined by the site 's CSS. If Tridactyl can' t get it , it ' ll default to 22 pixels .
* /
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 ) {
// Get line height
const cssHeight = window . getComputedStyle ( document . body ) . getPropertyValue ( "line-height" )
// Remove the "px" at the end
lineHeight = parseInt ( cssHeight . substr ( 0 , cssHeight . length - 2 ) )
// Is there a better way to compute a fallback? Maybe fetch from about:preferences?
if ( ! lineHeight ) lineHeight = 22
}
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-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-06-23 17:36:49 +02:00
export function find ( str : string ) {
console . log ( "find " , str )
2018-03-18 15:08:09 +00:00
}
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-06-23 17:36:49 +02:00
console . log ( "findnext " , 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-04-26 09:10:25 +01:00
if ( [ "about:blank" ] . includes ( url ) ) {
url = url || undefined
2018-04-19 16:35:10 +01:00
browserBg . tabs . update ( await activeTabId ( ) , { url } )
2018-04-26 18:24:34 +01:00
// Open URLs that firefox won't let us by running `firefox <URL>` on the command line
2018-05-04 23:05:00 +01:00
} else if ( ! ABOUT_WHITELIST . includes ( url ) && url . match ( /^(about|file):.*/ ) ) {
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-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-06-07 12:52:11 +02:00
` :help something ` jumps to the entry for something . Something can be an excmd , an alias for an excmd or a binding .
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-05-19 09:36:55 -04: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() {
if ( await l ( 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-06-01 08:39:43 +02:00
loadaucmds ( "DocStart" )
2018-01-31 19:51:08 +00:00
2018-06-02 05:35:48 +02:00
//#content_helper
window . addEventListener ( "pagehide" , ( ) = > loadaucmds ( "DocEnd" ) )
2018-04-12 22:33:10 +01:00
/** @hidden */
2018-01-31 19:51:08 +00:00
//#content
2018-06-02 05:35:48 +02:00
export async function loadaucmds ( cmdType : "DocStart" | "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 ) {
2017-11-28 00:01:41 +00:00
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 ) {
2017-11-28 00:01:41 +00:00
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-04-13 19:28:03 +01:00
index = ( index - 1 ) . mod ( ( await l ( browser . tabs . query ( { currentWindow : true } ) ) ) . length ) + 1
2017-11-28 00:01:41 +00:00
// Return id of tab with that index.
2018-04-13 19:28:03 +01:00
return ( await l (
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 .
Known bug : This supports relative movement , but autocomple doesn ' t know
that yet and will override positive and negative indexes .
Put a space in front of tabmove if you want to disable completion and have
the relative indexes at the command line .
Binds are unaffected .
@param index
New index for the current tab .
1 is the first index . 0 is the last index . - 1 is the penultimate , etc .
* /
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
}
// }}}
// {{{ 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
* /
2018-05-12 17:13:52 +00: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 )
}
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 `
2018-06-14 15:25:38 +00: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 ( )
} 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 [ ] ) {
let cmd = state . 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
2017-12-24 14:16:40 +01:00
function showcmdline() {
2017-11-22 18:05:54 +00:00
CommandLineBackground . show ( )
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 *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-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
}
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 .
2017-11-19 06:05:15 +00:00
Unfortunately , javascript can only give us the ` clipboard ` clipboard , not e . g . the X selection clipboard .
* /
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 = [ ]
2017-10-28 19:20:31 +08:00
switch ( excmd ) {
2018-04-13 19:28:03 +01:00
case "yankshort" :
2017-11-30 18:04:16 +01:00
urls = await geturlsforlinks ( "rel" , "shortlink" )
if ( urls . length == 0 ) {
urls = await geturlsforlinks ( "rev" , "canonical" )
}
2017-11-27 19:48:49 +01:00
if ( urls . length > 0 ) {
messageActiveTab ( "commandline_frame" , "setClipboard" , [ urls [ 0 ] ] )
break
}
2018-04-13 19:28:03 +01:00
case "yankcanon" :
2017-11-30 18:04:16 +01:00
urls = await geturlsforlinks ( "rel" , "canonical" )
2017-11-27 19:48:49 +01:00
if ( urls . length > 0 ) {
messageActiveTab ( "commandline_frame" , "setClipboard" , [ urls [ 0 ] ] )
break
}
2018-04-13 19:28:03 +01:00
case "yank" :
2017-10-28 13:42:54 +01:00
await messageActiveTab ( "commandline_content" , "focus" )
2018-04-13 19:28:03 +01:00
content = content == "" ? ( await activeTab ( ) ) . url : content
2017-11-04 17:30:34 +00:00
messageActiveTab ( "commandline_frame" , "setClipboard" , [ content ] )
2017-10-28 19:20:31 +08:00
break
2018-04-13 19:28:03 +01:00
case "yanktitle" :
2018-04-11 19:05:30 +01:00
messageActiveTab ( "commandline_frame" , "setClipboard" , [ content ] )
break
2018-04-13 19:28:03 +01:00
case "yankmd" :
2018-04-11 19:05:30 +01:00
content = "[" + ( await activeTab ( ) ) . title + "](" + ( await activeTab ( ) ) . url + ")"
messageActiveTab ( "commandline_frame" , "setClipboard" , [ content ] )
2017-10-28 19:20:31 +08:00
break
2018-04-13 19:28:03 +01:00
case "open" :
2017-10-28 13:42:54 +01:00
await messageActiveTab ( "commandline_content" , "focus" )
2017-11-16 19:58:33 +00:00
url = await messageActiveTab ( "commandline_frame" , "getClipboard" )
url && open ( url )
break
2018-04-13 19:28:03 +01:00
case "tabopen" :
2017-11-16 19:58:33 +00:00
await messageActiveTab ( "commandline_content" , "focus" )
url = await messageActiveTab ( "commandline_frame" , "getClipboard" )
url && tabopen ( url )
2017-10-28 19:20:31 +08:00
break
default :
// todo: maybe we should have some common error and error handler
throw new Error ( ` [clipboard] unknown excmd: ${ excmd } ` )
2017-10-19 20:15:01 +01:00
}
2017-11-22 18:05:54 +00:00
CommandLineBackground . hide ( )
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-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-05-23 20:29:45 +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-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-06-02 05:35:48 +02:00
@param event Curently , 'TriStart' , 'DocStart' , '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-06-02 05:35:48 +02:00
if ( ! [ "DocStart" , "DocEnd" , "TriStart" , "TabEnter" , "TabLeft" ] . 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-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-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-04-13 19:28:03 +01:00
if ( dts . tridactyllocal === true ) browser . storage . local . clear ( )
2017-12-02 00:20:32 +01:00
delete dts . tridactyllocal
2018-04-13 19:28:03 +01:00
if ( dts . tridactylsync === true ) 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
//#background_helper
2018-04-13 19:28:03 +01:00
import * as hinting from "./hinting_background"
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
- - 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-04-20 19:47:18 +01:00
- ` -W excmd... ` append hint href to excmd and execute , e . g , ` hint -W exclaim mpv ` to open YouTube videos
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
* /
2017-11-09 00:41:07 +00:00
//#background
2018-05-18 15:25:11 +01:00
export function hint ( option? : string , selectors? : string , . . . rest : string [ ] ) {
2018-04-13 19:28:03 +01:00
if ( option === "-b" ) hinting . hintPageOpenInBackground ( )
2017-11-22 19:09:52 +00:00
else if ( option === "-y" ) hinting . hintPageYank ( )
2017-11-22 20:47:35 +00:00
else if ( option === "-p" ) hinting . hintPageTextYank ( )
2018-05-18 18:15:13 +03:00
else if ( option === "-P" ) hinting . hintPageTitleAltTextYank ( )
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-28 22:51:53 +00:00
else if ( option === "-#" ) hinting . hintPageAnchorYank ( )
2017-11-29 13:23:30 +00:00
else if ( option === "-c" ) hinting . hintPageSimple ( 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 ( )
2018-04-20 19:32:09 +01:00
else if ( option === "-W" ) hinting . hintPageExStr ( [ selectors , . . . rest ] . join ( " " ) )
2018-03-12 17:07:42 +00:00
else if ( option === "-wp" ) hinting . hintPageWindowPrivate ( )
2017-11-18 01:51:46 +00:00
else hinting . hintPageSimple ( )
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