From c831fdbdc68aefccad8a6a4d5ad2308a5de38eda Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Wed, 2 Sep 2026 14:40:09 +0200 Subject: [PATCH] Added `sidebar_group` frontmatter --- packages/app-web-docs/package.json | 1 + .../app-web-docs/sidebarItemsGenerator.ts | 221 ++++++++++-------- packages/app-web-docs/src/docs/user/README.md | 1 + .../src/docs/user/actions/_category_.json | 7 +- .../src/docs/user/alphabet-and-symbols.md | 5 + .../src/docs/user/custom-ide-actions.md | 5 + .../src/docs/user/custom-regex-scopes.md | 5 + .../src/docs/user/customization.md | 5 + .../src/docs/user/destinations.md | 5 + .../src/docs/user/draft-editor.md | 5 + .../docs/user/experimental/_category_.json | 6 +- .../app-web-docs/src/docs/user/glossary.md | 5 + .../src/docs/user/hat-assignment.md | 5 + packages/app-web-docs/src/docs/user/how-to.md | 5 + .../src/docs/user/installation.md | 6 + .../src/docs/user/languages/README.md | 4 + .../src/docs/user/learning-resources.md | 7 +- .../src/docs/user/local-command-history.md | 5 + .../src/docs/user/modifiers/_category_.json | 6 +- .../src/docs/user/paired-delimiters.md | 5 + .../src/docs/user/public-talon-api.md | 5 + .../src/docs/user/recommendations.md | 5 + .../docs/user/release-notes/_category_.json | 6 +- .../src/docs/user/scope-visualizer.md | 5 + .../src/docs/user/scopes/_category_.json | 6 +- .../user/{scope-sidebar.md => sidebar.md} | 6 + .../src/docs/user/talon-dependencies.md | 6 + .../app-web-docs/src/docs/user/targets.md | 5 + .../src/docs/user/troubleshooting.md | 5 + .../src/docs/user/unicode-support.md | 5 + .../app-web-docs/src/docs/user/updating.md | 6 + .../src/docs/user/visual-accessibility.md | 5 + .../src/sidebarItemsGenerator.test.ts | 142 +++++++++++ packages/app-web-docs/tsconfig.json | 1 + .../src/updateGraphemeDefaultSpokenFormsMd.ts | 5 + .../src/updatePairedDelimitersMd.ts | 14 +- 36 files changed, 432 insertions(+), 109 deletions(-) rename packages/app-web-docs/src/docs/user/{scope-sidebar.md => sidebar.md} (96%) create mode 100644 packages/app-web-docs/src/sidebarItemsGenerator.test.ts diff --git a/packages/app-web-docs/package.json b/packages/app-web-docs/package.json index f581bfbbf63..6b285ef9d43 100644 --- a/packages/app-web-docs/package.json +++ b/packages/app-web-docs/package.json @@ -19,6 +19,7 @@ "scripts": { "typecheck": "tsc", "clean": "docusaurus clear && rm -rf ./out ./dist ./tsconfig.tsbuildinfo", + "test": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --experimental-strip-types --test src/sidebarItemsGenerator.test.ts", "dev": "docusaurus start", "build": "docusaurus build --out-dir out" }, diff --git a/packages/app-web-docs/sidebarItemsGenerator.ts b/packages/app-web-docs/sidebarItemsGenerator.ts index 1a29caa5fff..a9df5fcbbaa 100644 --- a/packages/app-web-docs/sidebarItemsGenerator.ts +++ b/packages/app-web-docs/sidebarItemsGenerator.ts @@ -3,65 +3,12 @@ import type * as ClassicPreset from "@docusaurus/preset-classic"; type DocsOptions = Exclude; type SidebarItemsGenerator = NonNullable; -interface Group { - label: string; - items: string[]; -} - -const items: (Group | string)[] = [ - "Cursorless", - { - label: "Getting started", - items: [ - "Installation", - "Recommendations", - "Learning resources", - "How-to guides", - "Troubleshooting", - ], - }, - { - label: "Reference", - items: [ - "Actions", - "Modifiers", - "Scopes", - "Targets", - "Destinations", - "Paired delimiters", - "Alphabet and symbols", - ], - }, - "Supported languages", - { - label: "Customization", - items: [ - "Customization", - "Hat assignment", - "Visual Accessibility", - "Custom IDE actions", - "Custom regex scopes", - "Public Talon API", - ], - }, - { - label: "Tools", - items: [ - "Cursorless sidebar", - "Scope visualizer", - "Local command history", - "Draft editor", - ], - }, - { - label: "Advanced", - items: ["Talon dependencies", "Unicode support", "Experimental features"], - }, - { - label: "Project", - items: ["Updating", "Glossary", "Release notes"], - }, -]; +/** + * Set directly in Markdown front matter. For `_category_.json`, set it under + * `customProps`, which is Docusaurus's supported category metadata extension. + */ +const sidebarGroupKey = "sidebar_group"; +const sidebarGroupPositionKey = "sidebar_group_position"; export const sidebarItemsGenerator: SidebarItemsGenerator = async ({ defaultSidebarItemsGenerator, @@ -93,6 +40,60 @@ export const sidebarItemsGenerator: SidebarItemsGenerator = async ({ return docId != null && docsById.get(docId)?.sidebarPosition != null; } + function getSidebarGroup(item: NormalizedSidebarItem): string | undefined { + let sidebarGroup: unknown; + if (item.type === "doc" || item.type === "ref") { + sidebarGroup = docsById.get(item.id)?.frontMatter[sidebarGroupKey]; + } else if (item.type === "category") { + sidebarGroup = item.customProps?.[sidebarGroupKey]; + } + if (sidebarGroup === undefined) { + return undefined; + } + if (typeof sidebarGroup !== "string" || sidebarGroup.trim().length === 0) { + throw new Error( + `Invalid ${sidebarGroupKey} for sidebar item '${getLabel(item)}'`, + ); + } + return sidebarGroup.trim(); + } + + function getSidebarGroupPosition( + item: NormalizedSidebarItem, + ): number | undefined { + let sidebarGroupPosition: unknown; + if (item.type === "doc" || item.type === "ref") { + sidebarGroupPosition = docsById.get(item.id)?.frontMatter[ + sidebarGroupPositionKey + ]; + } else if (item.type === "category") { + sidebarGroupPosition = item.customProps?.[sidebarGroupPositionKey]; + } + if (sidebarGroupPosition === undefined) { + return undefined; + } + if ( + typeof sidebarGroupPosition !== "number" || + !Number.isFinite(sidebarGroupPosition) + ) { + // oxlint-disable-next-line unicorn/prefer-type-error + throw new Error( + `Invalid ${sidebarGroupPositionKey} for sidebar item '${getLabel(item)}'`, + ); + } + return sidebarGroupPosition; + } + + function getSidebarPosition(item: NormalizedSidebarItem): number | undefined { + let docId: string | undefined; + if (item.type === "doc" || item.type === "ref") { + docId = item.id; + } else if (item.type === "category" && item.link?.type === "doc") { + docId = item.link.id; + } + return docId == null ? undefined : docsById.get(docId)?.sidebarPosition; + } + function sortByLabel( items: NormalizedSidebarItem[], ): NormalizedSidebarItem[] { @@ -122,36 +123,25 @@ export const sidebarItemsGenerator: SidebarItemsGenerator = async ({ }); } - const sortedItems = sortByLabel(generatedItems); - if (args.item.dirName !== "user") { - return sortedItems; - } - - const remainingItems = sortedItems.slice(); - - function takeItem(label: string): NormalizedSidebarItem { - const index = remainingItems.findIndex( - (item) => getLabel(item)?.toLowerCase() === label.toLowerCase(), - ); - const item = remainingItems[index]; - if (item == null) { - throw new Error(`Missing generated sidebar item '${label}'`); - } - remainingItems.splice(index, 1); - return item; + return sortByLabel(generatedItems); } - function createGroup(group: Group): NormalizedSidebarItem { - const items = group.items.map(takeItem); + function createGroup( + label: string, + groupItems: NormalizedSidebarItem[], + ): NormalizedSidebarItem { + const items = groupItems.slice(); const linkIndex = items.findIndex( - (item) => item.type === "doc" && getLabel(item) === group.label, + (item) => + item.type === "doc" && + getLabel(item)?.toLowerCase() === label.toLowerCase(), ); const [link] = linkIndex === -1 ? [] : items.splice(linkIndex, 1); return { type: "category", - label: group.label, + label, collapsible: false, collapsed: false, ...(link?.type === "doc" ? { link: { type: "doc", id: link.id } } : {}), @@ -159,27 +149,58 @@ export const sidebarItemsGenerator: SidebarItemsGenerator = async ({ }; } - const groupedItems = items.map((item): NormalizedSidebarItem => { - if (typeof item === "string") { - return takeItem(item); + const itemsByGroup = new Map(); + const positionsByGroup = new Map(); + const ungroupedItems: NormalizedSidebarItem[] = []; + for (const item of generatedItems) { + const sidebarGroup = getSidebarGroup(item); + const sidebarGroupPosition = getSidebarGroupPosition(item); + if (sidebarGroup == null) { + if (sidebarGroupPosition != null) { + throw new Error( + `${sidebarGroupPositionKey} requires ${sidebarGroupKey} for sidebar item '${getLabel(item)}'`, + ); + } + ungroupedItems.push(item); + continue; } - return createGroup(item); - }); - - if (remainingItems.length > 0) { - const remainingJson = remainingItems - .map((item) => { - if (item.type === "category") { - const { items, ...rest } = item; - return JSON.stringify(rest); - } - return JSON.stringify(item); - }) - .join(", "); - throw new Error( - `Unassigned generated user sidebar items: ${remainingJson}`, - ); + + if (sidebarGroupPosition != null) { + const existingPosition = positionsByGroup.get(sidebarGroup); + if ( + existingPosition != null && + existingPosition !== sidebarGroupPosition + ) { + throw new Error( + `Conflicting ${sidebarGroupPositionKey} values for sidebar group '${sidebarGroup}': ${existingPosition} and ${sidebarGroupPosition}`, + ); + } + positionsByGroup.set(sidebarGroup, sidebarGroupPosition); + } + + const groupItems = itemsByGroup.get(sidebarGroup) ?? []; + groupItems.push(item); + itemsByGroup.set(sidebarGroup, groupItems); + } + + const topLevelItems = sortByLabel(ungroupedItems).map((item) => ({ + item, + position: getSidebarPosition(item), + })); + for (const [label, groupItems] of itemsByGroup) { + topLevelItems.push({ + item: createGroup(label, sortByLabel(groupItems)), + position: positionsByGroup.get(label), + }); } - return groupedItems; + return topLevelItems + .toSorted((a, b) => { + const aPosition = a.position ?? Number.POSITIVE_INFINITY; + const bPosition = b.position ?? Number.POSITIVE_INFINITY; + return aPosition === bPosition + ? (getLabel(a.item) ?? "").localeCompare(getLabel(b.item) ?? "") + : aPosition - bPosition; + }) + .map(({ item }) => item); }; diff --git a/packages/app-web-docs/src/docs/user/README.md b/packages/app-web-docs/src/docs/user/README.md index e408256ab88..d9063fac6b4 100644 --- a/packages/app-web-docs/src/docs/user/README.md +++ b/packages/app-web-docs/src/docs/user/README.md @@ -1,5 +1,6 @@ --- slug: / +sidebar_position: 1 --- # Cursorless diff --git a/packages/app-web-docs/src/docs/user/actions/_category_.json b/packages/app-web-docs/src/docs/user/actions/_category_.json index 9aac6c92fec..4a8dee65a62 100644 --- a/packages/app-web-docs/src/docs/user/actions/_category_.json +++ b/packages/app-web-docs/src/docs/user/actions/_category_.json @@ -1,3 +1,8 @@ { - "label": "Actions" + "label": "Actions", + "position": 1, + "customProps": { + "sidebar_group": "Reference", + "sidebar_group_position": 3 + } } diff --git a/packages/app-web-docs/src/docs/user/alphabet-and-symbols.md b/packages/app-web-docs/src/docs/user/alphabet-and-symbols.md index 692eff96e69..5ca134958e2 100644 --- a/packages/app-web-docs/src/docs/user/alphabet-and-symbols.md +++ b/packages/app-web-docs/src/docs/user/alphabet-and-symbols.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Reference +sidebar_position: 7 +--- + # Alphabet and symbols Cursorless uses the [Talon Community](https://github.com/talonhub/community) alphabet, digits, and symbol spoken forms via the [`user.any_alphanumeric_key`](https://github.com/talonhub/community/blob/607c3415f5f29a5f75db6fe5648e37f514f62ac5/core/keys/keys.py#L71-L74) capture. diff --git a/packages/app-web-docs/src/docs/user/custom-ide-actions.md b/packages/app-web-docs/src/docs/user/custom-ide-actions.md index 493029fe8d1..53940167e37 100644 --- a/packages/app-web-docs/src/docs/user/custom-ide-actions.md +++ b/packages/app-web-docs/src/docs/user/custom-ide-actions.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Customization +sidebar_position: 3 +--- + # Custom IDE actions You can use Cursorless to run any built-in IDE command on a specific target. diff --git a/packages/app-web-docs/src/docs/user/custom-regex-scopes.md b/packages/app-web-docs/src/docs/user/custom-regex-scopes.md index de38a94859a..5cd239ab0f0 100644 --- a/packages/app-web-docs/src/docs/user/custom-regex-scopes.md +++ b/packages/app-web-docs/src/docs/user/custom-regex-scopes.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Customization +sidebar_position: 4 +--- + # Custom regex scopes You can define custom scopes using regexes in `user/cursorless-settings/experimental/regex_scope_types.csv` diff --git a/packages/app-web-docs/src/docs/user/customization.md b/packages/app-web-docs/src/docs/user/customization.md index b6877b64fda..c19f16d8a8e 100644 --- a/packages/app-web-docs/src/docs/user/customization.md +++ b/packages/app-web-docs/src/docs/user/customization.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Customization +sidebar_group_position: 5 +--- + # Customization Cursorless consists of diff --git a/packages/app-web-docs/src/docs/user/destinations.md b/packages/app-web-docs/src/docs/user/destinations.md index 14bc0ffae8e..e00e8f0a9c7 100644 --- a/packages/app-web-docs/src/docs/user/destinations.md +++ b/packages/app-web-docs/src/docs/user/destinations.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Reference +sidebar_position: 5 +--- + # Destinations Some actions expect a destination, rather than a target. Destinations combine a preposition and a target, to define a place and behavior for a Cursorless action. diff --git a/packages/app-web-docs/src/docs/user/draft-editor.md b/packages/app-web-docs/src/docs/user/draft-editor.md index 33ff98d9dbf..e1d64f5f125 100644 --- a/packages/app-web-docs/src/docs/user/draft-editor.md +++ b/packages/app-web-docs/src/docs/user/draft-editor.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Tools +sidebar_position: 4 +--- + # Draft editor The draft editor is a [Talon Community](https://github.com/talonhub/community) plugin that lets you revise text in a full text editor before inserting it diff --git a/packages/app-web-docs/src/docs/user/experimental/_category_.json b/packages/app-web-docs/src/docs/user/experimental/_category_.json index 1b181826ed7..e9e93884f10 100644 --- a/packages/app-web-docs/src/docs/user/experimental/_category_.json +++ b/packages/app-web-docs/src/docs/user/experimental/_category_.json @@ -1,3 +1,7 @@ { - "label": "Experimental features" + "label": "Experimental features", + "position": 3, + "customProps": { + "sidebar_group": "Advanced" + } } diff --git a/packages/app-web-docs/src/docs/user/glossary.md b/packages/app-web-docs/src/docs/user/glossary.md index ca7a2d12e7f..508441e0304 100644 --- a/packages/app-web-docs/src/docs/user/glossary.md +++ b/packages/app-web-docs/src/docs/user/glossary.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Project +sidebar_position: 2 +--- + # Glossary Here we provide definitions for several commonly used Cursorless terms. diff --git a/packages/app-web-docs/src/docs/user/hat-assignment.md b/packages/app-web-docs/src/docs/user/hat-assignment.md index 0898b7d7b57..6ac106b1448 100644 --- a/packages/app-web-docs/src/docs/user/hat-assignment.md +++ b/packages/app-web-docs/src/docs/user/hat-assignment.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Customization +sidebar_position: 1 +--- + # Hat assignment Every time you move your cursor, edit a document, or scroll, Cursorless assigns hats to the tokens in each visible editor. When selecting hats, Cursorless attempts to give "good" hats to tokens near your cursor, while at the same time keeping hats from moving around too much. diff --git a/packages/app-web-docs/src/docs/user/how-to.md b/packages/app-web-docs/src/docs/user/how-to.md index 3ae59254cd2..95e5affe5ca 100644 --- a/packages/app-web-docs/src/docs/user/how-to.md +++ b/packages/app-web-docs/src/docs/user/how-to.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Getting started +sidebar_position: 4 +--- + # How-to guides This section contains some how-to guides for common tasks. diff --git a/packages/app-web-docs/src/docs/user/installation.md b/packages/app-web-docs/src/docs/user/installation.md index 7e8da1d81d7..1c56b92f8bb 100644 --- a/packages/app-web-docs/src/docs/user/installation.md +++ b/packages/app-web-docs/src/docs/user/installation.md @@ -1,3 +1,9 @@ +--- +sidebar_group: Getting started +sidebar_group_position: 2 +sidebar_position: 1 +--- + # Installation 1. Install [Talon](https://talonvoice.com/) diff --git a/packages/app-web-docs/src/docs/user/languages/README.md b/packages/app-web-docs/src/docs/user/languages/README.md index e66f12d405e..2f77e652674 100644 --- a/packages/app-web-docs/src/docs/user/languages/README.md +++ b/packages/app-web-docs/src/docs/user/languages/README.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 4 +--- + # Supported languages Cursorless supports a wide range of languages, though our level of support varies a bit diff --git a/packages/app-web-docs/src/docs/user/learning-resources.md b/packages/app-web-docs/src/docs/user/learning-resources.md index 547d9392eed..a6cf7e215c6 100644 --- a/packages/app-web-docs/src/docs/user/learning-resources.md +++ b/packages/app-web-docs/src/docs/user/learning-resources.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Getting started +sidebar_position: 3 +--- + # Learning resources These resources can help you learn Cursorless, from your first commands through more advanced workflows. @@ -5,7 +10,7 @@ These resources can help you learn Cursorless, from your first commands through ## Start here - [Cursorless tutorial videos](https://www.youtube.com/watch?v=5mAzHGM2M0k&list=PLXv2sppxeoQZz49evjy4T0QJRIgc_JPqs) - Learn the basic concepts and commands. -- [Interactive tutorial](./scope-sidebar.md#tutorial) - Learn Cursorless inside your editor using the Cursorless sidebar. +- [Interactive tutorial](./sidebar.md#tutorial) - Learn Cursorless inside your editor using the Cursorless sidebar. ## Learn from examples diff --git a/packages/app-web-docs/src/docs/user/local-command-history.md b/packages/app-web-docs/src/docs/user/local-command-history.md index f892d584591..de5cc50cf20 100644 --- a/packages/app-web-docs/src/docs/user/local-command-history.md +++ b/packages/app-web-docs/src/docs/user/local-command-history.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Tools +sidebar_position: 3 +--- + # Local command history By default, Cursorless doesn't capture anything about your usage. However, we do have a way to opt in to a local, sanitized command history. This history is never sent to our servers, and any commands that may contain text will be sanitized. diff --git a/packages/app-web-docs/src/docs/user/modifiers/_category_.json b/packages/app-web-docs/src/docs/user/modifiers/_category_.json index bc1ec70d2f1..02e9c69ffa9 100644 --- a/packages/app-web-docs/src/docs/user/modifiers/_category_.json +++ b/packages/app-web-docs/src/docs/user/modifiers/_category_.json @@ -1,3 +1,7 @@ { - "label": "Modifiers" + "label": "Modifiers", + "position": 2, + "customProps": { + "sidebar_group": "Reference" + } } diff --git a/packages/app-web-docs/src/docs/user/paired-delimiters.md b/packages/app-web-docs/src/docs/user/paired-delimiters.md index 141507d33dc..9342f18f502 100644 --- a/packages/app-web-docs/src/docs/user/paired-delimiters.md +++ b/packages/app-web-docs/src/docs/user/paired-delimiters.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Reference +sidebar_position: 6 +--- + # Paired delimiters | Default spoken form | Delimiter name | Symbol inserted before target | Symbol inserted after target | Is wrapper? | Is selectable? | diff --git a/packages/app-web-docs/src/docs/user/public-talon-api.md b/packages/app-web-docs/src/docs/user/public-talon-api.md index c9e75a09aa6..43efcfd7493 100644 --- a/packages/app-web-docs/src/docs/user/public-talon-api.md +++ b/packages/app-web-docs/src/docs/user/public-talon-api.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Customization +sidebar_position: 5 +--- + # Public Talon API Cursorless exposes Talon actions and captures that you can use to define custom command grammars with Cursorless targets. diff --git a/packages/app-web-docs/src/docs/user/recommendations.md b/packages/app-web-docs/src/docs/user/recommendations.md index f9ccaeb4f55..199100988e6 100644 --- a/packages/app-web-docs/src/docs/user/recommendations.md +++ b/packages/app-web-docs/src/docs/user/recommendations.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Getting started +sidebar_position: 2 +--- + # Recommendations These optional recommendations can improve your Cursorless workflow but are not required by Cursorless. diff --git a/packages/app-web-docs/src/docs/user/release-notes/_category_.json b/packages/app-web-docs/src/docs/user/release-notes/_category_.json index 0fc9c0f9d82..7e50da3cf56 100644 --- a/packages/app-web-docs/src/docs/user/release-notes/_category_.json +++ b/packages/app-web-docs/src/docs/user/release-notes/_category_.json @@ -1,3 +1,7 @@ { - "label": "Release notes" + "label": "Release notes", + "position": 3, + "customProps": { + "sidebar_group": "Project" + } } diff --git a/packages/app-web-docs/src/docs/user/scope-visualizer.md b/packages/app-web-docs/src/docs/user/scope-visualizer.md index d4c9561d0b8..979e294e33a 100644 --- a/packages/app-web-docs/src/docs/user/scope-visualizer.md +++ b/packages/app-web-docs/src/docs/user/scope-visualizer.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Tools +sidebar_position: 2 +--- + # Scope visualizer The scope visualizer allows you to visualize Cursorless scopes on your code in real time. It is useful to understand how Cursorless scopes work, and is also useful for Cursorless contributors as they develop new scopes. You may find the [scope visualizer intro video](https://youtu.be/BbXEzUrf5lU) helpful. diff --git a/packages/app-web-docs/src/docs/user/scopes/_category_.json b/packages/app-web-docs/src/docs/user/scopes/_category_.json index b81a227cbb2..acf78fa9f02 100644 --- a/packages/app-web-docs/src/docs/user/scopes/_category_.json +++ b/packages/app-web-docs/src/docs/user/scopes/_category_.json @@ -1,3 +1,7 @@ { - "label": "Scopes" + "label": "Scopes", + "position": 3, + "customProps": { + "sidebar_group": "Reference" + } } diff --git a/packages/app-web-docs/src/docs/user/scope-sidebar.md b/packages/app-web-docs/src/docs/user/sidebar.md similarity index 96% rename from packages/app-web-docs/src/docs/user/scope-sidebar.md rename to packages/app-web-docs/src/docs/user/sidebar.md index 4eb465f8fb5..79a59cc3ea4 100644 --- a/packages/app-web-docs/src/docs/user/scope-sidebar.md +++ b/packages/app-web-docs/src/docs/user/sidebar.md @@ -1,3 +1,9 @@ +--- +sidebar_group: Tools +sidebar_group_position: 6 +sidebar_position: 1 +--- + # Cursorless sidebar You can say `"bar cursorless"` to show the Cursorless sidebar. diff --git a/packages/app-web-docs/src/docs/user/talon-dependencies.md b/packages/app-web-docs/src/docs/user/talon-dependencies.md index 85e7416a483..0df94fc9a45 100644 --- a/packages/app-web-docs/src/docs/user/talon-dependencies.md +++ b/packages/app-web-docs/src/docs/user/talon-dependencies.md @@ -1,3 +1,9 @@ +--- +sidebar_group: Advanced +sidebar_group_position: 7 +sidebar_position: 1 +--- + # Talon dependencies Cursorless uses several definitions from [Talon Community](https://github.com/talonhub/community). If your Talon setup includes Talon Community and you have not removed any of the dependencies listed below, you can safely ignore this page. diff --git a/packages/app-web-docs/src/docs/user/targets.md b/packages/app-web-docs/src/docs/user/targets.md index b9ce2dfcda2..35550d94d47 100644 --- a/packages/app-web-docs/src/docs/user/targets.md +++ b/packages/app-web-docs/src/docs/user/targets.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Reference +sidebar_position: 4 +--- + # Targets There are two types of targets: primitive targets and compound targets. Compound targets are constructed from primitive targets, so let's begin with primitive targets. diff --git a/packages/app-web-docs/src/docs/user/troubleshooting.md b/packages/app-web-docs/src/docs/user/troubleshooting.md index 200aa37b661..616816d2e95 100644 --- a/packages/app-web-docs/src/docs/user/troubleshooting.md +++ b/packages/app-web-docs/src/docs/user/troubleshooting.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Getting started +sidebar_position: 5 +--- + # Troubleshooting Here is a list of common issues you might encounter, along with possible solutions: diff --git a/packages/app-web-docs/src/docs/user/unicode-support.md b/packages/app-web-docs/src/docs/user/unicode-support.md index 1ce163c613f..39f526768ee 100644 --- a/packages/app-web-docs/src/docs/user/unicode-support.md +++ b/packages/app-web-docs/src/docs/user/unicode-support.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Advanced +sidebar_position: 2 +--- + # Unicode support Cursorless has first-class support for Unicode. By default, when constructing hats, Cursorless will ignore capitalization and any accents or diacritics over letters. For example, each of the following four tokens could each be selected by saying `"take air"` if there were a gray hat over their first letter (note the accents on the first letter for some of them): diff --git a/packages/app-web-docs/src/docs/user/updating.md b/packages/app-web-docs/src/docs/user/updating.md index d478c3a809f..e746e54fb14 100644 --- a/packages/app-web-docs/src/docs/user/updating.md +++ b/packages/app-web-docs/src/docs/user/updating.md @@ -1,3 +1,9 @@ +--- +sidebar_group: Project +sidebar_group_position: 8 +sidebar_position: 1 +--- + # Updating Cursorless consists of two components: diff --git a/packages/app-web-docs/src/docs/user/visual-accessibility.md b/packages/app-web-docs/src/docs/user/visual-accessibility.md index c9232c33433..68714d31b42 100644 --- a/packages/app-web-docs/src/docs/user/visual-accessibility.md +++ b/packages/app-web-docs/src/docs/user/visual-accessibility.md @@ -1,3 +1,8 @@ +--- +sidebar_group: Customization +sidebar_position: 2 +--- + # Visual Accessibility Cursorless has multiple settings that can be customized to improve accessibility for users with color blindness or other vision impairments. The primary visual elements of Cursorless are the [hats](./targets.md#decorated-symbol), so this guide will focus on customizing the hats. diff --git a/packages/app-web-docs/src/sidebarItemsGenerator.test.ts b/packages/app-web-docs/src/sidebarItemsGenerator.test.ts new file mode 100644 index 00000000000..4966a312fdf --- /dev/null +++ b/packages/app-web-docs/src/sidebarItemsGenerator.test.ts @@ -0,0 +1,142 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { sidebarItemsGenerator } from "../sidebarItemsGenerator.ts"; + +type GeneratorArgs = Parameters[0]; + +test("groups sidebar items using document and category metadata", async () => { + const generatedItems = [ + { type: "doc", id: "user/index" }, + { type: "doc", id: "user/install" }, + { + type: "category", + label: "Actions", + collapsible: true, + collapsed: true, + customProps: { + sidebar_group: "Reference", + sidebar_group_position: 3, + }, + items: [], + }, + { type: "doc", id: "user/customization" }, + { type: "doc", id: "user/new-page" }, + ]; + const docs = [ + { + id: "user/index", + title: "Cursorless", + frontMatter: {}, + sidebarPosition: 1, + source: "", + sourceDirName: "user", + }, + { + id: "user/install", + title: "Installation", + frontMatter: { + sidebar_group: "Getting started", + sidebar_group_position: 2, + }, + source: "", + sourceDirName: "user", + }, + { + id: "user/customization", + title: "Customization", + frontMatter: { + sidebar_group: "Customization", + sidebar_group_position: 4, + }, + source: "", + sourceDirName: "user", + }, + { + id: "user/new-page", + title: "New page", + frontMatter: {}, + source: "", + sourceDirName: "user", + }, + ]; + const args = { + defaultSidebarItemsGenerator: () => generatedItems, + item: { type: "autogenerated", dirName: "user" }, + docs, + categoriesMetadata: {}, + numberPrefixParser: () => ({}), + isCategoryIndex: () => false, + version: { versionName: "current", contentPath: "" }, + } as unknown as GeneratorArgs; + + const result = await sidebarItemsGenerator(args); + + assert.deepEqual( + result.map( + (item) => + ("label" in item ? item.label : undefined) ?? + docs.find((doc) => doc.id === ("id" in item ? item.id : undefined)) + ?.title, + ), + ["Cursorless", "Getting started", "Reference", "Customization", "New page"], + ); + + const gettingStarted = result[1]; + assert.equal(gettingStarted.type, "category"); + assert.deepEqual(gettingStarted.items, [{ type: "doc", id: "user/install" }]); + + const reference = result[2]; + assert.equal(reference.type, "category"); + assert.deepEqual(reference.items, [generatedItems[2]]); + + const customization = result[3]; + assert.equal(customization.type, "category"); + assert.deepEqual(customization.link, { + type: "doc", + id: "user/customization", + }); + assert.deepEqual(customization.items, []); +}); + +test("rejects conflicting positions for the same sidebar group", async () => { + const generatedItems = [ + { type: "doc", id: "user/first" }, + { type: "doc", id: "user/second" }, + ]; + const docs = [ + { + id: "user/first", + title: "First", + frontMatter: { + sidebar_group: "Reference", + sidebar_group_position: 1, + }, + source: "", + sourceDirName: "user", + }, + { + id: "user/second", + title: "Second", + frontMatter: { + sidebar_group: "Reference", + sidebar_group_position: 2, + }, + source: "", + sourceDirName: "user", + }, + ]; + const args = { + defaultSidebarItemsGenerator: () => generatedItems, + item: { type: "autogenerated", dirName: "user" }, + docs, + categoriesMetadata: {}, + numberPrefixParser: () => ({}), + isCategoryIndex: () => false, + version: { versionName: "current", contentPath: "" }, + } as unknown as GeneratorArgs; + + await assert.rejects( + async () => await sidebarItemsGenerator(args), + /Conflicting sidebar_group_position values for sidebar group 'Reference': 1 and 2/u, + ); +}); diff --git a/packages/app-web-docs/tsconfig.json b/packages/app-web-docs/tsconfig.json index a8f69878d5f..33873c5c734 100644 --- a/packages/app-web-docs/tsconfig.json +++ b/packages/app-web-docs/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.web.json", "compilerOptions": { + "allowImportingTsExtensions": true, "jsx": "react-jsx", "jsxImportSource": "react", "skipLibCheck": true, diff --git a/packages/tool-meta-updater/src/updateGraphemeDefaultSpokenFormsMd.ts b/packages/tool-meta-updater/src/updateGraphemeDefaultSpokenFormsMd.ts index db2a52f70bc..e5b61936965 100644 --- a/packages/tool-meta-updater/src/updateGraphemeDefaultSpokenFormsMd.ts +++ b/packages/tool-meta-updater/src/updateGraphemeDefaultSpokenFormsMd.ts @@ -28,6 +28,11 @@ export function updateGraphemeDefaultSpokenFormsMd( } return [ + "---", + "sidebar_group: Reference", + "sidebar_position: 7", + "---", + "", "# Alphabet and symbols", "", "Cursorless uses the [Talon Community](https://github.com/talonhub/community) alphabet, digits, and symbol spoken forms via the [`user.any_alphanumeric_key`](https://github.com/talonhub/community/blob/607c3415f5f29a5f75db6fe5648e37f514f62ac5/core/keys/keys.py#L71-L74) capture.", diff --git a/packages/tool-meta-updater/src/updatePairedDelimitersMd.ts b/packages/tool-meta-updater/src/updatePairedDelimitersMd.ts index a672046ef26..6dba4ea2d43 100644 --- a/packages/tool-meta-updater/src/updatePairedDelimitersMd.ts +++ b/packages/tool-meta-updater/src/updatePairedDelimitersMd.ts @@ -40,9 +40,17 @@ export function updatePairedDelimitersMd( ]; }); - return ["# Paired delimiters", "", ...formatTable(HEADERS, rows), ""].join( - "\n", - ); + return [ + "---", + "sidebar_group: Reference", + "sidebar_position: 6", + "---", + "", + "# Paired delimiters", + "", + ...formatTable(HEADERS, rows), + "", + ].join("\n"); } function code(value: string): string {