@rimelight/cms 0.0.2 → 0.0.3
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/LICENSE +21 -0
- package/package.json +16 -14
- package/src/admin/api/media-file.ts +39 -0
- package/src/admin/api/media.ts +199 -0
- package/src/admin/layouts/CMSDashboardLayout.astro +178 -0
- package/src/admin/layouts/DefaultParentLayout.astro +50 -0
- package/src/admin/pages/assets.astro +424 -0
- package/src/admin/pages/byline-schema.astro +62 -0
- package/src/admin/pages/bylines.astro +16 -0
- package/src/admin/pages/categories.astro +21 -0
- package/src/admin/pages/content-types.astro +55 -0
- package/src/admin/pages/dashboard.astro +61 -0
- package/src/admin/pages/pages-edit.astro +595 -0
- package/src/admin/pages/pages-index.astro +146 -0
- package/src/admin/pages/pages-new.astro +130 -0
- package/src/admin/pages/pages-preview.astro +111 -0
- package/src/admin/pages/pages-review.astro +236 -0
- package/src/admin/pages/settings.astro +203 -0
- package/src/admin/pages/tags.astro +21 -0
- package/src/admin/pages/templates-edit.astro +234 -0
- package/src/admin/pages/templates-index.astro +95 -0
- package/src/admin/pages/templates-new.astro +193 -0
- package/src/admin/pages/users.astro +74 -0
- package/src/admin/pages/versions.astro +74 -0
- package/src/astro/BlockRenderer.astro +44 -102
- package/src/astro/PageRenderer.astro +9 -2
- package/src/astro/blocks/CalloutBlock.astro +7 -7
- package/src/astro/blocks/CardBlock.astro +5 -5
- package/src/astro/blocks/CodeBlock.astro +2 -2
- package/src/astro/blocks/FileTreeBlock.astro +7 -7
- package/src/astro/blocks/ImageBlock.astro +2 -2
- package/src/astro/blocks/OrderedListBlock.astro +2 -2
- package/src/astro/blocks/ParagraphBlock.astro +3 -3
- package/src/astro/blocks/SectionBlock.astro +6 -6
- package/src/astro/blocks/StepItemBlock.astro +3 -3
- package/src/astro/blocks/StepsBlock.astro +1 -1
- package/src/astro/blocks/TableBlock.astro +6 -6
- package/src/astro/blocks/TabsBlock.astro +9 -9
- package/src/astro/blocks/UnorderedListBlock.astro +2 -2
- package/src/astro/blocks/index.ts +35 -0
- package/src/client/components/RimelightBlock.tsx +19 -19
- package/src/client/components/RimelightBlockPicker.tsx +9 -13
- package/src/client/components/RimelightBylinesManager.tsx +438 -0
- package/src/client/components/RimelightEditor.tsx +3 -3
- package/src/client/components/RimelightPreview.tsx +37 -45
- package/src/client/components/RimelightPropertiesPanel.tsx +368 -26
- package/src/client/components/RimelightTaxonomyManager.tsx +369 -0
- package/src/client/components/RimelightTaxonomyMasterDetail.tsx +412 -0
- package/src/client/components/RimelightTemplateEditor.tsx +6 -8
- package/src/client/components/editors/CalloutEditor.tsx +2 -4
- package/src/client/components/editors/CardsEditor.tsx +2 -4
- package/src/client/components/editors/CodeEditor.tsx +1 -3
- package/src/client/components/editors/FileTreeEditor.tsx +2 -4
- package/src/client/components/editors/ImageEditor.tsx +3 -3
- package/src/client/components/editors/ParagraphEditor.tsx +2 -2
- package/src/client/components/editors/SceneEditor.tsx +6 -12
- package/src/client/components/editors/ScriptEditor.tsx +8 -16
- package/src/client/components/editors/SectionEditor.tsx +4 -4
- package/src/client/components/editors/StepItemEditor.tsx +2 -2
- package/src/client/components/editors/StepsEditor.tsx +2 -4
- package/src/client/components/editors/TabItemEditor.tsx +3 -3
- package/src/client/components/editors/TableEditor.tsx +5 -5
- package/src/client/components/editors/TabsEditor.tsx +2 -4
- package/src/client/components/index.ts +3 -0
- package/src/client/loader.ts +3 -0
- package/src/core/diff.ts +145 -0
- package/src/core/excerpt.ts +159 -0
- package/src/core/preview.ts +87 -0
- package/src/core/registry.ts +0 -86
- package/src/core/types/blocks.ts +6 -1
- package/src/core/types/bylines.ts +14 -0
- package/src/core/types/media.ts +13 -0
- package/src/core/types/taxonomies.ts +20 -0
- package/src/cron/scheduled.ts +57 -0
- package/src/docs/agent.ts +116 -0
- package/src/docs/config.ts +25 -0
- package/src/docs/index.ts +5 -0
- package/src/docs/routing.ts +104 -0
- package/src/docs/sidebar.ts +251 -0
- package/src/docs/toc.ts +47 -0
- package/src/env.d.ts +13 -0
- package/src/index.ts +40 -8
- package/src/integration.ts +196 -0
- package/src/loader/live.ts +254 -0
- package/src/markdown/index.ts +0 -1
- package/src/mcp/index.ts +228 -0
- package/src/schema/bylines.ts +23 -0
- package/src/schema/index.ts +3 -4
- package/src/schema/page_versions.ts +2 -0
- package/src/schema/pages.ts +2 -0
- package/src/schema/site_settings.ts +41 -0
- package/src/schema/taxonomies.ts +49 -0
- package/src/services/bylines.ts +74 -0
- package/src/services/search-indexer.ts +61 -0
- package/src/services/site-settings.ts +108 -0
- package/src/services/taxonomies.ts +276 -0
- package/src/storage/index.ts +253 -0
- package/src/astro/blocks/ApiDocumentationBlock.astro +0 -350
- package/src/astro/blocks/DialogueBlock.astro +0 -23
- package/src/astro/blocks/LivePreviewBlock.astro +0 -27
- package/src/astro/blocks/SceneBlock.astro +0 -65
- package/src/astro/blocks/ScriptBlock.astro +0 -59
- package/src/core/template-sync.ts +0 -75
- package/src/markdown/parser.ts +0 -347
- package/src/migration-tools.ts +0 -334
- package/src/migration.ts +0 -155
- package/src/schema/component_dependencies.ts +0 -21
- package/src/schema/component_metadata.ts +0 -31
- package/src/schema/component_metadata_cache.ts +0 -15
- package/src/schema/component_versions.ts +0 -19
- package/src/search/sync.ts +0 -32
- package/src/services/component-metadata.ts +0 -134
package/src/core/registry.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { TimeOfDay, Setting, Transition } from "./types/blocks"
|
|
2
|
-
|
|
3
1
|
export interface BlockDefinition {
|
|
4
2
|
type: string
|
|
5
3
|
label: string
|
|
@@ -60,87 +58,3 @@ BlockRegistry.register({
|
|
|
60
58
|
description: "Code block with syntax highlighting",
|
|
61
59
|
defaultProps: { code: "", language: "typescript" }
|
|
62
60
|
})
|
|
63
|
-
|
|
64
|
-
BlockRegistry.register({
|
|
65
|
-
type: "ScriptBlock",
|
|
66
|
-
label: "Script",
|
|
67
|
-
description: "Script block with title, synopsis, characters, and plot tracking",
|
|
68
|
-
defaultProps: {
|
|
69
|
-
title: "",
|
|
70
|
-
synopsis: "",
|
|
71
|
-
characters: [],
|
|
72
|
-
aPlot: "",
|
|
73
|
-
bPlot: "",
|
|
74
|
-
cPlot: "",
|
|
75
|
-
stinger: ""
|
|
76
|
-
},
|
|
77
|
-
allowChildren: true
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
BlockRegistry.register({
|
|
81
|
-
type: "SceneBlock",
|
|
82
|
-
label: "Scene",
|
|
83
|
-
description: "Scene block with location, time, setting, and transition",
|
|
84
|
-
defaultProps: {
|
|
85
|
-
location: "",
|
|
86
|
-
timeOfDay: "OTHER" as TimeOfDay,
|
|
87
|
-
setting: "OTHER" as Setting,
|
|
88
|
-
transition: "NONE" as Transition,
|
|
89
|
-
description: ""
|
|
90
|
-
},
|
|
91
|
-
allowChildren: true
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
BlockRegistry.register({
|
|
95
|
-
type: "DialogueBlock",
|
|
96
|
-
label: "Dialogue",
|
|
97
|
-
description: "Dialogue block with character, parenthetical, and line",
|
|
98
|
-
defaultProps: { character: "", parenthetical: "", line: "" }
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
BlockRegistry.register({
|
|
102
|
-
type: "ComponentShowcaseBlock",
|
|
103
|
-
label: "Component Showcase",
|
|
104
|
-
description: "Live component rendering with props injection and multi-framework code examples",
|
|
105
|
-
defaultProps: {
|
|
106
|
-
componentName: "",
|
|
107
|
-
componentPath: "",
|
|
108
|
-
framework: "astro",
|
|
109
|
-
defaultProps: {},
|
|
110
|
-
propsConfig: {},
|
|
111
|
-
togglingPattern: "tab-segmented",
|
|
112
|
-
codeExamples: {}
|
|
113
|
-
},
|
|
114
|
-
allowChildren: true
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
BlockRegistry.register({
|
|
118
|
-
type: "ApiDocumentationBlock",
|
|
119
|
-
label: "API Documentation",
|
|
120
|
-
description: "Automated API documentation generation for component props, slots, emits, and theme",
|
|
121
|
-
defaultProps: {
|
|
122
|
-
componentName: "",
|
|
123
|
-
sections: {
|
|
124
|
-
props: true,
|
|
125
|
-
slots: true,
|
|
126
|
-
emits: true,
|
|
127
|
-
theme: true,
|
|
128
|
-
changelog: false
|
|
129
|
-
},
|
|
130
|
-
hide: [],
|
|
131
|
-
show: []
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
BlockRegistry.register({
|
|
136
|
-
type: "LivePreviewBlock",
|
|
137
|
-
label: "Live Preview",
|
|
138
|
-
description: "Runtime code execution with live editing and error handling",
|
|
139
|
-
defaultProps: {
|
|
140
|
-
componentCode: "",
|
|
141
|
-
framework: "astro",
|
|
142
|
-
height: "300px",
|
|
143
|
-
width: "100%",
|
|
144
|
-
editable: false
|
|
145
|
-
}
|
|
146
|
-
})
|
package/src/core/types/blocks.ts
CHANGED
|
@@ -120,10 +120,15 @@ export interface CalloutBlockProps {
|
|
|
120
120
|
children: BaseBlock[]
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
import type { MediaValue } from "./media"
|
|
124
|
+
|
|
123
125
|
export interface ImageBlockProps {
|
|
124
|
-
src: string
|
|
126
|
+
src: string | MediaValue
|
|
125
127
|
alt: string
|
|
126
128
|
caption?: string
|
|
129
|
+
width?: number
|
|
130
|
+
height?: number
|
|
131
|
+
aspectRatio?: string
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
export interface CodeBlockProps {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type BylineRole =
|
|
2
|
+
| "written_by"
|
|
3
|
+
| "reviewed_by"
|
|
4
|
+
| "edited_by"
|
|
5
|
+
| "illustrated_by"
|
|
6
|
+
| "translated_by"
|
|
7
|
+
| "curated_by"
|
|
8
|
+
|
|
9
|
+
export interface BylineCredit {
|
|
10
|
+
userId: string
|
|
11
|
+
role: BylineRole
|
|
12
|
+
customTitle?: string
|
|
13
|
+
displayOrder?: number
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface MediaValue {
|
|
2
|
+
id: string
|
|
3
|
+
src: string
|
|
4
|
+
alt: string
|
|
5
|
+
width?: number
|
|
6
|
+
height?: number
|
|
7
|
+
format?: string
|
|
8
|
+
size?: number
|
|
9
|
+
blurhash?: string
|
|
10
|
+
previewUrl?: string
|
|
11
|
+
provider: "r2" | "external" | "local"
|
|
12
|
+
meta?: Record<string, unknown>
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Localized } from "./pages"
|
|
2
|
+
|
|
3
|
+
export interface TaxonomyTerm {
|
|
4
|
+
id: string
|
|
5
|
+
taxonomy: string
|
|
6
|
+
slug: string
|
|
7
|
+
label: Localized | string
|
|
8
|
+
description?: Localized | string | null
|
|
9
|
+
parentId?: string | null
|
|
10
|
+
displayOrder?: number
|
|
11
|
+
children?: TaxonomyTerm[]
|
|
12
|
+
count?: number
|
|
13
|
+
createdAt?: Date
|
|
14
|
+
updatedAt?: Date
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TaxonomyQueryOptions {
|
|
18
|
+
includeCounts?: boolean
|
|
19
|
+
locale?: string
|
|
20
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { and, isNotNull, isNull, lt, lte } from "drizzle-orm"
|
|
2
|
+
import { pageDraftLocks } from "../schema/page_draft_locks.ts"
|
|
3
|
+
import { pages } from "../schema/pages.ts"
|
|
4
|
+
|
|
5
|
+
export interface CmsCronResult {
|
|
6
|
+
expiredLocksCleaned: number
|
|
7
|
+
scheduledPagesActive: number
|
|
8
|
+
timestamp: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handles the periodic Cloudflare Worker cron trigger (e.g. "* * * * *").
|
|
13
|
+
*
|
|
14
|
+
* - Cleans up expired page draft locks
|
|
15
|
+
* - Checks for active scheduled pages
|
|
16
|
+
*/
|
|
17
|
+
export async function handleCmsCron(
|
|
18
|
+
_event: any,
|
|
19
|
+
_env: any,
|
|
20
|
+
_ctx: any,
|
|
21
|
+
db: any
|
|
22
|
+
): Promise<CmsCronResult> {
|
|
23
|
+
const result: CmsCronResult = {
|
|
24
|
+
expiredLocksCleaned: 0,
|
|
25
|
+
scheduledPagesActive: 0,
|
|
26
|
+
timestamp: new Date().toISOString()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!db) return result
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const now = new Date()
|
|
33
|
+
|
|
34
|
+
// 1. Purge expired draft edit locks
|
|
35
|
+
const deletedLocks = await db
|
|
36
|
+
.delete(pageDraftLocks)
|
|
37
|
+
.where(lt(pageDraftLocks.expiresAt, now))
|
|
38
|
+
.returning({ pageId: pageDraftLocks.pageId })
|
|
39
|
+
|
|
40
|
+
result.expiredLocksCleaned = deletedLocks.length
|
|
41
|
+
|
|
42
|
+
// 2. Query active published pages in current window
|
|
43
|
+
const activeScheduled = await db
|
|
44
|
+
.select({ id: pages.id, slug: pages.slug })
|
|
45
|
+
.from(pages)
|
|
46
|
+
.where(
|
|
47
|
+
and(isNull(pages.deletedAt), isNotNull(pages.publishedVersionId), lte(pages.postedAt, now))
|
|
48
|
+
)
|
|
49
|
+
.limit(10)
|
|
50
|
+
|
|
51
|
+
result.scheduledPagesActive = activeScheduled.length
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("[CmsCron] Error executing scheduled tasks:", error)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { blocksToMarkdown } from "../markdown/index.ts"
|
|
2
|
+
import { pages } from "../schema/pages.ts"
|
|
3
|
+
import { eq, and, isNull, isNotNull } from "drizzle-orm"
|
|
4
|
+
|
|
5
|
+
export interface AgentMarkdownOptions {
|
|
6
|
+
siteUrl?: string
|
|
7
|
+
llmsUrl?: string
|
|
8
|
+
sourceUrl?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function resolveLocalized(val: unknown, locale: string = "en"): string {
|
|
12
|
+
if (!val) return ""
|
|
13
|
+
if (typeof val === "string") return val
|
|
14
|
+
if (typeof val === "object" && val !== null) {
|
|
15
|
+
const rec = val as Record<string, string>
|
|
16
|
+
return rec[locale] || rec.en || Object.values(rec)[0] || ""
|
|
17
|
+
}
|
|
18
|
+
return typeof val === "number" ? String(val) : ""
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Renders a CMS page record into clean, agent-ready Markdown with YAML frontmatter.
|
|
23
|
+
*/
|
|
24
|
+
export function renderPageAsMarkdown(
|
|
25
|
+
page: any,
|
|
26
|
+
locale: string = "en",
|
|
27
|
+
options: AgentMarkdownOptions = {}
|
|
28
|
+
): string {
|
|
29
|
+
const { siteUrl = "https://rimelight.com", llmsUrl = "/llms.txt", sourceUrl } = options
|
|
30
|
+
|
|
31
|
+
let contentObj = page.content
|
|
32
|
+
if (typeof contentObj === "string") {
|
|
33
|
+
try {
|
|
34
|
+
contentObj = JSON.parse(contentObj)
|
|
35
|
+
} catch {
|
|
36
|
+
contentObj = { blocks: [], properties: {} }
|
|
37
|
+
}
|
|
38
|
+
} else if (!contentObj || typeof contentObj !== "object") {
|
|
39
|
+
contentObj = { blocks: [], properties: {} }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const blocks = Array.isArray(contentObj.blocks) ? contentObj.blocks : []
|
|
43
|
+
const properties = contentObj.properties || {}
|
|
44
|
+
|
|
45
|
+
const title = resolveLocalized(page.title, locale) || properties.title || "Untitled"
|
|
46
|
+
const description = resolveLocalized(page.description, locale) || properties.description || ""
|
|
47
|
+
const version = properties.version || page.templateVersion
|
|
48
|
+
const markdownBody = blocksToMarkdown(blocks)
|
|
49
|
+
|
|
50
|
+
const frontmatter = [
|
|
51
|
+
"---",
|
|
52
|
+
`title: ${JSON.stringify(title)}`,
|
|
53
|
+
...(description ? [`description: ${JSON.stringify(description)}`] : []),
|
|
54
|
+
...(version ? [`version: ${JSON.stringify(version)}`] : []),
|
|
55
|
+
...(page.type ? [`type: ${JSON.stringify(page.type)}`] : []),
|
|
56
|
+
"---",
|
|
57
|
+
"",
|
|
58
|
+
"> Documentation Index",
|
|
59
|
+
`> Fetch the complete documentation index at: ${new URL(llmsUrl, siteUrl).href}`,
|
|
60
|
+
"> Use this file to discover all available pages and sections.",
|
|
61
|
+
"",
|
|
62
|
+
`# ${title}`,
|
|
63
|
+
"",
|
|
64
|
+
markdownBody,
|
|
65
|
+
""
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
if (sourceUrl) {
|
|
69
|
+
frontmatter.push(`Source: ${new URL(sourceUrl, siteUrl).href}`, "")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return frontmatter.join("\n")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Collate all published pages of a given type into a single corpus markdown document.
|
|
77
|
+
*/
|
|
78
|
+
export async function renderCorpusMarkdown(
|
|
79
|
+
db: any,
|
|
80
|
+
options: {
|
|
81
|
+
type?: string
|
|
82
|
+
locale?: string
|
|
83
|
+
siteUrl?: string
|
|
84
|
+
title?: string
|
|
85
|
+
description?: string
|
|
86
|
+
} = {}
|
|
87
|
+
): Promise<string> {
|
|
88
|
+
const {
|
|
89
|
+
type = "doc",
|
|
90
|
+
locale = "en",
|
|
91
|
+
siteUrl = "https://rimelight.com",
|
|
92
|
+
title = "Rimelight Documentation Corpus",
|
|
93
|
+
description = "Complete single-corpus documentation for AI agents."
|
|
94
|
+
} = options
|
|
95
|
+
|
|
96
|
+
if (!db) return `# ${title}\n\n${description}`
|
|
97
|
+
|
|
98
|
+
const conditions = [isNull(pages.deletedAt), isNotNull(pages.publishedVersionId)]
|
|
99
|
+
if (type) {
|
|
100
|
+
conditions.push(eq(pages.type, type))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const rows = await db
|
|
104
|
+
.select()
|
|
105
|
+
.from(pages)
|
|
106
|
+
.where(and(...conditions))
|
|
107
|
+
|
|
108
|
+
const parts = [`# ${title}`, "", description, "", `Generated for site: ${siteUrl}`, "", "---", ""]
|
|
109
|
+
|
|
110
|
+
for (const page of rows) {
|
|
111
|
+
const pageMd = renderPageAsMarkdown(page, locale, { siteUrl })
|
|
112
|
+
parts.push(pageMd, "", "---", "")
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return parts.join("\n")
|
|
116
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SidebarItem, SidebarScope } from "@rimelight/ui/components/sidebar/sidebar.ts"
|
|
2
|
+
import type { BadgeProps } from "@rimelight/ui/components/badge/badge.ts"
|
|
3
|
+
|
|
4
|
+
export interface EditLinkConfig {
|
|
5
|
+
baseUrl: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TableOfContentsConfig {
|
|
9
|
+
minHeadingLevel?: number
|
|
10
|
+
maxHeadingLevel?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DocsConfig {
|
|
14
|
+
basePath?: string
|
|
15
|
+
sidebar:
|
|
16
|
+
| (SidebarItem | SidebarScope)[]
|
|
17
|
+
| {
|
|
18
|
+
scopes: SidebarScope[]
|
|
19
|
+
}
|
|
20
|
+
editLink?: EditLinkConfig
|
|
21
|
+
tableOfContents?: false | TableOfContentsConfig
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const defineDocsConfig = <const T extends DocsConfig>(config: T): T => config
|
|
25
|
+
export type { BadgeProps, SidebarItem, SidebarScope }
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export interface DocEntry {
|
|
2
|
+
id?: string
|
|
3
|
+
slug?: string
|
|
4
|
+
version?: string
|
|
5
|
+
properties?: Record<string, any>
|
|
6
|
+
[key: string]: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getDocsVersions(entries: (DocEntry | string)[], locale?: string): string[] {
|
|
10
|
+
const versions = new Set<string>()
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
if (typeof entry === "string") {
|
|
13
|
+
const parts = entry.replace(/\\/g, "/").split("/")
|
|
14
|
+
if (parts[0]?.startsWith("v")) {
|
|
15
|
+
versions.add(parts[0])
|
|
16
|
+
}
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (entry.version && entry.version.startsWith("v")) {
|
|
21
|
+
versions.add(entry.version)
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (entry.properties?.version && String(entry.properties.version).startsWith("v")) {
|
|
26
|
+
versions.add(String(entry.properties.version))
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const path = (entry.slug || entry.id || "").replace(/\\/g, "/")
|
|
31
|
+
const parts = path.split("/")
|
|
32
|
+
if (locale && parts[1] === locale && parts[0]?.startsWith("v")) {
|
|
33
|
+
versions.add(parts[0])
|
|
34
|
+
} else if (parts[0]?.startsWith("v")) {
|
|
35
|
+
versions.add(parts[0])
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return Array.from(versions).toSorted((a, b) => {
|
|
39
|
+
return b.localeCompare(a, undefined, { numeric: true, sensitivity: "base" })
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function parseDocsSlug(slug: string | undefined, versions: string[]) {
|
|
44
|
+
if (versions.length === 0) {
|
|
45
|
+
return {
|
|
46
|
+
activeVersion: "",
|
|
47
|
+
contentPath: slug || "index",
|
|
48
|
+
includeVersionInUrl: false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const defaultVersion = versions[0]
|
|
53
|
+
const isMultiVersion = versions.length > 1
|
|
54
|
+
|
|
55
|
+
if (!slug) {
|
|
56
|
+
return {
|
|
57
|
+
activeVersion: defaultVersion,
|
|
58
|
+
contentPath: "index",
|
|
59
|
+
includeVersionInUrl: isMultiVersion
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const parts = slug.split("/")
|
|
64
|
+
const firstPart = parts[0]
|
|
65
|
+
|
|
66
|
+
if (firstPart && versions.includes(firstPart)) {
|
|
67
|
+
return {
|
|
68
|
+
activeVersion: firstPart,
|
|
69
|
+
contentPath: parts.slice(1).join("/") || "index",
|
|
70
|
+
includeVersionInUrl: isMultiVersion
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
activeVersion: defaultVersion,
|
|
76
|
+
contentPath: slug || "index",
|
|
77
|
+
includeVersionInUrl: isMultiVersion
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function formatDocsUrl(
|
|
82
|
+
href: string,
|
|
83
|
+
locale: string,
|
|
84
|
+
activeVersion?: string,
|
|
85
|
+
includeVersionInUrl?: boolean
|
|
86
|
+
): string {
|
|
87
|
+
let url = href.replace("{locale}", locale)
|
|
88
|
+
if (activeVersion && includeVersionInUrl) {
|
|
89
|
+
if (url.includes(`/${activeVersion}/`) || url.endsWith(`/${activeVersion}`)) {
|
|
90
|
+
return url
|
|
91
|
+
}
|
|
92
|
+
const docsPattern = new RegExp(`(/${locale}/docs)(/|$)`)
|
|
93
|
+
const companyDocsPattern = new RegExp(`(/${locale}/company/docs)(/|$)`)
|
|
94
|
+
|
|
95
|
+
if (companyDocsPattern.test(url)) {
|
|
96
|
+
url = url.replace(companyDocsPattern, `$1/${activeVersion}$2`)
|
|
97
|
+
} else if (docsPattern.test(url)) {
|
|
98
|
+
url = url.replace(docsPattern, `$1/${activeVersion}$2`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
url = url.replace(/\/{2,}/g, "/")
|
|
102
|
+
}
|
|
103
|
+
return url
|
|
104
|
+
}
|