@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,366 @@
|
|
|
1
|
+
import type { ComponentChildren, FunctionComponent } from 'preact'
|
|
2
|
+
import { useState } from 'preact/hooks'
|
|
3
|
+
import { cn } from '../lib/cn'
|
|
4
|
+
import * as signals from '../signals'
|
|
5
|
+
import { showConfirmDialog } from '../signals'
|
|
6
|
+
import type { CollectionDefinition } from '../types'
|
|
7
|
+
|
|
8
|
+
export interface ToolbarCallbacks {
|
|
9
|
+
onEdit: () => void
|
|
10
|
+
onCompare: () => void
|
|
11
|
+
onSave: () => void
|
|
12
|
+
onDiscard: () => void
|
|
13
|
+
onAIChat?: () => void
|
|
14
|
+
onMediaLibrary?: () => void
|
|
15
|
+
onDismissDeployment?: () => void
|
|
16
|
+
onNavigateChange?: () => void
|
|
17
|
+
onEditContent?: () => void
|
|
18
|
+
onToggleHighlights?: () => void
|
|
19
|
+
onSeoEditor?: () => void
|
|
20
|
+
onOpenCollection?: (name: string) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ToolbarProps {
|
|
24
|
+
callbacks: ToolbarCallbacks
|
|
25
|
+
collectionDefinitions?: Record<string, CollectionDefinition>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DeploymentStatusIndicator = ({ onDismiss }: { onDismiss?: () => void }) => {
|
|
29
|
+
const deploymentStatus = signals.deploymentStatus.value
|
|
30
|
+
const lastDeployedAt = signals.lastDeployedAt.value
|
|
31
|
+
|
|
32
|
+
if (!deploymentStatus) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isActive = deploymentStatus === 'pending' || deploymentStatus === 'queued' || deploymentStatus === 'running'
|
|
37
|
+
const isCompleted = deploymentStatus === 'completed'
|
|
38
|
+
const isFailed = deploymentStatus === 'failed'
|
|
39
|
+
|
|
40
|
+
if (!isActive && !isCompleted && !isFailed) {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const formatTimeAgo = (dateStr: string) => {
|
|
45
|
+
const date = new Date(dateStr)
|
|
46
|
+
const now = new Date()
|
|
47
|
+
const diffMs = now.getTime() - date.getTime()
|
|
48
|
+
const diffSec = Math.floor(diffMs / 1000)
|
|
49
|
+
const diffMin = Math.floor(diffSec / 60)
|
|
50
|
+
|
|
51
|
+
if (diffMin < 1) return 'just now'
|
|
52
|
+
if (diffMin === 1) return '1m ago'
|
|
53
|
+
if (diffMin < 60) return `${diffMin}m ago`
|
|
54
|
+
const diffHour = Math.floor(diffMin / 60)
|
|
55
|
+
if (diffHour === 1) return '1h ago'
|
|
56
|
+
return `${diffHour}h ago`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
class={cn(
|
|
62
|
+
'flex items-center gap-1.5 sm:gap-2 px-3 py-2 sm:px-5 sm:py-2.5 text-sm font-medium rounded-cms-pill transition-all',
|
|
63
|
+
isActive && 'text-white/80',
|
|
64
|
+
isCompleted && 'bg-cms-primary text-cms-primary-text',
|
|
65
|
+
isFailed && 'bg-cms-error/20 text-cms-error cursor-pointer hover:bg-cms-error/30',
|
|
66
|
+
)}
|
|
67
|
+
onClick={isFailed ? onDismiss : undefined}
|
|
68
|
+
title={isFailed ? 'Click to dismiss' : undefined}
|
|
69
|
+
>
|
|
70
|
+
{isActive && (
|
|
71
|
+
<>
|
|
72
|
+
<span class="inline-block w-3.5 h-3.5 border-2 border-white/80 border-t-transparent rounded-full animate-spin" />
|
|
73
|
+
<span class="hidden sm:inline">Deploying</span>
|
|
74
|
+
</>
|
|
75
|
+
)}
|
|
76
|
+
{isCompleted && (
|
|
77
|
+
<>
|
|
78
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
79
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" />
|
|
80
|
+
</svg>
|
|
81
|
+
<span class="hidden sm:inline">Live{lastDeployedAt ? ` ${formatTimeAgo(lastDeployedAt)}` : ''}</span>
|
|
82
|
+
</>
|
|
83
|
+
)}
|
|
84
|
+
{isFailed && (
|
|
85
|
+
<>
|
|
86
|
+
<svg class="w-4 h-4 sm:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
87
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
88
|
+
</svg>
|
|
89
|
+
<span class="hidden sm:inline">Failed</span>
|
|
90
|
+
</>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const Toolbar = ({ callbacks, collectionDefinitions }: ToolbarProps) => {
|
|
97
|
+
const isEditing = signals.isEditing.value
|
|
98
|
+
const showingOriginal = signals.showingOriginal.value
|
|
99
|
+
const isChatOpen = signals.isChatOpen.value
|
|
100
|
+
const dirtyCount = signals.totalDirtyCount.value
|
|
101
|
+
const isSaving = signals.isSaving.value
|
|
102
|
+
const deploymentStatus = signals.deploymentStatus.value
|
|
103
|
+
const showEditableHighlights = signals.showEditableHighlights.value
|
|
104
|
+
const isPreviewingMarkdown = signals.isMarkdownPreview.value
|
|
105
|
+
const currentPageCollection = signals.currentPageCollection.value
|
|
106
|
+
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
|
107
|
+
|
|
108
|
+
if (isPreviewingMarkdown) return null
|
|
109
|
+
if (isChatOpen && !isEditing) return null
|
|
110
|
+
|
|
111
|
+
const showDeploymentStatus = deploymentStatus !== null
|
|
112
|
+
|
|
113
|
+
const stopPropagation = (e: Event) => e.stopPropagation()
|
|
114
|
+
|
|
115
|
+
const handleDiscard = async () => {
|
|
116
|
+
const confirmed = await showConfirmDialog({
|
|
117
|
+
title: 'Discard Changes',
|
|
118
|
+
message: 'Discard all changes? This cannot be undone.',
|
|
119
|
+
confirmLabel: 'Discard',
|
|
120
|
+
cancelLabel: 'Cancel',
|
|
121
|
+
variant: 'danger',
|
|
122
|
+
})
|
|
123
|
+
if (confirmed) {
|
|
124
|
+
callbacks.onDiscard()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const isToolbarOpen = isEditing
|
|
129
|
+
|
|
130
|
+
// Build menu items dynamically
|
|
131
|
+
const menuItems: Array<{ label: string; icon: ComponentChildren; onClick: () => void; isActive?: boolean }> = []
|
|
132
|
+
|
|
133
|
+
if (callbacks.onAIChat) {
|
|
134
|
+
menuItems.push({
|
|
135
|
+
label: 'AI Chat',
|
|
136
|
+
icon: (
|
|
137
|
+
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
138
|
+
<path d="M12 3l1.912 5.813a2 2 0 0 0 1.275 1.275L21 12l-5.813 1.912a2 2 0 0 0-1.275 1.275L12 21l-1.912-5.813a2 2 0 0 0-1.275-1.275L3 12l5.813-1.912a2 2 0 0 0 1.275-1.275L12 3z" />
|
|
139
|
+
</svg>
|
|
140
|
+
),
|
|
141
|
+
onClick: () => callbacks.onAIChat?.(),
|
|
142
|
+
isActive: isChatOpen,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Collection items from definitions
|
|
147
|
+
if (collectionDefinitions) {
|
|
148
|
+
for (const def of Object.values(collectionDefinitions)) {
|
|
149
|
+
menuItems.push({
|
|
150
|
+
label: def.label,
|
|
151
|
+
icon: (
|
|
152
|
+
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
153
|
+
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
154
|
+
<rect x="14" y="3" width="7" height="7" rx="1" />
|
|
155
|
+
<rect x="3" y="14" width="7" height="7" rx="1" />
|
|
156
|
+
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
157
|
+
</svg>
|
|
158
|
+
),
|
|
159
|
+
onClick: () => callbacks.onOpenCollection?.(def.name),
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (callbacks.onSeoEditor) {
|
|
165
|
+
menuItems.push({
|
|
166
|
+
label: 'SEO',
|
|
167
|
+
icon: (
|
|
168
|
+
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
169
|
+
<circle cx="11" cy="11" r="8" />
|
|
170
|
+
<path d="m21 21-4.3-4.3" />
|
|
171
|
+
</svg>
|
|
172
|
+
),
|
|
173
|
+
onClick: () => callbacks.onSeoEditor?.(),
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
menuItems.push({
|
|
178
|
+
label: 'Edit Page',
|
|
179
|
+
icon: (
|
|
180
|
+
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
181
|
+
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
|
|
182
|
+
</svg>
|
|
183
|
+
),
|
|
184
|
+
onClick: () => callbacks.onEdit(),
|
|
185
|
+
isActive: isEditing,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
if (currentPageCollection && callbacks.onEditContent) {
|
|
189
|
+
menuItems.push({
|
|
190
|
+
label: 'Content',
|
|
191
|
+
icon: (
|
|
192
|
+
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
193
|
+
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z" />
|
|
194
|
+
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
|
|
195
|
+
<path d="M10 13H8" />
|
|
196
|
+
<path d="M16 17H8" />
|
|
197
|
+
<path d="M16 13h-2" />
|
|
198
|
+
</svg>
|
|
199
|
+
),
|
|
200
|
+
onClick: () => callbacks.onEditContent?.(),
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<div
|
|
206
|
+
class={cn(
|
|
207
|
+
'fixed bottom-4 sm:bottom-8 z-2147483647 font-sans transition-all duration-300',
|
|
208
|
+
isToolbarOpen
|
|
209
|
+
? 'left-4 right-4 sm:left-1/2 sm:right-auto sm:-translate-x-1/2'
|
|
210
|
+
: 'right-4 sm:right-8',
|
|
211
|
+
)}
|
|
212
|
+
data-cms-ui
|
|
213
|
+
onMouseDown={stopPropagation}
|
|
214
|
+
onClick={stopPropagation}
|
|
215
|
+
>
|
|
216
|
+
<div class="flex items-center justify-between sm:justify-start gap-2 sm:gap-1.5 px-2 sm:px-2 py-2 sm:py-2 bg-cms-dark rounded-cms-xl shadow-[0_8px_32px_rgba(0,0,0,0.3)] border border-white/10">
|
|
217
|
+
{/* Outlines toggle - visible in toolbar when editing */}
|
|
218
|
+
{isEditing && !showingOriginal && callbacks.onToggleHighlights && (
|
|
219
|
+
<ToolbarButton
|
|
220
|
+
onClick={() => callbacks.onToggleHighlights?.()}
|
|
221
|
+
class={'flex gap-2.5 bg-white/10 text-white/80 hover:bg-white/20 hover:text-white py-2! pr-1.5!'}
|
|
222
|
+
>
|
|
223
|
+
Outlines
|
|
224
|
+
<span
|
|
225
|
+
class={cn(
|
|
226
|
+
'inline-block w-6 h-6 rounded-full shrink-0 transition-colors',
|
|
227
|
+
showEditableHighlights ? 'bg-cms-primary/50 border border-cms-primary' : 'bg-cms-dark',
|
|
228
|
+
)}
|
|
229
|
+
/>
|
|
230
|
+
</ToolbarButton>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
{/* Primary actions group */}
|
|
234
|
+
<div class="flex items-center gap-2 sm:gap-1.5">
|
|
235
|
+
{/* Deployment Status */}
|
|
236
|
+
{showDeploymentStatus && <DeploymentStatusIndicator onDismiss={callbacks.onDismissDeployment} />}
|
|
237
|
+
|
|
238
|
+
{/* Saving indicator */}
|
|
239
|
+
{isSaving && !showingOriginal && (
|
|
240
|
+
<div class="flex items-center gap-1.5 px-3 py-2 sm:px-5 sm:py-2.5 text-sm font-medium text-white/80">
|
|
241
|
+
<span class="inline-block w-3.5 h-3.5 border-2 border-white/80 border-t-transparent rounded-full animate-spin" />
|
|
242
|
+
<span>Saving</span>
|
|
243
|
+
</div>
|
|
244
|
+
)}
|
|
245
|
+
|
|
246
|
+
{/* Dirty indicator + Save/Discard group */}
|
|
247
|
+
{dirtyCount > 0 && !isSaving && !showingOriginal && (
|
|
248
|
+
<>
|
|
249
|
+
<button
|
|
250
|
+
onClick={callbacks.onNavigateChange}
|
|
251
|
+
class="hidden sm:block px-3 py-2 text-sm text-white/50 hover:text-white/80 hover:bg-white/10 rounded-cms-pill transition-all cursor-pointer tabular-nums"
|
|
252
|
+
title="Click to navigate through changes"
|
|
253
|
+
>
|
|
254
|
+
{dirtyCount} unsaved
|
|
255
|
+
</button>
|
|
256
|
+
{/* Mobile: show count badge only */}
|
|
257
|
+
<span class="sm:hidden px-2 py-1 text-xs text-white/50 tabular-nums">
|
|
258
|
+
{dirtyCount}
|
|
259
|
+
</span>
|
|
260
|
+
<ToolbarButton
|
|
261
|
+
class="bg-cms-primary text-cms-primary-text hover:bg-cms-primary-hover"
|
|
262
|
+
onClick={callbacks.onSave}
|
|
263
|
+
>
|
|
264
|
+
Save
|
|
265
|
+
</ToolbarButton>
|
|
266
|
+
<ToolbarButton
|
|
267
|
+
onClick={handleDiscard}
|
|
268
|
+
class="bg-cms-error text-white hover:bg-red-600"
|
|
269
|
+
>
|
|
270
|
+
Discard
|
|
271
|
+
</ToolbarButton>
|
|
272
|
+
</>
|
|
273
|
+
)}
|
|
274
|
+
|
|
275
|
+
{isEditing
|
|
276
|
+
? (
|
|
277
|
+
<button
|
|
278
|
+
onClick={(e) => {
|
|
279
|
+
e.stopPropagation()
|
|
280
|
+
callbacks.onEdit()
|
|
281
|
+
}}
|
|
282
|
+
class="w-10 h-10 flex items-center justify-center rounded-full text-white/60 hover:text-white hover:bg-white/10 transition-all duration-150 cursor-pointer"
|
|
283
|
+
title="Done editing"
|
|
284
|
+
>
|
|
285
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
286
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
287
|
+
</svg>
|
|
288
|
+
</button>
|
|
289
|
+
)
|
|
290
|
+
: (
|
|
291
|
+
<div class="relative">
|
|
292
|
+
<button
|
|
293
|
+
onClick={(e) => {
|
|
294
|
+
e.stopPropagation()
|
|
295
|
+
setIsMenuOpen(!isMenuOpen)
|
|
296
|
+
}}
|
|
297
|
+
class="w-10 h-10 rounded-full bg-cms-primary flex items-center justify-center cursor-pointer transition-all duration-150 hover:bg-cms-primary-hover"
|
|
298
|
+
aria-label="Menu"
|
|
299
|
+
>
|
|
300
|
+
<span class="w-3 h-3 rounded-full bg-black" />
|
|
301
|
+
</button>
|
|
302
|
+
|
|
303
|
+
{isMenuOpen && (
|
|
304
|
+
<>
|
|
305
|
+
{/* Backdrop to close menu */}
|
|
306
|
+
<div
|
|
307
|
+
class="fixed inset-0 z-[-1]"
|
|
308
|
+
onClick={(e) => {
|
|
309
|
+
e.stopPropagation()
|
|
310
|
+
setIsMenuOpen(false)
|
|
311
|
+
}}
|
|
312
|
+
/>
|
|
313
|
+
{/* Menu popover */}
|
|
314
|
+
<div class="absolute bottom-full right-0 mb-4 min-w-[180px] bg-cms-dark rounded-cms-lg shadow-[0_8px_32px_rgba(0,0,0,0.4)] border border-white/10 overflow-hidden py-1">
|
|
315
|
+
{menuItems.map((item, index) => (
|
|
316
|
+
<button
|
|
317
|
+
key={index}
|
|
318
|
+
onClick={(e) => {
|
|
319
|
+
e.stopPropagation()
|
|
320
|
+
item.onClick()
|
|
321
|
+
setIsMenuOpen(false)
|
|
322
|
+
}}
|
|
323
|
+
class={cn(
|
|
324
|
+
'w-full px-4 py-2.5 text-sm font-medium text-left transition-colors cursor-pointer flex items-center gap-3',
|
|
325
|
+
item.isActive
|
|
326
|
+
? 'bg-white/20 text-white'
|
|
327
|
+
: 'text-white/80 hover:bg-white/10 hover:text-white',
|
|
328
|
+
)}
|
|
329
|
+
>
|
|
330
|
+
<span class="shrink-0 opacity-70">{item.icon}</span>
|
|
331
|
+
{item.label}
|
|
332
|
+
</button>
|
|
333
|
+
))}
|
|
334
|
+
</div>
|
|
335
|
+
</>
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
)}
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
interface ToolbarButtonProps {
|
|
346
|
+
onClick?: () => void
|
|
347
|
+
class?: string
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const ToolbarButton: FunctionComponent<ToolbarButtonProps> = ({ children, onClick, class: className }) => {
|
|
351
|
+
return (
|
|
352
|
+
<button
|
|
353
|
+
onClick={(e) => {
|
|
354
|
+
e.stopPropagation()
|
|
355
|
+
onClick?.()
|
|
356
|
+
}}
|
|
357
|
+
class={cn(
|
|
358
|
+
'px-3 py-2 sm:px-5 sm:py-2.5 text-sm font-medium transition-all duration-150 flex items-center justify-center rounded-cms-pill whitespace-nowrap border-transparent border',
|
|
359
|
+
onClick && 'cursor-pointer',
|
|
360
|
+
className,
|
|
361
|
+
)}
|
|
362
|
+
>
|
|
363
|
+
{children}
|
|
364
|
+
</button>
|
|
365
|
+
)
|
|
366
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CmsConfig } from './types'
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_CONFIG: CmsConfig = {
|
|
4
|
+
apiBase: '/_nua/cms',
|
|
5
|
+
highlightColor: '#005AE0',
|
|
6
|
+
debug: false,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getConfig(): CmsConfig {
|
|
10
|
+
const userConfig = typeof window !== 'undefined' ? window.NuaCmsConfig || {} : {}
|
|
11
|
+
return { ...DEFAULT_CONFIG, ...userConfig }
|
|
12
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for the CMS editor
|
|
3
|
+
* Centralizes magic numbers and configuration values
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Z-index layers for CMS UI elements.
|
|
8
|
+
* Uses high values to ensure CMS UI appears above all page content.
|
|
9
|
+
* Each layer is ordered from back to front.
|
|
10
|
+
*/
|
|
11
|
+
export const Z_INDEX = {
|
|
12
|
+
/** Highlight overlay for hovered elements */
|
|
13
|
+
HIGHLIGHT: 2147483645,
|
|
14
|
+
/** Overlay backdrop for modals */
|
|
15
|
+
OVERLAY: 2147483646,
|
|
16
|
+
/** Modal panels (block editor, AI chat) */
|
|
17
|
+
MODAL: 2147483647,
|
|
18
|
+
/** Toast notifications - always on top */
|
|
19
|
+
TOAST: 2147483648,
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Timing constants for UI interactions
|
|
24
|
+
*/
|
|
25
|
+
export const TIMING = {
|
|
26
|
+
/** Throttle interval for element detection during mouse move (ms) */
|
|
27
|
+
ELEMENT_DETECTION_THROTTLE_MS: 16,
|
|
28
|
+
/** Delay before clearing focus state on blur (ms) */
|
|
29
|
+
BLUR_DELAY_MS: 10,
|
|
30
|
+
/** Duration before toast starts fading out (ms) */
|
|
31
|
+
TOAST_VISIBLE_DURATION_MS: 2200,
|
|
32
|
+
/** Duration of toast fade out animation (ms) */
|
|
33
|
+
TOAST_FADE_DURATION_MS: 200,
|
|
34
|
+
/** Duration to show component insertion preview before removal (ms) */
|
|
35
|
+
PREVIEW_SUCCESS_DURATION_MS: 3000,
|
|
36
|
+
/** Duration to show error preview before removal (ms) */
|
|
37
|
+
PREVIEW_ERROR_DURATION_MS: 5000,
|
|
38
|
+
/** Delay before focusing input after expansion (ms) */
|
|
39
|
+
FOCUS_DELAY_MS: 50,
|
|
40
|
+
} as const
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Layout constants for UI positioning
|
|
44
|
+
*/
|
|
45
|
+
export const LAYOUT = {
|
|
46
|
+
/** Edge threshold for component selection (pixels from border) */
|
|
47
|
+
COMPONENT_EDGE_THRESHOLD: 32,
|
|
48
|
+
/** Minimum space needed to show label outside the element */
|
|
49
|
+
LABEL_OUTSIDE_THRESHOLD: 28,
|
|
50
|
+
/** Padding from viewport edges for sticky label */
|
|
51
|
+
STICKY_PADDING: 8,
|
|
52
|
+
/** Default padding from viewport edges */
|
|
53
|
+
VIEWPORT_PADDING: 16,
|
|
54
|
+
/** Default tooltip width */
|
|
55
|
+
TOOLTIP_WIDTH: 200,
|
|
56
|
+
/** Expanded tooltip minimum width */
|
|
57
|
+
TOOLTIP_EXPANDED_MIN_WIDTH: 280,
|
|
58
|
+
/** Expanded tooltip maximum width */
|
|
59
|
+
TOOLTIP_EXPANDED_MAX_WIDTH: 320,
|
|
60
|
+
/** Block editor panel width */
|
|
61
|
+
BLOCK_EDITOR_WIDTH: 400,
|
|
62
|
+
/** Block editor approximate height for positioning */
|
|
63
|
+
BLOCK_EDITOR_HEIGHT: 500,
|
|
64
|
+
/** AI chat panel width */
|
|
65
|
+
AI_CHAT_WIDTH: 400,
|
|
66
|
+
} as const
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* API request configuration
|
|
70
|
+
*/
|
|
71
|
+
export const API = {
|
|
72
|
+
/** Default request timeout in milliseconds */
|
|
73
|
+
REQUEST_TIMEOUT_MS: 30000,
|
|
74
|
+
/** AI streaming request timeout in milliseconds */
|
|
75
|
+
AI_STREAM_TIMEOUT_MS: 120000,
|
|
76
|
+
/** Maximum retry attempts for failed requests */
|
|
77
|
+
MAX_RETRIES: 3,
|
|
78
|
+
/** Base delay for exponential backoff (ms) */
|
|
79
|
+
RETRY_BASE_DELAY_MS: 1000,
|
|
80
|
+
} as const
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Storage keys for persistence
|
|
84
|
+
*/
|
|
85
|
+
export const STORAGE_KEYS = {
|
|
86
|
+
PENDING_EDITS: 'cms-pending-edits',
|
|
87
|
+
PENDING_IMAGE_EDITS: 'cms-pending-image-edits',
|
|
88
|
+
PENDING_COLOR_EDITS: 'cms-pending-color-edits',
|
|
89
|
+
PENDING_ATTRIBUTE_EDITS: 'cms-pending-attribute-edits',
|
|
90
|
+
SETTINGS: 'cms-settings',
|
|
91
|
+
PENDING_ENTRY_NAVIGATION: 'cms-pending-entry-navigation',
|
|
92
|
+
} as const
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* CSS class prefixes and identifiers
|
|
96
|
+
*/
|
|
97
|
+
export const CSS = {
|
|
98
|
+
/** Data attribute for CMS UI elements (to prevent event propagation) */
|
|
99
|
+
UI_ATTRIBUTE: 'data-cms-ui',
|
|
100
|
+
/** Data attribute for CMS element IDs */
|
|
101
|
+
ID_ATTRIBUTE: 'data-cms-id',
|
|
102
|
+
/** Data attribute for component IDs */
|
|
103
|
+
COMPONENT_ID_ATTRIBUTE: 'data-cms-component-id',
|
|
104
|
+
/** Custom element tag for highlight overlay */
|
|
105
|
+
HIGHLIGHT_ELEMENT: 'cms-highlight-overlay',
|
|
106
|
+
} as const
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useEffect } from 'preact/hooks'
|
|
2
|
+
import * as signals from './signals'
|
|
3
|
+
import type { CmsConfig, EditorState } from './types'
|
|
4
|
+
|
|
5
|
+
export interface CmsProviderProps {
|
|
6
|
+
children: any
|
|
7
|
+
initialConfig?: CmsConfig
|
|
8
|
+
initialState?: EditorState
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* CMS Provider component.
|
|
13
|
+
*
|
|
14
|
+
* With signals, this provider is mainly for initialization and legacy compatibility.
|
|
15
|
+
* New code should import signals directly rather than using context.
|
|
16
|
+
*/
|
|
17
|
+
export function CmsProvider({ children, initialConfig, initialState }: CmsProviderProps) {
|
|
18
|
+
// Initialize signals from props if provided
|
|
19
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: only run on mount
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (initialConfig) {
|
|
22
|
+
signals.setConfig(initialConfig)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (initialState) {
|
|
26
|
+
signals.batch(() => {
|
|
27
|
+
signals.setEnabled(initialState.isEnabled)
|
|
28
|
+
signals.setEditing(initialState.isEditing)
|
|
29
|
+
signals.setShowingOriginal(initialState.showingOriginal)
|
|
30
|
+
signals.setCurrentEditingId(initialState.currentEditingId)
|
|
31
|
+
signals.setCurrentComponentId(initialState.currentComponentId)
|
|
32
|
+
signals.setManifest(initialState.manifest)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
}, [])
|
|
36
|
+
|
|
37
|
+
return <>{children}</>
|
|
38
|
+
}
|