@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
|
@@ -1,65 +0,0 @@
|
|
|
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>
|
|
@@ -1,59 +0,0 @@
|
|
|
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 { title, synopsis, characters, aPlot, bPlot, cPlot, stinger } = block.props
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
<div class="my-6 p-4 border-l-4 border-amber-400 bg-amber-50/20 dark:bg-amber-950/10 rounded-r-lg">
|
|
14
|
-
<div class="mb-4">
|
|
15
|
-
<h3 class="text-xl font-bold text-amber-900 dark:text-amber-100">{title}</h3>
|
|
16
|
-
{synopsis && <p class="text-sm text-gray-600 dark:text-gray-400 mt-1 italic">{synopsis}</p>}
|
|
17
|
-
{characters && characters.length > 0 && (
|
|
18
|
-
<div class="flex flex-wrap gap-3 mt-3 text-xs">
|
|
19
|
-
{characters.map((char: string) => (
|
|
20
|
-
<span class="px-2 py-0.5 rounded-full bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-200">
|
|
21
|
-
{char}
|
|
22
|
-
</span>
|
|
23
|
-
))}
|
|
24
|
-
</div>
|
|
25
|
-
)}
|
|
26
|
-
</div>
|
|
27
|
-
{(aPlot || bPlot || cPlot) && (
|
|
28
|
-
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 my-3">
|
|
29
|
-
{aPlot && (
|
|
30
|
-
<div class="bg-white/50 dark:bg-neutral-900/30 rounded p-2 border border-gray-200 dark:border-gray-700">
|
|
31
|
-
<div class="text-[10px] font-bold uppercase tracking-wider text-gray-600 dark:text-gray-400">A Plot</div>
|
|
32
|
-
<div class="text-sm mt-0.5">{aPlot}</div>
|
|
33
|
-
</div>
|
|
34
|
-
)}
|
|
35
|
-
{bPlot && (
|
|
36
|
-
<div class="bg-white/50 dark:bg-neutral-900/30 rounded p-2 border border-gray-200 dark:border-gray-700">
|
|
37
|
-
<div class="text-[10px] font-bold uppercase tracking-wider text-gray-600 dark:text-gray-400">B Plot</div>
|
|
38
|
-
<div class="text-sm mt-0.5">{bPlot}</div>
|
|
39
|
-
</div>
|
|
40
|
-
)}
|
|
41
|
-
{cPlot && (
|
|
42
|
-
<div class="bg-white/50 dark:bg-neutral-900/30 rounded p-2 border border-gray-200 dark:border-gray-700">
|
|
43
|
-
<div class="text-[10px] font-bold uppercase tracking-wider text-gray-600 dark:text-gray-400">C Plot</div>
|
|
44
|
-
<div class="text-sm mt-0.5">{cPlot}</div>
|
|
45
|
-
</div>
|
|
46
|
-
)}
|
|
47
|
-
</div>
|
|
48
|
-
)}
|
|
49
|
-
{stinger && (
|
|
50
|
-
<p class="text-sm text-gray-600 dark:text-gray-400 italic mt-2 border-t border-gray-200 dark:border-gray-700 pt-2">
|
|
51
|
-
Stinger: {stinger}
|
|
52
|
-
</p>
|
|
53
|
-
)}
|
|
54
|
-
{((block.children && block.children.length > 0) || (block.props?.children && block.props.children.length > 0)) && (
|
|
55
|
-
<div class="mt-4">
|
|
56
|
-
<slot />
|
|
57
|
-
</div>
|
|
58
|
-
)}
|
|
59
|
-
</div>
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { BaseBlock } from "./types/blocks"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Merges updated template blocks into an existing page block tree. Matches templated blocks by
|
|
5
|
-
* block `type` + `isTemplated: true` tag and structural index. Fits updated template anchors while
|
|
6
|
-
* preserving user-inserted custom blocks around them.
|
|
7
|
-
*/
|
|
8
|
-
export function mergeTemplateBlocks(
|
|
9
|
-
existingPageBlocks: BaseBlock[],
|
|
10
|
-
newTemplateBlocks: BaseBlock[]
|
|
11
|
-
): BaseBlock[] {
|
|
12
|
-
const merged: BaseBlock[] = []
|
|
13
|
-
|
|
14
|
-
const newTemplatedAnchors = newTemplateBlocks.filter((b) => b.isTemplated)
|
|
15
|
-
let existingIndex = 0
|
|
16
|
-
|
|
17
|
-
for (const templateAnchor of newTemplatedAnchors) {
|
|
18
|
-
// Collect user-inserted custom blocks preceding this template anchor
|
|
19
|
-
while (
|
|
20
|
-
existingIndex < existingPageBlocks.length &&
|
|
21
|
-
!existingPageBlocks[existingIndex]?.isTemplated
|
|
22
|
-
) {
|
|
23
|
-
const block = existingPageBlocks[existingIndex]
|
|
24
|
-
if (block) merged.push(block)
|
|
25
|
-
existingIndex++
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Match existing template anchor by block type
|
|
29
|
-
const existingAnchor = existingPageBlocks[existingIndex]
|
|
30
|
-
|
|
31
|
-
if (existingAnchor && existingAnchor.type === templateAnchor.type) {
|
|
32
|
-
merged.push({
|
|
33
|
-
...templateAnchor,
|
|
34
|
-
id: existingAnchor.id,
|
|
35
|
-
props: mergeBlockProps(
|
|
36
|
-
existingAnchor.props,
|
|
37
|
-
templateAnchor.props,
|
|
38
|
-
existingAnchor.templatedProps
|
|
39
|
-
)
|
|
40
|
-
})
|
|
41
|
-
existingIndex++
|
|
42
|
-
} else {
|
|
43
|
-
// New template anchor inserted in template update: add fresh block with new random UUID
|
|
44
|
-
merged.push({
|
|
45
|
-
...templateAnchor,
|
|
46
|
-
id: crypto.randomUUID()
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Append any remaining custom user blocks at the end of the document
|
|
52
|
-
while (existingIndex < existingPageBlocks.length) {
|
|
53
|
-
const block = existingPageBlocks[existingIndex]
|
|
54
|
-
if (block) merged.push(block)
|
|
55
|
-
existingIndex++
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return merged
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function mergeBlockProps(
|
|
62
|
-
existingProps: any,
|
|
63
|
-
templateProps: any,
|
|
64
|
-
templatedProps?: boolean | string[]
|
|
65
|
-
) {
|
|
66
|
-
if (templatedProps === true) return { ...templateProps }
|
|
67
|
-
if (Array.isArray(templatedProps)) {
|
|
68
|
-
const updated = { ...existingProps }
|
|
69
|
-
for (const key of templatedProps) {
|
|
70
|
-
updated[key] = templateProps[key]
|
|
71
|
-
}
|
|
72
|
-
return updated
|
|
73
|
-
}
|
|
74
|
-
return { ...existingProps, ...templateProps }
|
|
75
|
-
}
|
package/src/markdown/parser.ts
DELETED
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import type { BaseBlock, HeadingLevel } from "../core/types/blocks"
|
|
2
|
-
import type { InlineNode } from "../core/types/inline"
|
|
3
|
-
|
|
4
|
-
function genId(): string {
|
|
5
|
-
return typeof crypto !== "undefined" && crypto.randomUUID
|
|
6
|
-
? crypto.randomUUID()
|
|
7
|
-
: Math.random().toString(36).slice(2, 11)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function parseInlines(text: string): InlineNode[] {
|
|
11
|
-
if (!text) return []
|
|
12
|
-
|
|
13
|
-
const inlines: InlineNode[] = []
|
|
14
|
-
// Regex to match markdown links: [text](url)
|
|
15
|
-
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g
|
|
16
|
-
let lastIndex = 0
|
|
17
|
-
let match: RegExpExecArray | null
|
|
18
|
-
|
|
19
|
-
while ((match = linkRegex.exec(text)) !== null) {
|
|
20
|
-
const beforeText = text.substring(lastIndex, match.index)
|
|
21
|
-
if (beforeText) {
|
|
22
|
-
inlines.push(...parseFormattedText(beforeText))
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const linkText = match[1] || ""
|
|
26
|
-
const linkUrl = match[2] || ""
|
|
27
|
-
|
|
28
|
-
inlines.push({
|
|
29
|
-
type: "link",
|
|
30
|
-
url: linkUrl,
|
|
31
|
-
children: [{ type: "text", text: linkText }]
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
lastIndex = linkRegex.lastIndex
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const remainingText = text.substring(lastIndex)
|
|
38
|
-
if (remainingText) {
|
|
39
|
-
inlines.push(...parseFormattedText(remainingText))
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return inlines.length > 0 ? inlines : [{ type: "text", text }]
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function parseFormattedText(str: string): InlineNode[] {
|
|
46
|
-
if (!str) return []
|
|
47
|
-
|
|
48
|
-
// Tokenize bold, italic, code marks
|
|
49
|
-
// Format: `code`, **bold**, *italic*, ~~strikethrough~~
|
|
50
|
-
const result: InlineNode[] = []
|
|
51
|
-
const tokenRegex = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|~~[^~]+~~)/g
|
|
52
|
-
let lastIdx = 0
|
|
53
|
-
let match: RegExpExecArray | null
|
|
54
|
-
|
|
55
|
-
while ((match = tokenRegex.exec(str)) !== null) {
|
|
56
|
-
const rawBefore = str.substring(lastIdx, match.index)
|
|
57
|
-
if (rawBefore) {
|
|
58
|
-
result.push({ type: "text", text: rawBefore })
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const token = match[1] || ""
|
|
62
|
-
if (token.startsWith("`") && token.endsWith("`")) {
|
|
63
|
-
result.push({
|
|
64
|
-
type: "text",
|
|
65
|
-
text: token.slice(1, -1),
|
|
66
|
-
marks: ["code"]
|
|
67
|
-
})
|
|
68
|
-
} else if (token.startsWith("**") && token.endsWith("**")) {
|
|
69
|
-
result.push({
|
|
70
|
-
type: "text",
|
|
71
|
-
text: token.slice(2, -2),
|
|
72
|
-
marks: ["bold"]
|
|
73
|
-
})
|
|
74
|
-
} else if (token.startsWith("*") && token.endsWith("*")) {
|
|
75
|
-
result.push({
|
|
76
|
-
type: "text",
|
|
77
|
-
text: token.slice(1, -1),
|
|
78
|
-
marks: ["italic"]
|
|
79
|
-
})
|
|
80
|
-
} else if (token.startsWith("~~") && token.endsWith("~~")) {
|
|
81
|
-
result.push({
|
|
82
|
-
type: "text",
|
|
83
|
-
text: token.slice(2, -2),
|
|
84
|
-
marks: ["strikethrough"]
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
lastIdx = tokenRegex.lastIndex
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const remaining = str.substring(lastIdx)
|
|
92
|
-
if (remaining) {
|
|
93
|
-
result.push({ type: "text", text: remaining })
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return result
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function parseMarkdownTable(tableText: string): BaseBlock | null {
|
|
100
|
-
const lines = tableText
|
|
101
|
-
.trim()
|
|
102
|
-
.split("\n")
|
|
103
|
-
.map((l) => l.trim())
|
|
104
|
-
.filter(Boolean)
|
|
105
|
-
if (lines.length < 2) return null
|
|
106
|
-
|
|
107
|
-
const parseRow = (line: string) =>
|
|
108
|
-
line
|
|
109
|
-
.replace(/^\|/, "")
|
|
110
|
-
.replace(/\|$/, "")
|
|
111
|
-
.split("|")
|
|
112
|
-
.map((c) => c.trim())
|
|
113
|
-
|
|
114
|
-
const headerCells = parseRow(lines[0] || "")
|
|
115
|
-
const alignCells = parseRow(lines[1] || "")
|
|
116
|
-
|
|
117
|
-
// Verify second line is alignment separator (e.g. ---, :---:, ---:)
|
|
118
|
-
if (!alignCells.every((c) => /^[:-]+$/.test(c))) {
|
|
119
|
-
return null
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const columns = headerCells.map((header, idx) => {
|
|
123
|
-
const alignStr = alignCells[idx] || ""
|
|
124
|
-
let align: "left" | "center" | "right" = "left"
|
|
125
|
-
if (alignStr.startsWith(":") && alignStr.endsWith(":")) {
|
|
126
|
-
align = "center"
|
|
127
|
-
} else if (alignStr.endsWith(":")) {
|
|
128
|
-
align = "right"
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
key: `col_${idx}`,
|
|
132
|
-
header,
|
|
133
|
-
align
|
|
134
|
-
}
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
const rows: Record<string, string>[] = []
|
|
138
|
-
for (let i = 2; i < lines.length; i++) {
|
|
139
|
-
const cells = parseRow(lines[i] || "")
|
|
140
|
-
const rowObj: Record<string, string> = {}
|
|
141
|
-
columns.forEach((col, idx) => {
|
|
142
|
-
rowObj[col.key] = cells[idx] || ""
|
|
143
|
-
})
|
|
144
|
-
rows.push(rowObj)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
id: genId(),
|
|
149
|
-
type: "TableBlock",
|
|
150
|
-
props: {
|
|
151
|
-
columns,
|
|
152
|
-
rows,
|
|
153
|
-
caption: ""
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function markdownToBlocks(markdown: string): BaseBlock[] {
|
|
159
|
-
if (!markdown) return []
|
|
160
|
-
|
|
161
|
-
// Strip frontmatter if present
|
|
162
|
-
let cleanMd = markdown.trim()
|
|
163
|
-
if (cleanMd.startsWith("---")) {
|
|
164
|
-
const endFm = cleanMd.indexOf("---", 3)
|
|
165
|
-
if (endFm !== -1) {
|
|
166
|
-
cleanMd = cleanMd.substring(endFm + 3).trim()
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const rootBlocks: BaseBlock[] = []
|
|
171
|
-
const sectionStack: { level: number; block: BaseBlock }[] = []
|
|
172
|
-
|
|
173
|
-
const appendToCurrentScope = (block: BaseBlock) => {
|
|
174
|
-
if (sectionStack.length > 0) {
|
|
175
|
-
const topSection = sectionStack[sectionStack.length - 1]!.block
|
|
176
|
-
if (!topSection.children) topSection.children = []
|
|
177
|
-
if (!topSection.props.children) topSection.props.children = []
|
|
178
|
-
topSection.children.push(block)
|
|
179
|
-
topSection.props.children.push(block)
|
|
180
|
-
} else {
|
|
181
|
-
rootBlocks.push(block)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const lines = cleanMd.split("\n")
|
|
186
|
-
let i = 0
|
|
187
|
-
|
|
188
|
-
while (i < lines.length) {
|
|
189
|
-
const line = lines[i] || ""
|
|
190
|
-
const trimmed = line.trim()
|
|
191
|
-
|
|
192
|
-
if (!trimmed) {
|
|
193
|
-
i++
|
|
194
|
-
continue
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// 1. Code Block Fence (```)
|
|
198
|
-
if (trimmed.startsWith("```")) {
|
|
199
|
-
const lang = trimmed.slice(3).trim()
|
|
200
|
-
const codeLines: string[] = []
|
|
201
|
-
i++
|
|
202
|
-
while (i < lines.length && !(lines[i] || "").trim().startsWith("```")) {
|
|
203
|
-
codeLines.push(lines[i] || "")
|
|
204
|
-
i++
|
|
205
|
-
}
|
|
206
|
-
i++ // Skip closing ```
|
|
207
|
-
appendToCurrentScope({
|
|
208
|
-
id: genId(),
|
|
209
|
-
type: "CodeBlock",
|
|
210
|
-
props: {
|
|
211
|
-
language: lang || "text",
|
|
212
|
-
code: codeLines.join("\n"),
|
|
213
|
-
caption: ""
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
continue
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// 2. Headings (# to ######)
|
|
220
|
-
const headingMatch = line.match(/^(#{1,6})\s(.*)$/)
|
|
221
|
-
if (headingMatch) {
|
|
222
|
-
const hashes = headingMatch[1] || ""
|
|
223
|
-
const title = (headingMatch[2] || "").trim()
|
|
224
|
-
const level = Math.min(6, Math.max(2, hashes.length)) as HeadingLevel
|
|
225
|
-
|
|
226
|
-
const newSection: BaseBlock = {
|
|
227
|
-
id: genId(),
|
|
228
|
-
type: "SectionBlock",
|
|
229
|
-
children: [],
|
|
230
|
-
props: {
|
|
231
|
-
title,
|
|
232
|
-
level,
|
|
233
|
-
description: "",
|
|
234
|
-
children: []
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Pop sections with higher or equal level
|
|
239
|
-
while (sectionStack.length > 0 && sectionStack[sectionStack.length - 1]!.level >= level) {
|
|
240
|
-
sectionStack.pop()
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
appendToCurrentScope(newSection)
|
|
244
|
-
sectionStack.push({ level, block: newSection })
|
|
245
|
-
i++
|
|
246
|
-
continue
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// 3. Callouts / Blockquotes (> [!NOTE], > [!WARNING], > etc.)
|
|
250
|
-
if (trimmed.startsWith(">")) {
|
|
251
|
-
const calloutLines: string[] = []
|
|
252
|
-
while (i < lines.length && (lines[i] || "").trim().startsWith(">")) {
|
|
253
|
-
calloutLines.push((lines[i] || "").trim().replace(/^>\s?/, ""))
|
|
254
|
-
i++
|
|
255
|
-
}
|
|
256
|
-
const firstLine = calloutLines[0] || ""
|
|
257
|
-
const variantMatch = firstLine.match(/^\[!([A-Z]+)\]/i)
|
|
258
|
-
let variant = "info"
|
|
259
|
-
let contentLines = calloutLines
|
|
260
|
-
|
|
261
|
-
if (variantMatch) {
|
|
262
|
-
variant = (variantMatch[1] || "info").toLowerCase()
|
|
263
|
-
contentLines = calloutLines.slice(1)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const childMd = contentLines.join("\n").trim()
|
|
267
|
-
const calloutChildren = childMd ? markdownToBlocks(childMd) : []
|
|
268
|
-
|
|
269
|
-
appendToCurrentScope({
|
|
270
|
-
id: genId(),
|
|
271
|
-
type: "CalloutBlock",
|
|
272
|
-
children: calloutChildren,
|
|
273
|
-
props: {
|
|
274
|
-
variant,
|
|
275
|
-
children: calloutChildren
|
|
276
|
-
}
|
|
277
|
-
})
|
|
278
|
-
continue
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// 4. Markdown Table (| Header | Header |)
|
|
282
|
-
if (trimmed.startsWith("|") && trimmed.endsWith("|")) {
|
|
283
|
-
const tableLines: string[] = []
|
|
284
|
-
while (
|
|
285
|
-
i < lines.length &&
|
|
286
|
-
(lines[i] || "").trim().startsWith("|") &&
|
|
287
|
-
(lines[i] || "").trim().endsWith("|")
|
|
288
|
-
) {
|
|
289
|
-
tableLines.push(lines[i] || "")
|
|
290
|
-
i++
|
|
291
|
-
}
|
|
292
|
-
const tableBlock = parseMarkdownTable(tableLines.join("\n"))
|
|
293
|
-
if (tableBlock) {
|
|
294
|
-
appendToCurrentScope(tableBlock)
|
|
295
|
-
continue
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// 5. Image ()
|
|
300
|
-
const imageMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)$/)
|
|
301
|
-
if (imageMatch) {
|
|
302
|
-
appendToCurrentScope({
|
|
303
|
-
id: genId(),
|
|
304
|
-
type: "ImageBlock",
|
|
305
|
-
props: {
|
|
306
|
-
alt: imageMatch[1] || "",
|
|
307
|
-
src: imageMatch[2] || "",
|
|
308
|
-
caption: ""
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
i++
|
|
312
|
-
continue
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// 6. Regular Paragraph (gather lines until blank line or block start)
|
|
316
|
-
const paragraphLines: string[] = []
|
|
317
|
-
while (
|
|
318
|
-
i < lines.length &&
|
|
319
|
-
(lines[i] || "").trim() &&
|
|
320
|
-
!(lines[i] || "").trim().startsWith("```") &&
|
|
321
|
-
!(lines[i] || "").trim().startsWith("#") &&
|
|
322
|
-
!(lines[i] || "").trim().startsWith(">") &&
|
|
323
|
-
!((lines[i] || "").trim().startsWith("|") && (lines[i] || "").trim().endsWith("|")) &&
|
|
324
|
-
!/^[0-9]+\.\s/.test((lines[i] || "").trim())
|
|
325
|
-
) {
|
|
326
|
-
paragraphLines.push(lines[i] || "")
|
|
327
|
-
i++
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (paragraphLines.length > 0) {
|
|
331
|
-
const paragraphText = paragraphLines.join(" ").trim()
|
|
332
|
-
const inlines = parseInlines(paragraphText)
|
|
333
|
-
appendToCurrentScope({
|
|
334
|
-
id: genId(),
|
|
335
|
-
type: "ParagraphBlock",
|
|
336
|
-
props: {
|
|
337
|
-
text: inlines
|
|
338
|
-
}
|
|
339
|
-
})
|
|
340
|
-
} else {
|
|
341
|
-
// Advance if no block consumed
|
|
342
|
-
i++
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return rootBlocks
|
|
347
|
-
}
|