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 @@
+
+
+
+