Merge branch 'master' of github.com:cmcaine/tridactyl into glacambre-add_prettier

This commit is contained in:
Oliver Blanthorn 2018-04-13 18:54:34 +01:00
commit ebde6d63e4
No known key found for this signature in database
GPG key ID: 2BB8C36BB504BFF3
38 changed files with 1143 additions and 231 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ src/excmds_content.ts
generated
web-ext-artifacts
yarn.lock
.vscode/

View file

@ -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

View file

@ -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
View file

@ -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": {

View file

@ -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",

View file

@ -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

View 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"

View file

@ -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

View file

@ -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
}

View file

@ -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:

View file

@ -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))

View file

@ -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)
}

View file

@ -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()
}
}

View file

@ -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 !== "")

View file

@ -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"
}

View file

@ -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
}
}

View file

@ -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
View 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
View 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: ''}
}

View file

@ -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,

View file

@ -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])
}

View file

@ -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
}

View file

@ -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))

View file

@ -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)
}

View file

@ -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)

View file

@ -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

View file

@ -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" |

View file

@ -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)

View file

@ -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[] = []

View 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;
}

View file

@ -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;
}

View file

@ -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>

View 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;
}

View file

@ -1,40 +1,54 @@
![Tridactyl logo](logo/Tridactyl_100px.png)
# 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

View file

@ -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
View file

16
src/static/viewsource.css Normal file
View 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
View file

@ -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;