@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,101 @@
|
|
|
1
|
+
import type { RefObject } from 'preact'
|
|
2
|
+
import { useEffect, useRef } from 'preact/hooks'
|
|
3
|
+
import { CSS } from '../constants'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if a mouse event originates from CMS UI elements.
|
|
7
|
+
* This includes the toolbar, AI chat, color picker, and other CMS overlays.
|
|
8
|
+
*/
|
|
9
|
+
export function isEventOnCmsUI(ev: MouseEvent): boolean {
|
|
10
|
+
const path = ev.composedPath()
|
|
11
|
+
const cmsOverlay = document.querySelector(CSS.HIGHLIGHT_ELEMENT)
|
|
12
|
+
|
|
13
|
+
for (const el of path) {
|
|
14
|
+
if (el === cmsOverlay) return true
|
|
15
|
+
if (el instanceof HTMLElement) {
|
|
16
|
+
// Check for CMS custom elements
|
|
17
|
+
if (el.tagName?.startsWith('CMS-')) return true
|
|
18
|
+
// Check for toolbar and AI chat by data attribute
|
|
19
|
+
if (el.hasAttribute?.(CSS.UI_ATTRIBUTE)) return true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check if a target element is part of CMS UI.
|
|
27
|
+
* Non-event based variant for direct element checks.
|
|
28
|
+
*/
|
|
29
|
+
export function isElementInCmsUI(target: HTMLElement | null): boolean {
|
|
30
|
+
if (!target) return false
|
|
31
|
+
return target.hasAttribute?.(CSS.UI_ATTRIBUTE) || !!target.closest?.(`[${CSS.UI_ATTRIBUTE}]`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hook for tracking an element's position on scroll and resize.
|
|
36
|
+
* Returns a callback to get the latest rect, and automatically updates
|
|
37
|
+
* when the element scrolls or the window resizes.
|
|
38
|
+
*
|
|
39
|
+
* @param element - The element to track
|
|
40
|
+
* @param onPositionChange - Callback when position changes
|
|
41
|
+
* @param enabled - Whether tracking is enabled
|
|
42
|
+
*/
|
|
43
|
+
export function usePositionTracking(
|
|
44
|
+
element: HTMLElement | null,
|
|
45
|
+
onPositionChange: (rect: DOMRect | null) => void,
|
|
46
|
+
enabled: boolean = true,
|
|
47
|
+
): void {
|
|
48
|
+
const elementRef = useRef(element)
|
|
49
|
+
elementRef.current = element
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!enabled || !element) return
|
|
53
|
+
|
|
54
|
+
const updatePosition = () => {
|
|
55
|
+
if (elementRef.current && document.contains(elementRef.current)) {
|
|
56
|
+
onPositionChange(elementRef.current.getBoundingClientRect())
|
|
57
|
+
} else {
|
|
58
|
+
onPositionChange(null)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
window.addEventListener('scroll', updatePosition, true)
|
|
63
|
+
window.addEventListener('resize', updatePosition)
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
window.removeEventListener('scroll', updatePosition, true)
|
|
67
|
+
window.removeEventListener('resize', updatePosition)
|
|
68
|
+
}
|
|
69
|
+
}, [element, enabled, onPositionChange])
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Hook for throttling a callback function.
|
|
74
|
+
* Returns a throttled version that will only execute once per interval.
|
|
75
|
+
*/
|
|
76
|
+
export function useThrottle<T extends (...args: unknown[]) => void>(
|
|
77
|
+
callback: T,
|
|
78
|
+
intervalMs: number,
|
|
79
|
+
): T {
|
|
80
|
+
const lastCallTime = useRef<number>(0)
|
|
81
|
+
const callbackRef = useRef(callback)
|
|
82
|
+
callbackRef.current = callback
|
|
83
|
+
|
|
84
|
+
return ((...args: Parameters<T>) => {
|
|
85
|
+
const now = Date.now()
|
|
86
|
+
if (now - lastCallTime.current >= intervalMs) {
|
|
87
|
+
lastCallTime.current = now
|
|
88
|
+
callbackRef.current(...args)
|
|
89
|
+
}
|
|
90
|
+
}) as T
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Creates a ref that always contains the latest value.
|
|
95
|
+
* Useful for accessing current values in event handlers without stale closures.
|
|
96
|
+
*/
|
|
97
|
+
export function useLatestRef<T>(value: T): RefObject<T> {
|
|
98
|
+
const ref = useRef(value)
|
|
99
|
+
ref.current = value
|
|
100
|
+
return ref
|
|
101
|
+
}
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { render } from 'preact'
|
|
2
|
+
import { useCallback, useEffect } from 'preact/hooks'
|
|
3
|
+
import { fetchManifest } from './api'
|
|
4
|
+
import { AIChat } from './components/ai-chat'
|
|
5
|
+
import { AITooltip } from './components/ai-tooltip'
|
|
6
|
+
import { AttributeEditor } from './components/attribute-editor'
|
|
7
|
+
import { BlockEditor } from './components/block-editor'
|
|
8
|
+
import { CollectionsBrowser } from './components/collections-browser'
|
|
9
|
+
import { ColorToolbar } from './components/color-toolbar'
|
|
10
|
+
import { ConfirmDialog } from './components/confirm-dialog'
|
|
11
|
+
import { CreatePageModal } from './components/create-page-modal'
|
|
12
|
+
import { EditableHighlights } from './components/editable-highlights'
|
|
13
|
+
import { ErrorBoundary } from './components/error-boundary'
|
|
14
|
+
import { ImageOverlay } from './components/image-overlay'
|
|
15
|
+
import { MarkdownEditorOverlay } from './components/markdown-editor-overlay'
|
|
16
|
+
import { MediaLibrary } from './components/media-library'
|
|
17
|
+
import { Outline } from './components/outline'
|
|
18
|
+
import { RedirectCountdown } from './components/redirect-countdown'
|
|
19
|
+
import { SeoEditor } from './components/seo-editor'
|
|
20
|
+
import { TextStyleToolbar } from './components/text-style-toolbar'
|
|
21
|
+
import { ToastContainer } from './components/toast/toast-container'
|
|
22
|
+
import { Toolbar } from './components/toolbar'
|
|
23
|
+
import { getConfig } from './config'
|
|
24
|
+
import { logDebug } from './dom'
|
|
25
|
+
import {
|
|
26
|
+
discardAllChanges,
|
|
27
|
+
dismissDeploymentStatus,
|
|
28
|
+
handleColorChange,
|
|
29
|
+
saveAllChanges,
|
|
30
|
+
startEditMode,
|
|
31
|
+
stopEditMode,
|
|
32
|
+
toggleShowOriginal,
|
|
33
|
+
} from './editor'
|
|
34
|
+
import { performRedo, performUndo } from './history'
|
|
35
|
+
import CMS_STYLES from './styles.css?inline'
|
|
36
|
+
import {
|
|
37
|
+
useAIHandlers,
|
|
38
|
+
useBlockEditorHandlers,
|
|
39
|
+
useComponentClickHandler,
|
|
40
|
+
useElementDetection,
|
|
41
|
+
useImageHoverDetection,
|
|
42
|
+
useTextSelection,
|
|
43
|
+
useTooltipState,
|
|
44
|
+
} from './hooks'
|
|
45
|
+
import {
|
|
46
|
+
openCollectionsBrowser,
|
|
47
|
+
openMarkdownEditorForCurrentPage,
|
|
48
|
+
openSeoEditor,
|
|
49
|
+
selectBrowserCollection,
|
|
50
|
+
setMediaLibraryOpen,
|
|
51
|
+
toggleShowEditableHighlights,
|
|
52
|
+
updateSettings,
|
|
53
|
+
} from './signals'
|
|
54
|
+
import * as signals from './signals'
|
|
55
|
+
import { hasPendingEntryNavigation, loadSettingsFromStorage, saveSettingsToStorage } from './storage'
|
|
56
|
+
import { generateCSSVariables, resolveTheme } from './themes'
|
|
57
|
+
|
|
58
|
+
const CmsUI = () => {
|
|
59
|
+
const config = signals.config.value
|
|
60
|
+
const outlineState = useElementDetection()
|
|
61
|
+
const imageHoverState = useImageHoverDetection()
|
|
62
|
+
const textSelectionState = useTextSelection()
|
|
63
|
+
const { tooltipState, showTooltipForElement, hideTooltip } = useTooltipState()
|
|
64
|
+
const updateUI = useCallback(() => {
|
|
65
|
+
showTooltipForElement()
|
|
66
|
+
}, [showTooltipForElement])
|
|
67
|
+
|
|
68
|
+
// Load settings from localStorage on mount
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const savedSettings = loadSettingsFromStorage()
|
|
71
|
+
if (savedSettings) {
|
|
72
|
+
updateSettings(savedSettings)
|
|
73
|
+
}
|
|
74
|
+
}, [])
|
|
75
|
+
|
|
76
|
+
// Fetch manifest on mount so toolbar has collection/SEO data before edit mode
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
fetchManifest().then((manifest) => {
|
|
79
|
+
signals.setManifest(manifest)
|
|
80
|
+
}).catch(() => {})
|
|
81
|
+
}, [])
|
|
82
|
+
|
|
83
|
+
// Auto-open markdown editor when there's a pending entry navigation from collections browser
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (hasPendingEntryNavigation()) {
|
|
86
|
+
openMarkdownEditorForCurrentPage()
|
|
87
|
+
}
|
|
88
|
+
}, [])
|
|
89
|
+
|
|
90
|
+
const {
|
|
91
|
+
handleAIChatToggle,
|
|
92
|
+
handleChatClose,
|
|
93
|
+
handleChatCancel,
|
|
94
|
+
handleTooltipPromptSubmit,
|
|
95
|
+
handleChatSend,
|
|
96
|
+
handleApplyToElement,
|
|
97
|
+
} = useAIHandlers({
|
|
98
|
+
config,
|
|
99
|
+
showToast: signals.showToast,
|
|
100
|
+
onTooltipHide: hideTooltip,
|
|
101
|
+
onUIUpdate: updateUI,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const {
|
|
105
|
+
blockEditorRect,
|
|
106
|
+
handleComponentSelect,
|
|
107
|
+
handleBlockEditorClose,
|
|
108
|
+
handleUpdateProps,
|
|
109
|
+
handleInsertComponent,
|
|
110
|
+
handleRemoveBlock,
|
|
111
|
+
} = useBlockEditorHandlers({
|
|
112
|
+
config,
|
|
113
|
+
showToast: signals.showToast,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
useComponentClickHandler({ onComponentSelect: handleComponentSelect })
|
|
117
|
+
|
|
118
|
+
// Editor control handlers
|
|
119
|
+
const handleEditToggle = useCallback(async () => {
|
|
120
|
+
if (signals.isEditing.value) {
|
|
121
|
+
hideTooltip()
|
|
122
|
+
stopEditMode(updateUI)
|
|
123
|
+
} else {
|
|
124
|
+
await startEditMode(config, updateUI)
|
|
125
|
+
}
|
|
126
|
+
}, [config, updateUI, hideTooltip])
|
|
127
|
+
|
|
128
|
+
const handleCompare = useCallback(() => {
|
|
129
|
+
toggleShowOriginal(config, updateUI)
|
|
130
|
+
}, [config, updateUI])
|
|
131
|
+
|
|
132
|
+
const handleSave = useCallback(async () => {
|
|
133
|
+
try {
|
|
134
|
+
const result = await saveAllChanges(config, updateUI)
|
|
135
|
+
if (result.success) {
|
|
136
|
+
signals.showToast(`Saved ${result.updated} change(s) successfully!`, 'success')
|
|
137
|
+
} else if (result.errors) {
|
|
138
|
+
signals.showToast(`Saved ${result.updated}, ${result.errors.length} failed`, 'error')
|
|
139
|
+
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
signals.showToast('Save failed – see console', 'error')
|
|
142
|
+
}
|
|
143
|
+
}, [config, updateUI])
|
|
144
|
+
|
|
145
|
+
const handleDiscard = useCallback(() => {
|
|
146
|
+
discardAllChanges(updateUI)
|
|
147
|
+
signals.showToast('All changes discarded', 'info')
|
|
148
|
+
}, [updateUI])
|
|
149
|
+
|
|
150
|
+
const handleOpenCollection = useCallback((name: string) => {
|
|
151
|
+
openCollectionsBrowser()
|
|
152
|
+
selectBrowserCollection(name)
|
|
153
|
+
}, [])
|
|
154
|
+
|
|
155
|
+
const handleMediaLibrary = useCallback(() => {
|
|
156
|
+
setMediaLibraryOpen(true)
|
|
157
|
+
}, [])
|
|
158
|
+
|
|
159
|
+
const handleDismissDeployment = useCallback(() => {
|
|
160
|
+
dismissDeploymentStatus()
|
|
161
|
+
}, [])
|
|
162
|
+
|
|
163
|
+
const handleEditContent = useCallback(async () => {
|
|
164
|
+
if (!await openMarkdownEditorForCurrentPage()) {
|
|
165
|
+
signals.showToast('No collection content found on this page', 'error')
|
|
166
|
+
}
|
|
167
|
+
}, [])
|
|
168
|
+
|
|
169
|
+
const handleToggleHighlights = useCallback(() => {
|
|
170
|
+
toggleShowEditableHighlights()
|
|
171
|
+
// Save settings to localStorage
|
|
172
|
+
const newSettings = signals.settings.value
|
|
173
|
+
saveSettingsToStorage(newSettings)
|
|
174
|
+
}, [])
|
|
175
|
+
|
|
176
|
+
const handleSeoEditor = useCallback(() => {
|
|
177
|
+
openSeoEditor()
|
|
178
|
+
}, [])
|
|
179
|
+
|
|
180
|
+
// Color toolbar handlers
|
|
181
|
+
const handleColorToolbarChange = useCallback((colorType: 'bg' | 'text' | 'border' | 'hoverBg' | 'hoverText', oldClass: string, newClass: string, previousClassName: string, previousStyleCssText: string) => {
|
|
182
|
+
const targetId = signals.colorEditorState.value.targetElementId
|
|
183
|
+
if (!targetId) return
|
|
184
|
+
|
|
185
|
+
handleColorChange(config, targetId, colorType, oldClass, newClass, updateUI, previousClassName, previousStyleCssText)
|
|
186
|
+
}, [config, updateUI])
|
|
187
|
+
|
|
188
|
+
const handleColorToolbarClose = useCallback(() => {
|
|
189
|
+
signals.closeColorEditor()
|
|
190
|
+
}, [])
|
|
191
|
+
|
|
192
|
+
// Handle color swatch click from outline
|
|
193
|
+
const handleOutlineColorClick = useCallback((cmsId: string, rect: DOMRect) => {
|
|
194
|
+
signals.setHoveringSwatches(false)
|
|
195
|
+
signals.openColorEditor(cmsId, rect)
|
|
196
|
+
}, [])
|
|
197
|
+
|
|
198
|
+
// Handle attribute button click from outline
|
|
199
|
+
const handleOutlineAttributeClick = useCallback((cmsId: string, rect: DOMRect) => {
|
|
200
|
+
signals.openAttributeEditor(cmsId, rect)
|
|
201
|
+
}, [])
|
|
202
|
+
|
|
203
|
+
// Handle attribute editor close
|
|
204
|
+
const handleAttributeEditorClose = useCallback(() => {
|
|
205
|
+
signals.closeAttributeEditor()
|
|
206
|
+
}, [])
|
|
207
|
+
|
|
208
|
+
// Get reactive values from signals
|
|
209
|
+
const isEditing = signals.isEditing.value
|
|
210
|
+
const isAIProcessing = signals.isAIProcessing.value
|
|
211
|
+
const isChatOpen = signals.isChatOpen.value
|
|
212
|
+
const blockEditorState = signals.blockEditorState.value
|
|
213
|
+
const colorEditorState = signals.colorEditorState.value
|
|
214
|
+
const manifest = signals.manifest.value
|
|
215
|
+
const toasts = signals.toasts.value
|
|
216
|
+
const collectionDefinitions = manifest.collectionDefinitions ?? {}
|
|
217
|
+
const showEditableHighlights = signals.showEditableHighlights.value
|
|
218
|
+
const hasSeoData = !!(manifest as any).seo
|
|
219
|
+
|
|
220
|
+
// Get color toolbar data
|
|
221
|
+
const colorEditorElement = colorEditorState.targetElementId
|
|
222
|
+
? signals.pendingColorChanges.value.get(colorEditorState.targetElementId)?.element ?? null
|
|
223
|
+
: null
|
|
224
|
+
const colorEditorCurrentClasses = colorEditorState.targetElementId
|
|
225
|
+
? signals.pendingColorChanges.value.get(colorEditorState.targetElementId)?.newClasses
|
|
226
|
+
: undefined
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<>
|
|
230
|
+
<ErrorBoundary componentName="Editable Highlights">
|
|
231
|
+
<EditableHighlights visible={showEditableHighlights && isEditing} />
|
|
232
|
+
</ErrorBoundary>
|
|
233
|
+
|
|
234
|
+
<ErrorBoundary componentName="Outline">
|
|
235
|
+
<Outline
|
|
236
|
+
visible={outlineState.visible}
|
|
237
|
+
rect={outlineState.rect}
|
|
238
|
+
isComponent={outlineState.isComponent}
|
|
239
|
+
componentName={outlineState.componentName}
|
|
240
|
+
tagName={outlineState.tagName}
|
|
241
|
+
element={outlineState.element}
|
|
242
|
+
cmsId={outlineState.cmsId}
|
|
243
|
+
onColorClick={handleOutlineColorClick}
|
|
244
|
+
onAttributeClick={handleOutlineAttributeClick}
|
|
245
|
+
/>
|
|
246
|
+
</ErrorBoundary>
|
|
247
|
+
|
|
248
|
+
<ErrorBoundary componentName="ImageOverlay">
|
|
249
|
+
<ImageOverlay
|
|
250
|
+
visible={imageHoverState.visible && isEditing}
|
|
251
|
+
rect={imageHoverState.rect}
|
|
252
|
+
element={imageHoverState.element}
|
|
253
|
+
cmsId={imageHoverState.cmsId}
|
|
254
|
+
/>
|
|
255
|
+
</ErrorBoundary>
|
|
256
|
+
|
|
257
|
+
<ErrorBoundary componentName="Toolbar">
|
|
258
|
+
<Toolbar
|
|
259
|
+
callbacks={{
|
|
260
|
+
onEdit: handleEditToggle,
|
|
261
|
+
onCompare: handleCompare,
|
|
262
|
+
onSave: handleSave,
|
|
263
|
+
onDiscard: handleDiscard,
|
|
264
|
+
onAIChat: handleAIChatToggle,
|
|
265
|
+
onMediaLibrary: handleMediaLibrary,
|
|
266
|
+
onDismissDeployment: handleDismissDeployment,
|
|
267
|
+
onNavigateChange: () => {
|
|
268
|
+
signals.navigateToNextChange()
|
|
269
|
+
},
|
|
270
|
+
onEditContent: handleEditContent,
|
|
271
|
+
onToggleHighlights: handleToggleHighlights,
|
|
272
|
+
onSeoEditor: hasSeoData ? handleSeoEditor : undefined,
|
|
273
|
+
onOpenCollection: handleOpenCollection,
|
|
274
|
+
}}
|
|
275
|
+
collectionDefinitions={Object.keys(collectionDefinitions).length > 0 ? collectionDefinitions : undefined}
|
|
276
|
+
/>
|
|
277
|
+
</ErrorBoundary>
|
|
278
|
+
|
|
279
|
+
<ErrorBoundary componentName="AI Tooltip">
|
|
280
|
+
<AITooltip
|
|
281
|
+
callbacks={{
|
|
282
|
+
onPromptSubmit: handleTooltipPromptSubmit,
|
|
283
|
+
}}
|
|
284
|
+
visible={!!tooltipState.elementId && isEditing && !isAIProcessing && !textSelectionState.hasSelection}
|
|
285
|
+
elementId={tooltipState.elementId}
|
|
286
|
+
rect={tooltipState.rect}
|
|
287
|
+
processing={isAIProcessing}
|
|
288
|
+
/>
|
|
289
|
+
</ErrorBoundary>
|
|
290
|
+
|
|
291
|
+
<ErrorBoundary componentName="Text Style Toolbar">
|
|
292
|
+
<TextStyleToolbar
|
|
293
|
+
visible={textSelectionState.hasSelection && isEditing && !isAIProcessing}
|
|
294
|
+
rect={textSelectionState.rect}
|
|
295
|
+
element={textSelectionState.element}
|
|
296
|
+
onStyleChange={updateUI}
|
|
297
|
+
/>
|
|
298
|
+
</ErrorBoundary>
|
|
299
|
+
|
|
300
|
+
<ErrorBoundary componentName="Color Toolbar">
|
|
301
|
+
<ColorToolbar
|
|
302
|
+
visible={colorEditorState.isOpen && isEditing}
|
|
303
|
+
rect={colorEditorState.targetRect}
|
|
304
|
+
element={colorEditorElement}
|
|
305
|
+
availableColors={manifest.availableColors}
|
|
306
|
+
currentClasses={colorEditorCurrentClasses}
|
|
307
|
+
onColorChange={handleColorToolbarChange}
|
|
308
|
+
onClose={handleColorToolbarClose}
|
|
309
|
+
/>
|
|
310
|
+
</ErrorBoundary>
|
|
311
|
+
|
|
312
|
+
<ErrorBoundary componentName="Attribute Editor">
|
|
313
|
+
<AttributeEditor
|
|
314
|
+
onClose={handleAttributeEditorClose}
|
|
315
|
+
/>
|
|
316
|
+
</ErrorBoundary>
|
|
317
|
+
|
|
318
|
+
<ErrorBoundary componentName="AI Chat">
|
|
319
|
+
<AIChat
|
|
320
|
+
callbacks={{
|
|
321
|
+
onSend: handleChatSend,
|
|
322
|
+
onClose: handleChatClose,
|
|
323
|
+
onCancel: handleChatCancel,
|
|
324
|
+
onApplyToElement: handleApplyToElement,
|
|
325
|
+
}}
|
|
326
|
+
/>
|
|
327
|
+
</ErrorBoundary>
|
|
328
|
+
|
|
329
|
+
<ErrorBoundary componentName="Block Editor">
|
|
330
|
+
<BlockEditor
|
|
331
|
+
visible={blockEditorState.isOpen && isEditing}
|
|
332
|
+
componentId={blockEditorState.currentComponentId}
|
|
333
|
+
rect={blockEditorRect}
|
|
334
|
+
onClose={handleBlockEditorClose}
|
|
335
|
+
onUpdateProps={handleUpdateProps}
|
|
336
|
+
onInsertComponent={handleInsertComponent}
|
|
337
|
+
onRemoveBlock={handleRemoveBlock}
|
|
338
|
+
/>
|
|
339
|
+
</ErrorBoundary>
|
|
340
|
+
|
|
341
|
+
<ErrorBoundary componentName="SEO Editor">
|
|
342
|
+
<SeoEditor />
|
|
343
|
+
</ErrorBoundary>
|
|
344
|
+
|
|
345
|
+
<ErrorBoundary componentName="Collections Browser">
|
|
346
|
+
<CollectionsBrowser />
|
|
347
|
+
</ErrorBoundary>
|
|
348
|
+
|
|
349
|
+
<ErrorBoundary componentName="Create Page Modal">
|
|
350
|
+
<CreatePageModal />
|
|
351
|
+
</ErrorBoundary>
|
|
352
|
+
|
|
353
|
+
<ErrorBoundary componentName="Markdown Editor">
|
|
354
|
+
<MarkdownEditorOverlay />
|
|
355
|
+
</ErrorBoundary>
|
|
356
|
+
|
|
357
|
+
<ErrorBoundary componentName="Media Library">
|
|
358
|
+
<MediaLibrary />
|
|
359
|
+
</ErrorBoundary>
|
|
360
|
+
|
|
361
|
+
<ErrorBoundary componentName="Confirm Dialog">
|
|
362
|
+
<ConfirmDialog />
|
|
363
|
+
</ErrorBoundary>
|
|
364
|
+
|
|
365
|
+
<RedirectCountdown />
|
|
366
|
+
<ToastContainer toasts={toasts} onRemove={signals.removeToast} />
|
|
367
|
+
</>
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
class CmsEditor {
|
|
372
|
+
private appRoot: HTMLElement | null = null
|
|
373
|
+
private shadowRoot: ShadowRoot | null = null
|
|
374
|
+
private config = getConfig()
|
|
375
|
+
|
|
376
|
+
async init(): Promise<void> {
|
|
377
|
+
signals.setConfig(this.config)
|
|
378
|
+
|
|
379
|
+
logDebug(this.config.debug, 'Initializing CMS editor with config:', this.config)
|
|
380
|
+
|
|
381
|
+
this.setupUI()
|
|
382
|
+
this.setupKeyboardShortcuts()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private setupUI(): void {
|
|
386
|
+
const hostElement = document.createElement('div')
|
|
387
|
+
hostElement.id = 'cms-app-host'
|
|
388
|
+
hostElement.style.cssText = 'position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483647;'
|
|
389
|
+
document.body.appendChild(hostElement)
|
|
390
|
+
|
|
391
|
+
// Create shadow DOM with closed mode for better isolation
|
|
392
|
+
this.shadowRoot = hostElement.attachShadow({ mode: 'open' })
|
|
393
|
+
|
|
394
|
+
// Inject theme CSS variables BEFORE main styles
|
|
395
|
+
this.injectThemeStyles()
|
|
396
|
+
|
|
397
|
+
// Inject Tailwind CSS styles into shadow DOM
|
|
398
|
+
const styleElement = document.createElement('style')
|
|
399
|
+
styleElement.textContent = CMS_STYLES
|
|
400
|
+
this.shadowRoot.appendChild(styleElement)
|
|
401
|
+
|
|
402
|
+
// Create the app root container
|
|
403
|
+
this.appRoot = document.createElement('div')
|
|
404
|
+
this.appRoot.id = 'cms-app-root'
|
|
405
|
+
this.appRoot.className = 'cms-root'
|
|
406
|
+
this.shadowRoot.appendChild(this.appRoot)
|
|
407
|
+
|
|
408
|
+
// Render Preact app into the shadow DOM
|
|
409
|
+
render(<CmsUI />, this.appRoot)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private injectThemeStyles(): void {
|
|
413
|
+
if (!this.shadowRoot) return
|
|
414
|
+
|
|
415
|
+
const theme = resolveTheme(this.config)
|
|
416
|
+
const cssVars = generateCSSVariables(theme)
|
|
417
|
+
|
|
418
|
+
const styleEl = document.createElement('style')
|
|
419
|
+
styleEl.id = 'cms-theme-vars'
|
|
420
|
+
styleEl.textContent = `:host { ${cssVars} }`
|
|
421
|
+
this.shadowRoot.insertBefore(styleEl, this.shadowRoot.firstChild)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private setupKeyboardShortcuts(): void {
|
|
425
|
+
document.addEventListener('keydown', (ev) => {
|
|
426
|
+
// Cmd+Shift+E: Toggle CMS visibility
|
|
427
|
+
const combo = (ev.metaKey || ev.ctrlKey) && ev.shiftKey && ev.key.toLowerCase() === 'e'
|
|
428
|
+
if (combo) {
|
|
429
|
+
ev.preventDefault()
|
|
430
|
+
this.toggleVisibility()
|
|
431
|
+
return
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Undo/Redo only when editing
|
|
435
|
+
if (!signals.isEditing.value) return
|
|
436
|
+
if (!(ev.metaKey || ev.ctrlKey)) return
|
|
437
|
+
|
|
438
|
+
const key = ev.key.toLowerCase()
|
|
439
|
+
|
|
440
|
+
// Cmd+Shift+Z: Redo
|
|
441
|
+
if (key === 'z' && ev.shiftKey) {
|
|
442
|
+
ev.preventDefault()
|
|
443
|
+
performRedo()
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Cmd+Z: Undo
|
|
448
|
+
if (key === 'z' && !ev.shiftKey) {
|
|
449
|
+
ev.preventDefault()
|
|
450
|
+
performUndo()
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private toggleVisibility(): void {
|
|
457
|
+
if (!this.appRoot) return
|
|
458
|
+
|
|
459
|
+
const isHidden = this.appRoot.style.display === 'none'
|
|
460
|
+
this.appRoot.style.display = isHidden ? 'block' : 'none'
|
|
461
|
+
|
|
462
|
+
// Use signals.showToast for consistency
|
|
463
|
+
signals.showToast(isHidden ? 'CMS editing enabled' : 'CMS editing disabled', 'info')
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Auto-initialize when DOM is ready
|
|
468
|
+
if (typeof window !== 'undefined') {
|
|
469
|
+
const initEditor = () => {
|
|
470
|
+
const editor = new CmsEditor()
|
|
471
|
+
editor.init()
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (document.readyState === 'loading') {
|
|
475
|
+
document.addEventListener('DOMContentLoaded', initEditor)
|
|
476
|
+
} else {
|
|
477
|
+
initEditor()
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export { CmsEditor }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CmsManifest, ComponentDefinition, ComponentInstance, ManifestEntry } from './types'
|
|
2
|
+
|
|
3
|
+
type GetManifestEntry = (manifest: CmsManifest, id: string) => ManifestEntry | undefined
|
|
4
|
+
export const getManifestEntry: GetManifestEntry = (manifest, id) => manifest.entries[id]
|
|
5
|
+
|
|
6
|
+
type HasManifestEntry = (manifest: CmsManifest, id: string) => boolean
|
|
7
|
+
export const hasManifestEntry: HasManifestEntry = (manifest, id) => id in manifest.entries
|
|
8
|
+
|
|
9
|
+
type GetComponentInstances = (manifest: CmsManifest) => Record<string, ComponentInstance>
|
|
10
|
+
export const getComponentInstances: GetComponentInstances = manifest => manifest.components ?? {}
|
|
11
|
+
|
|
12
|
+
type GetComponentInstance = (manifest: CmsManifest, id: string) => ComponentInstance | undefined
|
|
13
|
+
export const getComponentInstance: GetComponentInstance = (manifest, id) => manifest.components?.[id]
|
|
14
|
+
|
|
15
|
+
type GetComponentDefinitions = (manifest: CmsManifest) => Record<string, ComponentDefinition>
|
|
16
|
+
export const getComponentDefinitions: GetComponentDefinitions = manifest => manifest.componentDefinitions ?? {}
|
|
17
|
+
|
|
18
|
+
type GetComponentDefinition = (manifest: CmsManifest, name: string) => ComponentDefinition | undefined
|
|
19
|
+
export const getComponentDefinition: GetComponentDefinition = (manifest, name) => getComponentDefinitions(manifest)[name]
|
|
20
|
+
|
|
21
|
+
type GetManifestEntryCount = (manifest: CmsManifest) => number
|
|
22
|
+
export const getManifestEntryCount: GetManifestEntryCount = (manifest: CmsManifest) => Object.keys(manifest.entries).length
|
|
23
|
+
|
|
24
|
+
type GetAvailableComponentNames = (manifest: CmsManifest) => string[]
|
|
25
|
+
export const getAvailableComponentNames: GetAvailableComponentNames = manifest => Object.keys(getComponentDefinitions(manifest))
|