@chassis-ui/docs 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +286 -0
- package/README.md +227 -71
- package/UPGRADING.md +243 -0
- package/index.ts +7 -1
- package/package.json +31 -8
- package/src/components/DocsSidebar.astro +9 -9
- package/src/components/shortcodes/Callout.astro +4 -4
- package/src/components/shortcodes/Code.astro +3 -3
- package/src/components/shortcodes/JsDocs.astro +6 -7
- package/src/components/shortcodes/ScssDocs.astro +7 -9
- package/src/components/shortcodes/ScssDocsSimple.astro +6 -7
- package/src/integration.ts +271 -0
- package/src/js/clipboard.ts +65 -0
- package/src/layouts/BaseLayout.astro +7 -9
- package/src/layouts/DocsLayout.astro +11 -13
- package/src/layouts/RedirectLayout.astro +1 -1
- package/src/layouts/head/Analytics.astro +6 -4
- package/src/layouts/head/Head.astro +2 -2
- package/src/layouts/head/Social.astro +5 -6
- package/src/layouts/header/Navigation.astro +28 -19
- package/src/libs/config.ts +138 -0
- package/src/libs/docs-path.ts +12 -0
- package/src/libs/highlight.ts +4 -44
- package/src/libs/paths.ts +160 -0
- package/src/libs/remark.ts +184 -0
- package/src/libs/schema.ts +211 -0
- package/src/libs/shortcodes.ts +86 -71
- package/src/libs/snippet.ts +21 -0
- package/src/libs/source.ts +17 -40
- package/src/libs/toc.ts +3 -1
- package/src/scss/_vars.scss +4 -1
- package/src/site.ts +150 -0
- package/virtual.d.ts +25 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chassis-ui/docs",
|
|
3
3
|
"description": "Shared Astro components and layouts for Chassis documentation sites.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -25,18 +25,35 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"src",
|
|
27
27
|
"index.ts",
|
|
28
|
+
"virtual.d.ts",
|
|
28
29
|
"README.md",
|
|
29
|
-
"
|
|
30
|
+
"UPGRADING.md",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"CHANGELOG.md"
|
|
30
33
|
],
|
|
31
34
|
"main": "index.ts",
|
|
32
35
|
"exports": {
|
|
33
36
|
".": "./index.ts",
|
|
34
|
-
"./
|
|
37
|
+
"./integration": "./src/integration.ts",
|
|
38
|
+
"./schema": "./src/libs/schema.ts",
|
|
39
|
+
"./site": "./src/site.ts",
|
|
40
|
+
"./layouts/BaseLayout.astro": "./src/layouts/BaseLayout.astro",
|
|
41
|
+
"./layouts/DocsLayout.astro": "./src/layouts/DocsLayout.astro",
|
|
42
|
+
"./layouts/RedirectLayout.astro": "./src/layouts/RedirectLayout.astro",
|
|
43
|
+
"./layouts/SingleLayout.astro": "./src/layouts/SingleLayout.astro",
|
|
44
|
+
"./components/DocsSidebar.astro": "./src/components/DocsSidebar.astro",
|
|
45
|
+
"./components/FeatureCard.astro": "./src/components/FeatureCard.astro",
|
|
46
|
+
"./components/NavLink.astro": "./src/components/NavLink.astro",
|
|
47
|
+
"./components/ResponsiveImage.astro": "./src/components/ResponsiveImage.astro",
|
|
48
|
+
"./components/TableOfContents.astro": "./src/components/TableOfContents.astro",
|
|
49
|
+
"./components/ThemeToggler.astro": "./src/components/ThemeToggler.astro",
|
|
35
50
|
"./shortcodes/*": "./src/components/shortcodes/*",
|
|
36
|
-
"./layouts/*": "./src/layouts/*",
|
|
37
|
-
"./libs/*": "./src/libs/*",
|
|
38
51
|
"./js/*": "./src/js/*",
|
|
39
|
-
"./scss
|
|
52
|
+
"./scss/main": "./src/scss/main.scss",
|
|
53
|
+
"./scss/main.scss": "./src/scss/main.scss",
|
|
54
|
+
"./scss/vars": "./src/scss/_vars.scss",
|
|
55
|
+
"./scss/_vars.scss": "./src/scss/_vars.scss",
|
|
56
|
+
"./package.json": "./package.json"
|
|
40
57
|
},
|
|
41
58
|
"publishConfig": {
|
|
42
59
|
"access": "public"
|
|
@@ -59,18 +76,24 @@
|
|
|
59
76
|
"shiki": "^4.3.1"
|
|
60
77
|
},
|
|
61
78
|
"dependencies": {
|
|
79
|
+
"@types/hast": "^3.0.5",
|
|
80
|
+
"@types/mdast": "^4.0.4",
|
|
81
|
+
"clipboard": "^2.0.11",
|
|
62
82
|
"github-slugger": "^2.0.0",
|
|
63
83
|
"image-size": "^2.0.4",
|
|
84
|
+
"js-yaml": "^5.4.2",
|
|
64
85
|
"mdast-util-from-markdown": "^2.0.3",
|
|
86
|
+
"mdast-util-mdx-jsx": "^3.2.0",
|
|
65
87
|
"mdast-util-to-string": "^4.0.0",
|
|
66
88
|
"medium-zoom": "^1.1.0",
|
|
67
89
|
"remark": "^15.0.1",
|
|
68
90
|
"remark-html": "^16.0.1",
|
|
91
|
+
"unified": "^11.0.5",
|
|
69
92
|
"unist-util-visit": "^5.1.0"
|
|
70
93
|
},
|
|
71
94
|
"devDependencies": {
|
|
72
|
-
"@types/hast": "^3.0.5",
|
|
73
95
|
"astro": "^7.3.5",
|
|
74
|
-
"
|
|
96
|
+
"remark-mdx": "^3.1.1",
|
|
97
|
+
"vfile": "^6.0.3"
|
|
75
98
|
}
|
|
76
99
|
}
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
* Navigation sidebar for documentation pages with hierarchical menu structure.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { getConfig } from '@libs/config'
|
|
10
|
-
import { docsPages } from '@libs/content'
|
|
8
|
+
import type { SidebarItem, SidebarSubItem } from '../libs/schema'
|
|
11
9
|
import { getSlug } from '../libs/utils'
|
|
10
|
+
import { getDocsPages, getDocsPath, getSidebar } from '../site'
|
|
12
11
|
import Icon from './shortcodes/Icon.astro'
|
|
13
12
|
|
|
14
|
-
const sidebar =
|
|
13
|
+
const sidebar = getSidebar()
|
|
14
|
+
const docsPages = await getDocsPages()
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
<nav class="cxd-links w-100" id="cxd-docs-nav" aria-label="Docs navigation">
|
|
@@ -32,7 +32,7 @@ const sidebar = getData('sidebar')
|
|
|
32
32
|
{group.icon && (
|
|
33
33
|
<Icon
|
|
34
34
|
name={group.icon}
|
|
35
|
-
style={group.
|
|
35
|
+
style={group.iconColor && `color: var(--cx-${group.iconColor});`}
|
|
36
36
|
/>
|
|
37
37
|
)}
|
|
38
38
|
{group.title}
|
|
@@ -48,7 +48,7 @@ const sidebar = getData('sidebar')
|
|
|
48
48
|
const docSlug = getSlug(page.title)
|
|
49
49
|
const unversionedPageSlug = `${groupSlug}/${docSlug}`
|
|
50
50
|
|
|
51
|
-
const url =
|
|
51
|
+
const url = getDocsPath(unversionedPageSlug)
|
|
52
52
|
const active = Astro.params.slug === unversionedPageSlug
|
|
53
53
|
|
|
54
54
|
const generatedPage = docsPages.find(
|
|
@@ -58,7 +58,7 @@ const sidebar = getData('sidebar')
|
|
|
58
58
|
// This test should not be necessary, see comments for `getSlug()` in `src/libs/utils.ts`.
|
|
59
59
|
if (!generatedPage) {
|
|
60
60
|
throw new Error(
|
|
61
|
-
`The page '${page.title}' referenced in '
|
|
61
|
+
`The page '${page.title}' referenced in 'data/sidebar.yml' does not exist at '${url}'.`
|
|
62
62
|
)
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -86,7 +86,7 @@ const sidebar = getData('sidebar')
|
|
|
86
86
|
const docSlug = getSlug(item.title)
|
|
87
87
|
const unversionedPageSlug = `${groupSlug}/${docSlug}`
|
|
88
88
|
|
|
89
|
-
const url =
|
|
89
|
+
const url = getDocsPath(unversionedPageSlug)
|
|
90
90
|
const active = Astro.params.slug === unversionedPageSlug
|
|
91
91
|
|
|
92
92
|
const generatedPage = docsPages.find((page) => page.id === unversionedPageSlug)
|
|
@@ -120,7 +120,7 @@ const sidebar = getData('sidebar')
|
|
|
120
120
|
<>
|
|
121
121
|
<li class="cxd-links-span-all mt-2xs mb-md mx-lg border-top" />
|
|
122
122
|
<a
|
|
123
|
-
href={`${
|
|
123
|
+
href={getDocsPath(`${groupSlug}/`)}
|
|
124
124
|
class:list={['cxd-links-link cxd-links-span-all', { active }]}
|
|
125
125
|
aria-current={active ? 'page' : undefined}
|
|
126
126
|
>
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
* Display informational callout boxes with different types (info, danger, warning).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { getCalloutByName, type CalloutName } from '@libs/content'
|
|
9
8
|
import { render } from 'astro:content'
|
|
9
|
+
import { getCallout } from '../../site'
|
|
10
10
|
import type { MarkdownInstance } from 'astro'
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
/**
|
|
14
|
-
* The name of an
|
|
14
|
+
* The name of an entry of the site's `callouts` collection to display.
|
|
15
15
|
* This will override any content passed in via the default slot.
|
|
16
16
|
*/
|
|
17
|
-
name?:
|
|
17
|
+
name?: string
|
|
18
18
|
/**
|
|
19
19
|
* The type of callout to display. One of `info`, `danger`, or `warning`.
|
|
20
20
|
* @default 'info'
|
|
@@ -31,7 +31,7 @@ const { name, type = 'info', class: className } = Astro.props
|
|
|
31
31
|
let Content: MarkdownInstance<{}>['Content'] | undefined
|
|
32
32
|
|
|
33
33
|
if (name) {
|
|
34
|
-
const callout = await
|
|
34
|
+
const callout = await getCallout(name)
|
|
35
35
|
|
|
36
36
|
if (!callout) {
|
|
37
37
|
throw new Error(`Could not find callout with name '${name}'.`)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import { transformerNotationDiff, transformerNotationHighlight } from '@shikijs/transformers'
|
|
4
4
|
import { highlightCode } from '../../libs/highlight'
|
|
5
|
-
import { getSourceFsPath, getSourceUrl } from '../../
|
|
5
|
+
import { getSourceFsPath, getSourceUrl } from '../../site'
|
|
6
6
|
import Icon from './Icon.astro'
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
@@ -41,7 +41,7 @@ interface Props {
|
|
|
41
41
|
fileMatch?: string
|
|
42
42
|
/**
|
|
43
43
|
* A path to the file containing the code to highlight, relative to the `sourceDir` of the
|
|
44
|
-
* site's `config.yml` (the
|
|
44
|
+
* site's `config.yml` (the site's root when it is not set).
|
|
45
45
|
* This takes precedence over the `code` prop.
|
|
46
46
|
*/
|
|
47
47
|
filePath?: string
|
|
@@ -147,7 +147,7 @@ if (codeToDisplay && detectedLang) {
|
|
|
147
147
|
|
|
148
148
|
<script>
|
|
149
149
|
import { Tooltip } from '@chassis-ui/css'
|
|
150
|
-
import { initCopyButtons } from '
|
|
150
|
+
import { initCopyButtons } from '../../js/clipboard'
|
|
151
151
|
|
|
152
152
|
document.querySelectorAll('.button-edit').forEach((button) => {
|
|
153
153
|
Tooltip.getOrCreateInstance(button, { title: 'Edit on StackBlitz' })
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import fs from 'node:fs'
|
|
3
|
-
import { getSourceFsPath } from '../../
|
|
3
|
+
import { getSourceFsPath } from '../../site'
|
|
4
|
+
import { extractDocsSnippet } from '../../libs/snippet'
|
|
4
5
|
import Code from './Code.astro'
|
|
5
6
|
|
|
6
7
|
// Prints everything between `// js-docs-start "name"` and `// js-docs-end "name"`
|
|
@@ -13,7 +14,7 @@ interface Props {
|
|
|
13
14
|
name: string
|
|
14
15
|
/**
|
|
15
16
|
* File path that contains the content to display, relative to the `sourceDir` of the site's
|
|
16
|
-
* `config.yml` (the
|
|
17
|
+
* `config.yml` (the site's root when it is not set).
|
|
17
18
|
*/
|
|
18
19
|
file: string
|
|
19
20
|
}
|
|
@@ -31,17 +32,15 @@ let content: string
|
|
|
31
32
|
try {
|
|
32
33
|
const fileContent = fs.readFileSync(getSourceFsPath(file), 'utf8')
|
|
33
34
|
|
|
34
|
-
const
|
|
35
|
-
new RegExp(`// js-docs-start ${name}\n((?:.|\n)*)// js-docs-end ${name}`, 'm')
|
|
36
|
-
)
|
|
35
|
+
const snippet = extractDocsSnippet(fileContent, 'js', name)
|
|
37
36
|
|
|
38
|
-
if (!
|
|
37
|
+
if (!snippet) {
|
|
39
38
|
throw new Error(
|
|
40
39
|
`Failed to find the content named '${name}', make sure that '// js-docs-start ${name}' and '// js-docs-end ${name}' are defined.`
|
|
41
40
|
)
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
content =
|
|
43
|
+
content = snippet
|
|
45
44
|
|
|
46
45
|
// Fix the identation by removing extra spaces at the beginning of each line
|
|
47
46
|
const lines = content.split('\n')
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
import fs from 'node:fs'
|
|
3
|
-
import path from 'node:path'
|
|
4
3
|
import * as sass from 'sass'
|
|
5
|
-
import { getSourceFsPath } from '../../
|
|
4
|
+
import { getNodeModulesPaths, getSourceFsPath } from '../../site'
|
|
5
|
+
import { extractDocsSnippet } from '../../libs/snippet'
|
|
6
6
|
import Code from './Code.astro'
|
|
7
7
|
|
|
8
8
|
// Prints everything between `// scss-docs-start "name"` and `// scss-docs-end "name"`
|
|
@@ -16,7 +16,7 @@ interface Props {
|
|
|
16
16
|
name: string
|
|
17
17
|
/**
|
|
18
18
|
* File path that contains the content to display, relative to the `sourceDir` of the site's
|
|
19
|
-
* `config.yml` (the
|
|
19
|
+
* `config.yml` (the site's root when it is not set).
|
|
20
20
|
*/
|
|
21
21
|
file: string
|
|
22
22
|
/**
|
|
@@ -78,17 +78,15 @@ let lang: string = 'scss'
|
|
|
78
78
|
try {
|
|
79
79
|
const fileContent = fs.readFileSync(getSourceFsPath(file), 'utf8')
|
|
80
80
|
|
|
81
|
-
const
|
|
82
|
-
new RegExp(`// scss-docs-start ${name}\n((?:.|\n)*)// scss-docs-end ${name}`, 'm')
|
|
83
|
-
)
|
|
81
|
+
const snippet = extractDocsSnippet(fileContent, 'scss', name)
|
|
84
82
|
|
|
85
|
-
if (!
|
|
83
|
+
if (!snippet) {
|
|
86
84
|
throw new Error(
|
|
87
85
|
`Failed to find the content named '${name}', make sure that '// scss-docs-start ${name}' and '// scss-docs-end ${name}' are defined.`
|
|
88
86
|
)
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
const rawContent =
|
|
89
|
+
const rawContent = snippet
|
|
92
90
|
|
|
93
91
|
if (compile) {
|
|
94
92
|
// Derive the SCSS load path from the file argument (e.g. "scss/_foo.scss" → "scss/")
|
|
@@ -113,7 +111,7 @@ try {
|
|
|
113
111
|
// Vite's own Sass importer resolves bare npm specifiers (e.g. `@chassis-ui/tokens/...`)
|
|
114
112
|
// through its module resolver, independent of `loadPaths`. This raw Sass JS API call
|
|
115
113
|
// bypasses Vite, so `node_modules` must be added here explicitly.
|
|
116
|
-
|
|
114
|
+
...getNodeModulesPaths()
|
|
117
115
|
],
|
|
118
116
|
// Match the same deprecation silencing used in astro.config.ts / vite scss options
|
|
119
117
|
silenceDeprecations: ['import', 'global-builtin', 'color-functions', 'if-function'] as any
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import fs from 'node:fs'
|
|
3
|
-
import { getSourceFsPath } from '../../
|
|
3
|
+
import { getSourceFsPath } from '../../site'
|
|
4
|
+
import { extractDocsSnippet } from '../../libs/snippet'
|
|
4
5
|
import Code from './Code.astro'
|
|
5
6
|
|
|
6
7
|
// Prints everything between `// scss-docs-start "name"` and `// scss-docs-end "name"`
|
|
@@ -13,7 +14,7 @@ interface Props {
|
|
|
13
14
|
name: string
|
|
14
15
|
/**
|
|
15
16
|
* File path that contains the content to display, relative to the `sourceDir` of the site's
|
|
16
|
-
* `config.yml` (the
|
|
17
|
+
* `config.yml` (the site's root when it is not set).
|
|
17
18
|
*/
|
|
18
19
|
file: string
|
|
19
20
|
}
|
|
@@ -33,17 +34,15 @@ let content: string
|
|
|
33
34
|
try {
|
|
34
35
|
const fileContent = fs.readFileSync(getSourceFsPath(file), 'utf8')
|
|
35
36
|
|
|
36
|
-
const
|
|
37
|
-
new RegExp(`// scss-docs-start ${name}\n((?:.|\n)*)// scss-docs-end ${name}`, 'm')
|
|
38
|
-
)
|
|
37
|
+
const snippet = extractDocsSnippet(fileContent, 'scss', name)
|
|
39
38
|
|
|
40
|
-
if (!
|
|
39
|
+
if (!snippet) {
|
|
41
40
|
throw new Error(
|
|
42
41
|
`Failed to find the content named '${name}', make sure that '// scss-docs-start ${name}' and '// scss-docs-end ${name}' are defined.`
|
|
43
42
|
)
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
content =
|
|
45
|
+
content = snippet.replaceAll(' !default', '')
|
|
47
46
|
|
|
48
47
|
// Fix the identation by removing extra spaces at the beginning of each line
|
|
49
48
|
const lines = content.split('\n')
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import type { AstroIntegration, AstroUserConfig, ViteUserConfig } from 'astro'
|
|
5
|
+
import type { z } from 'astro/zod'
|
|
6
|
+
import { loadConfig, loadSidebar } from './libs/config'
|
|
7
|
+
import { joinDocsPath } from './libs/docs-path'
|
|
8
|
+
import { getDocsMarkdownConfig, type DocsMarkdownConfigOptions } from './libs/markdown'
|
|
9
|
+
import { getInstalledPackageFsPath, getPackageRoot } from './libs/paths'
|
|
10
|
+
import { remarkCxConfig, remarkCxDocsref } from './libs/remark'
|
|
11
|
+
import { configSchema, type ChassisConfig, type Sidebar } from './libs/schema'
|
|
12
|
+
import { chassisAutoImport, type ChassisAutoImportOptions } from './libs/shortcodes'
|
|
13
|
+
import { getSiteUrl } from './libs/site'
|
|
14
|
+
|
|
15
|
+
export interface ChassisDocsOptions<TSchema extends z.ZodType = typeof configSchema> {
|
|
16
|
+
/**
|
|
17
|
+
* The config, for a site that has loaded it with `loadConfig()` to use it in
|
|
18
|
+
* `astro.config.ts`. `configFile` is then only watched, and `configSchema` is not used.
|
|
19
|
+
*/
|
|
20
|
+
config?: ChassisConfig
|
|
21
|
+
/**
|
|
22
|
+
* Path of the config file, from the site's root.
|
|
23
|
+
* @default 'config.yml'
|
|
24
|
+
*/
|
|
25
|
+
configFile?: string
|
|
26
|
+
/**
|
|
27
|
+
* Schema of the config file: `configSchema` from `@chassis-ui/docs/schema`, or an
|
|
28
|
+
* extension of it when the site has keys of its own.
|
|
29
|
+
*/
|
|
30
|
+
configSchema?: TSchema
|
|
31
|
+
/**
|
|
32
|
+
* Path of the sidebar data file, from the site's root. `false` for a site that does not
|
|
33
|
+
* render `<DocsSidebar>`.
|
|
34
|
+
* @default 'data/sidebar.yml' when the file exists
|
|
35
|
+
*/
|
|
36
|
+
sidebarFile?: string | false
|
|
37
|
+
/**
|
|
38
|
+
* Stylesheets that every page loads, from the site's root. A site's stylesheet is expected
|
|
39
|
+
* to `@use "@chassis-ui/docs/scss/main"`.
|
|
40
|
+
* @default ['src/scss/docs.scss'] when the file exists, the package's styles otherwise
|
|
41
|
+
*/
|
|
42
|
+
styles?: string[]
|
|
43
|
+
/** Which shortcodes are imported into every MDX file. */
|
|
44
|
+
shortcodes?: Pick<ChassisAutoImportOptions, 'dir' | 'include' | 'exclude'>
|
|
45
|
+
/** Plugins of the site. They run after the plugins of the package. */
|
|
46
|
+
markdown?: Pick<DocsMarkdownConfigOptions, 'rehypePlugins' | 'remarkPlugins' | 'remarkRehype'>
|
|
47
|
+
/**
|
|
48
|
+
* What happens when a `[[docsref:]]` link points to a page that was not built.
|
|
49
|
+
* @default 'error'
|
|
50
|
+
*/
|
|
51
|
+
brokenDocsrefs?: 'error' | 'warn' | 'ignore'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type VitePlugin = Extract<NonNullable<ViteUserConfig['plugins']>[number], { name: string }>
|
|
55
|
+
|
|
56
|
+
const virtualPrefix = 'virtual:chassis-docs/'
|
|
57
|
+
const resolvedPrefix = '\0' + virtualPrefix
|
|
58
|
+
|
|
59
|
+
function virtualModules(modules: Record<string, string>): VitePlugin {
|
|
60
|
+
return {
|
|
61
|
+
name: 'chassis-docs:virtual',
|
|
62
|
+
resolveId(id) {
|
|
63
|
+
if (id.startsWith(virtualPrefix) && id.slice(virtualPrefix.length) in modules) {
|
|
64
|
+
return '\0' + id
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
load(id) {
|
|
68
|
+
if (id.startsWith(resolvedPrefix)) {
|
|
69
|
+
return modules[id.slice(resolvedPrefix.length)]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The declarations of `virtual.d.ts`, for a site.
|
|
76
|
+
const virtualTypes = `declare module 'virtual:chassis-docs/config' {
|
|
77
|
+
const config: import('@chassis-ui/docs/schema').ChassisConfig
|
|
78
|
+
export default config
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
declare module 'virtual:chassis-docs/sidebar' {
|
|
82
|
+
const sidebar: import('@chassis-ui/docs/schema').Sidebar
|
|
83
|
+
export default sidebar
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare module 'virtual:chassis-docs/paths' {
|
|
87
|
+
export const root: string
|
|
88
|
+
export const publicDir: string
|
|
89
|
+
export const sourceDir: string
|
|
90
|
+
export const packageRoot: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
declare module 'virtual:chassis-docs/styles' {}
|
|
94
|
+
`
|
|
95
|
+
|
|
96
|
+
// The folder of `@chassis-ui/css` with its default `_chassis-tokens.scss`, when the site has
|
|
97
|
+
// the package installed.
|
|
98
|
+
function getDefaultTokensLoadPath(root: string): string | undefined {
|
|
99
|
+
try {
|
|
100
|
+
return path.join(getInstalledPackageFsPath('@chassis-ui/css', root), 'scss/vendor')
|
|
101
|
+
} catch {
|
|
102
|
+
return undefined
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Append `index.html` when the path names a page, and drop the hash.
|
|
107
|
+
function toBuiltFilePath(docsPath: string): string {
|
|
108
|
+
const withoutHash = docsPath.split('#')[0]
|
|
109
|
+
|
|
110
|
+
return withoutHash.includes('.') ? withoutHash : path.join(withoutHash, 'index.html')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The Astro integration of `@chassis-ui/docs`. A site adds it in `astro.config.ts`.
|
|
115
|
+
*
|
|
116
|
+
* It reads `config.yml` and `data/sidebar.yml`, gives them to the layouts and components of
|
|
117
|
+
* the package, sets `site` and `markdown` of the Astro config, and imports the shortcodes
|
|
118
|
+
* into every MDX file.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* import { defineConfig } from 'astro/config'
|
|
123
|
+
* import { chassisDocs } from '@chassis-ui/docs/integration'
|
|
124
|
+
*
|
|
125
|
+
* export default defineConfig({
|
|
126
|
+
* integrations: [chassisDocs()]
|
|
127
|
+
* })
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export function chassisDocs<TSchema extends z.ZodType = typeof configSchema>(
|
|
131
|
+
options: ChassisDocsOptions<TSchema> = {}
|
|
132
|
+
): AstroIntegration {
|
|
133
|
+
const { brokenDocsrefs = 'error' } = options
|
|
134
|
+
|
|
135
|
+
// Docs paths that `[[docsref:]]` links name, collected while the pages are rendered.
|
|
136
|
+
const linkedDocsPaths = new Set<string>()
|
|
137
|
+
let config: ChassisConfig
|
|
138
|
+
let typeDefinitions = ''
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
name: '@chassis-ui/docs',
|
|
142
|
+
hooks: {
|
|
143
|
+
'astro:config:setup': ({ config: astroConfig, command, updateConfig, addWatchFile }) => {
|
|
144
|
+
const root = path.resolve(fileURLToPath(astroConfig.root))
|
|
145
|
+
const configFile = path.resolve(root, options.configFile ?? 'config.yml')
|
|
146
|
+
|
|
147
|
+
config =
|
|
148
|
+
options.config ??
|
|
149
|
+
loadConfig({
|
|
150
|
+
file: configFile,
|
|
151
|
+
schema: (options.configSchema ?? configSchema) as z.ZodType<ChassisConfig>
|
|
152
|
+
})
|
|
153
|
+
addWatchFile(configFile)
|
|
154
|
+
|
|
155
|
+
let sidebar: Sidebar = []
|
|
156
|
+
|
|
157
|
+
if (options.sidebarFile !== false) {
|
|
158
|
+
const sidebarFile = path.resolve(root, options.sidebarFile ?? 'data/sidebar.yml')
|
|
159
|
+
|
|
160
|
+
// The default file is optional. A file that the site names must exist.
|
|
161
|
+
if (options.sidebarFile !== undefined || fs.existsSync(sidebarFile)) {
|
|
162
|
+
sidebar = loadSidebar({ file: sidebarFile })
|
|
163
|
+
addWatchFile(sidebarFile)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const defaultStyles = path.join(root, 'src/scss/docs.scss')
|
|
168
|
+
const styles = options.styles
|
|
169
|
+
? options.styles.map((file) => path.resolve(root, file))
|
|
170
|
+
: fs.existsSync(defaultStyles)
|
|
171
|
+
? [defaultStyles]
|
|
172
|
+
: ['@chassis-ui/docs/scss/main.scss']
|
|
173
|
+
|
|
174
|
+
const autoImport = chassisAutoImport({ root, ...options.shortcodes })
|
|
175
|
+
typeDefinitions = autoImport.typeDefinitions
|
|
176
|
+
|
|
177
|
+
const paths = {
|
|
178
|
+
root,
|
|
179
|
+
publicDir: path.resolve(fileURLToPath(astroConfig.publicDir)),
|
|
180
|
+
sourceDir: path.resolve(root, config.sourceDir),
|
|
181
|
+
packageRoot: getPackageRoot()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const tokensLoadPath = getDefaultTokensLoadPath(root)
|
|
185
|
+
|
|
186
|
+
const update: AstroUserConfig = {
|
|
187
|
+
// A `site` that the Astro config sets itself is kept.
|
|
188
|
+
...(astroConfig.site ? {} : { site: getSiteUrl(config) }),
|
|
189
|
+
markdown: getDocsMarkdownConfig({
|
|
190
|
+
anchors: config.anchors,
|
|
191
|
+
remarkPlugins: [
|
|
192
|
+
autoImport.plugin(),
|
|
193
|
+
[remarkCxConfig, { config }],
|
|
194
|
+
[
|
|
195
|
+
remarkCxDocsref,
|
|
196
|
+
{
|
|
197
|
+
docsPath: config.docsPath,
|
|
198
|
+
onDocsPath:
|
|
199
|
+
command === 'build'
|
|
200
|
+
? (docsPath: string) => linkedDocsPaths.add(docsPath)
|
|
201
|
+
: undefined
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
...(options.markdown?.remarkPlugins ?? [])
|
|
205
|
+
],
|
|
206
|
+
rehypePlugins: options.markdown?.rehypePlugins,
|
|
207
|
+
remarkRehype: options.markdown?.remarkRehype
|
|
208
|
+
}),
|
|
209
|
+
vite: {
|
|
210
|
+
// Scripts of the package and of the site both import `@chassis-ui/css`, and two
|
|
211
|
+
// copies of it register every listener twice. In the dev server, a script of the
|
|
212
|
+
// package would be pre-bundled with a copy of its own, and a script of the site
|
|
213
|
+
// would get a pre-bundled copy while the package gets the source. Both packages
|
|
214
|
+
// are served from source, and every import resolves to the site's copy.
|
|
215
|
+
optimizeDeps: {
|
|
216
|
+
exclude: ['@chassis-ui/docs', '@chassis-ui/css'],
|
|
217
|
+
// The one CommonJS dependency of the package's scripts. It has no default export
|
|
218
|
+
// unless it is pre-bundled.
|
|
219
|
+
include: ['@chassis-ui/docs > clipboard']
|
|
220
|
+
},
|
|
221
|
+
resolve: {
|
|
222
|
+
dedupe: ['@chassis-ui/css']
|
|
223
|
+
},
|
|
224
|
+
// The styles of `@chassis-ui/css` load the design tokens as `chassis-tokens`. This
|
|
225
|
+
// adds its default as the last load path, after the site's own load paths, so a
|
|
226
|
+
// site with a `_chassis-tokens.scss` of its own puts that folder in its config.
|
|
227
|
+
...(tokensLoadPath
|
|
228
|
+
? { css: { preprocessorOptions: { scss: { loadPaths: [tokensLoadPath] } } } }
|
|
229
|
+
: {}),
|
|
230
|
+
plugins: [
|
|
231
|
+
virtualModules({
|
|
232
|
+
config: `export default ${JSON.stringify(config)}`,
|
|
233
|
+
sidebar: `export default ${JSON.stringify(sidebar)}`,
|
|
234
|
+
paths: Object.entries(paths)
|
|
235
|
+
.map(([name, value]) => `export const ${name} = ${JSON.stringify(value)}`)
|
|
236
|
+
.join('\n'),
|
|
237
|
+
styles: styles.map((file) => `import ${JSON.stringify(file)}`).join('\n')
|
|
238
|
+
})
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// The user config is wider than what `updateConfig()` declares for `markdown`.
|
|
244
|
+
updateConfig(update as Parameters<typeof updateConfig>[0])
|
|
245
|
+
},
|
|
246
|
+
'astro:config:done': ({ injectTypes }) => {
|
|
247
|
+
injectTypes({ filename: 'virtual.d.ts', content: virtualTypes })
|
|
248
|
+
injectTypes({ filename: 'auto-import.d.ts', content: typeDefinitions })
|
|
249
|
+
},
|
|
250
|
+
'astro:build:done': ({ dir, logger }) => {
|
|
251
|
+
if (brokenDocsrefs === 'ignore') return
|
|
252
|
+
|
|
253
|
+
const broken = [...linkedDocsPaths].filter((docsPath) => {
|
|
254
|
+
const builtPath = toBuiltFilePath(joinDocsPath(config.docsPath, docsPath))
|
|
255
|
+
|
|
256
|
+
return !fs.existsSync(path.join(fileURLToPath(dir), builtPath))
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
if (broken.length === 0) return
|
|
260
|
+
|
|
261
|
+
const message = `These [[docsref:]] links point to a page or file that was not built:\n${broken.map((docsPath) => ` - ${docsPath}`).join('\n')}`
|
|
262
|
+
|
|
263
|
+
if (brokenDocsrefs === 'error') {
|
|
264
|
+
throw new Error(message)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
logger.warn(message)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import ClipboardJS from 'clipboard'
|
|
2
|
+
import { Tooltip } from '@chassis-ui/css'
|
|
3
|
+
|
|
4
|
+
const btnTitle = 'Copy'
|
|
5
|
+
|
|
6
|
+
export function initCopyButtons(
|
|
7
|
+
selector: string,
|
|
8
|
+
textFn: (_element: Element) => string
|
|
9
|
+
): ClipboardJS {
|
|
10
|
+
document.querySelectorAll(selector).forEach((btn) => {
|
|
11
|
+
Tooltip.getOrCreateInstance(btn, { title: btnTitle })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const clipboard = new ClipboardJS(selector, { text: textFn })
|
|
15
|
+
|
|
16
|
+
clipboard.on('success', (event) => {
|
|
17
|
+
const useEl = event.trigger.querySelector('.icon use')
|
|
18
|
+
const tooltipBtn = Tooltip.getInstance(event.trigger)
|
|
19
|
+
const originalHref = useEl?.getAttribute('href')
|
|
20
|
+
|
|
21
|
+
if (originalHref === '#check-solid') {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
tooltipBtn?.setContent({ '.tooltip-inner': 'Copied!' })
|
|
26
|
+
|
|
27
|
+
event.trigger.addEventListener(
|
|
28
|
+
'hidden.cx.tooltip',
|
|
29
|
+
() => {
|
|
30
|
+
tooltipBtn?.setContent({ '.tooltip-inner': btnTitle })
|
|
31
|
+
},
|
|
32
|
+
{ once: true }
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
event.clearSelection()
|
|
36
|
+
|
|
37
|
+
if (useEl) {
|
|
38
|
+
useEl.setAttribute('href', '#check-solid')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
if (useEl && originalHref) {
|
|
43
|
+
useEl.setAttribute('href', originalHref)
|
|
44
|
+
}
|
|
45
|
+
}, 2000)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
clipboard.on('error', (event) => {
|
|
49
|
+
const modifierKey = /mac/i.test(navigator.userAgent) ? '\u2318' : 'Ctrl-'
|
|
50
|
+
const fallbackMsg = `Press ${modifierKey}C to copy`
|
|
51
|
+
const tooltipBtn = Tooltip.getInstance(event.trigger)
|
|
52
|
+
|
|
53
|
+
tooltipBtn?.setContent({ '.tooltip-inner': fallbackMsg })
|
|
54
|
+
|
|
55
|
+
event.trigger.addEventListener(
|
|
56
|
+
'hidden.cx.tooltip',
|
|
57
|
+
() => {
|
|
58
|
+
tooltipBtn?.setContent({ '.tooltip-inner': btnTitle })
|
|
59
|
+
},
|
|
60
|
+
{ once: true }
|
|
61
|
+
)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return clipboard
|
|
65
|
+
}
|