@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.
Files changed (87) hide show
  1. package/README.md +19 -0
  2. package/package.json +69 -0
  3. package/src/astro/BlockRenderer.astro +111 -0
  4. package/src/astro/PageRenderer.astro +23 -0
  5. package/src/astro/blocks/CalloutBlock.astro +78 -0
  6. package/src/astro/blocks/CardBlock.astro +43 -0
  7. package/src/astro/blocks/CardsBlock.astro +24 -0
  8. package/src/astro/blocks/CodeBlock.astro +16 -0
  9. package/src/astro/blocks/DialogueBlock.astro +23 -0
  10. package/src/astro/blocks/FileTreeBlock.astro +45 -0
  11. package/src/astro/blocks/ImageBlock.astro +17 -0
  12. package/src/astro/blocks/OrderedListBlock.astro +55 -0
  13. package/src/astro/blocks/ParagraphBlock.astro +65 -0
  14. package/src/astro/blocks/SceneBlock.astro +65 -0
  15. package/src/astro/blocks/ScriptBlock.astro +59 -0
  16. package/src/astro/blocks/SectionBlock.astro +46 -0
  17. package/src/astro/blocks/StepItemBlock.astro +25 -0
  18. package/src/astro/blocks/StepsBlock.astro +14 -0
  19. package/src/astro/blocks/TabItemBlock.astro +19 -0
  20. package/src/astro/blocks/TableBlock.astro +49 -0
  21. package/src/astro/blocks/TabsBlock.astro +67 -0
  22. package/src/astro/blocks/UnorderedListBlock.astro +55 -0
  23. package/src/auth/editor-guards.ts +148 -0
  24. package/src/auth/filter-blocks.ts +44 -0
  25. package/src/auth/filter-templates.ts +81 -0
  26. package/src/auth/permissions.ts +40 -0
  27. package/src/cache/purge.ts +37 -0
  28. package/src/client/components/RimelightBlock.tsx +487 -0
  29. package/src/client/components/RimelightBlockPicker.tsx +305 -0
  30. package/src/client/components/RimelightEditor.tsx +131 -0
  31. package/src/client/components/RimelightEditorLayout.tsx +143 -0
  32. package/src/client/components/RimelightPreview.tsx +354 -0
  33. package/src/client/components/RimelightPropertiesPanel.tsx +408 -0
  34. package/src/client/components/RimelightTemplateEditor.tsx +245 -0
  35. package/src/client/components/editors/CalloutEditor.tsx +185 -0
  36. package/src/client/components/editors/CardEditor.tsx +60 -0
  37. package/src/client/components/editors/CardsEditor.tsx +132 -0
  38. package/src/client/components/editors/CodeEditor.tsx +101 -0
  39. package/src/client/components/editors/DialogueEditor.tsx +52 -0
  40. package/src/client/components/editors/FileTreeEditor.tsx +65 -0
  41. package/src/client/components/editors/ImageEditor.tsx +70 -0
  42. package/src/client/components/editors/ParagraphEditor.tsx +314 -0
  43. package/src/client/components/editors/SceneEditor.tsx +245 -0
  44. package/src/client/components/editors/ScriptEditor.tsx +245 -0
  45. package/src/client/components/editors/SectionEditor.tsx +139 -0
  46. package/src/client/components/editors/StepItemEditor.tsx +117 -0
  47. package/src/client/components/editors/StepsEditor.tsx +105 -0
  48. package/src/client/components/editors/TabItemEditor.tsx +113 -0
  49. package/src/client/components/editors/TableEditor.tsx +181 -0
  50. package/src/client/components/editors/TabsEditor.tsx +105 -0
  51. package/src/client/components/index.ts +7 -0
  52. package/src/client/loader.ts +19 -0
  53. package/src/client/state/autosave.ts +122 -0
  54. package/src/client/state/editor-store.ts +411 -0
  55. package/src/client/state/history.ts +251 -0
  56. package/src/client/state/lock-manager.ts +89 -0
  57. package/src/client/styles/cms-editor.css +653 -0
  58. package/src/client/toast.ts +12 -0
  59. package/src/client/utils/sortable.ts +184 -0
  60. package/src/client/utils/splitpane.ts +87 -0
  61. package/src/client/utils/tree-sanitizer.ts +33 -0
  62. package/src/core/page-definitions.ts +502 -0
  63. package/src/core/registry.ts +99 -0
  64. package/src/core/template-sync.ts +75 -0
  65. package/src/core/types/blocks.ts +367 -0
  66. package/src/core/types/inline.ts +23 -0
  67. package/src/core/types/pages.ts +36 -0
  68. package/src/core/types/templates.ts +32 -0
  69. package/src/core/types/versioning.ts +44 -0
  70. package/src/core/validator.ts +28 -0
  71. package/src/env.d.ts +4 -0
  72. package/src/index.ts +47 -0
  73. package/src/markdown/index.ts +2 -0
  74. package/src/markdown/parser.ts +347 -0
  75. package/src/markdown/serializer.ts +219 -0
  76. package/src/migration.ts +155 -0
  77. package/src/schema/index.ts +8 -0
  78. package/src/schema/page_draft_locks.ts +13 -0
  79. package/src/schema/page_drafts.ts +18 -0
  80. package/src/schema/page_templates.ts +27 -0
  81. package/src/schema/page_version_approvals.ts +12 -0
  82. package/src/schema/page_version_comments.ts +14 -0
  83. package/src/schema/page_versions.ts +34 -0
  84. package/src/schema/pages.ts +42 -0
  85. package/src/schema/search.ts +23 -0
  86. package/src/search/query.ts +10 -0
  87. 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, "&amp;")
16
+ .replace(/</g, "&lt;")
17
+ .replace(/>/g, "&gt;")
18
+ .replace(/"/g, "&quot;")
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, "&amp;")
16
+ .replace(/</g, "&lt;")
17
+ .replace(/>/g, "&gt;")
18
+ .replace(/"/g, "&quot;")
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>