diff --git a/site/src/content/docs/getting-started/migration.mdx b/site/src/content/docs/getting-started/migration.mdx index 80cee40331..3c050ecda6 100644 --- a/site/src/content/docs/getting-started/migration.mdx +++ b/site/src/content/docs/getting-started/migration.mdx @@ -17,7 +17,7 @@ toc: true ``` -## v1.4.0 +## v1.4.0 [[version-changelog-link]]
@@ -115,7 +115,7 @@ toc: true -## v1.3.0 +## v1.3.0 [[version-changelog-link]]
@@ -155,7 +155,7 @@ toc: true 1. Change the `@include rfs($font-size-base, --#{$prefix}body-font-size);` into `--#{$prefix}body-font-size: #{$font-size-base};`. -## v1.2.0 +## v1.2.0 [[version-changelog-link]]
@@ -206,7 +206,7 @@ toc: true 2. Remove `@import "boosted/scss/nav";`, `@import "boosted/scss/navbar";`, `@import "boosted/scss/footer";`, and `@import "boosted/scss/orange-navbar";` from the Boosted custom import stack. 3. Change the `@include rfs($font-size-base, --#{$prefix}body-font-size);` into `--#{$prefix}body-font-size: #{$font-size-base};`. -## v1.1.0 +## v1.1.0 [[version-changelog-link]]
@@ -257,7 +257,7 @@ toc: true 3. Add back `--#{$prefix}highlight-bg` and `--#{$prefix}highlight-color` for light and dark mode and add `--#{$prefix}modal-border-radius: 0px;` for light mode. 4. Remove `@import "boosted/scss/alert";` and `@import "boosted/scss/close";` from the Boosted custom import stack. -## v1.0.0 +## v1.0.0 [[version-changelog-link]]
@@ -344,7 +344,7 @@ toc: true - Warning Opacity levels values have been changed. Read more in our [opacity page]([[docsref:/utilities/opacity]]). -## v0.6.0 +## v0.6.0 [[version-changelog-link]]
@@ -438,7 +438,7 @@ toc: true - **5xl** is renamed to `5xlarge` : for example `.m-5xl` → `.m-5xlarge` - **Negative** these changes apply to negative spacings as well, for example `.m-n3xs` → `.m-n3xsmall` -## v0.5.0 +## v0.5.0 [[version-changelog-link]]
@@ -511,7 +511,7 @@ toc: true - Breaking A parameter from the `focus-visible()` mixin has been removed. If you use the `focus-visible()` mixin in your scss files with a `z-index` as a parameter, please adapt your code. -## v0.4.0 +## v0.4.0 [[version-changelog-link]]
@@ -573,7 +573,7 @@ toc: true -## v0.3.0 +## v0.3.0 [[version-changelog-link]]
@@ -656,7 +656,7 @@ toc: true -## v0.2.0 +## v0.2.0 [[version-changelog-link]]
@@ -814,7 +814,7 @@ toc: true -## v0.1.0 +## v0.1.0 [[version-changelog-link]]
@@ -2435,7 +2435,7 @@ toc: true - New `color-mode({mode}, {root}, {inverted-mode})` mixin. -## v0.0.3 +## v0.0.3 [[version-changelog-link]]
@@ -2503,7 +2503,7 @@ From now on, by default, OUDS Web won’t embed Bootstrap elements (like helpers -## v0.0.1 +## v0.0.1 [[version-changelog-link]]
diff --git a/site/src/libs/astro.ts b/site/src/libs/astro.ts index 5a3ea9cc2c..5ee3748917 100644 --- a/site/src/libs/astro.ts +++ b/site/src/libs/astro.ts @@ -8,8 +8,8 @@ import autoImport from 'astro-auto-import' import type { Element, Text } from 'hast' import rehypeAutolinkHeadings from 'rehype-autolink-headings' import { getConfig } from './config' -import { rehypeBsTable } from './rehype' -import { remarkBsComp, remarkBsConfig, remarkBsDocsref } from './remark' +import { rehypeBsTable, rehypeCustomHeaderSlug, rehypeHeaderLinksOrder } from './rehype' +import { remarkBsComp, remarkBsConfig, remarkBsDocsref, remarkBsVersionLink } from './remark' import { configurePrism } from './prism' import { docsDirectory, @@ -18,6 +18,7 @@ import { getDocsStaticFsPath, validateVersionedDocsPaths } from './path' +import { isHeading } from './utils.ts' // A list of directories in `src/components` that contains components that will be auto imported in all pages for // convenience. @@ -48,8 +49,6 @@ const sitemapExcludes = [ `/${getConfig().brand}/docs/${getConfig().docs_version}/about` ] -const headingsRangeRegex = new RegExp(`^h[${getConfig().anchors.min}-${getConfig().anchors.max}]$`) - export function oudsWeb(): AstroIntegration[] { const sitemapExcludedUrls = sitemapExcludes.map((url) => `${getConfig().baseURL}${url}/`) @@ -69,21 +68,23 @@ export function oudsWeb(): AstroIntegration[] { markdown: { rehypePlugins: [ rehypeHeadingIds, + rehypeCustomHeaderSlug, [ rehypeAutolinkHeadings, { - behavior: 'append', + behavior: 'prepend', content: [{ type: 'text', value: ' '}], properties: (element: Element) => ({ class: 'anchor-link', ariaLabel: `Link to this section: ${(element.children[0] as Text).value}` }), - test: (element: Element) => element.tagName.match(headingsRangeRegex) + test: (element: Element) => isHeading(element.tagName) } ], + rehypeHeaderLinksOrder, rehypeBsTable ], - remarkPlugins: [remarkBsConfig, remarkBsDocsref, remarkBsComp] + remarkPlugins: [remarkBsConfig, remarkBsDocsref, remarkBsComp, remarkBsVersionLink] } }) }, diff --git a/site/src/libs/rehype.ts b/site/src/libs/rehype.ts index 5b08120ac7..cad6223f29 100644 --- a/site/src/libs/rehype.ts +++ b/site/src/libs/rehype.ts @@ -1,6 +1,7 @@ import type { Root } from 'hast' import type { Plugin } from 'unified' import { SKIP, visit } from 'unist-util-visit' +import { getHeadingSlug, isHeading } from './utils' // A rehype plugin to apply CSS classes to tables rendered in markdown (or MDX) files when wrapped in a `` // component. @@ -32,3 +33,47 @@ export const rehypeBsTable: Plugin<[], Root> = function () { }) } } + +// A rehype plugin to reorder header anchor link and header text +export const rehypeHeaderLinksOrder: Plugin<[], Root> = function () { + return function rehypeHeaderLinksOrderPlugin(ast) { + visit(ast, 'element', (node, index, parent) => { + if (!isHeading(node.tagName) || !parent) { + return + } + + const hasAnchorClass = (child: any) => { + const childClass = child?.properties?.class + if (typeof childClass === 'string') { + return childClass.includes('anchor-link') + } + + if (Array.isArray(childClass)) { + return childClass.some(item => String(item).includes('anchor-link')) + } + + return false + } + + const hasAnchor = hasAnchorClass(node.children[0]) + const headingIndex = node.children.findIndex(child => child.type === 'text') + if (!hasAnchor || headingIndex === -1) { + return + } + + const [anchorLink] = node.children.splice(0, 1) + node.children.splice(headingIndex, 0, anchorLink) + }) + } +} + +// A rehype plugin to generate correct heading id +export const rehypeCustomHeaderSlug: Plugin<[], Root> = function () { + return function rehypeCustomHeaderSlugPlugin(ast) { + visit(ast, 'element', (node) => { + if (/^h[1-6]$/.test(node.tagName) && node?.properties?.id && typeof node.properties.id === 'string') { + node.properties = { ...node.properties, id: getHeadingSlug(node.properties.id) } + } + }) + } +} diff --git a/site/src/libs/remark.ts b/site/src/libs/remark.ts index 8cc082422a..9aa4545aea 100644 --- a/site/src/libs/remark.ts +++ b/site/src/libs/remark.ts @@ -4,7 +4,7 @@ import type { Plugin } from 'unified' import { visit } from 'unist-util-visit' import { getConfig } from './config' import { getVersionedDocsPath } from './path' -import { getComponentSVG } from './utils' +import { getComponentSVG, getVersionLink } from './utils' // [[config:foo]] // [[config:foo.bar]] @@ -14,6 +14,8 @@ const configRegExp = /\[\[config:(?[\w\.]+)]]/g const docsrefRegExp = /\[\[docsref:(?[\w\.\/#-]+)]]/g // [[comp]] const compRegExp = /\[\[comp\]\]\s*/ +// [[version-changelog-link]] +const versionLinkRegExp = /\[\[version-changelog-link\]\]\s*/ // A remark plugin to replace config values embedded in markdown (or MDX) files. // For example, [[config:foo]] will be replaced with the value of the `foo` key in the `config.yml` file. @@ -102,7 +104,7 @@ export const remarkBsComp: Plugin<[], Root> = function () { type: 'html', value: getComponentSVG('hl-small-icon me-scaled-2xsmall mb-xsmall') }, ...parent.children] - node.value = replaceCompInText(node.value) + node.value = replaceRegexInText(node.value, compRegExp) } break } @@ -111,8 +113,32 @@ export const remarkBsComp: Plugin<[], Root> = function () { } } -function replaceCompInText(text: string) { - return text.replace(compRegExp, (_match, path) => { +// A remark plugin to add a link to changelog to version titles. +export const remarkBsVersionLink: Plugin<[], Root> = function () { + return function remarkBsVersionLinkPlugin(ast) { + // https://github.com/syntax-tree/mdast#nodes + // https://github.com/syntax-tree/mdast-util-mdx-jsx#nodes + visit(ast, ['text'], (node, index, parent) => { + switch (node.type) { + case 'text': { + const regexMatch = node.value.match(versionLinkRegExp) + if (regexMatch) { + const version = node.value.replace(' ' + regexMatch[0], '') + parent.children = [...parent.children, { + type: 'html', + value: getVersionLink(version) + }] + node.value = replaceRegexInText(node.value, versionLinkRegExp) + } + break + } + } + }) + } +} + +function replaceRegexInText(text: string, regex: RegExp) { + return text.replace(regex, (_match, path) => { return `` }) } diff --git a/site/src/libs/toc.ts b/site/src/libs/toc.ts index 4e840389a3..35aedf6cf7 100644 --- a/site/src/libs/toc.ts +++ b/site/src/libs/toc.ts @@ -1,5 +1,6 @@ import type { MarkdownHeading } from 'astro' import { getConfig } from './config' +import { getHeadingSlug } from '@libs/utils.ts' // Generate a tree like structure from a list of headings. export function generateToc(allHeadings: MarkdownHeading[], types?: string[]) { @@ -11,6 +12,12 @@ export function generateToc(allHeadings: MarkdownHeading[], types?: string[]) { const toc: TocEntry[] = [] for (const heading of headings) { + if (heading.text.includes('Full changelog')) { + heading.text = heading.text.replace(' Full changelog', '') + } + + heading.slug = getHeadingSlug(heading.slug) + if (toc.length === 0) { toc.push({ ...heading, children: [] }) continue diff --git a/site/src/libs/utils.ts b/site/src/libs/utils.ts index 6cb7113385..2d8fdcfd8a 100644 --- a/site/src/libs/utils.ts +++ b/site/src/libs/utils.ts @@ -4,6 +4,7 @@ import toString from 'mdast-util-to-string' import { remark } from 'remark' import remarkHtml from 'remark-html' import { getVersionedDocsPath } from './path' +import { getConfig } from './config' export function capitalizeFirstLetter(str: string) { return str.charAt(0).toUpperCase() + str.slice(1) @@ -30,6 +31,10 @@ export function getSlug(str: string) { return slug(str).replace(/--+/g, '-') } +export function getHeadingSlug(str: string) { + return str.replace(/-------full-changelog---------------------/g, '') +} + export function trimLeadingAndTrailingSlashes(str: string) { return str.replace(/^\/+|\/+$/g, '') } @@ -47,3 +52,17 @@ export function processMarkdownToHtml(markdown: string): string { export function getComponentSVG(className: string): string { return `` } + +export function getVersionLink(version: string): string { + return ` + Full changelog + + ` +} + +export function isHeading(tag: string): boolean { + const headingsRangeRegex = new RegExp(`^h[${getConfig().anchors.min}-${getConfig().anchors.max}]$`) + return !!tag.match(headingsRangeRegex) +} diff --git a/site/static/orange-compact/docs/[version]/assets/img/ouds-web-sprite.svg b/site/static/orange-compact/docs/[version]/assets/img/ouds-web-sprite.svg index f55ea3bd3a..29ecfcc243 100644 --- a/site/static/orange-compact/docs/[version]/assets/img/ouds-web-sprite.svg +++ b/site/static/orange-compact/docs/[version]/assets/img/ouds-web-sprite.svg @@ -183,4 +183,8 @@ + + + + diff --git a/site/static/orange/docs/[version]/assets/img/ouds-web-sprite.svg b/site/static/orange/docs/[version]/assets/img/ouds-web-sprite.svg index f55ea3bd3a..29ecfcc243 100644 --- a/site/static/orange/docs/[version]/assets/img/ouds-web-sprite.svg +++ b/site/static/orange/docs/[version]/assets/img/ouds-web-sprite.svg @@ -183,4 +183,8 @@ + + + + diff --git a/site/static/sosh/docs/[version]/assets/img/ouds-web-sprite.svg b/site/static/sosh/docs/[version]/assets/img/ouds-web-sprite.svg index bc4618bc99..93a9dc4f38 100644 --- a/site/static/sosh/docs/[version]/assets/img/ouds-web-sprite.svg +++ b/site/static/sosh/docs/[version]/assets/img/ouds-web-sprite.svg @@ -164,4 +164,8 @@ + + + +