diff --git a/packages/app-web-docs/package.json b/packages/app-web-docs/package.json index f581bfbbf6..6b285ef9d4 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 1a29caa5ff..a9df5fcbba 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 e408256ab8..d9063fac6b 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 9aac6c92fe..4a8dee65a6 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 692eff96e6..5ca134958e 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 493029fe8d..53940167e3 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 de38a94859..5cd239ab0f 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 b6877b64fd..c19f16d8a8 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 14bc0ffae8..e00e8f0a9c 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 33ff98d9db..e1d64f5f12 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 1b181826ed..e9e93884f1 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 ca7a2d12e7..508441e030 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 0898b7d7b5..6ac106b144 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 3ae59254cd..95e5affe5c 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 7e8da1d81d..1c56b92f8b 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 e66f12d405..2f77e65267 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 547d9392ee..a6cf7e215c 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 f892d58459..de5cc50cf2 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 bc1ec70d2f..02e9c69ffa 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 141507d33d..9342f18f50 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 c9e75a09aa..43efcfd749 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 f9ccaeb4f5..199100988e 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 0fc9c0f9d8..7e50da3cf5 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 d4c9561d0b..979e294e33 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 b81a227cbb..acf78fa9f0 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 4eb465f8fb..79a59cc3ea 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 85e7416a48..0df94fc9a4 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 b9ce2dfcda..35550d94d4 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 200aa37b66..616816d2e9 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 1ce163c613..39f526768e 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 d478c3a809..e746e54fb1 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 c9232c3343..68714d31b4 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 0000000000..4966a312fd --- /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 a8f69878d5..33873c5c73 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 db2a52f70b..e5b6193696 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 a672046ef2..6dba4ea2d4 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 {