mirror of
https://github.com/vale981/tridactyl
synced 2025-03-04 17:11:40 -05:00
Merge branch 'master' of github.com:cmcaine/tridactyl into configdocs
This commit is contained in:
commit
fd218e060b
38 changed files with 886 additions and 430 deletions
|
@ -52,9 +52,9 @@ quickmark t https://github.com/cmcaine/tridactyl/issues?utf8=%E2%9C%93&q=sort%3A
|
|||
"
|
||||
|
||||
" New reddit is bad
|
||||
autocmd DocStart www.reddit.com js tri.excmds.urlmodify("-t www old")
|
||||
autocmd DocStart www.reddit.com js tri.excmds.urlmodify("-t", "www", "old")
|
||||
" Mosquito nets won't make themselves
|
||||
autocmd DocStart www.amazon.co.uk js tri.excmds.urlmodify("-t www smile")
|
||||
autocmd DocStart www.amazon.co.uk js tri.excmds.urlmodify("-t", "www", "smile")
|
||||
|
||||
" This will have to do until someone writes us a nice syntax file :)
|
||||
" vim: set filetype=vim:
|
||||
|
|
43
CHANGELOG.md
43
CHANGELOG.md
|
@ -1,5 +1,48 @@
|
|||
# Tridactyl changelog
|
||||
|
||||
## Release 1.13.3 / 2018-08-21
|
||||
|
||||
* New features:
|
||||
|
||||
* Our command line now has more completions:
|
||||
|
||||
* Ex commands and ex aliases with a little bit of help for each command.
|
||||
* Settings, showing their current value (currently does not support options within options)
|
||||
|
||||
* Rapid hint mode improvements:
|
||||
* Less jank (particularly if you hold a key down)
|
||||
* Most hint modes now have a rapid mode with `hint -q[flag]` and bound to `;g[key]`
|
||||
* The divergence from Pentadactyl is because we already have `g;` bound to "switch to tab containing last used input field and focus it", which is my pet favourite command.
|
||||
* `tab` is now an alias for `buffer` (I meant to add this months ago)
|
||||
|
||||
* Old features:
|
||||
|
||||
* More hint modes from Pentadactyl that no-one missed added:
|
||||
* `;O`, `;W`, and `;T` pre-fill the command line with the hinted URL and open/tabopen/winopen.
|
||||
* Added I, Shift-Escape ignore binds back
|
||||
* You can unbind them with `unbind --mode=... [key]`
|
||||
|
||||
* Bug fixes:
|
||||
|
||||
* Yank element text hint mode was broken (`;p`) and we fixed it by accident
|
||||
* You can now unbind keys which were bound to `Esc` by default (#921)
|
||||
* Less console spam: fixed "1.1" error if config was at latest version
|
||||
* Our command line now plays nicely with left scrollbars (#909)
|
||||
* `guiset gui none` now pads maximised windows to fix a bug on Windows where the top of the page is cut off
|
||||
|
||||
* Boring internal changes:
|
||||
|
||||
* All hint modes now use a newfangled method with less message passing
|
||||
* We're now using Typescript 3
|
||||
* We now generate a bunch of metadata about Tridactyl for use with introspection
|
||||
* As an added bonus, build times are now a bit slower.
|
||||
|
||||
Thanks to all of our contributors for this release: Oliver Blanthorn, glacambre, Anton Vilhelm Ásgeirsson, and Henré Botha.
|
||||
|
||||
Extra special thanks go to Henré Botha, who contributed for the first time.
|
||||
|
||||
Last, but not least - thank you to everyone who reported issues.
|
||||
|
||||
## Release 1.13.2 / 2018-08-15
|
||||
|
||||
* New features:
|
||||
|
|
|
@ -2,10 +2,99 @@ import * as ts from "typescript"
|
|||
import * as fs from "fs"
|
||||
import * as commandLineArgs from "command-line-args"
|
||||
|
||||
class SimpleType {
|
||||
name: string
|
||||
kind: string
|
||||
// Support for generics is mostly done but it might not be needed for now
|
||||
// generics: Array<SimpleType>
|
||||
arguments: Array<SimpleType>
|
||||
type: SimpleType
|
||||
|
||||
constructor(typeNode) {
|
||||
switch (typeNode.kind) {
|
||||
case ts.SyntaxKind.VoidKeyword:
|
||||
this.kind = "void"
|
||||
break
|
||||
case ts.SyntaxKind.AnyKeyword:
|
||||
this.kind = "any"
|
||||
break
|
||||
case ts.SyntaxKind.BooleanKeyword:
|
||||
this.kind = "boolean"
|
||||
break
|
||||
case ts.SyntaxKind.NumberKeyword:
|
||||
this.kind = "number"
|
||||
break
|
||||
case ts.SyntaxKind.ObjectKeyword:
|
||||
this.kind = "object"
|
||||
break
|
||||
case ts.SyntaxKind.StringKeyword:
|
||||
this.kind = "string"
|
||||
break
|
||||
case ts.SyntaxKind.Parameter:
|
||||
// 149 is "Parameter". We don't care about that so let's
|
||||
// convert its type into a SimpleType and grab what we need from it
|
||||
let ttype = new SimpleType(typeNode.type)
|
||||
this.kind = ttype.kind
|
||||
// this.generics = ttype.generics
|
||||
this.arguments = ttype.arguments
|
||||
this.type = ttype.type
|
||||
this.name = typeNode.name.original.escapedText
|
||||
break
|
||||
case ts.SyntaxKind.TypeReference:
|
||||
// 162 is "TypeReference". Not sure what the rules are here but it seems to be used for generics
|
||||
this.kind = typeNode.typeName.escapedText
|
||||
if (typeNode.typeArguments) {
|
||||
this.arguments = typeNode.typeArguments.map(
|
||||
t => new SimpleType(typeNode.typeArguments[0]),
|
||||
)
|
||||
}
|
||||
break
|
||||
case ts.SyntaxKind.FunctionType:
|
||||
this.kind = "function"
|
||||
// Probably don't need generics for now
|
||||
// this.generics = (typeNode.typeParameters || []).map(p => new SimpleType(p))
|
||||
this.arguments = typeNode.parameters.map(p => new SimpleType(p))
|
||||
this.type = new SimpleType(typeNode.type)
|
||||
break
|
||||
case ts.SyntaxKind.TypeLiteral:
|
||||
// This is a type literal. i.e., something like this: { [str: string]: string[] }
|
||||
// Very complicated and perhaps not useful to know about. Let's just say "object" for now
|
||||
this.kind = "object"
|
||||
break
|
||||
case ts.SyntaxKind.ArrayType:
|
||||
this.kind = "array"
|
||||
this.type = new SimpleType(typeNode.elementType)
|
||||
break
|
||||
case ts.SyntaxKind.TupleType:
|
||||
this.kind = "tuple"
|
||||
this.arguments = typeNode.elementTypes.map(
|
||||
t => new SimpleType(t),
|
||||
)
|
||||
break
|
||||
case ts.SyntaxKind.UnionType:
|
||||
this.kind = "union"
|
||||
this.arguments = typeNode.types.map(t => new SimpleType(t))
|
||||
break
|
||||
case ts.SyntaxKind.LiteralType:
|
||||
// "LiteralType". I'm not sure what this is. Probably things like type a = "b" | "c"
|
||||
this.kind = "LiteralType"
|
||||
this.name = typeNode.literal.text
|
||||
break
|
||||
default:
|
||||
console.log(typeNode)
|
||||
throw new Error(
|
||||
`Unhandled kind (${typeNode.kind}) for ${typeNode}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** True if this is visible outside this file, false otherwise */
|
||||
function isNodeExported(node: ts.Node): boolean {
|
||||
return (
|
||||
(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0 ||
|
||||
(ts.getCombinedModifierFlags(<ts.Declaration>node) &
|
||||
ts.ModifierFlags.Export) !==
|
||||
0 ||
|
||||
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
|
||||
)
|
||||
}
|
||||
|
@ -33,7 +122,11 @@ function visit(checker: any, filename: string, node: any, everything: any) {
|
|||
symbol,
|
||||
symbol.valueDeclaration!,
|
||||
)
|
||||
if (ttype) nodeInfo["type"] = checker.typeToString(ttype)
|
||||
if (ttype) {
|
||||
nodeInfo["type"] = new SimpleType(
|
||||
checker.typeToTypeNode(ttype),
|
||||
)
|
||||
}
|
||||
}
|
||||
break
|
||||
// Other declaration syntaxkinds:
|
||||
|
@ -61,7 +154,12 @@ function visit(checker: any, filename: string, node: any, everything: any) {
|
|||
ts.forEachChild(node, node => visit(checker, filename, node, everything))
|
||||
}
|
||||
|
||||
function generateMetadata(out: string, fileNames: string[]): void {
|
||||
function generateMetadata(
|
||||
out: string,
|
||||
themedir: string,
|
||||
fileNames: string[],
|
||||
): void {
|
||||
/* Parse Tridactyl */
|
||||
let program = ts.createProgram(fileNames, {
|
||||
target: ts.ScriptTarget.ES5,
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
|
@ -74,23 +172,33 @@ function generateMetadata(out: string, fileNames: string[]): void {
|
|||
if (n) visit(program.getTypeChecker(), n, sourceFile, everything)
|
||||
}
|
||||
|
||||
let metadataString = `\nexport let everything = ${JSON.stringify(
|
||||
everything,
|
||||
undefined,
|
||||
4,
|
||||
)}\n`
|
||||
|
||||
if (themedir) {
|
||||
metadataString += `\nexport let staticThemes = ${JSON.stringify(
|
||||
fs.readdirSync(themedir),
|
||||
)}\n`
|
||||
}
|
||||
|
||||
// print out the doc
|
||||
fs.writeFileSync(
|
||||
out,
|
||||
"export let everything = " + JSON.stringify(everything, undefined, 4),
|
||||
)
|
||||
fs.writeFileSync(out, metadataString)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let opts = commandLineArgs([
|
||||
{ name: "out", type: String },
|
||||
{ name: "themeDir", type: String },
|
||||
{ name: "src", type: String, multiple: true, defaultOption: true },
|
||||
])
|
||||
|
||||
if (!opts.out)
|
||||
if (!opts.out || opts.src.length < 1)
|
||||
throw new Error(
|
||||
"Argument syntax: --out outfile [--src] file1.ts [file2.ts ...]",
|
||||
)
|
||||
|
||||
generateMetadata(opts.out, opts.src)
|
||||
generateMetadata(opts.out, opts.themeDir, opts.src)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source ./scripts/common
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
|
@ -22,11 +22,18 @@ manifest_loc="https://raw.githubusercontent.com/cmcaine/tridactyl/master/native/
|
|||
native_loc="https://raw.githubusercontent.com/cmcaine/tridactyl/master/native/native_main.py"
|
||||
|
||||
# Decide where to put the manifest based on OS
|
||||
if [[ "$OSTYPE" == "linux-gnu" ]]; then
|
||||
manifest_home="$HOME/.mozilla/native-messaging-hosts/"
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
manifest_home="$HOME/Library/Application Support/Mozilla/NativeMessagingHosts/"
|
||||
fi
|
||||
case "$OSTYPE" in
|
||||
linux-gnu|linux|freebsd*)
|
||||
manifest_home="$HOME/.mozilla/native-messaging-hosts/"
|
||||
;;
|
||||
darwin*)
|
||||
manifest_home="$HOME/Library/Application Support/Mozilla/NativeMessagingHosts/"
|
||||
;;
|
||||
*)
|
||||
# Fallback to default Linux location for unknown OSTYPE
|
||||
manifest_home="$HOME/.mozilla/native-messaging-hosts/"
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p "$manifest_home" "$XDG_DATA_HOME"
|
||||
|
||||
|
|
132
package-lock.json
generated
132
package-lock.json
generated
|
@ -113,25 +113,42 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/css/-/css-0.0.31.tgz",
|
||||
"integrity": "sha512-Xt3xp8o0zueqrdIkAOO5gy5YNs+jYfmIUPeoeKiwrcmCRXuNWkIgR2Ph6vHuVfi1y6c9Tx214EQBWPEkU97djw=="
|
||||
},
|
||||
"@types/events": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
|
||||
"integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/fs-extra": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-4.0.0.tgz",
|
||||
"integrity": "sha512-PlKJw6ujJXLJjbvB3T0UCbY3jibKM6/Ya5cc9j1q+mYDeK3aR4Dp+20ZwxSuvJr9mIoPxp7+IL4aMOEvsscRTA==",
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.4.tgz",
|
||||
"integrity": "sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "5.0.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.35.tgz",
|
||||
"integrity": "sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/events": "*",
|
||||
"@types/minimatch": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/handlebars": {
|
||||
"version": "4.0.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.31.tgz",
|
||||
"integrity": "sha1-p/umb6/kJxOu6I7sqNuRGS7+bnI=",
|
||||
"version": "4.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.39.tgz",
|
||||
"integrity": "sha512-vjaS7Q0dVqFp85QhyPSZqDKnTTCemcSHNHFvDdalO1s0Ifz5KuE64jQD5xoUkfdWwF4WpqdJEl7LsWH8rzhKJA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/highlight.js": {
|
||||
"version": "9.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.1.8.tgz",
|
||||
"integrity": "sha1-0ifxi8uPPxh+FpZfJESFmgRol1g=",
|
||||
"version": "9.12.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.3.tgz",
|
||||
"integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/jest": {
|
||||
|
@ -141,21 +158,21 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.74",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.74.tgz",
|
||||
"integrity": "sha512-BZknw3E/z3JmCLqQVANcR17okqVTPZdlxvcIz0fJiJVLUCbSH1hK3zs9r634PVSmrzAxN+n/fxlVRiYoArdOIQ==",
|
||||
"version": "4.14.116",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.116.tgz",
|
||||
"integrity": "sha512-lRnAtKnxMXcYYXqOiotTmJd74uawNWuPnsnPrrO7HiFuE3npE2iQhfABatbYDyxTNqZNuXzcKGhw37R7RjBFLg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/marked": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.3.0.tgz",
|
||||
"integrity": "sha512-CSf9YWJdX1DkTNu9zcNtdCcn6hkRtB5ILjbhRId4ZOQqx30fXmdecuaXhugQL6eyrhuXtaHJ7PHI+Vm7k9ZJjg==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.4.0.tgz",
|
||||
"integrity": "sha512-xkURX55US18wHme+O2UlqJf3Fo7FqT5VAL+OJ/zK+jP2NX57naryDHoiqt/pMIwZjDc62sRvXUWuQQxQiBdheQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "2.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-2.0.29.tgz",
|
||||
"integrity": "sha1-UALhT3Xi1x5WQoHfBDHIwbSio2o=",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/nearley": {
|
||||
|
@ -170,11 +187,12 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/shelljs": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.7.0.tgz",
|
||||
"integrity": "sha1-IpwVfGvB5n1rmQ5sXhjb0v9Yz/A=",
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.0.tgz",
|
||||
"integrity": "sha512-vs1hCC8RxLHRu2bwumNyYRNrU3o8BtZhLysH5A4I98iYmA2APl6R3uNQb5ihl+WiwH0xdC9LLO+vRrXLs/Kyxg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/glob": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
|
@ -11800,47 +11818,51 @@
|
|||
"dev": true
|
||||
},
|
||||
"typedoc": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.9.0.tgz",
|
||||
"integrity": "sha512-numP0CtcUK4I1Vssw6E1N/FjyJWpWqhLT4Zb7Gw3i7ca3ElnYh6z41Y/tcUhMsMYn6L8b67E/Fu4XYYKkNaLbA==",
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.12.0.tgz",
|
||||
"integrity": "sha512-dsdlaYZ7Je8JC+jQ3j2Iroe4uyD0GhqzADNUVyBRgLuytQDP/g0dPkAw5PdM/4drnmmJjRzSWW97FkKo+ITqQg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/fs-extra": "4.0.0",
|
||||
"@types/handlebars": "4.0.31",
|
||||
"@types/highlight.js": "9.1.8",
|
||||
"@types/lodash": "4.14.74",
|
||||
"@types/marked": "0.3.0",
|
||||
"@types/minimatch": "2.0.29",
|
||||
"@types/shelljs": "0.7.0",
|
||||
"fs-extra": "^4.0.0",
|
||||
"@types/fs-extra": "^5.0.3",
|
||||
"@types/handlebars": "^4.0.38",
|
||||
"@types/highlight.js": "^9.12.3",
|
||||
"@types/lodash": "^4.14.110",
|
||||
"@types/marked": "^0.4.0",
|
||||
"@types/minimatch": "3.0.3",
|
||||
"@types/shelljs": "^0.8.0",
|
||||
"fs-extra": "^7.0.0",
|
||||
"handlebars": "^4.0.6",
|
||||
"highlight.js": "^9.0.0",
|
||||
"lodash": "^4.13.1",
|
||||
"marked": "^0.3.5",
|
||||
"lodash": "^4.17.10",
|
||||
"marked": "^0.4.0",
|
||||
"minimatch": "^3.0.0",
|
||||
"progress": "^2.0.0",
|
||||
"shelljs": "^0.7.0",
|
||||
"shelljs": "^0.8.2",
|
||||
"typedoc-default-themes": "^0.5.0",
|
||||
"typescript": "2.4.1"
|
||||
"typescript": "3.0.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"marked": {
|
||||
"version": "0.3.19",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
|
||||
"integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==",
|
||||
"dev": true
|
||||
"fs-extra": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz",
|
||||
"integrity": "sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz",
|
||||
"integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.1.tgz",
|
||||
"integrity": "sha1-w8yxbdqgsjFN4DHn5v7onlujRrw=",
|
||||
"dev": true
|
||||
"shelljs": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz",
|
||||
"integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.0.0",
|
||||
"interpret": "^1.0.0",
|
||||
"rechoir": "^0.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -11850,9 +11872,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz",
|
||||
"integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz",
|
||||
"integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==",
|
||||
"dev": true
|
||||
},
|
||||
"typical": {
|
||||
|
|
|
@ -27,9 +27,9 @@
|
|||
"source-map-loader": "^0.2.2",
|
||||
"ts-jest": "^21.1.3",
|
||||
"ts-node": "^3.3.0",
|
||||
"typedoc": "^0.9.0",
|
||||
"typedoc": "^0.12.0",
|
||||
"typedoc-default-themes": "git://github.com/glacambre/typedoc-default-themes#fix_weird_member_names_bin",
|
||||
"typescript": "^2.5.3",
|
||||
"typescript": "^3.0.1",
|
||||
"uglify-es": "^3.1.5",
|
||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||
"web-ext": "^2.7.0",
|
||||
|
|
|
@ -8,7 +8,7 @@ Replace Firefox's default control mechanism with one modelled on the one true ed
|
|||
|
||||
## Installing
|
||||
|
||||
[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). Alternatively, you can get our "stable" builds straight from [Mozilla][amo]. The changelog for the stable versions can be found [here](https://github.com/cmcaine/tridactyl/blob/master/CHANGELOG.md). If you want to use advanced features such as edit-in-Vim, you'll also need to install the native messenger or executable, instructions for which can be found by typing `:installnative` and hitting enter once you are in Tridactyl.
|
||||
[Simply click this link in Firefox to install our latest "beta" build][riskyclick]. These [betas][betas] 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). Alternatively, you can get our "stable" builds straight from [Mozilla][amo]. The changelog for the stable versions can be found [here](https://github.com/cmcaine/tridactyl/blob/master/CHANGELOG.md). If you want to use advanced features such as edit-in-Vim, you'll also need to install the native messenger or executable, instructions for which can be found by typing `:installnative` and hitting enter once you are in Tridactyl.
|
||||
|
||||
Type `:help` or press `<F1>` for online help once you're in :)
|
||||
|
||||
|
@ -363,4 +363,5 @@ The logo was designed by Jake Beazley using free vector art by <a target="_blank
|
|||
[matrix-badge]: https://matrix.to/img/matrix-badge.svg
|
||||
[matrix-link]: https://riot.im/app/#/room/#tridactyl:matrix.org
|
||||
[betas]: https://tridactyl.cmcaine.co.uk/betas/?sort=time&order=desc
|
||||
[riskyclick]: https://tridactyl.cmcaine.co.uk/betas/tridactyl-latest.xpi
|
||||
[amo]: https://addons.mozilla.org/en-US/firefox/addon/tridactyl-vim?src=external-github
|
||||
|
|
|
@ -38,7 +38,10 @@ fi
|
|||
# It's important to generate the metadata before the documentation because
|
||||
# missing imports might break documentation generation on clean builds
|
||||
"$(npm bin)/tsc" compiler/gen_metadata.ts -m commonjs \
|
||||
&& node compiler/gen_metadata.js --out src/.metadata.generated.ts src/*.ts
|
||||
&& node compiler/gen_metadata.js \
|
||||
--out src/.metadata.generated.ts \
|
||||
--themeDir src/static/themes \
|
||||
src/*.ts
|
||||
|
||||
scripts/newtab.md.sh
|
||||
scripts/make_tutorial.sh
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Accepts no arguments
|
||||
# Returns git-add'ed files as a list of filenames separated by a newline character
|
||||
|
|
|
@ -1,27 +1,52 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Run prettier on each staged file that needs it without touching the working tree
|
||||
|
||||
source ./scripts/common
|
||||
|
||||
set -e
|
||||
set -Eeuo pipefail
|
||||
|
||||
uglyFiles="$(ugly "$(cachedFiles)")"
|
||||
echoe() {
|
||||
echo "$@" >&2
|
||||
}
|
||||
|
||||
if [ -n "$uglyFiles" ]; then
|
||||
IFS=$'\n'
|
||||
for file in $uglyFiles; do
|
||||
if cmp -s <(staged "$file") "$file"; then
|
||||
prettier --write "$file"
|
||||
git add "$file"
|
||||
else
|
||||
echo "WARN: Staged and working file differ: '$file'"
|
||||
echo "WARN: Moving working file temporarily (this might upset your editor)"
|
||||
# Get crazy: backup the working copy of the file, paste the staged version in its place, prettify, add, restore backup
|
||||
backup=$(mktemp -p . "$file.XXXXXXXXX")
|
||||
mv "$file" "$backup"
|
||||
staged "$file" | prettier --stdin-filepath "$file" > "$file"
|
||||
git add "$file"
|
||||
mv "$backup" "$file"
|
||||
echo "WARN: Working file restored"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
lock() {
|
||||
local lockfile="$1"
|
||||
if [ -e "$lockfile" ]; then
|
||||
echoe "Lockfile '$lockfile' already exists. Check no other operation is occuring or delete lockfile"
|
||||
return 1
|
||||
else
|
||||
touch "$lockfile"
|
||||
fi
|
||||
}
|
||||
|
||||
unlock() {
|
||||
rm "$1"
|
||||
}
|
||||
|
||||
trap "unlock $(git rev-parse --show-toplevel)/.git/index.lock || true" ERR
|
||||
|
||||
main() {
|
||||
local stagedFiles=$(cachedFiles)
|
||||
|
||||
if [ -n "$stagedFiles" ]; then
|
||||
# Could use git-update-index --cacheinfo to add a file without creating directories and stuff.
|
||||
local tmpdir=$(mktemp -p . -d "pretty.XXXXXXXXX")
|
||||
IFS=$'\n'
|
||||
for file in $stagedFiles; do
|
||||
(
|
||||
cd "$tmpdir"
|
||||
mkdir -p $(dirname $file)
|
||||
lock ../.git/index.lock
|
||||
staged "$file" | prettier --stdin-filepath "$file" > "$file"
|
||||
chmod --reference="../$file" "$file" # match permissions
|
||||
# Can't hold lock while git add occurs. Hopefully release and reacquire happen fast enough to prevent race.
|
||||
unlock ../.git/index.lock
|
||||
GIT_WORK_TREE=. git add "$file"
|
||||
)
|
||||
done
|
||||
rm -rf "$tmpdir"
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash -e
|
||||
#!/usr/bin/env bash -e
|
||||
|
||||
# This script must be run from the root Tridactyl directory
|
||||
TRIDIR="$(pwd)"
|
||||
|
|
|
@ -34,3 +34,23 @@ export function expandExstr(
|
|||
prevExpansions,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all aliases for all commands.
|
||||
*
|
||||
* @param aliases An object mapping aliases to commands
|
||||
* @return commands An object mapping commands to an array of aliases
|
||||
*/
|
||||
export function getCmdAliasMapping(
|
||||
aliases = config.get("exaliases"),
|
||||
): { [str: string]: string[] } {
|
||||
let commands = {}
|
||||
// aliases look like this: {alias: command} but what we really need is this: {command: [alias1, alias2...]}
|
||||
// This is what this loop builds
|
||||
for (let alias in aliases) {
|
||||
let cmd = expandExstr(alias, aliases).trim()
|
||||
if (!commands[cmd]) commands[cmd] = []
|
||||
commands[cmd].push(alias.trim())
|
||||
}
|
||||
return commands
|
||||
}
|
||||
|
|
|
@ -8,13 +8,13 @@ import { BufferCompletionSource } from "./completions/Buffer"
|
|||
import { BmarkCompletionSource } from "./completions/Bmark"
|
||||
import { ExcmdCompletionSource } from "./completions/Excmd"
|
||||
import { HistoryCompletionSource } from "./completions/History"
|
||||
import { SettingsCompletionSource } from "./completions/Settings"
|
||||
import * as Messaging from "./messaging"
|
||||
import * as Config from "./config"
|
||||
import * as SELF from "./commandline_frame"
|
||||
import "./number.clamp"
|
||||
import state from "./state"
|
||||
import Logger from "./logging"
|
||||
import * as aliases from "./aliases"
|
||||
import { theme } from "./styling"
|
||||
const logger = new Logger("cmdline")
|
||||
|
||||
|
@ -59,6 +59,7 @@ function enableCompletions() {
|
|||
new BufferAllCompletionSource(completionsDiv),
|
||||
new BufferCompletionSource(completionsDiv),
|
||||
new ExcmdCompletionSource(completionsDiv),
|
||||
new SettingsCompletionSource(completionsDiv),
|
||||
new HistoryCompletionSource(completionsDiv),
|
||||
]
|
||||
|
||||
|
@ -207,13 +208,12 @@ clInput.addEventListener("keydown", function(keyevent) {
|
|||
|
||||
clInput.addEventListener("input", () => {
|
||||
const exstr = clInput.value
|
||||
const expandedCmd = aliases.expandExstr(exstr)
|
||||
|
||||
// Fire each completion and add a callback to resize area
|
||||
enableCompletions()
|
||||
logger.debug(activeCompletions)
|
||||
activeCompletions.forEach(comp =>
|
||||
comp.filter(expandedCmd).then(() => resizeArea()),
|
||||
comp.filter(exstr).then(() => resizeArea()),
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ Concrete completion classes have been moved to src/completions/.
|
|||
import * as Fuse from "fuse.js"
|
||||
import { enumerate } from "./itertools"
|
||||
import { toNumber } from "./convert"
|
||||
import * as config from "./config"
|
||||
import * as aliases from "./aliases"
|
||||
|
||||
export const DEFAULT_FAVICON = browser.extension.getURL(
|
||||
"static/defaultFavicon.svg",
|
||||
|
@ -33,6 +35,20 @@ export abstract class CompletionSource {
|
|||
readonly options: CompletionOption[]
|
||||
node: HTMLElement
|
||||
public completion: string
|
||||
protected prefixes: string[] = []
|
||||
|
||||
constructor(prefixes) {
|
||||
let commands = aliases.getCmdAliasMapping()
|
||||
|
||||
// Now, for each prefix given as argument, add it to the completionsource's prefix list and also add any alias it has
|
||||
prefixes.map(p => p.trim()).forEach(p => {
|
||||
this.prefixes.push(p)
|
||||
if (commands[p]) this.prefixes = this.prefixes.concat(commands[p])
|
||||
})
|
||||
|
||||
// Not sure this is necessary but every completion source has it
|
||||
this.prefixes = this.prefixes.map(p => p + " ")
|
||||
}
|
||||
|
||||
/** Update [[node]] to display completions relevant to exstr */
|
||||
public abstract filter(exstr: string): Promise<void>
|
||||
|
@ -117,8 +133,8 @@ export abstract class CompletionSourceFuse extends CompletionSource {
|
|||
|
||||
protected optionContainer = html`<table class="optionContainer">`
|
||||
|
||||
constructor(private prefixes, className: string, title?: string) {
|
||||
super()
|
||||
constructor(prefixes, className: string, title?: string) {
|
||||
super(prefixes)
|
||||
this.node = html`<div class="${className} hidden">
|
||||
<div class="sectionHeader">${title || className}</div>
|
||||
</div>`
|
||||
|
|
|
@ -36,7 +36,7 @@ export class BmarkCompletionSource extends Completions.CompletionSourceFuse {
|
|||
public options: BmarkCompletionOption[]
|
||||
|
||||
constructor(private _parent) {
|
||||
super(["bmarks "], "BmarkCompletionSource", "Bookmarks")
|
||||
super(["bmarks"], "BmarkCompletionSource", "Bookmarks")
|
||||
|
||||
this._parent.appendChild(this.node)
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
|
|||
|
||||
constructor(private _parent) {
|
||||
super(
|
||||
["buffer ", "tabclose ", "tabdetach ", "tabduplicate ", "tabmove "],
|
||||
["buffer", "tabclose", "tabdetach", "tabduplicate", "tabmove"],
|
||||
"BufferCompletionSource",
|
||||
"Buffers",
|
||||
)
|
||||
|
|
|
@ -42,7 +42,7 @@ export class BufferAllCompletionSource extends Completions.CompletionSourceFuse
|
|||
public options: BufferAllCompletionOption[]
|
||||
|
||||
constructor(private _parent) {
|
||||
super(["bufferall "], "BufferAllCompletionSource", "All Buffers")
|
||||
super(["bufferall"], "BufferAllCompletionSource", "All Buffers")
|
||||
|
||||
this.updateOptions()
|
||||
this._parent.appendChild(this.node)
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import * as Completions from "../completions"
|
||||
import { typeToSimpleString } from "../metadata"
|
||||
import * as Metadata from "../.metadata.generated"
|
||||
import state from "../state"
|
||||
import * as config from "../config"
|
||||
import * as aliases from "../aliases"
|
||||
|
||||
class ExcmdCompletionOption extends Completions.CompletionOptionHTML
|
||||
implements Completions.CompletionOptionFuse {
|
||||
public fuseKeys = []
|
||||
constructor(
|
||||
public value: string,
|
||||
public ttype: string,
|
||||
public documentation: string,
|
||||
public ttype: string = "",
|
||||
public documentation: string = "",
|
||||
) {
|
||||
super()
|
||||
this.fuseKeys.push(this.value)
|
||||
|
@ -16,10 +19,10 @@ class ExcmdCompletionOption extends Completions.CompletionOptionHTML
|
|||
// Create HTMLElement
|
||||
this.html = html`<tr class="ExcmdCompletionOption option">
|
||||
<td class="excmd">${value}</td>
|
||||
<td class="type">${ttype}</td>
|
||||
<td class="documentation">${documentation}</td>
|
||||
</tr>`
|
||||
}
|
||||
// <td class="type">${ttype}</td>
|
||||
}
|
||||
|
||||
export class ExcmdCompletionSource extends Completions.CompletionSourceFuse {
|
||||
|
@ -50,29 +53,56 @@ export class ExcmdCompletionSource extends Completions.CompletionSourceFuse {
|
|||
this.options = (await this.scoreOptions(
|
||||
Object.keys(fns).filter(f => f.startsWith(exstr)),
|
||||
)).map(f => {
|
||||
try {
|
||||
return new ExcmdCompletionOption(f, fns[f].type, fns[f].doc)
|
||||
} catch {
|
||||
return new ExcmdCompletionOption("", "", "")
|
||||
}
|
||||
let t = ""
|
||||
if (fns[f].type) t = typeToSimpleString(fns[f].type)
|
||||
return new ExcmdCompletionOption(f, t, fns[f].doc)
|
||||
})
|
||||
|
||||
let exaliases = config.get("exaliases")
|
||||
for (let alias of Object.keys(exaliases).filter(a =>
|
||||
a.startsWith(exstr),
|
||||
)) {
|
||||
let cmd = aliases.expandExstr(alias)
|
||||
if (fns[cmd]) {
|
||||
this.options = this.options.concat(
|
||||
new ExcmdCompletionOption(
|
||||
alias,
|
||||
fns[cmd].type ? typeToSimpleString(fns[cmd].type) : "",
|
||||
`Alias for \`${cmd}\`. ${fns[cmd].doc}`,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
// This can happen when the alias is a composite command or a command with arguments. We can't display type or doc because we don't know what parameter the alias takes or what it does.
|
||||
this.options = this.options.concat(
|
||||
new ExcmdCompletionOption(
|
||||
alias,
|
||||
"",
|
||||
`Alias for \`${cmd}\`.`,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this.options.forEach(o => (o.state = "normal"))
|
||||
this.updateChain()
|
||||
}
|
||||
|
||||
private async scoreOptions(exstrs: string[]) {
|
||||
let histpos = state.cmdHistory.map(s => s.split(" ")[0]).reverse()
|
||||
return exstrs.sort((a, b) => {
|
||||
let posa = histpos.findIndex(x => x == a)
|
||||
let posb = histpos.findIndex(x => x == b)
|
||||
// If two ex commands have the same position, sort lexically
|
||||
if (posa == posb) return a < b ? -1 : 1
|
||||
// If they aren't found in the list they get lower priority
|
||||
if (posa == -1) return 1
|
||||
if (posb == -1) return -1
|
||||
// Finally, sort by history position
|
||||
return posa < posb ? -1 : 1
|
||||
})
|
||||
return exstrs.sort()
|
||||
|
||||
// Too slow with large profiles
|
||||
// let histpos = state.cmdHistory.map(s => s.split(" ")[0]).reverse()
|
||||
// return exstrs.sort((a, b) => {
|
||||
// let posa = histpos.findIndex(x => x == a)
|
||||
// let posb = histpos.findIndex(x => x == b)
|
||||
// // If two ex commands have the same position, sort lexically
|
||||
// if (posa == posb) return a < b ? -1 : 1
|
||||
// // If they aren't found in the list they get lower priority
|
||||
// if (posa == -1) return 1
|
||||
// if (posb == -1) return -1
|
||||
// // Finally, sort by history position
|
||||
// return posa < posb ? -1 : 1
|
||||
// })
|
||||
}
|
||||
|
||||
select(option: ExcmdCompletionOption) {
|
||||
|
|
|
@ -35,7 +35,7 @@ export class HistoryCompletionSource extends Completions.CompletionSourceFuse {
|
|||
|
||||
constructor(private _parent) {
|
||||
super(
|
||||
["open ", "tabopen ", "winopen "],
|
||||
["open", "tabopen", "winopen"],
|
||||
"HistoryCompletionSource",
|
||||
"History",
|
||||
)
|
||||
|
|
72
src/completions/Settings.ts
Normal file
72
src/completions/Settings.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import * as Completions from "../completions"
|
||||
import * as config from "../config"
|
||||
import { browserBg } from "../lib/webext"
|
||||
|
||||
class SettingsCompletionOption extends Completions.CompletionOptionHTML
|
||||
implements Completions.CompletionOptionFuse {
|
||||
public fuseKeys = []
|
||||
|
||||
constructor(
|
||||
public value: string,
|
||||
setting: { name: string; value: string; docs: string },
|
||||
) {
|
||||
super()
|
||||
this.html = html`<tr class="SettingsCompletionOption option">
|
||||
<td class="title">${setting.name}</td>
|
||||
<td class="content">${setting.value}</td>
|
||||
</tr>`
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsCompletionSource extends Completions.CompletionSourceFuse {
|
||||
public options: SettingsCompletionOption[]
|
||||
|
||||
constructor(private _parent) {
|
||||
super(["set", "get"], "SettingsCompletionSource", "Settings")
|
||||
|
||||
this._parent.appendChild(this.node)
|
||||
}
|
||||
|
||||
public async filter(exstr: string) {
|
||||
this.lastExstr = exstr
|
||||
let [prefix, query] = this.splitOnPrefix(exstr)
|
||||
let options = ""
|
||||
|
||||
// Hide self and stop if prefixes don't match
|
||||
if (prefix) {
|
||||
// Show self if prefix and currently hidden
|
||||
if (this.state === "hidden") {
|
||||
this.state = "normal"
|
||||
}
|
||||
} else {
|
||||
this.state = "hidden"
|
||||
return
|
||||
}
|
||||
|
||||
let settings = config.get()
|
||||
this.options = Object.keys(settings)
|
||||
.filter(x => x.startsWith(query))
|
||||
.sort()
|
||||
.map(
|
||||
setting =>
|
||||
new SettingsCompletionOption(setting, {
|
||||
name: setting,
|
||||
value: JSON.stringify(settings[setting]),
|
||||
docs: "",
|
||||
}),
|
||||
)
|
||||
// this.options = [new SettingsCompletionOption("ok", {name: "ok", docs:""})]
|
||||
|
||||
this.updateChain()
|
||||
}
|
||||
|
||||
updateChain() {
|
||||
// Options are pre-trimmed to the right length.
|
||||
this.options.forEach(option => (option.state = "normal"))
|
||||
|
||||
// Call concrete class
|
||||
this.updateDisplay()
|
||||
}
|
||||
|
||||
onInput() {}
|
||||
}
|
|
@ -192,7 +192,7 @@ config.getAsync("leavegithubalone").then(v => {
|
|||
// On quick loading pages, the document is already loaded
|
||||
// if (document.location.host == "github.com") {
|
||||
document.body.addEventListener("keydown", function(e) {
|
||||
if ("/".indexOf(e.key) != -1) {
|
||||
if ("/".indexOf(e.key) != -1 && state.mode == "normal") {
|
||||
e.cancelBubble = true
|
||||
e.stopImmediatePropagation()
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ config.getAsync("leavegithubalone").then(v => {
|
|||
window.addEventListener("DOMContentLoaded", () => {
|
||||
// if (document.location.host == "github.com") {
|
||||
document.body.addEventListener("keydown", function(e) {
|
||||
if ("/".indexOf(e.key) != -1) {
|
||||
if ("/".indexOf(e.key) != -1 && state.mode == "normal") {
|
||||
e.cancelBubble = true
|
||||
e.stopImmediatePropagation()
|
||||
}
|
||||
|
|
|
@ -111,6 +111,13 @@ export const potentialRules = {
|
|||
show: ``,
|
||||
},
|
||||
},
|
||||
padwhenmaximised: {
|
||||
name: `#main-window[sizemode="maximized"] #content-deck`,
|
||||
options: {
|
||||
some: `padding-top: 8px;`,
|
||||
none: ``,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Vimperator's options for reference:
|
||||
|
@ -141,6 +148,7 @@ export const metaRules = {
|
|||
navbar: "autohide",
|
||||
titlebar: "hide",
|
||||
menubar: "grey",
|
||||
padwhenmaximised: "some",
|
||||
},
|
||||
full: {
|
||||
hoverlink: "left",
|
||||
|
@ -148,6 +156,7 @@ export const metaRules = {
|
|||
navbar: "always",
|
||||
titlebar: "show",
|
||||
menubar: "default",
|
||||
padwhenmaximised: "none",
|
||||
},
|
||||
},
|
||||
tabs: {
|
||||
|
|
426
src/excmds.ts
426
src/excmds.ts
|
@ -95,7 +95,7 @@
|
|||
|
||||
// Shared
|
||||
import * as Messaging from "./messaging"
|
||||
import { browserBg, activeTabId, activeTabContainerId, openInNewTab } from "./lib/webext"
|
||||
import { browserBg, activeTabId, activeTabContainerId, openInNewTab, openInNewWindow } from "./lib/webext"
|
||||
import * as Container from "./lib/containers"
|
||||
import state from "./state"
|
||||
import * as UrlUtil from "./url_util"
|
||||
|
@ -851,7 +851,7 @@ export async function reloadhard(n = 1) {
|
|||
// I went through the whole list https://developer.mozilla.org/en-US/Firefox/The_about_protocol
|
||||
// about:blank is even more special
|
||||
/** @hidden */
|
||||
export const ABOUT_WHITELIST = ["about:home", "about:license", "about:logo", "about:rights"]
|
||||
export const ABOUT_WHITELIST = ["about:license", "about:logo", "about:rights"]
|
||||
|
||||
/** Open a new page in the current tab.
|
||||
*
|
||||
|
@ -1602,6 +1602,7 @@ export async function tabopen(...addressarr: string[]) {
|
|||
let url: string
|
||||
let address = (await argParse(addressarr)).join(" ")
|
||||
|
||||
if (address == "") address = config.get("newtab")
|
||||
if (!ABOUT_WHITELIST.includes(address) && address.match(/^(about|file):.*/)) {
|
||||
if ((await browser.runtime.getPlatformInfo()).os === "mac" && (await browser.windows.getCurrent()).incognito) {
|
||||
fillcmdline_notrail("# nativeopen isn't supported in private mode on OSX. Consider installing Linux or Windows :).")
|
||||
|
@ -1611,7 +1612,6 @@ export async function tabopen(...addressarr: string[]) {
|
|||
return
|
||||
}
|
||||
} else if (address != "") url = forceURI(address)
|
||||
else url = forceURI(config.get("newtab"))
|
||||
|
||||
activeTabContainerId().then(containerId => {
|
||||
// Ensure -c has priority.
|
||||
|
@ -1762,17 +1762,24 @@ export async function tabclosealltoleft() {
|
|||
//#background
|
||||
export async function undo() {
|
||||
const current_win_id: number = (await browser.windows.getCurrent()).id
|
||||
const sessions = await browser.sessions.getRecentlyClosed()
|
||||
const sessions = await browser.sessions.getRecentlyClosed({ maxResults: 10 })
|
||||
|
||||
// The first session object that's a window or a tab from this window. Or undefined if sessions is empty.
|
||||
let closed = sessions.find(s => {
|
||||
return "window" in s || (s.tab && s.tab.windowId == current_win_id)
|
||||
const lastSession = sessions.find(s => {
|
||||
if (s.window) {
|
||||
return true
|
||||
} else if (s.tab && s.tab.windowId === current_win_id) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
if (closed) {
|
||||
if (closed.tab) {
|
||||
browser.sessions.restore(closed.tab.sessionId)
|
||||
} else if (closed.window) {
|
||||
browser.sessions.restore(closed.window.sessionId)
|
||||
|
||||
if (lastSession) {
|
||||
if (lastSession.tab) {
|
||||
browser.sessions.restore(lastSession.tab.sessionId)
|
||||
} else if (lastSession.window) {
|
||||
browser.sessions.restore(lastSession.window.sessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2892,7 +2899,6 @@ import * as hinting from "./hinting"
|
|||
|
||||
@param option
|
||||
- -b open in background
|
||||
- -br repeatedly open in background
|
||||
- -y copy (yank) link's target to clipboard
|
||||
- -p copy an element's text to the clipboard
|
||||
- -P copy an element's title/alt text to the clipboard
|
||||
|
@ -2909,14 +2915,13 @@ import * as hinting from "./hinting"
|
|||
- -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
|
||||
- `-pipe selector key` e.g, `-pipe * href` returns the key. Only makes sense with `composite`, e.g, `composite hint -pipe * textContent | yank`.
|
||||
- **DEPRECATED** `-W excmd...` append hint href to excmd and execute, e.g, `hint -W exclaim mpv` to open YouTube videos. Use `composite hint -pipe | [excmd]` instead.
|
||||
- -wp open in new private window
|
||||
- `-pipe selector key` e.g, `-pipe * href` returns the key. Only makes sense with `composite`, e.g, `composite hint -pipe * textContent | yank`. If you don't select a hint (i.e. press <Esc>), will return an empty string.
|
||||
- `-W excmd...` append hint href to excmd and execute, e.g, `hint -W exclaim mpv` to open YouTube videos.
|
||||
- -q* quick (or rapid) hints mode. Stay in hint mode until you press <Esc>, e.g. `:hint -qb` to open multiple hints in the background or `:hint -qW excmd` to execute excmd once for each hint. This will return an array containing all elements or the result of executed functions (e.g. `hint -qpipe a href` will return an array of links).
|
||||
- -br deprecated, use `-qb` instead
|
||||
|
||||
|
||||
Excepting the custom selector mode and background hint mode, each of these
|
||||
hint modes is available by default as `;<option character>`, so e.g. `;y`
|
||||
to yank a link's target.
|
||||
Excepting the custom selector mode and background hint mode, each of these hint modes is available by default as `;<option character>`, so e.g. `;y` to yank a link's target; `;g<option character>` starts rapid hint mode for all modes where it makes sense, and some others.
|
||||
|
||||
To open a hint in the background, the default bind is `F`.
|
||||
|
||||
|
@ -2943,150 +2948,257 @@ import * as hinting from "./hinting"
|
|||
*/
|
||||
//#content
|
||||
export async function hint(option?: string, selectors?: string, ...rest: string[]) {
|
||||
// NB: if you want something to work with rapid hinting, make it return a tuple of [something, hintCount] see option === "-b" below.
|
||||
if (!option) option = ""
|
||||
|
||||
if (option == "-br") option = "-qb"
|
||||
|
||||
let rapid = false
|
||||
if (option.startsWith("-q")) {
|
||||
option = "-" + option.slice(2)
|
||||
rapid = true
|
||||
}
|
||||
|
||||
let selectHints = new Promise(r => r())
|
||||
let onSelected = a => a
|
||||
let hintTabOpen = async (href, active = !rapid) => {
|
||||
let containerId = await activeTabContainerId()
|
||||
if (containerId) {
|
||||
return await openInNewTab(href, {
|
||||
active,
|
||||
related: true,
|
||||
cookieStoreId: containerId,
|
||||
})
|
||||
} else {
|
||||
return await openInNewTab(href, {
|
||||
active,
|
||||
related: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Open in background
|
||||
if (option === "-b") {
|
||||
selectHints = hinting.pipe(DOM.HINTTAGS_selectors)
|
||||
onSelected = async result => {
|
||||
let [link, hintCount] = result as [HTMLAnchorElement, number]
|
||||
link.focus()
|
||||
if (link.href) {
|
||||
let containerId = await activeTabContainerId()
|
||||
if (containerId) {
|
||||
openInNewTab(link.href, {
|
||||
active: false,
|
||||
related: true,
|
||||
cookieStoreId: containerId,
|
||||
}).catch(() => DOM.simulateClick(link))
|
||||
} else {
|
||||
openInNewTab(link.href, {
|
||||
active: false,
|
||||
related: true,
|
||||
}).catch(() => DOM.simulateClick(link))
|
||||
}
|
||||
switch (option) {
|
||||
case "-b":
|
||||
// Open in background
|
||||
selectHints = hinting.pipe(
|
||||
DOM.HINTTAGS_selectors,
|
||||
async link => {
|
||||
link.focus()
|
||||
if (link.href) {
|
||||
hintTabOpen(link.href, false).catch(() => DOM.simulateClick(link))
|
||||
} else {
|
||||
DOM.simulateClick(link)
|
||||
}
|
||||
return link
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-y":
|
||||
// Yank link
|
||||
selectHints = hinting.pipe(
|
||||
DOM.HINTTAGS_selectors,
|
||||
elem => {
|
||||
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
|
||||
run_exstr("yank " + elem["href"])
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-p":
|
||||
// Yank text content
|
||||
selectHints = hinting.pipe_elements(
|
||||
DOM.elementsWithText(),
|
||||
elem => {
|
||||
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
|
||||
run_exstr("yank " + elem["textContent"])
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-P":
|
||||
// Yank link alt text
|
||||
// ???: Neither anchors nor links posses an "alt" attribute. I'm assuming that the person who wrote this code also wanted to select the alt text of images
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link
|
||||
selectHints = hinting.pipe_elements(
|
||||
DOM.getElemsBySelector("[title], [alt]", [DOM.isVisible]),
|
||||
link => {
|
||||
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
|
||||
run_exstr("yank " + (link.title ? link.title : link.alt))
|
||||
return link
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-#":
|
||||
// Yank anchor
|
||||
selectHints = hinting.pipe_elements(
|
||||
DOM.anchors(),
|
||||
link => {
|
||||
let anchorUrl = new URL(window.location.href)
|
||||
// ???: What purpose does selecting elements with a name attribute have? Selecting values that only have meaning in forms doesn't seem very useful.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
|
||||
anchorUrl.hash = link.id || link.name
|
||||
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
|
||||
run_exstr("yank " + anchorUrl.href)
|
||||
return link
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-c":
|
||||
selectHints = hinting.pipe(
|
||||
selectors,
|
||||
elem => {
|
||||
DOM.simulateClick(elem as HTMLElement)
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-W":
|
||||
selectHints = hinting.pipe(
|
||||
DOM.HINTTAGS_selectors,
|
||||
elem => {
|
||||
// /!\ RACY RACY RACY!
|
||||
run_exstr(selectors + " " + rest.join(" ") + " " + elem)
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-pipe":
|
||||
selectHints = hinting.pipe(selectors, elem => elem[rest.join(" ")], rapid)
|
||||
break
|
||||
|
||||
case "-i":
|
||||
selectHints = hinting.pipe_elements(
|
||||
hinting.hintableImages(),
|
||||
elem => {
|
||||
open(new URL(elem.getAttribute("src"), window.location.href).href)
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-I":
|
||||
selectHints = hinting.pipe_elements(
|
||||
hinting.hintableImages(),
|
||||
async elem => {
|
||||
await hintTabOpen(new URL(elem.getAttribute("src"), window.location.href).href)
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-k":
|
||||
selectHints = hinting.pipe_elements(
|
||||
hinting.killables(),
|
||||
elem => {
|
||||
elem.remove()
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-s":
|
||||
case "-a":
|
||||
case "-S":
|
||||
case "-A":
|
||||
let elems = []
|
||||
// s: don't ask the user where to save the file
|
||||
// a: ask the user where to save the file
|
||||
let saveAs = true
|
||||
if (option[1].toLowerCase() == "s") saveAs = false
|
||||
// Lowercase: anchors
|
||||
// Uppercase: images
|
||||
let attr = "href"
|
||||
if (option[1].toLowerCase() == option[1]) {
|
||||
attr = "href"
|
||||
elems = hinting.saveableElements()
|
||||
} else {
|
||||
DOM.simulateClick(link)
|
||||
attr = "src"
|
||||
elems = hinting.hintableImages()
|
||||
}
|
||||
return [link.href, hintCount]
|
||||
}
|
||||
selectHints = hinting.pipe_elements(
|
||||
elems,
|
||||
elem => {
|
||||
Messaging.message("download_background", "downloadUrl", [new URL(elem[attr], window.location.href).href, saveAs])
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-;":
|
||||
selectHints = hinting.pipe_elements(
|
||||
hinting.hintables(selectors),
|
||||
elem => {
|
||||
elem.focus()
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-r":
|
||||
selectHints = hinting.pipe_elements(
|
||||
DOM.elementsWithText(),
|
||||
elem => {
|
||||
TTS.readText(elem.textContent)
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-w":
|
||||
selectHints = hinting.pipe_elements(
|
||||
hinting.hintables(),
|
||||
elem => {
|
||||
elem.focus()
|
||||
if (elem.href) openInNewWindow({ url: new URL(elem.href, window.location.href).href })
|
||||
else DOM.simulateClick(elem)
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
case "-wp":
|
||||
selectHints = hinting.pipe_elements(
|
||||
hinting.hintables(),
|
||||
elem => {
|
||||
elem.focus()
|
||||
if (elem.href) return openInNewWindow({ url: elem.href, incognito: true })
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
break
|
||||
|
||||
default:
|
||||
selectHints = hinting.pipe(
|
||||
DOM.HINTTAGS_selectors,
|
||||
elem => {
|
||||
DOM.simulateClick(elem as HTMLElement)
|
||||
return elem
|
||||
},
|
||||
rapid,
|
||||
)
|
||||
}
|
||||
|
||||
// Yank link
|
||||
else if (option === "-y") {
|
||||
selectHints = hinting.pipe(DOM.HINTTAGS_selectors)
|
||||
onSelected = result => {
|
||||
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
|
||||
run_exstr("yank " + result[0]["href"])
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// Yank text content
|
||||
else if (option === "-p") {
|
||||
selectHints = hinting.pipe_elements(DOM.elementsWithText())
|
||||
onSelected = result => {
|
||||
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
|
||||
run_exstr("yank " + result["textContent"])
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// Yank link alt text
|
||||
// ???: Neither anchors nor links posses an "alt" attribute. I'm assuming that the person who wrote this code also wanted to select the alt text of images
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link
|
||||
else if (option === "-P") {
|
||||
selectHints = hinting.pipe_elements(DOM.getElemsBySelector("[title], [alt]", [DOM.isVisible]))
|
||||
onSelected = result => {
|
||||
let link = result[0] as HTMLAnchorElement & HTMLImageElement
|
||||
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
|
||||
run_exstr("yank " + (link.title ? link.title : link.alt))
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// Yank anchor
|
||||
else if (option === "-#") {
|
||||
selectHints = hinting.pipe_elements(DOM.anchors())
|
||||
onSelected = result => {
|
||||
let anchorUrl = new URL(window.location.href)
|
||||
let link = result[0] as any
|
||||
// ???: What purpose does selecting elements with a name attribute have? Selecting values that only have meaning in forms doesn't seem very useful.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
|
||||
anchorUrl.hash = link.id || link.name
|
||||
// /!\ Warning: This is racy! This can easily be fixed by adding an await but do we want this? yank can be pretty slow, especially with yankto=selection
|
||||
run_exstr("yank " + anchorUrl.href)
|
||||
return result
|
||||
}
|
||||
} else if (option === "-c") {
|
||||
selectHints = hinting.pipe(selectors)
|
||||
onSelected = result => {
|
||||
DOM.simulateClick(result[0] as HTMLElement)
|
||||
return result
|
||||
}
|
||||
}
|
||||
// Deprecated: hint exstr
|
||||
else if (option === "-W") {
|
||||
selectHints = hinting.pipe(DOM.HINTTAGS_selectors)
|
||||
onSelected = result => {
|
||||
// /!\ RACY RACY RACY!
|
||||
run_exstr(selectors + " " + rest.join(" ") + " " + result[0])
|
||||
return result
|
||||
}
|
||||
} else if (option === "-pipe") {
|
||||
selectHints = hinting.pipe(selectors)
|
||||
onSelected = result => result[0][rest.join(" ")]
|
||||
} else if (option === "-br") {
|
||||
while (true) {
|
||||
// The typecast can be removed once the function is completely ported
|
||||
let result = (await hint("-b")) as [HTMLElement, number]
|
||||
if (result === null) return null
|
||||
let [_, hintCount] = result
|
||||
if (hintCount < 2) break
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: port these to new fangled way
|
||||
else if (option === "-i") hinting.hintImage(false)
|
||||
else if (option === "-I") hinting.hintImage(true)
|
||||
else if (option === "-k") hinting.hintKill()
|
||||
else if (option === "-s") hinting.hintSave("link", false)
|
||||
else if (option === "-S") hinting.hintSave("img", false)
|
||||
else if (option === "-a") hinting.hintSave("link", true)
|
||||
else if (option === "-A") hinting.hintSave("img", true)
|
||||
else if (option === "-;") hinting.hintFocus(selectors)
|
||||
else if (option === "-r") hinting.hintRead()
|
||||
else if (option === "-w") hinting.hintPageWindow()
|
||||
else if (option === "-wp") hinting.hintPageWindowPrivate()
|
||||
else {
|
||||
selectHints = hinting.pipe(DOM.HINTTAGS_selectors)
|
||||
onSelected = result => {
|
||||
DOM.simulateClick(result[0] as HTMLElement)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
selectHints.then(
|
||||
async result => resolve(await onSelected(result)),
|
||||
rejectionReason => {
|
||||
// We have to resolve when we don't want to have our messages be logged in the command line but this feels wrong since no hint has been selected
|
||||
// Perhaps we should implement a mechanism to allow specific errors to go unreported?
|
||||
if (rejectionReason == hinting.HintRejectionReason.User) {
|
||||
logger.debug("Hint promise rejected because user left hint mode without selecting a hint")
|
||||
resolve(null)
|
||||
} else if (rejectionReason == hinting.HintRejectionReason.NoHints) {
|
||||
logger.debug("Hint promise rejected because there are no hints to select")
|
||||
resolve(null)
|
||||
} else {
|
||||
reject(rejectionReason)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
return selectHints
|
||||
}
|
||||
|
||||
// how 2 crash pc
|
||||
|
|
170
src/hinting.ts
170
src/hinting.ts
|
@ -28,31 +28,25 @@ import Logger from "./logging"
|
|||
import * as Messaging from "./messaging"
|
||||
const logger = new Logger("hinting")
|
||||
|
||||
export enum HintRejectionReason {
|
||||
Err,
|
||||
User,
|
||||
NoHints,
|
||||
}
|
||||
|
||||
/** Simple container for the state of a single frame's hints. */
|
||||
class HintState {
|
||||
public focusedHint: Hint
|
||||
readonly hintHost = document.createElement("div")
|
||||
readonly hints: Hint[] = []
|
||||
public selectedHints: Hint[] = []
|
||||
public filter = ""
|
||||
public hintchars = ""
|
||||
|
||||
constructor(
|
||||
public filterFunc: HintFilter,
|
||||
public resolve: (Hint) => void,
|
||||
public reject: (HintRejectionReason) => any,
|
||||
public reject: (any) => void,
|
||||
public rapid: boolean,
|
||||
) {
|
||||
this.hintHost.classList.add("TridactylHintHost", "cleanslate")
|
||||
}
|
||||
|
||||
destructor(abort) {
|
||||
if (!this.focusedHint) this.focusedHint = this.hints[0]
|
||||
|
||||
destructor() {
|
||||
// Undo any alterations of the hinted elements
|
||||
for (const hint of this.hints) {
|
||||
hint.hidden = true
|
||||
|
@ -61,9 +55,11 @@ class HintState {
|
|||
// Remove all hints from the DOM.
|
||||
this.hintHost.remove()
|
||||
|
||||
if (abort) this.reject(HintRejectionReason.User)
|
||||
else if (!this.focusedHint) this.reject(HintRejectionReason.NoHints)
|
||||
else this.resolve([this.focusedHint.target, this.hints.length])
|
||||
if (this.rapid) this.resolve(this.selectedHints.map(h => h.result))
|
||||
else
|
||||
this.resolve(
|
||||
this.selectedHints[0] ? this.selectedHints[0].result : "",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,13 +71,25 @@ export function hintPage(
|
|||
onSelect: HintSelectedCallback,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
rapid = false,
|
||||
) {
|
||||
let buildHints: HintBuilder = defaultHintBuilder()
|
||||
let filterHints: HintFilter = defaultHintFilter()
|
||||
state.mode = "hint"
|
||||
modeState = new HintState(filterHints, resolve, reject)
|
||||
modeState = new HintState(filterHints, resolve, reject, rapid)
|
||||
|
||||
buildHints(hintableElements, onSelect)
|
||||
if (rapid == false) {
|
||||
buildHints(hintableElements, hint => {
|
||||
hint.result = onSelect(hint.target)
|
||||
modeState.selectedHints.push(hint)
|
||||
reset()
|
||||
})
|
||||
} else {
|
||||
buildHints(hintableElements, hint => {
|
||||
hint.result = onSelect(hint.target)
|
||||
modeState.selectedHints.push(hint)
|
||||
})
|
||||
}
|
||||
|
||||
if (modeState.hints.length) {
|
||||
let firstTarget = modeState.hints[0].target
|
||||
|
@ -227,6 +235,7 @@ type HintSelectedCallback = (Hint) => any
|
|||
/** Place a flag by each hintworthy element */
|
||||
class Hint {
|
||||
public readonly flag = document.createElement("span")
|
||||
public result: any = null
|
||||
|
||||
constructor(
|
||||
public readonly target: Element,
|
||||
|
@ -444,8 +453,10 @@ function filterHintsVimperator(fstr, reflow = false) {
|
|||
* If abort is true, we're resetting because the user pressed escape.
|
||||
* If it is false, we're resetting because the user selected a hint.
|
||||
**/
|
||||
function reset(abort = false) {
|
||||
modeState.destructor(abort)
|
||||
function reset() {
|
||||
if (modeState) {
|
||||
modeState.destructor()
|
||||
}
|
||||
modeState = undefined
|
||||
state.mode = "normal"
|
||||
}
|
||||
|
@ -473,7 +484,7 @@ function pushKey(ke) {
|
|||
1. Within viewport
|
||||
2. Not hidden by another element
|
||||
*/
|
||||
function hintables(selectors = DOM.HINTTAGS_selectors, withjs = false) {
|
||||
export function hintables(selectors = DOM.HINTTAGS_selectors, withjs = false) {
|
||||
let elems = DOM.getElemsBySelector(selectors, [])
|
||||
if (withjs) {
|
||||
elems = elems.concat(DOM.hintworthy_js_elems)
|
||||
|
@ -484,19 +495,19 @@ function hintables(selectors = DOM.HINTTAGS_selectors, withjs = false) {
|
|||
|
||||
/** Returns elements that point to a saveable resource
|
||||
*/
|
||||
function saveableElements() {
|
||||
export function saveableElements() {
|
||||
return DOM.getElemsBySelector(DOM.HINTTAGS_saveable, [DOM.isVisible])
|
||||
}
|
||||
|
||||
/** Get array of images in the viewport
|
||||
*/
|
||||
function hintableImages() {
|
||||
export function hintableImages() {
|
||||
return DOM.getElemsBySelector(DOM.HINTTAGS_img_selectors, [DOM.isVisible])
|
||||
}
|
||||
|
||||
/** Array of items that can be killed with hint kill
|
||||
*/
|
||||
function killables() {
|
||||
export function killables() {
|
||||
return DOM.getElemsBySelector(DOM.HINTTAGS_killable_selectors, [
|
||||
DOM.isVisible,
|
||||
])
|
||||
|
@ -505,130 +516,33 @@ function killables() {
|
|||
import { openInNewTab, activeTabContainerId } from "./lib/webext"
|
||||
import { openInNewWindow } from "./lib/webext"
|
||||
|
||||
export function hintPageWindow() {
|
||||
hintPage(hintables(), hint => {
|
||||
hint.target.focus()
|
||||
if (hint.target.href) {
|
||||
openInNewWindow({ url: hint.target.href })
|
||||
} else {
|
||||
// This is to mirror vimperator behaviour.
|
||||
DOM.simulateClick(hint.target)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function hintPageWindowPrivate() {
|
||||
hintPage(hintables(), hint => {
|
||||
hint.target.focus()
|
||||
if (hint.target.href) {
|
||||
openInNewWindow({ url: hint.target.href, incognito: true })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function pipe(
|
||||
selectors = DOM.HINTTAGS_selectors,
|
||||
action: HintSelectedCallback = _ => _,
|
||||
rapid = false,
|
||||
): Promise<[Element, number]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
hintPage(hintables(selectors, true), () => {}, resolve, reject)
|
||||
hintPage(hintables(selectors, true), action, resolve, reject, rapid)
|
||||
})
|
||||
}
|
||||
|
||||
export function pipe_elements(
|
||||
elements: any = DOM.elementsWithText,
|
||||
action: HintSelectedCallback = _ => _,
|
||||
rapid = false,
|
||||
): Promise<[Element, number]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
hintPage(elements, () => {}, resolve, reject)
|
||||
})
|
||||
}
|
||||
|
||||
/** Hint images, opening in the same tab, or in a background tab
|
||||
*
|
||||
* @param inBackground opens the image source URL in a background tab,
|
||||
* as opposed to the current tab
|
||||
*/
|
||||
export function hintImage(inBackground) {
|
||||
hintPage(hintableImages(), hint => {
|
||||
let img_src = hint.target.getAttribute("src")
|
||||
|
||||
if (inBackground) {
|
||||
openInNewTab(new URL(img_src, window.location.href).href, {
|
||||
active: false,
|
||||
related: true,
|
||||
})
|
||||
} else {
|
||||
window.location.href = img_src
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** Hint elements to focus */
|
||||
export function hintFocus(selectors?) {
|
||||
hintPage(hintables(selectors), hint => {
|
||||
hint.target.focus()
|
||||
})
|
||||
}
|
||||
|
||||
/** Hint items and read out the content of the selection */
|
||||
export function hintRead() {
|
||||
hintPage(DOM.elementsWithText(), hint => {
|
||||
TTS.readText(hint.target.textContent)
|
||||
})
|
||||
}
|
||||
|
||||
/** Hint elements and delete the selection from the page
|
||||
*/
|
||||
export function hintKill() {
|
||||
hintPage(killables(), hint => {
|
||||
hint.target.remove()
|
||||
})
|
||||
}
|
||||
|
||||
/** Type for "hint save" actions:
|
||||
* - "link": elements that point to another resource (eg
|
||||
* links to pages/files) - the link target is saved
|
||||
* - "img": image elements
|
||||
*/
|
||||
export type HintSaveType = "link" | "img"
|
||||
|
||||
/** Hint link elements to save
|
||||
*
|
||||
* @param hintType the type of elements to hint and save:
|
||||
* - "link": elements that point to another resource (eg
|
||||
* links to pages/files) - the link targer is saved
|
||||
* - "img": image elements
|
||||
* @param saveAs prompt for save location
|
||||
*/
|
||||
export function hintSave(hintType: HintSaveType, saveAs: boolean) {
|
||||
function saveHintElems(hintType) {
|
||||
return hintType === "link" ? saveableElements() : hintableImages()
|
||||
}
|
||||
|
||||
function urlFromElem(hintType, elem) {
|
||||
return hintType === "link" ? elem.href : elem.src
|
||||
}
|
||||
|
||||
hintPage(saveHintElems(hintType), hint => {
|
||||
const urlToSave = new URL(
|
||||
urlFromElem(hintType, hint.target),
|
||||
window.location.href,
|
||||
)
|
||||
|
||||
// Pass to background context to allow saving from data URLs.
|
||||
// Convert to href because can't clone URL across contexts
|
||||
message("download_background", "downloadUrl", [urlToSave.href, saveAs])
|
||||
hintPage(elements, action, resolve, reject, rapid)
|
||||
})
|
||||
}
|
||||
|
||||
function selectFocusedHint(delay = false) {
|
||||
logger.debug("Selecting hint.", state.mode)
|
||||
const focused = modeState.focusedHint
|
||||
let select = () => {
|
||||
reset()
|
||||
focused.select()
|
||||
}
|
||||
if (delay) setTimeout(select, config.get("hintdelay"))
|
||||
else select()
|
||||
modeState.filter = ""
|
||||
modeState.hints.forEach(h => (h.hidden = false))
|
||||
if (delay) setTimeout(() => focused.select(), config.get("hintdelay"))
|
||||
else focused.select()
|
||||
}
|
||||
|
||||
import { addListener, attributeCaller } from "./messaging"
|
||||
|
|
|
@ -300,11 +300,6 @@ export function mapstrMapToKeyMap(mapstrMap: Map<string, MapTarget>): KeyMap {
|
|||
return newKeyMap
|
||||
}
|
||||
|
||||
export function mapstrObjToKeyMap(mapstrObj): KeyMap {
|
||||
const mapstrMap = new Map(Object.entries(mapstrObj))
|
||||
return mapstrMapToKeyMap(mapstrMap)
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// {{{ Utility functions for dealing with KeyboardEvents
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Tridactyl",
|
||||
"version": "1.13.2",
|
||||
"version": "1.13.3",
|
||||
"icons": {
|
||||
"64": "static/logo/Tridactyl_64px.png",
|
||||
"100": "static/logo/Tridactyl_100px.png",
|
||||
|
|
68
src/metadata.ts
Normal file
68
src/metadata.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Gets the type sitting inside a promise
|
||||
function unwrapPromise(type) {
|
||||
while (type.kind == "Promise") type = type.arguments[0]
|
||||
return type
|
||||
}
|
||||
|
||||
// This turns TYPE into a string, the format of which is quite short in order to be useful for completions
|
||||
export function typeToSimpleString(type): string {
|
||||
let result = ""
|
||||
switch (type.kind) {
|
||||
case "function":
|
||||
result = type.arguments.map(typeToSimpleString).join(", ")
|
||||
let t = unwrapPromise(type.type)
|
||||
if (!["any", "object", "void"].includes(t.kind)) {
|
||||
if (result == "") result = "()"
|
||||
result += "->" + typeToString(t)
|
||||
}
|
||||
break
|
||||
default:
|
||||
result = type.name || ""
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// This turns TYPE into a string, the format of which is quite close to the one tsc uses to display type strings
|
||||
export function typeToString(type): string {
|
||||
let allTypes = arr => arr.map(typeToString).join(", ")
|
||||
let result = ""
|
||||
switch (type.kind) {
|
||||
case "function":
|
||||
result =
|
||||
"(" +
|
||||
allTypes(type.arguments) +
|
||||
")" +
|
||||
" => " +
|
||||
typeToString(type.type)
|
||||
break
|
||||
case "array":
|
||||
result = typeToString(type.type) + "[]"
|
||||
break
|
||||
case "Promise":
|
||||
result = "Promise<" + allTypes(type.arguments) + ">"
|
||||
break
|
||||
case "union":
|
||||
result = type.arguments.map(typeToString).join(" | ")
|
||||
break
|
||||
case "LiteralType":
|
||||
result = type.name
|
||||
break
|
||||
case "tuple":
|
||||
result = "(" + type.arguments.map(typeToString).join(", ") + ")"
|
||||
case "any":
|
||||
case "object":
|
||||
case "boolean":
|
||||
case "number":
|
||||
case "string":
|
||||
case "void":
|
||||
default:
|
||||
result = type.kind
|
||||
}
|
||||
|
||||
let name = ""
|
||||
// If the type has a name, it could be interesting to add it to the string
|
||||
// representation of the type. However, when the type is a LiteralType, the
|
||||
// name is already in result, so there's no need to add it.
|
||||
if (type.kind != "LiteralType" && type.name) name = type.name + ": "
|
||||
return name + result
|
||||
}
|
|
@ -381,6 +381,10 @@ export async function getProfileDir() {
|
|||
}
|
||||
}
|
||||
|
||||
if (config.get("profiledir") != "auto") {
|
||||
return config.get("profiledir")
|
||||
}
|
||||
|
||||
// First, see if we can get the profile from the arguments that were given
|
||||
// to Firefox
|
||||
let args = await ffargs()
|
||||
|
|
|
@ -10,7 +10,7 @@ function getChangelogDiv() {
|
|||
function updateChangelogStatus() {
|
||||
const changelogDiv = getChangelogDiv()
|
||||
const changelogContent = changelogDiv.textContent
|
||||
if (localStorage.changelogContent == changelogContent) {
|
||||
if (localStorage["changelogContent"] == changelogContent) {
|
||||
const changelogButton = document.querySelector('input[id^="spoiler"]')
|
||||
if (!changelogButton) {
|
||||
console.error("Couldn't find changelog button!")
|
||||
|
@ -22,7 +22,7 @@ function updateChangelogStatus() {
|
|||
|
||||
function readChangelog() {
|
||||
const changelogDiv = getChangelogDiv()
|
||||
localStorage.changelogContent = changelogDiv.textContent
|
||||
localStorage["changelogContent"] = changelogDiv.textContent
|
||||
updateChangelogStatus()
|
||||
}
|
||||
|
||||
|
|
|
@ -226,9 +226,14 @@ a.url:hover {
|
|||
padding-left: 0.5em;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.SettingsCompletionOption td.title {
|
||||
padding-left: 0.5em;
|
||||
width: 20%;
|
||||
}
|
||||
.ExcmdCompletionOption td.type {
|
||||
width: 30%;
|
||||
}
|
||||
.ExcmdCompletionOption td.documentation {
|
||||
width: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#cmdline_iframe {
|
||||
position: fixed !important;
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
z-index: 2147483647 !important;
|
||||
width: 100% !important;
|
||||
height: 0 !important;
|
||||
|
|
|
@ -77,6 +77,10 @@
|
|||
padding-top: 7px !important;
|
||||
}
|
||||
|
||||
#main-window[sizemode="maximized"] #content-deck {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
/* Hide URL notifications that aren't particularly useful and cover up the command line
|
||||
* TODO: move it above the command line / bring functionality into status line like Vimperator */
|
||||
statuspanel[type="overLink"],
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { staticThemes } from "./.metadata.generated"
|
||||
import * as config from "./config"
|
||||
import * as Logging from "./logging"
|
||||
|
||||
const logger = new Logging.Logger("styling")
|
||||
|
||||
// find a way of getting theme list without hard-coding it
|
||||
// using a macro might be an option
|
||||
const THEMES = ["dark", "greenmat", "shydactyl", "quake"]
|
||||
const THEMES = staticThemes
|
||||
|
||||
function capitalise(str) {
|
||||
if (str === "") return str
|
||||
|
|
Loading…
Add table
Reference in a new issue