diff --git a/.gitignore b/.gitignore index 9ffc58c4..bc2bcdc7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ dist *.tgz tsconfig.bundle.generated.json PureGate +purestack-setup *.vsix .codex/* /.tmp diff --git a/frontend/purestack.studio/guides/_nav.json b/frontend/purestack.studio/guides/_nav.json index 90bedd33..b943bb6f 100644 --- a/frontend/purestack.studio/guides/_nav.json +++ b/frontend/purestack.studio/guides/_nav.json @@ -5,6 +5,7 @@ "components", "purestack-cli.mdx", "site-config.mdx", + "links.mdx", "vscode-extension.mdx", "regor.mdx", "typography.mdx", diff --git a/frontend/purestack.studio/guides/index.mdx b/frontend/purestack.studio/guides/index.mdx index 7d6a0728..4feb75ab 100644 --- a/frontend/purestack.studio/guides/index.mdx +++ b/frontend/purestack.studio/guides/index.mdx @@ -1,6 +1,6 @@ --- title: Guides -description: Learn how to build and style a PureStack site with the CLI, site configuration, VS Code extension, Regor, typography, utility classes, semantic tones, icons, and themes. +description: Learn how to build and style a PureStack site with the CLI, site configuration, links, VS Code extension, Regor, typography, utility classes, semantic tones, icons, and themes. template: doc layout: showNav: true @@ -26,6 +26,11 @@ These guides follow the path from a content folder to a finished interface. Star

Set output paths, navigation, themes, search, metadata, languages, and optional scripts.

Configure a site + +

Links

+

Link pages and sections relative to your file, and let every build check them.

+ Write links +

VS Code extension

Find component source, complete props and frontmatter, and format markup on save.

diff --git a/frontend/purestack.studio/guides/links.mdx b/frontend/purestack.studio/guides/links.mdx new file mode 100644 index 00000000..97619c58 --- /dev/null +++ b/frontend/purestack.studio/guides/links.mdx @@ -0,0 +1,124 @@ +--- +title: Links +description: Link to pages, sections, and other sites. Write links relative to your file, and every build checks that they still work. +template: doc +layout: + showNav: true + showToc: true + showFooter: true +nav: + order: 16 + icon: tabler:link +--- + +# Links + +Write a link the way you see your files. PureStack turns it into the right page address and checks it every time the site builds, so a renamed or deleted page never leaves a broken link behind. + +## Link to another page + +Start from the file you are writing in, just like in your editor's file tree. Take this content folder: + +```text +index.mdx +guides/ + index.mdx + semantic-tones.mdx + themes.mdx + components/ + index.mdx + buttons.mdx +``` + +From `guides/semantic-tones.mdx`, these links work: + +| Write | Opens | +| --- | --- | +| `[Themes](./themes)` | `/guides/themes/`, a page in the same folder | +| `[Buttons](./components/buttons)` | `/guides/components/buttons/`, a page in a subfolder | +| `[Components](./components/)` | `/guides/components/`, the main page of a subfolder | +| `[All guides](./)` | `/guides/`, the main page of this folder | +| `[Home](../)` | `/`, the main page one folder up | + +The file extension is optional. `./themes` and `./themes.mdx` open the same page. + +A folder's main page is its `index.mdx`, or a file named after the folder, such as `components/components.mdx`. Link to the folder, like `./components/`, and you reach it either way. + +## Jump to a section + +Add `#` and the section name after the page: + +```md +[Create a skin](./themes#create-a-skin) +[Back to the top](#links) +``` + +A section name is its heading in lowercase, with hyphens between the words. The heading "Create a skin" becomes `#create-a-skin`. Try it: [create a skin](./themes#create-a-skin). + +PureStack checks that the page exists, not the section, so double-check section names after you rename a heading. + +## Links in components + +The same rule works for `href` on any tag, including components and links they build from a value: + +```mdx +Read about themes + +Next + +Back to home +``` + +Links in a shared header or footer start from that header or footer file, so they work on every page that shows them. + +## Link outside your content + +Anything that starts with `/` or a protocol is used as written: + +| Write | Use it for | +| --- | --- | +| `/blog/` | Pages served from the same domain but built separately | +| `https://github.com/PureStackStudio/PureStack` | Other websites | +| `mailto:hello@example.com` | Email | + +PureStack doesn't check these links, because the pages behind them aren't part of your content folder. + +You can link your own pages this way too, as in `/guides/themes/`, but a relative link is the better habit: if that page moves, the build tells you. + +If the site lives under a base path, such as `/docs`, PureStack adds it to every link for you. Set it once in the [site configuration](./site-config). + +## When a link is broken + +The build stops and names the file and the link: + +```text +Content link "./themse" in "guides/semantic-tones.mdx" does not match any page. +``` + +While `purestack serve` runs, only that page shows the error, and it updates as soon as you fix the link. Most fixes are one of these: + +- **A typo or different capitals.** `./Themes` doesn't find `themes.mdx`. +- **The page moved.** Write the path again, starting from your file. +- **The page is built elsewhere.** Start the path at the site root, such as `/blog/`. + +## Translated sites + +Link pages inside your language folder as usual. If a page isn't translated yet, the link opens the default language's version, so you can translate at your own pace. + +## Quick reference + +| To reach | Write | +| --- | --- | +| A page in the same folder | `./themes` | +| A page in a subfolder | `./components/buttons` | +| A folder's main page | `./components/` | +| The page one folder up | `../` | +| A section of another page | `./themes#create-a-skin` | +| A section of this page | `#quick-reference` | +| A page built separately | `/blog/` | +| Another website | `https://example.com` | + + + Link your own pages relative to your file. Use a full path or address for + everything else. + diff --git a/frontend/purestack.studio/guides/semantic-tones.mdx b/frontend/purestack.studio/guides/semantic-tones.mdx index acfc5558..22332215 100644 --- a/frontend/purestack.studio/guides/semantic-tones.mdx +++ b/frontend/purestack.studio/guides/semantic-tones.mdx @@ -468,4 +468,4 @@ root.select('.my-action:focus-visible').css({ The symbolic reference keeps the style responsive to the nearest tone and theme. Apply `tone--danger` to that action and its focus ring follows the danger button role without another selector. -Continue with [themes and skins](/guides/themes) for palette authoring and [utility classes](/guides/utility-css-classes) for geometry and layout. +Continue with [themes and skins](./themes) for palette authoring and [utility classes](./utility-css-classes) for geometry and layout. diff --git a/package.json b/package.json index fb02492a..d33dc7b1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "purestack-repo", - "version": "1.1.1", + "version": "1.1.2", "packageManager": "yarn@4.18.1", "type": "module", "sideEffects": false, diff --git a/packages/purestack/package.json b/packages/purestack/package.json index df21fa6e..68ead2e9 100644 --- a/packages/purestack/package.json +++ b/packages/purestack/package.json @@ -1,6 +1,6 @@ { "name": "purestack", - "version": "1.1.1", + "version": "1.1.2", "packageManager": "yarn@4.9.2", "module": "./dist/purestack.mjs", "types": "./dist/purestack.d.mts", diff --git a/packages/purestack/src/index.ts b/packages/purestack/src/index.ts index 09e9b440..5598cd9f 100644 --- a/packages/purestack/src/index.ts +++ b/packages/purestack/src/index.ts @@ -1,3 +1,3 @@ -export const version: string = '1.1.1' +export const version: string = '1.1.2' export * from '@purestack/ts-ssg' diff --git a/packages/ts-common/package.json b/packages/ts-common/package.json index b4b370db..92373060 100644 --- a/packages/ts-common/package.json +++ b/packages/ts-common/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-common", - "version": "1.1.1", + "version": "1.1.2", "packageManager": "yarn@4.9.2", "module": "./dist/ts-common.mjs", "types": "./dist/ts-common.d.mts", diff --git a/packages/ts-components/package.json b/packages/ts-components/package.json index cf7a7a90..91cf9d66 100644 --- a/packages/ts-components/package.json +++ b/packages/ts-components/package.json @@ -1,6 +1,6 @@ { "name": "@purestack/ts-components", - "version": "1.1.1", + "version": "1.1.2", "packageManager": "yarn@4.9.2", "module": "./dist/ts-components.mjs", "types": "./dist/ts-components.d.mts", diff --git a/packages/ts-components/src/standard/btn/btn.test.ts b/packages/ts-components/src/standard/btn/btn.test.ts index 19b409c4..1add61e4 100644 --- a/packages/ts-components/src/standard/btn/btn.test.ts +++ b/packages/ts-components/src/standard/btn/btn.test.ts @@ -112,7 +112,8 @@ describe('Button rendering', () => { cleanup() expect(html).toContain('Read docs') diff --git a/packages/ts-components/src/standard/btn/btn.ts b/packages/ts-components/src/standard/btn/btn.ts index 14c2836d..dbd58e94 100644 --- a/packages/ts-components/src/standard/btn/btn.ts +++ b/packages/ts-components/src/standard/btn/btn.ts @@ -1,6 +1,5 @@ import { type TsSsgContext, tryResolveTsSsgContext } from '@purestack/ts-common' import type { SemanticTone } from '@purestack/ts-style' -import { urlNormalizer } from '@purestack/ts-util' import { type ComputedRef, computed, @@ -178,10 +177,9 @@ function resolveShowEndIcon(props: BtnBase) { } function resolveButtonHref(props: BtnLink, context: TsSsgContext | undefined) { - const normalized = urlNormalizer.normalizeHref(unref(props.href)) - return normalized - ? (context?.resolvePublicHref(normalized) ?? normalized) - : undefined + const value = unref(props.href) + const href = typeof value === 'string' ? value.trim() : undefined + return href ? (context?.resolvePublicHref(href) ?? href) : undefined } function resolveButtonRel(props: BtnLink) { diff --git a/packages/ts-components/src/standard/btnGroup/btnGroup.test.ts b/packages/ts-components/src/standard/btnGroup/btnGroup.test.ts index 143d6f37..8b00f4b8 100644 --- a/packages/ts-components/src/standard/btnGroup/btnGroup.test.ts +++ b/packages/ts-components/src/standard/btnGroup/btnGroup.test.ts @@ -32,7 +32,7 @@ describe('Button group rendering', () => { expect(html).toContain('Save') - expect(html).toContain('href="/docs"') + expect(html).toContain('href="./docs"') }) it('renders a native dropdown menu with default trigger affordance', () => { @@ -103,7 +103,7 @@ describe('Button group rendering', () => { `) expect(html).toContain('Default') - expect(html).toContain('href="/settings"') + expect(html).toContain('href="./settings"') expect(html).toContain('Settings') expect(html).toContain('