@nuasite/cms 0.19.1 → 0.20.2

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 (39) hide show
  1. package/dist/editor.js +12615 -12689
  2. package/package.json +3 -3
  3. package/src/build-processor.ts +4 -4
  4. package/src/dev-middleware.ts +185 -189
  5. package/src/editor/api.ts +0 -251
  6. package/src/editor/components/fields.tsx +6 -6
  7. package/src/editor/components/markdown-editor-overlay.tsx +46 -70
  8. package/src/editor/components/markdown-inline-editor.tsx +34 -165
  9. package/src/editor/components/mdx-block-view.tsx +351 -47
  10. package/src/editor/components/mdx-component-picker.tsx +35 -11
  11. package/src/editor/components/media-library.tsx +1 -15
  12. package/src/editor/components/modal-shell.tsx +1 -1
  13. package/src/editor/components/toolbar.tsx +0 -75
  14. package/src/editor/constants.ts +0 -4
  15. package/src/editor/editor.ts +2 -192
  16. package/src/editor/hooks/index.ts +0 -3
  17. package/src/editor/hooks/useBlockEditorHandlers.ts +1 -8
  18. package/src/editor/hooks/useTooltipState.ts +1 -2
  19. package/src/editor/index.tsx +2 -18
  20. package/src/editor/milkdown-mdx-plugin.tsx +116 -19
  21. package/src/editor/milkdown-utils.ts +174 -0
  22. package/src/editor/post-message.ts +0 -6
  23. package/src/editor/signals.ts +0 -183
  24. package/src/editor/styles.css +0 -108
  25. package/src/editor/types.ts +0 -76
  26. package/src/html-processor.ts +9 -7
  27. package/src/source-finder/cache.ts +47 -0
  28. package/src/source-finder/collection-finder.ts +181 -0
  29. package/src/source-finder/index.ts +5 -2
  30. package/src/source-finder/search-index.ts +79 -0
  31. package/src/source-finder/snippet-utils.ts +36 -61
  32. package/src/types.ts +0 -4
  33. package/src/utils.ts +10 -0
  34. package/src/vite-plugin.ts +24 -4
  35. package/src/editor/ai.ts +0 -185
  36. package/src/editor/components/ai-chat.tsx +0 -631
  37. package/src/editor/components/ai-tooltip.tsx +0 -180
  38. package/src/editor/components/mdx-props-editor.tsx +0 -94
  39. package/src/editor/hooks/useAIHandlers.ts +0 -345
@@ -13,7 +13,6 @@ export interface ToolbarCallbacks {
13
13
  onDiscard: () => void
14
14
  onSelectElement?: () => void
15
15
  onMediaLibrary?: () => void
16
- onDismissDeployment?: () => void
17
16
  onNavigateChange?: () => void
18
17
  onEditContent?: () => void
19
18
  onToggleHighlights?: () => void
@@ -39,80 +38,11 @@ const GridIcon = () => (
39
38
  </svg>
40
39
  )
41
40
 
42
- const DeploymentStatusIndicator = ({ onDismiss }: { onDismiss?: () => void }) => {
43
- const deploymentStatus = signals.deploymentStatus.value
44
- const lastDeployedAt = signals.lastDeployedAt.value
45
-
46
- if (!deploymentStatus) {
47
- return null
48
- }
49
-
50
- const isActive = deploymentStatus === 'pending' || deploymentStatus === 'queued' || deploymentStatus === 'running'
51
- const isCompleted = deploymentStatus === 'completed'
52
- const isFailed = deploymentStatus === 'failed'
53
-
54
- if (!isActive && !isCompleted && !isFailed) {
55
- return null
56
- }
57
-
58
- const formatTimeAgo = (dateStr: string) => {
59
- const date = new Date(dateStr)
60
- const now = new Date()
61
- const diffMs = now.getTime() - date.getTime()
62
- const diffSec = Math.floor(diffMs / 1000)
63
- const diffMin = Math.floor(diffSec / 60)
64
-
65
- if (diffMin < 1) return 'just now'
66
- if (diffMin === 1) return '1m ago'
67
- if (diffMin < 60) return `${diffMin}m ago`
68
- const diffHour = Math.floor(diffMin / 60)
69
- if (diffHour === 1) return '1h ago'
70
- return `${diffHour}h ago`
71
- }
72
-
73
- return (
74
- <div
75
- class={cn(
76
- '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',
77
- isActive && 'text-white/80',
78
- isCompleted && 'bg-cms-primary text-cms-primary-text',
79
- isFailed && 'bg-cms-error/20 text-cms-error cursor-pointer hover:bg-cms-error/30',
80
- )}
81
- onClick={isFailed ? onDismiss : undefined}
82
- title={isFailed ? 'Click to dismiss' : undefined}
83
- >
84
- {isActive && (
85
- <>
86
- <span class="inline-block w-3.5 h-3.5 border-2 border-white/80 border-t-transparent rounded-full animate-spin" />
87
- <span class="hidden sm:inline">Deploying</span>
88
- </>
89
- )}
90
- {isCompleted && (
91
- <>
92
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
93
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" />
94
- </svg>
95
- <span class="hidden sm:inline">Live{lastDeployedAt ? ` ${formatTimeAgo(lastDeployedAt)}` : ''}</span>
96
- </>
97
- )}
98
- {isFailed && (
99
- <>
100
- <svg class="w-4 h-4 sm:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
101
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
102
- </svg>
103
- <span class="hidden sm:inline">Failed</span>
104
- </>
105
- )}
106
- </div>
107
- )
108
- }
109
-
110
41
  export const Toolbar = ({ callbacks, collectionDefinitions }: ToolbarProps) => {
111
42
  const isEditing = signals.isEditing.value
112
43
  const showingOriginal = signals.showingOriginal.value
113
44
  const dirtyCount = signals.totalDirtyCount.value
114
45
  const isSaving = signals.isSaving.value
115
- const deploymentStatus = signals.deploymentStatus.value
116
46
  const showEditableHighlights = signals.showEditableHighlights.value
117
47
  const isPreviewingMarkdown = signals.isMarkdownPreview.value
118
48
  const currentPageCollection = signals.currentPageCollection.value
@@ -123,8 +53,6 @@ export const Toolbar = ({ callbacks, collectionDefinitions }: ToolbarProps) => {
123
53
 
124
54
  if (isPreviewingMarkdown) return null
125
55
 
126
- const showDeploymentStatus = deploymentStatus !== null
127
-
128
56
  const stopPropagation = (e: Event) => e.stopPropagation()
129
57
 
130
58
  const handleDiscard = async () => {
@@ -298,9 +226,6 @@ export const Toolbar = ({ callbacks, collectionDefinitions }: ToolbarProps) => {
298
226
 
299
227
  {/* Primary actions group */}
300
228
  <div class="flex items-center gap-2 sm:gap-1.5">
301
- {/* Deployment Status */}
302
- {showDeploymentStatus && <DeploymentStatusIndicator onDismiss={callbacks.onDismissDeployment} />}
303
-
304
229
  {/* Saving indicator */}
305
230
  {isSaving && !showingOriginal && (
306
231
  <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">
@@ -67,8 +67,6 @@ export const LAYOUT = {
67
67
  BLOCK_EDITOR_WIDTH: 400,
68
68
  /** Block editor approximate height for positioning */
69
69
  BLOCK_EDITOR_HEIGHT: 500,
70
- /** AI chat panel width */
71
- AI_CHAT_WIDTH: 400,
72
70
  } as const
73
71
 
74
72
  /**
@@ -77,8 +75,6 @@ export const LAYOUT = {
77
75
  export const API = {
78
76
  /** Default request timeout in milliseconds */
79
77
  REQUEST_TIMEOUT_MS: 30000,
80
- /** AI streaming request timeout in milliseconds */
81
- AI_STREAM_TIMEOUT_MS: 120000,
82
78
  /** Maximum retry attempts for failed requests */
83
79
  MAX_RETRIES: 3,
84
80
  /** Base delay for exponential backoff (ms) */
@@ -1,4 +1,4 @@
1
- import { fetchManifest, getDeploymentStatus, getMarkdownContent, saveBatchChanges } from './api'
1
+ import { fetchManifest, getMarkdownContent, saveBatchChanges } from './api'
2
2
  import { CSS, TIMING } from './constants'
3
3
  import {
4
4
  cleanupHighlightSystem,
@@ -33,7 +33,7 @@ import {
33
33
  saveImageEditsToStorage,
34
34
  } from './storage'
35
35
  import type { Attribute } from './types'
36
- import type { AttributeChangePayload, ChangePayload, CmsConfig, DeploymentStatusResponse, ManifestEntry, SavedAttributeEdit } from './types'
36
+ import type { AttributeChangePayload, ChangePayload, CmsConfig, ManifestEntry, SavedAttributeEdit } from './types'
37
37
 
38
38
  // CSS attribute for markdown content elements
39
39
  const MARKDOWN_ATTRIBUTE = 'data-cms-markdown'
@@ -261,10 +261,6 @@ export async function startEditMode(
261
261
  const targetId = innermostCms.getAttribute('data-cms-id')
262
262
  innermostCms.focus()
263
263
  signals.setCurrentEditingId(targetId)
264
- // Update chat context if chat is open
265
- if (signals.isChatOpen.value && targetId) {
266
- signals.setChatContextElement(targetId)
267
- }
268
264
  logDebug(config.debug, 'Click - focusing innermost CMS element:', targetId)
269
265
  onStateChange?.()
270
266
  }
@@ -277,10 +273,6 @@ export async function startEditMode(
277
273
  (e) => {
278
274
  if (e.target === el) {
279
275
  signals.setCurrentEditingId(cmsId)
280
- // Update chat context if chat is open
281
- if (signals.isChatOpen.value && cmsId) {
282
- signals.setChatContextElement(cmsId)
283
- }
284
276
  logDebug(config.debug, 'Focus on', cmsId)
285
277
  onStateChange?.()
286
278
  }
@@ -884,9 +876,6 @@ export async function saveAllChanges(
884
876
  return { success: false, updated: result.updated, errors: result.errors }
885
877
  }
886
878
 
887
- // Start polling for deployment status after successful save
888
- startDeploymentPolling(config)
889
-
890
879
  onStateChange?.()
891
880
  return { success: true, updated: result.updated }
892
881
  } catch (err) {
@@ -1383,185 +1372,6 @@ export function handleColorChange(
1383
1372
  onStateChange?.()
1384
1373
  }
1385
1374
 
1386
- // ============================================================================
1387
- // Deployment Status Polling
1388
- // ============================================================================
1389
-
1390
- const DEPLOYMENT_POLL_INTERVAL_MS = 3000
1391
- const DEPLOYMENT_SUCCESS_HIDE_DELAY_MS = 5000
1392
- const DEPLOYMENT_INITIAL_DELAY_MS = 2000
1393
- const DEPLOYMENT_MAX_WAIT_ATTEMPTS = 10 // Keep polling for up to 30 seconds waiting for deployment to start
1394
-
1395
- let deploymentPollTimer: ReturnType<typeof setInterval> | null = null
1396
- let deploymentHideTimer: ReturnType<typeof setTimeout> | null = null
1397
- let deploymentWaitAttempts = 0
1398
- let deploymentStartTimestamp: string | null = null
1399
- let deploymentCallback: ((status: 'completed' | 'failed' | 'timeout') => void) | null = null
1400
-
1401
- export interface DeploymentPollingOptions {
1402
- /** Called when deployment completes, fails, or times out */
1403
- onComplete?: (status: 'completed' | 'failed' | 'timeout') => void
1404
- }
1405
-
1406
- /**
1407
- * Start polling for deployment status after a save operation.
1408
- * Polls the API every 3 seconds until deployment completes or fails.
1409
- * Waits for deployment to appear for up to 30 seconds before giving up.
1410
- * Skips polling entirely when deployment is not available (e.g. local dev).
1411
- */
1412
- export async function startDeploymentPolling(config: CmsConfig, options?: DeploymentPollingOptions): Promise<void> {
1413
- // Clear any existing timers
1414
- stopDeploymentPolling()
1415
-
1416
- // Do a preflight check to see if deployment is available
1417
- try {
1418
- const preflight = await getDeploymentStatus(config.apiBase)
1419
- if (preflight.deploymentEnabled === false) {
1420
- // Deployment not available (e.g. local dev) — skip polling entirely
1421
- return
1422
- }
1423
- } catch {
1424
- // If we can't even reach the endpoint, skip polling
1425
- return
1426
- }
1427
-
1428
- // Reset wait attempts counter and store the timestamp when we started
1429
- deploymentWaitAttempts = 0
1430
- deploymentStartTimestamp = new Date().toISOString()
1431
- deploymentCallback = options?.onComplete ?? null
1432
-
1433
- // Set initial status to indicate deployment started
1434
- signals.updateDeploymentState({
1435
- status: 'pending',
1436
- isPolling: true,
1437
- error: null,
1438
- })
1439
-
1440
- const poll = async () => {
1441
- try {
1442
- const status: DeploymentStatusResponse = await getDeploymentStatus(config.apiBase)
1443
-
1444
- if (status.currentDeployment) {
1445
- // Found an active deployment - reset wait counter
1446
- deploymentWaitAttempts = 0
1447
-
1448
- signals.updateDeploymentState({
1449
- status: status.currentDeployment.status,
1450
- })
1451
-
1452
- // Check if deployment is still active
1453
- const isActive = ['pending', 'queued', 'running'].includes(status.currentDeployment.status)
1454
-
1455
- if (!isActive) {
1456
- // Deployment finished
1457
- const cb = deploymentCallback
1458
- stopDeploymentPolling()
1459
-
1460
- if (status.currentDeployment.status === 'completed') {
1461
- // Update last deployed timestamp
1462
- if (status.lastSuccessfulDeployment) {
1463
- signals.setLastDeployedAt(status.lastSuccessfulDeployment.completedAt)
1464
- }
1465
-
1466
- // Auto-hide after 5 seconds for successful deployments
1467
- deploymentHideTimer = setTimeout(() => {
1468
- signals.resetDeploymentState()
1469
- }, DEPLOYMENT_SUCCESS_HIDE_DELAY_MS)
1470
-
1471
- cb?.('completed')
1472
- } else {
1473
- // For failed deployments, keep showing until user dismisses
1474
- cb?.('failed')
1475
- }
1476
- }
1477
- } else {
1478
- // No active deployment found
1479
- deploymentWaitAttempts++
1480
-
1481
- // Check if we have a recent successful deployment (completed after we started polling)
1482
- if (status.lastSuccessfulDeployment && deploymentStartTimestamp) {
1483
- const lastDeployTime = new Date(status.lastSuccessfulDeployment.completedAt).getTime()
1484
- const startTime = new Date(deploymentStartTimestamp).getTime()
1485
-
1486
- if (lastDeployTime > startTime) {
1487
- // Deployment completed after we started - show success
1488
- signals.updateDeploymentState({
1489
- status: 'completed',
1490
- lastDeployedAt: status.lastSuccessfulDeployment.completedAt,
1491
- isPolling: false,
1492
- })
1493
-
1494
- // Auto-hide after 5 seconds
1495
- deploymentHideTimer = setTimeout(() => {
1496
- signals.resetDeploymentState()
1497
- }, DEPLOYMENT_SUCCESS_HIDE_DELAY_MS)
1498
-
1499
- const cb = deploymentCallback
1500
- stopDeploymentPolling()
1501
- cb?.('completed')
1502
- return
1503
- }
1504
- }
1505
-
1506
- // Keep waiting if we haven't exceeded max attempts
1507
- if (deploymentWaitAttempts >= DEPLOYMENT_MAX_WAIT_ATTEMPTS) {
1508
- // Give up waiting - deployment may have failed to start
1509
- console.warn('[CMS] No deployment found after waiting, giving up')
1510
- const cb = deploymentCallback
1511
- signals.resetDeploymentState()
1512
- stopDeploymentPolling()
1513
- cb?.('timeout')
1514
- }
1515
- // Otherwise keep polling with "pending" status
1516
- }
1517
- } catch (error) {
1518
- console.error('[CMS] Failed to fetch deployment status:', error)
1519
- signals.updateDeploymentState({
1520
- status: 'failed',
1521
- error: error instanceof Error ? error.message : 'Unknown error',
1522
- isPolling: false,
1523
- })
1524
- const cb = deploymentCallback
1525
- stopDeploymentPolling()
1526
- cb?.('failed')
1527
- }
1528
- }
1529
-
1530
- // Delay initial poll to allow deployment to be registered
1531
- setTimeout(() => {
1532
- poll()
1533
- // Then poll every 3 seconds
1534
- deploymentPollTimer = setInterval(poll, DEPLOYMENT_POLL_INTERVAL_MS)
1535
- }, DEPLOYMENT_INITIAL_DELAY_MS)
1536
- }
1537
-
1538
- /**
1539
- * Stop polling for deployment status.
1540
- */
1541
- export function stopDeploymentPolling(): void {
1542
- if (deploymentPollTimer) {
1543
- clearInterval(deploymentPollTimer)
1544
- deploymentPollTimer = null
1545
- }
1546
- deploymentWaitAttempts = 0
1547
- deploymentStartTimestamp = null
1548
- deploymentCallback = null
1549
- signals.setDeploymentPolling(false)
1550
- }
1551
-
1552
- /**
1553
- * Dismiss the deployment status indicator.
1554
- * Used when user clicks on a failed deployment status.
1555
- */
1556
- export function dismissDeploymentStatus(): void {
1557
- if (deploymentHideTimer) {
1558
- clearTimeout(deploymentHideTimer)
1559
- deploymentHideTimer = null
1560
- }
1561
- stopDeploymentPolling()
1562
- signals.resetDeploymentState()
1563
- }
1564
-
1565
1375
  // ============================================================================
1566
1376
  // Attribute Tracking
1567
1377
  // ============================================================================
@@ -6,9 +6,6 @@ export { isElementInCmsUI, usePositionTracking } from './utils'
6
6
  export { useTooltipState } from './useTooltipState'
7
7
  export type { TooltipState, UseTooltipStateOptions } from './useTooltipState'
8
8
 
9
- export { useAIHandlers } from './useAIHandlers'
10
- export type { AIHandlersOptions } from './useAIHandlers'
11
-
12
9
  export { useBlockEditorHandlers } from './useBlockEditorHandlers'
13
10
  export type { BlockEditorHandlersOptions } from './useBlockEditorHandlers'
14
11
 
@@ -1,6 +1,5 @@
1
1
  import { useCallback, useState } from 'preact/hooks'
2
2
  import { logDebug } from '../dom'
3
- import { startDeploymentPolling } from '../editor'
4
3
  import { getComponentInstances } from '../manifest'
5
4
  import * as signals from '../signals'
6
5
  import type { CmsConfig, CmsManifest, ComponentInstance, InsertPosition } from '../types'
@@ -178,9 +177,6 @@ export function useBlockEditorHandlers({
178
177
 
179
178
  showToast(`${componentName} inserted ${position} component`, 'success')
180
179
  }
181
-
182
- // Trigger deployment polling after successful insert
183
- startDeploymentPolling(config)
184
180
  } catch (error) {
185
181
  console.error('[CMS] Failed to insert component:', error)
186
182
 
@@ -238,10 +234,7 @@ export function useBlockEditorHandlers({
238
234
 
239
235
  showToast(arrayMode ? 'Item removed' : 'Component removed', 'success')
240
236
 
241
- // Trigger deployment polling after successful remove
242
- startDeploymentPolling(config)
243
-
244
- // Visually collapse and hide the component until page refreshes after deploy
237
+ // Visually collapse and hide the component until HMR refreshes the page
245
238
  if (componentEl) {
246
239
  collapseElement(componentEl)
247
240
  }
@@ -29,9 +29,8 @@ export function useTooltipState(_options?: UseTooltipStateOptions) {
29
29
  */
30
30
  const showTooltipForElement = useCallback(() => {
31
31
  const currentEditingId = signals.currentEditingId.value
32
- const isProcessing = signals.isAIProcessing.value
33
32
 
34
- if (!currentEditingId || isProcessing) {
33
+ if (!currentEditingId) {
35
34
  setTooltipState({ elementId: null, rect: null, element: null })
36
35
  return
37
36
  }
@@ -27,15 +27,7 @@ import { Toolbar } from './components/toolbar'
27
27
  import { getConfig } from './config'
28
28
  import { Z_INDEX } from './constants'
29
29
  import { disableAllInteractiveElements, enableAllInteractiveElements, logDebug } from './dom'
30
- import {
31
- discardAllChanges,
32
- dismissDeploymentStatus,
33
- handleColorChange,
34
- saveAllChanges,
35
- startEditMode,
36
- stopEditMode,
37
- toggleShowOriginal,
38
- } from './editor'
30
+ import { discardAllChanges, handleColorChange, saveAllChanges, startEditMode, stopEditMode, toggleShowOriginal } from './editor'
39
31
  import { canRedo, canUndo, performRedo, performUndo } from './history'
40
32
  import {
41
33
  useBgImageHoverDetection,
@@ -244,8 +236,6 @@ const CmsUI = () => {
244
236
  seo: signals.dirtySeoChangesCount.value,
245
237
  total: signals.totalDirtyCount.value,
246
238
  },
247
- deploymentStatus: signals.deploymentStatus.value,
248
- lastDeployedAt: signals.lastDeployedAt.value,
249
239
  canUndo: canUndo.value,
250
240
  canRedo: canRedo.value,
251
241
  })
@@ -337,10 +327,6 @@ const CmsUI = () => {
337
327
  setMediaLibraryOpen(true)
338
328
  }, [])
339
329
 
340
- const handleDismissDeployment = useCallback(() => {
341
- dismissDeploymentStatus()
342
- }, [])
343
-
344
330
  const handleEditContent = useCallback(async () => {
345
331
  if (!await openMarkdownEditorForCurrentPage()) {
346
332
  signals.showToast('No collection content found on this page', 'error')
@@ -474,7 +460,6 @@ const CmsUI = () => {
474
460
 
475
461
  // Get reactive values from signals
476
462
  const isEditing = signals.isEditing.value
477
- const isAIProcessing = signals.isAIProcessing.value
478
463
  const blockEditorState = signals.blockEditorState.value
479
464
  const colorEditorState = signals.colorEditorState.value
480
465
  const manifest = signals.manifest.value
@@ -556,7 +541,6 @@ const CmsUI = () => {
556
541
  onDiscard: handleDiscard,
557
542
  onSelectElement: handleSelectElementToggle,
558
543
  onMediaLibrary: handleMediaLibrary,
559
- onDismissDeployment: handleDismissDeployment,
560
544
  onNavigateChange: () => {
561
545
  signals.navigateToNextChange()
562
546
  },
@@ -572,7 +556,7 @@ const CmsUI = () => {
572
556
 
573
557
  <ErrorBoundary componentName="Text Style Toolbar">
574
558
  <TextStyleToolbar
575
- visible={textSelectionState.hasSelection && isEditing && !isAIProcessing && isTextStylingAllowed}
559
+ visible={textSelectionState.hasSelection && isEditing && isTextStylingAllowed}
576
560
  rect={textSelectionState.rect}
577
561
  element={textSelectionState.element}
578
562
  onStyleChange={updateUI}