@nuasite/cms 0.1.0
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 +237 -0
- package/dist/src/build-processor.d.ts +20 -0
- package/dist/src/build-processor.d.ts.map +1 -0
- package/dist/src/collection-scanner.d.ts +6 -0
- package/dist/src/collection-scanner.d.ts.map +1 -0
- package/dist/src/component-registry.d.ts +63 -0
- package/dist/src/component-registry.d.ts.map +1 -0
- package/dist/src/config.d.ts +24 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/dev-middleware.d.ts +20 -0
- package/dist/src/dev-middleware.d.ts.map +1 -0
- package/dist/src/editor/ai.d.ts +60 -0
- package/dist/src/editor/ai.d.ts.map +1 -0
- package/dist/src/editor/api.d.ts +140 -0
- package/dist/src/editor/api.d.ts.map +1 -0
- package/dist/src/editor/color-utils.d.ts +106 -0
- package/dist/src/editor/color-utils.d.ts.map +1 -0
- package/dist/src/editor/components/ai-chat.d.ts +11 -0
- package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
- package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
- package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
- package/dist/src/editor/components/attribute-editor.d.ts +5 -0
- package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
- package/dist/src/editor/components/block-editor.d.ts +12 -0
- package/dist/src/editor/components/block-editor.d.ts.map +1 -0
- package/dist/src/editor/components/collections-browser.d.ts +2 -0
- package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
- package/dist/src/editor/components/color-toolbar.d.ts +12 -0
- package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
- package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
- package/dist/src/editor/components/create-page-modal.d.ts +2 -0
- package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
- package/dist/src/editor/components/editable-highlights.d.ts +9 -0
- package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
- package/dist/src/editor/components/error-boundary.d.ts +32 -0
- package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
- package/dist/src/editor/components/fields.d.ts +75 -0
- package/dist/src/editor/components/fields.d.ts.map +1 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
- package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
- package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
- package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/image-overlay.d.ts +12 -0
- package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
- package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
- package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
- package/dist/src/editor/components/media-library.d.ts +2 -0
- package/dist/src/editor/components/media-library.d.ts.map +1 -0
- package/dist/src/editor/components/outline.d.ts +21 -0
- package/dist/src/editor/components/outline.d.ts.map +1 -0
- package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
- package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
- package/dist/src/editor/components/seo-editor.d.ts +2 -0
- package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
- package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
- package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
- package/dist/src/editor/components/toast/toast.d.ts +7 -0
- package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
- package/dist/src/editor/components/toast/types.d.ts +7 -0
- package/dist/src/editor/components/toast/types.d.ts.map +1 -0
- package/dist/src/editor/components/toolbar.d.ts +21 -0
- package/dist/src/editor/components/toolbar.d.ts.map +1 -0
- package/dist/src/editor/config.d.ts +4 -0
- package/dist/src/editor/config.d.ts.map +1 -0
- package/dist/src/editor/constants.d.ts +101 -0
- package/dist/src/editor/constants.d.ts.map +1 -0
- package/dist/src/editor/context.d.ts +14 -0
- package/dist/src/editor/context.d.ts.map +1 -0
- package/dist/src/editor/dom.d.ts +77 -0
- package/dist/src/editor/dom.d.ts.map +1 -0
- package/dist/src/editor/editor.d.ts +64 -0
- package/dist/src/editor/editor.d.ts.map +1 -0
- package/dist/src/editor/history.d.ts +20 -0
- package/dist/src/editor/history.d.ts.map +1 -0
- package/dist/src/editor/hooks/index.d.ts +14 -0
- package/dist/src/editor/hooks/index.d.ts.map +1 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
- package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
- package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
- package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
- package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
- package/dist/src/editor/hooks/utils.d.ts +32 -0
- package/dist/src/editor/hooks/utils.d.ts.map +1 -0
- package/dist/src/editor/index.d.ts +12 -0
- package/dist/src/editor/index.d.ts.map +1 -0
- package/dist/src/editor/lib/cn.d.ts +3 -0
- package/dist/src/editor/lib/cn.d.ts.map +1 -0
- package/dist/src/editor/manifest.d.ts +19 -0
- package/dist/src/editor/manifest.d.ts.map +1 -0
- package/dist/src/editor/markdown-api.d.ts +36 -0
- package/dist/src/editor/markdown-api.d.ts.map +1 -0
- package/dist/src/editor/signals.d.ts +242 -0
- package/dist/src/editor/signals.d.ts.map +1 -0
- package/dist/src/editor/storage.d.ts +27 -0
- package/dist/src/editor/storage.d.ts.map +1 -0
- package/dist/src/editor/text-styling.d.ts +350 -0
- package/dist/src/editor/text-styling.d.ts.map +1 -0
- package/dist/src/editor/themes.d.ts +38 -0
- package/dist/src/editor/themes.d.ts.map +1 -0
- package/dist/src/editor/types.d.ts +454 -0
- package/dist/src/editor/types.d.ts.map +1 -0
- package/dist/src/error-collector.d.ts +56 -0
- package/dist/src/error-collector.d.ts.map +1 -0
- package/dist/src/handlers/component-ops.d.ts +34 -0
- package/dist/src/handlers/component-ops.d.ts.map +1 -0
- package/dist/src/handlers/markdown-ops.d.ts +41 -0
- package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
- package/dist/src/handlers/request-utils.d.ts +20 -0
- package/dist/src/handlers/request-utils.d.ts.map +1 -0
- package/dist/src/handlers/source-writer.d.ts +51 -0
- package/dist/src/handlers/source-writer.d.ts.map +1 -0
- package/dist/src/html-processor.d.ts +63 -0
- package/dist/src/html-processor.d.ts.map +1 -0
- package/dist/src/index.d.ts +41 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/manifest-writer.d.ts +111 -0
- package/dist/src/manifest-writer.d.ts.map +1 -0
- package/dist/src/media/contember.d.ts +15 -0
- package/dist/src/media/contember.d.ts.map +1 -0
- package/dist/src/media/local.d.ts +9 -0
- package/dist/src/media/local.d.ts.map +1 -0
- package/dist/src/media/s3.d.ts +12 -0
- package/dist/src/media/s3.d.ts.map +1 -0
- package/dist/src/media/types.d.ts +40 -0
- package/dist/src/media/types.d.ts.map +1 -0
- package/dist/src/preview-generator.d.ts +19 -0
- package/dist/src/preview-generator.d.ts.map +1 -0
- package/dist/src/seo-processor.d.ts +23 -0
- package/dist/src/seo-processor.d.ts.map +1 -0
- package/dist/src/source-finder/ast-extractors.d.ts +35 -0
- package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
- package/dist/src/source-finder/ast-parser.d.ts +16 -0
- package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
- package/dist/src/source-finder/cache.d.ts +18 -0
- package/dist/src/source-finder/cache.d.ts.map +1 -0
- package/dist/src/source-finder/collection-finder.d.ts +29 -0
- package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
- package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
- package/dist/src/source-finder/element-finder.d.ts +42 -0
- package/dist/src/source-finder/element-finder.d.ts.map +1 -0
- package/dist/src/source-finder/image-finder.d.ts +24 -0
- package/dist/src/source-finder/image-finder.d.ts.map +1 -0
- package/dist/src/source-finder/index.d.ts +9 -0
- package/dist/src/source-finder/index.d.ts.map +1 -0
- package/dist/src/source-finder/search-index.d.ts +27 -0
- package/dist/src/source-finder/search-index.d.ts.map +1 -0
- package/dist/src/source-finder/snippet-utils.d.ts +90 -0
- package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
- package/dist/src/source-finder/source-lookup.d.ts +16 -0
- package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
- package/dist/src/source-finder/types.d.ts +167 -0
- package/dist/src/source-finder/types.d.ts.map +1 -0
- package/dist/src/source-finder/variable-extraction.d.ts +37 -0
- package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
- package/dist/src/tailwind-colors.d.ts +54 -0
- package/dist/src/tailwind-colors.d.ts.map +1 -0
- package/dist/src/tsconfig.tsbuildinfo +1 -0
- package/dist/src/types.d.ts +367 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils.d.ts +61 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/vite-plugin.d.ts +14 -0
- package/dist/src/vite-plugin.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +80 -0
- package/src/build-processor.ts +784 -0
- package/src/collection-scanner.ts +304 -0
- package/src/component-registry.ts +393 -0
- package/src/config.ts +74 -0
- package/src/dev-middleware.ts +525 -0
- package/src/dist/src/tsconfig.tsbuildinfo +1 -0
- package/src/editor/ai.ts +185 -0
- package/src/editor/api.ts +513 -0
- package/src/editor/color-utils.ts +556 -0
- package/src/editor/components/ai-chat.tsx +632 -0
- package/src/editor/components/ai-tooltip.tsx +179 -0
- package/src/editor/components/attribute-editor.tsx +596 -0
- package/src/editor/components/block-editor.tsx +546 -0
- package/src/editor/components/collections-browser.tsx +248 -0
- package/src/editor/components/color-toolbar.tsx +314 -0
- package/src/editor/components/confirm-dialog.tsx +69 -0
- package/src/editor/components/create-page-modal.tsx +163 -0
- package/src/editor/components/editable-highlights.tsx +260 -0
- package/src/editor/components/error-boundary.tsx +87 -0
- package/src/editor/components/fields.tsx +387 -0
- package/src/editor/components/frontmatter-fields.tsx +469 -0
- package/src/editor/components/highlight-overlay.ts +229 -0
- package/src/editor/components/image-overlay.tsx +230 -0
- package/src/editor/components/markdown-editor-overlay.tsx +505 -0
- package/src/editor/components/markdown-inline-editor.tsx +780 -0
- package/src/editor/components/media-library.tsx +297 -0
- package/src/editor/components/outline.tsx +402 -0
- package/src/editor/components/redirect-countdown.tsx +45 -0
- package/src/editor/components/seo-editor.tsx +498 -0
- package/src/editor/components/text-style-toolbar.tsx +362 -0
- package/src/editor/components/toast/toast-container.tsx +15 -0
- package/src/editor/components/toast/toast.tsx +49 -0
- package/src/editor/components/toast/types.ts +7 -0
- package/src/editor/components/toolbar.tsx +366 -0
- package/src/editor/config.ts +12 -0
- package/src/editor/constants.ts +106 -0
- package/src/editor/context.tsx +38 -0
- package/src/editor/dom.ts +357 -0
- package/src/editor/editor.ts +1510 -0
- package/src/editor/env.d.ts +4 -0
- package/src/editor/history.ts +355 -0
- package/src/editor/hooks/index.ts +19 -0
- package/src/editor/hooks/useAIHandlers.ts +345 -0
- package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
- package/src/editor/hooks/useElementDetection.ts +284 -0
- package/src/editor/hooks/useImageHoverDetection.ts +102 -0
- package/src/editor/hooks/useTextSelection.ts +187 -0
- package/src/editor/hooks/useTooltipState.ts +126 -0
- package/src/editor/hooks/utils.ts +101 -0
- package/src/editor/index.tsx +481 -0
- package/src/editor/lib/cn.ts +4 -0
- package/src/editor/manifest.ts +25 -0
- package/src/editor/markdown-api.ts +209 -0
- package/src/editor/signals.ts +1351 -0
- package/src/editor/storage.ts +266 -0
- package/src/editor/styles.css +465 -0
- package/src/editor/text-styling.ts +773 -0
- package/src/editor/themes.ts +210 -0
- package/src/editor/types.ts +591 -0
- package/src/error-collector.ts +106 -0
- package/src/handlers/component-ops.ts +463 -0
- package/src/handlers/markdown-ops.ts +202 -0
- package/src/handlers/request-utils.ts +151 -0
- package/src/handlers/source-writer.ts +649 -0
- package/src/html-processor.ts +1108 -0
- package/src/index.ts +284 -0
- package/src/manifest-writer.ts +371 -0
- package/src/media/contember.ts +84 -0
- package/src/media/local.ts +114 -0
- package/src/media/s3.ts +133 -0
- package/src/media/types.ts +33 -0
- package/src/preview-generator.ts +293 -0
- package/src/seo-processor.ts +567 -0
- package/src/source-finder/ast-extractors.ts +185 -0
- package/src/source-finder/ast-parser.ts +150 -0
- package/src/source-finder/cache.ts +76 -0
- package/src/source-finder/collection-finder.ts +335 -0
- package/src/source-finder/cross-file-tracker.ts +741 -0
- package/src/source-finder/element-finder.ts +387 -0
- package/src/source-finder/image-finder.ts +283 -0
- package/src/source-finder/index.ts +37 -0
- package/src/source-finder/search-index.ts +525 -0
- package/src/source-finder/snippet-utils.ts +668 -0
- package/src/source-finder/source-lookup.ts +200 -0
- package/src/source-finder/types.ts +210 -0
- package/src/source-finder/variable-extraction.ts +406 -0
- package/src/tailwind-colors.ts +874 -0
- package/src/tsconfig.json +25 -0
- package/src/types.ts +406 -0
- package/src/utils.ts +186 -0
- package/src/vite-plugin.ts +42 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { type Editor, editorViewCtx } from '@milkdown/core'
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
|
3
|
+
import { updateMarkdownPage } from '../api'
|
|
4
|
+
import { STORAGE_KEYS } from '../constants'
|
|
5
|
+
import { startDeploymentPolling } from '../editor'
|
|
6
|
+
import { createMarkdownPage } from '../markdown-api'
|
|
7
|
+
import {
|
|
8
|
+
config,
|
|
9
|
+
currentMarkdownPage,
|
|
10
|
+
isMarkdownPreview,
|
|
11
|
+
markdownEditorState,
|
|
12
|
+
resetMarkdownEditorState,
|
|
13
|
+
showToast,
|
|
14
|
+
startRedirectCountdown,
|
|
15
|
+
updateMarkdownFrontmatter,
|
|
16
|
+
} from '../signals'
|
|
17
|
+
import { CreateModeFrontmatter, EditModeFrontmatter, slugify } from './frontmatter-fields'
|
|
18
|
+
import { MarkdownInlineEditor } from './markdown-inline-editor'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Wrapper component that renders the editor in place of markdown content.
|
|
22
|
+
* Supports both "edit" mode (existing page) and "create" mode (new page).
|
|
23
|
+
*/
|
|
24
|
+
export function MarkdownEditorOverlay() {
|
|
25
|
+
const page = currentMarkdownPage.value
|
|
26
|
+
const editorState = markdownEditorState.value
|
|
27
|
+
const isCreateMode = editorState.mode === 'create'
|
|
28
|
+
const createOptions = editorState.createOptions
|
|
29
|
+
const collectionDef = editorState.collectionDefinition
|
|
30
|
+
|
|
31
|
+
const [isSaving, setIsSaving] = useState(false)
|
|
32
|
+
const [showFrontmatter, setShowFrontmatter] = useState(isCreateMode)
|
|
33
|
+
// Track whether the user has manually edited the slug (disables auto-slug from title)
|
|
34
|
+
const [slugManuallyEdited, setSlugManuallyEdited] = useState(false)
|
|
35
|
+
// Preview mode state
|
|
36
|
+
const [isPreview, setIsPreview] = useState(false)
|
|
37
|
+
const originalHTMLRef = useRef<string | null>(null)
|
|
38
|
+
const editorInstanceRef = useRef<Editor | null>(null)
|
|
39
|
+
|
|
40
|
+
// Open metadata by default when entering create mode
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (isCreateMode) {
|
|
43
|
+
setShowFrontmatter(true)
|
|
44
|
+
}
|
|
45
|
+
}, [isCreateMode])
|
|
46
|
+
|
|
47
|
+
const handleDeploymentComplete = useCallback(
|
|
48
|
+
(status: 'completed' | 'failed' | 'timeout') => {
|
|
49
|
+
if (status === 'failed') {
|
|
50
|
+
showToast('Deployment failed', 'error')
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
[],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const restoreOriginalHTML = useCallback(() => {
|
|
57
|
+
const activeId = markdownEditorState.value.activeElementId
|
|
58
|
+
if (originalHTMLRef.current !== null && activeId) {
|
|
59
|
+
const el = document.querySelector(`[data-cms-id="${activeId}"]`)
|
|
60
|
+
if (el) {
|
|
61
|
+
el.innerHTML = originalHTMLRef.current
|
|
62
|
+
}
|
|
63
|
+
originalHTMLRef.current = null
|
|
64
|
+
}
|
|
65
|
+
}, [])
|
|
66
|
+
|
|
67
|
+
const handleSave = useCallback(
|
|
68
|
+
async (content: string) => {
|
|
69
|
+
if (isSaving) return
|
|
70
|
+
const currentPage = currentMarkdownPage.value
|
|
71
|
+
if (!currentPage) return
|
|
72
|
+
|
|
73
|
+
setIsSaving(true)
|
|
74
|
+
try {
|
|
75
|
+
const result = await updateMarkdownPage(config.value.apiBase, {
|
|
76
|
+
filePath: currentPage.filePath,
|
|
77
|
+
content,
|
|
78
|
+
frontmatter: currentPage.frontmatter,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if (result.success) {
|
|
82
|
+
// Keep the preview HTML in place so user sees changes immediately
|
|
83
|
+
// If not in preview mode, inject editor HTML into the page element
|
|
84
|
+
const activeId = markdownEditorState.value.activeElementId
|
|
85
|
+
if (activeId && editorInstanceRef.current && !isPreview) {
|
|
86
|
+
const el = document.querySelector(
|
|
87
|
+
`[data-cms-id="${activeId}"]`,
|
|
88
|
+
)
|
|
89
|
+
if (el) {
|
|
90
|
+
try {
|
|
91
|
+
const view = editorInstanceRef.current.ctx.get(editorViewCtx)
|
|
92
|
+
el.innerHTML = view.dom.innerHTML
|
|
93
|
+
} catch {
|
|
94
|
+
// If we can't get editor HTML, leave the page as-is
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Clear the original ref without restoring — we want to keep the new content visible
|
|
99
|
+
originalHTMLRef.current = null
|
|
100
|
+
setIsPreview(false)
|
|
101
|
+
isMarkdownPreview.value = false
|
|
102
|
+
setIsSaving(false)
|
|
103
|
+
|
|
104
|
+
showToast('Content saved, deploying...', 'success')
|
|
105
|
+
// Clear pending entry navigation so editor doesn't auto-open after save
|
|
106
|
+
sessionStorage.removeItem(STORAGE_KEYS.PENDING_ENTRY_NAVIGATION)
|
|
107
|
+
// Close the editor immediately — the toolbar will show deploying state
|
|
108
|
+
resetMarkdownEditorState()
|
|
109
|
+
startDeploymentPolling(config.value, {
|
|
110
|
+
onComplete: handleDeploymentComplete,
|
|
111
|
+
})
|
|
112
|
+
} else {
|
|
113
|
+
showToast(result.error || 'Failed to save markdown', 'error')
|
|
114
|
+
setIsSaving(false)
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
118
|
+
showToast(`Save failed: ${message}`, 'error')
|
|
119
|
+
setIsSaving(false)
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
[isSaving, isPreview, handleDeploymentComplete],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const handleCreate = useCallback(async () => {
|
|
126
|
+
if (isSaving) return
|
|
127
|
+
const currentPage = currentMarkdownPage.value
|
|
128
|
+
const opts = markdownEditorState.value.createOptions
|
|
129
|
+
if (!currentPage || !opts) return
|
|
130
|
+
|
|
131
|
+
const title = (currentPage.frontmatter.title as string) || ''
|
|
132
|
+
if (!title.trim()) {
|
|
133
|
+
showToast('Please enter a title', 'error')
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const slug = currentPage.slug || slugify(title)
|
|
138
|
+
if (!slug) {
|
|
139
|
+
showToast('Please enter a slug', 'error')
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setIsSaving(true)
|
|
144
|
+
try {
|
|
145
|
+
// Build frontmatter excluding title and slug
|
|
146
|
+
const frontmatter: Record<string, unknown> = {}
|
|
147
|
+
for (const [key, value] of Object.entries(currentPage.frontmatter)) {
|
|
148
|
+
if (key !== 'title' && value !== undefined && value !== '') {
|
|
149
|
+
frontmatter[key] = value
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result = await createMarkdownPage(config.value, {
|
|
154
|
+
collection: opts.collectionName,
|
|
155
|
+
title: title.trim(),
|
|
156
|
+
slug,
|
|
157
|
+
frontmatter,
|
|
158
|
+
content: currentPage.content || '',
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
if (result.success) {
|
|
162
|
+
// Derive the new page URL from existing collection entry pathnames
|
|
163
|
+
const entries = opts.collectionDefinition.entries ?? []
|
|
164
|
+
const existingEntry = entries.find((e) => e.pathname)
|
|
165
|
+
let redirectUrl: string | undefined
|
|
166
|
+
if (existingEntry?.pathname) {
|
|
167
|
+
// Extract base path from an existing entry pathname (e.g., "/blog/first-post" → "/blog/")
|
|
168
|
+
const lastSlash = existingEntry.pathname.lastIndexOf('/')
|
|
169
|
+
if (lastSlash >= 0) {
|
|
170
|
+
redirectUrl = existingEntry.pathname.slice(0, lastSlash + 1) + slug
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
showToast('Page created, deploying...', 'success')
|
|
175
|
+
resetMarkdownEditorState()
|
|
176
|
+
startDeploymentPolling(config.value, {
|
|
177
|
+
onComplete: (status) => {
|
|
178
|
+
handleDeploymentComplete(status)
|
|
179
|
+
if (status === 'completed' && redirectUrl) {
|
|
180
|
+
startRedirectCountdown(redirectUrl, title.trim())
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
} else {
|
|
185
|
+
showToast(result.error || 'Failed to create page', 'error')
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
189
|
+
showToast(`Create failed: ${message}`, 'error')
|
|
190
|
+
} finally {
|
|
191
|
+
setIsSaving(false)
|
|
192
|
+
}
|
|
193
|
+
}, [isSaving, handleDeploymentComplete])
|
|
194
|
+
|
|
195
|
+
const handlePreview = useCallback(() => {
|
|
196
|
+
const activeId = markdownEditorState.value.activeElementId
|
|
197
|
+
if (!editorInstanceRef.current || !activeId) return
|
|
198
|
+
|
|
199
|
+
if (!isPreview) {
|
|
200
|
+
// Enter preview
|
|
201
|
+
const el = document.querySelector(`[data-cms-id="${activeId}"]`)
|
|
202
|
+
if (!el) {
|
|
203
|
+
showToast('Could not find page element to preview', 'error')
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
originalHTMLRef.current = el.innerHTML
|
|
207
|
+
try {
|
|
208
|
+
const view = editorInstanceRef.current.ctx.get(editorViewCtx)
|
|
209
|
+
el.innerHTML = view.dom.innerHTML
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('Failed to get editor HTML for preview:', error)
|
|
212
|
+
originalHTMLRef.current = null
|
|
213
|
+
showToast('Failed to generate preview', 'error')
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
setIsPreview(true)
|
|
217
|
+
isMarkdownPreview.value = true
|
|
218
|
+
} else {
|
|
219
|
+
// Exit preview
|
|
220
|
+
restoreOriginalHTML()
|
|
221
|
+
setIsPreview(false)
|
|
222
|
+
isMarkdownPreview.value = false
|
|
223
|
+
}
|
|
224
|
+
}, [isPreview, restoreOriginalHTML])
|
|
225
|
+
|
|
226
|
+
const handleCancel = useCallback(() => {
|
|
227
|
+
restoreOriginalHTML()
|
|
228
|
+
isMarkdownPreview.value = false
|
|
229
|
+
resetMarkdownEditorState()
|
|
230
|
+
}, [restoreOriginalHTML])
|
|
231
|
+
|
|
232
|
+
if (!page) return null
|
|
233
|
+
|
|
234
|
+
const stopPropagation = (e: Event) => e.stopPropagation()
|
|
235
|
+
const collectionLabel = createOptions?.collectionDefinition.label ?? 'Page'
|
|
236
|
+
|
|
237
|
+
if (isPreview) {
|
|
238
|
+
return (
|
|
239
|
+
<div
|
|
240
|
+
class="fixed bottom-6 left-1/2 -translate-x-1/2 z-2147483647 flex items-center gap-3 px-5 py-3 bg-cms-dark/95 border border-white/15 rounded-cms-pill shadow-[0_8px_32px_rgba(0,0,0,0.4)] backdrop-blur-md"
|
|
241
|
+
data-cms-ui
|
|
242
|
+
onMouseDown={stopPropagation}
|
|
243
|
+
onClick={stopPropagation}
|
|
244
|
+
>
|
|
245
|
+
<div class="flex items-center gap-2 text-white/70">
|
|
246
|
+
<svg
|
|
247
|
+
class="w-4 h-4"
|
|
248
|
+
fill="none"
|
|
249
|
+
stroke="currentColor"
|
|
250
|
+
viewBox="0 0 24 24"
|
|
251
|
+
stroke-width="2"
|
|
252
|
+
>
|
|
253
|
+
<path
|
|
254
|
+
stroke-linecap="round"
|
|
255
|
+
stroke-linejoin="round"
|
|
256
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
257
|
+
/>
|
|
258
|
+
<path
|
|
259
|
+
stroke-linecap="round"
|
|
260
|
+
stroke-linejoin="round"
|
|
261
|
+
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
262
|
+
/>
|
|
263
|
+
</svg>
|
|
264
|
+
<span class="text-sm font-medium">Previewing</span>
|
|
265
|
+
</div>
|
|
266
|
+
<div class="w-px h-5 bg-white/20" />
|
|
267
|
+
<button
|
|
268
|
+
type="button"
|
|
269
|
+
onClick={handlePreview}
|
|
270
|
+
class="px-3 py-1.5 text-sm text-white/80 hover:text-white hover:bg-white/10 rounded-cms-pill transition-colors"
|
|
271
|
+
data-cms-ui
|
|
272
|
+
>
|
|
273
|
+
Back to Editor
|
|
274
|
+
</button>
|
|
275
|
+
<button
|
|
276
|
+
type="button"
|
|
277
|
+
onClick={() => {
|
|
278
|
+
const currentContent = currentMarkdownPage.value?.content
|
|
279
|
+
if (currentContent !== undefined) {
|
|
280
|
+
handleSave(currentContent)
|
|
281
|
+
}
|
|
282
|
+
}}
|
|
283
|
+
disabled={isSaving}
|
|
284
|
+
class="px-3 py-1.5 text-sm bg-cms-primary text-cms-primary-text hover:bg-cms-primary-hover rounded-cms-pill transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1.5"
|
|
285
|
+
data-cms-ui
|
|
286
|
+
>
|
|
287
|
+
{isSaving && <div class="animate-spin rounded-full h-3 w-3 border-2 border-cms-primary-text/30 border-t-cms-primary-text" />}
|
|
288
|
+
{isSaving ? 'Saving...' : 'Save'}
|
|
289
|
+
</button>
|
|
290
|
+
</div>
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return (
|
|
295
|
+
<div
|
|
296
|
+
class="fixed inset-0 z-2147483647 bg-black/40 flex items-center justify-center p-4 backdrop-blur-md"
|
|
297
|
+
data-cms-ui
|
|
298
|
+
onMouseDown={stopPropagation}
|
|
299
|
+
onClick={stopPropagation}
|
|
300
|
+
>
|
|
301
|
+
<div
|
|
302
|
+
class="bg-cms-dark rounded-cms-xl shadow-[0_8px_32px_rgba(0,0,0,0.4)] border border-white/10 w-full max-w-4xl max-h-[90vh] flex flex-col"
|
|
303
|
+
data-cms-ui
|
|
304
|
+
>
|
|
305
|
+
{/* Header */}
|
|
306
|
+
<div class="flex items-center justify-between px-5 py-4 border-b border-white/10">
|
|
307
|
+
<div class="flex items-center gap-3">
|
|
308
|
+
<div class="flex items-center text-white">
|
|
309
|
+
<svg
|
|
310
|
+
width="20"
|
|
311
|
+
height="20"
|
|
312
|
+
viewBox="0 0 24 24"
|
|
313
|
+
fill="none"
|
|
314
|
+
stroke="currentColor"
|
|
315
|
+
stroke-width="2"
|
|
316
|
+
stroke-linecap="round"
|
|
317
|
+
stroke-linejoin="round"
|
|
318
|
+
>
|
|
319
|
+
<path d="M14.5 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7.5L14.5 2z" />
|
|
320
|
+
<polyline points="14 2 14 8 20 8" />
|
|
321
|
+
<line x1="16" y1="13" x2="8" y2="13" />
|
|
322
|
+
<line x1="16" y1="17" x2="8" y2="17" />
|
|
323
|
+
<line x1="10" y1="9" x2="8" y2="9" />
|
|
324
|
+
</svg>
|
|
325
|
+
</div>
|
|
326
|
+
<div>
|
|
327
|
+
<input
|
|
328
|
+
type="text"
|
|
329
|
+
value={(page.frontmatter.title as string) || ''}
|
|
330
|
+
placeholder="Page title..."
|
|
331
|
+
onInput={(e) => {
|
|
332
|
+
const title = (e.target as HTMLInputElement).value
|
|
333
|
+
updateMarkdownFrontmatter({ title })
|
|
334
|
+
// Auto-generate slug in create mode if not manually edited
|
|
335
|
+
if (isCreateMode && !slugManuallyEdited) {
|
|
336
|
+
markdownEditorState.value = {
|
|
337
|
+
...markdownEditorState.value,
|
|
338
|
+
currentPage: markdownEditorState.value.currentPage
|
|
339
|
+
? {
|
|
340
|
+
...markdownEditorState.value.currentPage,
|
|
341
|
+
slug: slugify(title),
|
|
342
|
+
isDirty: true,
|
|
343
|
+
}
|
|
344
|
+
: null,
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}}
|
|
348
|
+
class="text-base font-semibold text-white m-0 bg-transparent border-none outline-none placeholder-white/40 w-64"
|
|
349
|
+
data-cms-ui
|
|
350
|
+
/>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
<div class="flex items-center gap-2">
|
|
354
|
+
<button
|
|
355
|
+
type="button"
|
|
356
|
+
onClick={() => setShowFrontmatter(!showFrontmatter)}
|
|
357
|
+
class={`px-3 py-2 text-sm rounded-cms-pill transition-colors flex items-center gap-1.5 ${
|
|
358
|
+
showFrontmatter
|
|
359
|
+
? 'bg-white/20 text-white'
|
|
360
|
+
: 'text-white/70 hover:text-white hover:bg-white/10'
|
|
361
|
+
}`}
|
|
362
|
+
data-cms-ui
|
|
363
|
+
>
|
|
364
|
+
<svg
|
|
365
|
+
class="w-4 h-4"
|
|
366
|
+
fill="none"
|
|
367
|
+
stroke="currentColor"
|
|
368
|
+
viewBox="0 0 24 24"
|
|
369
|
+
stroke-width="2"
|
|
370
|
+
>
|
|
371
|
+
<path
|
|
372
|
+
stroke-linecap="round"
|
|
373
|
+
stroke-linejoin="round"
|
|
374
|
+
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
|
375
|
+
/>
|
|
376
|
+
<path
|
|
377
|
+
stroke-linecap="round"
|
|
378
|
+
stroke-linejoin="round"
|
|
379
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
380
|
+
/>
|
|
381
|
+
</svg>
|
|
382
|
+
Metadata
|
|
383
|
+
<svg
|
|
384
|
+
class={`w-3.5 h-3.5 transition-transform ${showFrontmatter ? 'rotate-180' : ''}`}
|
|
385
|
+
fill="none"
|
|
386
|
+
stroke="currentColor"
|
|
387
|
+
viewBox="0 0 24 24"
|
|
388
|
+
stroke-width="2.5"
|
|
389
|
+
>
|
|
390
|
+
<path
|
|
391
|
+
stroke-linecap="round"
|
|
392
|
+
stroke-linejoin="round"
|
|
393
|
+
d="M19 9l-7 7-7-7"
|
|
394
|
+
/>
|
|
395
|
+
</svg>
|
|
396
|
+
</button>
|
|
397
|
+
{!isCreateMode && editorState.activeElementId && (
|
|
398
|
+
<button
|
|
399
|
+
type="button"
|
|
400
|
+
onClick={handlePreview}
|
|
401
|
+
class={`px-3 py-2 text-sm rounded-cms-pill transition-colors flex items-center gap-1.5 ${
|
|
402
|
+
isPreview
|
|
403
|
+
? 'bg-white/20 text-white'
|
|
404
|
+
: 'text-white/70 hover:text-white hover:bg-white/10'
|
|
405
|
+
}`}
|
|
406
|
+
data-cms-ui
|
|
407
|
+
>
|
|
408
|
+
<svg
|
|
409
|
+
class="w-4 h-4"
|
|
410
|
+
fill="none"
|
|
411
|
+
stroke="currentColor"
|
|
412
|
+
viewBox="0 0 24 24"
|
|
413
|
+
stroke-width="2"
|
|
414
|
+
>
|
|
415
|
+
<path
|
|
416
|
+
stroke-linecap="round"
|
|
417
|
+
stroke-linejoin="round"
|
|
418
|
+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
419
|
+
/>
|
|
420
|
+
<path
|
|
421
|
+
stroke-linecap="round"
|
|
422
|
+
stroke-linejoin="round"
|
|
423
|
+
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
424
|
+
/>
|
|
425
|
+
</svg>
|
|
426
|
+
Preview
|
|
427
|
+
</button>
|
|
428
|
+
)}
|
|
429
|
+
<button
|
|
430
|
+
type="button"
|
|
431
|
+
onClick={handleCancel}
|
|
432
|
+
class="px-4 py-2 text-sm text-white/70 hover:text-white hover:bg-white/10 rounded-cms-pill transition-colors"
|
|
433
|
+
data-cms-ui
|
|
434
|
+
>
|
|
435
|
+
Cancel
|
|
436
|
+
</button>
|
|
437
|
+
{isCreateMode
|
|
438
|
+
? (
|
|
439
|
+
<button
|
|
440
|
+
type="button"
|
|
441
|
+
onClick={handleCreate}
|
|
442
|
+
disabled={isSaving || !(page.frontmatter.title as string)?.trim()}
|
|
443
|
+
class="px-4 py-2 text-sm bg-cms-primary text-cms-primary-text hover:bg-cms-primary-hover rounded-cms-pill transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
|
444
|
+
data-cms-ui
|
|
445
|
+
>
|
|
446
|
+
{isSaving && <div class="animate-spin rounded-full h-3.5 w-3.5 border-2 border-cms-primary-text/30 border-t-cms-primary-text" />}
|
|
447
|
+
{isSaving ? 'Creating...' : `Create ${collectionLabel}`}
|
|
448
|
+
</button>
|
|
449
|
+
)
|
|
450
|
+
: (
|
|
451
|
+
<button
|
|
452
|
+
type="button"
|
|
453
|
+
onClick={() => {
|
|
454
|
+
const currentContent = currentMarkdownPage.value?.content
|
|
455
|
+
if (currentContent !== undefined) {
|
|
456
|
+
handleSave(currentContent)
|
|
457
|
+
}
|
|
458
|
+
}}
|
|
459
|
+
disabled={isSaving}
|
|
460
|
+
class="px-4 py-2 text-sm bg-cms-primary text-cms-primary-text hover:bg-cms-primary-hover rounded-cms-pill transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
|
461
|
+
data-cms-ui
|
|
462
|
+
>
|
|
463
|
+
{isSaving && <div class="animate-spin rounded-full h-3.5 w-3.5 border-2 border-cms-primary-text/30 border-t-cms-primary-text" />}
|
|
464
|
+
{isSaving ? 'Saving...' : 'Save'}
|
|
465
|
+
</button>
|
|
466
|
+
)}
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
{/* Frontmatter Editor */}
|
|
471
|
+
{showFrontmatter && (
|
|
472
|
+
<div class="px-5 py-4 border-b border-white/10 bg-white/5">
|
|
473
|
+
{isCreateMode && createOptions
|
|
474
|
+
? (
|
|
475
|
+
<CreateModeFrontmatter
|
|
476
|
+
page={page}
|
|
477
|
+
collectionDefinition={createOptions.collectionDefinition}
|
|
478
|
+
onSlugManualEdit={() => setSlugManuallyEdited(true)}
|
|
479
|
+
/>
|
|
480
|
+
)
|
|
481
|
+
: (
|
|
482
|
+
<EditModeFrontmatter
|
|
483
|
+
page={page}
|
|
484
|
+
collectionDefinition={collectionDef}
|
|
485
|
+
/>
|
|
486
|
+
)}
|
|
487
|
+
</div>
|
|
488
|
+
)}
|
|
489
|
+
|
|
490
|
+
{/* Editor */}
|
|
491
|
+
<div class="flex-1 min-h-0 overflow-auto bg-black/20">
|
|
492
|
+
<MarkdownInlineEditor
|
|
493
|
+
elementId={page.slug || 'new-page'}
|
|
494
|
+
initialContent={page.content}
|
|
495
|
+
onSave={isCreateMode ? () => handleCreate() : handleSave}
|
|
496
|
+
onCancel={handleCancel}
|
|
497
|
+
onEditorReady={(editor) => {
|
|
498
|
+
editorInstanceRef.current = editor
|
|
499
|
+
}}
|
|
500
|
+
/>
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
)
|
|
505
|
+
}
|