@ciderpress/ui 1.0.0-rc.11 → 1.0.0-rc.14
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/dist/head/css/themes/honeycrisp.css +1 -1
- package/dist/node.mjs +719 -26
- package/dist/plugins/mermaid/MermaidRenderer.tsx +2 -2
- package/dist/theme/components/home/hero-banner.tsx +225 -0
- package/dist/theme/components/home/hero-demo.css +492 -61
- package/dist/theme/components/home/hero-demo.test.tsx +159 -0
- package/dist/theme/components/home/hero-demo.tsx +335 -32
- package/dist/theme/components/home/hero.css +142 -9
- package/dist/theme/components/home/hero.test.tsx +105 -0
- package/dist/theme/components/home/hero.tsx +201 -4
- package/dist/theme/components/home/home-visual.css +98 -0
- package/dist/theme/components/home/home-visual.test.tsx +27 -0
- package/dist/theme/components/home/home-visual.tsx +20 -13
- package/dist/theme/components/home/layout.tsx +125 -1
- package/dist/theme/components/home/split.css +2 -2
- package/dist/theme/components/home/tabs.css +1 -1
- package/dist/theme/components/home/trust-strip.css +12 -0
- package/dist/theme/components/home/trust-strip.test.tsx +33 -0
- package/dist/theme/components/home/trust-strip.tsx +76 -15
- package/dist/theme/components/nav/layout.tsx +10 -14
- package/dist/theme/components/nav/nav-logo.tsx +2 -3
- package/dist/theme/components/seo-head-data.test.ts +196 -0
- package/dist/theme/components/seo-head-data.ts +490 -0
- package/dist/theme/components/seo-head.tsx +102 -0
- package/dist/theme/components/shared/IssueLinkIcon.tsx +42 -0
- package/dist/theme/components/shared/issue-link.css +74 -0
- package/dist/theme/components/sidebar/sidebar-filter.test.ts +49 -9
- package/dist/theme/components/sidebar/sidebar-filter.ts +24 -4
- package/dist/theme/index.tsx +9 -1
- package/dist/theme/lib/rich-text-parse.ts +0 -2
- package/dist/theme/lib/seo-url.ts +28 -0
- package/dist/theme/lib/theme-favicon.ts +0 -3
- package/dist/theme/styles/themes/honeycrisp.css +20 -20
- package/package.json +23 -21
- package/src/theme/components/home/hero-banner.tsx +225 -0
- package/src/theme/components/home/hero-demo.css +492 -61
- package/src/theme/components/home/hero-demo.test.tsx +159 -0
- package/src/theme/components/home/hero-demo.tsx +335 -32
- package/src/theme/components/home/hero.css +142 -9
- package/src/theme/components/home/hero.test.tsx +105 -0
- package/src/theme/components/home/hero.tsx +201 -4
- package/src/theme/components/home/home-visual.css +98 -0
- package/src/theme/components/home/home-visual.test.tsx +27 -0
- package/src/theme/components/home/home-visual.tsx +20 -13
- package/src/theme/components/home/layout.tsx +125 -1
- package/src/theme/components/home/split.css +2 -2
- package/src/theme/components/home/tabs.css +1 -1
- package/src/theme/components/home/trust-strip.css +12 -0
- package/src/theme/components/home/trust-strip.test.tsx +33 -0
- package/src/theme/components/home/trust-strip.tsx +76 -15
- package/src/theme/components/nav/layout.tsx +10 -14
- package/src/theme/components/nav/nav-logo.tsx +2 -3
- package/src/theme/components/seo-head-data.test.ts +196 -0
- package/src/theme/components/seo-head-data.ts +490 -0
- package/src/theme/components/seo-head.tsx +102 -0
- package/src/theme/components/shared/IssueLinkIcon.tsx +42 -0
- package/src/theme/components/shared/issue-link.css +74 -0
- package/src/theme/components/sidebar/sidebar-filter.test.ts +49 -9
- package/src/theme/components/sidebar/sidebar-filter.ts +24 -4
- package/src/theme/index.tsx +9 -1
- package/src/theme/lib/rich-text-parse.ts +0 -2
- package/src/theme/lib/seo-url.ts +28 -0
- package/src/theme/lib/theme-favicon.ts +0 -3
- package/src/theme/styles/themes/honeycrisp.css +20 -20
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Head, useFrontmatter, useLocation, usePageData, useSite } from '@rspress/core/runtime'
|
|
2
|
+
import { match } from 'massaman/match'
|
|
3
|
+
import { isNil, isNotNil } from 'massaman/predicate'
|
|
4
|
+
|
|
5
|
+
import type { SeoThemeConfig } from '../../seo-theme-config'
|
|
6
|
+
import type { ResolvedSeoHeadData } from './seo-head-data'
|
|
7
|
+
import { resolveSeoHeadData } from './seo-head-data'
|
|
8
|
+
|
|
9
|
+
type SeoMetaEntry = readonly ['name' | 'property', string, string | undefined]
|
|
10
|
+
type ResolvedSeoMetaEntry = readonly ['name' | 'property', string, string]
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Adds canonical, robots, Open Graph, and Twitter metadata on top of Rspress defaults.
|
|
14
|
+
*
|
|
15
|
+
* @returns Route-aware metadata for the current page, or `null` when SEO is not configured
|
|
16
|
+
*/
|
|
17
|
+
export default function SeoHead(): React.ReactElement | null {
|
|
18
|
+
const { site } = useSite()
|
|
19
|
+
const { page } = usePageData()
|
|
20
|
+
const { frontmatter } = useFrontmatter()
|
|
21
|
+
const { pathname } = useLocation()
|
|
22
|
+
const themeConfig = site.themeConfig as SeoThemeConfig
|
|
23
|
+
const siteSeo = themeConfig.seo
|
|
24
|
+
const seoBase = themeConfig.seoBase
|
|
25
|
+
|
|
26
|
+
if (isNil(siteSeo) || isNil(seoBase)) {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const metadata = resolveSeoHeadData({
|
|
31
|
+
siteSeo,
|
|
32
|
+
base: seoBase,
|
|
33
|
+
siteDescription: site.description,
|
|
34
|
+
page,
|
|
35
|
+
pathname,
|
|
36
|
+
frontmatter,
|
|
37
|
+
})
|
|
38
|
+
const metaTags = resolveMetaTags(metadata)
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Head>
|
|
42
|
+
{isNotNil(metadata.title) && <title>{metadata.title}</title>}
|
|
43
|
+
{isNotNil(metadata.canonical) && <link rel="canonical" href={metadata.canonical} />}
|
|
44
|
+
{metaTags.map(renderMetaTag)}
|
|
45
|
+
</Head>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Converts resolved SEO data into typed HTML meta-tag attributes.
|
|
51
|
+
*
|
|
52
|
+
* @private
|
|
53
|
+
*/
|
|
54
|
+
function resolveMetaTags(metadata: ResolvedSeoHeadData): readonly ResolvedSeoMetaEntry[] {
|
|
55
|
+
const entries: readonly SeoMetaEntry[] = [
|
|
56
|
+
['name', 'description', metadata.description],
|
|
57
|
+
['name', 'robots', metadata.robots],
|
|
58
|
+
...match(metadata.openGraph)
|
|
59
|
+
.with(false, () => [])
|
|
60
|
+
.otherwise((openGraph): readonly SeoMetaEntry[] => [
|
|
61
|
+
['property', 'og:url', openGraph.url],
|
|
62
|
+
['property', 'og:title', openGraph.title],
|
|
63
|
+
['property', 'og:description', openGraph.description],
|
|
64
|
+
['property', 'og:type', openGraph.type],
|
|
65
|
+
['property', 'og:site_name', openGraph.siteName],
|
|
66
|
+
['property', 'og:locale', openGraph.locale],
|
|
67
|
+
['property', 'og:image', openGraph.image],
|
|
68
|
+
]),
|
|
69
|
+
...match(metadata.twitter)
|
|
70
|
+
.with(false, () => [])
|
|
71
|
+
.otherwise((twitter): readonly SeoMetaEntry[] => [
|
|
72
|
+
['name', 'twitter:card', twitter.card],
|
|
73
|
+
['name', 'twitter:title', twitter.title],
|
|
74
|
+
['name', 'twitter:description', twitter.description],
|
|
75
|
+
['name', 'twitter:site', twitter.site],
|
|
76
|
+
['name', 'twitter:creator', twitter.creator],
|
|
77
|
+
['name', 'twitter:image', twitter.image],
|
|
78
|
+
]),
|
|
79
|
+
]
|
|
80
|
+
return entries.filter(isResolvedMetaEntry)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Narrows a meta entry after missing content is filtered out.
|
|
85
|
+
*
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
function isResolvedMetaEntry(entry: SeoMetaEntry): entry is ResolvedSeoMetaEntry {
|
|
89
|
+
return isNotNil(entry[2])
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Renders one typed meta-tag descriptor.
|
|
94
|
+
*
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
function renderMetaTag([attribute, key, content]: ResolvedSeoMetaEntry): React.ReactElement {
|
|
98
|
+
return match(attribute)
|
|
99
|
+
.with('name', () => <meta key={key} name={key} content={content} />)
|
|
100
|
+
.with('property', () => <meta key={key} property={key} content={content} />)
|
|
101
|
+
.exhaustive()
|
|
102
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
|
|
3
|
+
import type { IssueLinkProvider } from '../../../plugins/issue-links/remark-issue-links.ts'
|
|
4
|
+
import { Icon } from './icon.tsx'
|
|
5
|
+
|
|
6
|
+
const PROVIDER_ICONS = {
|
|
7
|
+
asana: 'logos:asana-icon',
|
|
8
|
+
bitbucket: 'devicon:bitbucket',
|
|
9
|
+
clickup: 'simple-icons:clickup',
|
|
10
|
+
confluence: 'devicon:confluence',
|
|
11
|
+
dash0: 'simple-icons:dash0',
|
|
12
|
+
datadog: 'logos:datadog',
|
|
13
|
+
figma: 'devicon:figma',
|
|
14
|
+
github: 'simple-icons:github',
|
|
15
|
+
gitlab: 'logos:gitlab-icon',
|
|
16
|
+
'google-docs': 'mdi:file-document',
|
|
17
|
+
'google-drive': 'logos:google-drive',
|
|
18
|
+
'google-sheets': 'mdi:google-spreadsheet',
|
|
19
|
+
jira: 'devicon:jira',
|
|
20
|
+
launchdarkly: 'logos:launchdarkly-icon',
|
|
21
|
+
linear: 'simple-icons:linear',
|
|
22
|
+
loom: 'logos:loom-icon',
|
|
23
|
+
notion: 'simple-icons:notion',
|
|
24
|
+
npm: 'logos:npm-icon',
|
|
25
|
+
postman: 'logos:postman-icon',
|
|
26
|
+
posthog: 'logos:posthog-icon',
|
|
27
|
+
pulumi: 'logos:pulumi-icon',
|
|
28
|
+
pagerduty: 'logos:pagerduty',
|
|
29
|
+
sentry: 'simple-icons:sentry',
|
|
30
|
+
slack: 'logos:slack-icon',
|
|
31
|
+
trello: 'logos:trello',
|
|
32
|
+
vercel: 'simple-icons:vercel',
|
|
33
|
+
} as const satisfies Record<IssueLinkProvider, string>
|
|
34
|
+
|
|
35
|
+
interface IssueLinkIconProps {
|
|
36
|
+
readonly provider: IssueLinkProvider
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Render a URL badge's provider mark through the shared Iconify pipeline. */
|
|
40
|
+
export default function IssueLinkIcon({ provider }: IssueLinkIconProps): React.ReactElement {
|
|
41
|
+
return <Icon aria-hidden="true" className="cp-issue-link__icon" icon={PROVIDER_ICONS[provider]} />
|
|
42
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** URL-derived resource badges with provider-specific icons. */
|
|
2
|
+
|
|
3
|
+
.rp-doc a.rp-link.cp-issue-link {
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
gap: 0.35em;
|
|
6
|
+
align-items: center;
|
|
7
|
+
padding: 0.14em 0.55em;
|
|
8
|
+
border: 1px solid var(--cp-c-border);
|
|
9
|
+
border-radius: var(--cp-radius-pill);
|
|
10
|
+
background: var(--cp-c-bg-soft);
|
|
11
|
+
background-image: none;
|
|
12
|
+
color: var(--cp-c-text-2);
|
|
13
|
+
font-size: 0.82em;
|
|
14
|
+
font-weight: 600;
|
|
15
|
+
line-height: 1.35;
|
|
16
|
+
text-decoration: none;
|
|
17
|
+
text-shadow: none;
|
|
18
|
+
vertical-align: 0.08em;
|
|
19
|
+
white-space: nowrap;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.rp-doc a.rp-link.cp-issue-link:hover {
|
|
23
|
+
border-color: var(--cp-c-text-3);
|
|
24
|
+
background: color-mix(in srgb, var(--cp-c-bg-soft) 82%, var(--cp-c-text-1));
|
|
25
|
+
background-image: none;
|
|
26
|
+
color: var(--cp-c-text-1);
|
|
27
|
+
opacity: 1;
|
|
28
|
+
text-decoration: none;
|
|
29
|
+
text-shadow: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.rp-doc a.rp-link.cp-issue-link[target='_blank']::after {
|
|
33
|
+
content: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.rp-doc a.rp-link.cp-issue-link:focus-visible {
|
|
37
|
+
outline: 2px solid var(--cp-c-brand-1);
|
|
38
|
+
outline-offset: 2px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.cp-issue-link__icon {
|
|
42
|
+
display: inline-block;
|
|
43
|
+
width: 1em;
|
|
44
|
+
height: 1em;
|
|
45
|
+
flex: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.cp-issue-link__reference {
|
|
49
|
+
color: var(--cp-c-text-3);
|
|
50
|
+
font-size: 0.92em;
|
|
51
|
+
font-weight: 650;
|
|
52
|
+
font-variant-numeric: tabular-nums;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.cp-issue-link--github .cp-issue-link__icon,
|
|
56
|
+
.cp-issue-link--notion .cp-issue-link__icon,
|
|
57
|
+
.cp-issue-link--sentry .cp-issue-link__icon {
|
|
58
|
+
color: var(--cp-c-text-1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.cp-issue-link--google-docs .cp-issue-link__icon {
|
|
62
|
+
color: #4285f4;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.cp-issue-link--google-sheets .cp-issue-link__icon {
|
|
66
|
+
color: #0f9d58;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
html[data-cp-variant='dark'] .cp-issue-link--linear .cp-issue-link__icon,
|
|
70
|
+
html[data-cp-variant='dark'] .cp-issue-link--launchdarkly .cp-issue-link__icon,
|
|
71
|
+
html[data-cp-variant='dark'] .cp-issue-link--vercel .cp-issue-link__icon {
|
|
72
|
+
color: #fff;
|
|
73
|
+
fill: #fff;
|
|
74
|
+
}
|
|
@@ -43,6 +43,22 @@ const groupWithoutLink: SidebarData[number] = {
|
|
|
43
43
|
items: [{ text: 'Deep Page', link: '/packages/deep/page' }],
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
const launchpad: SidebarData[number] = {
|
|
47
|
+
text: 'Launchpad',
|
|
48
|
+
link: '/launchpad',
|
|
49
|
+
collapsible: true,
|
|
50
|
+
collapsed: true,
|
|
51
|
+
items: [
|
|
52
|
+
{
|
|
53
|
+
text: 'Concepts',
|
|
54
|
+
collapsible: true,
|
|
55
|
+
collapsed: false,
|
|
56
|
+
items: [{ text: 'Architecture', link: '/launchpad/concepts/architecture' }],
|
|
57
|
+
},
|
|
58
|
+
{ text: 'Getting Started', link: '/launchpad/getting-started' },
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
|
|
46
62
|
const fullSidebar: SidebarData = [gettingStarted, packages, contributing]
|
|
47
63
|
const scopes: readonly string[] = ['/packages', '/contributing']
|
|
48
64
|
|
|
@@ -60,25 +76,49 @@ describe('resolveScopedSidebar()', () => {
|
|
|
60
76
|
expect(result[0]).toMatchObject({ text: 'Getting Started' })
|
|
61
77
|
})
|
|
62
78
|
|
|
63
|
-
it('should
|
|
79
|
+
it('should flatten the matching standalone section on an exact scope path', () => {
|
|
64
80
|
const result = resolveScopedSidebar(fullSidebar, '/packages', scopes)
|
|
65
81
|
|
|
66
|
-
expect(result).
|
|
67
|
-
|
|
82
|
+
expect(result).toStrictEqual([
|
|
83
|
+
{ text: 'Packages', link: '/packages' },
|
|
84
|
+
{ text: 'CLI', link: '/packages/cli' },
|
|
85
|
+
{ text: 'Core', link: '/packages/core' },
|
|
86
|
+
])
|
|
68
87
|
})
|
|
69
88
|
|
|
70
|
-
it('should
|
|
89
|
+
it('should flatten the matching standalone section on a nested scope path', () => {
|
|
71
90
|
const result = resolveScopedSidebar(fullSidebar, '/packages/cli', scopes)
|
|
72
91
|
|
|
73
|
-
expect(result).
|
|
74
|
-
|
|
92
|
+
expect(result).toStrictEqual([
|
|
93
|
+
{ text: 'Packages', link: '/packages' },
|
|
94
|
+
{ text: 'CLI', link: '/packages/cli' },
|
|
95
|
+
{ text: 'Core', link: '/packages/core' },
|
|
96
|
+
])
|
|
75
97
|
})
|
|
76
98
|
|
|
77
|
-
it('should
|
|
99
|
+
it('should flatten contributing items on the contributing scope', () => {
|
|
78
100
|
const result = resolveScopedSidebar(fullSidebar, '/contributing/guides', scopes)
|
|
79
101
|
|
|
80
|
-
expect(result).
|
|
81
|
-
|
|
102
|
+
expect(result).toStrictEqual([
|
|
103
|
+
{ text: 'Contributing', link: '/contributing' },
|
|
104
|
+
{ text: 'Guides', link: '/contributing/guides' },
|
|
105
|
+
{ text: 'Standards', link: '/contributing/standards' },
|
|
106
|
+
])
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should promote an island page before its preserved nested groups', () => {
|
|
110
|
+
const result = resolveScopedSidebar([launchpad], '/launchpad', ['/launchpad'])
|
|
111
|
+
|
|
112
|
+
expect(result).toStrictEqual([
|
|
113
|
+
{ text: 'Launchpad', link: '/launchpad' },
|
|
114
|
+
{
|
|
115
|
+
text: 'Concepts',
|
|
116
|
+
collapsible: true,
|
|
117
|
+
collapsed: false,
|
|
118
|
+
items: [{ text: 'Architecture', link: '/launchpad/concepts/architecture' }],
|
|
119
|
+
},
|
|
120
|
+
{ text: 'Getting Started', link: '/launchpad/getting-started' },
|
|
121
|
+
])
|
|
82
122
|
})
|
|
83
123
|
|
|
84
124
|
it('should not mutate the original sidebar data', () => {
|
|
@@ -19,9 +19,9 @@ type SidebarDataItem = SidebarData[number]
|
|
|
19
19
|
* Resolve the sidebar items visible for the current scope.
|
|
20
20
|
*
|
|
21
21
|
* When the current pathname matches a standalone scope (e.g. `/packages`),
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* unified sidebar.
|
|
22
|
+
* a linked section is promoted to a sidebar root before its children. An
|
|
23
|
+
* unlinked section returns only its children. For all other paths, standalone
|
|
24
|
+
* sections are excluded and the remaining sections form a unified sidebar.
|
|
25
25
|
*
|
|
26
26
|
* @param items - Full unified sidebar items
|
|
27
27
|
* @param pathname - Current decoded URL pathname
|
|
@@ -44,7 +44,7 @@ export function resolveScopedSidebar(
|
|
|
44
44
|
const all = [...items]
|
|
45
45
|
|
|
46
46
|
if (standaloneMatch) {
|
|
47
|
-
return all.filter((item) => belongsToScope(item, standaloneMatch))
|
|
47
|
+
return all.filter((item) => belongsToScope(item, standaloneMatch)).flatMap(flattenScopeRoot)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
return all.filter((item) => !scopes.some((scope) => belongsToScope(item, scope)))
|
|
@@ -77,3 +77,23 @@ export function belongsToScope(item: SidebarDataItem, scope: string): boolean {
|
|
|
77
77
|
}
|
|
78
78
|
return false
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Convert a standalone section wrapper into a normal root page followed by
|
|
83
|
+
* its nested sidebar structure.
|
|
84
|
+
*
|
|
85
|
+
* @private
|
|
86
|
+
* @param item - Matching standalone sidebar root
|
|
87
|
+
* @returns The root page and children, or the root itself when it has no children
|
|
88
|
+
*/
|
|
89
|
+
function flattenScopeRoot(item: SidebarDataItem): readonly SidebarDataItem[] {
|
|
90
|
+
if (Object.hasOwn(item, 'items')) {
|
|
91
|
+
const { items } = item as { readonly items: readonly SidebarDataItem[] }
|
|
92
|
+
if (Object.hasOwn(item, 'text') && Object.hasOwn(item, 'link')) {
|
|
93
|
+
const { text, link } = item as { readonly text: string; readonly link: string }
|
|
94
|
+
return [{ text, link }, ...items]
|
|
95
|
+
}
|
|
96
|
+
return items
|
|
97
|
+
}
|
|
98
|
+
return [item]
|
|
99
|
+
}
|
package/src/theme/index.tsx
CHANGED
|
@@ -73,6 +73,7 @@ import './components/openapi/openapi.css'
|
|
|
73
73
|
import './components/shared/accordion.css'
|
|
74
74
|
import './components/shared/columns.css'
|
|
75
75
|
import './components/shared/status-badge.css'
|
|
76
|
+
import './components/shared/issue-link.css'
|
|
76
77
|
import './components/shared/frame.css'
|
|
77
78
|
import './components/shared/tooltip.css'
|
|
78
79
|
import './components/shared/prompt.css'
|
|
@@ -101,7 +102,14 @@ export type { FeatureCardProps, FeatureItem } from './components/home/feature-ca
|
|
|
101
102
|
export { PageRail } from './components/home/page-rail'
|
|
102
103
|
export type { PageRailProps } from './components/home/page-rail'
|
|
103
104
|
export { Hero } from './components/home/hero'
|
|
104
|
-
export type {
|
|
105
|
+
export type {
|
|
106
|
+
HeroProps,
|
|
107
|
+
HeroAction,
|
|
108
|
+
HeroBackground,
|
|
109
|
+
HeroBackgroundImage,
|
|
110
|
+
HeroBackgroundSource,
|
|
111
|
+
HeroBackgroundVariants,
|
|
112
|
+
} from './components/home/hero'
|
|
105
113
|
export { HeroDemo } from './components/home/hero-demo'
|
|
106
114
|
export { TrustStrip } from './components/home/trust-strip'
|
|
107
115
|
export type { TrustStripProps } from './components/home/trust-strip'
|
|
@@ -82,10 +82,8 @@ export const MARK_CLASS = 'cp-mark'
|
|
|
82
82
|
// to 128k chars (0.19ms -> 0.45ms) across tag-name runs, attribute runs,
|
|
83
83
|
// quote runs, unbalanced parens, and unterminated quotes. The pattern this
|
|
84
84
|
// replaced *was* quadratic (2ms -> 427ms over the same range).
|
|
85
|
-
/* oxlint-disable security/detect-unsafe-regex -- disjoint alternations, measured linear */
|
|
86
85
|
const TOKEN_PATTERN =
|
|
87
86
|
/(?:\\([*=`[\]<\\]))|(?:`([^`]+)`)|(?:\*\*([\s\S]+?)\*\*)|(?:==([\s\S]+?)==)|(?:(?<!\*)\*(?=[^\s*])([^*\n]+?)(?<=[^\s*])\*(?!\*))|(?:\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)\))|(?:<(\/?)\s*([a-zA-Z][a-zA-Z0-9-]*)(?=[\s/>])((?:"[^"]*"|'[^']*'|[^>"'])*)>)/
|
|
88
|
-
/* oxlint-enable security/detect-unsafe-regex */
|
|
89
87
|
|
|
90
88
|
const ATTR_PATTERN = /([a-zA-Z-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g
|
|
91
89
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the production root URL for a base-mounted documentation site.
|
|
3
|
+
*
|
|
4
|
+
* @param params - Production origin and resolved Rspress base path
|
|
5
|
+
* @returns Absolute URL for the deployed site root
|
|
6
|
+
*/
|
|
7
|
+
export function resolveSeoSiteUrl(params: {
|
|
8
|
+
readonly origin: string
|
|
9
|
+
readonly base: string
|
|
10
|
+
}): string {
|
|
11
|
+
return new URL(params.base, params.origin).href
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolves a basename-stripped router pathname against the deployed site root.
|
|
16
|
+
*
|
|
17
|
+
* @param params - Production origin, resolved Rspress base, and router pathname
|
|
18
|
+
* @returns Absolute production URL for the page
|
|
19
|
+
*/
|
|
20
|
+
export function resolveSeoPageUrl(params: {
|
|
21
|
+
readonly origin: string
|
|
22
|
+
readonly base: string
|
|
23
|
+
readonly pathname: string
|
|
24
|
+
}): string {
|
|
25
|
+
const siteUrl = resolveSeoSiteUrl(params)
|
|
26
|
+
const relativePathname = params.pathname.replace(/^\/+/, '')
|
|
27
|
+
return new URL(relativePathname, siteUrl).href
|
|
28
|
+
}
|
|
@@ -23,9 +23,7 @@ export function syncThemeFavicon(html: HTMLElement): void {
|
|
|
23
23
|
const surface = resolveSurfaceColor(html)
|
|
24
24
|
const dataUri = buildIconDataUri(brand, shadow, surface)
|
|
25
25
|
const link = ensureFaviconLink()
|
|
26
|
-
// oxlint-disable-next-line functional/immutable-data -- boundary mutation: updating link element href
|
|
27
26
|
link.type = 'image/svg+xml'
|
|
28
|
-
// oxlint-disable-next-line functional/immutable-data -- boundary mutation: updating link element href
|
|
29
27
|
link.href = dataUri
|
|
30
28
|
}
|
|
31
29
|
|
|
@@ -118,7 +116,6 @@ function ensureFaviconLink(): HTMLLinkElement {
|
|
|
118
116
|
return existing
|
|
119
117
|
}
|
|
120
118
|
const link = globalThis.document.createElement('link')
|
|
121
|
-
// oxlint-disable-next-line functional/immutable-data -- boundary mutation: initialising newly created link element
|
|
122
119
|
link.rel = 'icon'
|
|
123
120
|
globalThis.document.head.append(link)
|
|
124
121
|
return link
|
|
@@ -288,7 +288,7 @@ html[data-cp-theme='honeycrisp'][data-cp-variant='dark'] {
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
html[data-cp-theme='honeycrisp'][data-cp-variant='light'] {
|
|
291
|
-
--cp-c-brand-1: #
|
|
291
|
+
--cp-c-brand-1: #d02020;
|
|
292
292
|
--cp-c-brand-2: #b91c1c;
|
|
293
293
|
--cp-c-brand-3: #7f1d1d;
|
|
294
294
|
--cp-c-brand-fg: #ffffff;
|
|
@@ -301,15 +301,15 @@ html[data-cp-theme='honeycrisp'][data-cp-variant='light'] {
|
|
|
301
301
|
--cp-c-warn: #d97706;
|
|
302
302
|
--cp-c-info: #2563eb;
|
|
303
303
|
--cp-c-muted: #888888;
|
|
304
|
-
--cp-c-bg: #
|
|
305
|
-
--cp-c-bg-alt: #
|
|
306
|
-
--cp-c-bg-elv: #
|
|
307
|
-
--cp-c-bg-soft: #
|
|
308
|
-
--cp-c-bg-icon: #
|
|
309
|
-
--cp-c-home-bg: #
|
|
304
|
+
--cp-c-bg: #f7f5f0;
|
|
305
|
+
--cp-c-bg-alt: #f1eee7;
|
|
306
|
+
--cp-c-bg-elv: #fffdf8;
|
|
307
|
+
--cp-c-bg-soft: #ebe7dc;
|
|
308
|
+
--cp-c-bg-icon: #d5cebe;
|
|
309
|
+
--cp-c-home-bg: #f7f5f0;
|
|
310
310
|
--cp-c-overlay-faint: rgba(0, 0, 0, 0.1);
|
|
311
|
-
--cp-c-gutter: #
|
|
312
|
-
--cp-code-block-bg: #
|
|
311
|
+
--cp-c-gutter: #f1eee7;
|
|
312
|
+
--cp-code-block-bg: #f1eee7;
|
|
313
313
|
--cp-c-text-1: #1a1a1a;
|
|
314
314
|
--cp-c-text-2: rgba(26, 26, 26, 0.72);
|
|
315
315
|
--cp-c-text-3: rgba(26, 26, 26, 0.48);
|
|
@@ -384,7 +384,7 @@ html[data-cp-theme='honeycrisp'][data-cp-variant='light'] {
|
|
|
384
384
|
--cp-oas-deprecated: var(--cp-c-text-3);
|
|
385
385
|
--cp-oas-required: #dc2626;
|
|
386
386
|
--cp-button-brand-bg: #b91c1c;
|
|
387
|
-
--cp-button-brand-hover-bg: #
|
|
387
|
+
--cp-button-brand-hover-bg: #d02020;
|
|
388
388
|
--cp-button-brand-active-bg: #7f1d1d;
|
|
389
389
|
--cp-button-brand-text: #ffffff;
|
|
390
390
|
--cp-s-1: 1px;
|
|
@@ -535,12 +535,12 @@ html[data-cp-theme='honeycrisp'][data-cp-variant='light'] {
|
|
|
535
535
|
--cp-blur-base: 8px;
|
|
536
536
|
--cp-gradient-brand: linear-gradient(135deg, var(--cp-c-brand-1), var(--cp-c-brand-3));
|
|
537
537
|
--cp-gradient-hero-title: linear-gradient(135deg, var(--cp-c-brand-1), var(--cp-c-brand-light));
|
|
538
|
-
--rp-c-bg: #
|
|
539
|
-
--rp-c-bg-alt: #
|
|
540
|
-
--rp-c-bg-dark: #
|
|
541
|
-
--rp-c-bg-mute: #
|
|
542
|
-
--rp-c-bg-soft: #
|
|
543
|
-
--rp-c-brand: #
|
|
538
|
+
--rp-c-bg: #f7f5f0;
|
|
539
|
+
--rp-c-bg-alt: #f1eee7;
|
|
540
|
+
--rp-c-bg-dark: #f1eee7;
|
|
541
|
+
--rp-c-bg-mute: #f7f5f0;
|
|
542
|
+
--rp-c-bg-soft: #ebe7dc;
|
|
543
|
+
--rp-c-brand: #d02020;
|
|
544
544
|
--rp-c-brand-light: #ef4444;
|
|
545
545
|
--rp-c-brand-lighter: #fca5a5;
|
|
546
546
|
--rp-c-brand-dark: #b91c1c;
|
|
@@ -549,15 +549,15 @@ html[data-cp-theme='honeycrisp'][data-cp-variant='light'] {
|
|
|
549
549
|
--rp-c-divider: #e2e2e2;
|
|
550
550
|
--rp-c-divider-dark: #d0d0d0;
|
|
551
551
|
--rp-c-divider-light: #e2e2e2;
|
|
552
|
-
--rp-c-link: #
|
|
552
|
+
--rp-c-link: #d02020;
|
|
553
553
|
--rp-c-text-1: #1a1a1a;
|
|
554
554
|
--rp-c-text-2: rgba(26, 26, 26, 0.72);
|
|
555
555
|
--rp-c-text-3: rgba(26, 26, 26, 0.48);
|
|
556
556
|
--rp-c-text-4: rgba(26, 26, 26, 0.48);
|
|
557
557
|
--rp-c-text-code: #1a1a1a;
|
|
558
|
-
--rp-code-block-bg: #
|
|
559
|
-
--rp-home-background-bg: #
|
|
560
|
-
--rp-home-feature-bg: #
|
|
558
|
+
--rp-code-block-bg: #f1eee7;
|
|
559
|
+
--rp-home-background-bg: #f7f5f0;
|
|
560
|
+
--rp-home-feature-bg: #ebe7dc;
|
|
561
561
|
--rp-home-hero-secondary-color: rgba(26, 26, 26, 0.72);
|
|
562
562
|
--rp-home-hero-title-color: #1a1a1a;
|
|
563
563
|
--rp-font-family-base:
|