@nuasite/cms 0.3.0 → 0.5.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/dist/editor.js +35991 -0
- package/package.json +12 -11
- package/src/dev-middleware.ts +82 -2
- package/src/editor/components/block-editor.tsx +48 -57
- package/src/editor/components/outline.tsx +42 -35
- package/src/editor/components/toolbar.tsx +7 -4
- package/src/editor/hooks/useBlockEditorHandlers.ts +5 -5
- package/src/editor/hooks/useElementDetection.ts +2 -2
- package/src/editor/index.tsx +3 -2
- package/src/handlers/array-ops.ts +4 -4
- package/src/index.ts +54 -28
- package/dist/src/build-processor.d.ts +0 -20
- package/dist/src/build-processor.d.ts.map +0 -1
- package/dist/src/collection-scanner.d.ts +0 -6
- package/dist/src/collection-scanner.d.ts.map +0 -1
- package/dist/src/component-registry.d.ts +0 -67
- package/dist/src/component-registry.d.ts.map +0 -1
- package/dist/src/config.d.ts +0 -24
- package/dist/src/config.d.ts.map +0 -1
- package/dist/src/dev-middleware.d.ts +0 -20
- package/dist/src/dev-middleware.d.ts.map +0 -1
- package/dist/src/editor/ai.d.ts +0 -60
- package/dist/src/editor/ai.d.ts.map +0 -1
- package/dist/src/editor/api.d.ts +0 -154
- package/dist/src/editor/api.d.ts.map +0 -1
- package/dist/src/editor/color-utils.d.ts +0 -106
- package/dist/src/editor/color-utils.d.ts.map +0 -1
- package/dist/src/editor/components/ai-chat.d.ts +0 -11
- package/dist/src/editor/components/ai-chat.d.ts.map +0 -1
- package/dist/src/editor/components/ai-tooltip.d.ts +0 -12
- package/dist/src/editor/components/ai-tooltip.d.ts.map +0 -1
- package/dist/src/editor/components/attribute-editor.d.ts +0 -5
- package/dist/src/editor/components/attribute-editor.d.ts.map +0 -1
- package/dist/src/editor/components/block-editor.d.ts +0 -12
- package/dist/src/editor/components/block-editor.d.ts.map +0 -1
- package/dist/src/editor/components/collections-browser.d.ts +0 -2
- package/dist/src/editor/components/collections-browser.d.ts.map +0 -1
- package/dist/src/editor/components/color-toolbar.d.ts +0 -12
- package/dist/src/editor/components/color-toolbar.d.ts.map +0 -1
- package/dist/src/editor/components/confirm-dialog.d.ts +0 -2
- package/dist/src/editor/components/confirm-dialog.d.ts.map +0 -1
- package/dist/src/editor/components/create-page-modal.d.ts +0 -2
- package/dist/src/editor/components/create-page-modal.d.ts.map +0 -1
- package/dist/src/editor/components/editable-highlights.d.ts +0 -9
- package/dist/src/editor/components/editable-highlights.d.ts.map +0 -1
- package/dist/src/editor/components/error-boundary.d.ts +0 -32
- package/dist/src/editor/components/error-boundary.d.ts.map +0 -1
- package/dist/src/editor/components/fields.d.ts +0 -75
- package/dist/src/editor/components/fields.d.ts.map +0 -1
- package/dist/src/editor/components/frontmatter-fields.d.ts +0 -29
- package/dist/src/editor/components/frontmatter-fields.d.ts.map +0 -1
- package/dist/src/editor/components/highlight-overlay.d.ts +0 -64
- package/dist/src/editor/components/highlight-overlay.d.ts.map +0 -1
- package/dist/src/editor/components/image-overlay.d.ts +0 -12
- package/dist/src/editor/components/image-overlay.d.ts.map +0 -1
- package/dist/src/editor/components/markdown-editor-overlay.d.ts +0 -6
- package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +0 -1
- package/dist/src/editor/components/markdown-inline-editor.d.ts +0 -10
- package/dist/src/editor/components/markdown-inline-editor.d.ts.map +0 -1
- package/dist/src/editor/components/media-library.d.ts +0 -2
- package/dist/src/editor/components/media-library.d.ts.map +0 -1
- package/dist/src/editor/components/outline.d.ts +0 -21
- package/dist/src/editor/components/outline.d.ts.map +0 -1
- package/dist/src/editor/components/redirect-countdown.d.ts +0 -2
- package/dist/src/editor/components/redirect-countdown.d.ts.map +0 -1
- package/dist/src/editor/components/seo-editor.d.ts +0 -2
- package/dist/src/editor/components/seo-editor.d.ts.map +0 -1
- package/dist/src/editor/components/text-style-toolbar.d.ts +0 -8
- package/dist/src/editor/components/text-style-toolbar.d.ts.map +0 -1
- package/dist/src/editor/components/toast/toast-container.d.ts +0 -7
- package/dist/src/editor/components/toast/toast-container.d.ts.map +0 -1
- package/dist/src/editor/components/toast/toast.d.ts +0 -7
- package/dist/src/editor/components/toast/toast.d.ts.map +0 -1
- package/dist/src/editor/components/toast/types.d.ts +0 -7
- package/dist/src/editor/components/toast/types.d.ts.map +0 -1
- package/dist/src/editor/components/toolbar.d.ts +0 -21
- package/dist/src/editor/components/toolbar.d.ts.map +0 -1
- package/dist/src/editor/config.d.ts +0 -4
- package/dist/src/editor/config.d.ts.map +0 -1
- package/dist/src/editor/constants.d.ts +0 -102
- package/dist/src/editor/constants.d.ts.map +0 -1
- package/dist/src/editor/context.d.ts +0 -14
- package/dist/src/editor/context.d.ts.map +0 -1
- package/dist/src/editor/dom.d.ts +0 -86
- package/dist/src/editor/dom.d.ts.map +0 -1
- package/dist/src/editor/editor.d.ts +0 -64
- package/dist/src/editor/editor.d.ts.map +0 -1
- package/dist/src/editor/history.d.ts +0 -20
- package/dist/src/editor/history.d.ts.map +0 -1
- package/dist/src/editor/hooks/index.d.ts +0 -14
- package/dist/src/editor/hooks/index.d.ts.map +0 -1
- package/dist/src/editor/hooks/useAIHandlers.d.ts +0 -22
- package/dist/src/editor/hooks/useAIHandlers.d.ts.map +0 -1
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +0 -18
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +0 -1
- package/dist/src/editor/hooks/useElementDetection.d.ts +0 -26
- package/dist/src/editor/hooks/useElementDetection.d.ts.map +0 -1
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts +0 -12
- package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +0 -1
- package/dist/src/editor/hooks/useTextSelection.d.ts +0 -23
- package/dist/src/editor/hooks/useTextSelection.d.ts.map +0 -1
- package/dist/src/editor/hooks/useTooltipState.d.ts +0 -19
- package/dist/src/editor/hooks/useTooltipState.d.ts.map +0 -1
- package/dist/src/editor/hooks/utils.d.ts +0 -32
- package/dist/src/editor/hooks/utils.d.ts.map +0 -1
- package/dist/src/editor/index.d.ts +0 -12
- package/dist/src/editor/index.d.ts.map +0 -1
- package/dist/src/editor/lib/cn.d.ts +0 -3
- package/dist/src/editor/lib/cn.d.ts.map +0 -1
- package/dist/src/editor/manifest.d.ts +0 -19
- package/dist/src/editor/manifest.d.ts.map +0 -1
- package/dist/src/editor/markdown-api.d.ts +0 -36
- package/dist/src/editor/markdown-api.d.ts.map +0 -1
- package/dist/src/editor/signals.d.ts +0 -242
- package/dist/src/editor/signals.d.ts.map +0 -1
- package/dist/src/editor/storage.d.ts +0 -29
- package/dist/src/editor/storage.d.ts.map +0 -1
- package/dist/src/editor/text-styling.d.ts +0 -350
- package/dist/src/editor/text-styling.d.ts.map +0 -1
- package/dist/src/editor/themes.d.ts +0 -38
- package/dist/src/editor/themes.d.ts.map +0 -1
- package/dist/src/editor/types.d.ts +0 -454
- package/dist/src/editor/types.d.ts.map +0 -1
- package/dist/src/error-collector.d.ts +0 -56
- package/dist/src/error-collector.d.ts.map +0 -1
- package/dist/src/handlers/array-ops.d.ts +0 -59
- package/dist/src/handlers/array-ops.d.ts.map +0 -1
- package/dist/src/handlers/component-ops.d.ts +0 -60
- package/dist/src/handlers/component-ops.d.ts.map +0 -1
- package/dist/src/handlers/markdown-ops.d.ts +0 -41
- package/dist/src/handlers/markdown-ops.d.ts.map +0 -1
- package/dist/src/handlers/request-utils.d.ts +0 -20
- package/dist/src/handlers/request-utils.d.ts.map +0 -1
- package/dist/src/handlers/source-writer.d.ts +0 -51
- package/dist/src/handlers/source-writer.d.ts.map +0 -1
- package/dist/src/html-processor.d.ts +0 -63
- package/dist/src/html-processor.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -41
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/manifest-writer.d.ts +0 -111
- package/dist/src/manifest-writer.d.ts.map +0 -1
- package/dist/src/media/contember.d.ts +0 -15
- package/dist/src/media/contember.d.ts.map +0 -1
- package/dist/src/media/local.d.ts +0 -9
- package/dist/src/media/local.d.ts.map +0 -1
- package/dist/src/media/s3.d.ts +0 -12
- package/dist/src/media/s3.d.ts.map +0 -1
- package/dist/src/media/types.d.ts +0 -40
- package/dist/src/media/types.d.ts.map +0 -1
- package/dist/src/preview-generator.d.ts +0 -19
- package/dist/src/preview-generator.d.ts.map +0 -1
- package/dist/src/seo-processor.d.ts +0 -23
- package/dist/src/seo-processor.d.ts.map +0 -1
- package/dist/src/source-finder/ast-extractors.d.ts +0 -35
- package/dist/src/source-finder/ast-extractors.d.ts.map +0 -1
- package/dist/src/source-finder/ast-parser.d.ts +0 -18
- package/dist/src/source-finder/ast-parser.d.ts.map +0 -1
- package/dist/src/source-finder/cache.d.ts +0 -18
- package/dist/src/source-finder/cache.d.ts.map +0 -1
- package/dist/src/source-finder/collection-finder.d.ts +0 -29
- package/dist/src/source-finder/collection-finder.d.ts.map +0 -1
- package/dist/src/source-finder/cross-file-tracker.d.ts +0 -39
- package/dist/src/source-finder/cross-file-tracker.d.ts.map +0 -1
- package/dist/src/source-finder/element-finder.d.ts +0 -42
- package/dist/src/source-finder/element-finder.d.ts.map +0 -1
- package/dist/src/source-finder/image-finder.d.ts +0 -24
- package/dist/src/source-finder/image-finder.d.ts.map +0 -1
- package/dist/src/source-finder/index.d.ts +0 -9
- package/dist/src/source-finder/index.d.ts.map +0 -1
- package/dist/src/source-finder/search-index.d.ts +0 -27
- package/dist/src/source-finder/search-index.d.ts.map +0 -1
- package/dist/src/source-finder/snippet-utils.d.ts +0 -96
- package/dist/src/source-finder/snippet-utils.d.ts.map +0 -1
- package/dist/src/source-finder/source-lookup.d.ts +0 -16
- package/dist/src/source-finder/source-lookup.d.ts.map +0 -1
- package/dist/src/source-finder/types.d.ts +0 -167
- package/dist/src/source-finder/types.d.ts.map +0 -1
- package/dist/src/source-finder/variable-extraction.d.ts +0 -37
- package/dist/src/source-finder/variable-extraction.d.ts.map +0 -1
- package/dist/src/tailwind-colors.d.ts +0 -54
- package/dist/src/tailwind-colors.d.ts.map +0 -1
- package/dist/src/tsconfig.tsbuildinfo +0 -1
- package/dist/src/types.d.ts +0 -367
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/utils.d.ts +0 -61
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/vite-plugin.d.ts +0 -14
- package/dist/src/vite-plugin.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"directory": "packages/astro-cms"
|
|
15
15
|
},
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
|
-
"version": "0.
|
|
17
|
+
"version": "0.5.0",
|
|
18
18
|
"module": "src/index.ts",
|
|
19
19
|
"types": "src/index.ts",
|
|
20
20
|
"type": "module",
|
|
@@ -28,24 +28,23 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@astrojs/compiler": "^2.13.0",
|
|
30
30
|
"@babel/parser": "^7.24.0",
|
|
31
|
+
"node-html-parser": "^6.1.13",
|
|
32
|
+
"yaml": "^2.8.2"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
31
35
|
"@milkdown/core": "^7.8.0",
|
|
36
|
+
"@milkdown/ctx": "^7.8.0",
|
|
32
37
|
"@milkdown/plugin-listener": "^7.8.0",
|
|
33
38
|
"@milkdown/preset-commonmark": "^7.8.0",
|
|
34
39
|
"@milkdown/preset-gfm": "^7.8.0",
|
|
40
|
+
"@milkdown/prose": "^7.8.0",
|
|
35
41
|
"@milkdown/utils": "^7.8.0",
|
|
36
42
|
"@preact/signals": "^2.6.1",
|
|
37
43
|
"@tailwindcss/vite": "^4.1.11",
|
|
44
|
+
"@types/bun": "latest",
|
|
38
45
|
"clsx": "^2.1.1",
|
|
39
46
|
"marked": "^17.0.1",
|
|
40
|
-
"node-html-parser": "^6.1.13",
|
|
41
47
|
"preact": "^10.28.0",
|
|
42
|
-
"tailwind-merge": "^3.4.0",
|
|
43
|
-
"yaml": "^2.8.2"
|
|
44
|
-
},
|
|
45
|
-
"devDependencies": {
|
|
46
|
-
"@types/bun": "latest",
|
|
47
|
-
"@milkdown/ctx": "^7.8.0",
|
|
48
|
-
"@milkdown/prose": "^7.8.0",
|
|
49
48
|
"prosemirror-commands": "^1.7.1",
|
|
50
49
|
"prosemirror-inputrules": "^1.5.1",
|
|
51
50
|
"prosemirror-keymap": "^1.2.3",
|
|
@@ -54,7 +53,8 @@
|
|
|
54
53
|
"prosemirror-schema-list": "^1.5.1",
|
|
55
54
|
"prosemirror-state": "^1.4.4",
|
|
56
55
|
"prosemirror-transform": "^1.10.5",
|
|
57
|
-
"prosemirror-view": "^1.41.5"
|
|
56
|
+
"prosemirror-view": "^1.41.5",
|
|
57
|
+
"tailwind-merge": "^3.4.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"astro": "^5.16.6",
|
|
@@ -68,7 +68,8 @@
|
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
|
-
"
|
|
71
|
+
"build": "vite build --config vite.config.editor.ts",
|
|
72
|
+
"prepack": "bun run build && bun run ../../scripts/workspace-deps/resolve-deps.ts"
|
|
72
73
|
},
|
|
73
74
|
"keywords": [
|
|
74
75
|
"astro",
|
package/src/dev-middleware.ts
CHANGED
|
@@ -98,18 +98,50 @@ export function createDevMiddleware(
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Serve global CMS manifest (component definitions, available colors, collection definitions, and settings)
|
|
101
|
-
server.middlewares.use((req, res, next) => {
|
|
101
|
+
server.middlewares.use(async (req, res, next) => {
|
|
102
102
|
const pathname = (req.url || '').split('?')[0]
|
|
103
103
|
if (pathname === '/cms-manifest.json') {
|
|
104
104
|
res.setHeader('Content-Type', 'application/json')
|
|
105
105
|
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
106
106
|
res.setHeader('Cache-Control', 'no-store')
|
|
107
|
+
|
|
108
|
+
// Build pages from visited pages (have titles from SEO) + filesystem scan
|
|
109
|
+
const pageMap = new Map<string, { pathname: string; title?: string }>()
|
|
110
|
+
|
|
111
|
+
// 1. Add pages discovered from filesystem (src/pages)
|
|
112
|
+
const discoveredPages = await discoverPagesFromFilesystem()
|
|
113
|
+
for (const pagePath of discoveredPages) {
|
|
114
|
+
pageMap.set(pagePath, { pathname: pagePath })
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 2. Add collection entry pages from collection definitions
|
|
118
|
+
const collectionDefs = manifestWriter.getCollectionDefinitions()
|
|
119
|
+
for (const def of Object.values(collectionDefs)) {
|
|
120
|
+
if (def.entries) {
|
|
121
|
+
for (const entry of def.entries) {
|
|
122
|
+
if (entry.pathname) {
|
|
123
|
+
pageMap.set(entry.pathname, { pathname: entry.pathname, title: entry.title })
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 3. Overlay visited pages (they have SEO titles)
|
|
130
|
+
for (const [pagePath, data] of manifestWriter.getPageDataForPreviews()) {
|
|
131
|
+
const existing = pageMap.get(pagePath)
|
|
132
|
+
const title = data.seo?.title?.content || existing?.title
|
|
133
|
+
pageMap.set(pagePath, { pathname: pagePath, ...(title ? { title } : {}) })
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const pages = Array.from(pageMap.values())
|
|
137
|
+
.sort((a, b) => a.pathname.localeCompare(b.pathname))
|
|
138
|
+
|
|
107
139
|
const manifest: Record<string, unknown> = {
|
|
108
140
|
componentDefinitions,
|
|
109
141
|
availableColors: manifestWriter.getAvailableColors(),
|
|
110
142
|
availableTextStyles: manifestWriter.getAvailableTextStyles(),
|
|
143
|
+
pages,
|
|
111
144
|
}
|
|
112
|
-
const collectionDefs = manifestWriter.getCollectionDefinitions()
|
|
113
145
|
if (Object.keys(collectionDefs).length > 0) {
|
|
114
146
|
manifest.collectionDefinitions = collectionDefs
|
|
115
147
|
}
|
|
@@ -577,6 +609,54 @@ async function processHtmlForDev(
|
|
|
577
609
|
}
|
|
578
610
|
}
|
|
579
611
|
|
|
612
|
+
/** Page file extensions recognized by Astro */
|
|
613
|
+
const PAGE_EXTENSIONS = new Set(['.astro', '.md', '.mdx'])
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Scan src/pages directory to discover all static page routes.
|
|
617
|
+
* Skips dynamic routes (files with [ in the name) and API routes (.ts/.js).
|
|
618
|
+
*/
|
|
619
|
+
async function discoverPagesFromFilesystem(): Promise<string[]> {
|
|
620
|
+
const projectRoot = getProjectRoot()
|
|
621
|
+
const pagesDir = path.join(projectRoot, 'src', 'pages')
|
|
622
|
+
|
|
623
|
+
try {
|
|
624
|
+
await fs.access(pagesDir)
|
|
625
|
+
} catch {
|
|
626
|
+
return []
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const pages: string[] = []
|
|
630
|
+
|
|
631
|
+
async function walk(dir: string, urlPrefix: string) {
|
|
632
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
633
|
+
for (const entry of entries) {
|
|
634
|
+
if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue
|
|
635
|
+
|
|
636
|
+
const fullPath = path.join(dir, entry.name)
|
|
637
|
+
if (entry.isDirectory()) {
|
|
638
|
+
// Skip directories with dynamic segments
|
|
639
|
+
if (entry.name.includes('[')) continue
|
|
640
|
+
await walk(fullPath, `${urlPrefix}${entry.name}/`)
|
|
641
|
+
} else {
|
|
642
|
+
const ext = path.extname(entry.name)
|
|
643
|
+
if (!PAGE_EXTENSIONS.has(ext)) continue
|
|
644
|
+
// Skip dynamic routes
|
|
645
|
+
if (entry.name.includes('[')) continue
|
|
646
|
+
|
|
647
|
+
const baseName = path.basename(entry.name, ext)
|
|
648
|
+
const pagePath = baseName === 'index'
|
|
649
|
+
? urlPrefix.replace(/\/$/, '') || '/'
|
|
650
|
+
: `${urlPrefix}${baseName}`
|
|
651
|
+
pages.push(pagePath)
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
await walk(pagesDir, '/')
|
|
657
|
+
return pages
|
|
658
|
+
}
|
|
659
|
+
|
|
580
660
|
function mediaMimeFromExt(ext: string): string {
|
|
581
661
|
const map: Record<string, string> = {
|
|
582
662
|
'.jpg': 'image/jpeg',
|
|
@@ -7,7 +7,7 @@ import type { ComponentProp, InsertPosition } from '../types'
|
|
|
7
7
|
export interface BlockEditorProps {
|
|
8
8
|
visible: boolean
|
|
9
9
|
componentId: string | null
|
|
10
|
-
|
|
10
|
+
cursor: { x: number; y: number } | null
|
|
11
11
|
onClose: () => void
|
|
12
12
|
onUpdateProps: (componentId: string, props: Record<string, any>) => void
|
|
13
13
|
onInsertComponent: (
|
|
@@ -24,7 +24,7 @@ type EditorMode = 'edit' | 'insert-picker' | 'insert-props' | 'confirm-remove'
|
|
|
24
24
|
export function BlockEditor({
|
|
25
25
|
visible,
|
|
26
26
|
componentId,
|
|
27
|
-
|
|
27
|
+
cursor,
|
|
28
28
|
onClose,
|
|
29
29
|
onUpdateProps,
|
|
30
30
|
onInsertComponent,
|
|
@@ -66,7 +66,7 @@ export function BlockEditor({
|
|
|
66
66
|
setSelectedComponent(null)
|
|
67
67
|
setInsertPosition('after')
|
|
68
68
|
}
|
|
69
|
-
}, [visible
|
|
69
|
+
}, [visible])
|
|
70
70
|
|
|
71
71
|
useEffect(() => {
|
|
72
72
|
if (currentInstance) {
|
|
@@ -79,7 +79,6 @@ export function BlockEditor({
|
|
|
79
79
|
|
|
80
80
|
const updatePosition = () => {
|
|
81
81
|
const editorWidth = LAYOUT.BLOCK_EDITOR_WIDTH
|
|
82
|
-
const editorHeight = LAYOUT.BLOCK_EDITOR_HEIGHT
|
|
83
82
|
const padding = LAYOUT.VIEWPORT_PADDING
|
|
84
83
|
const viewportWidth = window.innerWidth
|
|
85
84
|
const viewportHeight = window.innerHeight
|
|
@@ -87,18 +86,11 @@ export function BlockEditor({
|
|
|
87
86
|
let top: number
|
|
88
87
|
let left: number
|
|
89
88
|
|
|
90
|
-
if (
|
|
91
|
-
top =
|
|
92
|
-
left =
|
|
93
|
-
|
|
94
|
-
if (top + editorHeight > viewportHeight - padding) {
|
|
95
|
-
top = Math.max(padding, rect.top - editorHeight - padding)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (top < padding) {
|
|
99
|
-
top = Math.max(padding, (viewportHeight - editorHeight) / 2)
|
|
100
|
-
}
|
|
89
|
+
if (cursor) {
|
|
90
|
+
top = cursor.y
|
|
91
|
+
left = cursor.x
|
|
101
92
|
|
|
93
|
+
// Keep within viewport bounds
|
|
102
94
|
if (left + editorWidth > viewportWidth - padding) {
|
|
103
95
|
left = viewportWidth - editorWidth - padding
|
|
104
96
|
}
|
|
@@ -106,7 +98,7 @@ export function BlockEditor({
|
|
|
106
98
|
left = padding
|
|
107
99
|
}
|
|
108
100
|
} else {
|
|
109
|
-
top =
|
|
101
|
+
top = viewportHeight / 2
|
|
110
102
|
left = (viewportWidth - editorWidth) / 2
|
|
111
103
|
}
|
|
112
104
|
|
|
@@ -119,13 +111,11 @@ export function BlockEditor({
|
|
|
119
111
|
|
|
120
112
|
updatePosition()
|
|
121
113
|
window.addEventListener('resize', updatePosition)
|
|
122
|
-
window.addEventListener('scroll', updatePosition)
|
|
123
114
|
|
|
124
115
|
return () => {
|
|
125
116
|
window.removeEventListener('resize', updatePosition)
|
|
126
|
-
window.removeEventListener('scroll', updatePosition)
|
|
127
117
|
}
|
|
128
|
-
}, [visible,
|
|
118
|
+
}, [visible, cursor])
|
|
129
119
|
|
|
130
120
|
// Inject/remove inline mock preview into the real page at the insertion point
|
|
131
121
|
useEffect(() => {
|
|
@@ -361,28 +351,13 @@ export function BlockEditor({
|
|
|
361
351
|
</div>
|
|
362
352
|
|
|
363
353
|
{/* Content */}
|
|
364
|
-
<div class=
|
|
354
|
+
<div class={`flex-1 flex flex-col overflow-hidden bg-cms-dark ${mode === 'edit' && currentDefinition ? '' : 'overflow-y-auto'}`}>
|
|
365
355
|
{mode === 'edit' && currentDefinition
|
|
366
356
|
? (
|
|
367
357
|
<>
|
|
368
|
-
{/*
|
|
369
|
-
<div class="
|
|
370
|
-
|
|
371
|
-
onClick={() => handleStartInsert('before')}
|
|
372
|
-
class="flex-1 py-2.5 px-3 bg-white/10 text-white/80 rounded-cms-md cursor-pointer text-[13px] font-medium flex items-center justify-center gap-1.5 hover:bg-white/20 hover:text-white transition-colors"
|
|
373
|
-
>
|
|
374
|
-
<span class="text-base">↑</span> {isArrayItem ? 'Add item before' : 'Insert before'}
|
|
375
|
-
</button>
|
|
376
|
-
<button
|
|
377
|
-
onClick={() => handleStartInsert('after')}
|
|
378
|
-
class="flex-1 py-2.5 px-3 bg-white/10 text-white/80 rounded-cms-md cursor-pointer text-[13px] font-medium flex items-center justify-center gap-1.5 hover:bg-white/20 hover:text-white transition-colors"
|
|
379
|
-
>
|
|
380
|
-
<span class="text-base">↓</span> {isArrayItem ? 'Add item after' : 'Insert after'}
|
|
381
|
-
</button>
|
|
382
|
-
</div>
|
|
383
|
-
|
|
384
|
-
{/* Props editor */}
|
|
385
|
-
<div class="mb-5">
|
|
358
|
+
{/* Scrollable body */}
|
|
359
|
+
<div class="p-5 overflow-y-auto flex-1">
|
|
360
|
+
{/* Props editor */}
|
|
386
361
|
<div class="text-xs font-medium text-white/50 tracking-wide mb-3 uppercase">
|
|
387
362
|
Properties
|
|
388
363
|
</div>
|
|
@@ -396,34 +371,50 @@ export function BlockEditor({
|
|
|
396
371
|
))}
|
|
397
372
|
</div>
|
|
398
373
|
|
|
399
|
-
{/* Actions */}
|
|
400
|
-
<div class="
|
|
401
|
-
<button
|
|
402
|
-
onClick={() => setMode('confirm-remove')}
|
|
403
|
-
class="px-4 py-2.5 bg-cms-error text-white rounded-cms-pill cursor-pointer hover:bg-red-600 transition-colors font-medium"
|
|
404
|
-
>
|
|
405
|
-
{isArrayItem ? 'Remove item' : 'Remove'}
|
|
406
|
-
</button>
|
|
374
|
+
{/* Insert buttons + Actions — outside scroll area */}
|
|
375
|
+
<div class="px-5 py-4 border-t border-white/10 flex flex-col gap-3">
|
|
407
376
|
<div class="flex gap-2">
|
|
408
377
|
<button
|
|
409
|
-
onClick={
|
|
410
|
-
class="
|
|
378
|
+
onClick={() => handleStartInsert('before')}
|
|
379
|
+
class="flex-1 py-2.5 px-3 bg-white/10 text-white/80 rounded-cms-md cursor-pointer text-[13px] font-medium flex items-center justify-center gap-1.5 hover:bg-white/20 hover:text-white transition-colors"
|
|
380
|
+
>
|
|
381
|
+
<span class="text-base">↑</span> {isArrayItem ? 'Add item before' : 'Insert before'}
|
|
382
|
+
</button>
|
|
383
|
+
<button
|
|
384
|
+
onClick={() => handleStartInsert('after')}
|
|
385
|
+
class="flex-1 py-2.5 px-3 bg-white/10 text-white/80 rounded-cms-md cursor-pointer text-[13px] font-medium flex items-center justify-center gap-1.5 hover:bg-white/20 hover:text-white transition-colors"
|
|
411
386
|
>
|
|
412
|
-
|
|
387
|
+
<span class="text-base">↓</span> {isArrayItem ? 'Add item after' : 'Insert after'}
|
|
413
388
|
</button>
|
|
389
|
+
</div>
|
|
390
|
+
<div class="flex gap-2 justify-between">
|
|
414
391
|
<button
|
|
415
|
-
onClick={
|
|
416
|
-
class="px-4 py-2.5 bg-cms-
|
|
392
|
+
onClick={() => setMode('confirm-remove')}
|
|
393
|
+
class="px-4 py-2.5 bg-cms-error text-white rounded-cms-pill cursor-pointer hover:bg-red-600 transition-colors font-medium"
|
|
417
394
|
>
|
|
418
|
-
|
|
395
|
+
{isArrayItem ? 'Remove item' : 'Remove'}
|
|
419
396
|
</button>
|
|
397
|
+
<div class="flex gap-2">
|
|
398
|
+
<button
|
|
399
|
+
onClick={onClose}
|
|
400
|
+
class="px-4 py-2.5 bg-white/10 text-white/80 rounded-cms-pill cursor-pointer hover:bg-white/20 hover:text-white transition-colors font-medium"
|
|
401
|
+
>
|
|
402
|
+
Cancel
|
|
403
|
+
</button>
|
|
404
|
+
<button
|
|
405
|
+
onClick={handleSave}
|
|
406
|
+
class="px-4 py-2.5 bg-cms-primary text-cms-primary-text rounded-cms-pill cursor-pointer hover:bg-cms-primary-hover transition-all font-medium"
|
|
407
|
+
>
|
|
408
|
+
Save
|
|
409
|
+
</button>
|
|
410
|
+
</div>
|
|
420
411
|
</div>
|
|
421
412
|
</div>
|
|
422
413
|
</>
|
|
423
414
|
)
|
|
424
415
|
: mode === 'confirm-remove'
|
|
425
416
|
? (
|
|
426
|
-
<div class="text-center
|
|
417
|
+
<div class="text-center p-5">
|
|
427
418
|
<div class="px-4 py-3 bg-red-500/10 border border-red-500/30 rounded-cms-md mb-5 text-[13px] text-white">
|
|
428
419
|
{isArrayItem
|
|
429
420
|
? (
|
|
@@ -460,7 +451,7 @@ export function BlockEditor({
|
|
|
460
451
|
)
|
|
461
452
|
: mode === 'insert-props' && selectedComponent
|
|
462
453
|
? (
|
|
463
|
-
|
|
454
|
+
<div class="p-5">
|
|
464
455
|
{/* New component props */}
|
|
465
456
|
<div class="mb-5">
|
|
466
457
|
<div class="px-4 py-3 bg-white/10 rounded-cms-md mb-4 text-[13px] text-white">
|
|
@@ -500,12 +491,12 @@ export function BlockEditor({
|
|
|
500
491
|
{isArrayItem ? 'Add item' : 'Insert component'}
|
|
501
492
|
</button>
|
|
502
493
|
</div>
|
|
503
|
-
|
|
494
|
+
</div>
|
|
504
495
|
)
|
|
505
496
|
: mode === 'insert-picker'
|
|
506
497
|
? (
|
|
507
498
|
/* Component picker for insertion */
|
|
508
|
-
<div>
|
|
499
|
+
<div class="p-5">
|
|
509
500
|
<div class="text-xs font-medium text-white/50 tracking-wide mb-4 uppercase">
|
|
510
501
|
Select component to insert
|
|
511
502
|
</div>
|
|
@@ -559,7 +550,7 @@ export function BlockEditor({
|
|
|
559
550
|
)
|
|
560
551
|
: (
|
|
561
552
|
/* No component selected - show placeholder */
|
|
562
|
-
<div class="text-center text-white/50 py-8">
|
|
553
|
+
<div class="text-center text-white/50 p-5 py-8">
|
|
563
554
|
<p>Select a component to edit its properties.</p>
|
|
564
555
|
</div>
|
|
565
556
|
)}
|
|
@@ -110,6 +110,19 @@ export function Outline(
|
|
|
110
110
|
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
|
111
111
|
pointer-events: auto;
|
|
112
112
|
z-index: ${Z_INDEX.MODAL};
|
|
113
|
+
margin-top: 6px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.element-toolbar::before {
|
|
117
|
+
content: '';
|
|
118
|
+
position: absolute;
|
|
119
|
+
top: -25px;
|
|
120
|
+
left: -50px;
|
|
121
|
+
right: -50px;
|
|
122
|
+
bottom: 0;
|
|
123
|
+
z-index: -1;
|
|
124
|
+
pointer-events: auto;
|
|
125
|
+
background: transparent;
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
.element-toolbar.hidden {
|
|
@@ -143,8 +156,8 @@ export function Outline(
|
|
|
143
156
|
}
|
|
144
157
|
|
|
145
158
|
.attr-button {
|
|
146
|
-
width:
|
|
147
|
-
height:
|
|
159
|
+
width: 32px;
|
|
160
|
+
height: 32px;
|
|
148
161
|
display: flex;
|
|
149
162
|
align-items: center;
|
|
150
163
|
justify-content: center;
|
|
@@ -161,8 +174,8 @@ export function Outline(
|
|
|
161
174
|
}
|
|
162
175
|
|
|
163
176
|
.attr-button svg {
|
|
164
|
-
width:
|
|
165
|
-
height:
|
|
177
|
+
width: 18px;
|
|
178
|
+
height: 18px;
|
|
166
179
|
color: rgba(255,255,255,0.7);
|
|
167
180
|
}
|
|
168
181
|
|
|
@@ -325,41 +338,35 @@ export function Outline(
|
|
|
325
338
|
}
|
|
326
339
|
}
|
|
327
340
|
|
|
328
|
-
// Add color
|
|
341
|
+
// Add unified color swatch
|
|
329
342
|
if (hasColorSwatches && colorClasses) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
343
|
+
const bgParsed = colorClasses.bg?.value ? parseColorClass(colorClasses.bg.value) : null
|
|
344
|
+
const textParsed = colorClasses.text?.value ? parseColorClass(colorClasses.text.value) : null
|
|
345
|
+
const bgPreview = bgParsed ? getColorPreview(bgParsed.colorName, bgParsed.shade) : null
|
|
346
|
+
const textPreview = textParsed ? getColorPreview(textParsed.colorName, textParsed.shade) : null
|
|
347
|
+
|
|
348
|
+
const swatch = document.createElement('div')
|
|
349
|
+
const isWhite = (bgParsed && !textParsed && bgParsed.colorName === 'white')
|
|
350
|
+
|| (!bgParsed && textParsed && textParsed.colorName === 'white')
|
|
351
|
+
swatch.className = `color-swatch${isWhite ? ' white' : ''}`
|
|
352
|
+
|
|
353
|
+
if (bgPreview && textPreview) {
|
|
354
|
+
// Split swatch: diagonal half bg, half text
|
|
355
|
+
swatch.style.background = `linear-gradient(135deg, ${bgPreview} 50%, ${textPreview} 50%)`
|
|
356
|
+
swatch.title = `Background: ${colorClasses.bg!.value} / Text: ${colorClasses.text!.value}`
|
|
357
|
+
} else if (bgParsed && bgPreview) {
|
|
358
|
+
applySwatchStyle(swatch, bgParsed.colorName, bgPreview)
|
|
359
|
+
swatch.title = `Background: ${colorClasses.bg!.value}`
|
|
360
|
+
} else if (textParsed && textPreview) {
|
|
361
|
+
applySwatchStyle(swatch, textParsed.colorName, textPreview)
|
|
362
|
+
swatch.title = `Text: ${colorClasses.text!.value}`
|
|
345
363
|
}
|
|
346
364
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (parsed) {
|
|
351
|
-
const preview = getColorPreview(parsed.colorName, parsed.shade)
|
|
352
|
-
const swatch = document.createElement('div')
|
|
353
|
-
swatch.className = `color-swatch${parsed.colorName === 'white' ? ' white' : ''}`
|
|
354
|
-
applySwatchStyle(swatch, parsed.colorName, preview)
|
|
355
|
-
swatch.title = `Text: ${colorClasses.text.value}`
|
|
356
|
-
swatch.onclick = (e) => {
|
|
357
|
-
e.stopPropagation()
|
|
358
|
-
if (cmsId && onColorClick) onColorClick(cmsId, rect)
|
|
359
|
-
}
|
|
360
|
-
toolbarRef.current.appendChild(swatch)
|
|
361
|
-
}
|
|
365
|
+
swatch.onclick = (e) => {
|
|
366
|
+
e.stopPropagation()
|
|
367
|
+
if (cmsId && onColorClick) onColorClick(cmsId, rect)
|
|
362
368
|
}
|
|
369
|
+
toolbarRef.current.appendChild(swatch)
|
|
363
370
|
}
|
|
364
371
|
|
|
365
372
|
// Add divider and attribute button if needed
|
|
@@ -18,6 +18,7 @@ export interface ToolbarCallbacks {
|
|
|
18
18
|
onToggleHighlights?: () => void
|
|
19
19
|
onSeoEditor?: () => void
|
|
20
20
|
onOpenCollection?: (name: string) => void
|
|
21
|
+
onOpenCollections?: () => void
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export interface ToolbarProps {
|
|
@@ -143,11 +144,13 @@ export const Toolbar = ({ callbacks, collectionDefinitions }: ToolbarProps) => {
|
|
|
143
144
|
})
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
//
|
|
147
|
+
// Single consolidated collections item
|
|
147
148
|
if (collectionDefinitions) {
|
|
148
|
-
|
|
149
|
+
const labels = Object.values(collectionDefinitions).map((d) => d.label)
|
|
150
|
+
const collectionsLabel = labels.length <= 2 ? labels.join(', ') : `${labels.slice(0, 2).join(', ')}, ...`
|
|
151
|
+
if (labels.length > 0) {
|
|
149
152
|
menuItems.push({
|
|
150
|
-
label:
|
|
153
|
+
label: collectionsLabel,
|
|
151
154
|
icon: (
|
|
152
155
|
<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
156
|
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
@@ -156,7 +159,7 @@ export const Toolbar = ({ callbacks, collectionDefinitions }: ToolbarProps) => {
|
|
|
156
159
|
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
157
160
|
</svg>
|
|
158
161
|
),
|
|
159
|
-
onClick: () => callbacks.
|
|
162
|
+
onClick: () => callbacks.onOpenCollections?.(),
|
|
160
163
|
})
|
|
161
164
|
}
|
|
162
165
|
}
|
|
@@ -50,16 +50,16 @@ export function useBlockEditorHandlers({
|
|
|
50
50
|
config,
|
|
51
51
|
showToast,
|
|
52
52
|
}: BlockEditorHandlersOptions) {
|
|
53
|
-
const [
|
|
53
|
+
const [blockEditorCursor, setBlockEditorCursor] = useState<{ x: number; y: number } | null>(null)
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Open block editor for a component
|
|
57
57
|
*/
|
|
58
58
|
const handleComponentSelect = useCallback(
|
|
59
|
-
(componentId: string,
|
|
59
|
+
(componentId: string, cursor: { x: number; y: number }) => {
|
|
60
60
|
signals.setCurrentComponentId(componentId)
|
|
61
61
|
signals.setBlockEditorOpen(true)
|
|
62
|
-
|
|
62
|
+
setBlockEditorCursor(cursor)
|
|
63
63
|
},
|
|
64
64
|
[],
|
|
65
65
|
)
|
|
@@ -70,7 +70,7 @@ export function useBlockEditorHandlers({
|
|
|
70
70
|
const handleBlockEditorClose = useCallback(() => {
|
|
71
71
|
signals.setBlockEditorOpen(false)
|
|
72
72
|
signals.setCurrentComponentId(null)
|
|
73
|
-
|
|
73
|
+
setBlockEditorCursor(null)
|
|
74
74
|
}, [])
|
|
75
75
|
|
|
76
76
|
/**
|
|
@@ -253,7 +253,7 @@ export function useBlockEditorHandlers({
|
|
|
253
253
|
)
|
|
254
254
|
|
|
255
255
|
return {
|
|
256
|
-
|
|
256
|
+
blockEditorCursor,
|
|
257
257
|
handleComponentSelect,
|
|
258
258
|
handleBlockEditorClose,
|
|
259
259
|
handleUpdateProps,
|
|
@@ -209,7 +209,7 @@ export function useElementDetection(): OutlineState {
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
export interface ComponentClickHandlerOptions {
|
|
212
|
-
onComponentSelect: (componentId: string,
|
|
212
|
+
onComponentSelect: (componentId: string, cursor: { x: number; y: number }) => void
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
/**
|
|
@@ -269,7 +269,7 @@ export function useComponentClickHandler({
|
|
|
269
269
|
if (componentId) {
|
|
270
270
|
ev.preventDefault()
|
|
271
271
|
ev.stopPropagation()
|
|
272
|
-
onComponentSelect(componentId,
|
|
272
|
+
onComponentSelect(componentId, { x: ev.clientX, y: ev.clientY })
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
}
|
package/src/editor/index.tsx
CHANGED
|
@@ -109,7 +109,7 @@ const CmsUI = () => {
|
|
|
109
109
|
})
|
|
110
110
|
|
|
111
111
|
const {
|
|
112
|
-
|
|
112
|
+
blockEditorCursor,
|
|
113
113
|
handleComponentSelect,
|
|
114
114
|
handleBlockEditorClose,
|
|
115
115
|
handleUpdateProps,
|
|
@@ -287,6 +287,7 @@ const CmsUI = () => {
|
|
|
287
287
|
onToggleHighlights: handleToggleHighlights,
|
|
288
288
|
onSeoEditor: hasSeoData ? handleSeoEditor : undefined,
|
|
289
289
|
onOpenCollection: handleOpenCollection,
|
|
290
|
+
onOpenCollections: openCollectionsBrowser,
|
|
290
291
|
}}
|
|
291
292
|
collectionDefinitions={Object.keys(collectionDefinitions).length > 0 ? collectionDefinitions : undefined}
|
|
292
293
|
/>
|
|
@@ -346,7 +347,7 @@ const CmsUI = () => {
|
|
|
346
347
|
<BlockEditor
|
|
347
348
|
visible={blockEditorState.isOpen && isEditing}
|
|
348
349
|
componentId={blockEditorState.currentComponentId}
|
|
349
|
-
|
|
350
|
+
cursor={blockEditorCursor}
|
|
350
351
|
onClose={handleBlockEditorClose}
|
|
351
352
|
onUpdateProps={handleUpdateProps}
|
|
352
353
|
onInsertComponent={handleInsertComponent}
|
|
@@ -131,7 +131,7 @@ function extractElementBounds(
|
|
|
131
131
|
): ArrayElementBounds[] {
|
|
132
132
|
const bounds: ArrayElementBounds[] = []
|
|
133
133
|
for (const el of elements) {
|
|
134
|
-
if (el
|
|
134
|
+
if (el?.loc) {
|
|
135
135
|
bounds.push({
|
|
136
136
|
// Babel loc is 1-indexed; convert to 0-indexed file lines
|
|
137
137
|
startLine: el.loc.start.line - 1 + frontmatterStartLine,
|
|
@@ -273,7 +273,7 @@ export async function handleRemoveArrayItem(
|
|
|
273
273
|
const freshLines = freshContent.split('\n')
|
|
274
274
|
|
|
275
275
|
const bounds = elementBounds[arrayIndex]!
|
|
276
|
-
|
|
276
|
+
const removeStart = bounds.startLine
|
|
277
277
|
let removeEnd = bounds.endLine
|
|
278
278
|
|
|
279
279
|
// Clean up trailing comma on the line after the element, or leading comma
|
|
@@ -298,7 +298,7 @@ export async function handleRemoveArrayItem(
|
|
|
298
298
|
if (removeStart > 0 && removeStart <= freshLines.length) {
|
|
299
299
|
const prevLine = freshLines[removeStart - 1]!
|
|
300
300
|
const nextLine = freshLines[removeStart]
|
|
301
|
-
if (nextLine
|
|
301
|
+
if (nextLine?.trim().startsWith(']') && prevLine.trimEnd().endsWith(',')) {
|
|
302
302
|
freshLines[removeStart - 1] = prevLine.replace(/,\s*$/, '')
|
|
303
303
|
}
|
|
304
304
|
}
|
|
@@ -397,7 +397,7 @@ export async function handleAddArrayItem(
|
|
|
397
397
|
for (let i = freshLines.length - 1; i >= 0; i--) {
|
|
398
398
|
if (freshLines[i]!.trim().startsWith(']')) {
|
|
399
399
|
const prev = freshLines[i - 1]
|
|
400
|
-
if (prev
|
|
400
|
+
if (prev?.trimEnd().endsWith(',')) {
|
|
401
401
|
// Check if this is the array we're editing by scanning backwards
|
|
402
402
|
// to find the array variable
|
|
403
403
|
freshLines[i - 1] = prev.replace(/,(\s*)$/, '$1')
|