@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,357 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearAllHighlights,
|
|
3
|
+
clearElementHighlight,
|
|
4
|
+
destroyHighlightContainer,
|
|
5
|
+
initHighlightContainer,
|
|
6
|
+
setElementHighlight,
|
|
7
|
+
} from './components/highlight-overlay'
|
|
8
|
+
import { CSS } from './constants'
|
|
9
|
+
import type { ChildCmsElement } from './types'
|
|
10
|
+
|
|
11
|
+
/** Style element for contenteditable focus styles injected into the host page */
|
|
12
|
+
let focusStyleElement: HTMLStyleElement | null = null
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the best CMS element at a specific position using elementsFromPoint.
|
|
16
|
+
* This is more reliable than using event.target for nested elements.
|
|
17
|
+
*
|
|
18
|
+
* @param x - clientX position
|
|
19
|
+
* @param y - clientY position
|
|
20
|
+
* @param manifestEntries - Optional manifest entries to filter by
|
|
21
|
+
*/
|
|
22
|
+
export function getCmsElementAtPosition(
|
|
23
|
+
x: number,
|
|
24
|
+
y: number,
|
|
25
|
+
manifestEntries?: Record<string, any>,
|
|
26
|
+
): HTMLElement | null {
|
|
27
|
+
const elementsAtPoint = document.elementsFromPoint(x, y)
|
|
28
|
+
|
|
29
|
+
// First pass: find the deepest CMS element that is editable
|
|
30
|
+
// We prioritize elements that have contentEditable="true" and are in the manifest
|
|
31
|
+
for (const el of elementsAtPoint) {
|
|
32
|
+
if (!(el instanceof HTMLElement)) continue
|
|
33
|
+
if (!el.hasAttribute(CSS.ID_ATTRIBUTE)) continue
|
|
34
|
+
// Skip component roots - they should be handled separately
|
|
35
|
+
if (el.hasAttribute(CSS.COMPONENT_ID_ATTRIBUTE)) continue
|
|
36
|
+
|
|
37
|
+
const cmsId = el.getAttribute(CSS.ID_ATTRIBUTE)
|
|
38
|
+
|
|
39
|
+
// If we have manifest entries, only return elements that are in it
|
|
40
|
+
if (manifestEntries && cmsId && !manifestEntries[cmsId]) {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check if the element is actually editable
|
|
45
|
+
if (el.contentEditable === 'true') {
|
|
46
|
+
return el
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Second pass: find any CMS element (even if not editable yet)
|
|
51
|
+
// This handles the case where we're hovering before edit mode is fully set up
|
|
52
|
+
for (const el of elementsAtPoint) {
|
|
53
|
+
if (!(el instanceof HTMLElement)) continue
|
|
54
|
+
if (!el.hasAttribute(CSS.ID_ATTRIBUTE)) continue
|
|
55
|
+
if (el.hasAttribute(CSS.COMPONENT_ID_ATTRIBUTE)) continue
|
|
56
|
+
|
|
57
|
+
const cmsId = el.getAttribute(CSS.ID_ATTRIBUTE)
|
|
58
|
+
|
|
59
|
+
if (manifestEntries && cmsId && !manifestEntries[cmsId]) {
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return el
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get a component element at a specific position.
|
|
71
|
+
* Only returns component roots (elements with data-cms-component-id).
|
|
72
|
+
*/
|
|
73
|
+
export function getComponentAtPosition(x: number, y: number): HTMLElement | null {
|
|
74
|
+
const elementsAtPoint = document.elementsFromPoint(x, y)
|
|
75
|
+
|
|
76
|
+
for (const el of elementsAtPoint) {
|
|
77
|
+
if (!(el instanceof HTMLElement)) continue
|
|
78
|
+
if (el.hasAttribute(CSS.COMPONENT_ID_ATTRIBUTE)) {
|
|
79
|
+
return el
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if a point is near the edge of an element's bounding rect.
|
|
88
|
+
* Used to only trigger component selection when hovering near borders.
|
|
89
|
+
*/
|
|
90
|
+
export function isNearElementEdge(x: number, y: number, rect: DOMRect, threshold: number = 24): boolean {
|
|
91
|
+
// Check if point is within the rect at all
|
|
92
|
+
if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check if near any edge
|
|
97
|
+
const nearLeft = x - rect.left < threshold
|
|
98
|
+
const nearRight = rect.right - x < threshold
|
|
99
|
+
const nearTop = y - rect.top < threshold
|
|
100
|
+
const nearBottom = rect.bottom - y < threshold
|
|
101
|
+
|
|
102
|
+
return nearLeft || nearRight || nearTop || nearBottom
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getCmsElementFromEvent(ev: MouseEvent): HTMLElement | null {
|
|
106
|
+
const target = ev.target
|
|
107
|
+
if (!(target instanceof HTMLElement)) return null
|
|
108
|
+
const el = target.closest(`[${CSS.ID_ATTRIBUTE}]`)
|
|
109
|
+
if (!el || !(el instanceof HTMLElement)) return null
|
|
110
|
+
return el
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if an element is a CMS-styled span (inline text styling)
|
|
115
|
+
*/
|
|
116
|
+
export function isStyledSpan(element: HTMLElement): boolean {
|
|
117
|
+
return element.hasAttribute('data-cms-styled')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Helper function to recursively extract plain text from child nodes,
|
|
122
|
+
* replacing CMS elements with their placeholders.
|
|
123
|
+
* Note: This returns plain text only - for styled content, use innerHTML directly.
|
|
124
|
+
*/
|
|
125
|
+
function extractTextFromChildNodes(parentNode: HTMLElement): string {
|
|
126
|
+
let text = ''
|
|
127
|
+
|
|
128
|
+
parentNode.childNodes.forEach(node => {
|
|
129
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
130
|
+
text += node.nodeValue || ''
|
|
131
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
132
|
+
const element = node as HTMLElement
|
|
133
|
+
const tagName = element.tagName.toLowerCase()
|
|
134
|
+
|
|
135
|
+
// Preserve <br> tags as-is (textContent strips them)
|
|
136
|
+
if (tagName === 'br') {
|
|
137
|
+
text += '<br>'
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const directCmsId = element.getAttribute(CSS.ID_ATTRIBUTE)
|
|
142
|
+
|
|
143
|
+
if (directCmsId) {
|
|
144
|
+
// Element has CMS ID - replace with placeholder
|
|
145
|
+
text += `{{cms:${directCmsId}}}`
|
|
146
|
+
} else {
|
|
147
|
+
// For all other elements (including styled spans), just get their text content
|
|
148
|
+
text += element.textContent || ''
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return text
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Extract plain text content from a CMS element.
|
|
158
|
+
* Styled spans are reduced to their text content (not HTML).
|
|
159
|
+
* Nested CMS elements are replaced with {{cms:id}} placeholders.
|
|
160
|
+
*/
|
|
161
|
+
export function getEditableTextFromElement(el: HTMLElement): string {
|
|
162
|
+
return extractTextFromChildNodes(el).trim()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get the HTML content of a CMS element suitable for saving.
|
|
167
|
+
* Preserves styled spans but cleans up editing artifacts.
|
|
168
|
+
*/
|
|
169
|
+
export function getEditableHtmlFromElement(el: HTMLElement): string {
|
|
170
|
+
const clone = el.cloneNode(true) as HTMLElement
|
|
171
|
+
|
|
172
|
+
// Remove contenteditable attribute
|
|
173
|
+
clone.removeAttribute('contenteditable')
|
|
174
|
+
|
|
175
|
+
// Clean up any editing-only attributes but keep styled spans
|
|
176
|
+
clone.querySelectorAll('[contenteditable]').forEach(child => {
|
|
177
|
+
child.removeAttribute('contenteditable')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
return clone.innerHTML
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function getChildCmsElements(el: HTMLElement): ChildCmsElement[] {
|
|
184
|
+
return Array.from(el.querySelectorAll(`[${CSS.ID_ATTRIBUTE}]`)).map(child => ({
|
|
185
|
+
id: child.getAttribute(CSS.ID_ATTRIBUTE) || '',
|
|
186
|
+
placeholder: `__CMS_CHILD_${child.getAttribute(CSS.ID_ATTRIBUTE)}__`,
|
|
187
|
+
}))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function findInnermostCmsElement(target: EventTarget | null): HTMLElement | null {
|
|
191
|
+
if (!target || !(target instanceof HTMLElement)) return null
|
|
192
|
+
|
|
193
|
+
let element: HTMLElement | null = target
|
|
194
|
+
|
|
195
|
+
while (element && element !== document.body) {
|
|
196
|
+
if (element.hasAttribute(CSS.ID_ATTRIBUTE) && element.contentEditable === 'true') {
|
|
197
|
+
return element
|
|
198
|
+
}
|
|
199
|
+
element = element.parentElement
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return null
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function getAllCmsElements(): NodeListOf<HTMLElement> {
|
|
206
|
+
return document.querySelectorAll(`[${CSS.ID_ATTRIBUTE}]`)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function makeElementEditable(el: HTMLElement): void {
|
|
210
|
+
el.contentEditable = 'true'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function makeElementNonEditable(el: HTMLElement): void {
|
|
214
|
+
el.contentEditable = 'false'
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Set highlight outline on an element using Shadow DOM overlay.
|
|
219
|
+
* This doesn't modify the element's styles directly.
|
|
220
|
+
*/
|
|
221
|
+
export function setElementOutline(el: HTMLElement, color: string, style: 'solid' | 'dashed' = 'solid'): void {
|
|
222
|
+
setElementHighlight(el, color, style)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Clear highlight from an element
|
|
227
|
+
*/
|
|
228
|
+
export function clearElementOutline(el: HTMLElement): void {
|
|
229
|
+
clearElementHighlight(el)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Initialize the highlight system (call when starting edit mode)
|
|
234
|
+
*/
|
|
235
|
+
export function initHighlightSystem(): void {
|
|
236
|
+
initHighlightContainer()
|
|
237
|
+
injectFocusStyles()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Clean up all highlights (call when stopping edit mode)
|
|
242
|
+
*/
|
|
243
|
+
export function cleanupHighlightSystem(): void {
|
|
244
|
+
clearAllHighlights()
|
|
245
|
+
destroyHighlightContainer()
|
|
246
|
+
removeFocusStyles()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Inject styles into the host page to replace the browser's default
|
|
251
|
+
* blue focus outline on contenteditable CMS elements with a subtle,
|
|
252
|
+
* on-brand indicator.
|
|
253
|
+
*/
|
|
254
|
+
function injectFocusStyles(): void {
|
|
255
|
+
if (focusStyleElement) return
|
|
256
|
+
focusStyleElement = document.createElement('style')
|
|
257
|
+
focusStyleElement.id = 'cms-focus-styles'
|
|
258
|
+
focusStyleElement.textContent = `
|
|
259
|
+
[contenteditable="true"][data-cms-id]:focus {
|
|
260
|
+
outline: 2px solid rgba(26, 26, 26, 0.15);
|
|
261
|
+
outline-offset: 6px;
|
|
262
|
+
border-radius: 4px;
|
|
263
|
+
}
|
|
264
|
+
`
|
|
265
|
+
document.head.appendChild(focusStyleElement)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Remove injected focus styles from the host page.
|
|
270
|
+
*/
|
|
271
|
+
function removeFocusStyles(): void {
|
|
272
|
+
if (focusStyleElement) {
|
|
273
|
+
focusStyleElement.remove()
|
|
274
|
+
focusStyleElement = null
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function logDebug(debug: boolean, ...args: any[]): void {
|
|
279
|
+
if (!debug) return
|
|
280
|
+
console.debug('[CMS]', ...args)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Disable all interactive elements (links, buttons, forms) to prevent
|
|
285
|
+
* accidental navigation or form submission while in edit mode.
|
|
286
|
+
*/
|
|
287
|
+
export function disableAllInteractiveElements(): void {
|
|
288
|
+
// Disable links
|
|
289
|
+
const links = document.querySelectorAll('a')
|
|
290
|
+
links.forEach(link => {
|
|
291
|
+
link.setAttribute('data-cms-disabled', 'true')
|
|
292
|
+
link.addEventListener('click', preventInteraction, true)
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
// Disable buttons (submit, button, reset)
|
|
296
|
+
const buttons = document.querySelectorAll('button, input[type="submit"], input[type="button"], input[type="reset"]')
|
|
297
|
+
buttons.forEach(button => {
|
|
298
|
+
button.setAttribute('data-cms-disabled', 'true')
|
|
299
|
+
button.addEventListener('click', preventInteraction, true)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
// Disable form submissions
|
|
303
|
+
const forms = document.querySelectorAll('form')
|
|
304
|
+
forms.forEach(form => {
|
|
305
|
+
form.setAttribute('data-cms-disabled', 'true')
|
|
306
|
+
form.addEventListener('submit', preventInteraction, true)
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Re-enable all interactive elements that were disabled.
|
|
312
|
+
*/
|
|
313
|
+
export function enableAllInteractiveElements(): void {
|
|
314
|
+
// Re-enable links
|
|
315
|
+
const links = document.querySelectorAll('a[data-cms-disabled]')
|
|
316
|
+
links.forEach(link => {
|
|
317
|
+
link.removeAttribute('data-cms-disabled')
|
|
318
|
+
link.removeEventListener('click', preventInteraction, true)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
// Re-enable buttons
|
|
322
|
+
const buttons = document.querySelectorAll('button[data-cms-disabled], input[data-cms-disabled]')
|
|
323
|
+
buttons.forEach(button => {
|
|
324
|
+
button.removeAttribute('data-cms-disabled')
|
|
325
|
+
button.removeEventListener('click', preventInteraction, true)
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
// Re-enable forms
|
|
329
|
+
const forms = document.querySelectorAll('form[data-cms-disabled]')
|
|
330
|
+
forms.forEach(form => {
|
|
331
|
+
form.removeAttribute('data-cms-disabled')
|
|
332
|
+
form.removeEventListener('submit', preventInteraction, true)
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @deprecated Use disableAllInteractiveElements instead
|
|
338
|
+
*/
|
|
339
|
+
export function disableAllLinks(): void {
|
|
340
|
+
disableAllInteractiveElements()
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* @deprecated Use enableAllInteractiveElements instead
|
|
345
|
+
*/
|
|
346
|
+
export function enableAllLinks(): void {
|
|
347
|
+
enableAllInteractiveElements()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function preventInteraction(event: Event): void {
|
|
351
|
+
const target = event.currentTarget as HTMLElement
|
|
352
|
+
if (target.hasAttribute('data-cms-disabled')) {
|
|
353
|
+
event.preventDefault()
|
|
354
|
+
event.stopPropagation()
|
|
355
|
+
event.stopImmediatePropagation()
|
|
356
|
+
}
|
|
357
|
+
}
|