/** The languages the key bar knows. Anything else gets the general set. */ export type Language = "html" | "css" | "javascript" | "json" | "markdown" | "shell" | "text"; /** A key that types something: a symbol, a pair with the caret inside, or a short snippet. */ export interface TypingKey { readonly label: string; /** The accessible name, when the label alone doesn't say it (pairs, snippets). */ readonly name?: string; readonly text: string; /** Where the caret lands inside `text`; default after it. */ readonly caret?: number; } const EXTENSIONS: Record = { html: "html", htm: "html", svg: "html", xml: "html", vue: "html", svelte: "html", astro: "html", css: "css", scss: "css", less: "css", js: "javascript", mjs: "javascript", cjs: "javascript", jsx: "javascript", ts: "javascript", mts: "javascript", cts: "javascript", tsx: "javascript", json: "json", jsonc: "json", webmanifest: "json", md: "markdown", markdown: "markdown", mdx: "markdown", sh: "shell", bash: "shell", zsh: "shell", }; /** The language of a file, from its extension. */ export function languageOf(path: string): Language { const name = path.slice(path.lastIndexOf("/") + 1); const dot = name.lastIndexOf("."); return (dot > 0 && EXTENSIONS[name.slice(dot + 1).toLowerCase()]) || "text"; } export const LANGUAGE_NAMES: Record = { html: "HTML", css: "CSS", javascript: "JavaScript", json: "JSON", markdown: "Markdown", shell: "Shell", text: "Coding", }; const pair = (open: string, close: string, name: string): TypingKey => ({ label: `${open} ${close}`, name, text: open + close, caret: open.length }); const chars = (list: string): TypingKey[] => [...list].map((char) => ({ label: char, text: char })); /** * The typing keys for each language, most reached-for first, since the row scrolls and the start * is what a thumb finds. These are the keys a phone keyboard buries behind its symbol pages. */ const KEYS: Record = { html: [ pair("<", ">", "Angle brackets"), ...chars("/="), pair('"', '"', "Double quotes"), { label: "", caret: 5 }, ...chars("!-&;#.:'"), pair("{", "}", "Braces"), pair("(", ")", "Parentheses"), ], css: [ pair("{", "}", "Braces"), ...chars(":;#.-"), pair("(", ")", "Parentheses"), ...chars("%!,>*~+@"), pair('"', '"', "Double quotes"), { label: "/*", name: "Comment", text: "/* */", caret: 3 }, ], javascript: [ pair("(", ")", "Parentheses"), pair("{", "}", "Braces"), pair("[", "]", "Brackets"), { label: "=>", name: "Arrow", text: " => " }, ...chars(";:=.,"), pair('"', '"', "Double quotes"), pair("'", "'", "Single quotes"), pair("`", "`", "Backticks"), ...chars("!?&|<>+-*/$_"), { label: "//", name: "Comment", text: "// " }, ], json: [ pair("{", "}", "Braces"), pair("[", "]", "Brackets"), pair('"', '"', "Double quotes"), ...chars(":,-."), ], markdown: [ ...chars("#*_`"), pair("[", "]", "Brackets"), pair("(", ")", "Parentheses"), ...chars("->!|~"), { label: "```", name: "Code fence", text: "```\n\n```", caret: 4 }, ], shell: [...chars("|>&;$~/-.*<"), pair('"', '"', "Double quotes"), pair("'", "'", "Single quotes"), ...chars("`=")], text: [ pair("(", ")", "Parentheses"), pair("{", "}", "Braces"), pair("[", "]", "Brackets"), pair("<", ">", "Angle brackets"), ...chars("\"'`;:=/\\|&!?_-+*#$%"), ], }; export function typingKeys(language: Language): readonly TypingKey[] { return KEYS[language]; }