@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.
Files changed (269) hide show
  1. package/README.md +237 -0
  2. package/dist/src/build-processor.d.ts +20 -0
  3. package/dist/src/build-processor.d.ts.map +1 -0
  4. package/dist/src/collection-scanner.d.ts +6 -0
  5. package/dist/src/collection-scanner.d.ts.map +1 -0
  6. package/dist/src/component-registry.d.ts +63 -0
  7. package/dist/src/component-registry.d.ts.map +1 -0
  8. package/dist/src/config.d.ts +24 -0
  9. package/dist/src/config.d.ts.map +1 -0
  10. package/dist/src/dev-middleware.d.ts +20 -0
  11. package/dist/src/dev-middleware.d.ts.map +1 -0
  12. package/dist/src/editor/ai.d.ts +60 -0
  13. package/dist/src/editor/ai.d.ts.map +1 -0
  14. package/dist/src/editor/api.d.ts +140 -0
  15. package/dist/src/editor/api.d.ts.map +1 -0
  16. package/dist/src/editor/color-utils.d.ts +106 -0
  17. package/dist/src/editor/color-utils.d.ts.map +1 -0
  18. package/dist/src/editor/components/ai-chat.d.ts +11 -0
  19. package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
  20. package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
  21. package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
  22. package/dist/src/editor/components/attribute-editor.d.ts +5 -0
  23. package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
  24. package/dist/src/editor/components/block-editor.d.ts +12 -0
  25. package/dist/src/editor/components/block-editor.d.ts.map +1 -0
  26. package/dist/src/editor/components/collections-browser.d.ts +2 -0
  27. package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
  28. package/dist/src/editor/components/color-toolbar.d.ts +12 -0
  29. package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
  30. package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
  31. package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
  32. package/dist/src/editor/components/create-page-modal.d.ts +2 -0
  33. package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
  34. package/dist/src/editor/components/editable-highlights.d.ts +9 -0
  35. package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
  36. package/dist/src/editor/components/error-boundary.d.ts +32 -0
  37. package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
  38. package/dist/src/editor/components/fields.d.ts +75 -0
  39. package/dist/src/editor/components/fields.d.ts.map +1 -0
  40. package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
  41. package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
  42. package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
  43. package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
  44. package/dist/src/editor/components/image-overlay.d.ts +12 -0
  45. package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
  46. package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
  47. package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
  48. package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
  49. package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
  50. package/dist/src/editor/components/media-library.d.ts +2 -0
  51. package/dist/src/editor/components/media-library.d.ts.map +1 -0
  52. package/dist/src/editor/components/outline.d.ts +21 -0
  53. package/dist/src/editor/components/outline.d.ts.map +1 -0
  54. package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
  55. package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
  56. package/dist/src/editor/components/seo-editor.d.ts +2 -0
  57. package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
  58. package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
  59. package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
  60. package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
  61. package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
  62. package/dist/src/editor/components/toast/toast.d.ts +7 -0
  63. package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
  64. package/dist/src/editor/components/toast/types.d.ts +7 -0
  65. package/dist/src/editor/components/toast/types.d.ts.map +1 -0
  66. package/dist/src/editor/components/toolbar.d.ts +21 -0
  67. package/dist/src/editor/components/toolbar.d.ts.map +1 -0
  68. package/dist/src/editor/config.d.ts +4 -0
  69. package/dist/src/editor/config.d.ts.map +1 -0
  70. package/dist/src/editor/constants.d.ts +101 -0
  71. package/dist/src/editor/constants.d.ts.map +1 -0
  72. package/dist/src/editor/context.d.ts +14 -0
  73. package/dist/src/editor/context.d.ts.map +1 -0
  74. package/dist/src/editor/dom.d.ts +77 -0
  75. package/dist/src/editor/dom.d.ts.map +1 -0
  76. package/dist/src/editor/editor.d.ts +64 -0
  77. package/dist/src/editor/editor.d.ts.map +1 -0
  78. package/dist/src/editor/history.d.ts +20 -0
  79. package/dist/src/editor/history.d.ts.map +1 -0
  80. package/dist/src/editor/hooks/index.d.ts +14 -0
  81. package/dist/src/editor/hooks/index.d.ts.map +1 -0
  82. package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
  83. package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
  84. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
  85. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
  86. package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
  87. package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
  88. package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
  89. package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
  90. package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
  91. package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
  92. package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
  93. package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
  94. package/dist/src/editor/hooks/utils.d.ts +32 -0
  95. package/dist/src/editor/hooks/utils.d.ts.map +1 -0
  96. package/dist/src/editor/index.d.ts +12 -0
  97. package/dist/src/editor/index.d.ts.map +1 -0
  98. package/dist/src/editor/lib/cn.d.ts +3 -0
  99. package/dist/src/editor/lib/cn.d.ts.map +1 -0
  100. package/dist/src/editor/manifest.d.ts +19 -0
  101. package/dist/src/editor/manifest.d.ts.map +1 -0
  102. package/dist/src/editor/markdown-api.d.ts +36 -0
  103. package/dist/src/editor/markdown-api.d.ts.map +1 -0
  104. package/dist/src/editor/signals.d.ts +242 -0
  105. package/dist/src/editor/signals.d.ts.map +1 -0
  106. package/dist/src/editor/storage.d.ts +27 -0
  107. package/dist/src/editor/storage.d.ts.map +1 -0
  108. package/dist/src/editor/text-styling.d.ts +350 -0
  109. package/dist/src/editor/text-styling.d.ts.map +1 -0
  110. package/dist/src/editor/themes.d.ts +38 -0
  111. package/dist/src/editor/themes.d.ts.map +1 -0
  112. package/dist/src/editor/types.d.ts +454 -0
  113. package/dist/src/editor/types.d.ts.map +1 -0
  114. package/dist/src/error-collector.d.ts +56 -0
  115. package/dist/src/error-collector.d.ts.map +1 -0
  116. package/dist/src/handlers/component-ops.d.ts +34 -0
  117. package/dist/src/handlers/component-ops.d.ts.map +1 -0
  118. package/dist/src/handlers/markdown-ops.d.ts +41 -0
  119. package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
  120. package/dist/src/handlers/request-utils.d.ts +20 -0
  121. package/dist/src/handlers/request-utils.d.ts.map +1 -0
  122. package/dist/src/handlers/source-writer.d.ts +51 -0
  123. package/dist/src/handlers/source-writer.d.ts.map +1 -0
  124. package/dist/src/html-processor.d.ts +63 -0
  125. package/dist/src/html-processor.d.ts.map +1 -0
  126. package/dist/src/index.d.ts +41 -0
  127. package/dist/src/index.d.ts.map +1 -0
  128. package/dist/src/manifest-writer.d.ts +111 -0
  129. package/dist/src/manifest-writer.d.ts.map +1 -0
  130. package/dist/src/media/contember.d.ts +15 -0
  131. package/dist/src/media/contember.d.ts.map +1 -0
  132. package/dist/src/media/local.d.ts +9 -0
  133. package/dist/src/media/local.d.ts.map +1 -0
  134. package/dist/src/media/s3.d.ts +12 -0
  135. package/dist/src/media/s3.d.ts.map +1 -0
  136. package/dist/src/media/types.d.ts +40 -0
  137. package/dist/src/media/types.d.ts.map +1 -0
  138. package/dist/src/preview-generator.d.ts +19 -0
  139. package/dist/src/preview-generator.d.ts.map +1 -0
  140. package/dist/src/seo-processor.d.ts +23 -0
  141. package/dist/src/seo-processor.d.ts.map +1 -0
  142. package/dist/src/source-finder/ast-extractors.d.ts +35 -0
  143. package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
  144. package/dist/src/source-finder/ast-parser.d.ts +16 -0
  145. package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
  146. package/dist/src/source-finder/cache.d.ts +18 -0
  147. package/dist/src/source-finder/cache.d.ts.map +1 -0
  148. package/dist/src/source-finder/collection-finder.d.ts +29 -0
  149. package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
  150. package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
  151. package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
  152. package/dist/src/source-finder/element-finder.d.ts +42 -0
  153. package/dist/src/source-finder/element-finder.d.ts.map +1 -0
  154. package/dist/src/source-finder/image-finder.d.ts +24 -0
  155. package/dist/src/source-finder/image-finder.d.ts.map +1 -0
  156. package/dist/src/source-finder/index.d.ts +9 -0
  157. package/dist/src/source-finder/index.d.ts.map +1 -0
  158. package/dist/src/source-finder/search-index.d.ts +27 -0
  159. package/dist/src/source-finder/search-index.d.ts.map +1 -0
  160. package/dist/src/source-finder/snippet-utils.d.ts +90 -0
  161. package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
  162. package/dist/src/source-finder/source-lookup.d.ts +16 -0
  163. package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
  164. package/dist/src/source-finder/types.d.ts +167 -0
  165. package/dist/src/source-finder/types.d.ts.map +1 -0
  166. package/dist/src/source-finder/variable-extraction.d.ts +37 -0
  167. package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
  168. package/dist/src/tailwind-colors.d.ts +54 -0
  169. package/dist/src/tailwind-colors.d.ts.map +1 -0
  170. package/dist/src/tsconfig.tsbuildinfo +1 -0
  171. package/dist/src/types.d.ts +367 -0
  172. package/dist/src/types.d.ts.map +1 -0
  173. package/dist/src/utils.d.ts +61 -0
  174. package/dist/src/utils.d.ts.map +1 -0
  175. package/dist/src/vite-plugin.d.ts +14 -0
  176. package/dist/src/vite-plugin.d.ts.map +1 -0
  177. package/dist/types/tsconfig.tsbuildinfo +1 -0
  178. package/package.json +80 -0
  179. package/src/build-processor.ts +784 -0
  180. package/src/collection-scanner.ts +304 -0
  181. package/src/component-registry.ts +393 -0
  182. package/src/config.ts +74 -0
  183. package/src/dev-middleware.ts +525 -0
  184. package/src/dist/src/tsconfig.tsbuildinfo +1 -0
  185. package/src/editor/ai.ts +185 -0
  186. package/src/editor/api.ts +513 -0
  187. package/src/editor/color-utils.ts +556 -0
  188. package/src/editor/components/ai-chat.tsx +632 -0
  189. package/src/editor/components/ai-tooltip.tsx +179 -0
  190. package/src/editor/components/attribute-editor.tsx +596 -0
  191. package/src/editor/components/block-editor.tsx +546 -0
  192. package/src/editor/components/collections-browser.tsx +248 -0
  193. package/src/editor/components/color-toolbar.tsx +314 -0
  194. package/src/editor/components/confirm-dialog.tsx +69 -0
  195. package/src/editor/components/create-page-modal.tsx +163 -0
  196. package/src/editor/components/editable-highlights.tsx +260 -0
  197. package/src/editor/components/error-boundary.tsx +87 -0
  198. package/src/editor/components/fields.tsx +387 -0
  199. package/src/editor/components/frontmatter-fields.tsx +469 -0
  200. package/src/editor/components/highlight-overlay.ts +229 -0
  201. package/src/editor/components/image-overlay.tsx +230 -0
  202. package/src/editor/components/markdown-editor-overlay.tsx +505 -0
  203. package/src/editor/components/markdown-inline-editor.tsx +780 -0
  204. package/src/editor/components/media-library.tsx +297 -0
  205. package/src/editor/components/outline.tsx +402 -0
  206. package/src/editor/components/redirect-countdown.tsx +45 -0
  207. package/src/editor/components/seo-editor.tsx +498 -0
  208. package/src/editor/components/text-style-toolbar.tsx +362 -0
  209. package/src/editor/components/toast/toast-container.tsx +15 -0
  210. package/src/editor/components/toast/toast.tsx +49 -0
  211. package/src/editor/components/toast/types.ts +7 -0
  212. package/src/editor/components/toolbar.tsx +366 -0
  213. package/src/editor/config.ts +12 -0
  214. package/src/editor/constants.ts +106 -0
  215. package/src/editor/context.tsx +38 -0
  216. package/src/editor/dom.ts +357 -0
  217. package/src/editor/editor.ts +1510 -0
  218. package/src/editor/env.d.ts +4 -0
  219. package/src/editor/history.ts +355 -0
  220. package/src/editor/hooks/index.ts +19 -0
  221. package/src/editor/hooks/useAIHandlers.ts +345 -0
  222. package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
  223. package/src/editor/hooks/useElementDetection.ts +284 -0
  224. package/src/editor/hooks/useImageHoverDetection.ts +102 -0
  225. package/src/editor/hooks/useTextSelection.ts +187 -0
  226. package/src/editor/hooks/useTooltipState.ts +126 -0
  227. package/src/editor/hooks/utils.ts +101 -0
  228. package/src/editor/index.tsx +481 -0
  229. package/src/editor/lib/cn.ts +4 -0
  230. package/src/editor/manifest.ts +25 -0
  231. package/src/editor/markdown-api.ts +209 -0
  232. package/src/editor/signals.ts +1351 -0
  233. package/src/editor/storage.ts +266 -0
  234. package/src/editor/styles.css +465 -0
  235. package/src/editor/text-styling.ts +773 -0
  236. package/src/editor/themes.ts +210 -0
  237. package/src/editor/types.ts +591 -0
  238. package/src/error-collector.ts +106 -0
  239. package/src/handlers/component-ops.ts +463 -0
  240. package/src/handlers/markdown-ops.ts +202 -0
  241. package/src/handlers/request-utils.ts +151 -0
  242. package/src/handlers/source-writer.ts +649 -0
  243. package/src/html-processor.ts +1108 -0
  244. package/src/index.ts +284 -0
  245. package/src/manifest-writer.ts +371 -0
  246. package/src/media/contember.ts +84 -0
  247. package/src/media/local.ts +114 -0
  248. package/src/media/s3.ts +133 -0
  249. package/src/media/types.ts +33 -0
  250. package/src/preview-generator.ts +293 -0
  251. package/src/seo-processor.ts +567 -0
  252. package/src/source-finder/ast-extractors.ts +185 -0
  253. package/src/source-finder/ast-parser.ts +150 -0
  254. package/src/source-finder/cache.ts +76 -0
  255. package/src/source-finder/collection-finder.ts +335 -0
  256. package/src/source-finder/cross-file-tracker.ts +741 -0
  257. package/src/source-finder/element-finder.ts +387 -0
  258. package/src/source-finder/image-finder.ts +283 -0
  259. package/src/source-finder/index.ts +37 -0
  260. package/src/source-finder/search-index.ts +525 -0
  261. package/src/source-finder/snippet-utils.ts +668 -0
  262. package/src/source-finder/source-lookup.ts +200 -0
  263. package/src/source-finder/types.ts +210 -0
  264. package/src/source-finder/variable-extraction.ts +406 -0
  265. package/src/tailwind-colors.ts +874 -0
  266. package/src/tsconfig.json +25 -0
  267. package/src/types.ts +406 -0
  268. package/src/utils.ts +186 -0
  269. 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
+ }