mirror of
https://github.com/tridactyl/tridactyl.git
synced 2026-09-10 07:16:33 -04:00
Merge branch 'master' of github.com:cmcaine/tridactyl into glacambre-add_prettier
This commit is contained in:
commit
ebde6d63e4
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,3 +5,4 @@ src/excmds_content.ts
|
|||
generated
|
||||
web-ext-artifacts
|
||||
yarn.lock
|
||||
.vscode/
|
||||
46
doc/amo.md
46
doc/amo.md
|
|
@ -29,4 +29,48 @@ lost sometimes `:help` might help you a lot.
|
|||
This add-on is very usable, but is in an early stage of development. We intend
|
||||
to implement the majority of Vimperator's features.
|
||||
|
||||
We recommend that you use the beta versions below.
|
||||
You can get beta builds from [our website][betas].
|
||||
|
||||
**Permissions:**
|
||||
|
||||
Since Tridactyl aims to provide all the features Vimperator and Pentadactyl
|
||||
had, it requires quite a few permissions. Here we describe the specific
|
||||
permissions and why we need them.
|
||||
|
||||
- Access your data for all websites:
|
||||
* This is Mozilla's way of saying that Tridactyl can read the content of web
|
||||
pages. This is necessary in order to e.g. find the links you can follow
|
||||
with the `:hint` command (bound to `f` by default).
|
||||
- Read and modify bookmarks:
|
||||
* Tridactyl's command line has a powerful autocompletion mechanism. In
|
||||
order to be able to autocomplete your bookmarks, Tridactyl needs to read
|
||||
them.
|
||||
- Clear recent browsing history, cookies, and related data:
|
||||
* Tridactyl implements the `:sanitise` command Vimperator and Pentadactyl
|
||||
had. It works a bit like the "Clear All History" dialog you can access by
|
||||
pressing `Ctrl+Shift+Del` on default Firefox.
|
||||
- Get data from the clipboard:
|
||||
* If your clipboard contains a URL, pressing `p` will make Tridactyl follow
|
||||
this URL in the current tab.
|
||||
- Input data to the clipboard:
|
||||
* Tridactyl lets you copy various elements to the clipboard such as a page's
|
||||
URL with `yy`, a link's URL with `;y` or the content of an HTML element
|
||||
with `;p`.
|
||||
- Download files and read and modify the browser's download history:
|
||||
* By pressing `;s`, `;S`, `;a` and `;A` you can save documents and pictures
|
||||
from a page to your download folder.
|
||||
- Access browsing history:
|
||||
* The URLs of websites you've visited previously can be suggested as
|
||||
arguments for `:tabopen` and similar commands.
|
||||
- Access recently closed tabs:
|
||||
* If you've accidentally closed a tab or window, Tridactyl will let you open
|
||||
it again with the `:undo` command which is bound to `u` by default.
|
||||
- Access browser tabs:
|
||||
* Tridactyl provides a quick tab-switching menu/command with the `:buffer`
|
||||
command (bound to `b`). This permission is also required to close, move,
|
||||
and pin tabs, amongst other things.
|
||||
- Access browser activity during navigation:
|
||||
* This is needed for Tridactyl to be able to go back to normal mode every
|
||||
time you open a new page. In the future we may use it for autocommands.
|
||||
|
||||
[betas]: https://tridactyl.cmcaine.co.uk/betas/?sort=time&order=desc
|
||||
|
|
|
|||
|
|
@ -1,5 +1,37 @@
|
|||
# Tridactyl changelogs
|
||||
|
||||
## Release 1.8.2
|
||||
|
||||
- Improve config API
|
||||
- `set key.subkey.subsubkey value` now works
|
||||
- Add user feedback to `bind` and `get`
|
||||
- Add save link/img hint submode (;s, ;S, ;a, ;A) (#148)
|
||||
- Add `autocmd [event] [filter] [ex command]`
|
||||
- Currently, only supports the event `DocStart`
|
||||
- Most useful for entering ignore mode on certain websites: `autocmd DocStart mail.google.com mode ignore`
|
||||
- Add exmode aliases with `command [alias] [ex_command]`. Many aliases have been ported from Pentadactyl. (#236)
|
||||
- Add urlmodify command (#286, #298)
|
||||
- Support Emacs-style C-(a|e|k|u) in cmdline (#277)
|
||||
- Support changing followpage pattern used in `]]` and `[[` to allow use with foreign languages
|
||||
- Add logging levels and make logging less verbose by default (#206)
|
||||
- Support %s magic string for search providers (#253)
|
||||
- Add hintfiltermode config and new "vimperator, vimperator-reflow" hinting modes
|
||||
- Make hintPage follow link if there's only 1 option
|
||||
- Fix high resource usage when typing under some circumstances (#311)
|
||||
- `set newtab foo.bar` now changes all new tab pages (#235)
|
||||
- Fix hints on some sites via cleanslate.css (#220)
|
||||
- Fix new config system (#321)
|
||||
- followpage now falls back to urlincrement
|
||||
- `tabopen` now opens tabs to the right of the curent tab
|
||||
- Fix floating commandline iframe on some sites (#289)
|
||||
- Enter insert mode on drop down menus (#281)
|
||||
- Support hinting on some dodgy old websites (#287)
|
||||
- Make :reloadall only refresh current window tabs (#288)
|
||||
- Remove `xx` binding (#262)
|
||||
- Fix gu in directories (#256)
|
||||
- Fix various typos (#247, #228)
|
||||
- Add FAQ and other updates to readme.md (#232)
|
||||
|
||||
## Release 1.7.3
|
||||
|
||||
- Hint tags are much better:
|
||||
|
|
|
|||
7
package-lock.json
generated
7
package-lock.json
generated
|
|
@ -6022,6 +6022,11 @@
|
|||
"object-visit": "1.0.1"
|
||||
}
|
||||
},
|
||||
"mark.js": {
|
||||
"version": "8.11.1",
|
||||
"resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz",
|
||||
"integrity": "sha1-GA8fnr74sOY45BZq1S24eb6y/8U="
|
||||
},
|
||||
"marked": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz",
|
||||
|
|
@ -9513,7 +9518,7 @@
|
|||
}
|
||||
},
|
||||
"web-ext-types": {
|
||||
"version": "github:kelseasy/web-ext-types#417d6ddcd76d8a05d7e2222aec3544e01637785b",
|
||||
"version": "github:kelseasy/web-ext-types#daae1085685b97efdb98c59668cbdfd91c5c874c",
|
||||
"dev": true
|
||||
},
|
||||
"webidl-conversions": {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
"version": "0.1.0",
|
||||
"description": "Vimperator/Pentadactyl successor",
|
||||
"dependencies": {
|
||||
"fuse.js": "^3.2.0"
|
||||
"fuse.js": "^3.2.0",
|
||||
"mark.js": "^8.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^21.1.4",
|
||||
|
|
|
|||
17
readme.md
17
readme.md
|
|
@ -6,7 +6,7 @@ Replace Firefox's default control mechanism with one modelled on the one true ed
|
|||
|
||||
## Installing
|
||||
|
||||
[Get our "beta" builds!][amo-betas] These are updated on AMO with each commit to master on this repo; your browser will automatically update from there once a day. If you want more frequent updates, you can change `extensions.update.interval` in `about:config` to whatever time you want, say, 15 minutes (900 seconds). Changelogs for the stable versions on the AMO can be found [here](https://github.com/cmcaine/tridactyl/blob/master/doc/changelog.md).
|
||||
[Get our "beta" builds!][betas] These are updated with each commit to master on this repo. Your browser will automatically update from there once a day. If you want more frequent updates, you can change `extensions.update.interval` in `about:config` to whatever time you want, say, 15 minutes (900 seconds). Changelogs for the stable versions on the AMO can be found [here](https://github.com/cmcaine/tridactyl/blob/master/doc/changelog.md).
|
||||
|
||||
Type `:help` for online help once you're in :)
|
||||
|
||||
|
|
@ -120,6 +120,9 @@ NOTE: key modifiers (eg: control, alt) are not supported yet. See the FAQ below.
|
|||
|
||||
## Frequently asked questions
|
||||
|
||||
- Why doesn't Tridactyl respect my search engine settings?
|
||||
|
||||
It's a webextension limitation. Firefox doesn't allow reading user preferences.
|
||||
|
||||
- How can I change the search engine?
|
||||
|
||||
|
|
@ -135,6 +138,10 @@ NOTE: key modifiers (eg: control, alt) are not supported yet. See the FAQ below.
|
|||
|
||||
You can edit this file to your heart's content. A more traditional rc file is planned but will require a native messenger. For more information, see [issue #79](https://github.com/cmcaine/tridactyl/issues/79).
|
||||
|
||||
- I hate the light, can I get a dark theme/dark mode?
|
||||
|
||||
Yes! `set theme dark`. Thanks to @fugerf.
|
||||
|
||||
- How can I bind keys using the control/alt key modifiers (eg: `ctrl+^`)?
|
||||
|
||||
You can't, yet. See [issue #41](https://github.com/cmcaine/tridactyl/issues/41).
|
||||
|
|
@ -145,7 +152,7 @@ NOTE: key modifiers (eg: control, alt) are not supported yet. See the FAQ below.
|
|||
|
||||
- When I type 'f', can I type link names (like Vimperator) in order to narrow down the number of highlighted links?
|
||||
|
||||
Not yet. See [issue #28](https://github.com/cmcaine/tridactyl/issues/28).
|
||||
You can, thanks to @saulrh. First `set hintfiltermode vimperator` and then `set hintchars 1234567890`.
|
||||
|
||||
- How to remap keybindings in both normal mode and ex mode?
|
||||
|
||||
|
|
@ -161,11 +168,11 @@ NOTE: key modifiers (eg: control, alt) are not supported yet. See the FAQ below.
|
|||
|
||||
- Why doesn't Tridactyl work on websites with frames?
|
||||
|
||||
It should work on some frames now. See [#122](https://github.com/cmcaine/tridactyl/issues/122).
|
||||
It should work on some frames now. See [#122](https://github.com/cmcaine/tridactyl/issues/122).
|
||||
|
||||
- Can I change proxy via commands?
|
||||
|
||||
No, this is a limitation of WebExtensions.
|
||||
Not yet, but this feature will eventually be implemented.
|
||||
|
||||
- How do I disable Tridactyl on certain sites?
|
||||
|
||||
|
|
@ -389,4 +396,4 @@ The logo was designed by Jake Beazley using free vector art by <a target="_blank
|
|||
[gitter-link]: https://gitter.im/tridactyl/Lobby
|
||||
[matrix-badge]: https://matrix.to/img/matrix-badge.svg
|
||||
[matrix-link]: https://riot.im/app/#/room/#tridactyl:matrix.org
|
||||
[amo-betas]: https://addons.mozilla.org/en-US/firefox/addon/tridactyl-vim/versions/beta
|
||||
[betas]: https://tridactyl.cmcaine.co.uk/betas/?sort=time&order=desc
|
||||
|
|
|
|||
6
scripts/amo_text_to_clipboard.sh
Executable file
6
scripts/amo_text_to_clipboard.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Put the AMO flavour text in your clipboard for easy pasting.
|
||||
# AMO doesn't support all HTML in markdown so we strip it out.
|
||||
|
||||
marked doc/amo.md | sed -r "s/<.?p>//g" | sed -r "s/<.?h.*>//g" | xclip -selection "clipboard"
|
||||
|
|
@ -5,4 +5,6 @@ manversion=$(grep '"version":' ./src/manifest.json | cut -d":" -f2 | tr -d \" |
|
|||
version=$manversion-$gitversion
|
||||
|
||||
sed -i.bak 's/REPLACE_ME_WITH_THE_VERSION_USING_SED/'$version'/' ./build/background.js
|
||||
sed -i.bak 's/REPLACE_ME_WITH_THE_VERSION_USING_SED/'$version'/' ./build/static/newtab.html
|
||||
rm ./build/background.js.bak
|
||||
rm ./build/static/newtab.html.bak
|
||||
|
|
|
|||
|
|
@ -11,14 +11,6 @@ publish_beta() {
|
|||
npm run clean
|
||||
npm run build
|
||||
scripts/version.js beta
|
||||
sign_and_submit
|
||||
}
|
||||
|
||||
publish_sideload() {
|
||||
npm run clean
|
||||
npm run build
|
||||
scripts/version.js beta
|
||||
sed 's/"name": "Tridactyl"/"name": "Tridactyl Sideload"/' -i build/manifest.json
|
||||
sed 's/tridactyl.vim@cmcaine/tridactyl.vim.betas@cmcaine/' -i build/manifest.json
|
||||
sign_and_submit
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,20 @@ async function add_beta(versionstr) {
|
|||
})
|
||||
}
|
||||
|
||||
function make_update_json(versionstr){
|
||||
updatejson = {
|
||||
"addons": {
|
||||
"tridactyl.vim.betas@cmcaine.co.uk": {
|
||||
"updates": [{
|
||||
"version": versionstr,
|
||||
"update_link": "https://tridactyl.cmcaine.co.uk/betas/tridactyl-" + versionstr + "-an+fx.xpi"
|
||||
},]
|
||||
}
|
||||
}
|
||||
}
|
||||
return updatejson
|
||||
}
|
||||
|
||||
function save_manifest(filename, manifest) {
|
||||
// Save file
|
||||
const fs = require('fs')
|
||||
|
|
@ -41,6 +55,12 @@ async function main() {
|
|||
filename = './build/manifest.json'
|
||||
manifest = require('.' + filename)
|
||||
manifest.version = await add_beta(manifest.version)
|
||||
manifest.applications.gecko.update_url = "https://tridactyl.cmcaine.co.uk/betas/updates.json"
|
||||
|
||||
// Make and write updates.json
|
||||
save_manifest("../public_html/betas/updates.json",make_update_json(manifest.version))
|
||||
|
||||
// Save manifest.json
|
||||
save_manifest(filename, manifest)
|
||||
break
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -52,6 +52,23 @@ export function blur() {
|
|||
cmdline_iframe.blur()
|
||||
}
|
||||
|
||||
export function executeWithoutCommandLine(fn) {
|
||||
let parent
|
||||
if (cmdline_iframe) {
|
||||
parent = cmdline_iframe.parentNode
|
||||
parent.removeChild(cmdline_iframe)
|
||||
}
|
||||
let result
|
||||
try {
|
||||
result = fn()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
if (cmdline_iframe) parent.appendChild(cmdline_iframe)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
import * as Messaging from './messaging'
|
||||
import * as SELF from './commandline_content'
|
||||
Messaging.addListener('commandline_content', Messaging.attributeCaller(SELF))
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import "./lib/html-tagged-template"
|
|||
|
||||
import * as Completions from './completions'
|
||||
import * as Messaging from './messaging'
|
||||
import * as Config from './config'
|
||||
import * as SELF from './commandline_frame'
|
||||
import './number.clamp'
|
||||
import state from './state'
|
||||
import * as Config from './config'
|
||||
import Logger from './logging'
|
||||
import * as aliases from './aliases'
|
||||
const logger = new Logger('cmdline')
|
||||
|
|
@ -55,10 +55,22 @@ function enableCompletions() {
|
|||
/* document.addEventListener("DOMContentLoaded", enableCompletions) */
|
||||
|
||||
let noblur = e => setTimeout(() => clInput.focus(), 0)
|
||||
let lastTheme: string
|
||||
|
||||
export function focus() {
|
||||
enableCompletions()
|
||||
document.body.classList.remove('hidden')
|
||||
|
||||
// update theme of command line
|
||||
let theme = Config.get("theme")
|
||||
if (theme !== lastTheme) {
|
||||
if (lastTheme) {
|
||||
document.querySelector(':root').classList.remove(lastTheme)
|
||||
}
|
||||
document.querySelector(':root').classList.add(theme)
|
||||
lastTheme = theme
|
||||
}
|
||||
|
||||
clInput.focus()
|
||||
clInput.addEventListener("blur",noblur)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@
|
|||
// Really, we'd like a way of just letting things use the variables
|
||||
//
|
||||
|
||||
/** # Tridactyl Configuration
|
||||
*
|
||||
* We very strongly recommend that you pretty much ignore this page and instead follow the link below DEFAULTS that will take you to our own source code which is formatted in a marginally more sane fashion.
|
||||
*
|
||||
*/
|
||||
|
||||
const CONFIGNAME = "userconfig"
|
||||
const WAITERS = []
|
||||
let INITIALISED = false
|
||||
|
|
@ -26,6 +32,7 @@ function schlepp(settings){
|
|||
// TODO: have list of possibilities for settings, e.g. hintmode: reverse | normal
|
||||
let USERCONFIG = o({})
|
||||
const DEFAULTS = o({
|
||||
"configversion": "0.0",
|
||||
"nmaps": o({
|
||||
"o": "fillcmdline open",
|
||||
"O": "current_url open",
|
||||
|
|
@ -52,7 +59,7 @@ const DEFAULTS = o({
|
|||
"gg": "scrollto 0",
|
||||
"$": "scrollto 100 x",
|
||||
// "0": "scrollto 0 x", // will get interpreted as a count
|
||||
"^": "scrollto 0 x",
|
||||
"^": "buffer #",
|
||||
"H": "back",
|
||||
"L": "forward",
|
||||
"d": "tabclose",
|
||||
|
|
@ -70,6 +77,11 @@ const DEFAULTS = o({
|
|||
":": "fillcmdline",
|
||||
"s": "fillcmdline open search",
|
||||
"S": "fillcmdline tabopen search",
|
||||
// find mode not suitable for general consumption yet.
|
||||
// "/": "find",
|
||||
// "?": "find -1",
|
||||
// "n": "findnext 1",
|
||||
// "N": "findnext -1",
|
||||
"M": "gobble 1 quickmark",
|
||||
// "B": "fillcmdline bufferall",
|
||||
"b": "fillcmdline buffer",
|
||||
|
|
@ -124,9 +136,10 @@ const DEFAULTS = o({
|
|||
"tlast": "tablast",
|
||||
"bd": "tabclose",
|
||||
"bdelete": "tabclose",
|
||||
"sanitize": "sanitise",
|
||||
}),
|
||||
followpagepatterns: o({
|
||||
next: "^(next|newer)\\b|»|>>",
|
||||
next: "^(next|newer)\\b|»|>>|more",
|
||||
prev: "^(prev(ious)?|older)\\b|«|<<",
|
||||
}),
|
||||
"searchengine": "google",
|
||||
|
|
@ -153,16 +166,29 @@ const DEFAULTS = o({
|
|||
|
||||
}),
|
||||
"newtab": "",
|
||||
"viewsource": "tridactyl", // "tridactyl" or "default"
|
||||
"storageloc": "sync",
|
||||
"homepages": [],
|
||||
"hintchars": "hjklasdfgyuiopqwertnmzxcvb",
|
||||
"hintfiltermode": "simple", // "simple", "vimperator", "vimperator-reflow"
|
||||
|
||||
tabopenpos: 'next',
|
||||
relatedopenpos: 'related',
|
||||
"ttsvoice": "default", // chosen from the listvoices list, or "default"
|
||||
"ttsvolume": 1, // 0 to 1
|
||||
"ttsrate": 1, // 0.1 to 10
|
||||
"ttspitch": 1, // 0 to 2
|
||||
"vimium-gi": true,
|
||||
|
||||
// either "nextinput" or "firefox"
|
||||
// If nextinput, <Tab> after gi brings selects the next input
|
||||
// If firefox, <Tab> selects the next selectable element, e.g. a link
|
||||
"gimode": "nextinput", // either "nextinput" or "firefox"
|
||||
|
||||
// either "beginning" or "end"
|
||||
// Decides where to place the cursor when selecting non-empty input fields
|
||||
"cursorpos": "end",
|
||||
|
||||
"theme": "default", // currently available: "default", "dark"
|
||||
|
||||
// Default logging levels - 2 === WARNING
|
||||
"logging": o({
|
||||
|
|
@ -282,29 +308,60 @@ export async function save(storage: "local" | "sync" = get("storageloc")){
|
|||
else browser.storage.sync.set(settingsobj)
|
||||
}
|
||||
|
||||
/** Updates the config to the latest version.
|
||||
Proposed semantic for config versionning:
|
||||
- x.y -> x+1.0 : major architectural changes
|
||||
- x.y -> x.y+1 : renaming settings/changing their types
|
||||
There's no need for an updater if you're only adding a new setting/changing
|
||||
a default setting
|
||||
|
||||
When adding updaters, don't forget to set("configversion", newversionnumber)!
|
||||
*/
|
||||
export async function update() {
|
||||
let updaters = {
|
||||
"0.0": async () => {
|
||||
try {
|
||||
// Before we had a config system, we had nmaps, and we put them in the
|
||||
// root namespace because we were young and bold.
|
||||
let legacy_nmaps = await browser.storage.sync.get("nmaps")
|
||||
if (legacy_nmaps) {
|
||||
USERCONFIG["nmaps"] = Object.assign(legacy_nmaps["nmaps"], USERCONFIG["nmaps"])
|
||||
}
|
||||
} finally {
|
||||
set("configversion", "1.0")
|
||||
}
|
||||
},
|
||||
"1.0": () => {
|
||||
let vimiumgi = getDeepProperty(USERCONFIG, "vimium-gi")
|
||||
if (vimiumgi === true || vimiumgi === "true")
|
||||
set("gimode", "nextinput")
|
||||
else if (vimiumgi === false || vimiumgi === "false")
|
||||
set("gimode", "firefox")
|
||||
unset("vimium-gi")
|
||||
set("configversion", "1.1")
|
||||
}
|
||||
}
|
||||
if (!get("configversion"))
|
||||
set("configversion", "0.0")
|
||||
while (updaters[get("configversion")] instanceof Function) {
|
||||
await updaters[get("configversion")]()
|
||||
}
|
||||
}
|
||||
|
||||
/** Read all user configuration from storage API then notify any waiting asynchronous calls
|
||||
|
||||
asynchronous calls generated by getAsync.
|
||||
*/
|
||||
async function init() {
|
||||
try {
|
||||
let syncConfig = await browser.storage.sync.get(CONFIGNAME)
|
||||
schlepp(syncConfig[CONFIGNAME])
|
||||
// Local storage overrides sync
|
||||
let localConfig = await browser.storage.local.get(CONFIGNAME)
|
||||
schlepp(localConfig[CONFIGNAME])
|
||||
|
||||
// Before we had a config system, we had nmaps, and we put them in the
|
||||
// root namespace because we were young and bold.
|
||||
let legacy_nmaps = await browser.storage.sync.get("nmaps")
|
||||
if (legacy_nmaps) {
|
||||
USERCONFIG["nmaps"] = Object.assign(legacy_nmaps["nmaps"], USERCONFIG["nmaps"])
|
||||
}
|
||||
} finally {
|
||||
INITIALISED = true
|
||||
for (let waiter of WAITERS) {
|
||||
waiter()
|
||||
}
|
||||
let syncConfig = await browser.storage.sync.get(CONFIGNAME)
|
||||
schlepp(syncConfig[CONFIGNAME])
|
||||
// Local storage overrides sync
|
||||
let localConfig = await browser.storage.local.get(CONFIGNAME)
|
||||
schlepp(localConfig[CONFIGNAME])
|
||||
await update()
|
||||
INITIALISED = true
|
||||
for (let waiter of WAITERS) {
|
||||
waiter()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,12 +18,14 @@ import * as config from './config'
|
|||
import * as dom from './dom'
|
||||
import * as excmds from './excmds_content'
|
||||
import * as hinting_content from './hinting'
|
||||
import * as finding_content from './finding'
|
||||
import * as itertools from './itertools'
|
||||
import * as keydown_content from "./keydown_content"
|
||||
import * as messaging from './messaging'
|
||||
import * as msgsafe from './msgsafe'
|
||||
import state from './state'
|
||||
import * as webext from './lib/webext'
|
||||
import Mark from 'mark.js'
|
||||
|
||||
(window as any).tri = Object.assign(Object.create(null), {
|
||||
browserBg: webext.browserBg,
|
||||
|
|
@ -33,8 +35,10 @@ import * as webext from './lib/webext'
|
|||
dom,
|
||||
excmds,
|
||||
hinting_content,
|
||||
finding_content,
|
||||
itertools,
|
||||
keydown_content,
|
||||
Mark,
|
||||
messaging,
|
||||
msgsafe,
|
||||
state,
|
||||
|
|
@ -42,6 +46,8 @@ import * as webext from './lib/webext'
|
|||
l: prom => prom.then(console.log).catch(console.error),
|
||||
})
|
||||
|
||||
dom.hijackPageListenerFunctions()
|
||||
|
||||
if (window.location.protocol === "moz-extension:" && window.location.pathname === "/static/newtab.html") {
|
||||
(window as any).tri.config.getAsync("newtab").then((newtab) => {
|
||||
if (newtab !== "")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Logger from "./logging"
|
|||
|
||||
import {parser as exmode_parser} from './parsers/exmode'
|
||||
import {parser as hintmode_parser} from './hinting_background'
|
||||
import {parser as findmode_parser} from './finding_background'
|
||||
import * as normalmode from "./parsers/normalmode"
|
||||
import * as insertmode from "./parsers/insertmode"
|
||||
import * as ignoremode from "./parsers/ignoremode"
|
||||
|
|
@ -23,6 +24,7 @@ function *ParserController () {
|
|||
insert: insertmode.parser,
|
||||
ignore: ignoremode.parser,
|
||||
hint: hintmode_parser,
|
||||
find: findmode_parser,
|
||||
gobble: gobblemode.parser,
|
||||
input: inputmode.parser,
|
||||
}
|
||||
|
|
@ -35,10 +37,14 @@ function *ParserController () {
|
|||
let keyevent: MsgSafeKeyboardEvent = yield
|
||||
let keypress = keyevent.key
|
||||
|
||||
// TODO: think about if this is robust
|
||||
if (state.mode != "ignore" && state.mode != "hint" && state.mode != "input") {
|
||||
// This code was sort of the cause of the most serious bug in Tridactyl
|
||||
// to date (March 2018).
|
||||
// https://github.com/cmcaine/tridactyl/issues/311
|
||||
if (state.mode != "ignore" && state.mode != "hint" && state.mode != "input" && state.mode != "find") {
|
||||
if (isTextEditable(keyevent.target)) {
|
||||
state.mode = "insert"
|
||||
if (state.mode !== 'insert') {
|
||||
state.mode = "insert"
|
||||
}
|
||||
} else if (state.mode === 'insert') {
|
||||
state.mode = "normal"
|
||||
}
|
||||
|
|
|
|||
148
src/dom.ts
148
src/dom.ts
|
|
@ -1,4 +1,7 @@
|
|||
import {MsgSafeNode} from './msgsafe'
|
||||
import * as config from './config'
|
||||
import {flatten} from './itertools'
|
||||
|
||||
// From saka-key lib/dom.js, under Apachev2
|
||||
|
||||
/**
|
||||
|
|
@ -59,16 +62,15 @@ function isEditableHTMLInput (element: MsgSafeNode) {
|
|||
* @param {{ ctrlKey, shiftKey, altKey, metaKey }} modifierKeys
|
||||
*/
|
||||
export function mouseEvent (element: Element, type: 'hover'|'unhover'|'click', modifierKeys = {}) {
|
||||
let events
|
||||
let events = []
|
||||
switch (type) {
|
||||
case 'hover':
|
||||
events = ['mouseover', 'mouseenter', 'mousemove']
|
||||
break
|
||||
case 'unhover':
|
||||
events = ['mousemove', 'mouseout', 'mouseleave']
|
||||
break
|
||||
case 'click':
|
||||
events = ['mouseover', 'mousedown', 'mouseup', 'click']
|
||||
events = ['mousedown', 'mouseup', 'click']
|
||||
case 'hover':
|
||||
events = ['mouseover', 'mouseenter', 'mousemove'].concat(events)
|
||||
break
|
||||
}
|
||||
events.forEach(type => {
|
||||
|
|
@ -223,6 +225,28 @@ export function isVisible (element: Element) {
|
|||
/* return true */
|
||||
}
|
||||
|
||||
/** Return all frames that belong to the document (frames that belong to
|
||||
* extensions are ignored).
|
||||
*
|
||||
* @param doc The document the frames should be fetched from
|
||||
*/
|
||||
export function getAllDocumentFrames(doc = document) {
|
||||
if (!(doc instanceof HTMLDocument))
|
||||
return []
|
||||
let frames = (<HTMLIFrameElement[] & HTMLFrameElement[]>Array.from(doc.getElementsByTagName("iframe")))
|
||||
.concat((Array.from(doc.getElementsByTagName("frame"))))
|
||||
.filter((frame) => !frame.src.startsWith("moz-extension://"))
|
||||
return frames.concat(frames.reduce((acc, f) => {
|
||||
// Errors could be thrown because of CSP
|
||||
let newFrames = []
|
||||
try {
|
||||
let doc = f.contentDocument || f.contentWindow.document
|
||||
newFrames = getAllDocumentFrames(doc)
|
||||
} catch (e){}
|
||||
return acc.concat(newFrames)
|
||||
}, []))
|
||||
}
|
||||
|
||||
/** Get all elements that match the given selector
|
||||
*
|
||||
* @param selector `the CSS selector to choose elements with
|
||||
|
|
@ -233,6 +257,17 @@ export function getElemsBySelector(selector: string,
|
|||
filters: Array<ElementFilter>) {
|
||||
|
||||
let elems = Array.from(document.querySelectorAll(selector))
|
||||
let frameElems = getAllDocumentFrames().reduce((acc, frame) => {
|
||||
let newElems = []
|
||||
// Errors could be thrown by CSP
|
||||
try {
|
||||
let doc = frame.contentDocument || frame.contentWindow.document
|
||||
newElems = Array.from(doc.querySelectorAll(selector))
|
||||
} catch (e){}
|
||||
return acc.concat(newElems)
|
||||
}, [])
|
||||
|
||||
elems = elems.concat(frameElems)
|
||||
|
||||
for (let filter of filters) {
|
||||
elems = elems.filter(filter)
|
||||
|
|
@ -271,3 +306,106 @@ export function compareElementArea(a: HTMLElement, b: HTMLElement): number {
|
|||
|
||||
return aArea - bArea
|
||||
}
|
||||
|
||||
export const hintworthy_js_elems = []
|
||||
|
||||
/** Adds or removes an element from the hintworthy_js_elems array of the
|
||||
* current tab.
|
||||
*
|
||||
* @param {EventTarget} elem The element add/removeEventListener is called on
|
||||
* @param {boolean} add true when called from addEventListener,
|
||||
* false from removeEventListener
|
||||
* @param {string} event The event name given to add/removeEventListener
|
||||
*
|
||||
* This function must be security reviewed when Custom Elements land in Firefox
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1406825
|
||||
*
|
||||
* This function is exported to the web content window but should only be
|
||||
* callable from our modified add/removeEventListener because we remove the
|
||||
* reference to it before web content runs (if added afterwards a
|
||||
* mutationobserver on the window object would probably capture a reference to
|
||||
* this function).
|
||||
*
|
||||
* Just in case web content does get a direct reference or the built-in
|
||||
* add/removeEventListener code doesn't validate elem correctly, this function
|
||||
* must assume that its inputs are potentially malicious.
|
||||
*/
|
||||
export function registerEvListenerAction(elem: EventTarget, add: boolean, event: string) {
|
||||
// We're only interested in the subset of EventTargets that are Elements.
|
||||
if (!(elem instanceof Element)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent bad elements from being processed
|
||||
//
|
||||
// This is defence in depth: we should never receive an invalid elem here
|
||||
// because add/removeEventListener currently throws a TypeError if the given
|
||||
// element is not a standard library EventTarget subclass.
|
||||
try {
|
||||
// Node prototype functions work on the C++ representation of the
|
||||
// Node, which a faked JS object won't have.
|
||||
// hasChildNodes() is chosen because it should be cheap.
|
||||
Node.prototype.hasChildNodes.apply(elem as Node)
|
||||
} catch (e) {
|
||||
// Don't throw a real exception because addEventListener wouldn't and we
|
||||
// don't want to break content code.
|
||||
console.error("Elem is not a real Node", elem)
|
||||
return
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case "click":
|
||||
case "mousedown":
|
||||
case "mouseup":
|
||||
case "mouseover":
|
||||
if (add) {
|
||||
hintworthy_js_elems.push(elem)
|
||||
} else {
|
||||
// Possible bug: If a page adds an event listener for "click" and
|
||||
// "mousedown" and removes "mousedown" twice, we lose track of the
|
||||
// elem even though it still has a "click" listener.
|
||||
// Fixing this might not be worth the added complexity.
|
||||
let index = hintworthy_js_elems.indexOf(elem)
|
||||
if (index >= 0)
|
||||
hintworthy_js_elems.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Replace the page's addEventListener with a closure containing a reference
|
||||
* to the original addEventListener and [[registerEvListenerAction]]. Do the
|
||||
* same with removeEventListener.
|
||||
*/
|
||||
export function hijackPageListenerFunctions(): void {
|
||||
let exportedName = 'registerEvListenerAction'
|
||||
exportFunction(registerEvListenerAction, window, {defineAs: exportedName})
|
||||
|
||||
let eval_str = ["addEventListener", "removeEventListener"].reduce((acc, cur) => `${acc};
|
||||
EventTarget.prototype.${cur} = ((realFunction, register) => {
|
||||
return function (...args) {
|
||||
let result = realFunction.apply(this, args)
|
||||
try {
|
||||
register(this, ${cur === "addEventListener"}, args[0])
|
||||
} catch (e) {
|
||||
// Don't let the page know something wrong happened here
|
||||
}
|
||||
return result
|
||||
}
|
||||
})(EventTarget.prototype.${cur}, ${exportedName})`
|
||||
, "")
|
||||
|
||||
window.eval(eval_str + `;delete ${exportedName}`)
|
||||
}
|
||||
|
||||
/** Focuses an input element and makes sure the cursor is put at the end of the input */
|
||||
export function focus(e: HTMLElement): void {
|
||||
e.focus()
|
||||
if (e instanceof HTMLInputElement) {
|
||||
let pos = 0
|
||||
if (config.get("cursorpos") === "end")
|
||||
pos = e.value.length
|
||||
e.selectionStart = pos
|
||||
e.selectionEnd = e.selectionStart
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
339
src/excmds.ts
339
src/excmds.ts
|
|
@ -7,6 +7,8 @@
|
|||
The default keybinds can be found [here](/static/docs/modules/_config_.html#defaults).
|
||||
You can also view them with [[bind]]. Try `bind j`.
|
||||
|
||||
For more information, and FAQs, check out our [readme][4] on github.
|
||||
|
||||
Tridactyl is in a pretty early stage of development. Please report any
|
||||
issues and make requests for missing features on the GitHub [project page][1].
|
||||
You can also get in touch using Matrix, Gitter, or IRC chat clients:
|
||||
|
|
@ -17,6 +19,23 @@
|
|||
|
||||
All three channels are mirrored together, so it doesn't matter which one you use.
|
||||
|
||||
## 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](/static/docs/modules/_config_.html#defaults) which is nigh-on unreadable on these pages.
|
||||
|
||||
|
||||
## Highlighted features:
|
||||
|
||||
- Press `b` to bring up a list of open tabs in the current window; you can
|
||||
|
|
@ -48,11 +67,12 @@
|
|||
file](2) available in our repository.
|
||||
|
||||
If you want a more fully-featured vimperator-alike, your best option is
|
||||
[Firefox ESR](3) and Vimperator :)
|
||||
[Firefox ESR][3] and Vimperator :)
|
||||
|
||||
[1]: https://github.com/cmcaine/tridactyl/issues
|
||||
[2]: https://github.com/cmcaine/tridactyl/blob/master/src/static/userChrome-minimal.css
|
||||
[3]: https://www.mozilla.org/en-US/firefox/organizations/
|
||||
[4]: https://github.com/cmcaine/tridactyl#readme
|
||||
|
||||
[gitter-badge]: /static/badges/gitter-badge.svg
|
||||
[gitter-link]: https://gitter.im/tridactyl/Lobby
|
||||
|
|
@ -65,44 +85,41 @@
|
|||
|
||||
// {{{ setup
|
||||
|
||||
// Shared
|
||||
import * as Messaging from "./messaging"
|
||||
import {l} from './lib/webext'
|
||||
import state from "./state"
|
||||
import * as UrlUtil from "./url_util"
|
||||
import * as config from './config'
|
||||
import * as aliases from './aliases'
|
||||
import * as Logging from "./logging"
|
||||
/** @hidden */
|
||||
const logger = new Logging.Logger('excmds')
|
||||
import Mark from 'mark.js'
|
||||
|
||||
//#content_omit_line
|
||||
//#content_helper
|
||||
// {
|
||||
import "./number.clamp"
|
||||
//#content_helper
|
||||
import * as SELF from "./excmds_content"
|
||||
//#content_helper
|
||||
Messaging.addListener('excmd_content', Messaging.attributeCaller(SELF))
|
||||
/** Message excmds_content.ts in the active tab of the currentWindow */
|
||||
import * as DOM from './dom'
|
||||
import { executeWithoutCommandLine } from "./commandline_content"
|
||||
// }
|
||||
|
||||
//#background_helper
|
||||
// {
|
||||
/** Message excmds_content.ts in the active tab of the currentWindow */
|
||||
import {messageActiveTab} from './messaging'
|
||||
|
||||
//#background_helper
|
||||
import "./number.mod"
|
||||
//#background_helper
|
||||
import {ModeName} from './state'
|
||||
//#background_helper
|
||||
import * as keydown from "./keydown_background"
|
||||
//#background_helper
|
||||
import {activeTab, activeTabId, firefoxVersionAtLeast} from './lib/webext'
|
||||
import * as UrlUtil from "./url_util"
|
||||
|
||||
//#background_helper
|
||||
import {activeTab, activeTabId, firefoxVersionAtLeast, openInNewTab} from './lib/webext'
|
||||
import * as CommandLineBackground from './commandline_background'
|
||||
//#content_helper
|
||||
import * as DOM from './dom'
|
||||
|
||||
import * as config from './config'
|
||||
import * as Logging from "./logging"
|
||||
const logger = new Logging.Logger('excmds')
|
||||
|
||||
import * as aliases from './aliases'
|
||||
|
||||
/** @hidden */
|
||||
//#background_helper
|
||||
export const cmd_params = new Map<string, Map<string, string>>()
|
||||
// }
|
||||
|
||||
/** @hidden */
|
||||
function hasScheme(uri: string) {
|
||||
|
|
@ -203,7 +220,10 @@ export function unfocus() {
|
|||
|
||||
//#content
|
||||
export function scrollpx(a: number, b: number) {
|
||||
window.scrollBy(a, b)
|
||||
let top = document.body.getClientRects()[0].top;
|
||||
window.scrollBy(a, b);
|
||||
if (top == document.body.getClientRects()[0].top)
|
||||
recursiveScroll(a, b, [document.body])
|
||||
}
|
||||
|
||||
/** If two numbers are given, treat as x and y values to give to window.scrollTo
|
||||
|
|
@ -227,13 +247,91 @@ export function scrollto(a: number, b: number | "x" | "y" = "y") {
|
|||
}
|
||||
}
|
||||
|
||||
/** Tries to find a node which can be scrolled either x pixels to the right or
|
||||
* 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.
|
||||
*/
|
||||
//#content_helper
|
||||
function recursiveScroll(x: number, y: number, nodes: Element[]) {
|
||||
let index = 0
|
||||
do {
|
||||
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
|
||||
// This check speeds things up on the aforementioned website while
|
||||
// still letting scrolling work on twitch/website with frames so we'll
|
||||
// consider it good enough for now.
|
||||
while (!rect || rect.top >= innerHeight - 4) {
|
||||
node = nodes[index++];
|
||||
// No node means we've reached the end of the array
|
||||
if (!node)
|
||||
return
|
||||
rect = node.getClientRects()[0]
|
||||
}
|
||||
let top = rect.top
|
||||
let left = rect.left
|
||||
node.scrollBy(x, y);
|
||||
rect = node.getClientRects()[0]
|
||||
// if the node moved, stop
|
||||
if (top != rect.top || left != rect.left)
|
||||
return
|
||||
nodes = nodes.concat(Array.prototype.slice.call(node.children))
|
||||
if (node.contentDocument)
|
||||
nodes.push(node.contentDocument.body)
|
||||
} while (index < nodes.length)
|
||||
}
|
||||
|
||||
//#content
|
||||
export function scrollline(n = 1) {
|
||||
let top = document.body.getClientRects()[0].top
|
||||
window.scrollByLines(n)
|
||||
if (top == document.body.getClientRects()[0].top) {
|
||||
const cssHeight = window.getComputedStyle(document.body).getPropertyValue('line-height')
|
||||
// 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
|
||||
if (lineHeight)
|
||||
recursiveScroll(0, lineHeight * n, [window.document.body])
|
||||
}
|
||||
}
|
||||
|
||||
//#content
|
||||
export function scrollpage(n = 1) {
|
||||
window.scrollBy(0, window.innerHeight * n)
|
||||
scrollpx(0, window.innerHeight * n)
|
||||
}
|
||||
|
||||
//#background_helper
|
||||
import * as finding from './finding_background'
|
||||
|
||||
/** Start find mode. Work in progress.
|
||||
*
|
||||
* @param direction - the direction to search in: 1 is forwards, -1 is backwards.
|
||||
*
|
||||
*/
|
||||
//#background
|
||||
export function find(direction?: number){
|
||||
if (direction === undefined) direction = 1
|
||||
finding.findPage(direction)
|
||||
}
|
||||
|
||||
/** Highlight the next occurence of the previously searched for word.
|
||||
*
|
||||
* @param number - number of words to advance down the page (use 1 for next word, -1 for previous)
|
||||
*
|
||||
*/
|
||||
//#background
|
||||
export function findnext(n: number){
|
||||
finding.findPageNavigate(n)
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
|
|
@ -295,6 +393,32 @@ export function open(...urlarr: string[]) {
|
|||
window.location.href = forceURI(url)
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
//#content_helper
|
||||
let sourceElement = undefined
|
||||
//#content
|
||||
export function viewsource(url = "") {
|
||||
if (url === "")
|
||||
url = window.location.href
|
||||
if (config.get("viewsource") === "default") {
|
||||
window.location.href = "view-source:" + url
|
||||
return
|
||||
}
|
||||
if (!sourceElement) {
|
||||
sourceElement = executeWithoutCommandLine(() => {
|
||||
let pre = document.createElement("pre")
|
||||
pre.id = "TridactylViewsourceElement"
|
||||
pre.className = "cleanslate " + config.get("theme")
|
||||
pre.innerText = document.documentElement.innerHTML
|
||||
document.documentElement.appendChild(pre)
|
||||
return pre
|
||||
})
|
||||
} else {
|
||||
sourceElement.parentNode.removeChild(sourceElement)
|
||||
sourceElement = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** Go to your homepage(s)
|
||||
|
||||
@param all
|
||||
|
|
@ -364,7 +488,7 @@ function selectLast(selector: string): HTMLElement | null {
|
|||
/** Find a likely next/previous link and follow it
|
||||
|
||||
If a link or anchor element with rel=rel exists, use that, otherwise fall back to:
|
||||
|
||||
|
||||
1) find the last anchor on the page with innerText matching the appropriate `followpagepattern`.
|
||||
2) call [[urlincrement]] with 1 or -1
|
||||
|
||||
|
|
@ -585,6 +709,9 @@ export async function zoom(level=0, rel="false"){
|
|||
browser.tabs.setZoom(level)
|
||||
}
|
||||
|
||||
/** Opens the current page in Firefox's reader mode.
|
||||
* You currently cannot use Tridactyl while in reader mode.
|
||||
*/
|
||||
//#background
|
||||
export async function reader() {
|
||||
if (await l(firefoxVersionAtLeast(58))) {
|
||||
|
|
@ -601,24 +728,22 @@ export async function reader() {
|
|||
//#content_helper
|
||||
loadaucmds()
|
||||
|
||||
//@hidden
|
||||
/** @hidden */
|
||||
//#content
|
||||
export async function loadaucmds(){
|
||||
console.log("AUCMDS TRIED TO RUN")
|
||||
// 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`)
|
||||
let aucmds = await config.getAsync("autocmds", "DocStart")
|
||||
console.log(aucmds)
|
||||
const ausites = Object.keys(aucmds)
|
||||
// yes, this is lazy
|
||||
const aukey = ausites.find(e=>window.document.location.href.includes(e))
|
||||
if (aukey !== undefined){
|
||||
console.log(aukey)
|
||||
Messaging.message("commandline_background", "recvExStr", [aucmds[aukey]])
|
||||
}
|
||||
}
|
||||
|
||||
/** The kinds of input elements that we want to be included in the "focusinput"
|
||||
* command (gi)
|
||||
* @hidden
|
||||
*/
|
||||
export const INPUTTAGS_selectors = `
|
||||
input:not([disabled]):not([readonly]):-moz-any(
|
||||
|
|
@ -644,12 +769,15 @@ object,
|
|||
[role='application']
|
||||
`
|
||||
|
||||
/** Password field selectors */
|
||||
/** Password field selectors
|
||||
* @hidden
|
||||
*/
|
||||
const INPUTPASSWORD_selectors = `
|
||||
input[type='password']
|
||||
`
|
||||
|
||||
/** DOM reference to the last used Input field
|
||||
* @hidden
|
||||
*/
|
||||
//#content_helper
|
||||
let LAST_USED_INPUT: HTMLElement = null
|
||||
|
|
@ -736,8 +864,8 @@ export function focusinput(nth: number|string) {
|
|||
}
|
||||
|
||||
if (inputToFocus) {
|
||||
inputToFocus.focus()
|
||||
if (config.get('vimium-gi') && state.mode !== 'input') {
|
||||
DOM.focus(inputToFocus)
|
||||
if (config.get('gimode') === 'nextinput' && state.mode !== 'input') {
|
||||
state.mode = 'input'
|
||||
}
|
||||
}
|
||||
|
|
@ -762,7 +890,7 @@ document.addEventListener("focusin",e=>{if (DOM.isTextEditable(e.target as HTMLE
|
|||
*/
|
||||
/** @hidden */
|
||||
//#background_helper
|
||||
async function tabIndexSetActive(index: number) {
|
||||
async function tabIndexSetActive(index: number|string) {
|
||||
tabSetActive(await idFromIndex(index))
|
||||
}
|
||||
|
||||
|
|
@ -816,18 +944,26 @@ export async function tablast() {
|
|||
|
||||
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
|
||||
the author's preference. Open an issue if you don't like it :)
|
||||
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`
|
||||
|
||||
*/
|
||||
//#background
|
||||
export async function tabopen(...addressarr: string[]) {
|
||||
let uri
|
||||
let url: string
|
||||
let address = addressarr.join(' ')
|
||||
if (address != "") uri = forceURI(address)
|
||||
else uri = forceURI(config.get("newtab"))
|
||||
browser.tabs.create({
|
||||
url: uri,
|
||||
index: (await activeTabId()) + 1
|
||||
})
|
||||
|
||||
if (address != "") url = forceURI(address)
|
||||
else url = forceURI(config.get("newtab"))
|
||||
|
||||
openInNewTab(url)
|
||||
}
|
||||
|
||||
/** Resolve a tab index to the tab id of the corresponding tab in this window.
|
||||
|
|
@ -836,14 +972,21 @@ export async function tabopen(...addressarr: string[]) {
|
|||
1-based index of the tab to target. Wraps such that 0 = last tab, -1 =
|
||||
penultimate tab, etc.
|
||||
|
||||
also supports # for previous tab, % for current tab.
|
||||
|
||||
if undefined, return activeTabId()
|
||||
|
||||
@hidden
|
||||
*/
|
||||
//#background_helper
|
||||
async function idFromIndex(index?: number): Promise<number> {
|
||||
if (index !== undefined) {
|
||||
async function idFromIndex(index?: number|"%"|"#"|string): Promise<number> {
|
||||
if (index === "#") {
|
||||
// Support magic previous/current tab syntax everywhere
|
||||
return (await getSortedWinTabs())[1].id
|
||||
}
|
||||
else if (index !== undefined && index !== "%") {
|
||||
// Wrap
|
||||
index = Number(index)
|
||||
index = (index - 1).mod(
|
||||
(await l(browser.tabs.query({currentWindow: true}))).length)
|
||||
+ 1
|
||||
|
|
@ -891,6 +1034,30 @@ export async function tabdetach(index?: number) {
|
|||
browser.windows.create({tabId: await idFromIndex(index)})
|
||||
}
|
||||
|
||||
/** Get list of tabs sorted by most recent use
|
||||
|
||||
@hidden
|
||||
*/
|
||||
//#background_helper
|
||||
async function getSortedWinTabs(): Promise<browser.tabs.Tab[]> {
|
||||
const tabs = await browser.tabs.query({currentWindow: true})
|
||||
tabs.sort((a, b) => a.lastAccessed < b.lastAccessed ? 1 : -1)
|
||||
return tabs
|
||||
}
|
||||
|
||||
/** Toggle fullscreen state
|
||||
|
||||
*/
|
||||
//#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"
|
||||
browser.windows.update(wid,{state})
|
||||
}
|
||||
|
||||
/** Close a tab.
|
||||
|
||||
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.
|
||||
|
|
@ -901,8 +1068,9 @@ export async function tabdetach(index?: number) {
|
|||
//#background
|
||||
export async function tabclose(...indexes: string[]) {
|
||||
if (indexes.length > 0) {
|
||||
const idsPromise = indexes.map(index => idFromIndex(Number(index)))
|
||||
browser.tabs.remove(await Promise.all(idsPromise))
|
||||
let ids: number[]
|
||||
ids = await Promise.all(indexes.map(index => idFromIndex(index)))
|
||||
browser.tabs.remove(ids)
|
||||
} else {
|
||||
// Close current tab
|
||||
browser.tabs.remove(await activeTabId())
|
||||
|
|
@ -1015,7 +1183,9 @@ export async function qa() {
|
|||
|
||||
// {{{ MISC
|
||||
|
||||
/** Deprecated */
|
||||
/** Deprecated
|
||||
* @hidden
|
||||
*/
|
||||
//#background
|
||||
export function suppress(preventDefault?: boolean, stopPropagation?: boolean) {
|
||||
mode("ignore")
|
||||
|
|
@ -1023,9 +1193,7 @@ export function suppress(preventDefault?: boolean, stopPropagation?: boolean) {
|
|||
|
||||
//#background
|
||||
export function version(){
|
||||
clipboard("yank","REPLACE_ME_WITH_THE_VERSION_USING_SED")
|
||||
fillcmdline_notrail("REPLACE_ME_WITH_THE_VERSION_USING_SED")
|
||||
|
||||
}
|
||||
|
||||
/** Example:
|
||||
|
|
@ -1036,6 +1204,8 @@ export function mode(mode: ModeName) {
|
|||
// TODO: event emition on mode change.
|
||||
if (mode === "hint") {
|
||||
hint()
|
||||
} else if (mode === "find") {
|
||||
find()
|
||||
} else {
|
||||
state.mode = mode
|
||||
}
|
||||
|
|
@ -1102,7 +1272,7 @@ export function repeat(n = 1, ...exstr: string[]) {
|
|||
controller.acceptExCmd(cmd)
|
||||
}
|
||||
|
||||
/** Split `cmds` on pipes (|) and treat each as it's own command.
|
||||
/** Split `cmds` on pipes (|) and treat each as its own command.
|
||||
|
||||
Workaround: this should clearly be in the parser, but we haven't come up with a good way to deal with |s in URLs, search terms, etc. yet.
|
||||
*/
|
||||
|
|
@ -1112,9 +1282,9 @@ export function composite(...cmds: string[]) {
|
|||
cmds.forEach(controller.acceptExCmd)
|
||||
}
|
||||
|
||||
/** Please use fillcmdline instead */
|
||||
/** @hidden */
|
||||
//#background
|
||||
export function showcmdline() {
|
||||
function showcmdline() {
|
||||
CommandLineBackground.show()
|
||||
}
|
||||
|
||||
|
|
@ -1154,11 +1324,15 @@ export async function current_url(...strarr: string[]){
|
|||
|
||||
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/.
|
||||
|
||||
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.
|
||||
|
||||
Unfortunately, javascript can only give us the `clipboard` clipboard, not e.g. the X selection clipboard.
|
||||
|
||||
*/
|
||||
//#background
|
||||
export async function clipboard(excmd: "open"|"yank"|"yankshort"|"yankcanon"|"tabopen" = "open", ...toYank: string[]) {
|
||||
export async function clipboard(excmd: "open"|"yank"|"yankshort"|"yankcanon"|"yanktitle"|"yankmd"|"tabopen" = "open", ...toYank: string[]) {
|
||||
let content = toYank.join(" ")
|
||||
let url = ""
|
||||
let urls = []
|
||||
|
|
@ -1183,6 +1357,13 @@ export async function clipboard(excmd: "open"|"yank"|"yankshort"|"yankcanon"|"ta
|
|||
content = (content == "") ? (await activeTab()).url : content
|
||||
messageActiveTab("commandline_frame", "setClipboard", [content])
|
||||
break
|
||||
case 'yanktitle':
|
||||
messageActiveTab("commandline_frame", "setClipboard", [content])
|
||||
break
|
||||
case 'yankmd':
|
||||
content = "[" + (await activeTab()).title + "](" + (await activeTab()).url + ")"
|
||||
messageActiveTab("commandline_frame", "setClipboard", [content])
|
||||
break
|
||||
case 'open':
|
||||
await messageActiveTab("commandline_content", "focus")
|
||||
url = await messageActiveTab("commandline_frame", "getClipboard")
|
||||
|
|
@ -1229,16 +1410,7 @@ export async function buffers() {
|
|||
*/
|
||||
//#background
|
||||
export async function buffer(index: number | '#') {
|
||||
if (index === "#") {
|
||||
// Switch to the most recently accessed buffer
|
||||
tabIndexSetActive(
|
||||
(await browser.tabs.query({currentWindow: true})).sort((a, b) => {
|
||||
return a.lastAccessed < b.lastAccessed ? 1 : -1
|
||||
})[1].index + 1
|
||||
)
|
||||
} else if (Number.isInteger(Number(index))) {
|
||||
tabIndexSetActive(Number(index))
|
||||
}
|
||||
tabIndexSetActive(index)
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
|
@ -1453,21 +1625,21 @@ export async function reset(key: string){
|
|||
-t [0-9]+(m|h|d|w)
|
||||
|
||||
Examples:
|
||||
`sanitize all` -> Deletes everything
|
||||
`sanitize history` -> Deletes all history
|
||||
`sanitize commandline tridactyllocal tridactylsync` -> Deletes every bit of data Tridactyl holds
|
||||
`sanitize cookies -t 3d` -> Deletes cookies that were set during the last three days.
|
||||
`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.
|
||||
|
||||
*/
|
||||
//#background
|
||||
export async function sanitize(...args: string[]) {
|
||||
export async function sanitise(...args: string[]) {
|
||||
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) {
|
||||
let match = args[flagpos + 1].match('^([0-9])+(m|h|d|w)$')
|
||||
// If the arg of the flag matches Pentadactyl's sanitizetimespan format
|
||||
// If the arg of the flag matches Pentadactyl's sanitisetimespan format
|
||||
if (match !== null && match.length == 3) {
|
||||
// Compute the timespan in milliseconds and get a Date object
|
||||
let millis = parseInt(match[1]) * 1000
|
||||
|
|
@ -1479,10 +1651,10 @@ export async function sanitize(...args: string[]) {
|
|||
}
|
||||
since = { "since": (new Date()).getTime() - millis }
|
||||
} else {
|
||||
throw new Error(":sanitize error: expected time format: ^([0-9])+(m|h|d|w)$, given format:" + args[flagpos+1])
|
||||
throw new Error(":sanitise error: expected time format: ^([0-9])+(m|h|d|w)$, given format:" + args[flagpos+1])
|
||||
}
|
||||
} else {
|
||||
throw new Error(":sanitize error: -t given but no following arguments")
|
||||
throw new Error(":sanitise error: -t given but no following arguments")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1532,7 +1704,7 @@ export async function sanitize(...args: string[]) {
|
|||
browser.browsingData.remove(since, dts)
|
||||
}
|
||||
|
||||
/** Bind a quickmark for the current URL to a key.
|
||||
/** Bind a quickmark for the current URL or space-separated list of URLs to a key on the keyboard.
|
||||
|
||||
Afterwards use go[key], gn[key], or gw[key] to [[open]], [[tabopen]], or
|
||||
[[winopen]] the URL respectively.
|
||||
|
|
@ -1565,6 +1737,8 @@ export async function quickmark(key: string, ...addressarr: string[]) {
|
|||
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).
|
||||
|
||||
For example, you might try `get nmaps` to see all of your current binds.
|
||||
*/
|
||||
//#background
|
||||
export function get(...keys: string[]) {
|
||||
|
|
@ -1622,6 +1796,8 @@ import * as hinting from './hinting_background'
|
|||
- -# yank an element's anchor URL to clipboard
|
||||
- -c [selector] hint links that match the css selector
|
||||
- `bind ;c hint -c [class*="expand"],[class="togg"]` works particularly well on reddit and HN
|
||||
- -w open in new window
|
||||
-wp open in new private window
|
||||
|
||||
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`
|
||||
|
|
@ -1632,6 +1808,7 @@ import * as hinting from './hinting_background'
|
|||
Related settings:
|
||||
"hintchars": "hjklasdfgyuiopqwertnmzxcvb"
|
||||
"hintfiltermode": "simple" | "vimperator" | "vimperator-reflow"
|
||||
"relatedopenpos": "related" | "next" | "last"
|
||||
*/
|
||||
//#background
|
||||
export function hint(option?: string, selectors="") {
|
||||
|
|
@ -1649,6 +1826,8 @@ export function hint(option?: string, selectors="") {
|
|||
else if (option === "-#") hinting.hintPageAnchorYank()
|
||||
else if (option === "-c") hinting.hintPageSimple(selectors)
|
||||
else if (option === "-r") hinting.hintRead()
|
||||
else if (option === "-w") hinting.hintPageWindow()
|
||||
else if (option === "-wp") hinting.hintPageWindowPrivate()
|
||||
else hinting.hintPageSimple()
|
||||
}
|
||||
|
||||
|
|
@ -1776,4 +1955,22 @@ export async function bmark(url?: string, ...titlearr: string[] ){
|
|||
if (dupbmarks.length == 0 ) {browser.bookmarks.create({url, title})}
|
||||
}
|
||||
|
||||
/** Open a welcome page on first install.
|
||||
*
|
||||
* @hidden
|
||||
*/
|
||||
//#background_helper
|
||||
browser.runtime.onInstalled.addListener((details) => {
|
||||
if (details.reason == "install") tabopen()
|
||||
// could add elif "update" and show a changelog. Hide it behind a setting to make it less annoying?
|
||||
// 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)
|
||||
// }
|
||||
})
|
||||
|
||||
|
||||
// vim: tabstop=4 shiftwidth=4 expandtab
|
||||
|
|
|
|||
142
src/finding.ts
Normal file
142
src/finding.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/** Find mode.
|
||||
|
||||
TODO:
|
||||
|
||||
important
|
||||
n/N
|
||||
?
|
||||
show command line with user input?
|
||||
performance
|
||||
allow spaces
|
||||
*/
|
||||
|
||||
import * as DOM from './dom'
|
||||
import {hasModifiers} from './keyseq'
|
||||
import state from './state'
|
||||
import {messageActiveTab, message} from './messaging'
|
||||
import * as config from './config'
|
||||
import Logger from './logging'
|
||||
import Mark from 'mark.js'
|
||||
const logger = new Logger('finding')
|
||||
|
||||
function elementswithtext() {
|
||||
return DOM.getElemsBySelector("*",
|
||||
// offsetHeight tells us which elements are drawn
|
||||
[hint => {
|
||||
return ((hint as any).offsetHeight > 0 && (hint as any).offsetHeight !== undefined)
|
||||
},
|
||||
hint => {
|
||||
return hint.textContent != ""
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
||||
/** Simple container for the state of a single frame's finds. */
|
||||
class findState {
|
||||
readonly findHost = document.createElement('div')
|
||||
public mark = new Mark(elementswithtext())
|
||||
// ^ why does filtering by offsetHeight NOT work here
|
||||
public markedels = []
|
||||
public markpos = 0
|
||||
public direction: 1 | -1 = 1
|
||||
constructor(){
|
||||
this.findHost.classList.add("TridactylfindHost")
|
||||
}
|
||||
public filter = ''
|
||||
|
||||
destructor() {
|
||||
// Remove all finds from the DOM.
|
||||
this.findHost.remove()
|
||||
}
|
||||
}
|
||||
|
||||
let findModeState: findState = undefined
|
||||
|
||||
/** For each findable element, add a find */
|
||||
|
||||
|
||||
/** Show only finds prefixed by fstr. Focus first match */
|
||||
function filter(fstr) {
|
||||
// for some reason, doing the mark in the done function speeds this up immensely
|
||||
// nb: https://jsfiddle.net/julmot/973gdh8g/ is pretty much what we want
|
||||
findModeState.mark.unmark({done: () => {
|
||||
findModeState.mark.mark(fstr, {
|
||||
separateWordSearch: false,
|
||||
acrossElements: true,
|
||||
})
|
||||
}})
|
||||
}
|
||||
|
||||
/** Remove all finds, reset STATE. */
|
||||
function reset(args?) {
|
||||
if (args.leavemarks == "false") findModeState.mark.unmark()
|
||||
if (args.unfind == "true") {
|
||||
findModeState.mark.unmark()
|
||||
findModeState.destructor()
|
||||
findModeState = undefined
|
||||
}
|
||||
state.mode = 'normal'
|
||||
}
|
||||
|
||||
function mode(mode: "nav" | "search"){
|
||||
if (mode == "nav"){
|
||||
// really, this should happen all the time when in search - we always want first result to be green and the window to move to it (if not already on screen)
|
||||
findModeState.markedels = Array.from(window.document.getElementsByTagName("mark")).filter(el => el.offsetHeight > 0)
|
||||
// ^ why does filtering by offsetHeight work here
|
||||
findModeState.markpos = 0
|
||||
let el = findModeState.markedels[0]
|
||||
if (!DOM.isVisible(el)) el.scrollIntoView()
|
||||
// colour of the selected link
|
||||
el.style.background = "lawngreen"
|
||||
}
|
||||
}
|
||||
|
||||
import "./number.mod"
|
||||
function navigate(n: number = 1){
|
||||
// also - really - should probably actually make this be an excmd
|
||||
// people will want to be able to scroll and stuff.
|
||||
// should probably move this to an update function?
|
||||
// don't hardcode this colour
|
||||
findModeState.markedels[findModeState.markpos].style.background = "yellow"
|
||||
findModeState.markpos = (findModeState.markpos + n*findModeState.direction).mod(findModeState.markedels.length)
|
||||
// obvs need to do mod to wrap indices
|
||||
let el = findModeState.markedels[findModeState.markpos]
|
||||
if (!DOM.isVisible(el)) el.scrollIntoView()
|
||||
el.style.background = "lawngreen"
|
||||
}
|
||||
|
||||
export function findPage(direction?: 1|-1) {
|
||||
if (findModeState !== undefined) reset({unfind:"true"})
|
||||
state.mode = 'find'
|
||||
findModeState = new findState()
|
||||
if (direction !== undefined) findModeState.direction = direction
|
||||
document.body.appendChild(findModeState.findHost)
|
||||
}
|
||||
|
||||
/** If key is in findchars, add it to filtstr and filter */
|
||||
function pushKey(ke) {
|
||||
if (ke.key === 'Backspace') {
|
||||
findModeState.filter = findModeState.filter.slice(0,-1)
|
||||
filter(findModeState.filter)
|
||||
} else if (ke.key === 'Enter') {
|
||||
mode("nav")
|
||||
reset({leavemarks: "true"})
|
||||
} else if (ke.key === 'Escape'){
|
||||
reset({unfind: "true"})
|
||||
} else if (ke.key.length > 1) {
|
||||
return
|
||||
} else {
|
||||
findModeState.filter += ke.key
|
||||
filter(findModeState.filter)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import {addListener, attributeCaller} from './messaging'
|
||||
addListener('finding_content', attributeCaller({
|
||||
pushKey,
|
||||
mode,
|
||||
reset,
|
||||
findPage,
|
||||
navigate,
|
||||
}))
|
||||
37
src/finding_background.ts
Normal file
37
src/finding_background.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import {messageActiveTab} from './messaging'
|
||||
|
||||
async function pushKey(key) {
|
||||
return await messageActiveTab('finding_content', 'pushKey', [key])
|
||||
}
|
||||
|
||||
export async function findPage(direction) {
|
||||
return await messageActiveTab('finding_content', 'findPage', [direction])
|
||||
}
|
||||
|
||||
export async function findPageNavigate(n: number) {
|
||||
return await messageActiveTab('finding_content', 'navigate', [n])
|
||||
}
|
||||
|
||||
async function reset(args = {leavemarks: "false"}) {
|
||||
return await messageActiveTab('finding_content', 'reset', [args])
|
||||
}
|
||||
|
||||
import {MsgSafeKeyboardEvent} from './msgsafe'
|
||||
|
||||
/** At some point, this might be turned into a real keyseq parser
|
||||
|
||||
reset and selectFocusedfinds are OK candidates for map targets in the
|
||||
future. pushKey less so, I think.
|
||||
|
||||
*/
|
||||
export function parser(keys: MsgSafeKeyboardEvent[]) {
|
||||
const key = keys[0].key
|
||||
// if (key === 'Escape') {
|
||||
// reset()
|
||||
// } else if (key === 'Enter') {
|
||||
// reset({leavemarks: "true"})
|
||||
// } else {
|
||||
pushKey(keys[0])
|
||||
// }
|
||||
return {keys: [], ex_str: ''}
|
||||
}
|
||||
108
src/hinting.ts
108
src/hinting.ts
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
import * as DOM from './dom'
|
||||
import {log} from './math'
|
||||
import {permutationsWithReplacement, islice, izip, map} from './itertools'
|
||||
import {permutationsWithReplacement, islice, izip, map, unique} from './itertools'
|
||||
import {hasModifiers} from './keyseq'
|
||||
import state from './state'
|
||||
import {messageActiveTab, message} from './messaging'
|
||||
|
|
@ -61,17 +61,25 @@ export function hintPage(
|
|||
buildHints(hintableElements, onSelect)
|
||||
|
||||
if (modeState.hints.length) {
|
||||
let sameLinks = false
|
||||
for (let hint of modeState.hints) {
|
||||
sameLinks = hint.target instanceof HTMLAnchorElement
|
||||
&& hint.target.href === (<HTMLAnchorElement>modeState.hints[0].target).href
|
||||
if (!sameLinks)
|
||||
break
|
||||
}
|
||||
if (sameLinks) {
|
||||
modeState.hints[0].select()
|
||||
reset()
|
||||
let firstTarget = modeState.hints[0].target
|
||||
let shouldSelect = firstTarget instanceof HTMLAnchorElement
|
||||
&& firstTarget.href !== ""
|
||||
&& !firstTarget.href.startsWith("javascript:")
|
||||
if (shouldSelect) {
|
||||
// Try to find an element that is not a link or that doesn't point
|
||||
// to the same URL as the first hint
|
||||
let different = modeState.hints.find(h => {
|
||||
return !(h.target instanceof HTMLAnchorElement)
|
||||
|| (h.target.href !== (<HTMLAnchorElement>firstTarget).href)
|
||||
})
|
||||
|
||||
if (different === undefined) {
|
||||
modeState.hints[0].select()
|
||||
reset()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("hints", modeState.hints)
|
||||
modeState.focusedHint = modeState.hints[0]
|
||||
modeState.focusedHint.focused = true
|
||||
|
|
@ -163,6 +171,17 @@ class Hint {
|
|||
public readonly filterData: any,
|
||||
private readonly onSelect: HintSelectedCallback
|
||||
) {
|
||||
// We need to compute the offset for elements that are in an iframe
|
||||
let offsetTop = 0
|
||||
let offsetLeft = 0
|
||||
if (target.ownerDocument !== document) {
|
||||
let iframe = DOM.getAllDocumentFrames()
|
||||
.find((frame) => frame.contentDocument == target.ownerDocument)
|
||||
let rect = iframe.getClientRects()[0]
|
||||
offsetTop += rect.top
|
||||
offsetLeft += rect.left
|
||||
}
|
||||
|
||||
const rect = target.getClientRects()[0]
|
||||
this.flag.textContent = name
|
||||
this.flag.className = 'TridactylHint'
|
||||
|
|
@ -171,11 +190,11 @@ class Hint {
|
|||
/* left: ${rect.left}px; */
|
||||
/* ` */
|
||||
this.flag.style.cssText = `
|
||||
top: ${window.scrollY + rect.top}px !important;
|
||||
left: ${window.scrollX + rect.left}px !important;
|
||||
top: ${window.scrollY + offsetTop + rect.top}px !important;
|
||||
left: ${window.scrollX + offsetLeft + rect.left}px !important;
|
||||
`
|
||||
modeState.hintHost.appendChild(this.flag)
|
||||
target.classList.add('TridactylHintElem')
|
||||
this.hidden = false
|
||||
}
|
||||
|
||||
// These styles would be better with pseudo selectors. Can we do custom ones?
|
||||
|
|
@ -185,8 +204,17 @@ class Hint {
|
|||
if (hide) {
|
||||
this.focused = false
|
||||
this.target.classList.remove('TridactylHintElem')
|
||||
} else
|
||||
if (config.get("theme") === "dark")
|
||||
{
|
||||
document.querySelector(':root').classList.remove("TridactylThemeDark")
|
||||
}
|
||||
} else {
|
||||
this.target.classList.add('TridactylHintElem')
|
||||
if (config.get("theme") === "dark")
|
||||
{
|
||||
document.querySelector(':root').classList.add("TridactylThemeDark")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set focused(focus: boolean) {
|
||||
|
|
@ -385,8 +413,13 @@ function pushKey(ke) {
|
|||
1. Within viewport
|
||||
2. Not hidden by another element
|
||||
*/
|
||||
function hintables(selectors=HINTTAGS_selectors) {
|
||||
return DOM.getElemsBySelector(selectors, [DOM.isVisible])
|
||||
function hintables(selectors=HINTTAGS_selectors, withjs=false) {
|
||||
let elems = DOM.getElemsBySelector(selectors, [])
|
||||
if (withjs) {
|
||||
elems = elems.concat(DOM.hintworthy_js_elems)
|
||||
elems = unique(elems)
|
||||
}
|
||||
return elems.filter(DOM.isVisible)
|
||||
}
|
||||
|
||||
function elementswithtext() {
|
||||
|
|
@ -497,20 +530,20 @@ function simulateClick(target: HTMLElement) {
|
|||
if ((target as HTMLAnchorElement).target === '_blank' ||
|
||||
(target as HTMLAnchorElement).target === '_new'
|
||||
) {
|
||||
openInNewTab((target as HTMLAnchorElement).href)
|
||||
openInNewTab((target as HTMLAnchorElement).href, {related: true})
|
||||
} else {
|
||||
DOM.mouseEvent(target, "click")
|
||||
// Sometimes clicking the element doesn't focus it sufficiently.
|
||||
target.focus()
|
||||
// DOM.focus has additional logic for focusing inputs
|
||||
DOM.focus(target)
|
||||
}
|
||||
}
|
||||
|
||||
function hintPageOpenInBackground() {
|
||||
hintPage(hintables(), hint=>{
|
||||
function hintPageOpenInBackground(selectors=HINTTAGS_selectors) {
|
||||
hintPage(hintables(selectors, true), hint=>{
|
||||
hint.target.focus()
|
||||
if (hint.target.href) {
|
||||
// Try to open with the webext API. If that fails, simulate a click on this page anyway.
|
||||
openInNewTab(hint.target.href, false).catch(()=>simulateClick(hint.target))
|
||||
openInNewTab(hint.target.href, {active: false, related: true}).catch(()=>simulateClick(hint.target))
|
||||
} else {
|
||||
// This is to mirror vimperator behaviour.
|
||||
simulateClick(hint.target)
|
||||
|
|
@ -518,8 +551,31 @@ function hintPageOpenInBackground() {
|
|||
})
|
||||
}
|
||||
|
||||
import {openInNewWindow} from './lib/webext'
|
||||
|
||||
function hintPageWindow() {
|
||||
hintPage(hintables(), hint=>{
|
||||
hint.target.focus()
|
||||
if (hint.target.href) {
|
||||
openInNewWindow({url: hint.target.href})
|
||||
} else {
|
||||
// This is to mirror vimperator behaviour.
|
||||
simulateClick(hint.target)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function hintPageWindowPrivate() {
|
||||
hintPage(hintables(), hint=>{
|
||||
hint.target.focus()
|
||||
if (hint.target.href) {
|
||||
openInNewWindow({url: hint.target.href, incognito: true})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function hintPageSimple(selectors=HINTTAGS_selectors) {
|
||||
hintPage(hintables(selectors), hint=>{
|
||||
hintPage(hintables(selectors, true), hint=>{
|
||||
simulateClick(hint.target)
|
||||
})
|
||||
}
|
||||
|
|
@ -560,7 +616,7 @@ function hintImage(inBackground) {
|
|||
let img_src = hint.target.getAttribute("src")
|
||||
|
||||
if (inBackground) {
|
||||
openInNewTab(new URL(img_src, window.location.href).href, false)
|
||||
openInNewTab(new URL(img_src, window.location.href).href, {active: false, related: true})
|
||||
} else {
|
||||
window.location.href = img_src
|
||||
}
|
||||
|
|
@ -636,6 +692,8 @@ addListener('hinting_content', attributeCaller({
|
|||
hintPageTextYank,
|
||||
hintPageAnchorYank,
|
||||
hintPageOpenInBackground,
|
||||
hintPageWindow,
|
||||
hintPageWindowPrivate,
|
||||
hintImage,
|
||||
hintFocus,
|
||||
hintRead,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,14 @@ export async function hintPageOpenInBackground() {
|
|||
return await messageActiveTab('hinting_content', 'hintPageOpenInBackground')
|
||||
}
|
||||
|
||||
export async function hintPageWindow() {
|
||||
return await messageActiveTab('hinting_content', 'hintPageWindow')
|
||||
}
|
||||
|
||||
export async function hintPageWindowPrivate() {
|
||||
return await messageActiveTab('hinting_content', 'hintPageWindowPrivate')
|
||||
}
|
||||
|
||||
export async function hintImage(inBackground) {
|
||||
return await messageActiveTab('hinting_content', 'hintImage', [inBackground])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,3 +121,24 @@ export function* map(arr, func) {
|
|||
for (const v of arr)
|
||||
yield func(v)
|
||||
}
|
||||
|
||||
// Returns an array of unique elements.
|
||||
export function unique(arr) {
|
||||
return arr.reduce((acc, cur) => {
|
||||
if (acc.indexOf(cur) == -1)
|
||||
acc.push(cur)
|
||||
return acc
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function flatten(arr) {
|
||||
let result = []
|
||||
for (let elem of arr) {
|
||||
if (elem instanceof Array) {
|
||||
result = result.concat(flatten(elem))
|
||||
} else {
|
||||
result.push(elem)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import * as Messaging from './messaging'
|
||||
import * as msgsafe from './msgsafe'
|
||||
import {isTextEditable} from './dom'
|
||||
import {isTextEditable,getAllDocumentFrames} from './dom'
|
||||
import {isSimpleKey} from './keyseq'
|
||||
|
||||
function keyeventHandler(ke: KeyboardEvent) {
|
||||
|
|
@ -30,6 +30,7 @@ import state from './state'
|
|||
|
||||
// Keys not to suppress in normal mode.
|
||||
const normalmodewhitelist = [
|
||||
// comment line below out once find mode is done
|
||||
'/',
|
||||
"'",
|
||||
' ',
|
||||
|
|
@ -54,6 +55,7 @@ function TerribleModeSpecificSuppression(ke: KeyboardEvent) {
|
|||
break
|
||||
// Hintmode can't clean up after itself yet, so it needs to block more FF shortcuts.
|
||||
case "hint":
|
||||
case "find":
|
||||
if (! hintmodewhitelist.includes(ke.key)) {
|
||||
ke.preventDefault()
|
||||
ke.stopImmediatePropagation()
|
||||
|
|
@ -82,6 +84,11 @@ function TerribleModeSpecificSuppression(ke: KeyboardEvent) {
|
|||
|
||||
// Add listeners
|
||||
window.addEventListener("keydown", keyeventHandler, true)
|
||||
document.addEventListener("readystatechange", ev =>
|
||||
getAllDocumentFrames().map(frame => {
|
||||
frame.contentWindow.removeEventListener("keydown", keyeventHandler, true)
|
||||
frame.contentWindow.addEventListener("keydown", keyeventHandler, true)
|
||||
}));
|
||||
import * as SELF from './keydown_content'
|
||||
Messaging.addListener('keydown_content', Messaging.attributeCaller(SELF))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,25 @@
|
|||
import * as convert from '../convert'
|
||||
import browserProxy from './browser_proxy'
|
||||
import * as config from '../config'
|
||||
|
||||
export function inContentScript() {
|
||||
return ! ('tabs' in browser)
|
||||
return getContext() == 'content'
|
||||
}
|
||||
|
||||
/** WebExt code can be run from three contexts:
|
||||
|
||||
Content script
|
||||
Extension page
|
||||
Background page
|
||||
*/
|
||||
export function getContext() {
|
||||
if (! ('tabs' in browser)) {
|
||||
return 'content'
|
||||
} else if (browser.runtime.getURL('_generated_background_page.html') == window.location.href) {
|
||||
return 'background'
|
||||
} else {
|
||||
return 'extension'
|
||||
}
|
||||
}
|
||||
|
||||
export let browserBg
|
||||
|
|
@ -53,17 +70,53 @@ export async function firefoxVersionAtLeast(desiredmajor: number) {
|
|||
return actualmajor >= desiredmajor
|
||||
}
|
||||
|
||||
/** Open a new tab with a URL as if that URL had been middle clicked on the current tab
|
||||
/** Simpler tabs.create option.
|
||||
|
||||
If related = true && relatedopenpos = 'related' then open a new tab with
|
||||
some URL as if that URL had been middle clicked on the current tab. If
|
||||
relatedopenpos = 'next', open it as the next tab. If 'last', open it last
|
||||
and don't tell Firefox who opened it.
|
||||
|
||||
Similarly for tabopenpos, but only tell FF that the newtab is related to
|
||||
the activeTab if tabopenpos == 'related'.
|
||||
|
||||
i.e. place that tab just after the current tab and set openerTabId
|
||||
*/
|
||||
export async function openInNewTab(url: string, active = true) {
|
||||
export async function openInNewTab(url: string, kwargs: {active?, related?} = {active: true, related: false}) {
|
||||
const thisTab = await activeTab()
|
||||
const options: any = {
|
||||
active,
|
||||
active: kwargs.active,
|
||||
url,
|
||||
index: thisTab.index + 1,
|
||||
}
|
||||
if (await l(firefoxVersionAtLeast(57))) options.openerTabId = thisTab.id
|
||||
|
||||
// Be nice to behrmann, #342
|
||||
let pos
|
||||
if (kwargs.related) pos = config.get('relatedopenpos')
|
||||
else pos = config.get('tabopenpos')
|
||||
switch (pos) {
|
||||
case 'next':
|
||||
options.index = thisTab.index + 1
|
||||
if (kwargs.related && await l(firefoxVersionAtLeast(57)))
|
||||
options.openerTabId = thisTab.id
|
||||
break
|
||||
case 'last':
|
||||
// Infinity can't be serialised, apparently.
|
||||
options.index = 99999
|
||||
break
|
||||
case 'related':
|
||||
if (await l(firefoxVersionAtLeast(57))) {
|
||||
options.openerTabId = thisTab.id
|
||||
} else {
|
||||
options.index = thisTab.index + 1
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return browserBg.tabs.create(options)
|
||||
}
|
||||
|
||||
// lazily copied from excmds.ts' winopen - forceURI really ought to be moved to lib/webext
|
||||
// Should consider changing interface of this to match openInNewTab or vice versa
|
||||
export async function openInNewWindow(createData = {}) {
|
||||
browserBg.windows.create(createData)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
# Inspired by QuantumVim: https://github.com/shinglyu/QuantumVim
|
||||
|
||||
BACKGROUND_COLOUR = "yellow"
|
||||
|
||||
highlight_links = () ->
|
||||
links = document.querySelectorAll('a') # Just handles links, not buttons/inputs etc.
|
||||
code = 0
|
||||
for link in links
|
||||
link_highlight(link, code)
|
||||
code +=1
|
||||
|
||||
console.log(LINK_CODES)
|
||||
return code
|
||||
|
||||
LINK_CODES = {}
|
||||
|
||||
link_highlight = (elem, code) ->
|
||||
elem._background = elem.style.backgroundColor
|
||||
elem._position = elem.style.position
|
||||
# Why are these being saved?
|
||||
elem.style.backgroundColor = BACKGROUND_COLOUR
|
||||
elem.style.position="relative"
|
||||
codehint = generate_codehint(code)
|
||||
elem.appendChild(codehint)
|
||||
LINK_CODES[String(code)] = {
|
||||
element: elem
|
||||
codehint
|
||||
}
|
||||
|
||||
generate_codehint = (code) ->
|
||||
codehint = document.createElement('span')
|
||||
codehint.textContent = "" + code
|
||||
codehint.style.border = "solid 1 px black"
|
||||
codehint.style.backgroundColor="white"
|
||||
codehint.style.font="12px/14px bold sans-serif"
|
||||
codehint.style.color="darkred"
|
||||
codehint.style.position="absolute"
|
||||
codehint.style.top="0"
|
||||
codehint.style.left="0"
|
||||
codehint.style.padding="0.1em"
|
||||
codehint
|
||||
|
||||
linkMessageHandler = (message) ->
|
||||
switch(message.command)
|
||||
when "hint" then highlight_links()
|
||||
|
||||
browser.runtime.onMessage.addListener(linkMessageHandler)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Tridactyl",
|
||||
"version": "1.7.3",
|
||||
"version": "1.8.2",
|
||||
"icons": {
|
||||
"64": "static/logo/Tridactyl_64px.png",
|
||||
"100": "static/logo/Tridactyl_100px.png",
|
||||
|
|
@ -26,7 +26,9 @@
|
|||
"css": [
|
||||
"static/cleanslate.css",
|
||||
"static/content.css",
|
||||
"static/hint.css"
|
||||
"static/content-dark.css",
|
||||
"static/hint.css",
|
||||
"static/viewsource.css"
|
||||
],
|
||||
"run_at": "document_start",
|
||||
"match_about_blank": true
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ export type TabMessageType =
|
|||
"keydown_content" |
|
||||
"commandline_content" |
|
||||
"commandline_frame" |
|
||||
"hinting_content"
|
||||
"hinting_content" |
|
||||
"finding_content"
|
||||
export type NonTabMessageType =
|
||||
"keydown_background" |
|
||||
"commandline_background" |
|
||||
|
|
|
|||
|
|
@ -50,8 +50,6 @@ export function parser(ex_str: string): any[] {
|
|||
|
||||
if (ExCmds.cmd_params.has(func)) {
|
||||
try {
|
||||
let typedArgs = convertArgs(ExCmds.cmd_params.get(func), args)
|
||||
console.log(ex_str, typedArgs)
|
||||
return [ExCmds[func], convertArgs(ExCmds.cmd_params.get(func), args)]
|
||||
} catch (e) {
|
||||
console.error("Error executing or parsing:", ex_str, e)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import Logger from './logging'
|
||||
const logger = new Logger('state')
|
||||
|
||||
export type ModeName = 'normal' | 'insert' | 'hint' | 'ignore' | 'gobble' | 'input'
|
||||
export type ModeName = 'normal' | 'insert' | 'hint' | 'ignore' | 'gobble' | 'input' | 'find'
|
||||
class State {
|
||||
mode: ModeName = 'normal'
|
||||
cmdHistory: string[] = []
|
||||
|
|
|
|||
35
src/static/commandline-dark.css
Normal file
35
src/static/commandline-dark.css
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
:root.dark {
|
||||
--foreground: #ccc;
|
||||
--foreground-bright: #fff;
|
||||
--background: #111;
|
||||
}
|
||||
|
||||
:root.dark input {
|
||||
color: var(--foreground-bright);
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
:root.dark #completions {
|
||||
color: var(--foreground);
|
||||
background: #474747;
|
||||
border-top: 0.5px solid var(--background);
|
||||
}
|
||||
|
||||
:root.dark #completions table tr td {
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
:root.dark #completions .sectionHeader {
|
||||
color: var(--foreground-bright);
|
||||
background: linear-gradient(#333, #222, #111);
|
||||
}
|
||||
|
||||
:root.dark .option:not(.focused) .url {
|
||||
color: var(--foreground);
|
||||
background: #333;
|
||||
}
|
||||
|
||||
:root.dark .focused, :root.dark .focused .url {
|
||||
color: var(--foreground-bright);
|
||||
background: #204e8a;
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ input {
|
|||
font-size: 9pt;
|
||||
line-height: 1.5;
|
||||
color: black;
|
||||
background: white;
|
||||
border: unset;
|
||||
/* we currently have a border from the completions */
|
||||
/* border-top: solid 1px lightgray; */
|
||||
|
|
@ -20,8 +21,8 @@ input {
|
|||
|
||||
#completions {
|
||||
--option-height: 1.4em;
|
||||
background: white;
|
||||
color: black;
|
||||
background: white;
|
||||
display: inline-block;
|
||||
font-size: 10pt;
|
||||
font-family: monospace;
|
||||
|
|
@ -135,6 +136,6 @@ a.url:hover {
|
|||
|
||||
/* Pick the .url out especially because otherwise its link styles dominate. */
|
||||
.focused, .focused .url {
|
||||
background: #FFEC8B;
|
||||
color: black;
|
||||
background: #FFEC8B;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="cleanslate.css">
|
||||
<link rel="stylesheet" href="commandline.css">
|
||||
<link rel="stylesheet" href="commandline-dark.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="completions"></div>
|
||||
<input id=tridactyl-input></input>
|
||||
<input id="tridactyl-input"></input>
|
||||
<script src="../commandline_frame.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
18
src/static/content-dark.css
Normal file
18
src/static/content-dark.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
:root.TridactylThemeDark span.TridactylHint {
|
||||
color: white !important;
|
||||
background-color: #204e8a !important;
|
||||
}
|
||||
|
||||
:root.TridactylThemeDark .TridactylHintElem, :root.TridactylThemeDark .TridactylHintActive {
|
||||
color: #333 !important !important;
|
||||
}
|
||||
|
||||
:root.TridactylThemeDark .TridactylHintElem {
|
||||
background-color: rgba(13, 31, 54, 0.25) !important;
|
||||
outline: 1px solid #204e8a !important;
|
||||
}
|
||||
|
||||
:root.TridactylThemeDark .TridactylHintActive {
|
||||
background-color: #88FF00 !important;
|
||||
outline: 1px solid #000 !important;
|
||||
}
|
||||
|
|
@ -1,40 +1,54 @@
|
|||

|
||||
|
||||
# Tridactyl new tab page
|
||||
# Tridactyl REPLACE_ME_WITH_THE_VERSION_USING_SED
|
||||
|
||||
Tridactyl overrides your newtab page because it cannot insert its content script on the default about:newtab. Without the content script, our shortcuts won't work, even if you're just passing through the page.
|
||||
Tridactyl has to override your new tab page due to WebExtension limitations. You can learn how to change it at the bottom of the page, otherwise please read on for some tips and tricks. (If you've just installed Tridactyl for the first time and are seeing this page, hello! Welcome! You'll probably be seeing this page a lot until you figure out how to disable it or uninstall the addon).
|
||||
|
||||
We're working with Firefox on improvements to the WebExtension APIs that will remove this restriction.
|
||||
- You can view the main help page by typing [`:help`][help].
|
||||
|
||||
In the meantime, here are some notes about Tridactyl:
|
||||
- You can contact the developers, other users and contributors for support or whatever on [Matrix][matrix-link], [Gitter][gitter-link], or [IRC][freenode-link].
|
||||
|
||||
- Tridactyl is in a pretty early stage of development. Please report any issues and make requests for missing features on the GitHub project page [[1]].
|
||||
## Highlighted features:
|
||||
|
||||
- You can view the main help page by typing [`:help`][help]
|
||||
- `f`/`F` — enter the "hint mode" to select a link to follow. `F` to open it in a background tab.
|
||||
- `I` — enter ignore mode to send all key presses to the web page you are on. `Shift` + `Escape` gets you back to the highly productive normal mode.
|
||||
- `H`/`L` — go back/forward in the history.
|
||||
- `o`/`O` — open a URL in this tab (`O` to pre-load current URL).
|
||||
- `t`/`T` — open a URL in a new tab (`T` to pre-load current URL).
|
||||
- `gt`/`gT` — go to the next/previous tab.
|
||||
- `d` — close the current tab.
|
||||
- `/` — open the find search box.
|
||||
- `A` — bookmark the current page
|
||||
- `b` — bring up a list of open tabs in the current window.
|
||||
- `s` — if you want to search for something that looks like a domain name or URL.
|
||||
- `gi` — scroll to and focus the last-used input on the page.
|
||||
- `gr` — open Firefox reader mode (note: Tridactyl will not work in this mode).
|
||||
- Bind your own commands with, e.g., `:bind J tabprev`. Type `:help bind` to see help on custom binds.
|
||||
- `yy` — copy the current page URL to your clipboard.
|
||||
- `[[`/`]]` — navigate forward/backward though paginated pages.
|
||||
- `ZZ` — 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".
|
||||
- [`:help hint`][help-hint] to see all the other useful hint modes (this is the `f` magic. :) ).
|
||||
|
||||
- You can contact the developers and other users and contributors for support, to discuss pull requests or whatever on [Matrix][matrix-link], [Gitter][gitter-link], or [IRC][freenode-link].
|
||||
|
||||
Highlighted features:
|
||||
|
||||
- Press `b` to bring up a list of open tabs in the current window; you can type the tab ID or part of the title or URL to choose a tab
|
||||
- Press `I` to enter ignore mode. `Shift` + `Escape` to return to normal mode.
|
||||
- Press `f` to start "hint mode", `F` to open in background
|
||||
- Type [`:help hint`][help-hint] to see all the other useful hint modes
|
||||
- Press `o` to `:open` a different page
|
||||
- Press `s` if you want to search for something that looks like a domain name or URL
|
||||
- Bind new commands with e.g. `:bind J tabprev`. Type `:help bind` to see help on custom binds.
|
||||
- Type [`:help`][help] for online help
|
||||
- Use `yy` to copy the current page URL to your clipboard
|
||||
- `]]` and `[[` to navigate through the pages of comics, paginated articles, etc.
|
||||
- Pressing `ZZ` will close all tabs and windows, but it will only "save" them if your about:preferences are set to "show your tabs and windows from last time"
|
||||
|
||||
There are some caveats common to all webextension vimperator-alikes:
|
||||
## Important limitations due to WebExtensions
|
||||
|
||||
- Do not try to navigate to any about:\* pages using `:open` as it will fail silently.
|
||||
- Firefox will not load Tridactyl on addons.mozilla.org, about:\*, some file:\* URIs, view-source:\*, or data:\*. On these pages Ctrl-L (or F6), Ctrl-Tab and Ctrl-W are your escape hatches.
|
||||
- Tridactyl does not currently support changing/hiding the Firefox GUI, but you can do it yourself by changing your userChrome. There is an example file available on our repository [[2]].
|
||||
- Tridactyl cannot capture key presses until web pages are loaded. You can use `:reloadall` to reload all tabs to make life more bearable, or flip `browser.sessionstore.restore_tabs_lazily` to false in `about:config`.
|
||||
|
||||
If you want a more fully-featured vimperator-alike, your best option is Firefox ESR [[3]] and Vimperator :)
|
||||
## Why do I see this here?
|
||||
|
||||
Tridactyl overrides your newtab page because it cannot insert its content script on the default about:newtab. Without the content script, our shortcuts won't work, even if you're just passing through the page. We're working with Firefox on improvements to the WebExtension APIs that will remove this restriction.
|
||||
|
||||
### How can I get rid of it?
|
||||
|
||||
- `:set newtab [URL]`
|
||||
- If you just want a blank page, you can use `set newtab data:text/html, <html style="background:white"></html>`. You can replace "white" with any CSS colour of your choosing.
|
||||
- `d`, Alt-F4, Ctrl-W and other such jokes.
|
||||
|
||||
## FAQ
|
||||
|
||||
You have more questions? Have a look at our [FAQ][faq-link].
|
||||
|
||||
[1]: https://github.com/cmcaine/tridactyl/issues
|
||||
[2]: https://github.com/cmcaine/tridactyl/blob/master/src/static/userChrome-minimal.css
|
||||
|
|
@ -46,6 +60,7 @@ If you want a more fully-featured vimperator-alike, your best option is Firefox
|
|||
\[3]: https://www.mozilla.org/en-US/firefox/organizations/<br />
|
||||
</div>
|
||||
|
||||
[faq-link]: https://github.com/cmcaine/tridactyl#frequently-asked-questions
|
||||
[help]: /static/docs/modules/_excmds_.html
|
||||
[help-hint]: /static/docs/modules/_excmds_.html#hint
|
||||
[gitter-badge]: /static/badges/gitter-badge.svg
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="newtab.css">
|
||||
<link rel="stylesheet" href="content.css">
|
||||
<link rel="stylesheet" href="content-dark.css">
|
||||
<link rel="stylesheet" href="hint.css">
|
||||
<link rel="shortcut icon" href="logo/Tridactyl_64px.png">
|
||||
</head>
|
||||
|
|
|
|||
0
src/static/userChrome-minimal.css
Executable file → Normal file
0
src/static/userChrome-minimal.css
Executable file → Normal file
16
src/static/viewsource.css
Normal file
16
src/static/viewsource.css
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
#TridactylViewsourceElement {
|
||||
position: absolute !important;
|
||||
/* This is the z-index of hint.css and content.css -1 */
|
||||
z-index: 2147483646 !important;
|
||||
top: 0px !important;
|
||||
font-family: monospace !important;
|
||||
background: white !important;
|
||||
white-space: pre !important;
|
||||
min-width: 100% !important;
|
||||
}
|
||||
|
||||
#TridactylViewsourceElement.dark {
|
||||
background: #111 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
6
src/tridactyl.d.ts
vendored
6
src/tridactyl.d.ts
vendored
|
|
@ -9,13 +9,15 @@ interface Number {
|
|||
clamp(lo: number, hi: number): number
|
||||
}
|
||||
|
||||
|
||||
// Firefox-specific dom properties
|
||||
interface Window {
|
||||
scrollByLines(n: number): void
|
||||
scrollByPages(n: number): void
|
||||
scrollByPages(n: number): void
|
||||
eval(str: string): any
|
||||
}
|
||||
|
||||
declare function exportFunction(func: Function, targetScope: object, options?: {defineAs?: string, allowCrossOriginArguments?: boolean}): Function
|
||||
|
||||
// Fix typescript bugs
|
||||
interface StringConstructor {
|
||||
toLowerCase(): string;
|
||||
|
|
|
|||
Loading…
Reference in a new issue