Merge branch 'master' of github.com:cmcaine/tridactyl into configdocs

This commit is contained in:
Oliver Blanthorn 2018-08-29 10:58:34 +01:00
commit fd218e060b
No known key found for this signature in database
GPG key ID: 2BB8C36BB504BFF3
38 changed files with 886 additions and 430 deletions

View file

@ -52,9 +52,9 @@ quickmark t https://github.com/cmcaine/tridactyl/issues?utf8=%E2%9C%93&q=sort%3A
" "
" New reddit is bad " 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 " 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 :) " This will have to do until someone writes us a nice syntax file :)
" vim: set filetype=vim: " vim: set filetype=vim:

View file

@ -1,5 +1,48 @@
# Tridactyl changelog # 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 ## Release 1.13.2 / 2018-08-15
* New features: * New features:

View file

@ -2,10 +2,99 @@ import * as ts from "typescript"
import * as fs from "fs" import * as fs from "fs"
import * as commandLineArgs from "command-line-args" 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 */ /** True if this is visible outside this file, false otherwise */
function isNodeExported(node: ts.Node): boolean { function isNodeExported(node: ts.Node): boolean {
return ( 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) (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
) )
} }
@ -33,7 +122,11 @@ function visit(checker: any, filename: string, node: any, everything: any) {
symbol, symbol,
symbol.valueDeclaration!, symbol.valueDeclaration!,
) )
if (ttype) nodeInfo["type"] = checker.typeToString(ttype) if (ttype) {
nodeInfo["type"] = new SimpleType(
checker.typeToTypeNode(ttype),
)
}
} }
break break
// Other declaration syntaxkinds: // 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)) 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, { let program = ts.createProgram(fileNames, {
target: ts.ScriptTarget.ES5, target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS, module: ts.ModuleKind.CommonJS,
@ -74,23 +172,33 @@ function generateMetadata(out: string, fileNames: string[]): void {
if (n) visit(program.getTypeChecker(), n, sourceFile, everything) 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 // print out the doc
fs.writeFileSync( fs.writeFileSync(out, metadataString)
out,
"export let everything = " + JSON.stringify(everything, undefined, 4),
)
return return
} }
let opts = commandLineArgs([ let opts = commandLineArgs([
{ name: "out", type: String }, { name: "out", type: String },
{ name: "themeDir", type: String },
{ name: "src", type: String, multiple: true, defaultOption: true }, { name: "src", type: String, multiple: true, defaultOption: true },
]) ])
if (!opts.out) if (!opts.out || opts.src.length < 1)
throw new Error( throw new Error(
"Argument syntax: --out outfile [--src] file1.ts [file2.ts ...]", "Argument syntax: --out outfile [--src] file1.ts [file2.ts ...]",
) )
generateMetadata(opts.out, opts.src) generateMetadata(opts.out, opts.themeDir, opts.src)

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
source ./scripts/common source ./scripts/common

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e 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" native_loc="https://raw.githubusercontent.com/cmcaine/tridactyl/master/native/native_main.py"
# Decide where to put the manifest based on OS # Decide where to put the manifest based on OS
if [[ "$OSTYPE" == "linux-gnu" ]]; then case "$OSTYPE" in
manifest_home="$HOME/.mozilla/native-messaging-hosts/" linux-gnu|linux|freebsd*)
elif [[ "$OSTYPE" == "darwin"* ]]; then manifest_home="$HOME/.mozilla/native-messaging-hosts/"
manifest_home="$HOME/Library/Application Support/Mozilla/NativeMessagingHosts/" ;;
fi 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" mkdir -p "$manifest_home" "$XDG_DATA_HOME"

132
package-lock.json generated
View file

@ -113,25 +113,42 @@
"resolved": "https://registry.npmjs.org/@types/css/-/css-0.0.31.tgz", "resolved": "https://registry.npmjs.org/@types/css/-/css-0.0.31.tgz",
"integrity": "sha512-Xt3xp8o0zueqrdIkAOO5gy5YNs+jYfmIUPeoeKiwrcmCRXuNWkIgR2Ph6vHuVfi1y6c9Tx214EQBWPEkU97djw==" "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": { "@types/fs-extra": {
"version": "4.0.0", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-4.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.4.tgz",
"integrity": "sha512-PlKJw6ujJXLJjbvB3T0UCbY3jibKM6/Ya5cc9j1q+mYDeK3aR4Dp+20ZwxSuvJr9mIoPxp7+IL4aMOEvsscRTA==", "integrity": "sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "*" "@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": { "@types/handlebars": {
"version": "4.0.31", "version": "4.0.39",
"resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.31.tgz", "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.39.tgz",
"integrity": "sha1-p/umb6/kJxOu6I7sqNuRGS7+bnI=", "integrity": "sha512-vjaS7Q0dVqFp85QhyPSZqDKnTTCemcSHNHFvDdalO1s0Ifz5KuE64jQD5xoUkfdWwF4WpqdJEl7LsWH8rzhKJA==",
"dev": true "dev": true
}, },
"@types/highlight.js": { "@types/highlight.js": {
"version": "9.1.8", "version": "9.12.3",
"resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.1.8.tgz", "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.3.tgz",
"integrity": "sha1-0ifxi8uPPxh+FpZfJESFmgRol1g=", "integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==",
"dev": true "dev": true
}, },
"@types/jest": { "@types/jest": {
@ -141,21 +158,21 @@
"dev": true "dev": true
}, },
"@types/lodash": { "@types/lodash": {
"version": "4.14.74", "version": "4.14.116",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.74.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.116.tgz",
"integrity": "sha512-BZknw3E/z3JmCLqQVANcR17okqVTPZdlxvcIz0fJiJVLUCbSH1hK3zs9r634PVSmrzAxN+n/fxlVRiYoArdOIQ==", "integrity": "sha512-lRnAtKnxMXcYYXqOiotTmJd74uawNWuPnsnPrrO7HiFuE3npE2iQhfABatbYDyxTNqZNuXzcKGhw37R7RjBFLg==",
"dev": true "dev": true
}, },
"@types/marked": { "@types/marked": {
"version": "0.3.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.4.0.tgz",
"integrity": "sha512-CSf9YWJdX1DkTNu9zcNtdCcn6hkRtB5ILjbhRId4ZOQqx30fXmdecuaXhugQL6eyrhuXtaHJ7PHI+Vm7k9ZJjg==", "integrity": "sha512-xkURX55US18wHme+O2UlqJf3Fo7FqT5VAL+OJ/zK+jP2NX57naryDHoiqt/pMIwZjDc62sRvXUWuQQxQiBdheQ==",
"dev": true "dev": true
}, },
"@types/minimatch": { "@types/minimatch": {
"version": "2.0.29", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-2.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha1-UALhT3Xi1x5WQoHfBDHIwbSio2o=", "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"dev": true "dev": true
}, },
"@types/nearley": { "@types/nearley": {
@ -170,11 +187,12 @@
"dev": true "dev": true
}, },
"@types/shelljs": { "@types/shelljs": {
"version": "0.7.0", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.7.0.tgz", "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.0.tgz",
"integrity": "sha1-IpwVfGvB5n1rmQ5sXhjb0v9Yz/A=", "integrity": "sha512-vs1hCC8RxLHRu2bwumNyYRNrU3o8BtZhLysH5A4I98iYmA2APl6R3uNQb5ihl+WiwH0xdC9LLO+vRrXLs/Kyxg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/glob": "*",
"@types/node": "*" "@types/node": "*"
} }
}, },
@ -11800,47 +11818,51 @@
"dev": true "dev": true
}, },
"typedoc": { "typedoc": {
"version": "0.9.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.9.0.tgz", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.12.0.tgz",
"integrity": "sha512-numP0CtcUK4I1Vssw6E1N/FjyJWpWqhLT4Zb7Gw3i7ca3ElnYh6z41Y/tcUhMsMYn6L8b67E/Fu4XYYKkNaLbA==", "integrity": "sha512-dsdlaYZ7Je8JC+jQ3j2Iroe4uyD0GhqzADNUVyBRgLuytQDP/g0dPkAw5PdM/4drnmmJjRzSWW97FkKo+ITqQg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/fs-extra": "4.0.0", "@types/fs-extra": "^5.0.3",
"@types/handlebars": "4.0.31", "@types/handlebars": "^4.0.38",
"@types/highlight.js": "9.1.8", "@types/highlight.js": "^9.12.3",
"@types/lodash": "4.14.74", "@types/lodash": "^4.14.110",
"@types/marked": "0.3.0", "@types/marked": "^0.4.0",
"@types/minimatch": "2.0.29", "@types/minimatch": "3.0.3",
"@types/shelljs": "0.7.0", "@types/shelljs": "^0.8.0",
"fs-extra": "^4.0.0", "fs-extra": "^7.0.0",
"handlebars": "^4.0.6", "handlebars": "^4.0.6",
"highlight.js": "^9.0.0", "highlight.js": "^9.0.0",
"lodash": "^4.13.1", "lodash": "^4.17.10",
"marked": "^0.3.5", "marked": "^0.4.0",
"minimatch": "^3.0.0", "minimatch": "^3.0.0",
"progress": "^2.0.0", "progress": "^2.0.0",
"shelljs": "^0.7.0", "shelljs": "^0.8.2",
"typedoc-default-themes": "^0.5.0", "typedoc-default-themes": "^0.5.0",
"typescript": "2.4.1" "typescript": "3.0.x"
}, },
"dependencies": { "dependencies": {
"marked": { "fs-extra": {
"version": "0.3.19", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz",
"integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "integrity": "sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==",
"dev": true "dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
}, },
"progress": { "shelljs": {
"version": "2.0.0", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz",
"integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==",
"dev": true "dev": true,
}, "requires": {
"typescript": { "glob": "^7.0.0",
"version": "2.4.1", "interpret": "^1.0.0",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.1.tgz", "rechoir": "^0.6.2"
"integrity": "sha1-w8yxbdqgsjFN4DHn5v7onlujRrw=", }
"dev": true
} }
} }
}, },
@ -11850,9 +11872,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "2.5.3", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz",
"integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==", "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==",
"dev": true "dev": true
}, },
"typical": { "typical": {

View file

@ -27,9 +27,9 @@
"source-map-loader": "^0.2.2", "source-map-loader": "^0.2.2",
"ts-jest": "^21.1.3", "ts-jest": "^21.1.3",
"ts-node": "^3.3.0", "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", "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", "uglify-es": "^3.1.5",
"uglifyjs-webpack-plugin": "^1.2.5", "uglifyjs-webpack-plugin": "^1.2.5",
"web-ext": "^2.7.0", "web-ext": "^2.7.0",

View file

@ -8,7 +8,7 @@ Replace Firefox's default control mechanism with one modelled on the one true ed
## Installing ## 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 :) 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-badge]: https://matrix.to/img/matrix-badge.svg
[matrix-link]: https://riot.im/app/#/room/#tridactyl:matrix.org [matrix-link]: https://riot.im/app/#/room/#tridactyl:matrix.org
[betas]: https://tridactyl.cmcaine.co.uk/betas/?sort=time&order=desc [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 [amo]: https://addons.mozilla.org/en-US/firefox/addon/tridactyl-vim?src=external-github

View file

@ -38,7 +38,10 @@ fi
# It's important to generate the metadata before the documentation because # It's important to generate the metadata before the documentation because
# missing imports might break documentation generation on clean builds # missing imports might break documentation generation on clean builds
"$(npm bin)/tsc" compiler/gen_metadata.ts -m commonjs \ "$(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/newtab.md.sh
scripts/make_tutorial.sh scripts/make_tutorial.sh

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# Accepts no arguments # Accepts no arguments
# Returns git-add'ed files as a list of filenames separated by a newline character # Returns git-add'ed files as a list of filenames separated by a newline character

View file

@ -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 source ./scripts/common
set -e set -Eeuo pipefail
uglyFiles="$(ugly "$(cachedFiles)")" echoe() {
echo "$@" >&2
}
if [ -n "$uglyFiles" ]; then lock() {
IFS=$'\n' local lockfile="$1"
for file in $uglyFiles; do if [ -e "$lockfile" ]; then
if cmp -s <(staged "$file") "$file"; then echoe "Lockfile '$lockfile' already exists. Check no other operation is occuring or delete lockfile"
prettier --write "$file" return 1
git add "$file" else
else touch "$lockfile"
echo "WARN: Staged and working file differ: '$file'" fi
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") unlock() {
mv "$file" "$backup" rm "$1"
staged "$file" | prettier --stdin-filepath "$file" > "$file" }
git add "$file"
mv "$backup" "$file" trap "unlock $(git rev-parse --show-toplevel)/.git/index.lock || true" ERR
echo "WARN: Working file restored"
fi main() {
done local stagedFiles=$(cachedFiles)
fi
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

View file

@ -1,4 +1,4 @@
#!/bin/bash -e #!/usr/bin/env bash -e
# This script must be run from the root Tridactyl directory # This script must be run from the root Tridactyl directory
TRIDIR="$(pwd)" TRIDIR="$(pwd)"

View file

@ -34,3 +34,23 @@ export function expandExstr(
prevExpansions, 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
}

View file

@ -8,13 +8,13 @@ import { BufferCompletionSource } from "./completions/Buffer"
import { BmarkCompletionSource } from "./completions/Bmark" import { BmarkCompletionSource } from "./completions/Bmark"
import { ExcmdCompletionSource } from "./completions/Excmd" import { ExcmdCompletionSource } from "./completions/Excmd"
import { HistoryCompletionSource } from "./completions/History" import { HistoryCompletionSource } from "./completions/History"
import { SettingsCompletionSource } from "./completions/Settings"
import * as Messaging from "./messaging" import * as Messaging from "./messaging"
import * as Config from "./config" import * as Config from "./config"
import * as SELF from "./commandline_frame" import * as SELF from "./commandline_frame"
import "./number.clamp" import "./number.clamp"
import state from "./state" import state from "./state"
import Logger from "./logging" import Logger from "./logging"
import * as aliases from "./aliases"
import { theme } from "./styling" import { theme } from "./styling"
const logger = new Logger("cmdline") const logger = new Logger("cmdline")
@ -59,6 +59,7 @@ function enableCompletions() {
new BufferAllCompletionSource(completionsDiv), new BufferAllCompletionSource(completionsDiv),
new BufferCompletionSource(completionsDiv), new BufferCompletionSource(completionsDiv),
new ExcmdCompletionSource(completionsDiv), new ExcmdCompletionSource(completionsDiv),
new SettingsCompletionSource(completionsDiv),
new HistoryCompletionSource(completionsDiv), new HistoryCompletionSource(completionsDiv),
] ]
@ -207,13 +208,12 @@ clInput.addEventListener("keydown", function(keyevent) {
clInput.addEventListener("input", () => { clInput.addEventListener("input", () => {
const exstr = clInput.value const exstr = clInput.value
const expandedCmd = aliases.expandExstr(exstr)
// Fire each completion and add a callback to resize area // Fire each completion and add a callback to resize area
enableCompletions() enableCompletions()
logger.debug(activeCompletions) logger.debug(activeCompletions)
activeCompletions.forEach(comp => activeCompletions.forEach(comp =>
comp.filter(expandedCmd).then(() => resizeArea()), comp.filter(exstr).then(() => resizeArea()),
) )
}) })

View file

@ -13,6 +13,8 @@ Concrete completion classes have been moved to src/completions/.
import * as Fuse from "fuse.js" import * as Fuse from "fuse.js"
import { enumerate } from "./itertools" import { enumerate } from "./itertools"
import { toNumber } from "./convert" import { toNumber } from "./convert"
import * as config from "./config"
import * as aliases from "./aliases"
export const DEFAULT_FAVICON = browser.extension.getURL( export const DEFAULT_FAVICON = browser.extension.getURL(
"static/defaultFavicon.svg", "static/defaultFavicon.svg",
@ -33,6 +35,20 @@ export abstract class CompletionSource {
readonly options: CompletionOption[] readonly options: CompletionOption[]
node: HTMLElement node: HTMLElement
public completion: string 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 */ /** Update [[node]] to display completions relevant to exstr */
public abstract filter(exstr: string): Promise<void> public abstract filter(exstr: string): Promise<void>
@ -117,8 +133,8 @@ export abstract class CompletionSourceFuse extends CompletionSource {
protected optionContainer = html`<table class="optionContainer">` protected optionContainer = html`<table class="optionContainer">`
constructor(private prefixes, className: string, title?: string) { constructor(prefixes, className: string, title?: string) {
super() super(prefixes)
this.node = html`<div class="${className} hidden"> this.node = html`<div class="${className} hidden">
<div class="sectionHeader">${title || className}</div> <div class="sectionHeader">${title || className}</div>
</div>` </div>`

View file

@ -36,7 +36,7 @@ export class BmarkCompletionSource extends Completions.CompletionSourceFuse {
public options: BmarkCompletionOption[] public options: BmarkCompletionOption[]
constructor(private _parent) { constructor(private _parent) {
super(["bmarks "], "BmarkCompletionSource", "Bookmarks") super(["bmarks"], "BmarkCompletionSource", "Bookmarks")
this._parent.appendChild(this.node) this._parent.appendChild(this.node)
} }

View file

@ -57,7 +57,7 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
constructor(private _parent) { constructor(private _parent) {
super( super(
["buffer ", "tabclose ", "tabdetach ", "tabduplicate ", "tabmove "], ["buffer", "tabclose", "tabdetach", "tabduplicate", "tabmove"],
"BufferCompletionSource", "BufferCompletionSource",
"Buffers", "Buffers",
) )

View file

@ -42,7 +42,7 @@ export class BufferAllCompletionSource extends Completions.CompletionSourceFuse
public options: BufferAllCompletionOption[] public options: BufferAllCompletionOption[]
constructor(private _parent) { constructor(private _parent) {
super(["bufferall "], "BufferAllCompletionSource", "All Buffers") super(["bufferall"], "BufferAllCompletionSource", "All Buffers")
this.updateOptions() this.updateOptions()
this._parent.appendChild(this.node) this._parent.appendChild(this.node)

View file

@ -1,14 +1,17 @@
import * as Completions from "../completions" import * as Completions from "../completions"
import { typeToSimpleString } from "../metadata"
import * as Metadata from "../.metadata.generated" import * as Metadata from "../.metadata.generated"
import state from "../state" import state from "../state"
import * as config from "../config"
import * as aliases from "../aliases"
class ExcmdCompletionOption extends Completions.CompletionOptionHTML class ExcmdCompletionOption extends Completions.CompletionOptionHTML
implements Completions.CompletionOptionFuse { implements Completions.CompletionOptionFuse {
public fuseKeys = [] public fuseKeys = []
constructor( constructor(
public value: string, public value: string,
public ttype: string, public ttype: string = "",
public documentation: string, public documentation: string = "",
) { ) {
super() super()
this.fuseKeys.push(this.value) this.fuseKeys.push(this.value)
@ -16,10 +19,10 @@ class ExcmdCompletionOption extends Completions.CompletionOptionHTML
// Create HTMLElement // Create HTMLElement
this.html = html`<tr class="ExcmdCompletionOption option"> this.html = html`<tr class="ExcmdCompletionOption option">
<td class="excmd">${value}</td> <td class="excmd">${value}</td>
<td class="type">${ttype}</td>
<td class="documentation">${documentation}</td> <td class="documentation">${documentation}</td>
</tr>` </tr>`
} }
// <td class="type">${ttype}</td>
} }
export class ExcmdCompletionSource extends Completions.CompletionSourceFuse { export class ExcmdCompletionSource extends Completions.CompletionSourceFuse {
@ -50,29 +53,56 @@ export class ExcmdCompletionSource extends Completions.CompletionSourceFuse {
this.options = (await this.scoreOptions( this.options = (await this.scoreOptions(
Object.keys(fns).filter(f => f.startsWith(exstr)), Object.keys(fns).filter(f => f.startsWith(exstr)),
)).map(f => { )).map(f => {
try { let t = ""
return new ExcmdCompletionOption(f, fns[f].type, fns[f].doc) if (fns[f].type) t = typeToSimpleString(fns[f].type)
} catch { return new ExcmdCompletionOption(f, t, fns[f].doc)
return new ExcmdCompletionOption("", "", "")
}
}) })
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.options.forEach(o => (o.state = "normal"))
this.updateChain() this.updateChain()
} }
private async scoreOptions(exstrs: string[]) { private async scoreOptions(exstrs: string[]) {
let histpos = state.cmdHistory.map(s => s.split(" ")[0]).reverse() return exstrs.sort()
return exstrs.sort((a, b) => {
let posa = histpos.findIndex(x => x == a) // Too slow with large profiles
let posb = histpos.findIndex(x => x == b) // let histpos = state.cmdHistory.map(s => s.split(" ")[0]).reverse()
// If two ex commands have the same position, sort lexically // return exstrs.sort((a, b) => {
if (posa == posb) return a < b ? -1 : 1 // let posa = histpos.findIndex(x => x == a)
// If they aren't found in the list they get lower priority // let posb = histpos.findIndex(x => x == b)
if (posa == -1) return 1 // // If two ex commands have the same position, sort lexically
if (posb == -1) return -1 // if (posa == posb) return a < b ? -1 : 1
// Finally, sort by history position // // If they aren't found in the list they get lower priority
return posa < posb ? -1 : 1 // if (posa == -1) return 1
}) // if (posb == -1) return -1
// // Finally, sort by history position
// return posa < posb ? -1 : 1
// })
} }
select(option: ExcmdCompletionOption) { select(option: ExcmdCompletionOption) {

View file

@ -35,7 +35,7 @@ export class HistoryCompletionSource extends Completions.CompletionSourceFuse {
constructor(private _parent) { constructor(private _parent) {
super( super(
["open ", "tabopen ", "winopen "], ["open", "tabopen", "winopen"],
"HistoryCompletionSource", "HistoryCompletionSource",
"History", "History",
) )

View 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() {}
}

View file

@ -192,7 +192,7 @@ config.getAsync("leavegithubalone").then(v => {
// On quick loading pages, the document is already loaded // On quick loading pages, the document is already loaded
// if (document.location.host == "github.com") { // if (document.location.host == "github.com") {
document.body.addEventListener("keydown", function(e) { document.body.addEventListener("keydown", function(e) {
if ("/".indexOf(e.key) != -1) { if ("/".indexOf(e.key) != -1 && state.mode == "normal") {
e.cancelBubble = true e.cancelBubble = true
e.stopImmediatePropagation() e.stopImmediatePropagation()
} }
@ -203,7 +203,7 @@ config.getAsync("leavegithubalone").then(v => {
window.addEventListener("DOMContentLoaded", () => { window.addEventListener("DOMContentLoaded", () => {
// if (document.location.host == "github.com") { // if (document.location.host == "github.com") {
document.body.addEventListener("keydown", function(e) { document.body.addEventListener("keydown", function(e) {
if ("/".indexOf(e.key) != -1) { if ("/".indexOf(e.key) != -1 && state.mode == "normal") {
e.cancelBubble = true e.cancelBubble = true
e.stopImmediatePropagation() e.stopImmediatePropagation()
} }

View file

@ -111,6 +111,13 @@ export const potentialRules = {
show: ``, show: ``,
}, },
}, },
padwhenmaximised: {
name: `#main-window[sizemode="maximized"] #content-deck`,
options: {
some: `padding-top: 8px;`,
none: ``,
},
},
} }
// Vimperator's options for reference: // Vimperator's options for reference:
@ -141,6 +148,7 @@ export const metaRules = {
navbar: "autohide", navbar: "autohide",
titlebar: "hide", titlebar: "hide",
menubar: "grey", menubar: "grey",
padwhenmaximised: "some",
}, },
full: { full: {
hoverlink: "left", hoverlink: "left",
@ -148,6 +156,7 @@ export const metaRules = {
navbar: "always", navbar: "always",
titlebar: "show", titlebar: "show",
menubar: "default", menubar: "default",
padwhenmaximised: "none",
}, },
}, },
tabs: { tabs: {

View file

@ -95,7 +95,7 @@
// Shared // Shared
import * as Messaging from "./messaging" 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 * as Container from "./lib/containers"
import state from "./state" import state from "./state"
import * as UrlUtil from "./url_util" 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 // I went through the whole list https://developer.mozilla.org/en-US/Firefox/The_about_protocol
// about:blank is even more special // about:blank is even more special
/** @hidden */ /** @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. /** Open a new page in the current tab.
* *
@ -1602,6 +1602,7 @@ export async function tabopen(...addressarr: string[]) {
let url: string let url: string
let address = (await argParse(addressarr)).join(" ") let address = (await argParse(addressarr)).join(" ")
if (address == "") address = config.get("newtab")
if (!ABOUT_WHITELIST.includes(address) && address.match(/^(about|file):.*/)) { if (!ABOUT_WHITELIST.includes(address) && address.match(/^(about|file):.*/)) {
if ((await browser.runtime.getPlatformInfo()).os === "mac" && (await browser.windows.getCurrent()).incognito) { 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 :).") 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 return
} }
} else if (address != "") url = forceURI(address) } else if (address != "") url = forceURI(address)
else url = forceURI(config.get("newtab"))
activeTabContainerId().then(containerId => { activeTabContainerId().then(containerId => {
// Ensure -c has priority. // Ensure -c has priority.
@ -1762,17 +1762,24 @@ export async function tabclosealltoleft() {
//#background //#background
export async function undo() { export async function undo() {
const current_win_id: number = (await browser.windows.getCurrent()).id 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. // 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 => { const lastSession = sessions.find(s => {
return "window" in s || (s.tab && s.tab.windowId == current_win_id) 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) { if (lastSession) {
browser.sessions.restore(closed.tab.sessionId) if (lastSession.tab) {
} else if (closed.window) { browser.sessions.restore(lastSession.tab.sessionId)
browser.sessions.restore(closed.window.sessionId) } else if (lastSession.window) {
browser.sessions.restore(lastSession.window.sessionId)
} }
} }
} }
@ -2892,7 +2899,6 @@ import * as hinting from "./hinting"
@param option @param option
- -b open in background - -b open in background
- -br repeatedly open in background
- -y copy (yank) link's target to clipboard - -y copy (yank) link's target to clipboard
- -p copy an element's text to the clipboard - -p copy an element's text to the clipboard
- -P copy an element's title/alt 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 - -c [selector] hint links that match the css selector
- `bind ;c hint -c [class*="expand"],[class="togg"]` works particularly well on reddit and HN - `bind ;c hint -c [class*="expand"],[class="togg"]` works particularly well on reddit and HN
- -w open in new window - -w open in new window
-wp open in new private 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`. - `-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.
- **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. - `-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; `;g<option character>` starts rapid hint mode for all modes where it makes sense, and some others.
Excepting the custom selector mode and background hint mode, each of these
hint modes is available by default as `;<option character>`, so e.g. `;y`
to yank a link's target.
To open a hint in the background, the default bind is `F`. To open a hint in the background, the default bind is `F`.
@ -2943,150 +2948,257 @@ import * as hinting from "./hinting"
*/ */
//#content //#content
export async function hint(option?: string, selectors?: string, ...rest: string[]) { 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 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 switch (option) {
if (option === "-b") { case "-b":
selectHints = hinting.pipe(DOM.HINTTAGS_selectors) // Open in background
onSelected = async result => { selectHints = hinting.pipe(
let [link, hintCount] = result as [HTMLAnchorElement, number] DOM.HINTTAGS_selectors,
link.focus() async link => {
if (link.href) { link.focus()
let containerId = await activeTabContainerId() if (link.href) {
if (containerId) { hintTabOpen(link.href, false).catch(() => DOM.simulateClick(link))
openInNewTab(link.href, { } else {
active: false, DOM.simulateClick(link)
related: true, }
cookieStoreId: containerId, return link
}).catch(() => DOM.simulateClick(link)) },
} else { rapid,
openInNewTab(link.href, { )
active: false, break
related: true,
}).catch(() => DOM.simulateClick(link)) 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 { } 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 return selectHints
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)
}
},
),
)
} }
// how 2 crash pc // how 2 crash pc

View file

@ -28,31 +28,25 @@ import Logger from "./logging"
import * as Messaging from "./messaging" import * as Messaging from "./messaging"
const logger = new Logger("hinting") const logger = new Logger("hinting")
export enum HintRejectionReason {
Err,
User,
NoHints,
}
/** Simple container for the state of a single frame's hints. */ /** Simple container for the state of a single frame's hints. */
class HintState { class HintState {
public focusedHint: Hint public focusedHint: Hint
readonly hintHost = document.createElement("div") readonly hintHost = document.createElement("div")
readonly hints: Hint[] = [] readonly hints: Hint[] = []
public selectedHints: Hint[] = []
public filter = "" public filter = ""
public hintchars = "" public hintchars = ""
constructor( constructor(
public filterFunc: HintFilter, public filterFunc: HintFilter,
public resolve: (Hint) => void, public resolve: (Hint) => void,
public reject: (HintRejectionReason) => any, public reject: (any) => void,
public rapid: boolean,
) { ) {
this.hintHost.classList.add("TridactylHintHost", "cleanslate") this.hintHost.classList.add("TridactylHintHost", "cleanslate")
} }
destructor(abort) { destructor() {
if (!this.focusedHint) this.focusedHint = this.hints[0]
// Undo any alterations of the hinted elements // Undo any alterations of the hinted elements
for (const hint of this.hints) { for (const hint of this.hints) {
hint.hidden = true hint.hidden = true
@ -61,9 +55,11 @@ class HintState {
// Remove all hints from the DOM. // Remove all hints from the DOM.
this.hintHost.remove() this.hintHost.remove()
if (abort) this.reject(HintRejectionReason.User) if (this.rapid) this.resolve(this.selectedHints.map(h => h.result))
else if (!this.focusedHint) this.reject(HintRejectionReason.NoHints) else
else this.resolve([this.focusedHint.target, this.hints.length]) this.resolve(
this.selectedHints[0] ? this.selectedHints[0].result : "",
)
} }
} }
@ -75,13 +71,25 @@ export function hintPage(
onSelect: HintSelectedCallback, onSelect: HintSelectedCallback,
resolve = () => {}, resolve = () => {},
reject = () => {}, reject = () => {},
rapid = false,
) { ) {
let buildHints: HintBuilder = defaultHintBuilder() let buildHints: HintBuilder = defaultHintBuilder()
let filterHints: HintFilter = defaultHintFilter() let filterHints: HintFilter = defaultHintFilter()
state.mode = "hint" 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) { if (modeState.hints.length) {
let firstTarget = modeState.hints[0].target let firstTarget = modeState.hints[0].target
@ -227,6 +235,7 @@ type HintSelectedCallback = (Hint) => any
/** Place a flag by each hintworthy element */ /** Place a flag by each hintworthy element */
class Hint { class Hint {
public readonly flag = document.createElement("span") public readonly flag = document.createElement("span")
public result: any = null
constructor( constructor(
public readonly target: Element, 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 abort is true, we're resetting because the user pressed escape.
* If it is false, we're resetting because the user selected a hint. * If it is false, we're resetting because the user selected a hint.
**/ **/
function reset(abort = false) { function reset() {
modeState.destructor(abort) if (modeState) {
modeState.destructor()
}
modeState = undefined modeState = undefined
state.mode = "normal" state.mode = "normal"
} }
@ -473,7 +484,7 @@ function pushKey(ke) {
1. Within viewport 1. Within viewport
2. Not hidden by another element 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, []) let elems = DOM.getElemsBySelector(selectors, [])
if (withjs) { if (withjs) {
elems = elems.concat(DOM.hintworthy_js_elems) 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 /** Returns elements that point to a saveable resource
*/ */
function saveableElements() { export function saveableElements() {
return DOM.getElemsBySelector(DOM.HINTTAGS_saveable, [DOM.isVisible]) return DOM.getElemsBySelector(DOM.HINTTAGS_saveable, [DOM.isVisible])
} }
/** Get array of images in the viewport /** Get array of images in the viewport
*/ */
function hintableImages() { export function hintableImages() {
return DOM.getElemsBySelector(DOM.HINTTAGS_img_selectors, [DOM.isVisible]) return DOM.getElemsBySelector(DOM.HINTTAGS_img_selectors, [DOM.isVisible])
} }
/** Array of items that can be killed with hint kill /** Array of items that can be killed with hint kill
*/ */
function killables() { export function killables() {
return DOM.getElemsBySelector(DOM.HINTTAGS_killable_selectors, [ return DOM.getElemsBySelector(DOM.HINTTAGS_killable_selectors, [
DOM.isVisible, DOM.isVisible,
]) ])
@ -505,130 +516,33 @@ function killables() {
import { openInNewTab, activeTabContainerId } from "./lib/webext" import { openInNewTab, activeTabContainerId } from "./lib/webext"
import { openInNewWindow } 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( export function pipe(
selectors = DOM.HINTTAGS_selectors, selectors = DOM.HINTTAGS_selectors,
action: HintSelectedCallback = _ => _,
rapid = false,
): Promise<[Element, number]> { ): Promise<[Element, number]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
hintPage(hintables(selectors, true), () => {}, resolve, reject) hintPage(hintables(selectors, true), action, resolve, reject, rapid)
}) })
} }
export function pipe_elements( export function pipe_elements(
elements: any = DOM.elementsWithText, elements: any = DOM.elementsWithText,
action: HintSelectedCallback = _ => _,
rapid = false,
): Promise<[Element, number]> { ): Promise<[Element, number]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
hintPage(elements, () => {}, resolve, reject) hintPage(elements, action, resolve, reject, rapid)
})
}
/** 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])
}) })
} }
function selectFocusedHint(delay = false) { function selectFocusedHint(delay = false) {
logger.debug("Selecting hint.", state.mode) logger.debug("Selecting hint.", state.mode)
const focused = modeState.focusedHint const focused = modeState.focusedHint
let select = () => { modeState.filter = ""
reset() modeState.hints.forEach(h => (h.hidden = false))
focused.select() if (delay) setTimeout(() => focused.select(), config.get("hintdelay"))
} else focused.select()
if (delay) setTimeout(select, config.get("hintdelay"))
else select()
} }
import { addListener, attributeCaller } from "./messaging" import { addListener, attributeCaller } from "./messaging"

View file

@ -300,11 +300,6 @@ export function mapstrMapToKeyMap(mapstrMap: Map<string, MapTarget>): KeyMap {
return newKeyMap return newKeyMap
} }
export function mapstrObjToKeyMap(mapstrObj): KeyMap {
const mapstrMap = new Map(Object.entries(mapstrObj))
return mapstrMapToKeyMap(mapstrMap)
}
// }}} // }}}
// {{{ Utility functions for dealing with KeyboardEvents // {{{ Utility functions for dealing with KeyboardEvents

View file

@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "Tridactyl", "name": "Tridactyl",
"version": "1.13.2", "version": "1.13.3",
"icons": { "icons": {
"64": "static/logo/Tridactyl_64px.png", "64": "static/logo/Tridactyl_64px.png",
"100": "static/logo/Tridactyl_100px.png", "100": "static/logo/Tridactyl_100px.png",

68
src/metadata.ts Normal file
View 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
}

View file

@ -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 // First, see if we can get the profile from the arguments that were given
// to Firefox // to Firefox
let args = await ffargs() let args = await ffargs()

View file

@ -10,7 +10,7 @@ function getChangelogDiv() {
function updateChangelogStatus() { function updateChangelogStatus() {
const changelogDiv = getChangelogDiv() const changelogDiv = getChangelogDiv()
const changelogContent = changelogDiv.textContent const changelogContent = changelogDiv.textContent
if (localStorage.changelogContent == changelogContent) { if (localStorage["changelogContent"] == changelogContent) {
const changelogButton = document.querySelector('input[id^="spoiler"]') const changelogButton = document.querySelector('input[id^="spoiler"]')
if (!changelogButton) { if (!changelogButton) {
console.error("Couldn't find changelog button!") console.error("Couldn't find changelog button!")
@ -22,7 +22,7 @@ function updateChangelogStatus() {
function readChangelog() { function readChangelog() {
const changelogDiv = getChangelogDiv() const changelogDiv = getChangelogDiv()
localStorage.changelogContent = changelogDiv.textContent localStorage["changelogContent"] = changelogDiv.textContent
updateChangelogStatus() updateChangelogStatus()
} }

View file

@ -226,9 +226,14 @@ a.url:hover {
padding-left: 0.5em; padding-left: 0.5em;
width: 20%; width: 20%;
} }
.SettingsCompletionOption td.title {
padding-left: 0.5em;
width: 20%;
}
.ExcmdCompletionOption td.type { .ExcmdCompletionOption td.type {
width: 30%; width: 30%;
} }
.ExcmdCompletionOption td.documentation { .ExcmdCompletionOption td.documentation {
width: 50%; width: 100%;
} }

View file

@ -1,7 +1,6 @@
#cmdline_iframe { #cmdline_iframe {
position: fixed !important; position: fixed !important;
bottom: 0 !important; bottom: 0 !important;
left: 0 !important;
z-index: 2147483647 !important; z-index: 2147483647 !important;
width: 100% !important; width: 100% !important;
height: 0 !important; height: 0 !important;

View file

@ -77,6 +77,10 @@
padding-top: 7px !important; 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 /* 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 */ * TODO: move it above the command line / bring functionality into status line like Vimperator */
statuspanel[type="overLink"], statuspanel[type="overLink"],

View file

@ -1,11 +1,10 @@
import { staticThemes } from "./.metadata.generated"
import * as config from "./config" import * as config from "./config"
import * as Logging from "./logging" import * as Logging from "./logging"
const logger = new Logging.Logger("styling") const logger = new Logging.Logger("styling")
// find a way of getting theme list without hard-coding it const THEMES = staticThemes
// using a macro might be an option
const THEMES = ["dark", "greenmat", "shydactyl", "quake"]
function capitalise(str) { function capitalise(str) {
if (str === "") return str if (str === "") return str