@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,513 @@
|
|
|
1
|
+
import { API } from './constants'
|
|
2
|
+
import { setAvailableTextStyles } from './text-styling'
|
|
3
|
+
import type {
|
|
4
|
+
CmsManifest,
|
|
5
|
+
ComponentInsertOperation,
|
|
6
|
+
DeploymentStatusResponse,
|
|
7
|
+
SaveBatchRequest,
|
|
8
|
+
SaveBatchResponse,
|
|
9
|
+
UpdateMarkdownPageRequest,
|
|
10
|
+
UpdateMarkdownPageResponse,
|
|
11
|
+
} from './types'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Response from fetching markdown content
|
|
15
|
+
*/
|
|
16
|
+
export interface GetMarkdownContentResponse {
|
|
17
|
+
content: string
|
|
18
|
+
frontmatter: Record<string, unknown>
|
|
19
|
+
filePath: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Types for CMS AI chat communication (SSE events)
|
|
24
|
+
*/
|
|
25
|
+
export interface CmsAiChatRequest {
|
|
26
|
+
prompt: string
|
|
27
|
+
elementId?: string
|
|
28
|
+
currentContent?: string
|
|
29
|
+
pageUrl: string
|
|
30
|
+
/** Source file context for the element being edited */
|
|
31
|
+
context?: string
|
|
32
|
+
sessionId?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type CmsAiEvent =
|
|
36
|
+
| CmsAiTokenEvent
|
|
37
|
+
| CmsAiStatusEvent
|
|
38
|
+
| CmsAiActionEvent
|
|
39
|
+
| CmsAiErrorEvent
|
|
40
|
+
| CmsAiDoneEvent
|
|
41
|
+
|
|
42
|
+
export interface CmsAiTokenEvent {
|
|
43
|
+
type: 'token'
|
|
44
|
+
token: string
|
|
45
|
+
fullText: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface CmsAiStatusEvent {
|
|
49
|
+
type: 'status'
|
|
50
|
+
status: 'thinking' | 'coding' | 'building' | 'deploying' | 'complete'
|
|
51
|
+
message?: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface CmsAiActionEvent {
|
|
55
|
+
type: 'action'
|
|
56
|
+
action: CmsAiAction
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type CmsAiAction =
|
|
60
|
+
| { name: 'refresh' }
|
|
61
|
+
| { name: 'preview'; url: string }
|
|
62
|
+
| { name: 'commit'; sha: string; message: string }
|
|
63
|
+
| { name: 'apply-edit'; elementId: string; content: string; htmlContent?: string }
|
|
64
|
+
|
|
65
|
+
export interface CmsAiErrorEvent {
|
|
66
|
+
type: 'error'
|
|
67
|
+
error: string
|
|
68
|
+
code?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface CmsAiDoneEvent {
|
|
72
|
+
type: 'done'
|
|
73
|
+
summary?: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface CmsAiStreamCallbacks {
|
|
77
|
+
onToken?: (token: string, fullText: string) => void
|
|
78
|
+
onStatus?: (status: CmsAiStatusEvent['status'], message?: string) => void
|
|
79
|
+
onAction?: (action: CmsAiAction) => void
|
|
80
|
+
onError?: (error: string, code?: string) => void
|
|
81
|
+
onDone?: (summary?: string) => void
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create a fetch request with timeout
|
|
86
|
+
*/
|
|
87
|
+
async function fetchWithTimeout(
|
|
88
|
+
url: string,
|
|
89
|
+
options: RequestInit = {},
|
|
90
|
+
timeoutMs: number = API.REQUEST_TIMEOUT_MS,
|
|
91
|
+
): Promise<Response> {
|
|
92
|
+
const controller = new AbortController()
|
|
93
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch(url, {
|
|
97
|
+
...options,
|
|
98
|
+
signal: controller.signal,
|
|
99
|
+
})
|
|
100
|
+
return response
|
|
101
|
+
} finally {
|
|
102
|
+
clearTimeout(timeoutId)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get the manifest URL for the current page
|
|
108
|
+
* For example: /about -> /about.json
|
|
109
|
+
* / -> /index.json
|
|
110
|
+
* /blog/post -> /blog/post.json
|
|
111
|
+
*/
|
|
112
|
+
export function getPageManifestUrl(pathname: string): string {
|
|
113
|
+
// Normalize path: remove trailing slash, default to 'index' for root
|
|
114
|
+
let path = pathname
|
|
115
|
+
if (path.length > 1 && path.endsWith('/')) {
|
|
116
|
+
path = path.slice(0, -1)
|
|
117
|
+
}
|
|
118
|
+
if (path === '/' || path === '') {
|
|
119
|
+
return '/index.json'
|
|
120
|
+
}
|
|
121
|
+
// Remove leading slash for the manifest path
|
|
122
|
+
const cleanPath = path.replace(/^\//, '')
|
|
123
|
+
return `/${cleanPath}.json`
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Fetch manifest by combining page-specific and global manifests:
|
|
128
|
+
* 1. Per-page manifest (/{page}.json) - contains entries, components for this page
|
|
129
|
+
* 2. Global manifest (/cms-manifest.json) - contains componentDefinitions, availableColors
|
|
130
|
+
*/
|
|
131
|
+
export async function fetchManifest(): Promise<CmsManifest> {
|
|
132
|
+
const pathname = window.location.pathname
|
|
133
|
+
const pageManifestUrl = getPageManifestUrl(pathname)
|
|
134
|
+
const globalManifestUrl = '/cms-manifest.json'
|
|
135
|
+
|
|
136
|
+
// Fetch both manifests in parallel with cache-busting to bypass CDN/edge caches
|
|
137
|
+
const cacheBuster = `_t=${Date.now()}`
|
|
138
|
+
const [pageResult, globalResult] = await Promise.allSettled([
|
|
139
|
+
fetchWithTimeout(`${pageManifestUrl}?${cacheBuster}`, { cache: 'no-store' }).then(res => res.ok ? res.json() : null),
|
|
140
|
+
fetchWithTimeout(`${globalManifestUrl}?${cacheBuster}`, { cache: 'no-store' }).then(res => res.ok ? res.json() : null),
|
|
141
|
+
])
|
|
142
|
+
|
|
143
|
+
const pageManifest = pageResult.status === 'fulfilled' ? pageResult.value : null
|
|
144
|
+
const globalManifest = globalResult.status === 'fulfilled' ? globalResult.value : null
|
|
145
|
+
|
|
146
|
+
// Need at least one manifest to work with
|
|
147
|
+
if (!pageManifest && !globalManifest) {
|
|
148
|
+
throw new Error('Failed to load manifest from all sources')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Get text styles from global manifest
|
|
152
|
+
const availableTextStyles = globalManifest?.availableTextStyles ?? pageManifest?.availableTextStyles
|
|
153
|
+
|
|
154
|
+
// Set text styles for inline style resolution
|
|
155
|
+
setAvailableTextStyles(availableTextStyles)
|
|
156
|
+
|
|
157
|
+
// Handle collection (singular) from page manifest - convert to collections format
|
|
158
|
+
let collections = pageManifest?.collections ?? {}
|
|
159
|
+
if (pageManifest?.collection) {
|
|
160
|
+
const entry = pageManifest.collection
|
|
161
|
+
const key = `${entry.collectionName}/${entry.collectionSlug}`
|
|
162
|
+
collections = { ...collections, [key]: entry }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Merge manifests: page-specific data + global settings
|
|
166
|
+
return {
|
|
167
|
+
entries: pageManifest?.entries ?? {},
|
|
168
|
+
components: pageManifest?.components ?? {},
|
|
169
|
+
componentDefinitions: globalManifest?.componentDefinitions ?? pageManifest?.componentDefinitions ?? {},
|
|
170
|
+
collectionDefinitions: globalManifest?.collectionDefinitions ?? {},
|
|
171
|
+
collections,
|
|
172
|
+
availableColors: globalManifest?.availableColors ?? pageManifest?.availableColors,
|
|
173
|
+
availableTextStyles,
|
|
174
|
+
pages: globalManifest?.pages ?? pageManifest?.pages,
|
|
175
|
+
metadata: pageManifest?.metadata,
|
|
176
|
+
// SEO data from page-specific manifest
|
|
177
|
+
seo: pageManifest?.seo,
|
|
178
|
+
} as CmsManifest
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function saveBatchChanges(
|
|
182
|
+
apiBase: string,
|
|
183
|
+
request: SaveBatchRequest,
|
|
184
|
+
): Promise<SaveBatchResponse> {
|
|
185
|
+
const res = await fetchWithTimeout(`${apiBase}/update`, {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
credentials: 'include',
|
|
188
|
+
headers: {
|
|
189
|
+
'Content-Type': 'application/json',
|
|
190
|
+
},
|
|
191
|
+
body: JSON.stringify(request),
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
const text = await res.text().catch(() => '')
|
|
196
|
+
throw new Error(`Save failed (${res.status}): ${text || res.statusText}`)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return res.json().catch(() => ({ updated: 0 }))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface InsertComponentResponse {
|
|
203
|
+
success: boolean
|
|
204
|
+
message?: string
|
|
205
|
+
sourceFile?: string
|
|
206
|
+
commit?: string | null
|
|
207
|
+
commitMessage?: string
|
|
208
|
+
error?: string
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function insertComponent(
|
|
212
|
+
apiBase: string,
|
|
213
|
+
operation: ComponentInsertOperation,
|
|
214
|
+
): Promise<InsertComponentResponse> {
|
|
215
|
+
const res = await fetchWithTimeout(`${apiBase}/insert-component`, {
|
|
216
|
+
method: 'POST',
|
|
217
|
+
credentials: 'include',
|
|
218
|
+
headers: {
|
|
219
|
+
'Content-Type': 'application/json',
|
|
220
|
+
},
|
|
221
|
+
body: JSON.stringify({
|
|
222
|
+
position: operation.position,
|
|
223
|
+
referenceComponentId: operation.referenceComponentId,
|
|
224
|
+
componentName: operation.componentName,
|
|
225
|
+
props: operation.props,
|
|
226
|
+
meta: {
|
|
227
|
+
source: 'inline-editor',
|
|
228
|
+
url: window.location.href,
|
|
229
|
+
},
|
|
230
|
+
}),
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
if (!res.ok) {
|
|
234
|
+
const text = await res.text().catch(() => '')
|
|
235
|
+
throw new Error(`Insert component failed (${res.status}): ${text || res.statusText}`)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return res.json()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface RemoveComponentResponse {
|
|
242
|
+
success: boolean
|
|
243
|
+
message?: string
|
|
244
|
+
sourceFile?: string
|
|
245
|
+
commit?: string | null
|
|
246
|
+
commitMessage?: string
|
|
247
|
+
error?: string
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function removeComponent(
|
|
251
|
+
apiBase: string,
|
|
252
|
+
componentId: string,
|
|
253
|
+
): Promise<RemoveComponentResponse> {
|
|
254
|
+
const res = await fetchWithTimeout(`${apiBase}/remove-component`, {
|
|
255
|
+
method: 'POST',
|
|
256
|
+
credentials: 'include',
|
|
257
|
+
headers: {
|
|
258
|
+
'Content-Type': 'application/json',
|
|
259
|
+
},
|
|
260
|
+
body: JSON.stringify({
|
|
261
|
+
componentId,
|
|
262
|
+
meta: {
|
|
263
|
+
source: 'inline-editor',
|
|
264
|
+
url: window.location.href,
|
|
265
|
+
},
|
|
266
|
+
}),
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
const text = await res.text().catch(() => '')
|
|
271
|
+
throw new Error(`Remove component failed (${res.status}): ${text || res.statusText}`)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return res.json()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Parse SSE data line to event
|
|
279
|
+
*/
|
|
280
|
+
export function parseSseEvent(data: string): CmsAiEvent | null {
|
|
281
|
+
if (data === '[DONE]') {
|
|
282
|
+
return { type: 'done' }
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
return JSON.parse(data) as CmsAiEvent
|
|
286
|
+
} catch {
|
|
287
|
+
return null
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Stream AI chat response from the server using SSE
|
|
293
|
+
* Provides rich events including status updates, actions (refresh, preview), and streaming tokens
|
|
294
|
+
*/
|
|
295
|
+
export async function streamAiChat(
|
|
296
|
+
apiBase: string,
|
|
297
|
+
request: CmsAiChatRequest,
|
|
298
|
+
callbacks: CmsAiStreamCallbacks,
|
|
299
|
+
abortSignal?: AbortSignal,
|
|
300
|
+
): Promise<void> {
|
|
301
|
+
const controller = new AbortController()
|
|
302
|
+
const timeoutId = setTimeout(() => controller.abort(), API.AI_STREAM_TIMEOUT_MS)
|
|
303
|
+
|
|
304
|
+
// Allow external abort signal to also abort the request
|
|
305
|
+
if (abortSignal) {
|
|
306
|
+
abortSignal.addEventListener('abort', () => controller.abort())
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let doneCalled = false
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const res = await fetch(`${apiBase}/ai/chat`, {
|
|
313
|
+
method: 'POST',
|
|
314
|
+
credentials: 'include',
|
|
315
|
+
headers: {
|
|
316
|
+
'Content-Type': 'application/json',
|
|
317
|
+
},
|
|
318
|
+
body: JSON.stringify(request),
|
|
319
|
+
signal: controller.signal,
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
if (!res.ok) {
|
|
323
|
+
const text = await res.text().catch(() => '')
|
|
324
|
+
callbacks.onError?.(text || `Request failed (${res.status})`, String(res.status))
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!res.body) {
|
|
329
|
+
callbacks.onError?.('No response body')
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const reader = res.body.getReader()
|
|
334
|
+
const decoder = new TextDecoder()
|
|
335
|
+
let buffer = ''
|
|
336
|
+
|
|
337
|
+
while (true) {
|
|
338
|
+
const { done, value } = await reader.read()
|
|
339
|
+
|
|
340
|
+
if (done) {
|
|
341
|
+
break
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
buffer += decoder.decode(value, { stream: true })
|
|
345
|
+
const lines = buffer.split('\n')
|
|
346
|
+
|
|
347
|
+
// Keep the last incomplete line in the buffer
|
|
348
|
+
buffer = lines.pop() || ''
|
|
349
|
+
|
|
350
|
+
for (const line of lines) {
|
|
351
|
+
if (line.startsWith('data: ')) {
|
|
352
|
+
const data = line.slice(6).trim()
|
|
353
|
+
if (!data) continue
|
|
354
|
+
|
|
355
|
+
const event = parseSseEvent(data)
|
|
356
|
+
if (!event) continue
|
|
357
|
+
|
|
358
|
+
switch (event.type) {
|
|
359
|
+
case 'token':
|
|
360
|
+
callbacks.onToken?.(event.token, event.fullText)
|
|
361
|
+
break
|
|
362
|
+
case 'status':
|
|
363
|
+
callbacks.onStatus?.(event.status, event.message)
|
|
364
|
+
break
|
|
365
|
+
case 'action':
|
|
366
|
+
callbacks.onAction?.(event.action)
|
|
367
|
+
break
|
|
368
|
+
case 'error':
|
|
369
|
+
callbacks.onError?.(event.error, event.code)
|
|
370
|
+
break
|
|
371
|
+
case 'done':
|
|
372
|
+
if (!doneCalled) {
|
|
373
|
+
doneCalled = true
|
|
374
|
+
callbacks.onDone?.(event.summary)
|
|
375
|
+
}
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Process any remaining buffer
|
|
383
|
+
if (buffer.startsWith('data: ')) {
|
|
384
|
+
const data = buffer.slice(6).trim()
|
|
385
|
+
if (data) {
|
|
386
|
+
const event = parseSseEvent(data)
|
|
387
|
+
if (event?.type === 'done' && !doneCalled) {
|
|
388
|
+
doneCalled = true
|
|
389
|
+
callbacks.onDone?.(event.summary)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!doneCalled) {
|
|
395
|
+
doneCalled = true
|
|
396
|
+
callbacks.onDone?.()
|
|
397
|
+
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
400
|
+
callbacks.onError?.('Request timed out or was cancelled', 'TIMEOUT')
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
callbacks.onError?.(error instanceof Error ? error.message : 'Unknown error')
|
|
404
|
+
} finally {
|
|
405
|
+
clearTimeout(timeoutId)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Chat message type from history endpoint
|
|
411
|
+
*/
|
|
412
|
+
export interface ChatHistoryMessage {
|
|
413
|
+
id: string
|
|
414
|
+
role: 'user' | 'assistant' | 'tool'
|
|
415
|
+
content: string | null
|
|
416
|
+
created_at: string
|
|
417
|
+
channel?: string
|
|
418
|
+
identifier?: string
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export interface ChatHistoryResponse {
|
|
422
|
+
messages: ChatHistoryMessage[]
|
|
423
|
+
hasMore: boolean
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Fetch AI chat history for the current project
|
|
428
|
+
*/
|
|
429
|
+
export async function getChatHistory(
|
|
430
|
+
apiBase: string,
|
|
431
|
+
limit = 50,
|
|
432
|
+
): Promise<ChatHistoryResponse> {
|
|
433
|
+
const res = await fetchWithTimeout(`${apiBase}/ai/chat/history?limit=${limit}`, {
|
|
434
|
+
method: 'GET',
|
|
435
|
+
credentials: 'include',
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
if (!res.ok) {
|
|
439
|
+
const text = await res.text().catch(() => '')
|
|
440
|
+
throw new Error(`Failed to fetch chat history (${res.status}): ${text || res.statusText}`)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return res.json()
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Fetch deployment status for the current project
|
|
448
|
+
*/
|
|
449
|
+
export async function getDeploymentStatus(
|
|
450
|
+
apiBase: string,
|
|
451
|
+
): Promise<DeploymentStatusResponse> {
|
|
452
|
+
const res = await fetchWithTimeout(`${apiBase}/deployment/status`, {
|
|
453
|
+
method: 'GET',
|
|
454
|
+
credentials: 'include',
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
if (!res.ok) {
|
|
458
|
+
const text = await res.text().catch(() => '')
|
|
459
|
+
throw new Error(`Failed to fetch deployment status (${res.status}): ${text || res.statusText}`)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return res.json()
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Fetch markdown content from a file
|
|
467
|
+
*/
|
|
468
|
+
export async function getMarkdownContent(
|
|
469
|
+
apiBase: string,
|
|
470
|
+
filePath: string,
|
|
471
|
+
): Promise<GetMarkdownContentResponse | null> {
|
|
472
|
+
const res = await fetchWithTimeout(
|
|
473
|
+
`${apiBase}/markdown/content?filePath=${encodeURIComponent(filePath)}`,
|
|
474
|
+
{
|
|
475
|
+
method: 'GET',
|
|
476
|
+
credentials: 'include',
|
|
477
|
+
},
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
if (!res.ok) {
|
|
481
|
+
if (res.status === 404) {
|
|
482
|
+
return null
|
|
483
|
+
}
|
|
484
|
+
const text = await res.text().catch(() => '')
|
|
485
|
+
throw new Error(`Failed to fetch markdown content (${res.status}): ${text || res.statusText}`)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return res.json()
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Update markdown page content
|
|
493
|
+
*/
|
|
494
|
+
export async function updateMarkdownPage(
|
|
495
|
+
apiBase: string,
|
|
496
|
+
request: UpdateMarkdownPageRequest,
|
|
497
|
+
): Promise<UpdateMarkdownPageResponse> {
|
|
498
|
+
const res = await fetchWithTimeout(`${apiBase}/markdown/update`, {
|
|
499
|
+
method: 'POST',
|
|
500
|
+
credentials: 'include',
|
|
501
|
+
headers: {
|
|
502
|
+
'Content-Type': 'application/json',
|
|
503
|
+
},
|
|
504
|
+
body: JSON.stringify(request),
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
if (!res.ok) {
|
|
508
|
+
const text = await res.text().catch(() => '')
|
|
509
|
+
throw new Error(`Failed to update markdown page (${res.status}): ${text || res.statusText}`)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return res.json()
|
|
513
|
+
}
|