Update to ts6

Most of the changed lines in this are from random
breaking changes in typedoc meaning that lots of
docstrings needed to be rewritten
This commit is contained in:
Oliver Blanthorn 2026-07-20 00:24:35 +02:00
parent 15aee6cf3f
commit fcb55685d2
No known key found for this signature in database
GPG key ID: 2BB8C36BB504BFF3
34 changed files with 1152 additions and 1010 deletions

View file

@ -50,35 +50,16 @@ module.exports = {
"@typescript-eslint/array-type": "off", "@typescript-eslint/array-type": "off",
"@typescript-eslint/await-thenable": "error", "@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-comment": "error", "@typescript-eslint/ban-ts-comment": "error",
"@typescript-eslint/ban-types": [ "@typescript-eslint/no-restricted-types": [
"error", "error",
{ {
"types": { "types": {
"Object": {
"message": "Avoid using the `Object` type. Did you mean `object`?"
},
"Function": {
"message": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`."
},
"Boolean": {
"message": "Avoid using the `Boolean` type. Did you mean `boolean`?"
},
"Number": {
"message": "Avoid using the `Number` type. Did you mean `number`?"
},
"String": {
"message": "Avoid using the `String` type. Did you mean `string`?"
},
"KeyboardEvent": { "KeyboardEvent": {
"message": "Use `TrustedKeyboardEvent` to prevent remote code injection from hostile pages." "message": "Use `TrustedKeyboardEvent` to prevent remote code injection from hostile pages."
},
"Symbol": {
"message": "Avoid using the `Symbol` type. Did you mean `symbol`?"
} }
} }
} }
], ],
"@typescript-eslint/class-name-casing": "off",
"@typescript-eslint/consistent-type-assertions": "error", "@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/consistent-type-definitions": "error", "@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/dot-notation": "off", // this should be "error" but the fix silently breaks code almost 100% of the time. not worth the headaches "@typescript-eslint/dot-notation": "off", // this should be "error" but the fix silently breaks code almost 100% of the time. not worth the headaches
@ -89,28 +70,14 @@ module.exports = {
} }
], ],
"@typescript-eslint/explicit-module-boundary-types": "off", //"warn", // This is another hard one to enable "@typescript-eslint/explicit-module-boundary-types": "off", //"warn", // This is another hard one to enable
"@typescript-eslint/indent": "off",
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/member-delimiter-style": [
"off",
{
"multiline": {
"delimiter": "none",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/member-ordering": [ "@typescript-eslint/member-ordering": [
"error", "error",
{ "default": ["field", "constructor", "method"] }, { "default": ["field", "constructor", "method"] },
], ],
"@typescript-eslint/no-array-delete": "off",
"@typescript-eslint/no-array-constructor": "error", "@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-base-to-string": "off",
"@typescript-eslint/no-empty-function": "error", "@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-extra-non-null-assertion": "error", "@typescript-eslint/no-extra-non-null-assertion": "error",
"@typescript-eslint/no-floating-promises": "off", //"error", // We should turn this on eventually but it will take a while to fix "@typescript-eslint/no-floating-promises": "off", //"error", // We should turn this on eventually but it will take a while to fix
@ -126,7 +93,7 @@ module.exports = {
"@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-asserted-optional-chain": "error", "@typescript-eslint/no-non-null-asserted-optional-chain": "error",
"@typescript-eslint/no-non-null-assertion": "warn", "@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-parameter-properties": "off", "@typescript-eslint/no-redundant-type-constituents": "off",
"@typescript-eslint/no-this-alias": "error", "@typescript-eslint/no-this-alias": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-unsafe-assignment": "off", //"error", "@typescript-eslint/no-unsafe-assignment": "off", //"error",
@ -146,17 +113,19 @@ module.exports = {
{ {
"args": "after-used", "args": "after-used",
"argsIgnorePattern": "^_", "argsIgnorePattern": "^_",
"caughtErrors": "none",
"varsIgnorePattern": "^_", "varsIgnorePattern": "^_",
}, },
], ],
"@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-var-requires": "error", "@typescript-eslint/only-throw-error": "off",
"@typescript-eslint/prefer-as-const": "error", "@typescript-eslint/prefer-as-const": "error",
"@typescript-eslint/prefer-for-of": "error", "@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-function-type": "error", "@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/prefer-namespace-keyword": "error", "@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/prefer-promise-reject-errors": "off",
"@typescript-eslint/prefer-regexp-exec": "error", "@typescript-eslint/prefer-regexp-exec": "error",
"@typescript-eslint/quotes": [ "quotes": [
"error", "error",
"double", "double",
{ {
@ -167,10 +136,6 @@ module.exports = {
"@typescript-eslint/require-await": "error", "@typescript-eslint/require-await": "error",
"@typescript-eslint/restrict-plus-operands": "off", //"error", // We use this a lot - fixing it is a problem for a rainy day "@typescript-eslint/restrict-plus-operands": "off", //"error", // We use this a lot - fixing it is a problem for a rainy day
"@typescript-eslint/restrict-template-expressions": "off", "@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/semi": [
"off",
null
],
"@typescript-eslint/triple-slash-reference": [ "@typescript-eslint/triple-slash-reference": [
"error", "error",
{ {
@ -179,7 +144,6 @@ module.exports = {
"lib": "always" "lib": "always"
} }
], ],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error", "@typescript-eslint/unbound-method": "error",
"@typescript-eslint/unified-signatures": "error", "@typescript-eslint/unified-signatures": "error",
"arrow-body-style": "error", "arrow-body-style": "error",
@ -207,7 +171,6 @@ module.exports = {
"import/order": "off", "import/order": "off",
"jsdoc/check-alignment": "off", "jsdoc/check-alignment": "off",
"jsdoc/check-indentation": "off", "jsdoc/check-indentation": "off",
"jsdoc/newline-after-description": "off",
"max-classes-per-file": "off", "max-classes-per-file": "off",
"max-len": "off", "max-len": "off",
"new-parens": "error", "new-parens": "error",
@ -296,10 +259,9 @@ module.exports = {
"files": ["src/content.ts", "src/commandline_frame.ts"], "files": ["src/content.ts", "src/commandline_frame.ts"],
"rules": { "rules": {
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/ban-types": [ "@typescript-eslint/no-restricted-types": [
"error", "error",
{ {
"extendDefaults": true,
"types": { "types": {
"TrustedKeyboardEvent": { "TrustedKeyboardEvent": {
"message": "Events must be validated with `isTrustedKeyboardEvent` at runtime" "message": "Events must be validated with `isTrustedKeyboardEvent` at runtime"

View file

@ -42,7 +42,7 @@ jobs:
cache: 'yarn' cache: 'yarn'
- name: Install deps - name: Install deps
run: yarn install run: yarn install --frozen-lockfile
- name: Setup Firefox - name: Setup Firefox
uses: browser-actions/setup-firefox@v1 uses: browser-actions/setup-firefox@v1

View file

@ -20,8 +20,12 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'yarn'
- name: Setup - name: Setup
run: yarn install run: yarn install --frozen-lockfile
- name: ${{ matrix.step }} - name: ${{ matrix.step }}
env: env:
STEP: ${{ matrix.step }} STEP: ${{ matrix.step }}

View file

@ -47,7 +47,7 @@ jobs:
- name: Build - name: Build
run: | run: |
cd tridactyl cd tridactyl
yarn install yarn install --frozen-lockfile
yarn run build yarn run build
find . -iname "*.html" -exec sed 's@href="/static@href="/build/static@' -i '{}' ';' # ideally this url would be less gnarly find . -iname "*.html" -exec sed 's@href="/static@href="/build/static@' -i '{}' ';' # ideally this url would be less gnarly
cd ../site cd ../site

View file

@ -26,7 +26,7 @@
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"tridactyl-arg": "git+https://github.com/GHolk/arg.git#v5.1.0", "tridactyl-arg": "git+https://github.com/GHolk/arg.git#v5.1.0",
"tsdef": "^0.0.14", "tsdef": "^0.0.14",
"typedoc": "0.22.18", "typedoc": "0.28.20",
"xss": "^1.0.15" "xss": "^1.0.15"
}, },
"devDependencies": { "devDependencies": {
@ -35,10 +35,10 @@
"@types/jest": "29.5.12", "@types/jest": "29.5.12",
"@types/nearley": "^2.11.5", "@types/nearley": "^2.11.5",
"@types/selenium-webdriver": "^4.1.10", "@types/selenium-webdriver": "^4.1.10",
"@typescript-eslint/eslint-plugin": "5.25.0", "@typescript-eslint/eslint-plugin": "8.64.0",
"@typescript-eslint/parser": "5.25.0", "@typescript-eslint/parser": "8.64.0",
"command-line-args": "^6.0.1", "command-line-args": "^6.0.1",
"eslint": "^7.32.0", "eslint": "8.57.1",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsdoc": "^50.6.1", "eslint-plugin-jsdoc": "^50.6.1",
@ -55,7 +55,7 @@
"prettier": "^3.4.2", "prettier": "^3.4.2",
"selenium-webdriver": "^4.7.1", "selenium-webdriver": "^4.7.1",
"ts-jest": "29.4.11", "ts-jest": "29.4.11",
"typescript": "4.7.4", "typescript": "6.0.3",
"web-ext": "^7.10.0", "web-ext": "^7.10.0",
"yaml-lint": "^1.7.0" "yaml-lint": "^1.7.0"
}, },

View file

@ -57,16 +57,19 @@ if [ "$QUICK_BUILD" != "1" ]; then
"$(yarn bin)/nearleyc" src/grammars/bracketexpr.ne > \ "$(yarn bin)/nearleyc" src/grammars/bracketexpr.ne > \
src/grammars/.bracketexpr.generated.ts src/grammars/.bracketexpr.generated.ts
printf '{"version":1,"commands":{},"settings":{}}' > src/.metadata.generated.json
printf 'export * from "./lib/metadata"\n' > src/.metadata.generated.ts
node -e "var fs=require('fs');fs.writeFileSync('src/.themes.generated.json',JSON.stringify(fs.readdirSync('src/static/themes').sort()))"
# Generate runtime metadata via typedoc. The generated TypeScript shim # Generate runtime metadata via typedoc. The generated TypeScript shim
# routes the public @src/.metadata.generated import to the JSON loader. # routes the public @src/.metadata.generated import to the JSON loader.
"$(yarn bin)/typedoc" --json src/.metadata.generated.json \ "$(yarn bin)/typedoc" --json src/.metadata.generated.json \
--excludeExternals --disableSources --plugin none --readme none \ --excludeExternals --disableSources --readme none \
--excludePrivate false --excludePrivateClassFields false \
--validation.notExported false \ --validation.notExported false \
--exclude 'src/.excmds_*.generated.ts' \ --exclude 'src/.excmds_*.generated.ts' \
src/excmds.ts src/lib/config.ts src/content/state_content.ts src/excmds.ts src/lib/config.ts src/content/state_content.ts
node scripts/minify_json.js src/.metadata.generated.json node scripts/convert_typedoc_metadata.js src/.metadata.generated.json
node -e "var fs=require('fs');fs.writeFileSync('src/.themes.generated.json',JSON.stringify(fs.readdirSync('src/static/themes').sort()))"
printf 'export * from "./lib/metadata"\n' > src/.metadata.generated.ts
scripts/newtab.md.sh scripts/newtab.md.sh
scripts/make_tutorial.sh scripts/make_tutorial.sh

View file

@ -0,0 +1,190 @@
#!/usr/bin/env node
const fs = require("fs")
const TYPEDOC_SCHEMA_VERSION = "2.0"
const METADATA_VERSION = 1
const KIND = { Module: 2, Function: 64, Class: 128 }
const compareNames = (a, b) => (a < b ? -1 : a > b ? 1 : 0)
const sortedRecord = entries =>
Object.fromEntries(entries.sort(([a], [b]) => compareNames(a, b)))
function convertMetadata(project) {
if (project.schemaVersion !== TYPEDOC_SCHEMA_VERSION)
throw new Error(
`Unsupported TypeDoc schemaVersion ${JSON.stringify(project.schemaVersion)}; expected ${TYPEDOC_SCHEMA_VERSION}`,
)
const reflections = {}
const collect = value => {
if (!value || typeof value !== "object") return
if (Array.isArray(value)) return value.forEach(collect)
if (value.id !== undefined) reflections[value.id] = value
Object.values(value).forEach(collect)
}
collect(project)
const accessor = node => node?.getSignature || node?.setSignature
const memberType = node => node?.type || accessor(node)?.type
const commentText = comment =>
(comment?.summary || [])
.map(part => part.text || "")
.join("")
.replace(/\n+$/, "")
const normalizeParameter = (parameter, resolving) => ({
name: parameter.name,
type: normalizeType(parameter.type, resolving),
...(parameter.flags?.isRest ? { flags: { isRest: true } } : {}),
})
const normalizeType = (type, resolving = new Set()) => {
if (!type) return { type: "intrinsic", name: "any" }
if (
type.type === "reference" &&
typeof type.target === "number" &&
!type.typeArguments?.length &&
reflections[type.target]?.type &&
!resolving.has(type.target)
) {
const next = new Set(resolving)
next.add(type.target)
return normalizeType(reflections[type.target].type, next)
}
switch (type.type) {
case "intrinsic":
return { type: "intrinsic", name: type.name }
case "array":
return {
type: "array",
elementType: normalizeType(type.elementType, resolving),
}
case "tuple":
return {
type: "tuple",
elements: (type.elements || []).map(element =>
normalizeType(element, resolving),
),
}
case "union":
return {
type: "union",
types: (type.types || []).map(member =>
normalizeType(member, resolving),
),
}
case "literal":
return { type: "literal", value: type.value }
case "reference":
return {
type: "reference",
name: type.name,
...(type.typeArguments?.length
? {
typeArguments: type.typeArguments.map(argument =>
normalizeType(argument, resolving),
),
}
: {}),
}
case "reflection": {
const source = type.declaration || {}
const declaration = {}
const signature = source.signatures?.[0]
if (signature)
declaration.signatures = [
{
parameters: (signature.parameters || []).map(
parameter =>
normalizeParameter(parameter, resolving),
),
type: normalizeType(signature.type, resolving),
},
]
const children = (source.children || [])
.map(child => {
const type = memberType(child)
return type
? {
name: child.name,
type: normalizeType(type, resolving),
}
: undefined
})
.filter(Boolean)
.sort((a, b) => compareNames(a.name, b.name))
if (children.length) declaration.children = children
const indexSignatures = (source.indexSignatures || []).map(
signature => ({
type: normalizeType(signature.type, resolving),
}),
)
if (indexSignatures.length)
declaration.indexSignatures = indexSignatures
return { type: "reflection", declaration }
}
default:
return { type: "intrinsic", name: "any" }
}
}
const modules = Object.fromEntries(
(project.children || [])
.filter(node => node.kind === KIND.Module)
.map(node => [
(node.name || "").replace(/^"|"$/g, "").replace(/\\/g, "/"),
node,
]),
)
const excmds = modules.excmds
const configClass = modules["lib/config"]?.children?.find(
node => node.kind === KIND.Class && node.name === "default_config",
)
if (!excmds || !configClass)
throw new Error("TypeDoc output is missing excmds or default_config")
const commands = sortedRecord(
(excmds.children || [])
.filter(node => node.kind === KIND.Function)
.map(node => {
const signature = node.signatures?.[0]
return [
node.name,
{
doc:
commentText(signature?.comment) ||
commentText(node.comment),
params: (signature?.parameters || []).map(parameter =>
normalizeParameter(parameter),
),
},
]
}),
)
const settings = sortedRecord(
(configClass.children || []).map(node => [
node.name,
{
doc:
commentText(accessor(node)?.comment) ||
commentText(node.comment),
type: normalizeType(memberType(node)),
},
]),
)
return { version: METADATA_VERSION, commands, settings }
}
module.exports = { convertMetadata, METADATA_VERSION, TYPEDOC_SCHEMA_VERSION }
if (require.main === module) {
const file = process.argv[2]
if (!file) throw new Error("Usage: convert_typedoc_metadata.js FILE")
const metadata = convertMetadata(JSON.parse(fs.readFileSync(file, "utf8")))
fs.writeFileSync(file, JSON.stringify(metadata))
}

View file

@ -201,5 +201,5 @@ def main():
print(output.rstrip(), file=sink) print(output.rstrip(), file=sink)
PRELUDE = "/** Generated from excmds.ts. Don't edit this file! */" PRELUDE = "/* Generated from excmds.ts. Don't edit this file! */"
main() main()

View file

@ -2,8 +2,18 @@
set -e set -e
dest=generated/static/docs dest=generated/static/docs
"$(yarn bin)/typedoc" --plugin ./scripts/typedoc-theme.js --theme tridactyl \ if revision=$(git rev-parse HEAD 2>/dev/null); then
set -- --basePath src --disableGit --gitRevision "$revision" \
--sourceLinkTemplate 'https://github.com/tridactyl/tridactyl/blob/{gitRevision}/src/{path}#L{line}'
else
set -- --disableSources
fi
"$(yarn bin)/typedoc" --plugin ./scripts/typedoc-theme.mjs \
--theme tridactyl --router tridactyl \
"$@" \
--entryPointStrategy expand --validation.notExported false \ --entryPointStrategy expand --validation.notExported false \
--excludePrivate false --excludePrivateClassFields false \
--includeHierarchySummary false \
--exclude "src/**/?(test_utils|*.test).ts" \ --exclude "src/**/?(test_utils|*.test).ts" \
--out "$dest" src --out "$dest" src
rm -rf build/static/docs rm -rf build/static/docs

View file

@ -1,120 +0,0 @@
const {
Converter,
DefaultTheme,
IntrinsicType,
JSX,
ReflectionKind,
ReflectionType,
TypeScript: ts,
} = require("typedoc")
const h = JSX.createElement
const css = href => h("link", { rel: "stylesheet", href })
function restoreObjectLiteral(context, reflection, node) {
const initializer = node && node.initializer
if (!initializer || !ts.isObjectLiteralExpression(initializer)) return
if (!initializer.properties.length) return
if (reflection.type instanceof ReflectionType)
context.project.removeReflection(reflection.type.declaration)
const inferred = context.converter.convertType(
context.withScope(reflection),
context.checker.getTypeAtLocation(initializer),
)
if (!(inferred instanceof ReflectionType)) return
const children = inferred.declaration.children || []
inferred.declaration.children = undefined
context.project.removeReflection(inferred.declaration)
reflection.kind = ReflectionKind.ObjectLiteral
reflection.type = new IntrinsicType("object")
reflection.children = children
for (const child of children) child.parent = reflection
}
class TridactylTheme extends DefaultTheme {
constructor(renderer) {
super(renderer)
this.defaultLayoutTemplate = page => {
const context = this.getRenderContext(page)
const navigations = context.navigation(page).children
const navigation = navigations[1] || navigations[0]
navigation.props.class = "tsd-navigation secondary scroller"
return h(
"html",
{ class: "minimal no-js TridactylOwnNamespace" },
h(
"head",
null,
h("meta", { charset: "utf-8" }),
h("meta", {
"http-equiv": "X-UA-Compatible",
content: "IE=edge",
}),
h(
"title",
null,
`${page.model.name} | ${page.project.name}`,
),
h("meta", {
name: "viewport",
content: "width=device-width, initial-scale=1",
}),
css(context.relativeURL("assets/style.css")),
css(context.relativeURL("assets/highlight.css")),
h("script", { src: "/content.js" }),
h("script", { src: "/help.js" }),
css("/static/css/content.css"),
css("/static/css/hint.css"),
css("/static/css/viewsource.css"),
css("/static/typedoc/assets/css/main.css"),
),
h(
"body",
null,
navigation,
h(
"div",
{ class: "container container-main scroller" },
h(
"div",
{ class: "content-wrap" },
page.template(page),
),
),
),
)
}
}
getUrls(project) {
for (const child of project.children || []) {
if (child.kind === ReflectionKind.Module)
child.name = `"src/${child.name}"`
}
const names = Object.values(project.reflections).map(reflection => [
reflection,
reflection.name,
])
for (const [reflection] of names)
reflection.name = reflection.name
.replace(/[^a-z0-9]/gi, "_")
.toLowerCase()
const urls = super.getUrls(project)
for (const [reflection, name] of names) reflection.name = name
const globals = urls.find(mapping => mapping.url === "modules.html")
if (globals) globals.url = project.url = "globals.html"
return urls
}
}
exports.load = app => {
app.converter.on(Converter.EVENT_CREATE_DECLARATION, restoreObjectLiteral, 100)
app.renderer.defineTheme("tridactyl", TridactylTheme)
}

343
scripts/typedoc-theme.mjs Normal file
View file

@ -0,0 +1,343 @@
import {
Converter,
DefaultTheme,
IntrinsicType,
JSX,
KindRouter,
PageKind,
Reflection,
ReflectionKind,
ReflectionType,
Slugger,
TypeScript as ts,
} from "typedoc"
const h = JSX.createElement
const css = href => h("link", { rel: "stylesheet", href })
const cleanName = name => name.replace(/[^a-z0-9]/gi, "_").toLowerCase()
const isGeneratedSource = source =>
/\/\.[^/]+\.generated\.ts$/.test(source.fullFileName)
function rewriteWikiLinks(parts, owner, reflections) {
return parts.flatMap(part => {
if (part.kind !== "text" || !part.text.includes("[[")) return part
const out = []
let end = 0
for (const match of part.text.matchAll(
/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g,
)) {
if (match.index > end)
out.push({
kind: "text",
text: part.text.slice(end, match.index),
})
const candidates = reflections.get(match[1]) || []
const ownerModule = getModule(owner)
const target =
(!owner.kindOf(
ReflectionKind.Module | ReflectionKind.Namespace,
) &&
candidates.find(
candidate => candidate.parent === owner.parent,
)) ||
candidates.find(
candidate => getModule(candidate) === ownerModule,
) ||
candidates.find(
candidate =>
!candidate.kindOf(
ReflectionKind.Module | ReflectionKind.Namespace,
) &&
!candidate.sources?.some(isGeneratedSource),
) ||
candidates.find(
candidate =>
!candidate.kindOf(
ReflectionKind.Module | ReflectionKind.Namespace,
),
) ||
candidates[0]
out.push(
target
? {
kind: "inline-tag",
tag: "@link",
text: match[2] || match[1],
target,
}
: { kind: "text", text: match[2] || match[1] },
)
end = match.index + match[0].length
}
if (end < part.text.length)
out.push({ kind: "text", text: part.text.slice(end) })
return out
})
}
function getModule(reflection) {
while (reflection.parent && !reflection.parent.isProject())
reflection = reflection.parent
return reflection
}
function escapeDependencyTags(parts) {
return parts.map(part =>
part.kind === "text"
? {
...part,
text: part.text.replace(
/<(\/?)(iframe|object|frame)>/gi,
"&lt;$1$2&gt;",
),
}
: part,
)
}
function restoreWikiLinks(context) {
const reflections = new Map()
for (const reflection of Object.values(context.project.reflections)) {
if (!reflection.isDeclaration()) continue
const matches = reflections.get(reflection.name) || []
matches.push(reflection)
reflections.set(reflection.name, matches)
}
for (const reflection of Object.values(context.project.reflections)) {
const fromDependency = reflection.sources?.some(source =>
source.fullFileName.includes("/node_modules/"),
)
for (const source of reflection.sources || [])
if (
source.fullFileName.includes("/node_modules/") ||
isGeneratedSource(source)
)
source.url = undefined
const comment = reflection.comment
if (!comment) continue
comment.summary = rewriteWikiLinks(
comment.summary,
reflection,
reflections,
)
for (const tag of comment.blockTags)
tag.content = rewriteWikiLinks(tag.content, reflection, reflections)
if (fromDependency) {
comment.summary = escapeDependencyTags(comment.summary)
for (const tag of comment.blockTags)
tag.content = escapeDependencyTags(tag.content)
}
}
if (context.project.readme)
context.project.readme = rewriteWikiLinks(
context.project.readme,
context.project,
reflections,
)
}
function restoreObjectLiteral(context, reflection) {
const node = context.getSymbolFromReflection(reflection)?.valueDeclaration
const initializer = node && "initializer" in node && node.initializer
if (!initializer || !ts.isObjectLiteralExpression(initializer)) return
if (!initializer.properties.length) return
if (reflection.type instanceof ReflectionType)
context.project.removeTypeReflections(reflection.type)
const inferred = context.converter.convertType(
context.withScope(reflection),
context.checker.getTypeAtLocation(initializer),
)
if (!(inferred instanceof ReflectionType)) return
reflection.type = new IntrinsicType("object")
context.project.mergeReflections(inferred.declaration, reflection)
}
class LegacySlugger extends Slugger {
serialize(value) {
return value.startsWith("\0") ? value.slice(1) : super.serialize(value)
}
}
class TridactylRouter extends KindRouter {
aliases = new Map()
aliasCounts = new Map()
getSlugger(target) {
while (!this.sluggers.has(target)) target = target.parent
let slugger = this.sluggers.get(target)
if (!(slugger instanceof LegacySlugger)) {
slugger = new LegacySlugger(this.sluggerConfiguration)
this.sluggers.set(target, slugger)
}
return slugger
}
getPageKind(target) {
if (!(target instanceof Reflection)) return
if (
target.kindOf(
ReflectionKind.Class |
ReflectionKind.Interface |
ReflectionKind.Enum |
ReflectionKind.Module |
ReflectionKind.Namespace,
)
)
return PageKind.Reflection
if (target.kindOf(ReflectionKind.Document)) return PageKind.Document
}
getIdealBaseName(reflection) {
const directory = this.directories.get(reflection.kind)
const parts = []
do {
let name = reflection.name
if (
reflection.parent?.isProject() &&
reflection.kind === ReflectionKind.Module
)
name = `"src/${name}"`
parts.unshift(cleanName(name))
reflection = reflection.parent
} while (reflection && !reflection.isProject())
return `${directory}/${parts.join(".")}`
}
getLegacyAlias(reflection, pageTarget) {
if (this.aliases.has(reflection)) return this.aliases.get(reflection)
const base = cleanName(reflection.name) || `reflection-${reflection.id}`
let counts = this.aliasCounts.get(pageTarget)
if (!counts) this.aliasCounts.set(pageTarget, (counts = new Map()))
const count = counts.get(base) || 0
const alias = count ? `${base}-${count}` : base
counts.set(base, count + 1)
this.aliases.set(reflection, alias)
return alias
}
createAnchor(target, pageTarget) {
if (!target.isDeclaration())
return super.createAnchor(target, pageTarget)
const parts = []
for (
let current = target;
current !== pageTarget;
current = current.parent
)
parts.unshift(this.getLegacyAlias(current, pageTarget))
return this.getSlugger(pageTarget).slug("\0" + parts.join("."))
}
buildPages(project) {
this.aliases.clear()
this.aliasCounts.clear()
const pages = super.buildPages(project)
const globals = pages.find(page => page.url === "modules.html")
if (globals) {
globals.url = "globals.html"
this.fullUrls.set(project, "globals.html")
}
return pages
}
}
class TridactylTheme extends DefaultTheme {
getReflectionClasses(reflection) {
const kind = ReflectionKind.classString(reflection.kind)
const parent =
reflection.parent &&
ReflectionKind.classString(reflection.parent.kind).replace(
"tsd-kind-",
"tsd-parent-kind-",
)
return [super.getReflectionClasses(reflection), kind, parent]
.filter(Boolean)
.join(" ")
}
constructor(renderer) {
super(renderer)
const reflectionTemplate = this.reflectionTemplate
this.reflectionTemplate = page => {
const content = reflectionTemplate(page)
if (
!page.model.kindOf(
ReflectionKind.Module | ReflectionKind.Namespace,
)
)
return content
const context = this.getRenderContext(page)
page.pageSections.length = 0
const renderMember = context.member
context.member = reflection =>
reflection.isReference() ? null : renderMember(reflection)
return h(JSX.Fragment, null, content, context.members(page.model))
}
this.defaultLayoutTemplate = (page, template) => {
const context = this.getRenderContext(page)
const content = template(page)
return h(
"html",
{ class: "minimal no-js TridactylOwnNamespace" },
h(
"head",
null,
h("meta", { charset: "utf-8" }),
h("meta", {
"http-equiv": "X-UA-Compatible",
content: "IE=edge",
}),
h(
"title",
null,
`${page.model.name} | ${page.project.name}`,
),
h("meta", {
name: "viewport",
content: "width=device-width, initial-scale=1",
}),
css(context.relativeURL("assets/style.css")),
css(context.relativeURL("assets/highlight.css")),
h("script", { src: "/content.js" }),
h("script", { src: "/help.js" }),
css("/static/css/content.css"),
css("/static/css/hint.css"),
css("/static/css/viewsource.css"),
css("/static/typedoc/assets/css/main.css"),
),
h(
"body",
null,
h(
"nav",
{ class: "tsd-navigation secondary scroller" },
context.pageNavigation(page),
),
h(
"div",
{ class: "container container-main scroller" },
h("div", { class: "content-wrap" }, content),
),
),
)
}
}
}
export function load(app) {
app.converter.on(Converter.EVENT_RESOLVE_END, restoreWikiLinks, 100)
app.converter.on(
Converter.EVENT_CREATE_DECLARATION,
restoreObjectLiteral,
100,
)
app.renderer.defineRouter("tridactyl", TridactylRouter)
app.renderer.defineTheme("tridactyl", TridactylTheme)
}

View file

@ -79,10 +79,10 @@ export async function downloadUrl(url: string, saveAs: boolean) {
* *
* Note: this requires a native messenger >=0.1.9. Make sure to nativegate for this. * Note: this requires a native messenger >=0.1.9. Make sure to nativegate for this.
* *
* @param URL the URL to download * @param url the URL to download
* @param saveAs If beginning with a slash, this is the absolute path the document should be moved to. If the first character of the string is a tilda, it will be expanded to an absolute path to the user's home directory. If saveAs begins with any other character, it will be considered a path relative to where the native messenger binary is located (e.g. "$HOME/.local/share/tridactyl" on linux). * @param saveAs If beginning with a slash, this is the absolute path the document should be moved to. If the first character of the string is a tilda, it will be expanded to an absolute path to the user's home directory. If saveAs begins with any other character, it will be considered a path relative to where the native messenger binary is located (e.g. "$HOME/.local/share/tridactyl" on linux).
* @param If true, overwrite the destination file, returns error code 1 otherwise if file exists * @param overwrite If true, overwrite the destination file, returns error code 1 otherwise if file exists
* @param If true, cleans up temporary downloaded source file e.g. in $HOME/Downlods/downloaded.doc when the move operation fails e.g. due to target destination exists, OS error etc. * @param cleanup If true, cleans up temporary downloaded source file e.g. in $HOME/Downlods/downloaded.doc when the move operation fails e.g. due to target destination exists, OS error etc.
*/ */
export async function downloadUrlAs( export async function downloadUrlAs(
url: string, url: string,

View file

@ -12,7 +12,7 @@ export const requestEventExpraInfoSpecMap = {
export const requestEvents = Object.keys(requestEventExpraInfoSpecMap) export const requestEvents = Object.keys(requestEventExpraInfoSpecMap)
// I'm being lazy - strictly the functions map strings to void | blocking responses // I'm being lazy - strictly the functions map strings to void | blocking responses
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
export const LISTENERS: Record<string, Record<string, Function>> = {} export const LISTENERS: Record<string, Record<string, Function>> = {}
export const registerWebRequestAutocmd = async ( export const registerWebRequestAutocmd = async (
@ -21,7 +21,7 @@ export const registerWebRequestAutocmd = async (
func: string, func: string,
) => { ) => {
// I'm being lazy - strictly the functions map strings to void | blocking responses // I'm being lazy - strictly the functions map strings to void | blocking responses
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
const listener = eval(func) as Function const listener = eval(func) as Function
if (!LISTENERS[requestEvent]) LISTENERS[requestEvent] = {} if (!LISTENERS[requestEvent]) LISTENERS[requestEvent] = {}

View file

@ -7,7 +7,7 @@ export class ExcmdCompletionOption extends Completions.CompletionOptionHTML impl
public fuseKeys = [] public fuseKeys = []
constructor( constructor(
public value: string, public value: string,
public documentation: string = "", public documentation = "",
) { ) {
super() super()
this.fuseKeys.push(this.value) this.fuseKeys.push(this.value)

View file

@ -6,7 +6,7 @@ export class ThemeCompletionOption
extends Completions.CompletionOptionHTML extends Completions.CompletionOptionHTML
implements Completions.CompletionOptionFuse { implements Completions.CompletionOptionFuse {
public fuseKeys = [] public fuseKeys = []
constructor(public value: string, public documentation: string = "") { constructor(public value: string, public documentation = "") {
super() super()
this.fuseKeys.push(this.value) this.fuseKeys.push(this.value)

View file

@ -9,7 +9,7 @@ export const CmdlineCmds = new Proxy(functions as any, {
get(target, property) { get(target, property) {
if (target[property]) { if (target[property]) {
return (...args) => return (...args) =>
messageOwnTab("commandline_cmd", property as string, args) messageOwnTab("commandline_cmd", property, args)
} }
return target[property] return target[property]
}, },

View file

@ -185,7 +185,7 @@ export async function jumpToMatch(searchQuery, option) {
const sensitive = const sensitive =
findcase === "sensitive" || findcase === "sensitive" ||
(findcase === "smart" && /[A-Z]/.test(searchQuery)) (findcase === "smart" && /[A-Z]/.test(searchQuery))
const findPromise = await browserBg.find.find(searchQuery, { const results = await browserBg.find.find(searchQuery, {
tabId: await activeTabId(), tabId: await activeTabId(),
caseSensitive: sensitive, caseSensitive: sensitive,
entireWord: false, entireWord: false,
@ -208,8 +208,6 @@ export async function jumpToMatch(searchQuery, option) {
nodes.push(node) nodes.push(node)
} while (node) } while (node)
const results = await findPromise
const host = getFindHost() const host = getFindHost()
for (let i = 0; i < results.count; ++i) { for (let i = 0; i < results.count; ++i) {
const range = results.rangeData[i] const range = results.rangeData[i]

View file

@ -224,7 +224,7 @@ export async function getRssLinks(): Promise<Array<{ type: string; url: string;
} }
if (seen.has(e.href)) return acc if (seen.has(e.href)) return acc
seen.add(e.href) seen.add(e.href)
return acc.concat({ type, url: e.href, title: e.title || e.innerText } as { type: string; url: string; title: string }) return acc.concat({ type, url: e.href, title: e.title || e.innerText })
}, []) }, [])
} }
@ -340,7 +340,7 @@ import { getEditor } from "editor-adapter"
* ``` * ```
* *
* Your editor of choice may need to run in a terminal. For example, this command opens neovim with kitty and exits after closing the editor: * Your editor of choice may need to run in a terminal. For example, this command opens neovim with kitty and exits after closing the editor:
* ```vim * ```text
* set editorcmd kitty nvim * set editorcmd kitty nvim
* ``` * ```
* *
@ -1071,7 +1071,7 @@ export async function curJumps() {
return jumps return jumps
} }
/** Calls [[jumpprev]](-n) */ /** Calls [[jumpprev]] with `-n`. */
//#content //#content
export function jumpnext(n = 1) { export function jumpnext(n = 1) {
return jumpprev(-n) return jumpprev(-n)
@ -1465,6 +1465,7 @@ export function scrollpage(n = 1, count = 1) {
/** /**
* Rudimentary find mode, left unbound by default as we don't currently support `incsearch`. Suggested binds: * Rudimentary find mode, left unbound by default as we don't currently support `incsearch`. Suggested binds:
* *
* ```text
* bind / fillcmdline find * bind / fillcmdline find
* bind ? fillcmdline find --reverse * bind ? fillcmdline find --reverse
* bind n findnext --search-from-view * bind n findnext --search-from-view
@ -1472,6 +1473,7 @@ export function scrollpage(n = 1, count = 1) {
* bind gn findselect * bind gn findselect
* bind gN composite findnext --search-from-view --reverse; findselect * bind gN composite findnext --search-from-view --reverse; findselect
* bind ,<Space> nohlsearch * bind ,<Space> nohlsearch
* ```
* *
* Argument: A string you want to search for. * Argument: A string you want to search for.
* *
@ -1512,7 +1514,7 @@ export function find(...args: string[]) {
* - `-f` or `--search-from-view` to search from the current view instead of the previous match * - `-f` or `--search-from-view` to search from the current view instead of the previous match
* - `-?` or `--reverse` to reverse the sign of the number * - `-?` or `--reverse` to reverse the sign of the number
* *
* @param number - number of words to advance down the page (use 1 for next word, -1 for previous), default to 1 * @param args - flags and number of words to advance down the page (use 1 for next word, -1 for previous), default to 1
* *
*/ */
//#content //#content
@ -2219,7 +2221,7 @@ export function urlparent(count = 1) {
* * -Q delete the given query * * -Q delete the given query
* * -g graft a new path onto URL or parent path of it * * -g graft a new path onto URL or parent path of it
* * -*u Use last argument as URL input instead of current URL * * -*u Use last argument as URL input instead of current URL
* @param replacement the replacement arguments (depends on mode): * @param args the replacement arguments (depends on mode):
* * -t <old> <new> * * -t <old> <new>
* * -r <regexp> <new> [flags] * * -r <regexp> <new> [flags]
* * -s <query> <value> * * -s <query> <value>
@ -3187,18 +3189,18 @@ export async function undo(item = "recent"): Promise<number> {
return -1 return -1
} }
/** Move the current tab to be just in front of the index specified. /**
* Move the current tab to be just in front of the index specified.
Known bug: This supports relative movement with `tabmove +pos` and `tabmove -pos`, but autocomplete doesn't know that yet and will override positive and negative indexes. *
* Known bug: This supports relative movement with `tabmove +pos` and `tabmove -pos`, but autocomplete doesn't know that yet and will override positive and negative indexes.
Put a space in front of tabmove if you want to disable completion and have the relative indexes at the command line. *
* Put a space in front of tabmove if you want to disable completion and have the relative indexes at the command line.
Binds are unaffected. *
* Binds are unaffected.
@param index *
New index for the current tab. * @param index New index for the current tab.
*
1,start,^ are aliases for the first index. 0,end,$ are aliases for the last index. * 1,start,^ are aliases for the first index. 0,end,$ are aliases for the last index.
*/ */
//#background //#background
export async function tabmove(index = "$") { export async function tabmove(index = "$") {
@ -3297,7 +3299,7 @@ export async function pin(index: string) {
Passing "all" to the excmd will operate on the mute state of all tabs. Passing "all" to the excmd will operate on the mute state of all tabs.
Passing "unmute" to the excmd will unmute. Passing "unmute" to the excmd will unmute.
Passing "toggle" to the excmd will toggle the state of `browser.tabs.tab.MutedInfo` Passing "toggle" to the excmd will toggle the state of `browser.tabs.tab.MutedInfo`
@param string[] muteArgs @param muteArgs
*/ */
//#background //#background
export async function mute(...muteArgs: string[]): Promise<void> { export async function mute(...muteArgs: string[]): Promise<void> {
@ -3419,7 +3421,7 @@ export async function winopen(...args: string[]) {
/** /**
* Close a window. * Close a window.
* *
* @param id - The window id. Defaults to the id of the current window. * @param ids - The window ids. Defaults to the id of the current window.
* *
* Example: `winclose` * Example: `winclose`
*/ */
@ -4245,7 +4247,7 @@ export async function clipboard(excmd: "open" | "yank" | "yankshort" | "yankcano
/** Copy an image to the clipboard. /** Copy an image to the clipboard.
@param url @param url
Absolute URL to the image to be copied. You can obtain an absolute URL from a relative one using [tri.urlutils.getAbsoluteURL](_src_lib_url_util_.html#getabsoluteurl). Absolute URL to the image to be copied. You can obtain an absolute URL from a relative one using <a href="/static/docs/modules/_src_lib_url_util_.html#getabsoluteurl">tri.urlutils.getAbsoluteURL</a>.
*/ */
//#background //#background
export async function yankimage(url: string): Promise<void> { export async function yankimage(url: string): Promise<void> {
@ -4265,16 +4267,16 @@ export async function yankimage(url: string): Promise<void> {
} }
} }
/** Change active tab. /**
* Change active tab.
@param id *
A bare number means the current window is used. Starts at 1. 0 refers to last tab of the current window, -1 to penultimate tab, etc. * @param id A bare number means the current window is used. Starts at 1. 0 refers to last tab of the current window, -1 to penultimate tab, etc.
*
A string following the following format: "[0-9]+.[0-9]+" means the first number being the index of the window that should be selected and the second one being the index of the tab within that window. [[taball]] has completions for this format. * A string following the following format: "[0-9]+.[0-9]+" means the first number being the index of the window that should be selected and the second one being the index of the tab within that window. [[taball]] has completions for this format.
*
"%" denotes the current tab and "#" denotes the tab that was last accessed in this window. "P", "A", "M" and "D" indicate tab status (i.e. a pinned, audible, muted or discarded tab). Use `:set completions.Tab.statusstylepretty true` to display unicode characters instead. "P","A","M","D" can be used to filter by tab status in either setting. * "%" denotes the current tab and "#" denotes the tab that was last accessed in this window. "P", "A", "M" and "D" indicate tab status (i.e. a pinned, audible, muted or discarded tab). Use `:set completions.Tab.statusstylepretty true` to display unicode characters instead. "P","A","M","D" can be used to filter by tab status in either setting.
*
A non integer string means to search the URL and title for matches, in this window if called from tab, all windows if called from taball. Title matches can contain '*' as a wildcard. * A non integer string means to search the URL and title for matches, in this window if called from tab, all windows if called from taball. Title matches can contain '*' as a wildcard.
*/ */
//#background //#background
export async function tab(...id: string[]) { export async function tab(...id: string[]) {
@ -4753,62 +4755,65 @@ const AUCMDS = ["DocStart", "DocLoad", "DocEnd", "TriStart", "TabEnter", "TabLef
export function getAutocmdEvents() { export function getAutocmdEvents() {
return AUCMDS return AUCMDS
} }
/** Set autocmds to run when certain events happen. /**
* Set autocmds to run when certain events happen.
* *
* @param event Currently, 'TriStart', 'DocStart', 'DocLoad', 'DocEnd', 'TabEnter', 'TabLeft', 'FullscreenChange', 'FullscreenEnter', 'FullscreenLeft', 'HistoryState', 'HistoryPushState', 'HistoryReplace', 'UriChange', 'AuthRequired', 'BeforeRedirect', 'BeforeRequest', 'BeforeSendHeaders', 'Completed', 'ErrorOccured', 'HeadersReceived', 'ResponseStarted', and 'SendHeaders' are supported * @param event Currently, 'TriStart', 'DocStart', 'DocLoad', 'DocEnd', 'TabEnter', 'TabLeft', 'FullscreenChange', 'FullscreenEnter', 'FullscreenLeft', 'HistoryState', 'HistoryPushState', 'HistoryReplace', 'UriChange', 'AuthRequired', 'BeforeRedirect', 'BeforeRequest', 'BeforeSendHeaders', 'Completed', 'ErrorOccured', 'HeadersReceived', 'ResponseStarted', and 'SendHeaders' are supported
* *
- DocStart: When a webpage loading. Exactly, when tridactyl is loading in a page. * - DocStart: When a webpage loading. Exactly, when tridactyl is loading in a page.
- DocLoad: When the whole html parsed, not including image/css loaded. (Just like jquery $(fn) or the [DOMContentLoaded event](https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event).) * - DocLoad: When the whole html parsed, not including image/css loaded. (Just like jquery $(fn) or the [DOMContentLoaded event](https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event).)
- DocEnd: When a webpage unloaded/closed or backward/forward in history. Exactly, the [pagehide event](https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event). * - DocEnd: When a webpage unloaded/closed or backward/forward in history. Exactly, the [pagehide event](https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event).
- TabEnter: When a tab get focus. * - TabEnter: When a tab get focus.
- TabLeft: When a tab lost focus or closed. * - TabLeft: When a tab lost focus or closed.
- A supported webRequest event (AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders): the corresponding [WebExtension webRequest event](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest#Events) * - A supported webRequest event (AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders): the corresponding [WebExtension webRequest event](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest#Events)
- The 'HistoryState' event is triggered when a page uses the web history API to change the page location / URI. It should be used in preference to 'UriChange' below since it will use almost no resources. The 'UriChange' event may work on websites where 'HistoryState' does not. * - The 'HistoryState' event is triggered when a page uses the web history API to change the page location / URI. It should be used in preference to 'UriChange' below since it will use almost no resources. The 'UriChange' event may work on websites where 'HistoryState' does not.
- The 'HistoryPushState' is triggered only when a page calls 'history.pushState' to change URI, and 'HistoryReplace' is for 'history.replace'. By the way, the HistoryPopState is not implemented. * - The 'HistoryPushState' is triggered only when a page calls 'history.pushState' to change URI, and 'HistoryReplace' is for 'history.replace'. By the way, the HistoryPopState is not implemented.
- The 'UriChange' event is for "single page applications" which change their URIs without triggering DocStart or DocLoad events. It uses a timer to check whether the URI has changed, which has a small impact on battery life on pages matching the `url` parameter. We suggest using it sparingly. * - The 'UriChange' event is for "single page applications" which change their URIs without triggering DocStart or DocLoad events. It uses a timer to check whether the URI has changed, which has a small impact on battery life on pages matching the `url` parameter. We suggest using it sparingly.
* *
* @param url type depends on the event * @param url type depends on the event
* *
- For most events (DocStart, DocEnd, TabEnter, TabLeft, ...): a JavaScript regex (e.g. `www\.amazon\.co.*`) * - For most events (DocStart, DocEnd, TabEnter, TabLeft, ...): a JavaScript regex (e.g. `www\.amazon\.co.*`)
- We just use [URL.search](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search) * - We just use [URL.search](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search)
- For TriStart: regular expression that matches the hostname of the computer the autocmd should be run on. This requires the native messenger to be installed, except for the ".*" regular expression which will always be triggered, even without the native messenger. * - For TriStart: regular expression that matches the hostname of the computer the autocmd should be run on. This requires the native messenger to be installed, except for the ".*" regular expression which will always be triggered, even without the native messenger.
- For webRequest events (AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders): a [URL match pattern](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns) * - For webRequest events (AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders): a [URL match pattern](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns)
* *
* @param command type depends on the event * @param excmd type depends on the event
- For most events (DocStart, DocEnd, TabEnter, TabLeft, ...): the excmd to run (use [[composite]] to run multiple commands). * - For most events (DocStart, DocEnd, TabEnter, TabLeft, ...): the excmd to run (use [[composite]] to run multiple commands).
- Example for zooming in more on a website: * - Example for zooming in more on a website:
``` *
autocmd DocStart .*example\.com.* zoom 150 false TRI_FIRED_MOZ_TABID * ```text
``` * autocmd DocStart .*example\.com.* zoom 150 false TRI_FIRED_MOZ_TABID
* ```
- For webRequest events (AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders): the text of a javascript function that should accept a [details objects specific to the event](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest#Events) and return a [blocking response](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/BlockingResponse). This JavaScript function will run in the background context. * - For webRequest events (AuthRequired, BeforeRedirect, BeforeRequest, BeforeSendHeaders, Completed, ErrorOccured, HeadersReceived, ResponseStarted and SendHeaders): the text of a javascript function that should accept a [details objects specific to the event](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest#Events) and return a [blocking response](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/BlockingResponse). This JavaScript function will run in the background context.
- Example for redirecting from new to old reddit: * - Example for redirecting from new to old reddit:
``` *
autocmd BeforeRequest https://www.reddit.com/r/* (details) => ({redirectUrl: details.url.replace(/^https:\/\/www\./, "https://old.")}) * ```text
``` * autocmd BeforeRequest https://www.reddit.com/r/* (details) => ({redirectUrl: details.url.replace(/^https:\/\/www\./, "https://old.")})
* ```
* For non-webRequest events, magic variables are available which are replaced with the relevant string at runtime: * For non-webRequest events, magic variables are available which are replaced with the relevant string at runtime:
- `TRI_FIRED_MOZ_TABID`: Provides Mozilla's `tabID` associated with the fired event. * - `TRI_FIRED_MOZ_TABID`: Provides Mozilla's `tabID` associated with the fired event.
- `TRI_FIRED_TRI_TABINDEX`: Provides tridactyls internal tab index associated with the fired event. * - `TRI_FIRED_TRI_TABINDEX`: Provides tridactyls internal tab index associated with the fired event.
- `TRI_FIRED_MOZ_WINID`: Provides Mozilla's `windowId` associated with the fired event. * - `TRI_FIRED_MOZ_WINID`: Provides Mozilla's `windowId` associated with the fired event.
- `TRI_FIRED_MOZ_OPENERTABID`: The ID of the tab that opened this tab. * - `TRI_FIRED_MOZ_OPENERTABID`: The ID of the tab that opened this tab.
- `TRI_FIRED_ACTIVE`: Whether the tab is active in its window. This may be true even if the tab's window is not currently focused. * - `TRI_FIRED_ACTIVE`: Whether the tab is active in its window. This may be true even if the tab's window is not currently focused.
- `TRI_FIRED_AUDIBLE`: Indicates whether the tab is producing sound (even if muted). * - `TRI_FIRED_AUDIBLE`: Indicates whether the tab is producing sound (even if muted).
- `TRI_FIRED_MUTED`: Indicates whether the tab is muted. * - `TRI_FIRED_MUTED`: Indicates whether the tab is muted.
- `TRI_FIRED_DISCARDED`: Whether the tab is discarded. A discarded tab is one whose content has been unloaded from memory. * - `TRI_FIRED_DISCARDED`: Whether the tab is discarded. A discarded tab is one whose content has been unloaded from memory.
- `TRI_FIRED_HEIGHT`: The height of the tab in pixels. * - `TRI_FIRED_HEIGHT`: The height of the tab in pixels.
- `TRI_FIRED_WIDTH`: The width of the tab in pixels. * - `TRI_FIRED_WIDTH`: The width of the tab in pixels.
- `TRI_FIRED_HIDDEN`: Whether the tab is hidden. * - `TRI_FIRED_HIDDEN`: Whether the tab is hidden.
- `TRI_FIRED_INCOGNITO`: Whether the tab is in a private browsing window. * - `TRI_FIRED_INCOGNITO`: Whether the tab is in a private browsing window.
- `TRI_FIRED_ISARTICLE`: True if the tab can be rendered in Reader Mode, false otherwise. * - `TRI_FIRED_ISARTICLE`: True if the tab can be rendered in Reader Mode, false otherwise.
- `TRI_FIRED_LASTACCESSED`: Time at which the tab was last accessed, in milliseconds since the epoch. * - `TRI_FIRED_LASTACCESSED`: Time at which the tab was last accessed, in milliseconds since the epoch.
- `TRI_FIRED_PINNED`: Whether the tab is pinned. * - `TRI_FIRED_PINNED`: Whether the tab is pinned.
- `TRI_FIRED_TITLE`: The title of the tab. * - `TRI_FIRED_TITLE`: The title of the tab.
- `TRI_FIRED_URL`: The URL of the document that the tab is displaying. * - `TRI_FIRED_URL`: The URL of the document that the tab is displaying.
* For debugging, use `:set logging.autocmds debug` and check the Firefox web console. `WebRequest` events have no logging. * For debugging, use `:set logging.autocmds debug` and check the Firefox web console. `WebRequest` events have no logging.
* *
@ -5297,98 +5302,103 @@ export function setnull(...keys: string[]) {
const KILL_STACK: Element[] = [] const KILL_STACK: Element[] = []
// {{{ HINTMODE // {{{ HINTMODE
/** Hint a page. /**
* Hint a page.
@param args Arguments to the `:hint` command. Multiple flags can be combined as long as they don't conflict. *
Selectors can be specified either standalone (without a flag preceding them) or with the `-c` option. Arguments that * @param args Arguments to the `:hint` command. Multiple flags can be combined as long as they don't conflict.
take callbacks (`-F` or `-W`) should be specified last, as they consume the rest of the command line. * Selectors can be specified either standalone (without a flag preceding them) or with the `-c` option. Arguments that
* take callbacks (`-F` or `-W`) should be specified last, as they consume the rest of the command line.
Hinting action flags (only one can be specified): *
- -t open in a new foreground tab * Hinting action flags (only one can be specified):
- -b open in background *
- -y copy (yank) link's target to clipboard * - -t open in a new foreground tab
- -p copy an element's text to the clipboard * - -b open in background
- -h select an element (as if you click-n-dragged over it) * - -y copy (yank) link's target to clipboard
- -P copy an element's title/alt text to the clipboard * - -p copy an element's text to the clipboard
- -r read an element's text with text-to-speech * - -h select an element (as if you click-n-dragged over it)
- -i view an image * - -P copy an element's title/alt text to the clipboard
- -I view an image in a new tab * - -r read an element's text with text-to-speech
- -k irreversibly deletes an element from the page (until reload) * - -i view an image
- -K hides an element on the page; hidden elements can be restored using [[elementunhide]]. * - -I view an image in a new tab
- -s save (download) the linked resource * - -k irreversibly deletes an element from the page (until reload)
- -S save the linked image * - -K hides an element on the page; hidden elements can be restored using [[elementunhide]].
- -a save-as the linked resource * - -s save (download) the linked resource
- -A save-as the linked image * - -S save the linked image
- -; focus an element and set it as the element or the child of the element to scroll * - -a save-as the linked resource
- -# yank an element's anchor URL to clipboard * - -A save-as the linked image
- -w open in new window * - -; focus an element and set it as the element or the child of the element to scroll
- -wp open in new private window * - -# yank an element's anchor URL to clipboard
- -z scroll an element to the top of the viewport * - -w open in new window
- `-pipe selector key` e.g, `-pipe a href` returns the URL of the chosen link on a page. Only makes sense with `composite`, e.g, `composite hint -pipe .some-class>a textContent | yank`. If you don't select a hint (i.e. press <Esc>), will return an empty string. Most useful when used like `-c` to do things other than opening links. NB: the query selector cannot contain any spaces. * - -wp open in new private window
- `-W excmd...` append hint href to excmd and execute, e.g, `hint -W mpvsafe` to open YouTube videos. NB: appending to bare [[exclaim]] is dangerous - see `get exaliases.mpvsafe` for an example of how to to it safely. If you need to use a query selector, use `-pipe` instead. * - -z scroll an element to the top of the viewport
- -F [callback] - run a custom callback on the selected hint, e.g. `hint -JF e => {tri.excmds.tabopen("-b",e.href); e.remove()}`. * - `-pipe selector key` e.g, `-pipe a href` returns the URL of the chosen link on a page. Only makes sense with `composite`, e.g, `composite hint -pipe .some-class>a textContent | yank`. If you don't select a hint (i.e. press `<Esc>`), will return an empty string. Most useful when used like `-c` to do things other than opening links. NB: the query selector cannot contain any spaces.
* - `-W excmd...` append hint href to excmd and execute, e.g, `hint -W mpvsafe` to open YouTube videos. NB: appending to bare [[exclaim]] is dangerous - see `get exaliases.mpvsafe` for an example of how to to it safely. If you need to use a query selector, use `-pipe` instead.
Element selection flags: * - -F [callback] - run a custom callback on the selected hint, e.g. `hint -JF e => {tri.excmds.tabopen("-b",e.href); e.remove()}`.
- -c [selector] hint links that match the css selector *
- `bind ;c hint -c [class*="expand"],[class*="togg"]` works particularly well on reddit and HN * Element selection flags:
- this works with most other hint modes, with the caveat that if other hint mode takes arguments your selector must contain no spaces, i.e. `hint -c[yourOtherFlag] [selector] [your other flag's arguments, which may contain spaces]` *
- -C [selector] like `-c [selector]` but also hints all elements that would normally be hinted given the other options selected * - -c [selector] hint links that match the css selector
- -x [selector] exclude the matched elements from hinting * - `bind ;c hint -c [class*="expand"],[class*="togg"]` works particularly well on reddit and HN
- -f [text] hint links and inputs that display the given text * - this works with most other hint modes, with the caveat that if other hint mode takes arguments your selector must contain no spaces, i.e. `hint -c[yourOtherFlag] [selector] [your other flag's arguments, which may contain spaces]`
- `bind <c-e> hint -f Edit` * - -C [selector] like `-c [selector]` but also hints all elements that would normally be hinted given the other options selected
- Backslashes can escape spaces: `bind <c-s> hint -f Save\ as` * - -x [selector] exclude the matched elements from hinting
- -fr [text] use RegExp to hint the links and inputs * - -f [text] hint links and inputs that display the given text
- -J* disable javascript hints. Don't generate hints related to javascript events. This is particularly useful when used with the `-c` option when you want to generate only hints for the specified css selectors. Also useful on sites with plenty of useless javascript elements such as google.com * - `bind <c-e> hint -f Edit`
- -V create hints for invisible elements. By default, elements outside the viewport when calling :hint are not hinted, this includes them anyways. * - Backslashes can escape spaces: `bind <c-s> hint -f Save\ as`
* - -fr [text] use RegExp to hint the links and inputs
Hinting mode selection: * - -J* disable javascript hints. Don't generate hints related to javascript events. This is particularly useful when used with the `-c` option when you want to generate only hints for the specified css selectors. Also useful on sites with plenty of useless javascript elements such as google.com
- -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). * - -V create hints for invisible elements. By default, elements outside the viewport when calling :hint are not hinted, this includes them anyways.
- For example, use `bind ;jg hint -Jc .rc > .r > a` on google.com to generate hints only for clickable search results of a given query *
- -! execute all hints without waiting for a selection * Hinting mode selection:
- For example, `hint -!bf Comments` opens in background tabs all visible links whose text matches `Comments` *
* - -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).
Deprecated options: * - For example, use `bind ;jg hint -Jc .rc > .r > a` on google.com to generate hints only for clickable search results of a given query
- -br deprecated, use `-qb` instead * - -! execute all hints without waiting for a selection
* - For example, `hint -!bf Comments` opens in background tabs all visible links whose text matches `Comments`
Excepting the custom selector mode, background hint mode and the "immediate" modifier, 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. *
* Deprecated options:
To open a hint in the background, the default bind is `F`. *
* - -br deprecated, use `-qb` instead
Ex-commands available exclusively in hint mode are listed [here](/static/docs/modules/_src_content_hinting_.html) *
* Excepting the custom selector mode, background hint mode and the "immediate" modifier, 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.
Related settings: *
- "hintchars": "hjklasdfgyuiopqwertnmzxcvb" * To open a hint in the background, the default bind is `F`.
- "hintfiltermode": "simple" | "vimperator" | "vimperator-reflow" *
- "relatedopenpos": "related" | "next" | "last" * Ex-commands available exclusively in hint mode are listed [here](/static/docs/modules/_src_content_hinting_.html)
- "hintuppercase": "true" | "false" *
- "hintnames": "short" | "uniform" | "numeric" * Related settings:
- "hintdelay": 300 *
- "hintshift": "true" | "false" * - "hintchars": "hjklasdfgyuiopqwertnmzxcvb"
- "hintautoselect": "true" | "false" * - "hintfiltermode": "simple" | "vimperator" | "vimperator-reflow"
* - "relatedopenpos": "related" | "next" | "last"
With "short" names, Tridactyl will generate short hints that * - "hintuppercase": "true" | "false"
are never prefixes of each other. With "uniform", Tridactyl * - "hintnames": "short" | "uniform" | "numeric"
will generate hints of uniform length. In either case, the * - "hintdelay": 300
hints are generated from the set in "hintchars". * - "hintshift": "true" | "false"
* - "hintautoselect": "true" | "false"
With "numeric" names, hints are always assigned using *
sequential integers, and "hintchars" is ignored. This has the * With "short" names, Tridactyl will generate short hints that
disadvantage that some hints are prefixes of others (and you * are never prefixes of each other. With "uniform", Tridactyl
need to hit space or enter to select such a hint). But it has * will generate hints of uniform length. In either case, the
the advantage that the hints tend to be more predictable * hints are generated from the set in "hintchars".
(e.g., a news site will have the same hints for its *
boilerplate each time you visit it, even if the number of * With "numeric" names, hints are always assigned using
links in the main body changes). * sequential integers, and "hintchars" is ignored. This has the
* disadvantage that some hints are prefixes of others (and you
There are some extra hint "modes" that are actually just normal-mode binds. We'll list them here: * need to hit space or enter to select such a hint). But it has
* the advantage that the hints tend to be more predictable
- `;gv` - "open link in MPV" - only available if you have [[native]] installed and `mpv` on your PATH * (e.g., a news site will have the same hints for its
- `;m` and `;M` - do a reverse image search using Google in the current tab and a new tab * boilerplate each time you visit it, even if the number of
- `;x` and `;X` - move cursor to element and perform a real click or ctrl-shift-click (to open in a new foreground tab). Only available on Linux, if you have [[native]] installed and `xdotool` on your PATH * links in the main body changes).
- `;d` and `;gd` - open links in discarded background tabs (defer loading until tab is switched to) *
* There are some extra hint "modes" that are actually just normal-mode binds. We'll list them here:
NB: by default, hinting respects whether links say they should be opened in new tabs (i.e. `target=_blank`). If you wish to override this you can use `:hint -JW open` to force the hints to open in the current tab. JavaScript hints (grey ones) will always open wherever they want, but if you want to include these anyway you can use `:hint -W open`. *
* - `;gv` - "open link in MPV" - only available if you have [[native]] installed and `mpv` on your PATH
* - `;m` and `;M` - do a reverse image search using Google in the current tab and a new tab
* - `;x` and `;X` - move cursor to element and perform a real click or ctrl-shift-click (to open in a new foreground tab). Only available on Linux, if you have [[native]] installed and `xdotool` on your PATH
* - `;d` and `;gd` - open links in discarded background tabs (defer loading until tab is switched to)
*
* NB: by default, hinting respects whether links say they should be opened in new tabs (i.e. `target=_blank`). If you wish to override this you can use `:hint -JW open` to force the hints to open in the current tab. JavaScript hints (grey ones) will always open wherever they want, but if you want to include these anyway you can use `:hint -W open`.
*/ */
//#content //#content
export async function hint(...args: string[]): Promise<any> { export async function hint(...args: string[]): Promise<any> {
@ -5777,7 +5787,7 @@ export async function ttscontrol(action: string) {
throw new Error("Unknown text-to-speech action: " + action) throw new Error("Unknown text-to-speech action: " + action)
} }
return TTS.doAction(action as TTS.Action) return TTS.doAction(action)
} }
//}}} //}}}

View file

@ -18,7 +18,7 @@ function initTridactylSettingElem(
bindingNode = document.createElement("p") bindingNode = document.createElement("p")
bindingNode.className = `TridactylSetting Tridactyl${kind}` bindingNode.className = `TridactylSetting Tridactyl${kind}`
bindingNode.textContent = kind + ": " bindingNode.textContent = kind + ": "
elem.insertBefore(bindingNode, elem.children[2]) elem.insertBefore(bindingNode, elem.children[1])
} }
return bindingNode as HTMLElement return bindingNode as HTMLElement
} }
@ -137,9 +137,11 @@ function addSettingInputs() {
return Promise.all( return Promise.all(
Array.from( Array.from(
document.querySelectorAll<HTMLAnchorElement>("a.tsd-anchor"), document.querySelectorAll<HTMLHeadingElement>(
".tsd-panel.tsd-member > h3.tsd-anchor-link[id]",
),
).map( ).map(
async (a: HTMLAnchorElement) => { async (a: HTMLHeadingElement) => {
const section = a.parentNode const section = a.parentNode
const settingName = a.id.split(".") const settingName = a.id.split(".")

View file

@ -21,8 +21,6 @@ import * as binding from "@src/lib/binding"
import * as platform from "@src/lib/platform" import * as platform from "@src/lib/platform"
import { DeepPartial } from "tsdef" import { DeepPartial } from "tsdef"
declare function structuredClone<T>(value: T): T // delete me once we move off typescript 3
/* Remove all nulls from objects recursively /* Remove all nulls from objects recursively
* NB: also applies to arrays * NB: also applies to arrays
*/ */

View file

@ -100,9 +100,7 @@ export async function remove(name: string) {
TODO: pass an object to this when tridactyl gets proper flag parsing TODO: pass an object to this when tridactyl gets proper flag parsing
NOTE: while browser.contextualIdentities.create does check for valid color/icon combos, browser.contextualIdentities.update does not. NOTE: while browser.contextualIdentities.create does check for valid color/icon combos, browser.contextualIdentities.update does not.
@param containerId Expects a cookieStringId e.g. "firefox-container-n". @param containerId Expects a cookieStringId e.g. "firefox-container-n".
@param name the new name of the container @param updateObj the new name, color, and icon of the container
@param color the new color of the container
@param icon the new icon of the container
*/ */
export function update( export function update(
containerId: string, containerId: string,
@ -139,7 +137,7 @@ export async function getFromId(
/** Fetches all containers from Firefox's contextual identities API and checks if one exists with the specified name. /** Fetches all containers from Firefox's contextual identities API and checks if one exists with the specified name.
Note: This operation is entirely case-insensitive. Note: This operation is entirely case-insensitive.
@param string cname @param cname
@returns boolean Returns true when cname matches an existing container or on query error. @returns boolean Returns true when cname matches an existing container or on query error.
*/ */
export async function exists(cname: string): Promise<boolean> { export async function exists(cname: string): Promise<boolean> {

View file

@ -114,7 +114,7 @@ export function setContentEditableValues(e, text, start, end) {
/** /**
* Take an editor function as parameter and return it wrapped in a function that will handle grabbing text and caret position from the HTML element it takes as parameter * Take an editor function as parameter and return it wrapped in a function that will handle grabbing text and caret position from the HTML element it takes as parameter
* *
* @param editor_function A function that takes a [string, selectionStart, selectionEnd] tuple as argument and returns a [string, selectionStart, selectionEnd] tuple corresponding to the new state of the text. * @param fn A function that takes a [string, selectionStart, selectionEnd] tuple as argument and returns a [string, selectionStart, selectionEnd] tuple corresponding to the new state of the text.
* *
* @return boolean Whether the editor function was actually called or not * @return boolean Whether the editor function was actually called or not
* *

View file

@ -33,7 +33,7 @@ const bracketexpr_parser = new Parser(bracketexpr_grammar)
// unspoofable keyboard events // unspoofable keyboard events
// this should be ~the only place in the code that accepts KeyboardEvent // this should be ~the only place in the code that accepts KeyboardEvent
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
export type TrustedKeyboardEvent = KeyboardEvent & { readonly isTrusted: true } export type TrustedKeyboardEvent = KeyboardEvent & { readonly isTrusted: true }
export const guarded = memoise( export const guarded = memoise(

View file

@ -100,7 +100,7 @@ function backgroundHandler<
return handler.apply(receiver, message.args) return handler.apply(receiver, message.args)
} }
export function setupListener<Root>(root: Root) { export function setupListener<Root extends object>(root: Root) {
browser.runtime.onMessage.addListener( browser.runtime.onMessage.addListener(
(message: any) => { (message: any) => {
if (message.type in root) { if (message.type in root) {

View file

@ -1,10 +1,6 @@
/** /**
* Runtime metadata over `typedoc --json` output for src/excmds.ts and * Runtime helpers over the compact metadata schema generated at build time.
* src/lib/config.ts. Exposes plain-object indexes (keyed by symbol name) plus * TypeDoc-specific conversion stays in scripts/convert_typedoc_metadata.js.
* a small set of free helpers that operate on raw typedoc nodes.
*
* @hidden symbols are dropped by typedoc itself before they reach this loader,
* so consumers never see them.
*/ */
import metadataJson from "../.metadata.generated.json" import metadataJson from "../.metadata.generated.json"
@ -12,123 +8,33 @@ import staticThemesJson from "../.themes.generated.json"
export const staticThemes: string[] = staticThemesJson export const staticThemes: string[] = staticThemesJson
// ============================================================================= type Node = Record<string, any>
// Indexes — built once at module load by walking the typedoc tree.
// =============================================================================
type Node = any const metadata = metadataJson as unknown as {
version: number
const reflections: Record<number, Node> = {} commands: Record<string, Node>
const fileBuckets: Record< settings: Record<string, Node>
string,
{ functions: Record<string, Node>; classes: Record<string, Node> }
> = {
excmds: { functions: {}, classes: {} },
"lib/config": { functions: {}, classes: {} },
} }
if (metadata.version !== 1)
throw new Error(`Unsupported metadata version: ${metadata.version}`)
function moduleName(node: Node): string { export const excmdsFunctions = metadata.commands
return (node.name || "").replace(/^"|"$/g, "").replace(/\\/g, "/") export const defaultConfigMembers = metadata.settings
}
function walk(node: Node) {
if (!node || typeof node !== "object") return
if (Array.isArray(node)) {
for (const v of node) walk(v)
return
}
if (node.id !== undefined) reflections[node.id] = node
if (node.kindString === "Module") {
const bucket = fileBuckets[moduleName(node)]
for (const child of bucket ? node.children || [] : []) {
if (child.kindString === "Function") {
bucket.functions[child.name] = child
} else if (child.kindString === "Class") {
bucket.classes[child.name] = child
}
}
}
if (node.children) walk(node.children)
}
walk(metadataJson)
export const excmdsFunctions: Record<string, Node> =
fileBuckets.excmds.functions
const defaultConfig: Node | undefined =
fileBuckets["lib/config"].classes["default_config"]
export const defaultConfigMembers: Record<string, Node> = (() => {
const out: Record<string, Node> = {}
for (const ch of defaultConfig?.children || []) out[ch.name] = ch
return out
})()
// =============================================================================
// Doc / type access on function and class-member nodes.
// =============================================================================
function readComment(c: Node | undefined): string {
if (!c) return ""
let s: string = c.shortText || ""
if (c.text) s += (s ? "\n\n" : "") + c.text
return s.replace(/\n+$/, "")
}
export function getDoc(node: Node | undefined): string { export function getDoc(node: Node | undefined): string {
if (!node) return "" return node?.doc || ""
return (
readComment(node.signatures?.[0]?.comment) || readComment(node.comment)
)
} }
export function memberDoc(node: Node | undefined): string { export function memberDoc(node: Node | undefined): string {
if (!node) return "" return node?.doc || ""
if (node.kindString === "Accessor") {
const sig = (node.getSignature || [])[0] || (node.setSignature || [])[0]
if (sig?.comment) return readComment(sig.comment)
}
return readComment(node.comment)
} }
/**
* Returns a typedoc type node for a class member, preserving the children of
* inferred object literals so downstream helpers can inspect their types.
*/
export function memberType(node: Node | undefined): Node | undefined { export function memberType(node: Node | undefined): Node | undefined {
if (!node) return undefined return node?.type
if (node.kindString === "Object literal") {
return { type: "reflection", declaration: node }
}
if (node.type) return node.type
if (node.kindString === "Accessor") {
const sig = (node.getSignature || [])[0] || (node.setSignature || [])[0]
return sig?.type
}
return undefined
} }
/** Parameter list for a function node — each entry has `name`, `type`, `flags`. */
export function paramTypes(fnNode: Node | undefined): Node[] { export function paramTypes(fnNode: Node | undefined): Node[] {
return fnNode?.signatures?.[0]?.parameters || [] return fnNode?.params || []
}
// =============================================================================
// Type coercion + stringification over typedoc type nodes.
// =============================================================================
function resolveType(t: Node | undefined): Node | undefined {
const seen = new Set<number>()
while (
t?.type === "reference" &&
t.id !== undefined &&
!t.typeArguments?.length &&
!seen.has(t.id)
) {
seen.add(t.id)
t = reflections[t.id]?.type || t
}
return t
} }
function intrinsicName(t) { function intrinsicName(t) {
@ -149,7 +55,6 @@ function intrinsicName(t) {
/** Normalised kind: "string" | "number" | "boolean" | "object" | "array" | "void" | "any" | ... */ /** Normalised kind: "string" | "number" | "boolean" | "object" | "array" | "void" | "any" | ... */
export function typeKind(t: Node | undefined): string { export function typeKind(t: Node | undefined): string {
t = resolveType(t)
if (!t) return "any" if (!t) return "any"
switch (t.type) { switch (t.type) {
case "intrinsic": case "intrinsic":
@ -170,7 +75,6 @@ export function typeKind(t: Node | undefined): string {
} }
export function typeToString(t: Node | undefined): string { export function typeToString(t: Node | undefined): string {
t = resolveType(t)
if (!t) return "any" if (!t) return "any"
switch (t.type) { switch (t.type) {
case "intrinsic": case "intrinsic":
@ -235,7 +139,6 @@ function convertIntrinsic(t, value) {
} }
export function convert(t: Node | undefined, value: any): any { export function convert(t: Node | undefined, value: any): any {
t = resolveType(t)
if (!t) return value if (!t) return value
switch (t.type) { switch (t.type) {
case "intrinsic": case "intrinsic":
@ -314,7 +217,6 @@ export function convertMember(
path: string[], path: string[],
value: any, value: any,
): any { ): any {
t = resolveType(t)
const decl = t?.type === "reflection" ? t.declaration : undefined const decl = t?.type === "reflection" ? t.declaration : undefined
const named: Record<string, Node> = {} const named: Record<string, Node> = {}
let indexSig: Node | undefined let indexSig: Node | undefined
@ -322,9 +224,8 @@ export function convertMember(
const childType = memberType(ch) const childType = memberType(ch)
if (ch.name && childType) named[ch.name] = childType if (ch.name && childType) named[ch.name] = childType
} }
const idx = decl?.indexSignature for (const sig of decl?.indexSignatures || [])
const idxArr = Array.isArray(idx) ? idx : idx ? [idx] : [] if (sig?.type) indexSig = sig.type
for (const sig of idxArr) if (sig?.type) indexSig = sig.type
const sub = named[path[0]] ?? indexSig const sub = named[path[0]] ?? indexSig
if (!sub) return value if (!sub) return value

View file

@ -232,8 +232,8 @@ export async function getBestEditor(): Promise<string> {
* helpful error message in the command line if the native messenger is not * helpful error message in the command line if the native messenger is not
* installed, or is the wrong version. * installed, or is the wrong version.
* *
* @arg version: A string representing the minimal required version. * @param version A string representing the minimal required version.
* @arg interactive: True if a message should be displayed on version mismatch. * @param interactive True if a message should be displayed on version mismatch.
* @return false if the required version is higher than the currently available * @return false if the required version is higher than the currently available
* native messenger version. * native messenger version.
*/ */

View file

@ -139,11 +139,9 @@ async function tabIdsOrCurrent(ids?: number | number[]): Promise<number[]> {
*/ */
export async function setTabTgroup(name: string, id?: number | number[]) { export async function setTabTgroup(name: string, id?: number | number[]) {
const ids = await tabIdsOrCurrent(id) const ids = await tabIdsOrCurrent(id)
return Promise.all( return ids.map(id => {
ids.map(id => {
browserBg.sessions.setTabValue(id, "tridactyl-tgroup", name) browserBg.sessions.setTabValue(id, "tridactyl-tgroup", name)
}), })
)
} }
/** /**
@ -154,11 +152,9 @@ export async function setTabTgroup(name: string, id?: number | number[]) {
*/ */
export async function clearTabTgroup(id?: number | number[]) { export async function clearTabTgroup(id?: number | number[]) {
const ids = await tabIdsOrCurrent(id) const ids = await tabIdsOrCurrent(id)
return Promise.all( return ids.map(id => {
ids.map(id => {
browserBg.sessions.removeTabValue(id, "tridactyl-tgroup") browserBg.sessions.removeTabValue(id, "tridactyl-tgroup")
}), })
)
} }
/** /**
@ -209,7 +205,7 @@ export async function tgroupLastTabId(name: string, previous = false) {
/** /**
* Clear stored information for a tab group. * Clear stored information for a tab group.
* *
* @param name The name of the tab group. * @param oldName The name of the tab group.
* @param newName A name to rename the group to. * @param newName A name to rename the group to.
* @param id The id of the window. Use the current window if not specified. * @param id The id of the window. Use the current window if not specified.
* *

View file

@ -176,7 +176,7 @@ function getExtensionForMimetype(mime: string): string {
* - otherwise, use the hostname of the URL * - otherwise, use the hostname of the URL
* - if that fails, "download" * - if that fails, "download"
* *
* @param URL the URL to make a filename for * @param url the URL to make a filename for
* @return the filename according to the above rules * @return the filename according to the above rules
*/ */
export function getDownloadFilenameForUrl(url: URL): string { export function getDownloadFilenameForUrl(url: URL): string {
@ -260,7 +260,7 @@ function setUrlQueries(url: URL, qys: string[]) {
* all instances are removed * all instances are removed
* *
* @param url the URL to act on * @param url the URL to act on
* @param query the query to delete * @param matchQuery the query to delete
* *
* @return the modified URL * @return the modified URL
*/ */

View file

@ -58,7 +58,7 @@ export function measured(
} }
/** /**
* Like the @measured decorator, but properly handles async functions * Like the `@measured` decorator, but properly handles async functions
* by chaining a resolution onto the promise that marks completion * by chaining a resolution onto the promise that marks completion
* when the function resolves its promise. * when the function resolves its promise.
*/ */

View file

@ -111,7 +111,7 @@ const state = new Proxy(overlay, {
} }
browser.storage.local.set({ browser.storage.local.set({
state: R.pick(PERSISTENT_KEYS, target), state: R.pick(PERSISTENT_KEYS, target),
} as any) })
} }
return true return true
}, },

View file

@ -2915,6 +2915,7 @@ img {
* THE FOLLOWING CSS SOURCE CODE WAS ADDED BY THE TRIDACTYL PROJECT * THE FOLLOWING CSS SOURCE CODE WAS ADDED BY THE TRIDACTYL PROJECT
****************************************************************************/ ****************************************************************************/
.container.container-main { .container.container-main {
display: block;
padding-top: 0px !important; padding-top: 0px !important;
} }
.tsd-navigation { .tsd-navigation {
@ -2942,6 +2943,11 @@ body {
background: var(--tridactyl-bg); background: var(--tridactyl-bg);
} }
.tsd-panel.tsd-member,
.tsd-member .tsd-description {
color: var(--tridactyl-fg);
}
a { a {
color: #05a805; color: #05a805;
} }

107
src/tridactyl.d.ts vendored
View file

@ -9,103 +9,25 @@ interface Number {
clamp(lo: number, hi: number): number clamp(lo: number, hi: number): number
} }
// Firefox-specific dom properties // Record that we've added convenience objects to window.
interface Window {
scrollByLines(n: number): void
scrollByPages(n: number): void
eval(str: string): any
}
interface Selection {
modify(
alter: "move" | "extend",
direction: "forward" | "backward" | "left" | "right",
granularity: "character" | "word" | "line",
): void
}
// Record that we've added a property with convenience objects to the
// window object:
interface Window { interface Window {
tri: any tri: any
} }
// This isn't an actual firefox type but it's nice to have one for this kind of object
// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/find/find
interface findResult {
count: number
rangeData: Array<{
framePos: number
startTextNodePos: number
endTextNodePos: number
startOffset: number
endOffset: number
text: string
}>
rectData: {
rectsAndTexts: Array<{
top: number
left: number
bottom: number
right: number
}>
textList: string[]
}
}
interface HTMLElement { interface HTMLElement {
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/inert
inert: boolean
// Firefox-only (?) Element attribute // Firefox-only (?) Element attribute
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/dom/openOrClosedShadowRoot // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/dom/openOrClosedShadowRoot
openOrClosedShadowRoot: ShadowRoot | null openOrClosedShadowRoot: ShadowRoot | null
// Let's be future proof:
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
focus(options?: any): void
// https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceChildren
replaceChildren(...nodes: Node[]): void
} }
/* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/no-unsafe-function-type */
// these functions really can be anything, ditto for the objects // these functions really can be anything, ditto for the objects
declare function exportFunction( declare function exportFunction(
func: Function, func: Function,
targetScope: object, targetScope: object,
options?: { defineAs?: string; allowCrossOriginArguments?: boolean }, options?: { defineAs?: string; allowCrossOriginArguments?: boolean },
): Function ): Function
/* eslint-enable @typescript-eslint/ban-types */ /* eslint-enable @typescript-eslint/no-unsafe-function-type */
// Web extension types not in web-ext-types yet
declare namespace browser.find {
function find(query, object): any
}
declare namespace browser.tabs {
function setZoom(zoomFactor: number): Promise<void>
// setZoom has an optional first argument of tabId: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/setZoom#Parameters
// eslint-disable-next-line @typescript-eslint/unified-signatures
function setZoom(tabId: number, zoomFactor: number): Promise<void>
function toggleReaderMode(tabId?: number): Promise<void>
}
// web-ext-browser barely declares a third of the management
// interface, and we can't switch to @types/firefox-webext-browser yet
// because their enums are all messed up (see
// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/28369)
// Instead, we'll copy-paste as much as we need from the fixed branch:
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d1180e5218a7bf69e6f0da5ac2e2584bd57a1cdf/types/firefox-webext-browser/index.d.ts
interface WebExtEventBase<
TAddListener extends (...args: any[]) => any,
TCallback,
> {
addListener: TAddListener
removeListener(cb: TCallback): void
hasListener(cb: TCallback): boolean
}
// html-tagged-template.js // html-tagged-template.js
declare function html( declare function html(
@ -113,30 +35,9 @@ declare function html(
...values: any[] ...values: any[]
): HTMLElement ): HTMLElement
declare namespace browser.search { // Custom matcher from src/lib/mod.test.ts.
function search(searchProperties: {
query: string
engine?: string
tabId?: number
}): void
function get(): Promise<
Array<{
name: string
isDefault: boolean
alias?: string
faviconURL?: string
}>
>
}
// Stop typedoc complaining about toBeAll.
declare namespace jest { declare namespace jest {
interface Matchers<R> { interface Matchers<R> {
toBeAll: any toBeAll: any
} }
} }
// jest-webextension-mock doesn't know about this Firefox specific API
declare namespace browser.commands {
function update(details)
}

View file

@ -1,7 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"moduleResolution": "node", "moduleResolution": "bundler",
"module": "es2020", "module": "es2020",
"strict": false,
"esModuleInterop": true, "esModuleInterop": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"noImplicitAny": false, "noImplicitAny": false,
@ -15,10 +16,9 @@
"strictBindCallApply": true, "strictBindCallApply": true,
"noImplicitThis": true, "noImplicitThis": true,
"strictFunctionTypes": true, "strictFunctionTypes": true,
"baseUrl": "src/",
"types": ["@types/firefox-webext-browser"], "types": ["@types/firefox-webext-browser"],
"paths": { "paths": {
"@src/*": ["*"] "@src/*": ["./src/*"]
} }
}, },
"include": [ "include": [

690
yarn.lock

File diff suppressed because it is too large Load diff