diff --git a/.eslintrc.js b/.eslintrc.js index 7cdfc9e5..4cf4d760 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -50,35 +50,16 @@ module.exports = { "@typescript-eslint/array-type": "off", "@typescript-eslint/await-thenable": "error", "@typescript-eslint/ban-ts-comment": "error", - "@typescript-eslint/ban-types": [ + "@typescript-eslint/no-restricted-types": [ "error", { "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": { "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-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 @@ -89,28 +70,14 @@ module.exports = { } ], "@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": [ "error", { "default": ["field", "constructor", "method"] }, ], + "@typescript-eslint/no-array-delete": "off", "@typescript-eslint/no-array-constructor": "error", + "@typescript-eslint/no-base-to-string": "off", "@typescript-eslint/no-empty-function": "error", - "@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-explicit-any": "off", "@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 @@ -126,7 +93,7 @@ module.exports = { "@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-non-null-asserted-optional-chain": "error", "@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-unnecessary-type-assertion": "error", "@typescript-eslint/no-unsafe-assignment": "off", //"error", @@ -146,17 +113,19 @@ module.exports = { { "args": "after-used", "argsIgnorePattern": "^_", + "caughtErrors": "none", "varsIgnorePattern": "^_", }, ], "@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-for-of": "error", "@typescript-eslint/prefer-function-type": "error", "@typescript-eslint/prefer-namespace-keyword": "error", + "@typescript-eslint/prefer-promise-reject-errors": "off", "@typescript-eslint/prefer-regexp-exec": "error", - "@typescript-eslint/quotes": [ + "quotes": [ "error", "double", { @@ -167,10 +136,6 @@ module.exports = { "@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-template-expressions": "off", - "@typescript-eslint/semi": [ - "off", - null - ], "@typescript-eslint/triple-slash-reference": [ "error", { @@ -179,7 +144,6 @@ module.exports = { "lib": "always" } ], - "@typescript-eslint/type-annotation-spacing": "error", "@typescript-eslint/unbound-method": "error", "@typescript-eslint/unified-signatures": "error", "arrow-body-style": "error", @@ -207,7 +171,6 @@ module.exports = { "import/order": "off", "jsdoc/check-alignment": "off", "jsdoc/check-indentation": "off", - "jsdoc/newline-after-description": "off", "max-classes-per-file": "off", "max-len": "off", "new-parens": "error", @@ -296,10 +259,9 @@ module.exports = { "files": ["src/content.ts", "src/commandline_frame.ts"], "rules": { "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/ban-types": [ + "@typescript-eslint/no-restricted-types": [ "error", { - "extendDefaults": true, "types": { "TrustedKeyboardEvent": { "message": "Events must be validated with `isTrustedKeyboardEvent` at runtime" diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 5e1ee0c1..b086b71f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -42,7 +42,7 @@ jobs: cache: 'yarn' - name: Install deps - run: yarn install + run: yarn install --frozen-lockfile - name: Setup Firefox uses: browser-actions/setup-firefox@v1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b40d7c08..65210ffd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,8 +20,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'yarn' - name: Setup - run: yarn install + run: yarn install --frozen-lockfile - name: ${{ matrix.step }} env: STEP: ${{ matrix.step }} diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index e0bc7e71..aaf89249 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -47,7 +47,7 @@ jobs: - name: Build run: | cd tridactyl - yarn install + yarn install --frozen-lockfile yarn run build find . -iname "*.html" -exec sed 's@href="/static@href="/build/static@' -i '{}' ';' # ideally this url would be less gnarly cd ../site diff --git a/package.json b/package.json index 737ee424..5d5c0680 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "stream-browserify": "^3.0.0", "tridactyl-arg": "git+https://github.com/GHolk/arg.git#v5.1.0", "tsdef": "^0.0.14", - "typedoc": "0.22.18", + "typedoc": "0.28.20", "xss": "^1.0.15" }, "devDependencies": { @@ -35,10 +35,10 @@ "@types/jest": "29.5.12", "@types/nearley": "^2.11.5", "@types/selenium-webdriver": "^4.1.10", - "@typescript-eslint/eslint-plugin": "5.25.0", - "@typescript-eslint/parser": "5.25.0", + "@typescript-eslint/eslint-plugin": "8.64.0", + "@typescript-eslint/parser": "8.64.0", "command-line-args": "^6.0.1", - "eslint": "^7.32.0", + "eslint": "8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsdoc": "^50.6.1", @@ -55,7 +55,7 @@ "prettier": "^3.4.2", "selenium-webdriver": "^4.7.1", "ts-jest": "29.4.11", - "typescript": "4.7.4", + "typescript": "6.0.3", "web-ext": "^7.10.0", "yaml-lint": "^1.7.0" }, diff --git a/scripts/build.sh b/scripts/build.sh index 9bd83a1d..2dfe148f 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -57,16 +57,19 @@ if [ "$QUICK_BUILD" != "1" ]; then "$(yarn bin)/nearleyc" src/grammars/bracketexpr.ne > \ 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 # routes the public @src/.metadata.generated import to the JSON loader. "$(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 \ --exclude 'src/.excmds_*.generated.ts' \ src/excmds.ts src/lib/config.ts src/content/state_content.ts - node scripts/minify_json.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 + node scripts/convert_typedoc_metadata.js src/.metadata.generated.json scripts/newtab.md.sh scripts/make_tutorial.sh diff --git a/scripts/convert_typedoc_metadata.js b/scripts/convert_typedoc_metadata.js new file mode 100644 index 00000000..4aa9289a --- /dev/null +++ b/scripts/convert_typedoc_metadata.js @@ -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)) +} diff --git a/scripts/excmds_macros.py b/scripts/excmds_macros.py index ecc1376c..d68657d1 100755 --- a/scripts/excmds_macros.py +++ b/scripts/excmds_macros.py @@ -201,5 +201,5 @@ def main(): 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() diff --git a/scripts/make_docs.sh b/scripts/make_docs.sh index de55b296..7d23c2df 100755 --- a/scripts/make_docs.sh +++ b/scripts/make_docs.sh @@ -2,8 +2,18 @@ set -e 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 \ + --excludePrivate false --excludePrivateClassFields false \ + --includeHierarchySummary false \ --exclude "src/**/?(test_utils|*.test).ts" \ --out "$dest" src rm -rf build/static/docs diff --git a/scripts/typedoc-theme.js b/scripts/typedoc-theme.js deleted file mode 100644 index 724bf036..00000000 --- a/scripts/typedoc-theme.js +++ /dev/null @@ -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) -} diff --git a/scripts/typedoc-theme.mjs b/scripts/typedoc-theme.mjs new file mode 100644 index 00000000..f9a21e37 --- /dev/null +++ b/scripts/typedoc-theme.mjs @@ -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, + "<$1$2>", + ), + } + : 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) +} diff --git a/src/background/download_background.ts b/src/background/download_background.ts index edd4e71e..b1f72a0e 100644 --- a/src/background/download_background.ts +++ b/src/background/download_background.ts @@ -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. * - * @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 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 overwrite If true, overwrite the destination file, returns error code 1 otherwise if file exists + * @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( url: string, diff --git a/src/background/webrequests.ts b/src/background/webrequests.ts index 26290e3f..ad67273e 100644 --- a/src/background/webrequests.ts +++ b/src/background/webrequests.ts @@ -12,7 +12,7 @@ export const requestEventExpraInfoSpecMap = { export const requestEvents = Object.keys(requestEventExpraInfoSpecMap) // 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> = {} export const registerWebRequestAutocmd = async ( @@ -21,7 +21,7 @@ export const registerWebRequestAutocmd = async ( func: string, ) => { // 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 if (!LISTENERS[requestEvent]) LISTENERS[requestEvent] = {} diff --git a/src/completions/Excmd.ts b/src/completions/Excmd.ts index 8f68226d..d3724fe6 100644 --- a/src/completions/Excmd.ts +++ b/src/completions/Excmd.ts @@ -7,7 +7,7 @@ export class ExcmdCompletionOption extends Completions.CompletionOptionHTML impl public fuseKeys = [] constructor( public value: string, - public documentation: string = "", + public documentation = "", ) { super() this.fuseKeys.push(this.value) diff --git a/src/completions/Theme.ts b/src/completions/Theme.ts index 578d494d..0a7f08e4 100644 --- a/src/completions/Theme.ts +++ b/src/completions/Theme.ts @@ -6,7 +6,7 @@ export class ThemeCompletionOption extends Completions.CompletionOptionHTML implements Completions.CompletionOptionFuse { public fuseKeys = [] - constructor(public value: string, public documentation: string = "") { + constructor(public value: string, public documentation = "") { super() this.fuseKeys.push(this.value) diff --git a/src/content/commandline_cmds.ts b/src/content/commandline_cmds.ts index 95c61422..c932703e 100644 --- a/src/content/commandline_cmds.ts +++ b/src/content/commandline_cmds.ts @@ -9,7 +9,7 @@ export const CmdlineCmds = new Proxy(functions as any, { get(target, property) { if (target[property]) { return (...args) => - messageOwnTab("commandline_cmd", property as string, args) + messageOwnTab("commandline_cmd", property, args) } return target[property] }, diff --git a/src/content/finding.ts b/src/content/finding.ts index 0e5450d3..78b64017 100644 --- a/src/content/finding.ts +++ b/src/content/finding.ts @@ -185,7 +185,7 @@ export async function jumpToMatch(searchQuery, option) { const sensitive = findcase === "sensitive" || (findcase === "smart" && /[A-Z]/.test(searchQuery)) - const findPromise = await browserBg.find.find(searchQuery, { + const results = await browserBg.find.find(searchQuery, { tabId: await activeTabId(), caseSensitive: sensitive, entireWord: false, @@ -208,8 +208,6 @@ export async function jumpToMatch(searchQuery, option) { nodes.push(node) } while (node) - const results = await findPromise - const host = getFindHost() for (let i = 0; i < results.count; ++i) { const range = results.rangeData[i] diff --git a/src/excmds.ts b/src/excmds.ts index 2337930a..7103ce95 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -224,7 +224,7 @@ export async function getRssLinks(): Promise nohlsearch + * ```text + * bind / fillcmdline find + * bind ? fillcmdline find --reverse + * bind n findnext --search-from-view + * bind N findnext --search-from-view --reverse + * bind gn findselect + * bind gN composite findnext --search-from-view --reverse; findselect + * bind , nohlsearch + * ``` * * 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 * - `-?` 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 @@ -2219,7 +2221,7 @@ export function urlparent(count = 1) { * * -Q delete the given query * * -g graft a new path onto URL or parent path of it * * -*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 * * -r [flags] * * -s @@ -3187,19 +3189,19 @@ export async function undo(item = "recent"): Promise { return -1 } -/** 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. - - 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. - - @param index - New index for the current tab. - - 1,start,^ are aliases for the first index. 0,end,$ are aliases for the last index. -*/ +/** + * 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. + * + * 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. + * + * @param index New index for the current tab. + * + * 1,start,^ are aliases for the first index. 0,end,$ are aliases for the last index. + */ //#background export async function tabmove(index = "$") { const aTab = await activeTab() @@ -3272,7 +3274,7 @@ export async function tabmove(index = "$") { * - `--title` sorts tabs by title * - `--url` sorts tabs by url (the default) * - `(tab1, tab2) => true|false` - * - sort by arbitrary comparison function. `tab{1,2}` are objects with properties described here: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/Tab + * - sort by arbitrary comparison function. `tab{1,2}` are objects with properties described here: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/Tab */ //#background export async function tabsort(...callbackchunks: string[]) { @@ -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 "unmute" to the excmd will unmute. Passing "toggle" to the excmd will toggle the state of `browser.tabs.tab.MutedInfo` - @param string[] muteArgs + @param muteArgs */ //#background export async function mute(...muteArgs: string[]): Promise { @@ -3419,7 +3421,7 @@ export async function winopen(...args: string[]) { /** * 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` */ @@ -4245,7 +4247,7 @@ export async function clipboard(excmd: "open" | "yank" | "yankshort" | "yankcano /** Copy an image to the clipboard. @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 tri.urlutils.getAbsoluteURL. */ //#background export async function yankimage(url: string): Promise { @@ -4265,16 +4267,16 @@ export async function yankimage(url: string): Promise { } } -/** 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. - - 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. - - 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. +/** + * 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. + * + * 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. + * + * 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 export async function tab(...id: string[]) { @@ -4753,62 +4755,65 @@ const AUCMDS = ["DocStart", "DocLoad", "DocEnd", "TriStart", "TabEnter", "TabLef export function getAutocmdEvents() { 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 * - - 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).) - - 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. - - TabLeft: When a tab lost focus or closed. + * - 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).) + * - 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. + * - 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 '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 '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 '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 * - - 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) - - 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 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) + * - 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) * - * @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). - - Example for zooming in more on a website: - ``` - autocmd DocStart .*example\.com.* zoom 150 false TRI_FIRED_MOZ_TABID - ``` + * - 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: + * + * ```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. - - Example for redirecting from new to old reddit: - ``` - autocmd BeforeRequest https://www.reddit.com/r/* (details) => ({redirectUrl: details.url.replace(/^https:\/\/www\./, "https://old.")}) - ``` + * - 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: + * + * ```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: - - `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_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_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_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_HEIGHT`: The height 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_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_LASTACCESSED`: Time at which the tab was last accessed, in milliseconds since the epoch. - - `TRI_FIRED_PINNED`: Whether the tab is pinned. - - `TRI_FIRED_TITLE`: The title of the tab. - - `TRI_FIRED_URL`: The URL of the document that the tab is displaying. + * - `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_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_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_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_HEIGHT`: The height 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_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_LASTACCESSED`: Time at which the tab was last accessed, in milliseconds since the epoch. + * - `TRI_FIRED_PINNED`: Whether the tab is pinned. + * - `TRI_FIRED_TITLE`: The title of the tab. + * - `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. * @@ -5297,99 +5302,104 @@ export function setnull(...keys: string[]) { const KILL_STACK: Element[] = [] // {{{ HINTMODE -/** 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 - 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 - - -b open in background - - -y copy (yank) link's target to clipboard - - -p copy an element's text to the clipboard - - -h select an element (as if you click-n-dragged over it) - - -P copy an element's title/alt text to the clipboard - - -r read an element's text with text-to-speech - - -i view an image - - -I view an image in a new tab - - -k irreversibly deletes an element from the page (until reload) - - -K hides an element on the page; hidden elements can be restored using [[elementunhide]]. - - -s save (download) the linked resource - - -S save the linked image - - -a save-as the linked resource - - -A save-as the linked image - - -; focus an element and set it as the element or the child of the element to scroll - - -# yank an element's anchor URL to clipboard - - -w open in new window - - -wp open in new private window - - -z scroll an element to the top of the viewport - - `-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 ), 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. - - -F [callback] - run a custom callback on the selected hint, e.g. `hint -JF e => {tri.excmds.tabopen("-b",e.href); e.remove()}`. - - Element selection flags: - - -c [selector] hint links that match the css selector - - `bind ;c hint -c [class*="expand"],[class*="togg"]` works particularly well on reddit and HN - - 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 - - -x [selector] exclude the matched elements from hinting - - -f [text] hint links and inputs that display the given text - - `bind hint -f Edit` - - Backslashes can escape spaces: `bind hint -f Save\ as` - - -fr [text] use RegExp to hint the links and inputs - - -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 - - -V create hints for invisible elements. By default, elements outside the viewport when calling :hint are not hinted, this includes them anyways. - - Hinting mode selection: - - -q* quick (or rapid) hints mode. Stay in hint mode until you press , 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). - - 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 - - For example, `hint -!bf Comments` opens in background tabs all visible links whose text matches `Comments` - - Deprecated options: - - -br deprecated, use `-qb` instead - - Excepting the custom selector mode, background hint mode and the "immediate" modifier, each of these hint modes is available by default as `;