@rimelight/cms 0.0.1
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/README.md +19 -0
- package/package.json +69 -0
- package/src/astro/BlockRenderer.astro +111 -0
- package/src/astro/PageRenderer.astro +23 -0
- package/src/astro/blocks/CalloutBlock.astro +78 -0
- package/src/astro/blocks/CardBlock.astro +43 -0
- package/src/astro/blocks/CardsBlock.astro +24 -0
- package/src/astro/blocks/CodeBlock.astro +16 -0
- package/src/astro/blocks/DialogueBlock.astro +23 -0
- package/src/astro/blocks/FileTreeBlock.astro +45 -0
- package/src/astro/blocks/ImageBlock.astro +17 -0
- package/src/astro/blocks/OrderedListBlock.astro +55 -0
- package/src/astro/blocks/ParagraphBlock.astro +65 -0
- package/src/astro/blocks/SceneBlock.astro +65 -0
- package/src/astro/blocks/ScriptBlock.astro +59 -0
- package/src/astro/blocks/SectionBlock.astro +46 -0
- package/src/astro/blocks/StepItemBlock.astro +25 -0
- package/src/astro/blocks/StepsBlock.astro +14 -0
- package/src/astro/blocks/TabItemBlock.astro +19 -0
- package/src/astro/blocks/TableBlock.astro +49 -0
- package/src/astro/blocks/TabsBlock.astro +67 -0
- package/src/astro/blocks/UnorderedListBlock.astro +55 -0
- package/src/auth/editor-guards.ts +148 -0
- package/src/auth/filter-blocks.ts +44 -0
- package/src/auth/filter-templates.ts +81 -0
- package/src/auth/permissions.ts +40 -0
- package/src/cache/purge.ts +37 -0
- package/src/client/components/RimelightBlock.tsx +487 -0
- package/src/client/components/RimelightBlockPicker.tsx +305 -0
- package/src/client/components/RimelightEditor.tsx +131 -0
- package/src/client/components/RimelightEditorLayout.tsx +143 -0
- package/src/client/components/RimelightPreview.tsx +354 -0
- package/src/client/components/RimelightPropertiesPanel.tsx +408 -0
- package/src/client/components/RimelightTemplateEditor.tsx +245 -0
- package/src/client/components/editors/CalloutEditor.tsx +185 -0
- package/src/client/components/editors/CardEditor.tsx +60 -0
- package/src/client/components/editors/CardsEditor.tsx +132 -0
- package/src/client/components/editors/CodeEditor.tsx +101 -0
- package/src/client/components/editors/DialogueEditor.tsx +52 -0
- package/src/client/components/editors/FileTreeEditor.tsx +65 -0
- package/src/client/components/editors/ImageEditor.tsx +70 -0
- package/src/client/components/editors/ParagraphEditor.tsx +314 -0
- package/src/client/components/editors/SceneEditor.tsx +245 -0
- package/src/client/components/editors/ScriptEditor.tsx +245 -0
- package/src/client/components/editors/SectionEditor.tsx +139 -0
- package/src/client/components/editors/StepItemEditor.tsx +117 -0
- package/src/client/components/editors/StepsEditor.tsx +105 -0
- package/src/client/components/editors/TabItemEditor.tsx +113 -0
- package/src/client/components/editors/TableEditor.tsx +181 -0
- package/src/client/components/editors/TabsEditor.tsx +105 -0
- package/src/client/components/index.ts +7 -0
- package/src/client/loader.ts +19 -0
- package/src/client/state/autosave.ts +122 -0
- package/src/client/state/editor-store.ts +411 -0
- package/src/client/state/history.ts +251 -0
- package/src/client/state/lock-manager.ts +89 -0
- package/src/client/styles/cms-editor.css +653 -0
- package/src/client/toast.ts +12 -0
- package/src/client/utils/sortable.ts +184 -0
- package/src/client/utils/splitpane.ts +87 -0
- package/src/client/utils/tree-sanitizer.ts +33 -0
- package/src/core/page-definitions.ts +502 -0
- package/src/core/registry.ts +99 -0
- package/src/core/template-sync.ts +75 -0
- package/src/core/types/blocks.ts +367 -0
- package/src/core/types/inline.ts +23 -0
- package/src/core/types/pages.ts +36 -0
- package/src/core/types/templates.ts +32 -0
- package/src/core/types/versioning.ts +44 -0
- package/src/core/validator.ts +28 -0
- package/src/env.d.ts +4 -0
- package/src/index.ts +47 -0
- package/src/markdown/index.ts +2 -0
- package/src/markdown/parser.ts +347 -0
- package/src/markdown/serializer.ts +219 -0
- package/src/migration.ts +155 -0
- package/src/schema/index.ts +8 -0
- package/src/schema/page_draft_locks.ts +13 -0
- package/src/schema/page_drafts.ts +18 -0
- package/src/schema/page_templates.ts +27 -0
- package/src/schema/page_version_approvals.ts +12 -0
- package/src/schema/page_version_comments.ts +14 -0
- package/src/schema/page_versions.ts +34 -0
- package/src/schema/pages.ts +42 -0
- package/src/schema/search.ts +23 -0
- package/src/search/query.ts +10 -0
- package/src/search/sync.ts +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Rimelight Entertainment Workspace
|
|
2
|
+
|
|
3
|
+
## Structure
|
|
4
|
+
|
|
5
|
+
### Apps (`/packages`)
|
|
6
|
+
|
|
7
|
+
- **`rimelight.com`**: The main company website.
|
|
8
|
+
- **`starter.rimelight.com`**: Our standardized starter template for Astro websites.
|
|
9
|
+
|
|
10
|
+
### Packages (`/packages`)
|
|
11
|
+
|
|
12
|
+
- **`@rimelight/auth`**: Authentication and authorization utilities for the Rimelight ecosystem.
|
|
13
|
+
- **`@rimelight/cli`**: The command line interface for managing Rimelight projects.
|
|
14
|
+
- **`@rimelight/cms`**: Enterprise content management, block rendering, and wiki engine.
|
|
15
|
+
- **`@rimelight/docs`**: Documentation components and utilities.
|
|
16
|
+
- **`@rimelight/i18n`**: Internationalization and localization tools.
|
|
17
|
+
- **`@rimelight/security`**: Astro security integration (CSP, SRI, and more).
|
|
18
|
+
- **`@rimelight/seo`**: SEO utilities including sitemap, robots, and meta components.
|
|
19
|
+
- **`@rimelight/ui`**: Our component library used in all our web projects.
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rimelight/cms",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Rimelight Entertainment's Content Management System Package",
|
|
6
|
+
"homepage": "https://rimelight.com/docs",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/Rimelight-Entertainment/rimelight/issues"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Rimelight Entertainment"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/Rimelight-Entertainment/rimelight.git"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": "./src/index.ts",
|
|
24
|
+
"./schema": "./src/schema/index.ts",
|
|
25
|
+
"./core/*": "./src/core/*",
|
|
26
|
+
"./astro/*": "./src/astro/*",
|
|
27
|
+
"./client/styles/*": "./src/client/styles/*",
|
|
28
|
+
"./client/components": "./src/client/components/index.ts",
|
|
29
|
+
"./client/components/*.tsx": "./src/client/components/*.tsx",
|
|
30
|
+
"./client/components/*.ts": "./src/client/components/*.ts",
|
|
31
|
+
"./client/components/*": "./src/client/components/*.tsx",
|
|
32
|
+
"./client/loader": "./src/client/loader.ts",
|
|
33
|
+
"./client/*": "./src/client/*.ts",
|
|
34
|
+
"./auth/*": "./src/auth/*",
|
|
35
|
+
"./cache/*": "./src/cache/*",
|
|
36
|
+
"./search/*": "./src/search/*"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"check": "pnpm audit --audit-level=moderate && vp check --fix && astro check"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@rimelight/i18n": "workspace:*",
|
|
46
|
+
"@rimelight/ui": "workspace:*",
|
|
47
|
+
"drizzle-orm": "0.45.2",
|
|
48
|
+
"solid-js": "1.9.15",
|
|
49
|
+
"sortablejs": "1.15.7"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@astrojs/check": "0.9.10",
|
|
53
|
+
"@rimelight/config": "workspace:*",
|
|
54
|
+
"@types/sortablejs": "1.15.9",
|
|
55
|
+
"astro": "7.2.2",
|
|
56
|
+
"better-auth": "1.6.30",
|
|
57
|
+
"typescript": "6.0.3",
|
|
58
|
+
"unocss": "66.7.5",
|
|
59
|
+
"vitest": "4.1.10"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"astro": ">=7.0.0",
|
|
63
|
+
"better-auth": ">=1.0.0"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=26.7.0"
|
|
67
|
+
},
|
|
68
|
+
"packageManager": "pnpm@11.22.0"
|
|
69
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "@rimelight/cms"
|
|
3
|
+
import ParagraphBlock from "./blocks/ParagraphBlock.astro"
|
|
4
|
+
import CalloutBlock from "./blocks/CalloutBlock.astro"
|
|
5
|
+
import ImageBlock from "./blocks/ImageBlock.astro"
|
|
6
|
+
import CodeBlock from "./blocks/CodeBlock.astro"
|
|
7
|
+
import ScriptBlock from "./blocks/ScriptBlock.astro"
|
|
8
|
+
import SceneBlock from "./blocks/SceneBlock.astro"
|
|
9
|
+
import DialogueBlock from "./blocks/DialogueBlock.astro"
|
|
10
|
+
import SectionBlock from "./blocks/SectionBlock.astro"
|
|
11
|
+
import TableBlock from "./blocks/TableBlock.astro"
|
|
12
|
+
import TabsBlock from "./blocks/TabsBlock.astro"
|
|
13
|
+
import TabItemBlock from "./blocks/TabItemBlock.astro"
|
|
14
|
+
import StepsBlock from "./blocks/StepsBlock.astro"
|
|
15
|
+
import StepItemBlock from "./blocks/StepItemBlock.astro"
|
|
16
|
+
import CardsBlock from "./blocks/CardsBlock.astro"
|
|
17
|
+
import CardBlock from "./blocks/CardBlock.astro"
|
|
18
|
+
import FileTreeBlock from "./blocks/FileTreeBlock.astro"
|
|
19
|
+
import UnorderedListBlock from "./blocks/UnorderedListBlock.astro"
|
|
20
|
+
import OrderedListBlock from "./blocks/OrderedListBlock.astro"
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
block: BaseBlock
|
|
24
|
+
locale?: string
|
|
25
|
+
sectionDepth?: number
|
|
26
|
+
index?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { block, locale = "en", sectionDepth = 0, index = 0 } = Astro.props
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
block.type === "ParagraphBlock" ? (
|
|
34
|
+
<ParagraphBlock block={block} locale={locale} />
|
|
35
|
+
) : block.type === "CalloutBlock" ? (
|
|
36
|
+
<CalloutBlock block={block} locale={locale}>
|
|
37
|
+
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
38
|
+
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
39
|
+
))}
|
|
40
|
+
</CalloutBlock>
|
|
41
|
+
) : block.type === "ImageBlock" ? (
|
|
42
|
+
<ImageBlock block={block} locale={locale} />
|
|
43
|
+
) : block.type === "CodeBlock" ? (
|
|
44
|
+
<CodeBlock block={block} locale={locale} />
|
|
45
|
+
) : block.type === "TableBlock" ? (
|
|
46
|
+
<TableBlock block={block} locale={locale} />
|
|
47
|
+
) : block.type === "TabsBlock" ? (
|
|
48
|
+
<TabsBlock block={block} locale={locale}>
|
|
49
|
+
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
50
|
+
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
51
|
+
))}
|
|
52
|
+
</TabsBlock>
|
|
53
|
+
) : block.type === "TabItemBlock" ? (
|
|
54
|
+
<TabItemBlock block={block} locale={locale} index={index} isFirst={index === 0}>
|
|
55
|
+
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
56
|
+
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
57
|
+
))}
|
|
58
|
+
</TabItemBlock>
|
|
59
|
+
) : block.type === "StepsBlock" ? (
|
|
60
|
+
<StepsBlock block={block} locale={locale}>
|
|
61
|
+
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
62
|
+
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
63
|
+
))}
|
|
64
|
+
</StepsBlock>
|
|
65
|
+
) : block.type === "StepItemBlock" ? (
|
|
66
|
+
<StepItemBlock block={block} locale={locale} index={index}>
|
|
67
|
+
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
68
|
+
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
69
|
+
))}
|
|
70
|
+
</StepItemBlock>
|
|
71
|
+
) : block.type === "CardsBlock" ? (
|
|
72
|
+
<CardsBlock block={block} locale={locale}>
|
|
73
|
+
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
74
|
+
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
75
|
+
))}
|
|
76
|
+
</CardsBlock>
|
|
77
|
+
) : block.type === "CardBlock" ? (
|
|
78
|
+
<CardBlock block={block} locale={locale} />
|
|
79
|
+
) : block.type === "FileTreeBlock" ? (
|
|
80
|
+
<FileTreeBlock block={block} locale={locale} />
|
|
81
|
+
) : block.type === "UnorderedListBlock" ? (
|
|
82
|
+
<UnorderedListBlock block={block} locale={locale} />
|
|
83
|
+
) : block.type === "OrderedListBlock" ? (
|
|
84
|
+
<OrderedListBlock block={block} locale={locale} />
|
|
85
|
+
) : block.type === "ScriptBlock" ? (
|
|
86
|
+
<ScriptBlock block={block} locale={locale}>
|
|
87
|
+
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
88
|
+
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
89
|
+
))}
|
|
90
|
+
</ScriptBlock>
|
|
91
|
+
) : block.type === "SceneBlock" ? (
|
|
92
|
+
<SceneBlock block={block} locale={locale}>
|
|
93
|
+
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
94
|
+
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
95
|
+
))}
|
|
96
|
+
</SceneBlock>
|
|
97
|
+
) : block.type === "DialogueBlock" ? (
|
|
98
|
+
<DialogueBlock block={block} locale={locale} />
|
|
99
|
+
) : block.type === "SectionBlock" ? (
|
|
100
|
+
<SectionBlock block={block} locale={locale} sectionDepth={sectionDepth}>
|
|
101
|
+
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
102
|
+
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth + 1} index={idx} />
|
|
103
|
+
))}
|
|
104
|
+
</SectionBlock>
|
|
105
|
+
) : (
|
|
106
|
+
<div class="cms-block-fallback p-4 border border-dashed border-gray-700 rounded my-2 text-sm text-gray-400">
|
|
107
|
+
Unknown Block Type: {block.type}
|
|
108
|
+
</div>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "../core/types/blocks"
|
|
3
|
+
import type { UserSessionContext } from "../auth/permissions"
|
|
4
|
+
import { filterBlocksForReader } from "../auth/filter-blocks"
|
|
5
|
+
import BlockRenderer from "./BlockRenderer.astro"
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
blocks: BaseBlock[]
|
|
9
|
+
session?: UserSessionContext
|
|
10
|
+
locale?: string
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { blocks, session, locale = "en", class: className = "" } = Astro.props
|
|
15
|
+
|
|
16
|
+
const sanitizedBlocks = filterBlocksForReader(blocks, session)
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<article class={`rimelight-cms-page ${className}`}>
|
|
20
|
+
{sanitizedBlocks.map((block) => (
|
|
21
|
+
<BlockRenderer block={block} locale={locale} />
|
|
22
|
+
))}
|
|
23
|
+
</article>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock, CalloutVariant } from "../../core/types/blocks"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
block: BaseBlock
|
|
6
|
+
locale?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { block } = Astro.props
|
|
10
|
+
const { variant = "info", to, target } = block.props || {}
|
|
11
|
+
|
|
12
|
+
const variantConfigs: Record<
|
|
13
|
+
string,
|
|
14
|
+
{
|
|
15
|
+
icon: string
|
|
16
|
+
classes: string
|
|
17
|
+
label: string
|
|
18
|
+
}
|
|
19
|
+
> = {
|
|
20
|
+
info: {
|
|
21
|
+
icon: "i-lucide-info",
|
|
22
|
+
classes:
|
|
23
|
+
"bg-blue-50/50 dark:bg-blue-950/10 border-blue-200 dark:border-blue-900/50 text-blue-800 dark:text-blue-200",
|
|
24
|
+
label: "Info"
|
|
25
|
+
},
|
|
26
|
+
success: {
|
|
27
|
+
icon: "i-lucide-check-circle",
|
|
28
|
+
classes:
|
|
29
|
+
"bg-green-50/50 dark:bg-green-950/10 border-green-200 dark:border-green-900/50 text-green-800 dark:text-green-200",
|
|
30
|
+
label: "Success"
|
|
31
|
+
},
|
|
32
|
+
warning: {
|
|
33
|
+
icon: "i-lucide-alert-triangle",
|
|
34
|
+
classes:
|
|
35
|
+
"bg-yellow-50/50 dark:bg-yellow-950/10 border-yellow-200 dark:border-yellow-900/50 text-yellow-800 dark:text-yellow-200",
|
|
36
|
+
label: "Warning"
|
|
37
|
+
},
|
|
38
|
+
error: {
|
|
39
|
+
icon: "i-lucide-alert-octagon",
|
|
40
|
+
classes:
|
|
41
|
+
"bg-red-50/50 dark:bg-red-950/10 border-red-200 dark:border-red-900/50 text-red-800 dark:text-red-200",
|
|
42
|
+
label: "Error"
|
|
43
|
+
},
|
|
44
|
+
commentary: {
|
|
45
|
+
icon: "i-lucide-message-square",
|
|
46
|
+
classes:
|
|
47
|
+
"bg-purple-50/50 dark:bg-purple-950/10 border-purple-200 dark:border-purple-900/50 text-purple-800 dark:text-purple-200",
|
|
48
|
+
label: "Commentary"
|
|
49
|
+
},
|
|
50
|
+
ideation: {
|
|
51
|
+
icon: "i-lucide-lightbulb",
|
|
52
|
+
classes:
|
|
53
|
+
"bg-amber-50/50 dark:bg-amber-950/10 border-amber-200 dark:border-amber-900/50 text-amber-800 dark:text-amber-200",
|
|
54
|
+
label: "Ideation"
|
|
55
|
+
},
|
|
56
|
+
source: {
|
|
57
|
+
icon: "i-lucide-code",
|
|
58
|
+
classes:
|
|
59
|
+
"bg-indigo-50/50 dark:bg-indigo-950/10 border-indigo-200 dark:border-indigo-900/50 text-indigo-800 dark:text-indigo-200",
|
|
60
|
+
label: "Source"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const config = (variantConfigs[variant as CalloutVariant] || variantConfigs.info)!
|
|
65
|
+
const Tag = to ? "a" : "div"
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
<Tag
|
|
69
|
+
href={to}
|
|
70
|
+
target={target}
|
|
71
|
+
class={`cms-callout border rounded-xl p-4 my-4 flex items-start gap-3 text-sm no-underline ${config.classes}`}
|
|
72
|
+
data-block-id={block.id}
|
|
73
|
+
>
|
|
74
|
+
<span class={`${config.icon} size-5 shrink-0 mt-0.5`} aria-hidden="true" />
|
|
75
|
+
<div class="flex-1 min-w-0 text-sm">
|
|
76
|
+
<slot />
|
|
77
|
+
</div>
|
|
78
|
+
</Tag>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "../../core/types/blocks"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
block: BaseBlock
|
|
6
|
+
locale?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { block } = Astro.props
|
|
10
|
+
const { title = "", description = "", icon = "i-lucide-book-open", href, badge } = block.props || {}
|
|
11
|
+
const Tag = href ? "a" : "div"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<Tag
|
|
15
|
+
href={href}
|
|
16
|
+
class="cms-card group relative block p-5 rounded-xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900/50 hover:border-primary-500/50 hover:shadow-md transition-all text-decoration-none no-underline"
|
|
17
|
+
data-block-id={block.id}
|
|
18
|
+
>
|
|
19
|
+
<div class="flex items-start justify-between gap-2">
|
|
20
|
+
<div class="flex items-center gap-3">
|
|
21
|
+
{icon && (
|
|
22
|
+
<div class="flex size-9 shrink-0 items-center justify-center rounded-lg bg-neutral-100 dark:bg-neutral-800 text-neutral-800 dark:text-neutral-200 group-hover:bg-primary-50 dark:group-hover:bg-primary-950/50 group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors">
|
|
23
|
+
<span class={`${icon} size-5`} aria-hidden="true" />
|
|
24
|
+
</div>
|
|
25
|
+
)}
|
|
26
|
+
{title && (
|
|
27
|
+
<h4 class="text-base font-semibold text-neutral-900 dark:text-white m-0 group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors">
|
|
28
|
+
{title}
|
|
29
|
+
</h4>
|
|
30
|
+
)}
|
|
31
|
+
</div>
|
|
32
|
+
{badge && (
|
|
33
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-neutral-100 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300">
|
|
34
|
+
{badge}
|
|
35
|
+
</span>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
{description && (
|
|
39
|
+
<p class="mt-3 text-sm text-neutral-600 dark:text-neutral-400 m-0">
|
|
40
|
+
{description}
|
|
41
|
+
</p>
|
|
42
|
+
)}
|
|
43
|
+
</Tag>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "../../core/types/blocks"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
block: BaseBlock
|
|
6
|
+
locale?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { block } = Astro.props
|
|
10
|
+
const { columns = 2 } = block.props || {}
|
|
11
|
+
|
|
12
|
+
const gridColsClass =
|
|
13
|
+
columns === 1
|
|
14
|
+
? "grid-cols-1"
|
|
15
|
+
: columns === 3
|
|
16
|
+
? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"
|
|
17
|
+
: columns === 4
|
|
18
|
+
? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
|
|
19
|
+
: "grid-cols-1 sm:grid-cols-2"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<div class={`cms-cards my-6 grid gap-4 ${gridColsClass}`} data-block-id={block.id}>
|
|
23
|
+
<slot />
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "@rimelight/cms"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
block: BaseBlock
|
|
6
|
+
locale?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { block } = Astro.props
|
|
10
|
+
const { code = "", language = "text" } = block.props
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div class="cms-code my-4 rounded-lg bg-gray-950 p-4 font-mono text-sm border border-gray-800 overflow-x-auto" data-block-id={block.id}>
|
|
14
|
+
<div class="text-xs text-gray-500 uppercase mb-2 select-none">{language}</div>
|
|
15
|
+
<pre class="m-0"><code>{code}</code></pre>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "@rimelight/cms"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
block: BaseBlock
|
|
6
|
+
locale?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { block } = Astro.props
|
|
10
|
+
const { character, parenthetical, line } = block.props
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div class="my-3 mx-8 max-w-md">
|
|
14
|
+
<div class="text-sm font-bold text-center text-pink-800 dark:text-pink-200">
|
|
15
|
+
{character}
|
|
16
|
+
</div>
|
|
17
|
+
{parenthetical && (
|
|
18
|
+
<div class="text-xs italic text-center text-gray-600 dark:text-gray-400 mt-0.5">
|
|
19
|
+
({parenthetical})
|
|
20
|
+
</div>
|
|
21
|
+
)}
|
|
22
|
+
<div class="text-sm text-center text-gray-700 dark:text-gray-300 mt-1">{line}</div>
|
|
23
|
+
</div>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock, FileTreeNode } from "../../core/types/blocks"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
block: BaseBlock
|
|
6
|
+
locale?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { block } = Astro.props
|
|
10
|
+
const { tree = [] } = block.props || {}
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div class="cms-file-tree my-6 rounded-xl border border-neutral-200 dark:border-neutral-800 bg-neutral-50/50 dark:bg-neutral-900/30 p-4 font-mono text-sm" data-block-id={block.id}>
|
|
14
|
+
<div class="cms-file-tree-root space-y-1">
|
|
15
|
+
{tree.map(function renderNode(node: FileTreeNode) {
|
|
16
|
+
return (
|
|
17
|
+
<div class="cms-tree-item">
|
|
18
|
+
<div class="flex items-center gap-2 py-0.5 text-neutral-800 dark:text-neutral-200">
|
|
19
|
+
<span
|
|
20
|
+
class={`${
|
|
21
|
+
node.isDir
|
|
22
|
+
? "i-lucide-folder text-amber-500"
|
|
23
|
+
: "i-lucide-file-text text-neutral-400 dark:text-neutral-500"
|
|
24
|
+
} size-4 shrink-0`}
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
/>
|
|
27
|
+
<span class={`font-medium ${node.isDir ? "text-neutral-900 dark:text-neutral-100" : ""}`}>
|
|
28
|
+
{node.name}
|
|
29
|
+
</span>
|
|
30
|
+
{node.comment && (
|
|
31
|
+
<span class="text-xs text-neutral-500 dark:text-neutral-400 italic font-sans ml-2">
|
|
32
|
+
— {node.comment}
|
|
33
|
+
</span>
|
|
34
|
+
)}
|
|
35
|
+
</div>
|
|
36
|
+
{node.children && node.children.length > 0 && (
|
|
37
|
+
<div class="pl-4 ml-2 border-l border-neutral-200 dark:border-neutral-800 space-y-1">
|
|
38
|
+
{node.children.map(renderNode)}
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
})}
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "../../core/types/blocks"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
block: BaseBlock
|
|
6
|
+
locale?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { block } = Astro.props
|
|
10
|
+
const { src = "", url = "", alt = "Image", caption = "" } = block.props || {}
|
|
11
|
+
const imageSrc = src || url
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<figure class="cms-image mx-auto my-6 flex flex-col items-center max-w-full" data-block-id={block.id}>
|
|
15
|
+
<img src={imageSrc} alt={alt || "Image"} class="h-auto max-w-full rounded-xl object-cover border border-neutral-200 dark:border-neutral-800 shadow-sm" />
|
|
16
|
+
{caption && <figcaption class="mt-2 text-center text-xs text-neutral-500 dark:text-neutral-400 italic">{caption}</figcaption>}
|
|
17
|
+
</figure>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "../../core/types/blocks"
|
|
3
|
+
import type { InlineNode } from "../../core/types/inline"
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
block: BaseBlock
|
|
7
|
+
locale?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { block } = Astro.props
|
|
11
|
+
const items = block.props?.items || block.children || []
|
|
12
|
+
|
|
13
|
+
function escapeHtml(str: string): string {
|
|
14
|
+
return (str || "")
|
|
15
|
+
.replace(/&/g, "&")
|
|
16
|
+
.replace(/</g, "<")
|
|
17
|
+
.replace(/>/g, ">")
|
|
18
|
+
.replace(/"/g, """)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderNodesToHtml(nodes: InlineNode[]): string {
|
|
22
|
+
if (!Array.isArray(nodes)) return ""
|
|
23
|
+
return nodes
|
|
24
|
+
.map((node) => {
|
|
25
|
+
if (!node) return ""
|
|
26
|
+
if (node.type === "text") {
|
|
27
|
+
let t = escapeHtml(node.text || "")
|
|
28
|
+
if (node.marks && Array.isArray(node.marks)) {
|
|
29
|
+
if (node.marks.includes("bold")) t = `<strong>${t}</strong>`
|
|
30
|
+
if (node.marks.includes("italic")) t = `<em>${t}</em>`
|
|
31
|
+
if (node.marks.includes("underline")) t = `<u>${t}</u>`
|
|
32
|
+
if (node.marks.includes("strikethrough")) t = `<s>${t}</s>`
|
|
33
|
+
if (node.marks.includes("code")) {
|
|
34
|
+
t = `<code class="bg-neutral-800 text-neutral-200 px-1.5 py-0.5 rounded text-sm font-mono">${t}</code>`
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return t
|
|
38
|
+
}
|
|
39
|
+
if (node.type === "link") {
|
|
40
|
+
const inner = node.children ? renderNodesToHtml(node.children) : escapeHtml((node as any).text || "")
|
|
41
|
+
const target = node.openInNewTab ? ' target="_blank" rel="noopener noreferrer"' : ""
|
|
42
|
+
const href = escapeHtml(node.url || "#")
|
|
43
|
+
return `<a href="${href}"${target} class="text-blue-500 hover:text-blue-400 underline underline-offset-2">${inner}</a>`
|
|
44
|
+
}
|
|
45
|
+
return ""
|
|
46
|
+
})
|
|
47
|
+
.join("")
|
|
48
|
+
}
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
<ol class="cms-ordered-list list-decimal list-inside space-y-2 my-4 text-base text-neutral-800 dark:text-neutral-200" data-block-id={block.id}>
|
|
52
|
+
{items.map((item: BaseBlock) => (
|
|
53
|
+
<li set:html={renderNodesToHtml(item.props?.content || [])} />
|
|
54
|
+
))}
|
|
55
|
+
</ol>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "../../core/types/blocks"
|
|
3
|
+
import type { InlineNode } from "../../core/types/inline"
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
block: BaseBlock
|
|
7
|
+
locale?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { block, locale = "en" } = Astro.props
|
|
11
|
+
const rawText = block.props?.text
|
|
12
|
+
|
|
13
|
+
function escapeHtml(str: string): string {
|
|
14
|
+
return (str || "")
|
|
15
|
+
.replace(/&/g, "&")
|
|
16
|
+
.replace(/</g, "<")
|
|
17
|
+
.replace(/>/g, ">")
|
|
18
|
+
.replace(/"/g, """)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderNodesToHtml(nodes: InlineNode[]): string {
|
|
22
|
+
if (!Array.isArray(nodes)) return ""
|
|
23
|
+
return nodes
|
|
24
|
+
.map((node) => {
|
|
25
|
+
if (!node) return ""
|
|
26
|
+
if (node.type === "text") {
|
|
27
|
+
let t = escapeHtml(node.text || "")
|
|
28
|
+
if (node.marks && Array.isArray(node.marks)) {
|
|
29
|
+
if (node.marks.includes("bold")) t = `<strong>${t}</strong>`
|
|
30
|
+
if (node.marks.includes("italic")) t = `<em>${t}</em>`
|
|
31
|
+
if (node.marks.includes("underline")) t = `<u>${t}</u>`
|
|
32
|
+
if (node.marks.includes("strikethrough")) t = `<s>${t}</s>`
|
|
33
|
+
if (node.marks.includes("code")) {
|
|
34
|
+
t = `<code class="bg-neutral-800 text-neutral-200 px-1.5 py-0.5 rounded text-sm font-mono">${t}</code>`
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return t
|
|
38
|
+
}
|
|
39
|
+
if (node.type === "link") {
|
|
40
|
+
const inner = node.children ? renderNodesToHtml(node.children) : escapeHtml((node as any).text || "")
|
|
41
|
+
const target = node.openInNewTab ? ' target="_blank" rel="noopener noreferrer"' : ""
|
|
42
|
+
const href = escapeHtml(node.url || "#")
|
|
43
|
+
return `<a href="${href}"${target} class="text-blue-500 hover:text-blue-400 underline underline-offset-2">${inner}</a>`
|
|
44
|
+
}
|
|
45
|
+
if (node.type === "page_mention") {
|
|
46
|
+
const title = escapeHtml(node.displayTitle || node.pageSlug || node.pageId || "Page")
|
|
47
|
+
const href = `/${escapeHtml(node.pageSlug || node.pageId)}`
|
|
48
|
+
return `<a href="${href}" class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-400 hover:bg-blue-500/20 text-sm font-medium no-underline"><span class="i-lucide-file-text size-3.5"></span>${title}</a>`
|
|
49
|
+
}
|
|
50
|
+
return ""
|
|
51
|
+
})
|
|
52
|
+
.join("")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let htmlContent = ""
|
|
56
|
+
if (Array.isArray(rawText)) {
|
|
57
|
+
htmlContent = renderNodesToHtml(rawText)
|
|
58
|
+
} else if (typeof rawText === "object" && rawText !== null) {
|
|
59
|
+
htmlContent = escapeHtml(rawText[locale] || rawText.en || "")
|
|
60
|
+
} else if (typeof rawText === "string") {
|
|
61
|
+
htmlContent = escapeHtml(rawText)
|
|
62
|
+
}
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
<p class="cms-paragraph text-base leading-relaxed text-neutral-800 dark:text-neutral-200 my-4 whitespace-pre-wrap break-words" data-block-id={block.id} set:html={htmlContent} />
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock, TimeOfDay, Setting, Transition } from "@rimelight/cms"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
block: BaseBlock
|
|
6
|
+
locale?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { block } = Astro.props
|
|
10
|
+
const { location, timeOfDay, setting, transition, description } = block.props
|
|
11
|
+
|
|
12
|
+
const timeOfDayLabels: Record<TimeOfDay, string> = {
|
|
13
|
+
MORNING: "Morning",
|
|
14
|
+
NOON: "Noon",
|
|
15
|
+
AFTERNOON: "Afternoon",
|
|
16
|
+
EVENING: "Evening",
|
|
17
|
+
NIGHT: "Night",
|
|
18
|
+
OTHER: "Other"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const settingLabels: Record<Setting, string> = {
|
|
22
|
+
INTERIOR: "INT.",
|
|
23
|
+
EXTERIOR: "EXT.",
|
|
24
|
+
OTHER: "Other"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const transitionLabels: Record<Transition, string> = {
|
|
28
|
+
CUT_TO: "CUT TO",
|
|
29
|
+
CUT_BACK_TO: "CUT BACK TO",
|
|
30
|
+
FADE_TO: "FADE TO",
|
|
31
|
+
NONE: ""
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const settingLabel = settingLabels[setting as Setting] || "Other"
|
|
35
|
+
const todLabel = timeOfDayLabels[timeOfDay as TimeOfDay] || "Other"
|
|
36
|
+
const transitionLabel = transitionLabels[transition as Transition] || ""
|
|
37
|
+
|
|
38
|
+
const headingParts = []
|
|
39
|
+
if (settingLabel && settingLabel !== "Other") headingParts.push(settingLabel)
|
|
40
|
+
if (location) headingParts.push(location)
|
|
41
|
+
if (todLabel && todLabel !== "Other") headingParts.push(`- ${todLabel}`)
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
<div class="my-4 p-3 border-l-4 border-indigo-400 bg-indigo-50/20 dark:bg-indigo-950/10 rounded-r-lg">
|
|
45
|
+
<div class="flex flex-wrap items-center gap-2 text-sm font-semibold text-indigo-800 dark:text-indigo-200 mb-2">
|
|
46
|
+
{headingParts.map((part) => (
|
|
47
|
+
<span>{part}</span>
|
|
48
|
+
))}
|
|
49
|
+
{transitionLabel && (
|
|
50
|
+
<span class="ml-auto text-[10px] italic text-gray-600 dark:text-gray-400">
|
|
51
|
+
{transitionLabel}
|
|
52
|
+
</span>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
{description && (
|
|
56
|
+
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2">
|
|
57
|
+
{description}
|
|
58
|
+
</p>
|
|
59
|
+
)}
|
|
60
|
+
{((block.children && block.children.length > 0) || (block.props?.children && block.props.children.length > 0)) && (
|
|
61
|
+
<div class="mt-3">
|
|
62
|
+
<slot />
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|