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-02-19 01:12:58 +00: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 / _ c o n f i g _ . h t m l # d e f a u l t s ) .
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 .
You do not need to worry about types or return values .
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 / _ 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 .
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
2017-12-02 23:06:12 +00:00
- Press ` I ` to enter ignore mode . ` Shift ` + ` Escape ` to return to normal
mode .
2017-11-19 06:05:15 +00:00
- Press ` f ` to start "hint mode" , ` F ` to open in background
- 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
2017-12-02 23:06:12 +00:00
- ` ]] ` and ` [[ ` to navigate through the pages of comics , paginated
articles , etc
- Pressing ` ZZ ` will close all tabs and windows , but it will only "save"
them if your about :preferences are set to " show your tabs and windows
from last time "
2017-11-19 06:05:15 +00:00
There are some caveats common to all webextension vimperator - alikes :
2017-12-02 23:06:12 +00:00
- Do not try to navigate to any about : \ * pages using ` :open ` as it will
fail silently
- Firefox will not load Tridactyl on addons . mozilla . org , about : \ * , some
file : \ * URIs , view - source : \ * , or data : \ * . On these pages Ctrl - L ( or F6 ) ,
Ctrl - Tab and Ctrl - W are your escape hatches
- Tridactyl does not currently support changing / hiding the Firefox GUI , but
you can do it yourself by changing your userChrome . There is an [ example
file ] ( 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
[ 3 ] : https : //www.mozilla.org/en-US/firefox/organizations/
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-04-13 19:28:03 +01:00
import { l } from "./lib/webext"
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"
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"
2017-10-12 04:02:01 +01:00
import * as SELF from "./excmds_content"
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-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"
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-13 19:28:03 +01:00
import { activeTab , activeTabId , firefoxVersionAtLeast , openInNewTab } from "./lib/webext"
import * as CommandLineBackground from "./commandline_background"
2017-10-12 04:02:01 +01:00
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
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
}
2017-10-12 04:02:01 +01:00
/** If maybeURI doesn't have a schema, affix http:// */
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
2017-11-20 01:11:38 +00:00
try {
return new URL ( maybeURI ) . href
} catch ( e ) {
2018-04-13 19:28:03 +01:00
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
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 ( )
2017-11-09 21:01:57 +00:00
}
2017-10-12 04:02:01 +01:00
//#content
export function scrollpx ( a : number , b : number ) {
2018-04-13 19:28:03 +01:00
let top = document . body . getClientRects ( ) [ 0 ] . top
window . scrollBy ( a , b )
if ( top == document . body . getClientRects ( ) [ 0 ] . top ) recursiveScroll ( a , b , [ document . body ] )
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
If one number is given , scroll to that percentage along a chosen axis ,
defaulting to the y - axis
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 )
2017-12-08 21:11:40 -08:00
if ( b === "y" ) {
2018-04-13 19:28:03 +01:00
window . scrollTo ( window . scrollX , a . clamp ( 0 , 100 ) * window . document . scrollingElement . scrollHeight / 100 )
} else if ( b === "x" ) {
window . scrollTo ( a . clamp ( 0 , 100 ) * window . document . scrollingElement . scrollWidth / 100 , window . scrollY )
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-04-11 23:49:36 +02:00
/ * * T r i e s t o f i n d a n o d e w h i c h c a n b e s c r o l l e d e i t h e r x p i x e l s t o t h e r i g h t o r
* y pixels down among the Elements in { nodes } and children of these Elements .
*
* This function used to be recursive but isn ' t anymore due to various
* attempts at optimizing the function in order to reduce GC pressure .
2018-04-13 19:28:03 +01:00
* /
2018-02-27 22:30:58 +01:00
//#content_helper
function recursiveScroll ( x : number , y : number , nodes : Element [ ] ) {
2018-04-11 23:49:36 +02:00
let index = 0
2018-02-27 22:30:58 +01:00
do {
2018-04-11 23:49:36 +02:00
let node = nodes [ index ++ ] as any
let rect = node . getClientRects ( ) [ 0 ]
// This check is quite arbitrary and even possibly wrong.
// We can't use DOM.isVisible because it breaks scrolling on some
// sites (e.g. twitch.com)
// We can't not check anything because it makes scrolling unbearably
// slow on some other sites, e.g.
// http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html
// and
// https://stripe.com/docs/api#intro
2018-04-12 00:02:19 +02:00
// This check speeds things up on the aforementioned website while
// still letting scrolling work on twitch/website with frames so we'll
2018-04-11 23:49:36 +02:00
// consider it good enough for now.
2018-04-12 00:02:19 +02:00
while ( ! rect || rect . top >= innerHeight - 4 ) {
2018-04-13 19:28:03 +01:00
node = nodes [ index ++ ]
2018-04-11 23:49:36 +02:00
// No node means we've reached the end of the array
2018-04-13 19:28:03 +01:00
if ( ! node ) return
2018-04-11 23:49:36 +02:00
rect = node . getClientRects ( ) [ 0 ]
}
let top = rect . top
let left = rect . left
2018-04-13 19:28:03 +01:00
node . scrollBy ( x , y )
2018-02-27 22:30:58 +01:00
rect = node . getClientRects ( ) [ 0 ]
2018-04-11 23:49:36 +02:00
// if the node moved, stop
2018-04-13 19:28:03 +01:00
if ( top != rect . top || left != rect . left ) return
2018-04-11 23:49:36 +02:00
nodes = nodes . concat ( Array . prototype . slice . call ( node . children ) )
2018-04-13 19:28:03 +01:00
if ( node . contentDocument ) nodes . push ( node . contentDocument . body )
2018-04-11 23:49:36 +02:00
} while ( index < nodes . length )
2018-02-27 22:30:58 +01:00
}
2017-10-12 04:02:01 +01:00
//#content
export function scrollline ( n = 1 ) {
2018-04-11 08:23:11 +02:00
let top = document . body . getClientRects ( ) [ 0 ] . top
2017-10-12 04:02:01 +01:00
window . scrollByLines ( n )
2018-04-11 08:23:11 +02:00
if ( top == document . body . getClientRects ( ) [ 0 ] . top ) {
2018-04-13 19:28:03 +01:00
const cssHeight = window . getComputedStyle ( document . body ) . getPropertyValue ( "line-height" )
2018-04-11 08:23:11 +02:00
// Remove the "px" at the end
const lineHeight = parseInt ( cssHeight . substr ( 0 , cssHeight . length - 2 ) )
// lineHeight probably can't be NaN but let's make sure
2018-04-13 19:28:03 +01:00
if ( lineHeight ) recursiveScroll ( 0 , lineHeight * n , [ window . document . body ] )
2018-04-11 08:23:11 +02:00
}
2017-10-12 04:02:01 +01:00
}
2018-02-27 22:30:58 +01:00
2017-10-12 04:02:01 +01:00
//#content
export function scrollpage ( n = 1 ) {
2018-02-27 22:30:58 +01:00
scrollpx ( 0 , window . innerHeight * n )
2017-10-12 04:02:01 +01:00
}
2018-01-28 21:18:29 +00:00
//#background_helper
2018-04-13 19:28:03 +01:00
import * as finding from "./finding_background"
2018-01-28 21:18:29 +00:00
2018-04-12 22:33:10 +01:00
/ * * S t a r t f i n d m o d e . W o r k i n p r o g r e s s .
*
* @param direction - the direction to search in : 1 is forwards , - 1 is backwards .
2018-04-13 19:28:03 +01:00
*
2018-04-12 22:33:10 +01:00
* /
2018-01-28 21:18:29 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function find ( direction? : number ) {
2018-03-18 15:08:09 +00:00
if ( direction === undefined ) direction = 1
finding . findPage ( direction )
}
2018-04-12 22:33:10 +01:00
/ * * H i g h l i g h t t h e n e x t o c c u r e n c e o f t h e p r e v i o u s l y s e a r c h e d f o r w o r d .
*
* @param number - number of words to advance down the page ( use 1 for next word , - 1 for previous )
2018-04-13 19:28:03 +01:00
*
2018-04-12 22:33:10 +01:00
* /
2018-03-18 15:08:09 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function findnext ( n : number ) {
2018-03-18 15:08:09 +00:00
finding . findPageNavigate ( n )
2017-10-12 04:02:01 +01:00
}
2017-11-03 19:10:12 +00:00
/** @hidden */
2017-10-12 04:02:01 +01:00
//#content_helper
function history ( n : number ) {
window . history . go ( n )
}
2017-11-19 06:05:15 +00:00
/** Navigate forward one page in history. */
2017-10-12 04:02:01 +01:00
//#content
export function forward ( n = 1 ) {
history ( n )
}
2017-11-19 06:05:15 +00:00
/** Navigate back one page in history. */
2017-10-12 04:02:01 +01:00
//#content
export function back ( n = 1 ) {
history ( n * - 1 )
}
/** Reload the next n tabs, starting with activeTab, possibly bypassingCache */
//#background
export async function reload ( n = 1 , hard = false ) {
2017-11-09 00:41:07 +00:00
let tabstoreload = await getnexttabs ( await activeTabId ( ) , n )
2018-04-13 19:28:03 +01:00
let reloadProperties = { bypassCache : hard }
2017-10-12 04:02:01 +01:00
tabstoreload . map ( n = > browser . tabs . reload ( n , reloadProperties ) )
}
2017-11-22 16:59:58 +00:00
/** Reloads all tabs, bypassing the cache if hard is set to true */
//#background
2018-04-13 19:28:03 +01:00
export async function reloadall ( hard = false ) {
let tabs = await browser . tabs . query ( { currentWindow : true } )
let reloadprops = { bypassCache : hard }
2017-11-22 16:59:58 +00:00
tabs . map ( tab = > browser . tabs . reload ( tab . id , reloadprops ) )
}
2017-10-12 04:02:01 +01:00
/** Reload the next n tabs, starting with activeTab. bypass cache for all */
//#background
export async function reloadhard ( n = 1 ) {
reload ( n , true )
}
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 .
@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
2017-11-29 20:13:40 +00:00
Related settings :
"searchengine" : "google" or any of [ [ SEARCH_URLS ] ]
2017-11-19 06:05:15 +00:00
* /
2017-10-12 04:02:01 +01:00
//#content
2017-10-22 16:33:43 +01:00
export function open ( . . . urlarr : string [ ] ) {
2017-11-09 15:30:09 +00:00
let url = urlarr . join ( " " )
2018-04-13 19:28:03 +01:00
if ( url === "" ) url = config . get ( "newtab" ) || browser . extension . getURL ( "static/newtab.html" )
2017-10-12 04:02:01 +01:00
window . location . href = 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-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 )
return pre
} )
} else {
sourceElement . parentNode . removeChild ( sourceElement )
sourceElement = undefined
}
2018-03-08 19:01:55 +01:00
}
2017-11-30 13:50:10 +00:00
/ * * G o t o y o u r h o m e p a g e ( s )
2017-12-08 21:11:40 -08:00
2017-11-30 13:50:10 +00:00
@param all
- if "true" , opens all homepages in new tabs
- if "false" or not given , opens the last homepage in the current tab
* /
//#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 .
` :help <excmd> ` jumps to the entry for that command .
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 ) {
const docpage = browser . extension . getURL ( "static/docs/modules/_excmds_.html#" )
if ( excmd === undefined ) excmd = "tridactyl-help-page"
if ( ( await activeTab ( ) ) . url . startsWith ( docpage ) ) {
open ( docpage + excmd )
} else {
tabopen ( docpage + excmd )
}
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
}
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 ) {
window . location . href = newUrl
}
}
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
}
if ( newUrl && newUrl !== oldUrl ) {
window . location . href = newUrl
}
}
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
loadaucmds ( )
2018-04-12 22:33:10 +01:00
/** @hidden */
2018-01-31 19:51:08 +00:00
//#content
2018-04-13 19:28:03 +01:00
export async function loadaucmds() {
2018-01-31 19:51:08 +00:00
// for some reason, this never changes from the default, even when there is user config (e.g. set via `aucmd bbc.co.uk mode ignore`)
2018-02-02 14:12:47 +00:00
let aucmds = await config . getAsync ( "autocmds" , "DocStart" )
2018-01-31 19:51:08 +00:00
const ausites = Object . keys ( aucmds )
// yes, this is lazy
2018-04-13 19:28:03 +01:00
const aukey = ausites . find ( e = > window . document . location . href . includes ( e ) )
if ( aukey !== undefined ) {
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' ]
`
/ * * D O M r e f e r e n c e t o t h e l a s t u s e d I n p u t f i e l d
2018-04-12 22:33:10 +01:00
* @hidden
2017-11-23 23:57:04 +00:00
* /
2017-11-26 12:18:06 +00:00
//#content_helper
let LAST_USED_INPUT : HTMLElement = null
2017-11-23 23:57:04 +00:00
/ * * 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)
2017-12-05 22:07:23 +00:00
if ( LAST_USED_INPUT ) {
inputToFocus = LAST_USED_INPUT
} 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 ) {
let index = inputs . indexOf ( LAST_USED_INPUT )
2017-12-05 22:07:23 +00:00
if ( LAST_USED_INPUT ) {
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-04-13 19:28:03 +01:00
if ( ! 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
}
2017-11-26 12:18:06 +00:00
// Store the last focused element
//#content_helper
2018-04-13 19:28:03 +01:00
document . addEventListener ( "focusin" , e = > {
if ( DOM . isTextEditable ( e . target as HTMLElement ) ) LAST_USED_INPUT = e . target as HTMLElement
} )
2017-11-26 12:18:06 +00:00
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 ] `
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-03-12 22:37:37 +00:00
the author ' s preference .
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 ` .
Hinting is controlled by ` relatedopenlast `
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-03-12 22:37:37 +00:00
let url : string
2018-04-13 19:28:03 +01:00
let address = addressarr . join ( " " )
2018-03-12 22:37:37 +00:00
if ( address != "" ) url = forceURI ( address )
else url = forceURI ( config . get ( "newtab" ) )
openInNewTab ( url )
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-04-11 18:19:29 +01:00
/ * * T o g g l e f u l l s c r e e n s t a t e
* /
//#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
}
}
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-12-06 05:37:18 -05:00
/** Synonym for [[tabclose]]. */
//#background
export async function quit() {
tabclose ( )
}
/** Convenience shortcut for [[quit]]. */
//#background
export async function q() {
tabclose ( )
}
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 = { }
if ( args [ 0 ] === "-private" ) {
createData [ "incognito" ] = true
2018-04-13 19:28:03 +01:00
address = args . slice ( 1 , args . length ) . join ( " " )
} else address = args . join ( " " )
2017-11-30 11:40:01 +00:00
createData [ "url" ] = address != "" ? forceURI ( address ) : forceURI ( config . get ( "newtab" ) )
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-12-06 05:37:18 -05:00
/** Convenience shortcut for [[qall]]. */
//#background
export async function qa() {
qall ( )
}
2017-10-12 04:02:01 +01:00
// }}}
// {{{ MISC
2018-04-12 22:33:10 +01:00
/ * * D e p r e c a t e d
* @hidden
* /
2017-10-23 09:42:50 +01:00
//#background
export function suppress ( preventDefault? : boolean , stopPropagation? : boolean ) {
2017-11-19 03:22:59 +00:00
mode ( "ignore" )
2017-10-23 09:42:50 +01:00
}
2017-11-26 00:06:02 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function version() {
2017-11-28 17:23:16 +00:00
fillcmdline_notrail ( "REPLACE_ME_WITH_THE_VERSION_USING_SED" )
2017-11-26 00:06:02 +00:00
}
2017-11-19 06:05:15 +00:00
/ * * E x a m p l e :
- ` mode ignore ` to ignore all keys .
* /
2017-10-12 04:02:01 +01:00
//#background
2017-11-09 00:41:07 +00:00
export function mode ( mode : ModeName ) {
2017-11-19 06:05:15 +00:00
// TODO: event emition on mode change.
2017-11-19 07:57:30 +00:00
if ( mode === "hint" ) {
2017-11-19 06:05:15 +00:00
hint ( )
2018-01-28 21:18:29 +00:00
} else if ( mode === "find" ) {
find ( )
2017-11-19 06:05:15 +00:00
} else {
state . mode = mode
}
2017-10-12 04:02:01 +01:00
}
2018-04-14 22:14:16 +01:00
/** @hidden */
2017-11-19 06:05:15 +00:00
//#background_helper
async function getnexttabs ( tabid : number , n? : number ) {
2017-10-12 04:02:01 +01:00
const curIndex : number = ( await browser . tabs . get ( tabid ) ) . index
const tabs : browser.tabs.Tab [ ] = await browser . tabs . query ( {
currentWindow : true ,
} )
const indexFilter = ( ( tab : browser.tabs.Tab ) = > {
2018-04-13 19:28:03 +01:00
return curIndex <= tab . index && ( n ? tab . index < curIndex + Number ( n ) : true )
2017-10-12 04:02:01 +01:00
} ) . bind ( n )
return tabs . filter ( indexFilter ) . map ( ( tab : browser.tabs.Tab ) = > {
return tab . id
} )
}
// Moderately slow; should load in results as they arrive, perhaps
// Todo: allow jumping to buffers once they are found
// Consider adding to buffers with incremental search
// maybe only if no other results in URL etc?
// Find out how to return context of each result
//#background
/* export async function findintabs(query: string) { */
/* const tabs = await browser.tabs.query({currentWindow: true}) */
/* console.log(query) */
/* const findintab = async tab => */
/* await browser.find.find(query, {tabId: tab.id}) */
/* let results = [] */
/* for (let tab of tabs) { */
/* let result = await findintab(tab) */
/* if (result.count > 0) { */
/* results.push({tab, result}) */
/* } */
/* } */
/* results.sort(r => r.result.count) */
/* console.log(results) */
/* return results */
/* } */
// }}}
// {{{ CMDLINE
2017-11-18 13:47:10 +00:00
//#background_helper
2018-04-13 19:28:03 +01:00
import * as controller from "./controller"
2017-11-18 13:47:10 +00:00
2017-12-02 12:08:30 +01:00
/ * * R e p e a t s a ` c m d ` ` n ` t i m e s .
Falls back to the last executed command if ` cmd ` doesn ' t exist .
Executes the command once if ` n ` isn ' t defined either .
* /
//#background
2017-12-04 06:16:29 +01:00
export function repeat ( n = 1 , . . . exstr : string [ ] ) {
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-04-12 22:33:10 +01:00
/ * * S p l i t ` c m d s ` o n p i p e s ( | ) a n d t r e a t e a c h a s i t s o w n c o m m a n d .
2017-11-18 13:47:10 +00:00
2017-11-19 06:05:15 +00: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 .
2017-11-18 13:47:10 +00:00
* /
//#background
export function composite ( . . . cmds : string [ ] ) {
cmds = cmds . join ( " " ) . split ( "|" )
cmds . forEach ( controller . acceptExCmd )
}
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 ] )
}
2017-11-19 06:05:15 +00:00
/ * * E q u i v a l e n t t o ` f i l l c m d l i n e _ n o t r a i l < y o u r a r g s > < c u r r e n t U R L > `
See also [ [ fillcmdline_notrail ] ]
* /
2017-11-09 15:30:09 +00:00
//#background
2018-04-13 19:28:03 +01:00
export async function current_url ( . . . strarr : string [ ] ) {
2017-11-09 15:30:09 +00:00
fillcmdline_notrail ( . . . strarr , ( 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-10-12 04:02:01 +01:00
// {{{ Buffer/completion stuff
2017-11-28 00:20:23 +00:00
/ * * E q u i v a l e n t t o ` f i l l c m d l i n e b u f f e r `
Sort of Vimperator alias
* /
2017-10-12 04:02:01 +01:00
//#background
2017-11-15 13:41:04 -08:00
export async function tabs() {
2017-10-12 04:02:01 +01:00
fillcmdline ( "buffer" )
2017-11-15 13:41:04 -08:00
}
2017-11-28 00:20:23 +00:00
/ * * E q u i v a l e n t t o ` f i l l c m d l i n e b u f f e r `
Sort of Vimperator alias
* /
2017-11-15 13:41:04 -08:00
//#background
export async function buffers() {
tabs ( )
2017-10-12 04:02: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
Starts at 1 . 0 refers to last tab , - 1 to penultimate tab , etc .
"#" means the tab that was last accessed in this 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
}
// }}}
// }}}
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 `
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 .
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 / _ c o n f i g _ . h t m l # d e f a u l t s )
e . g .
set searchurls . google https : //www.google.com/search?q=
set logging . messaging info
* /
//#background
2018-02-02 13:06:30 +00:00
export function set ( key : string , . . . values : string [ ] ) {
2018-04-13 19:28:03 +01:00
if ( ! key || ! values [ 0 ] ) {
2018-02-01 23:39:23 +00:00
throw "Both key and value must be provided!"
}
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 .
@param event Curently , only 'DocStart' is supported .
@param url The URL on which the events will trigger ( currently just uses "contains" )
@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-04-13 19:28:03 +01:00
if ( ! [ "DocStart" ] . 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
}
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-12 22:33:10 +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
}
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
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
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 :
"hintchars" : "hjklasdfgyuiopqwertnmzxcvb"
2017-12-28 14:47:39 -05:00
"hintfiltermode" : "simple" | "vimperator" | "vimperator-reflow"
2018-03-12 22:37:37 +00:00
"relatedopenpos" : "related" | "next" | "last"
2017-11-28 02:12:07 +00:00
* /
2017-11-09 00:41:07 +00:00
//#background
2018-04-13 19:28:03 +01:00
export function hint ( option? : string , selectors = "" ) {
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 ( )
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 )
2017-11-24 13:01:44 +00:00
else if ( option === "-;" ) hinting . hintFocus ( )
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 ( )
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
2017-11-22 12:18:41 +00:00
/ * * A d d o r r e m o v e a b o o k m a r k .
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 .
*
* If a bookmark already exists for the URL , it is removed .
* /
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-13 19:28:03 +01:00
let dupbmarks = await browser . bookmarks . search ( { url } )
dupbmarks . map ( bookmark = > browser . bookmarks . remove ( bookmark . id ) )
if ( dupbmarks . length == 0 ) {
browser . bookmarks . create ( { url , title } )
}
2017-11-22 11:54:17 +00:00
}
2017-11-05 14:10:11 +00: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-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