mirror of
https://github.com/tridactyl/tridactyl.git
synced 2026-09-10 07:16:33 -04:00
Fix #2980: add MVP :define and glossary
Now I just have to write all the definitions :(
This commit is contained in:
parent
1ad5c34ac6
commit
82985ae14c
3
doc/glossary.jsonl
Normal file
3
doc/glossary.jsonl
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{"word": "mozilla", "definition": "The foundation that controls Firefox and Thunderbird."}
|
||||
{"word": "bovine3dom", "definition": "The esteemed maintainer of Tridactyl and author of this glossary."}
|
||||
{"word": "vimperator", "definition": "A now defunct Firefox extension that was the main inspiration for Tridactyl."}
|
||||
|
|
@ -42,6 +42,7 @@ mkdir -p build
|
|||
mkdir -p build/static
|
||||
mkdir -p generated/static
|
||||
mkdir -p generated/static/clippy
|
||||
node scripts/make_glossary.js
|
||||
|
||||
if [ "$(isWindowsMinGW)" = "True" ]; then
|
||||
$WIN_PYTHON scripts/excmds_macros.py
|
||||
|
|
|
|||
76
scripts/make_glossary.js
Normal file
76
scripts/make_glossary.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
const root = path.resolve(__dirname, "..")
|
||||
const source = path.join(root, "doc/glossary.jsonl")
|
||||
const entries = []
|
||||
const anchors = new Set()
|
||||
|
||||
for (const [index, line] of fs
|
||||
.readFileSync(source, "utf8")
|
||||
.split("\n")
|
||||
.entries()) {
|
||||
if (!line.trim()) continue
|
||||
let entry
|
||||
try {
|
||||
entry = JSON.parse(line)
|
||||
} catch (error) {
|
||||
throw new Error(`${source}:${index + 1}: ${error.message}`)
|
||||
}
|
||||
if (
|
||||
!entry ||
|
||||
typeof entry.word !== "string" ||
|
||||
typeof entry.definition !== "string" ||
|
||||
!entry.word.trim() ||
|
||||
!entry.definition.trim()
|
||||
)
|
||||
throw new Error(
|
||||
`${source}:${index + 1}: word and definition must be non-empty strings`,
|
||||
)
|
||||
const word = entry.word.trim().normalize("NFC")
|
||||
const definition = entry.definition.trim()
|
||||
const anchor = word.toLowerCase()
|
||||
if (anchors.has(anchor))
|
||||
throw new Error(
|
||||
`${source}:${index + 1}: duplicate word ${JSON.stringify(word)}`,
|
||||
)
|
||||
anchors.add(anchor)
|
||||
entries.push({ word, definition, anchor })
|
||||
}
|
||||
|
||||
if (!entries.length) throw new Error(`${source}: glossary must not be empty`)
|
||||
entries.sort((a, b) => (a.word < b.word ? -1 : a.word > b.word ? 1 : 0))
|
||||
const escape = value =>
|
||||
value.replace(/[&<>"']/g, char => `&#${char.charCodeAt(0)};`)
|
||||
const body = [
|
||||
"<h1>Glossary</h1>",
|
||||
"<p>Terms used in Tridactyl's documentation.</p>",
|
||||
'<dl class="glossary-list">',
|
||||
...entries.map(
|
||||
entry =>
|
||||
`<div class="glossary-entry" id="${escape(entry.anchor)}"><dt><code>${escape(entry.word)}</code></dt><dd>${escape(entry.definition)}</dd></div>`,
|
||||
),
|
||||
"</dl>",
|
||||
].join("\n")
|
||||
let html = fs.readFileSync(
|
||||
path.join(root, "src/static/clippy/tutor.template.html"),
|
||||
"utf8",
|
||||
)
|
||||
for (const [marker, replacement] of [
|
||||
["<title>Tridactyl Tutorial</title>", "<title>Tridactyl Glossary</title>"],
|
||||
['href="./glossary.html"', 'aria-current="page" href="./glossary.html"'],
|
||||
["REPLACETHIS", body],
|
||||
]) {
|
||||
if (html.split(marker).length !== 2)
|
||||
throw new Error(
|
||||
`Expected one ${JSON.stringify(marker)} in tutor.template.html`,
|
||||
)
|
||||
html = html.replace(marker, replacement)
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(root, "src/.glossary.generated.json"),
|
||||
JSON.stringify(entries),
|
||||
)
|
||||
fs.writeFileSync(path.join(root, "generated/static/clippy/glossary.html"), html)
|
||||
|
|
@ -35,6 +35,7 @@ import { ExtensionsCompletionSource } from "@src/completions/Extensions"
|
|||
import { FileSystemCompletionSource } from "@src/completions/FileSystem"
|
||||
import { GotoCompletionSource } from "@src/completions/Goto"
|
||||
import { GuisetCompletionSource } from "@src/completions/Guiset"
|
||||
import { GlossaryCompletionSource } from "@src/completions/Glossary"
|
||||
import { HelpCompletionSource } from "@src/completions/Help"
|
||||
import { HistoryCompletionSource } from "@src/completions/History"
|
||||
import { PreferenceCompletionSource } from "@src/completions/Preferences"
|
||||
|
|
@ -153,6 +154,7 @@ export function enableCompletions() {
|
|||
FileSystemCompletionSource,
|
||||
GotoCompletionSource,
|
||||
GuisetCompletionSource,
|
||||
GlossaryCompletionSource,
|
||||
HelpCompletionSource,
|
||||
AproposCompletionSource,
|
||||
HistoryCompletionSource,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from "@src/.metadata.generated"
|
||||
import * as aliases from "@src/lib/aliases"
|
||||
import * as config from "@src/lib/config"
|
||||
import { glossaryOptions } from "@src/completions/Glossary"
|
||||
|
||||
class AproposCompletionOption extends Completions.CompletionOptionHTML implements Completions.CompletionOptionFuse {
|
||||
public fuseKeys = []
|
||||
|
|
@ -139,6 +140,10 @@ export class AproposCompletionSource extends Completions.CompletionSourceFuse {
|
|||
)
|
||||
}),
|
||||
),
|
||||
"-g": (options, query) =>
|
||||
options.concat(
|
||||
glossaryOptions(this.createOption.bind(this), query, false),
|
||||
),
|
||||
}
|
||||
|
||||
const args = query.split(" ")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export class ExcmdCompletionOption extends Completions.CompletionOptionHTML impl
|
|||
public documentation = "",
|
||||
) {
|
||||
super()
|
||||
this.fuseKeys.push(this.value)
|
||||
this.fuseKeys.push(this.value, this.documentation)
|
||||
|
||||
// Create HTMLElement
|
||||
this.html = html`<tr class="ExcmdCompletionOption option">
|
||||
|
|
|
|||
31
src/completions/Glossary.ts
Normal file
31
src/completions/Glossary.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import * as Completions from "@src/completions"
|
||||
import { ExcmdCompletionOption } from "@src/completions/Excmd"
|
||||
import glossary from "@src/.glossary.generated.json"
|
||||
|
||||
export function glossaryOptions(createOption, query: string, prefix: boolean) {
|
||||
const needle = query.toLowerCase()
|
||||
return glossary
|
||||
.filter(entry =>
|
||||
prefix
|
||||
? entry.word.toLowerCase().startsWith(needle)
|
||||
: (entry.word + entry.definition)
|
||||
.toLowerCase()
|
||||
.includes(needle),
|
||||
)
|
||||
.map(entry =>
|
||||
createOption(entry.word, `Glossary. ${entry.definition}`, "-g"),
|
||||
)
|
||||
}
|
||||
|
||||
export class GlossaryCompletionSource extends Completions.CompletionSourceFuse {
|
||||
public options: ExcmdCompletionOption[]
|
||||
|
||||
constructor(parent) {
|
||||
super(["define"], "GlossaryCompletionSource", "Glossary")
|
||||
this.options = glossary.map(
|
||||
entry => new ExcmdCompletionOption(entry.word, entry.definition),
|
||||
)
|
||||
this.sortScoredOptions = true
|
||||
parent.appendChild(this.node)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from "@src/.metadata.generated"
|
||||
import * as aliases from "@src/lib/aliases"
|
||||
import * as config from "@src/lib/config"
|
||||
import { glossaryOptions } from "@src/completions/Glossary"
|
||||
|
||||
class HelpCompletionOption extends Completions.CompletionOptionHTML implements Completions.CompletionOptionFuse {
|
||||
public fuseKeys = []
|
||||
|
|
@ -116,6 +117,10 @@ export class HelpCompletionSource extends AproposCompletionSource {
|
|||
)
|
||||
}),
|
||||
),
|
||||
"-g": (options, query) =>
|
||||
options.concat(
|
||||
glossaryOptions(this.createOption.bind(this), query, true),
|
||||
),
|
||||
}
|
||||
|
||||
const args = query.split(" ")
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ import * as Logging from "@src/lib/logging"
|
|||
import { AutoContain, markExplicitContainerTab } from "@src/lib/autocontainers"
|
||||
import * as CSS from "css"
|
||||
import * as Perf from "@src/perf"
|
||||
import { staticThemes, defaultConfigMembers, memberType, typeKind, convert, convertMember } from "@src/.metadata.generated"
|
||||
import { staticThemes, excmdsFunctions, defaultConfigMembers, memberType, typeKind, convert, convertMember } from "@src/.metadata.generated"
|
||||
import * as Native from "@src/lib/native"
|
||||
import * as TTS from "@src/lib/text_to_speech"
|
||||
import * as excmd_parser from "@src/parsers/exmode"
|
||||
|
|
@ -102,6 +102,7 @@ import * as R from "ramda"
|
|||
import * as treestyletab from "@src/interop/tst"
|
||||
import { uuidv4 } from "@src/lib/math"
|
||||
import { ABOUT_PAGES } from "@src/lib/about_pages"
|
||||
import glossary from "@src/.glossary.generated.json"
|
||||
|
||||
/**
|
||||
* This is used to drive some excmd handling in `composite`.
|
||||
|
|
@ -1806,11 +1807,11 @@ export function home(all: "false" | "true" = "false") {
|
|||
|
||||
/** Show this page.
|
||||
|
||||
`:help something` jumps to the entry for something. Something can be an excmd, an alias for an excmd, a binding or a setting.
|
||||
`:help something` jumps to the entry for something. Something can be an excmd, an alias for an excmd, a binding, a setting or a glossary term.
|
||||
|
||||
On the ex command page, the "nmaps" list is a list of all the bindings for the command you're seeing and the "exaliases" list lists all its aliases.
|
||||
|
||||
If there's a conflict (e.g. you have a "go" binding that does something, a "go" excmd that does something else and a "go" setting that does a third thing), the binding is chosen first, then the setting, then the excmd. In such situations, if you want to let Tridactyl know you're looking for something specfic, you can specify the following flags:
|
||||
If there's a conflict, bindings are chosen first, then settings, aliases, ex commands and finally glossary terms. You can select a category explicitly with the following flags:
|
||||
|
||||
`-a`: look for an alias
|
||||
|
||||
|
|
@ -1820,6 +1821,8 @@ export function home(all: "false" | "true" = "false") {
|
|||
|
||||
`-s`: look for a setting
|
||||
|
||||
`-g`: look in the glossary
|
||||
|
||||
`-B`: open the help page in a background tab
|
||||
|
||||
`-o`: open the help page in the current tab
|
||||
|
|
@ -1840,6 +1843,7 @@ export async function help(...args: string[]) {
|
|||
"-b": Boolean,
|
||||
"-e": Boolean,
|
||||
"-s": Boolean,
|
||||
"-g": Boolean,
|
||||
"-B": Boolean,
|
||||
"-o": Boolean,
|
||||
"-t": Boolean,
|
||||
|
|
@ -1848,7 +1852,10 @@ export async function help(...args: string[]) {
|
|||
{ argv: args, allowNegativePositional: true },
|
||||
)
|
||||
|
||||
const openInCurrentWindow = option["-o"] || ((await activeTab()).url.startsWith(browser.runtime.getURL("static/docs/")) && !(option["-B"] || option["-t"] || option["-w"]))
|
||||
const glossaryPage = browser.runtime.getURL("static/clippy/glossary.html")
|
||||
const excmdPage = browser.runtime.getURL("static/docs/modules/_src_excmds_.html")
|
||||
const activeUrl = (await activeTab()).url
|
||||
const openInCurrentWindow = option["-o"] || ((activeUrl.startsWith(browser.runtime.getURL("static/docs/")) || activeUrl.startsWith(glossaryPage)) && !(option["-B"] || option["-t"] || option["-w"]))
|
||||
const subject = option._.join(" ")
|
||||
const settings = await config.getAsync()
|
||||
let url = ""
|
||||
|
|
@ -1866,7 +1873,7 @@ export async function help(...args: string[]) {
|
|||
if (resolved.includes(helpItem)) break
|
||||
}
|
||||
if (resolved.length > 0) {
|
||||
return browser.runtime.getURL("static/docs/modules/_src_excmds_.html") + "#" + helpItem
|
||||
return excmdPage + "#" + helpItem
|
||||
}
|
||||
return ""
|
||||
},
|
||||
|
|
@ -1880,12 +1887,16 @@ export async function help(...args: string[]) {
|
|||
if (helpItem in bindings) {
|
||||
helpItem = bindings[helpItem].split(" ")
|
||||
helpItem = ["composite", "fillcmdline"].includes(helpItem[0]) ? helpItem[1] : helpItem[0]
|
||||
return browser.runtime.getURL("static/docs/modules/_src_excmds_.html") + "#" + helpItem
|
||||
return excmdPage + "#" + helpItem
|
||||
}
|
||||
}
|
||||
return ""
|
||||
},
|
||||
excmd: (helpItem: string) => browser.runtime.getURL("static/docs/modules/_src_excmds_.html") + "#" + helpItem,
|
||||
excmd: (helpItem: string) => Object.prototype.hasOwnProperty.call(excmdsFunctions, helpItem) ? excmdPage + "#" + helpItem : "",
|
||||
glossary: (helpItem: string) => {
|
||||
const entry = glossary.find(entry => entry.word === helpItem)
|
||||
return entry ? glossaryPage + "#" + encodeURIComponent(entry.anchor) : ""
|
||||
},
|
||||
setting: (helpItem: string) => {
|
||||
let subSettings = settings
|
||||
const settingNames = helpItem.split(".")
|
||||
|
|
@ -1905,22 +1916,27 @@ export async function help(...args: string[]) {
|
|||
}
|
||||
|
||||
if (subject === "") {
|
||||
url = browser.runtime.getURL("static/docs/modules/_src_excmds_.html")
|
||||
url = option["-g"] ? glossaryPage : excmdPage
|
||||
} else {
|
||||
const categoryFlags = ["-a", "-b", "-e", "-s", "-g"].filter(flag => option[flag])
|
||||
if (categoryFlags.length > 1) throw new Error("Only one help category may be selected")
|
||||
// If the user did specify what they wanted, specifically look for it
|
||||
if (option["-a"]) url = strategies.alias(subject)
|
||||
else if (option["-b"]) url = strategies.binding(subject)
|
||||
else if (option["-e"]) url = strategies.excmd(subject)
|
||||
else if (option["-s"]) url = strategies.setting(subject)
|
||||
else if (option["-g"]) url = strategies.glossary(subject)
|
||||
|
||||
// Otherwise or if it couldn't be found, try all possible items
|
||||
if (url === "") {
|
||||
const priority = [strategies.binding, strategies.setting, strategies.alias, strategies.excmd]
|
||||
if (url === "" && categoryFlags.length === 0) {
|
||||
const priority = [strategies.binding, strategies.setting, strategies.alias, strategies.excmd, strategies.glossary]
|
||||
for (const strategy of priority) {
|
||||
url = strategy(subject)
|
||||
if (url !== "") break
|
||||
}
|
||||
}
|
||||
if (url === "" && categoryFlags.length) throw new Error(`No ${categoryFlags[0]} help found for ${subject}`)
|
||||
if (url === "") url = excmdPage + "#" + subject
|
||||
}
|
||||
|
||||
let done
|
||||
|
|
@ -1937,6 +1953,12 @@ export async function help(...args: string[]) {
|
|||
return done.then(() => undefined)
|
||||
}
|
||||
|
||||
/** Look up a term in the glossary. */
|
||||
//#background
|
||||
export async function define(...words: string[]) {
|
||||
return help("-g", ...words)
|
||||
}
|
||||
|
||||
/**
|
||||
* Search through the help pages. Accepts the same flags as [[help]]. Only useful in interactive usage with completions; the command itself is just a wrapper for [[help]].
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -748,6 +748,7 @@ export class default_config {
|
|||
sanitize: "sanitise",
|
||||
"saveas!": "saveas --cleanup --overwrite",
|
||||
tutorial: "tutor",
|
||||
glossary: "define",
|
||||
h: "help",
|
||||
unmute: "mute unmute",
|
||||
authors: "credits",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
<li><a href="./8-marks.html">Marks</a></li>
|
||||
<li><a href="./8-1-i18n.html">Internationalisation</a></li>
|
||||
<li><a href="./9-help.html">Getting help</a></li>
|
||||
<li><a href="./glossary.html">Glossary</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
REPLACETHIS
|
||||
|
|
|
|||
|
|
@ -82,6 +82,37 @@ code {
|
|||
hyphens: none;
|
||||
}
|
||||
|
||||
.glossary-list {
|
||||
margin: 2em 0 0;
|
||||
}
|
||||
|
||||
.glossary-entry {
|
||||
padding: 1em 0.5em 1.1em;
|
||||
border-bottom: 1px solid var(--tridactyl-highlight-box-bg);
|
||||
scroll-margin-top: 1em;
|
||||
}
|
||||
|
||||
.glossary-entry:first-child {
|
||||
border-top: 1px solid var(--tridactyl-highlight-box-bg);
|
||||
}
|
||||
|
||||
.glossary-entry:target {
|
||||
background: var(--tridactyl-highlight-box-bg);
|
||||
}
|
||||
|
||||
.glossary-entry dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.glossary-entry dt code {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.glossary-entry dd {
|
||||
margin: 0.4em 0 0;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
|
|
|
|||
Loading…
Reference in a new issue