davaux 0.8.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/BASELINE.md +169 -0
- package/CLAUDE.md +518 -0
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/ROADMAP.md +198 -0
- package/build.mjs +101 -0
- package/client/control.ts +247 -0
- package/client/hydrate.ts +37 -0
- package/client/index.ts +19 -0
- package/client/jsx-runtime.ts +209 -0
- package/client/resource.ts +122 -0
- package/client/signal.ts +211 -0
- package/client/store.ts +110 -0
- package/client/useHead.ts +63 -0
- package/dist/build/config.d.ts +3 -0
- package/dist/build/config.d.ts.map +1 -0
- package/dist/build/config.js +38 -0
- package/dist/build/config.js.map +7 -0
- package/dist/build/index.d.ts +2 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +13 -0
- package/dist/build/index.js.map +7 -0
- package/dist/build/plugins.d.ts +7 -0
- package/dist/build/plugins.d.ts.map +1 -0
- package/dist/build/plugins.js +85 -0
- package/dist/build/plugins.js.map +7 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +427 -0
- package/dist/cli.js.map +7 -0
- package/dist/client/control.d.ts +49 -0
- package/dist/client/control.d.ts.map +1 -0
- package/dist/client/control.js +154 -0
- package/dist/client/control.js.map +7 -0
- package/dist/client/hydrate.d.ts +7 -0
- package/dist/client/hydrate.d.ts.map +1 -0
- package/dist/client/hydrate.js +23 -0
- package/dist/client/hydrate.js.map +7 -0
- package/dist/client/index.d.ts +12 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +32 -0
- package/dist/client/index.js.map +7 -0
- package/dist/client/jsx-runtime.d.ts +40 -0
- package/dist/client/jsx-runtime.d.ts.map +1 -0
- package/dist/client/jsx-runtime.js +139 -0
- package/dist/client/jsx-runtime.js.map +7 -0
- package/dist/client/resource.d.ts +31 -0
- package/dist/client/resource.d.ts.map +1 -0
- package/dist/client/resource.js +64 -0
- package/dist/client/resource.js.map +7 -0
- package/dist/client/signal.d.ts +90 -0
- package/dist/client/signal.d.ts.map +1 -0
- package/dist/client/signal.js +115 -0
- package/dist/client/signal.js.map +7 -0
- package/dist/client/store.d.ts +26 -0
- package/dist/client/store.d.ts.map +1 -0
- package/dist/client/store.js +63 -0
- package/dist/client/store.js.map +7 -0
- package/dist/client/useHead.d.ts +28 -0
- package/dist/client/useHead.d.ts.map +1 -0
- package/dist/client/useHead.js +33 -0
- package/dist/client/useHead.js.map +7 -0
- package/dist/config.d.ts +182 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +21 -0
- package/dist/config.js.map +7 -0
- package/dist/create-multisite.d.ts +2 -0
- package/dist/create-multisite.d.ts.map +1 -0
- package/dist/create-multisite.js +291 -0
- package/dist/create-multisite.js.map +7 -0
- package/dist/create.d.ts +2 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +179 -0
- package/dist/create.js.map +7 -0
- package/dist/dev/blueprints.d.ts +11 -0
- package/dist/dev/blueprints.d.ts.map +1 -0
- package/dist/dev/blueprints.js +65 -0
- package/dist/dev/blueprints.js.map +7 -0
- package/dist/dev/components.d.ts +19 -0
- package/dist/dev/components.d.ts.map +1 -0
- package/dist/dev/components.js +87 -0
- package/dist/dev/components.js.map +7 -0
- package/dist/dev/insert.d.ts +11 -0
- package/dist/dev/insert.d.ts.map +1 -0
- package/dist/dev/insert.js +160 -0
- package/dist/dev/insert.js.map +7 -0
- package/dist/dev/remove.d.ts +53 -0
- package/dist/dev/remove.d.ts.map +1 -0
- package/dist/dev/remove.js +518 -0
- package/dist/dev/remove.js.map +7 -0
- package/dist/dev/watch.d.ts +26 -0
- package/dist/dev/watch.d.ts.map +1 -0
- package/dist/dev/watch.js +2905 -0
- package/dist/dev/watch.js.map +7 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +63 -0
- package/dist/errors.js.map +7 -0
- package/dist/generate.d.ts +2 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +191 -0
- package/dist/generate.js.map +7 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +7 -0
- package/dist/island.d.ts +24 -0
- package/dist/island.d.ts.map +1 -0
- package/dist/island.js +15 -0
- package/dist/island.js.map +7 -0
- package/dist/jsx-runtime.d.ts +406 -0
- package/dist/jsx-runtime.d.ts.map +1 -0
- package/dist/jsx-runtime.js +90 -0
- package/dist/jsx-runtime.js.map +7 -0
- package/dist/link.d.ts +27 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +29 -0
- package/dist/link.js.map +7 -0
- package/dist/oml/fragment.d.ts +16 -0
- package/dist/oml/fragment.d.ts.map +1 -0
- package/dist/oml/fragment.js +26 -0
- package/dist/oml/fragment.js.map +7 -0
- package/dist/oml/index.d.ts +11 -0
- package/dist/oml/index.d.ts.map +1 -0
- package/dist/oml/index.js +21 -0
- package/dist/oml/index.js.map +7 -0
- package/dist/oml/jsx-runtime.d.ts +34 -0
- package/dist/oml/jsx-runtime.d.ts.map +1 -0
- package/dist/oml/jsx-runtime.js +59 -0
- package/dist/oml/jsx-runtime.js.map +7 -0
- package/dist/oml/jsx.d.ts +14 -0
- package/dist/oml/jsx.d.ts.map +1 -0
- package/dist/oml/jsx.js +96 -0
- package/dist/oml/jsx.js.map +7 -0
- package/dist/oml/page.d.ts +7 -0
- package/dist/oml/page.d.ts.map +1 -0
- package/dist/oml/page.js +6 -0
- package/dist/oml/page.js.map +7 -0
- package/dist/oml/render.d.ts +13 -0
- package/dist/oml/render.d.ts.map +1 -0
- package/dist/oml/render.js +117 -0
- package/dist/oml/render.js.map +7 -0
- package/dist/oml/types.d.ts +79 -0
- package/dist/oml/types.d.ts.map +1 -0
- package/dist/oml/types.js +64 -0
- package/dist/oml/types.js.map +7 -0
- package/dist/router/handler.d.ts +53 -0
- package/dist/router/handler.d.ts.map +1 -0
- package/dist/router/handler.js +342 -0
- package/dist/router/handler.js.map +7 -0
- package/dist/router/matcher.d.ts +21 -0
- package/dist/router/matcher.d.ts.map +1 -0
- package/dist/router/matcher.js +28 -0
- package/dist/router/matcher.js.map +7 -0
- package/dist/router/scanner.d.ts +17 -0
- package/dist/router/scanner.d.ts.map +1 -0
- package/dist/router/scanner.js +197 -0
- package/dist/router/scanner.js.map +7 -0
- package/dist/server/index.d.ts +23 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +29 -0
- package/dist/server/index.js.map +7 -0
- package/dist/signal.d.ts +15 -0
- package/dist/signal.d.ts.map +1 -0
- package/dist/signal.js +29 -0
- package/dist/signal.js.map +7 -0
- package/dist/ssg.d.ts +45 -0
- package/dist/ssg.d.ts.map +1 -0
- package/dist/ssg.js +175 -0
- package/dist/ssg.js.map +7 -0
- package/dist/test/actions.test.d.ts +2 -0
- package/dist/test/actions.test.d.ts.map +1 -0
- package/dist/test/body-limits.test.d.ts +2 -0
- package/dist/test/body-limits.test.d.ts.map +1 -0
- package/dist/test/errors.test.d.ts +2 -0
- package/dist/test/errors.test.d.ts.map +1 -0
- package/dist/test/fixtures/routes/[id].page.d.ts +4 -0
- package/dist/test/fixtures/routes/[id].page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_error.d.ts +3 -0
- package/dist/test/fixtures/routes/_error.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_global.d.ts +3 -0
- package/dist/test/fixtures/routes/_global.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_layout-template.d.ts +3 -0
- package/dist/test/fixtures/routes/_layout-template.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_layout.d.ts +3 -0
- package/dist/test/fixtures/routes/_layout.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_layout_scripts.d.ts +3 -0
- package/dist/test/fixtures/routes/_layout_scripts.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_middleware.d.ts +3 -0
- package/dist/test/fixtures/routes/_middleware.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_redirect301_mw.d.ts +3 -0
- package/dist/test/fixtures/routes/_redirect301_mw.d.ts.map +1 -0
- package/dist/test/fixtures/routes/_redirect_mw.d.ts +3 -0
- package/dist/test/fixtures/routes/_redirect_mw.d.ts.map +1 -0
- package/dist/test/fixtures/routes/about.page.d.ts +3 -0
- package/dist/test/fixtures/routes/about.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/action.page.d.ts +6 -0
- package/dist/test/fixtures/routes/action.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/form-all.post.d.ts +3 -0
- package/dist/test/fixtures/routes/api/form-all.post.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/form-limited.post.d.ts +6 -0
- package/dist/test/fixtures/routes/api/form-limited.post.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/response-obj.get.d.ts +3 -0
- package/dist/test/fixtures/routes/api/response-obj.get.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/upload.post.d.ts +12 -0
- package/dist/test/fixtures/routes/api/upload.post.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/users.get.d.ts +6 -0
- package/dist/test/fixtures/routes/api/users.get.d.ts.map +1 -0
- package/dist/test/fixtures/routes/api/xml.get.d.ts +3 -0
- package/dist/test/fixtures/routes/api/xml.get.d.ts.map +1 -0
- package/dist/test/fixtures/routes/auth/_middleware.d.ts +3 -0
- package/dist/test/fixtures/routes/auth/_middleware.d.ts.map +1 -0
- package/dist/test/fixtures/routes/auth/protected.page.d.ts +3 -0
- package/dist/test/fixtures/routes/auth/protected.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/index.page.d.ts +3 -0
- package/dist/test/fixtures/routes/index.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/oml.page.d.ts +3 -0
- package/dist/test/fixtures/routes/oml.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/redirect.page.d.ts +3 -0
- package/dist/test/fixtures/routes/redirect.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/ssg/[slug].page.d.ts +5 -0
- package/dist/test/fixtures/routes/ssg/[slug].page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/ssg/server.page.d.ts +4 -0
- package/dist/test/fixtures/routes/ssg/server.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/state.page.d.ts +3 -0
- package/dist/test/fixtures/routes/state.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/throw.page.d.ts +3 -0
- package/dist/test/fixtures/routes/throw.page.d.ts.map +1 -0
- package/dist/test/fixtures/routes/wiki/[...slug].page.d.ts +3 -0
- package/dist/test/fixtures/routes/wiki/[...slug].page.d.ts.map +1 -0
- package/dist/test/helpers.d.ts +37 -0
- package/dist/test/helpers.d.ts.map +1 -0
- package/dist/test/layouts.test.d.ts +2 -0
- package/dist/test/layouts.test.d.ts.map +1 -0
- package/dist/test/middleware.test.d.ts +2 -0
- package/dist/test/middleware.test.d.ts.map +1 -0
- package/dist/test/multipart.test.d.ts +2 -0
- package/dist/test/multipart.test.d.ts.map +1 -0
- package/dist/test/oml-routing.test.d.ts +2 -0
- package/dist/test/oml-routing.test.d.ts.map +1 -0
- package/dist/test/oml.test.d.ts +2 -0
- package/dist/test/oml.test.d.ts.map +1 -0
- package/dist/test/redirects.test.d.ts +2 -0
- package/dist/test/redirects.test.d.ts.map +1 -0
- package/dist/test/routing.test.d.ts +2 -0
- package/dist/test/routing.test.d.ts.map +1 -0
- package/dist/test/ssg.test.d.ts +2 -0
- package/dist/test/ssg.test.d.ts.map +1 -0
- package/dist/test/web-response.test.d.ts +2 -0
- package/dist/test/web-response.test.d.ts.map +1 -0
- package/dist/types.d.ts +314 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +292 -0
- package/dist/types.js.map +7 -0
- package/package.json +103 -0
- package/pka.config.json +32 -0
- package/src/build/config.ts +42 -0
- package/src/build/index.ts +6 -0
- package/src/build/plugins.ts +118 -0
- package/src/cli.ts +502 -0
- package/src/config.ts +197 -0
- package/src/create-multisite.ts +310 -0
- package/src/create.ts +194 -0
- package/src/dev/blueprints.ts +75 -0
- package/src/dev/components.ts +108 -0
- package/src/dev/insert.ts +221 -0
- package/src/dev/remove.ts +677 -0
- package/src/dev/watch.ts +3098 -0
- package/src/env.d.ts +5 -0
- package/src/errors.ts +64 -0
- package/src/generate.ts +228 -0
- package/src/index.ts +67 -0
- package/src/island.ts +47 -0
- package/src/jsx-runtime.d.ts +408 -0
- package/src/jsx-runtime.d.ts.map +1 -0
- package/src/jsx-runtime.ts +536 -0
- package/src/link.ts +49 -0
- package/src/oml/fragment.ts +54 -0
- package/src/oml/index.ts +21 -0
- package/src/oml/jsx-runtime.ts +121 -0
- package/src/oml/jsx.ts +151 -0
- package/src/oml/page.ts +13 -0
- package/src/oml/render.ts +181 -0
- package/src/oml/types.ts +159 -0
- package/src/router/handler.ts +515 -0
- package/src/router/matcher.ts +52 -0
- package/src/router/scanner.ts +272 -0
- package/src/server/index.ts +49 -0
- package/src/signal.ts +39 -0
- package/src/ssg.ts +253 -0
- package/src/test/actions.test.ts +40 -0
- package/src/test/body-limits.test.ts +83 -0
- package/src/test/errors.test.ts +53 -0
- package/src/test/fixtures/routes/[id].page.ts +3 -0
- package/src/test/fixtures/routes/_error.ts +6 -0
- package/src/test/fixtures/routes/_global.ts +8 -0
- package/src/test/fixtures/routes/_layout-template.ts +7 -0
- package/src/test/fixtures/routes/_layout.ts +7 -0
- package/src/test/fixtures/routes/_layout_scripts.ts +8 -0
- package/src/test/fixtures/routes/_middleware.ts +8 -0
- package/src/test/fixtures/routes/_redirect301_mw.ts +5 -0
- package/src/test/fixtures/routes/_redirect_mw.ts +5 -0
- package/src/test/fixtures/routes/about.page.ts +6 -0
- package/src/test/fixtures/routes/action.page.ts +11 -0
- package/src/test/fixtures/routes/api/form-all.post.ts +5 -0
- package/src/test/fixtures/routes/api/form-limited.post.ts +6 -0
- package/src/test/fixtures/routes/api/response-obj.get.ts +17 -0
- package/src/test/fixtures/routes/api/upload.post.ts +14 -0
- package/src/test/fixtures/routes/api/users.get.ts +3 -0
- package/src/test/fixtures/routes/api/xml.get.ts +5 -0
- package/src/test/fixtures/routes/auth/_middleware.ts +11 -0
- package/src/test/fixtures/routes/auth/protected.page.ts +3 -0
- package/src/test/fixtures/routes/index.page.ts +3 -0
- package/src/test/fixtures/routes/oml.page.ts +7 -0
- package/src/test/fixtures/routes/redirect.page.ts +3 -0
- package/src/test/fixtures/routes/ssg/[slug].page.ts +8 -0
- package/src/test/fixtures/routes/ssg/server.page.ts +5 -0
- package/src/test/fixtures/routes/state.page.ts +4 -0
- package/src/test/fixtures/routes/throw.page.ts +5 -0
- package/src/test/fixtures/routes/wiki/[...slug].page.ts +3 -0
- package/src/test/helpers.ts +132 -0
- package/src/test/layouts.test.ts +76 -0
- package/src/test/middleware.test.ts +69 -0
- package/src/test/multipart.test.ts +91 -0
- package/src/test/oml-routing.test.ts +59 -0
- package/src/test/oml.test.ts +429 -0
- package/src/test/redirects.test.ts +32 -0
- package/src/test/routing.test.ts +118 -0
- package/src/test/ssg.test.ts +273 -0
- package/src/test/web-response.test.ts +33 -0
- package/src/types.ts +670 -0
- package/tsconfig.client.json +17 -0
- package/tsconfig.json +20 -0
package/src/dev/watch.ts
ADDED
|
@@ -0,0 +1,3098 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import { createReadStream, existsSync, watch as fsWatch, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
import { extname, join, relative } from 'node:path'
|
|
6
|
+
import { context, type Plugin } from 'esbuild'
|
|
7
|
+
import { cssCollectorPlugin, generateIslandsEntry, islandServerPlugin } from '../build/plugins.js'
|
|
8
|
+
import {
|
|
9
|
+
collectEsbuildPlugins,
|
|
10
|
+
collectScannerSuffixes,
|
|
11
|
+
type DavauxPlugin,
|
|
12
|
+
pathsToAlias,
|
|
13
|
+
} from '../config.js'
|
|
14
|
+
import { formatBuildErrors } from '../errors.js'
|
|
15
|
+
import type { OmlPropSchema } from '../oml/types.js'
|
|
16
|
+
import { buildApp } from '../router/handler.js'
|
|
17
|
+
import { scanIslands, scanRoutes } from '../router/scanner.js'
|
|
18
|
+
import { startServer } from '../server/index.js'
|
|
19
|
+
import type { IslandFile, ScanResult } from '../types.js'
|
|
20
|
+
import { scanBlueprints } from './blueprints.js'
|
|
21
|
+
import { saveBlueprint, scanComponents, updateBlueprint } from './components.js'
|
|
22
|
+
import { insertAfterElement, insertJsx } from './insert.js'
|
|
23
|
+
import { getComponentFragment, getElementFragment, moveNode, removeElement, removeJsx, replaceComponentFragment, replaceElementAttrs, replaceElementFragment, replaceJsx, replaceTextContent } from './remove.js'
|
|
24
|
+
|
|
25
|
+
const MIME: Record<string, string> = {
|
|
26
|
+
'.js': 'application/javascript',
|
|
27
|
+
'.mjs': 'application/javascript',
|
|
28
|
+
'.css': 'text/css',
|
|
29
|
+
'.html': 'text/html',
|
|
30
|
+
'.json': 'application/json',
|
|
31
|
+
'.png': 'image/png',
|
|
32
|
+
'.jpg': 'image/jpeg',
|
|
33
|
+
'.jpeg': 'image/jpeg',
|
|
34
|
+
'.gif': 'image/gif',
|
|
35
|
+
'.svg': 'image/svg+xml',
|
|
36
|
+
'.ico': 'image/x-icon',
|
|
37
|
+
'.woff': 'font/woff',
|
|
38
|
+
'.woff2': 'font/woff2',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let _partialUpdatesScript: string | null = null
|
|
42
|
+
function getPartialUpdatesScript(): string {
|
|
43
|
+
if (_partialUpdatesScript !== null) return _partialUpdatesScript
|
|
44
|
+
try {
|
|
45
|
+
const r = createRequire(import.meta.url)
|
|
46
|
+
const templateForCjs = r.resolve('template-for-polyfill')
|
|
47
|
+
const templateForJs = readFileSync(
|
|
48
|
+
templateForCjs.replace(/template-for-polyfill\.cjs$/, 'template-for-polyfill.js'),
|
|
49
|
+
'utf-8',
|
|
50
|
+
)
|
|
51
|
+
const htmlSettersMain = r.resolve('html-setters-polyfill')
|
|
52
|
+
const htmlSettersJs = readFileSync(
|
|
53
|
+
htmlSettersMain.replace(/index\.js$/, 'index.min.js'),
|
|
54
|
+
'utf-8',
|
|
55
|
+
)
|
|
56
|
+
_partialUpdatesScript = `${templateForJs}\n${htmlSettersJs}`
|
|
57
|
+
} catch {
|
|
58
|
+
_partialUpdatesScript = '/* partial-updates polyfills unavailable */'
|
|
59
|
+
}
|
|
60
|
+
return _partialUpdatesScript
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function serveStatic(
|
|
64
|
+
publicDir: string,
|
|
65
|
+
): ((req: IncomingMessage, res: ServerResponse) => boolean) | null {
|
|
66
|
+
if (!existsSync(publicDir)) return null
|
|
67
|
+
return (req, res): boolean => {
|
|
68
|
+
const filePath = join(publicDir, new URL(req.url ?? '/', 'http://x').pathname)
|
|
69
|
+
if (!filePath.startsWith(`${publicDir}/`) && filePath !== publicDir) return false
|
|
70
|
+
if (!existsSync(filePath) || !statSync(filePath).isFile()) return false
|
|
71
|
+
const mime = MIME[extname(filePath)] ?? 'application/octet-stream'
|
|
72
|
+
res.writeHead(200, { 'Content-Type': mime })
|
|
73
|
+
createReadStream(filePath).pipe(res)
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Live reload (SSE) ────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
const sseClients = new Set<ServerResponse>()
|
|
81
|
+
let serverReady = false
|
|
82
|
+
let reloadTimer: ReturnType<typeof setTimeout> | undefined
|
|
83
|
+
|
|
84
|
+
function sendToClients(data: string): void {
|
|
85
|
+
for (const client of sseClients) {
|
|
86
|
+
try {
|
|
87
|
+
client.write(`data: ${data}\n\n`)
|
|
88
|
+
} catch {
|
|
89
|
+
sseClients.delete(client)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function scheduleReload(): void {
|
|
95
|
+
if (!serverReady) return
|
|
96
|
+
if (reloadTimer) clearTimeout(reloadTimer)
|
|
97
|
+
reloadTimer = setTimeout(() => {
|
|
98
|
+
reloadTimer = undefined
|
|
99
|
+
sendToClients('reload')
|
|
100
|
+
}, 50)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// SharedWorker script: one SSE connection per origin, fans messages to all tab ports.
|
|
104
|
+
// Falls back to a per-tab EventSource when SharedWorker is unavailable.
|
|
105
|
+
const SHARED_WORKER_SCRIPT = `var ports=[];var source=null;
|
|
106
|
+
self.onconnect=function(ev){
|
|
107
|
+
var port=ev.ports[0];port.start();ports.push(port);
|
|
108
|
+
if(!source){
|
|
109
|
+
source=new EventSource('/_davaux/livereload');
|
|
110
|
+
source.onmessage=function(e){
|
|
111
|
+
ports=ports.filter(function(p){try{p.postMessage(e.data);return true}catch(_){return false}});
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
};`
|
|
115
|
+
|
|
116
|
+
const LIVERELOAD_SCRIPT = `;(function(){
|
|
117
|
+
var overlay=null
|
|
118
|
+
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
119
|
+
function show(errors){
|
|
120
|
+
if(!overlay){
|
|
121
|
+
overlay=document.createElement('div')
|
|
122
|
+
overlay.style.cssText='position:fixed;inset:0;z-index:99999;overflow:auto;background:#0d0d0d;color:#e8e8e8;font:13px/1.6 monospace;padding:2rem;box-sizing:border-box'
|
|
123
|
+
document.body.appendChild(overlay)
|
|
124
|
+
}
|
|
125
|
+
overlay.innerHTML='<p style="color:#ff5555;font-size:1.1em;margin:0 0 1.5rem 0"><b>[davaux] Build Error</b></p>'+
|
|
126
|
+
errors.map(function(e){
|
|
127
|
+
var loc=e.file?(e.file+(e.line?':'+e.line+':'+e.column:'')):'unknown'
|
|
128
|
+
return '<div style="margin-bottom:1.25rem;border:1px solid #ff3333;border-radius:6px;padding:1rem">'+
|
|
129
|
+
'<div style="color:#888;font-size:0.9em;margin-bottom:0.4rem">'+esc(loc)+'</div>'+
|
|
130
|
+
'<div style="color:#ff6b6b">'+esc(e.text)+'</div>'+
|
|
131
|
+
(e.lineText?'<pre style="margin:0.75rem 0 0;padding:0.5rem;background:#1a1a1a;border-radius:4px;overflow:auto;color:#aaa">'+esc(e.lineText)+'</pre>':'')+
|
|
132
|
+
'</div>'
|
|
133
|
+
}).join('')
|
|
134
|
+
}
|
|
135
|
+
function handle(data){
|
|
136
|
+
if(data==='reload'){location.reload()}
|
|
137
|
+
else if(data.slice(0,6)==='error:'){show(JSON.parse(data.slice(6)))}
|
|
138
|
+
}
|
|
139
|
+
if(typeof SharedWorker!=='undefined'){
|
|
140
|
+
var w=new SharedWorker('/_davaux/livereload-worker.js')
|
|
141
|
+
w.port.onmessage=function(ev){handle(ev.data)}
|
|
142
|
+
w.port.start()
|
|
143
|
+
} else {
|
|
144
|
+
new EventSource('/_davaux/livereload').onmessage=function(ev){handle(ev.data)}
|
|
145
|
+
}
|
|
146
|
+
})()`
|
|
147
|
+
|
|
148
|
+
const INSPECTOR_SCRIPT = `;(function(){
|
|
149
|
+
if(window!==window.top)return
|
|
150
|
+
var KEY='_dv_ins'
|
|
151
|
+
var isOpen=sessionStorage.getItem(KEY)==='1'
|
|
152
|
+
var host=document.createElement('div')
|
|
153
|
+
var shadow=host.attachShadow({mode:'open'})
|
|
154
|
+
document.body.appendChild(host)
|
|
155
|
+
var st=document.createElement('style')
|
|
156
|
+
st.textContent=':host{all:initial;display:block;position:fixed;bottom:12px;right:12px;z-index:2147483647;font:13px/1.5 ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace}:host(.open){inset:0}*{box-sizing:border-box}.badge{display:block;padding:4px 10px;background:#0f0f1a;color:#7cc5f0;border:1px solid #2a2a4a;border-radius:20px;cursor:pointer;font:700 11px/1 inherit;letter-spacing:.05em;box-shadow:0 2px 8px rgba(0,0,0,.4);white-space:nowrap}.badge:hover{background:#1a1a2e}.edbtn{display:block;margin-top:6px;padding:4px 10px;background:#1a2a1a;color:#7eca9c;border:1px solid #2a4a2a;border-radius:20px;font:700 11px/1 inherit;letter-spacing:.05em;text-decoration:none;text-align:center;box-shadow:0 2px 8px rgba(0,0,0,.4);white-space:nowrap}.edbtn:hover{background:#243a24}:host(.open) .badge,:host(.open) .edbtn{display:none}.panel{display:none;position:absolute;inset:0;background:#0f0f1a;flex-direction:column;overflow:hidden}:host(.open) .panel{display:flex}.hd{display:flex;align-items:center;gap:8px;padding:8px 12px;background:#080816;border-bottom:1px solid #2a2a4a;flex-shrink:0}.title{color:#7cc5f0;font-weight:700;font-size:11px;letter-spacing:.1em;flex:1}.chip{font-size:10px;font-weight:700;padding:2px 7px;border-radius:10px;letter-spacing:.05em;display:none}.chip.oml{display:inline;background:#1a2a1a;color:#7eca9c;border:1px solid #2a4a2a}.chip.dom{display:inline;background:#0f0f1a;color:#aaa;border:1px solid #333}.xb{background:none;border:none;color:#555;cursor:pointer;font:inherit;padding:0;line-height:1}.xb:hover{color:#e8e8e8}.bd{overflow:auto;flex:1;padding:6px 0}.row{display:flex;align-items:baseline;padding:1px 8px;white-space:nowrap;border-radius:3px;color:#e8e8e8;cursor:default;-webkit-user-select:none;user-select:none}.row.c:hover{background:#1a1a2e;cursor:pointer}.tog{width:14px;font-size:9px;color:#666;flex-shrink:0;text-align:center}.el{color:#7cc5f0}.co{color:#7eca9c}.fr{color:#666}.tx{color:#888;font-style:italic}.pk{color:#b89eff}.pv{color:#f1c27d}.empty{padding:12px;color:#555;font-style:italic;text-align:center}'
|
|
157
|
+
shadow.appendChild(st)
|
|
158
|
+
;(function(){var p=(window.__DVX__&&window.__DVX__.pos)||'bottom-right';if(p!=='bottom-right'){var ps=document.createElement('style');var css=':host:not(.open){';css+=p.indexOf('top')>-1?'top:12px;bottom:auto;':'bottom:12px;top:auto;';css+=p.indexOf('left')>-1?'left:12px;right:auto;':'right:12px;left:auto;';css+='}';ps.textContent=css;shadow.appendChild(ps)}})()
|
|
159
|
+
var badge=document.createElement('button')
|
|
160
|
+
badge.className='badge'
|
|
161
|
+
badge.textContent=(window.__DVX__&&window.__DVX__.label)||'Inspector'
|
|
162
|
+
badge.title=((window.__DVX__&&window.__DVX__.label)||'Inspector')+' (Ctrl+Shift+O)'
|
|
163
|
+
shadow.appendChild(badge)
|
|
164
|
+
var edbtn=document.createElement('a')
|
|
165
|
+
edbtn.className='edbtn'
|
|
166
|
+
edbtn.textContent='Edit'
|
|
167
|
+
edbtn.title='Visual Editor (Ctrl+Shift+E)'
|
|
168
|
+
edbtn.href='/_davaux/editor?page='+encodeURIComponent(location.pathname+location.search)
|
|
169
|
+
shadow.appendChild(edbtn)
|
|
170
|
+
var panel=document.createElement('div')
|
|
171
|
+
panel.className='panel'
|
|
172
|
+
var hd=document.createElement('div')
|
|
173
|
+
hd.className='hd'
|
|
174
|
+
var ttl=document.createElement('span')
|
|
175
|
+
ttl.className='title'
|
|
176
|
+
ttl.textContent='INSPECTOR'
|
|
177
|
+
var chip=document.createElement('span')
|
|
178
|
+
chip.className='chip'
|
|
179
|
+
var xb=document.createElement('button')
|
|
180
|
+
xb.className='xb'
|
|
181
|
+
xb.textContent='✕'
|
|
182
|
+
xb.title='Close'
|
|
183
|
+
hd.appendChild(ttl)
|
|
184
|
+
hd.appendChild(chip)
|
|
185
|
+
hd.appendChild(xb)
|
|
186
|
+
var bd=document.createElement('div')
|
|
187
|
+
bd.className='bd'
|
|
188
|
+
panel.appendChild(hd)
|
|
189
|
+
panel.appendChild(bd)
|
|
190
|
+
shadow.appendChild(panel)
|
|
191
|
+
function setOpen(v){
|
|
192
|
+
isOpen=!!v
|
|
193
|
+
sessionStorage.setItem(KEY,v?'1':'0')
|
|
194
|
+
host.classList.toggle('open',!!v)
|
|
195
|
+
if(v)load()
|
|
196
|
+
}
|
|
197
|
+
badge.onclick=function(){setOpen(!isOpen)}
|
|
198
|
+
xb.onclick=function(){setOpen(false)}
|
|
199
|
+
document.addEventListener('keydown',function(e){
|
|
200
|
+
if(e.ctrlKey&&e.shiftKey&&(e.key==='O'||e.key==='o')){e.preventDefault();setOpen(!isOpen)}
|
|
201
|
+
if(e.ctrlKey&&e.shiftKey&&(e.key==='E'||e.key==='e')){e.preventDefault();window.location.href='/_davaux/editor?page='+encodeURIComponent(location.pathname+location.search)}
|
|
202
|
+
})
|
|
203
|
+
if(isOpen)setOpen(true)
|
|
204
|
+
function setMode(mode){
|
|
205
|
+
badge.textContent=mode
|
|
206
|
+
chip.textContent=mode
|
|
207
|
+
chip.className='chip '+mode.toLowerCase()
|
|
208
|
+
}
|
|
209
|
+
function load(){
|
|
210
|
+
bd.innerHTML='<div class="empty">Loading…</div>'
|
|
211
|
+
fetch('/_davaux/inspector?url='+encodeURIComponent(location.pathname+location.search))
|
|
212
|
+
.then(function(r){return r.json()})
|
|
213
|
+
.then(function(d){
|
|
214
|
+
bd.innerHTML=''
|
|
215
|
+
if(d.error){
|
|
216
|
+
setMode('DOM')
|
|
217
|
+
var bodySnap=domSnap(document.body)
|
|
218
|
+
if(bodySnap)bd.appendChild(tree(bodySnap,0))
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
setMode('OML')
|
|
222
|
+
bd.appendChild(tree(d.node,0))
|
|
223
|
+
})
|
|
224
|
+
.catch(function(){bd.innerHTML='<div class="empty">No tree available</div>'})
|
|
225
|
+
}
|
|
226
|
+
function domSnap(el){
|
|
227
|
+
if(el===host)return null
|
|
228
|
+
if(el.nodeType===3){var v=(el.textContent||'').trim();return v?{type:'#text',value:v}:null}
|
|
229
|
+
if(el.nodeType!==1)return null
|
|
230
|
+
var tag=el.tagName.toLowerCase()
|
|
231
|
+
var p={};for(var i=0;i<el.attributes.length;i++){var a=el.attributes[i];p[a.name]=a.value}
|
|
232
|
+
var cs=[];for(var j=0;j<el.childNodes.length;j++){var c=domSnap(el.childNodes[j]);if(c!==null)cs.push(c)}
|
|
233
|
+
return{type:'element',tag:tag,id:undefined,props:p,children:cs}
|
|
234
|
+
}
|
|
235
|
+
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
236
|
+
function propHtml(p){
|
|
237
|
+
if(!p)return''
|
|
238
|
+
var ks=Object.keys(p).filter(function(k){return!/^on[A-Z]/.test(k)&&k!=='children'&&k!=='dangerouslySetInnerHTML'})
|
|
239
|
+
var out=ks.slice(0,3).map(function(k){
|
|
240
|
+
var v=p[k],s=typeof v==='string'?'"'+(v.length>18?v.slice(0,18)+'…':esc(v))+'"':esc(String(v))
|
|
241
|
+
return' <span class="pk">'+esc(k)+'</span>=<span class="pv">'+s+'</span>'
|
|
242
|
+
}).join('')
|
|
243
|
+
if(ks.length>3)out+=' <span style="color:#555">+'+(ks.length-3)+'</span>'
|
|
244
|
+
return out
|
|
245
|
+
}
|
|
246
|
+
function tree(node,depth){
|
|
247
|
+
var w=document.createElement('div')
|
|
248
|
+
var pad=(depth*14+8)+'px'
|
|
249
|
+
if(node===null){
|
|
250
|
+
var r=document.createElement('div')
|
|
251
|
+
r.className='row'
|
|
252
|
+
r.style.paddingLeft=pad
|
|
253
|
+
r.innerHTML='<span class="tog"> </span><span class="fr">null</span>'
|
|
254
|
+
w.appendChild(r)
|
|
255
|
+
return w
|
|
256
|
+
}
|
|
257
|
+
if(node.type==='#text'){
|
|
258
|
+
var v=String(node.value||'')
|
|
259
|
+
var r=document.createElement('div')
|
|
260
|
+
r.className='row'
|
|
261
|
+
r.style.paddingLeft=pad
|
|
262
|
+
r.innerHTML='<span class="tog"> </span><span class="tx">"'+(v.length>60?esc(v.slice(0,60))+'…':esc(v))+'"</span>'
|
|
263
|
+
w.appendChild(r)
|
|
264
|
+
return w
|
|
265
|
+
}
|
|
266
|
+
if(node.type==='#raw'){
|
|
267
|
+
var v=String(node.value||'')
|
|
268
|
+
var r=document.createElement('div')
|
|
269
|
+
r.className='row'
|
|
270
|
+
r.style.paddingLeft=pad
|
|
271
|
+
r.innerHTML='<span class="tog"> </span><span class="fr">[raw html] </span><span class="tx">'+(v.length>60?esc(v.slice(0,60))+'…':esc(v))+'</span>'
|
|
272
|
+
w.appendChild(r)
|
|
273
|
+
return w
|
|
274
|
+
}
|
|
275
|
+
var cs=node.type==='#component'
|
|
276
|
+
?(node.output&&node.output.type!=='#raw'?[node.output]:(node.children||[]).filter(function(c){return c!==null}))
|
|
277
|
+
:(node.children||[]).filter(function(c){return c!==null})
|
|
278
|
+
var hk=cs.length>0
|
|
279
|
+
var closed=false
|
|
280
|
+
var row=document.createElement('div')
|
|
281
|
+
row.className='row'+(hk?' c':'')
|
|
282
|
+
row.style.paddingLeft=pad
|
|
283
|
+
var tog=document.createElement('span')
|
|
284
|
+
tog.className='tog'
|
|
285
|
+
tog.textContent=hk?'▼':' '
|
|
286
|
+
var lbl=document.createElement('span')
|
|
287
|
+
if(node.type==='element'){
|
|
288
|
+
lbl.innerHTML='<span class="el"><'+esc(node.tag)+'</span>'+propHtml(node.props)+'<span class="el">></span>'
|
|
289
|
+
}else if(node.type==='#component'){
|
|
290
|
+
lbl.innerHTML='<span class="co">◈ '+esc(node.name)+'</span>'+propHtml(node.props)
|
|
291
|
+
}else{
|
|
292
|
+
lbl.innerHTML='<span class="fr"><></span>'
|
|
293
|
+
}
|
|
294
|
+
row.appendChild(tog)
|
|
295
|
+
row.appendChild(lbl)
|
|
296
|
+
w.appendChild(row)
|
|
297
|
+
if(hk){
|
|
298
|
+
var kc=document.createElement('div')
|
|
299
|
+
cs.forEach(function(c){kc.appendChild(tree(c,depth+1))})
|
|
300
|
+
w.appendChild(kc)
|
|
301
|
+
row.onclick=function(){
|
|
302
|
+
closed=!closed
|
|
303
|
+
kc.style.display=closed?'none':''
|
|
304
|
+
tog.textContent=closed?'▶':'▼'
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return w
|
|
308
|
+
}
|
|
309
|
+
})()`
|
|
310
|
+
|
|
311
|
+
const EDITOR_HTML = `<!DOCTYPE html>
|
|
312
|
+
<html lang="en">
|
|
313
|
+
<head>
|
|
314
|
+
<meta charset="utf-8">
|
|
315
|
+
<title>Davaux Visual Editor</title>
|
|
316
|
+
<script type="importmap">
|
|
317
|
+
{
|
|
318
|
+
"imports": {
|
|
319
|
+
"https://esm.sh/@codemirror/state@^6.6.0?target=es2022": "https://esm.sh/@codemirror/state@6.6.0/es2022/state.mjs",
|
|
320
|
+
"https://esm.sh/@codemirror/state@^6.0.0?target=es2022": "https://esm.sh/@codemirror/state@6.6.0/es2022/state.mjs",
|
|
321
|
+
"https://esm.sh/@codemirror/view@^6.27.0?target=es2022": "https://esm.sh/@codemirror/view@6.43.0/es2022/view.mjs",
|
|
322
|
+
"https://esm.sh/@codemirror/view@^6.23.0?target=es2022": "https://esm.sh/@codemirror/view@6.43.0/es2022/view.mjs",
|
|
323
|
+
"https://esm.sh/@codemirror/language@^6.0.0?target=es2022": "https://esm.sh/@codemirror/language@6.12.3/es2022/language.mjs",
|
|
324
|
+
"https://esm.sh/@codemirror/language@^6.3.0?target=es2022": "https://esm.sh/@codemirror/language@6.12.3/es2022/language.mjs",
|
|
325
|
+
"https://esm.sh/@codemirror/autocomplete@^6.7.1?target=es2022": "https://esm.sh/@codemirror/autocomplete@6.20.2/es2022/autocomplete.mjs",
|
|
326
|
+
"https://esm.sh/@codemirror/lang-html@^6.0.0?target=es2022": "https://esm.sh/@codemirror/lang-html@6.4.11/es2022/lang-html.mjs",
|
|
327
|
+
"https://esm.sh/@lezer/common@^1.1.0?target=es2022": "https://esm.sh/@lezer/common@1.5.2/es2022/common.mjs",
|
|
328
|
+
"https://esm.sh/@lezer/common@^1.5.0?target=es2022": "https://esm.sh/@lezer/common@1.5.2/es2022/common.mjs",
|
|
329
|
+
"https://esm.sh/@lezer/highlight@^1.0.0?target=es2022": "https://esm.sh/@lezer/highlight@1.2.3/es2022/highlight.mjs",
|
|
330
|
+
"https://esm.sh/crelt@^1.0.6?target=es2022": "https://esm.sh/crelt@1.0.6/es2022/crelt.mjs"
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
</script>
|
|
334
|
+
<style>
|
|
335
|
+
:root{
|
|
336
|
+
--dvx-bg:#0d0d1a;--dvx-surf:#0f0f1a;--dvx-hd:#080816;--dvx-inp:#1a1a2e;
|
|
337
|
+
--dvx-bdr:#2a2a4a;--dvx-bdr-hi:#4a4a8a;
|
|
338
|
+
--dvx-txt:#e8e8e8;--dvx-txt-dim:#aaa;--dvx-txt-faint:#666;--dvx-txt-ghost:#555;
|
|
339
|
+
--dvx-row-hover:#1a1a2e;--dvx-row-sel:#1a2a3a;
|
|
340
|
+
}
|
|
341
|
+
[data-dvx-theme="light"]{
|
|
342
|
+
--dvx-bg:#f8f9fa;--dvx-surf:#ffffff;--dvx-hd:#e9ecef;--dvx-inp:#f1f3f5;
|
|
343
|
+
--dvx-bdr:#ced4da;--dvx-bdr-hi:#4c6ef5;
|
|
344
|
+
--dvx-txt:#212529;--dvx-txt-dim:#343a40;--dvx-txt-faint:#495057;--dvx-txt-ghost:#6c757d;
|
|
345
|
+
--dvx-row-hover:#f1f3f5;--dvx-row-sel:#dbe4ff;
|
|
346
|
+
}
|
|
347
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
348
|
+
body{display:flex;flex-direction:column;height:100vh;background:var(--dvx-bg);color:var(--dvx-txt);font:13px/1.5 ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace}
|
|
349
|
+
header{display:flex;align-items:center;gap:12px;padding:8px 16px;background:var(--dvx-hd);border-bottom:1px solid var(--dvx-bdr);flex-shrink:0}
|
|
350
|
+
.logo{color:#7cc5f0;font-weight:700;font-size:11px;letter-spacing:.1em}
|
|
351
|
+
.page-bar{display:flex;align-items:center;gap:8px;flex:1}
|
|
352
|
+
.page-bar span{color:var(--dvx-txt-faint);font-size:11px}
|
|
353
|
+
.pg-wrap{position:relative;display:inline-flex;align-items:center}
|
|
354
|
+
.page-input{background:var(--dvx-inp);border:1px solid var(--dvx-bdr);color:var(--dvx-txt);padding:3px 22px 3px 8px;border-radius:4px;font:inherit;width:240px}
|
|
355
|
+
.page-input:focus{outline:none;border-color:var(--dvx-bdr-hi)}
|
|
356
|
+
.pg-clear{position:absolute;right:6px;background:none;border:none;color:var(--dvx-txt-ghost);cursor:pointer;font:inherit;font-size:13px;line-height:1;padding:0;display:none}
|
|
357
|
+
.pg-clear:hover{color:var(--dvx-txt-dim)}
|
|
358
|
+
.hbtn{background:#1a2a3a;border:1px solid #2a4a6a;color:#7cc5f0;padding:3px 10px;border-radius:4px;cursor:pointer;font:inherit;text-decoration:none;display:inline-block}
|
|
359
|
+
.hbtn:hover{background:#243a4a}
|
|
360
|
+
.main{display:flex;flex:1;overflow:hidden}
|
|
361
|
+
.palette{width:260px;flex-shrink:0;background:var(--dvx-surf);border-right:1px solid var(--dvx-bdr);display:flex;flex-direction:column;overflow:hidden}
|
|
362
|
+
.section-hd{color:var(--dvx-txt-faint);font-size:10px;font-weight:700;letter-spacing:.1em;padding:4px 4px 8px;flex-shrink:0}
|
|
363
|
+
.bp-card{background:var(--dvx-inp);border:1px solid var(--dvx-bdr);border-radius:6px;margin-bottom:6px;cursor:pointer;overflow:hidden;flex-shrink:0}
|
|
364
|
+
.bp-card:hover{border-color:var(--dvx-bdr-hi)}
|
|
365
|
+
.bp-card.sel{border-color:#7cc5f0}
|
|
366
|
+
.bp-prev{padding:8px;min-height:36px;background:var(--dvx-surf);overflow:hidden;max-height:72px;font-size:12px}
|
|
367
|
+
.bp-lbl{padding:5px 8px;font-size:11px;font-weight:700;color:#7eca9c;border-top:1px solid var(--dvx-bdr)}
|
|
368
|
+
.empty{color:var(--dvx-txt-ghost);font-style:italic;padding:8px 4px;font-size:11px}
|
|
369
|
+
.center{flex:1;background:var(--dvx-bg);display:flex;flex-direction:column;overflow:hidden}
|
|
370
|
+
.ctoolbar{display:flex;align-items:center;gap:4px;padding:4px 8px;background:var(--dvx-hd);border-bottom:1px solid var(--dvx-bdr);flex-shrink:0}
|
|
371
|
+
.tool-btn{background:none;border:1px solid transparent;color:var(--dvx-txt-ghost);cursor:pointer;font:700 10px/1 inherit;letter-spacing:.05em;padding:4px 10px;border-radius:4px}
|
|
372
|
+
.tool-btn:hover{color:var(--dvx-txt-dim);border-color:var(--dvx-bdr)}
|
|
373
|
+
.tool-btn.active{background:var(--dvx-inp);color:#7cc5f0;border-color:var(--dvx-bdr-hi)}
|
|
374
|
+
.center iframe{flex:1;width:100%;border:none;background:#fff}
|
|
375
|
+
.details{width:260px;flex-shrink:0;background:var(--dvx-surf);border-left:1px solid var(--dvx-bdr);overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:0}
|
|
376
|
+
.d-title{color:#7cc5f0;font-weight:700;font-size:13px;margin-bottom:12px}
|
|
377
|
+
.d-lbl{color:var(--dvx-txt-faint);font-size:10px;font-weight:700;letter-spacing:.1em;margin:12px 0 6px}
|
|
378
|
+
.prop-row{margin-bottom:4px;font-size:12px}
|
|
379
|
+
.pn{color:#b89eff}
|
|
380
|
+
.pt{color:#888}
|
|
381
|
+
.pr{color:#ff6b6b;font-size:10px}
|
|
382
|
+
.jsx-block{background:var(--dvx-inp);border:1px solid var(--dvx-bdr);border-radius:4px;padding:8px;white-space:pre;overflow-x:auto;font-size:11px;color:var(--dvx-txt);margin-bottom:8px}
|
|
383
|
+
.cbtn{background:#1a3a1a;border:1px solid #2a6a2a;color:#7eca9c;padding:4px 12px;border-radius:4px;cursor:pointer;font:inherit;width:100%}
|
|
384
|
+
.cbtn:hover{background:#243a24}
|
|
385
|
+
.cbtn.ok{background:#1a4a1a;color:#5ef05e}
|
|
386
|
+
.ibtn{background:#1a2a3a;border-color:#2a4a6a;color:#7cc5f0;margin-top:4px}
|
|
387
|
+
.ibtn:hover{background:#243a4a}
|
|
388
|
+
.ins-st{font-size:10px;margin-top:5px;min-height:14px;color:#888;word-break:break-all}
|
|
389
|
+
.ins-st.ok{color:#7eca9c}
|
|
390
|
+
.ins-st.warn{color:#f5a623}
|
|
391
|
+
.ins-st.err{color:#ff8888}
|
|
392
|
+
.no-sel{color:var(--dvx-txt-ghost);font-style:italic;padding:8px 0;font-size:12px}
|
|
393
|
+
.src-ed{width:100%;font:12px/1.6 ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;background:var(--dvx-hd);border:1px solid var(--dvx-bdr);color:var(--dvx-txt);padding:12px;tab-size:2;white-space:pre;resize:none}
|
|
394
|
+
.src-ed:focus{outline:none;border-color:var(--dvx-bdr-hi)}
|
|
395
|
+
.src-file-lbl{font-size:10px;color:var(--dvx-txt-ghost);word-break:break-all;padding:0 2px}
|
|
396
|
+
.src-modal{position:fixed;inset:0;z-index:1000;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center}
|
|
397
|
+
.src-modal-box{display:flex;flex-direction:column;width:80vw;height:82vh;background:var(--dvx-surf);border:1px solid var(--dvx-bdr);border-radius:8px;overflow:hidden;box-shadow:0 8px 40px rgba(0,0,0,.4)}
|
|
398
|
+
.src-modal-hd{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--dvx-hd);border-bottom:1px solid var(--dvx-bdr);flex-shrink:0}
|
|
399
|
+
.src-modal-hd .cbtn{width:auto;flex-shrink:0}
|
|
400
|
+
.src-modal-hd #src-modal-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;word-break:normal}
|
|
401
|
+
.src-editor-area{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}
|
|
402
|
+
.src-modal-ta{flex:1;border-radius:0;border:none;border-top:1px solid var(--dvx-bdr);padding:12px 16px;min-height:0;background:var(--dvx-surf);color:var(--dvx-txt)}
|
|
403
|
+
#src-cm-host{flex:1;min-height:0;overflow:hidden;display:none;border-top:1px solid var(--dvx-bdr)}
|
|
404
|
+
#src-cm-host .cm-editor{height:100%!important;background:var(--dvx-bg)}
|
|
405
|
+
#src-cm-host .cm-scroller{overflow:auto!important}
|
|
406
|
+
.src-modal-ft{display:flex;align-items:center;gap:8px;padding:4px 8px;background:var(--dvx-hd);border-top:1px solid var(--dvx-bdr);flex-shrink:0}
|
|
407
|
+
.src-modal-ft .ins-st{flex:1;margin:0}
|
|
408
|
+
.xbtn{background:none;border:none;color:var(--dvx-txt-ghost);cursor:pointer;font:inherit;font-size:14px;line-height:1;padding:0 2px}.xbtn:hover{color:var(--dvx-txt)}
|
|
409
|
+
.pi-row{margin-bottom:6px;display:flex;flex-direction:column;gap:2px}
|
|
410
|
+
.pi-lbl{font-size:11px}.pi-lbl .pn{color:#b89eff}.pi-lbl .pt{color:var(--dvx-txt-faint)}.pi-lbl .pr{color:#ff6b6b}
|
|
411
|
+
.pi{background:var(--dvx-inp);border:1px solid var(--dvx-bdr);color:var(--dvx-txt);padding:3px 6px;border-radius:3px;font:inherit;font-size:11px;width:100%}
|
|
412
|
+
.pi:focus{outline:none;border-color:var(--dvx-bdr-hi)}
|
|
413
|
+
.pi-cb{accent-color:#7cc5f0;width:14px;height:14px;cursor:pointer}
|
|
414
|
+
.el-chip{display:inline-block;background:#1a2a3a;border:1px solid #2a4a6a;border-radius:4px;padding:2px 7px;font-size:11px;color:#7cc5f0;margin-bottom:8px}
|
|
415
|
+
.comp-chip{display:inline-block;background:#1a2a1a;border:1px solid #2a4a2a;border-radius:4px;padding:2px 7px;font-size:11px;color:#7eca9c;margin-bottom:4px}
|
|
416
|
+
.det-tabs{display:flex;border-bottom:1px solid var(--dvx-bdr);flex-shrink:0;margin-bottom:4px}
|
|
417
|
+
.dtab{background:none;border:none;border-bottom:2px solid transparent;color:var(--dvx-txt-faint);cursor:pointer;font:700 10px/1 inherit;letter-spacing:.1em;padding:7px 12px;flex:1}
|
|
418
|
+
.dtab.active{color:#7cc5f0;border-bottom-color:#7cc5f0}
|
|
419
|
+
.dtab:hover:not(.active){color:var(--dvx-txt-dim)}
|
|
420
|
+
.cv-row{margin-bottom:10px}
|
|
421
|
+
.cv-name{color:#b89eff;font-size:10px;margin-bottom:3px;word-break:break-all}
|
|
422
|
+
.cv-color-row{display:flex;gap:4px;align-items:center}
|
|
423
|
+
.cv-color{width:32px;height:24px;padding:1px 2px;border:1px solid var(--dvx-bdr);border-radius:3px;background:var(--dvx-inp);cursor:pointer;flex-shrink:0}
|
|
424
|
+
.cv-text{background:var(--dvx-inp);border:1px solid var(--dvx-bdr);color:var(--dvx-txt);padding:3px 6px;border-radius:3px;font:inherit;font-size:11px;width:100%;min-width:0}
|
|
425
|
+
.cv-text:focus{outline:none;border-color:var(--dvx-bdr-hi)}
|
|
426
|
+
.cv-color-row .cv-text{flex:1;width:auto}
|
|
427
|
+
.pal-tabs{display:flex;border-bottom:1px solid var(--dvx-bdr);flex-shrink:0}
|
|
428
|
+
.ptab{background:none;border:none;border-bottom:2px solid transparent;color:var(--dvx-txt-faint);cursor:pointer;font:700 10px/1 inherit;letter-spacing:.1em;padding:7px 8px;flex:1}
|
|
429
|
+
.ptab.active{color:#7cc5f0;border-bottom-color:#7cc5f0}
|
|
430
|
+
.ptab:hover:not(.active){color:var(--dvx-txt-dim)}
|
|
431
|
+
.pal-panel{display:none;flex:1;overflow-y:auto;padding:8px;flex-direction:column}
|
|
432
|
+
.pal-panel.active{display:flex}
|
|
433
|
+
.conv-hd{display:flex;gap:4px;margin-bottom:8px;align-items:center;flex-shrink:0}
|
|
434
|
+
.conv-dir{background:var(--dvx-inp);border:1px solid var(--dvx-bdr);color:var(--dvx-txt);padding:3px 6px;border-radius:3px;font:inherit;font-size:11px;flex:1;min-width:0}
|
|
435
|
+
.conv-dir:focus{outline:none;border-color:var(--dvx-bdr-hi)}
|
|
436
|
+
.conv-go{background:#1a2a3a;border:1px solid #2a4a6a;color:#7cc5f0;padding:3px 8px;border-radius:3px;cursor:pointer;font:inherit;font-size:11px;flex-shrink:0}
|
|
437
|
+
.conv-go:hover{background:#243a4a}
|
|
438
|
+
.conv-item{background:var(--dvx-inp);border:1px solid var(--dvx-bdr);border-radius:4px;padding:6px 8px;margin-bottom:4px;flex-shrink:0}
|
|
439
|
+
.conv-name{color:#7eca9c;font-weight:700;font-size:11px;margin-bottom:2px}
|
|
440
|
+
.conv-meta{color:var(--dvx-txt-ghost);font-size:10px;margin-bottom:4px;word-break:break-all}
|
|
441
|
+
.conv-btn{background:#1a3a1a;border:1px solid #2a5a2a;color:#7eca9c;padding:2px 8px;border-radius:3px;cursor:pointer;font:inherit;font-size:10px;letter-spacing:.05em}
|
|
442
|
+
.conv-btn:hover:not([disabled]){background:#243a24}
|
|
443
|
+
.conv-btn.done{background:#1a4a1a;color:#5ef05e;border-color:#2a6a2a;cursor:default}
|
|
444
|
+
.conv-btn.err{background:#3a1a1a;color:#ff8888;border-color:#5a2a2a}
|
|
445
|
+
.conv-acts{display:flex;gap:4px;align-items:center}
|
|
446
|
+
.conv-exists{font-size:10px;color:#5ef05e;flex:1}
|
|
447
|
+
.conv-upd{background:#1a2a3a;border:1px solid #2a4a6a;color:#7cc5f0}
|
|
448
|
+
.conv-upd:hover:not([disabled]){background:#243a4a}
|
|
449
|
+
.ebtn{background:#1a2a3a;border:1px solid #2a4a6a;color:#7cc5f0}
|
|
450
|
+
.ebtn:hover{background:#243a4a}
|
|
451
|
+
.ep-row{display:flex;gap:3px;align-items:center;margin-bottom:3px}
|
|
452
|
+
.ep-name{width:68px;flex-shrink:0}
|
|
453
|
+
.ep-type{width:64px;flex-shrink:0;background:var(--dvx-inp);border:1px solid var(--dvx-bdr);color:var(--dvx-txt);padding:3px 4px;border-radius:3px;font:inherit;font-size:11px}
|
|
454
|
+
.ep-def{flex:1;min-width:0}
|
|
455
|
+
.ep-req-lbl{display:flex;align-items:center;gap:2px;font-size:10px;color:var(--dvx-txt-faint);flex-shrink:0;cursor:pointer}
|
|
456
|
+
.ep-rm{background:none;border:none;color:var(--dvx-txt-ghost);cursor:pointer;font:inherit;font-size:11px;padding:0 2px;flex-shrink:0;line-height:1}
|
|
457
|
+
.ep-rm:hover{color:#ff8888}
|
|
458
|
+
.tr-row{display:flex;align-items:center;gap:3px;padding:2px 4px;border-radius:3px;cursor:pointer;user-select:none;min-width:0}
|
|
459
|
+
.tr-row:hover{background:var(--dvx-row-hover)}
|
|
460
|
+
.tr-row.tr-sel{background:var(--dvx-row-sel)}
|
|
461
|
+
.tr-tog{width:12px;flex-shrink:0;text-align:center;font-size:9px;color:var(--dvx-txt-ghost);cursor:pointer;padding-top:1px}
|
|
462
|
+
.tr-tog:hover{color:var(--dvx-txt-dim)}
|
|
463
|
+
.tr-lbl{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:11px}
|
|
464
|
+
.tr-tag{color:#7cc5f0}
|
|
465
|
+
.tr-comp{color:#7eca9c}
|
|
466
|
+
.tr-frag{color:var(--dvx-txt-ghost)}
|
|
467
|
+
.tr-txt{color:var(--dvx-txt-ghost);font-style:italic;font-size:10px}
|
|
468
|
+
</style>
|
|
469
|
+
</head>
|
|
470
|
+
<body>
|
|
471
|
+
<header>
|
|
472
|
+
<div class="logo">♢ DAVAUX EDITOR</div>
|
|
473
|
+
<div class="page-bar">
|
|
474
|
+
<span>Page:</span>
|
|
475
|
+
<div class="pg-wrap">
|
|
476
|
+
<input id="pg" class="page-input" value="/" list="route-list" autocomplete="off" />
|
|
477
|
+
<button id="pg-clear" class="pg-clear" type="button">✕</button>
|
|
478
|
+
</div>
|
|
479
|
+
<datalist id="route-list"></datalist>
|
|
480
|
+
<button class="hbtn" onclick="goPage()">Go</button>
|
|
481
|
+
</div>
|
|
482
|
+
<a id="back" href="/" class="hbtn">← Back to page</a>
|
|
483
|
+
</header>
|
|
484
|
+
<div class="main">
|
|
485
|
+
<div class="palette">
|
|
486
|
+
<div class="pal-tabs">
|
|
487
|
+
<button class="ptab active" data-pal="bps" onclick="switchPal('bps')">BLUEPRINTS</button>
|
|
488
|
+
<button class="ptab" data-pal="conv" onclick="switchPal('conv')">CONVERT</button>
|
|
489
|
+
<button class="ptab" data-pal="tree" onclick="switchPal('tree')">TREE</button>
|
|
490
|
+
</div>
|
|
491
|
+
<div id="pal-bps" class="pal-panel active">
|
|
492
|
+
<div id="bplist"><div class="empty">Loading…</div></div>
|
|
493
|
+
</div>
|
|
494
|
+
<div id="pal-tree" class="pal-panel">
|
|
495
|
+
<div id="tree-root"><div class="empty">Load a page to see the tree.</div></div>
|
|
496
|
+
</div>
|
|
497
|
+
<div id="pal-conv" class="pal-panel">
|
|
498
|
+
<div class="conv-hd">
|
|
499
|
+
<input id="conv-dir" class="conv-dir" value="src/components" title="Component folder (relative to project root)" />
|
|
500
|
+
<button class="conv-go" onclick="loadComponents()">Scan</button>
|
|
501
|
+
</div>
|
|
502
|
+
<div id="conv-list"><div class="empty">Click Scan to load components.</div></div>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
<div class="center">
|
|
506
|
+
<div class="ctoolbar">
|
|
507
|
+
<button id="sel-toggle" class="tool-btn" title="Inspect elements (I)" onclick="toggleInspect()">⌖ Inspect</button>
|
|
508
|
+
<button id="edit-src-btn" class="tool-btn" style="display:none" onclick="openPageSrcModal()">✎ Edit Source</button>
|
|
509
|
+
<div id="layout-btns"></div>
|
|
510
|
+
</div>
|
|
511
|
+
<iframe id="frame" src="/"></iframe>
|
|
512
|
+
<div id="src-modal" class="src-modal" style="display:none" onclick="if(event.target===this)closeSrcModal()">
|
|
513
|
+
<div class="src-modal-box">
|
|
514
|
+
<div class="src-modal-hd">
|
|
515
|
+
<span id="src-modal-title" class="src-file-lbl" style="flex:1;font-size:11px;color:#aaa"></span>
|
|
516
|
+
<button class="cbtn" id="src-save-btn" style="padding:3px 12px">Save</button>
|
|
517
|
+
<button class="xbtn" onclick="closeSrcModal()" title="Close (Esc)">✕</button>
|
|
518
|
+
</div>
|
|
519
|
+
<div class="src-editor-area">
|
|
520
|
+
<textarea class="src-ed src-modal-ta" id="src-ta" spellcheck="false"></textarea>
|
|
521
|
+
<div id="src-cm-host"></div>
|
|
522
|
+
</div>
|
|
523
|
+
<div class="src-modal-ft">
|
|
524
|
+
<div id="src-st" class="ins-st"></div>
|
|
525
|
+
<button id="src-hl-btn" class="tool-btn" style="font-size:10px;padding:3px 8px" onclick="toggleHighlight()">✦ Highlight</button>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
<div class="details">
|
|
531
|
+
<div class="det-tabs">
|
|
532
|
+
<button class="dtab active" data-mode="comp" onclick="switchMode('comp')">PROPS</button>
|
|
533
|
+
<button class="dtab" data-mode="styles" onclick="switchMode('styles')">STYLES</button>
|
|
534
|
+
</div>
|
|
535
|
+
<div id="det"><div class="no-sel">Select a component to see details.</div></div>
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
<script>
|
|
539
|
+
;(function(){
|
|
540
|
+
var NL=String.fromCharCode(10)
|
|
541
|
+
var bps=[]
|
|
542
|
+
var sel=-1
|
|
543
|
+
var overrides={}
|
|
544
|
+
var omlTree=null
|
|
545
|
+
var pageMode='oml'
|
|
546
|
+
var pageFilePath=null
|
|
547
|
+
var pageFileType=null
|
|
548
|
+
var pageSourceContent=''
|
|
549
|
+
var cmEditor=null
|
|
550
|
+
var cmModules=null
|
|
551
|
+
var srcHighlight=localStorage.getItem('dvx-src-hl')==='1'
|
|
552
|
+
var themeObserver=null
|
|
553
|
+
var insertTarget=null
|
|
554
|
+
var pageLayouts=[]
|
|
555
|
+
var pageOwnFilePath=null
|
|
556
|
+
var srcModalMode='file'
|
|
557
|
+
var srcFragmentComp=null
|
|
558
|
+
var srcFragmentTag=null
|
|
559
|
+
var srcFragmentIdx=0
|
|
560
|
+
var prevHovered=null
|
|
561
|
+
var selected=null
|
|
562
|
+
var selectedTreeRow=null
|
|
563
|
+
var cssVarMap={}
|
|
564
|
+
var styleEdits={}
|
|
565
|
+
var detMode='comp'
|
|
566
|
+
var frame=document.getElementById('frame')
|
|
567
|
+
var params=new URLSearchParams(location.search)
|
|
568
|
+
var pg=params.get('page')||'/'
|
|
569
|
+
document.getElementById('pg').value=pg
|
|
570
|
+
setFrame(pg)
|
|
571
|
+
document.getElementById('back').href=pg
|
|
572
|
+
var pgInp=document.getElementById('pg')
|
|
573
|
+
var pgClear=document.getElementById('pg-clear')
|
|
574
|
+
function syncPgClear(){pgClear.style.display=pgInp.value?'block':'none'}
|
|
575
|
+
pgInp.addEventListener('keydown',function(e){if(e.key==='Enter')goPage()})
|
|
576
|
+
pgInp.addEventListener('input',syncPgClear)
|
|
577
|
+
pgClear.addEventListener('click',function(){pgInp.value='';syncPgClear();pgInp.focus()})
|
|
578
|
+
syncPgClear()
|
|
579
|
+
|
|
580
|
+
function setFrame(path){
|
|
581
|
+
var sep=path.indexOf('?')>=0?'&':'?'
|
|
582
|
+
frame.src=path+sep+'_editor=1'
|
|
583
|
+
}
|
|
584
|
+
function goPage(){
|
|
585
|
+
pg=document.getElementById('pg').value.trim()||'/'
|
|
586
|
+
setFrame(pg)
|
|
587
|
+
document.getElementById('back').href=pg
|
|
588
|
+
history.replaceState(null,'','/_davaux/editor?page='+encodeURIComponent(pg))
|
|
589
|
+
}
|
|
590
|
+
window.goPage=goPage
|
|
591
|
+
|
|
592
|
+
// ── Click-to-select bridge ────────────────────────────────────────────────────
|
|
593
|
+
// ── Inspect mode ─────────────────────────────────────────────────────────────
|
|
594
|
+
var selectMode=false
|
|
595
|
+
function setSelectMode(v){
|
|
596
|
+
selectMode=!!v
|
|
597
|
+
var btn=document.getElementById('sel-toggle')
|
|
598
|
+
if(btn)btn.classList.toggle('active',selectMode)
|
|
599
|
+
if(!selectMode&&prevHovered){prevHovered.style.outline='';prevHovered=null}
|
|
600
|
+
if(!selectMode&&selected){try{selected.style.outline=''}catch(_){}selected=null}
|
|
601
|
+
try{frame.contentDocument.body.style.cursor=selectMode?'crosshair':''}catch(_){}
|
|
602
|
+
}
|
|
603
|
+
window.setSelectMode=setSelectMode
|
|
604
|
+
window.toggleInspect=function(){setSelectMode(!selectMode)}
|
|
605
|
+
window.openSrcModal=openSrcModal
|
|
606
|
+
window.openLayoutModal=openLayoutModal
|
|
607
|
+
window.openPageSrcModal=openPageSrcModal
|
|
608
|
+
window.closeSrcModal=closeSrcModal
|
|
609
|
+
window.toggleHighlight=toggleHighlight
|
|
610
|
+
window.openFragmentModal=openFragmentModal
|
|
611
|
+
window.openElementFragmentModal=openElementFragmentModal
|
|
612
|
+
document.addEventListener('keydown',function(e){
|
|
613
|
+
if(e.key==='Escape'){var m=document.getElementById('src-modal');if(m&&m.style.display!=='none'){closeSrcModal();return}}
|
|
614
|
+
if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA'||e.target.tagName==='SELECT'||e.target.isContentEditable)return
|
|
615
|
+
if(e.key==='i'||e.key==='I'){e.preventDefault();setSelectMode(!selectMode)}
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
frame.addEventListener('load',function(){
|
|
619
|
+
try{
|
|
620
|
+
var doc=frame.contentDocument
|
|
621
|
+
if(!doc||!doc.body)return
|
|
622
|
+
// Fetch OML tree for this page
|
|
623
|
+
var fp=frame.contentWindow.location
|
|
624
|
+
var rawSearch=fp.search.replace(/[?&]_editor=1/,'')
|
|
625
|
+
var treeUrl=fp.pathname+(rawSearch||'')
|
|
626
|
+
omlTree=null
|
|
627
|
+
ocState=null
|
|
628
|
+
oeState=null
|
|
629
|
+
selected=null
|
|
630
|
+
insertTarget=null
|
|
631
|
+
styleEdits={}
|
|
632
|
+
var det=document.getElementById('det')
|
|
633
|
+
if(det)det.innerHTML='<div class="no-sel">Select a component to see details.</div>'
|
|
634
|
+
fetch('/_davaux/inspector?url='+encodeURIComponent(treeUrl))
|
|
635
|
+
.then(function(r){return r.json()})
|
|
636
|
+
.then(function(d){
|
|
637
|
+
pageFileType=d.fileType||'tsx'
|
|
638
|
+
pageFilePath=d.filePath||null
|
|
639
|
+
pageOwnFilePath=d.filePath||null
|
|
640
|
+
pageLayouts=d.layouts||[]
|
|
641
|
+
omlTree=d.node||null
|
|
642
|
+
pageMode=(pageFileType==='mdx'||pageFileType==='md')?'source':'oml'
|
|
643
|
+
renderLayoutButtons()
|
|
644
|
+
if(pageMode==='source'){
|
|
645
|
+
enterSourceMode()
|
|
646
|
+
}else{
|
|
647
|
+
var esb=document.getElementById('edit-src-btn')
|
|
648
|
+
if(esb)esb.style.display=pageFilePath?'':'none'
|
|
649
|
+
pageSourceContent=''
|
|
650
|
+
if(pageFilePath){
|
|
651
|
+
fetch('/_davaux/get-source?file='+encodeURIComponent(pageFilePath))
|
|
652
|
+
.then(function(r){return r.json()})
|
|
653
|
+
.then(function(d2){pageSourceContent=d2.content||''})
|
|
654
|
+
.catch(function(){})
|
|
655
|
+
}
|
|
656
|
+
closeSrcModal()
|
|
657
|
+
renderTreePanel()
|
|
658
|
+
}
|
|
659
|
+
})
|
|
660
|
+
.catch(function(){renderTreePanel()})
|
|
661
|
+
scanCssVars(doc)
|
|
662
|
+
detectPageTheme(doc)
|
|
663
|
+
if(themeObserver)themeObserver.disconnect()
|
|
664
|
+
themeObserver=new MutationObserver(function(){detectPageTheme(doc)})
|
|
665
|
+
themeObserver.observe(doc.documentElement,{attributes:true,attributeFilter:['data-mantine-color-scheme','data-color-scheme','data-theme']})
|
|
666
|
+
if(detMode==='styles')renderStylesPanel()
|
|
667
|
+
if(selectMode)doc.body.style.cursor='crosshair'
|
|
668
|
+
// Hover — blue outline (inspect mode only)
|
|
669
|
+
doc.addEventListener('mouseover',function(e){
|
|
670
|
+
if(!selectMode)return
|
|
671
|
+
// Restore selection outline on previously hovered element (if it was selected)
|
|
672
|
+
if(prevHovered){
|
|
673
|
+
prevHovered.style.outline=prevHovered===selected?'2px solid #7cc5f0':''
|
|
674
|
+
prevHovered.style.outlineOffset=prevHovered===selected?'-1px':''
|
|
675
|
+
}
|
|
676
|
+
if(e.target===doc.body||e.target===doc.documentElement){prevHovered=null;return}
|
|
677
|
+
prevHovered=e.target
|
|
678
|
+
e.target.style.outline='2px solid rgba(124,197,240,0.7)'
|
|
679
|
+
e.target.style.outlineOffset='-1px'
|
|
680
|
+
})
|
|
681
|
+
doc.addEventListener('mouseout',function(){
|
|
682
|
+
if(!selectMode)return
|
|
683
|
+
if(prevHovered){
|
|
684
|
+
// Restore selection outline if this element is still selected
|
|
685
|
+
prevHovered.style.outline=prevHovered===selected?'2px solid #7cc5f0':''
|
|
686
|
+
prevHovered.style.outlineOffset=prevHovered===selected?'-1px':''
|
|
687
|
+
prevHovered=null
|
|
688
|
+
}
|
|
689
|
+
})
|
|
690
|
+
// Click — toggle selection and show details (inspect mode only)
|
|
691
|
+
doc.addEventListener('click',function(e){
|
|
692
|
+
if(!selectMode)return
|
|
693
|
+
e.preventDefault()
|
|
694
|
+
e.stopPropagation()
|
|
695
|
+
var target=e.target
|
|
696
|
+
// Clear previous selection outline
|
|
697
|
+
if(selected){selected.style.outline='';selected.style.outlineOffset=''}
|
|
698
|
+
if(selected===target){
|
|
699
|
+
// Same element clicked again — deselect
|
|
700
|
+
selected=null
|
|
701
|
+
ocState=null
|
|
702
|
+
oeState=null
|
|
703
|
+
var det=document.getElementById('det')
|
|
704
|
+
if(det)det.innerHTML='<div class="no-sel">Select a component to see details.</div>'
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
// Select the new element
|
|
708
|
+
selected=target
|
|
709
|
+
selected.style.outline='2px solid #7cc5f0'
|
|
710
|
+
selected.style.outlineOffset='-1px'
|
|
711
|
+
showElemDet(target)
|
|
712
|
+
},true)
|
|
713
|
+
}catch(_){}
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
function showElemDet(el){
|
|
717
|
+
detMode='comp'
|
|
718
|
+
document.querySelectorAll('.dtab').forEach(function(t){t.classList.toggle('active',t.dataset.mode==='comp')})
|
|
719
|
+
sel=-1
|
|
720
|
+
ocState=null
|
|
721
|
+
oeState=null
|
|
722
|
+
styleEdits={}
|
|
723
|
+
document.querySelectorAll('.bp-card').forEach(function(c){c.classList.remove('sel')})
|
|
724
|
+
var tag=el.tagName?el.tagName.toLowerCase():'?'
|
|
725
|
+
var cls=el.getAttribute?el.getAttribute('class')||'':''
|
|
726
|
+
var txt=(el.textContent||'').trim().slice(0,80)
|
|
727
|
+
var det=document.getElementById('det')
|
|
728
|
+
// Islands use data-island; library components use data-oml-comp injected at render time.
|
|
729
|
+
// Walk up from the clicked element to find the nearest marker.
|
|
730
|
+
var islandName=null
|
|
731
|
+
var omlCompEl=null
|
|
732
|
+
var n=el
|
|
733
|
+
while(n){
|
|
734
|
+
var di=n.getAttribute&&n.getAttribute('data-island');if(di){islandName=di;break}
|
|
735
|
+
var dc=n.getAttribute&&n.getAttribute('data-oml-comp');if(dc){omlCompEl=n;break}
|
|
736
|
+
n=n.parentElement
|
|
737
|
+
}
|
|
738
|
+
var match=islandName&&omlTree?findByName(omlTree,islandName)
|
|
739
|
+
:omlCompEl&&omlTree?findByOmlComp(omlTree,omlCompEl.getAttribute('data-oml-comp'),parseInt(omlCompEl.getAttribute('data-oml-inst')||'0',10))
|
|
740
|
+
:omlTree?findInTree(omlTree,tag,txt,cls):null
|
|
741
|
+
if(match&&match.type==='#component'){
|
|
742
|
+
var compIdx=countNameBefore(omlTree,match,match.name)
|
|
743
|
+
var rawProps=Object.assign({},match.props||{})
|
|
744
|
+
var chi=compChildrenInfo(match)
|
|
745
|
+
ocState={name:match.name,props:rawProps,instanceIndex:compIdx,childrenText:chi.childrenText,hasChildren:chi.hasChildren}
|
|
746
|
+
insertTarget={type:'comp',name:match.name,instanceIndex:compIdx}
|
|
747
|
+
renderOcPanel(det)
|
|
748
|
+
syncTreeRow(match)
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
if(match&&match.type==='element'){
|
|
752
|
+
var elemText=null
|
|
753
|
+
if(match.children&&match.children.length>0){
|
|
754
|
+
var allTxt=true
|
|
755
|
+
var accum=''
|
|
756
|
+
for(var ci=0;ci<match.children.length;ci++){
|
|
757
|
+
var ch=match.children[ci]
|
|
758
|
+
if(ch&&ch.type==='#text'){accum+=ch.value||''}
|
|
759
|
+
else{allTxt=false;break}
|
|
760
|
+
}
|
|
761
|
+
if(allTxt&&accum.trim())elemText=accum
|
|
762
|
+
}
|
|
763
|
+
var idx=countTagBefore(omlTree,match,match.tag)
|
|
764
|
+
oeState={tag:match.tag,props:Object.assign({},match.props||{}),instanceIndex:idx,textContent:elemText}
|
|
765
|
+
insertTarget={type:'elem',tag:match.tag,instanceIndex:idx}
|
|
766
|
+
renderOePanel(det)
|
|
767
|
+
syncTreeRow(match)
|
|
768
|
+
return
|
|
769
|
+
}
|
|
770
|
+
syncTreeRow(null)
|
|
771
|
+
var h='<span class="el-chip"><'+esc(tag)+'></span>'
|
|
772
|
+
if(cls)h+='<div class="prop-row"><span class="pn">class</span><span class="pt"> "'+esc(cls)+'"</span></div>'
|
|
773
|
+
if(txt)h+='<div class="d-lbl">TEXT</div><div style="color:#888;font-size:11px;word-break:break-all;line-height:1.4">'+esc(txt)+'</div>'
|
|
774
|
+
det.innerHTML=h
|
|
775
|
+
}
|
|
776
|
+
function compChildrenInfo(node){
|
|
777
|
+
var kids=node.children||[]
|
|
778
|
+
if(kids.length===0)return{childrenText:null,hasChildren:false}
|
|
779
|
+
if(kids.length===1&&kids[0]&&kids[0].type==='#text')return{childrenText:kids[0].value||null,hasChildren:true}
|
|
780
|
+
return{childrenText:null,hasChildren:true}
|
|
781
|
+
}
|
|
782
|
+
function isOmlVal(v){
|
|
783
|
+
if(!v||typeof v!=='object')return false
|
|
784
|
+
if(Array.isArray(v))return v.some(isOmlVal)
|
|
785
|
+
var t=v.type
|
|
786
|
+
return t==='element'||t==='#component'||t==='#fragment'||t==='#text'||t==='#raw'
|
|
787
|
+
}
|
|
788
|
+
function renderOcPanel(det){
|
|
789
|
+
if(!ocState)return
|
|
790
|
+
var entries=Object.entries(ocState.props).filter(function(e){return typeof e[1]!=='function'&&!isOmlVal(e[1])})
|
|
791
|
+
var readonlyEntries=Object.entries(ocState.props).filter(function(e){return isOmlVal(e[1])})
|
|
792
|
+
// Find matching blueprint to surface optional props not currently in the JSX
|
|
793
|
+
var bp=bps.find(function(b){return b.name===ocState.name})
|
|
794
|
+
var bpSchema=bp?bp.props||{}:{}
|
|
795
|
+
var missing=Object.keys(bpSchema).filter(function(k){return!(k in ocState.props)})
|
|
796
|
+
var h='<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">'
|
|
797
|
+
h+='<span class="comp-chip" style="margin:0">♢ '+esc(ocState.name)+'</span>'
|
|
798
|
+
h+='<button class="cbtn mv-up" style="padding:2px 6px;font-size:12px;width:auto" title="Move up">↑</button>'
|
|
799
|
+
h+='<button class="cbtn mv-dn" style="padding:2px 6px;font-size:12px;width:auto" title="Move down">↓</button>'
|
|
800
|
+
h+='<button class="cbtn rem-btn" style="background:#2a1a1a;border-color:#4a2a2a;color:#ff8888;padding:2px 8px;font-size:10px;width:auto;margin-left:auto" data-comp="'+esc(ocState.name)+'" data-idx="'+ocState.instanceIndex+'">Remove</button>'
|
|
801
|
+
h+='</div>'
|
|
802
|
+
h+='<div id="rem-st" class="ins-st"></div>'
|
|
803
|
+
if(entries.length||readonlyEntries.length){
|
|
804
|
+
h+='<div class="d-lbl">PROPS</div>'
|
|
805
|
+
entries.forEach(function(e){
|
|
806
|
+
var k=e[0],v=e[1]
|
|
807
|
+
h+='<div class="pi-row"><label class="pi-lbl"><span class="pn">'+esc(k)+'</span>'
|
|
808
|
+
h+='<span class="pt"> '+esc(typeof v)+'</span></label>'
|
|
809
|
+
if(typeof v==='boolean'){
|
|
810
|
+
h+='<input type="checkbox" class="pi pi-cb oc-inp" data-prop="'+esc(k)+'" data-type="boolean"'+(v?' checked':'')+'>'
|
|
811
|
+
}else if(typeof v==='number'){
|
|
812
|
+
h+='<input type="number" class="pi oc-inp" data-prop="'+esc(k)+'" data-type="number" value="'+esc(String(v))+'">'
|
|
813
|
+
}else if(typeof v==='object'&&v!==null){
|
|
814
|
+
h+='<input type="text" class="pi oc-inp" data-prop="'+esc(k)+'" data-type="json" value="'+esc(JSON.stringify(v))+'">'
|
|
815
|
+
}else{
|
|
816
|
+
h+='<input type="text" class="pi oc-inp" data-prop="'+esc(k)+'" data-type="string" value="'+esc(String(v!==null&&v!==undefined?v:''))+'">'
|
|
817
|
+
}
|
|
818
|
+
h+='</div>'
|
|
819
|
+
})
|
|
820
|
+
readonlyEntries.forEach(function(e){
|
|
821
|
+
var k=e[0]
|
|
822
|
+
h+='<div class="pi-row"><label class="pi-lbl"><span class="pn">'+esc(k)+'</span><span class="pt"> jsx</span></label>'
|
|
823
|
+
h+='<input type="text" class="pi" value="[JSX]" readonly style="opacity:0.4;cursor:default"></div>'
|
|
824
|
+
})
|
|
825
|
+
}else{
|
|
826
|
+
h+='<div class="no-sel" style="font-size:11px">No props</div>'
|
|
827
|
+
}
|
|
828
|
+
if(ocState.childrenText!==null&&ocState.childrenText!==undefined){
|
|
829
|
+
h+='<div class="d-lbl" style="margin-top:8px">CONTENT</div>'
|
|
830
|
+
h+='<textarea class="pi oc-txt" rows="2" style="width:100%;resize:vertical;font-family:monospace;font-size:11px">'+esc(ocState.childrenText)+'</textarea>'
|
|
831
|
+
}else if(ocState.hasChildren){
|
|
832
|
+
h+='<div class="d-lbl" style="margin-top:8px">CHILDREN</div>'
|
|
833
|
+
h+='<button class="cbtn ibtn" style="font-size:10px;padding:3px 8px;width:auto" onclick="openFragmentModal()">✎ Edit Children</button>'
|
|
834
|
+
}
|
|
835
|
+
// Blueprint optional props not yet in JSX
|
|
836
|
+
if(missing.length){
|
|
837
|
+
h+='<div class="d-lbl" style="margin-top:8px">OPTIONAL</div>'
|
|
838
|
+
missing.forEach(function(k){
|
|
839
|
+
var schema=bpSchema[k]
|
|
840
|
+
h+='<div class="pi-row"><span class="pn" style="opacity:0.5;font-size:11px;flex:1">'+esc(k)+'</span>'
|
|
841
|
+
h+='<span class="pt" style="opacity:0.4;font-size:10px;flex-shrink:0;margin-right:4px">'+esc(schema.type||'string')+'</span>'
|
|
842
|
+
h+='<button class="conv-btn oc-add-bp" data-prop="'+esc(k)+'" data-type="'+(schema.type||'string')+'" style="font-size:10px;padding:2px 6px">+ Add</button>'
|
|
843
|
+
h+='</div>'
|
|
844
|
+
})
|
|
845
|
+
}
|
|
846
|
+
// Manual add prop
|
|
847
|
+
h+='<button class="cbtn oc-add-manual" style="margin-top:6px;font-size:10px;background:#1a1a2e;color:#666">+ Add prop</button>'
|
|
848
|
+
h+='<div style="display:flex;gap:4px;margin-top:8px"><button class="cbtn" id="oc-save-btn">Save changes</button></div>'
|
|
849
|
+
h+='<div id="oc-st" class="ins-st"></div>'
|
|
850
|
+
det.innerHTML=h
|
|
851
|
+
det.querySelectorAll('.oc-inp').forEach(function(inp){
|
|
852
|
+
inp.addEventListener('input',function(){
|
|
853
|
+
var p=this.dataset.prop,t=this.dataset.type
|
|
854
|
+
if(t==='boolean')ocState.props[p]=this.checked
|
|
855
|
+
else if(t==='number')ocState.props[p]=Number(this.value)
|
|
856
|
+
else if(t==='json'){try{ocState.props[p]=JSON.parse(this.value)}catch(_){}}
|
|
857
|
+
else ocState.props[p]=this.value
|
|
858
|
+
})
|
|
859
|
+
})
|
|
860
|
+
var ocTxt=det.querySelector('.oc-txt')
|
|
861
|
+
if(ocTxt)ocTxt.addEventListener('input',function(){ocState.childrenText=this.value})
|
|
862
|
+
det.querySelectorAll('.oc-add-bp').forEach(function(btn){
|
|
863
|
+
btn.addEventListener('click',function(){
|
|
864
|
+
var k=this.dataset.prop,t=this.dataset.type||'string'
|
|
865
|
+
ocState.props[k]=defVal(t)
|
|
866
|
+
renderOcPanel(document.getElementById('det'))
|
|
867
|
+
})
|
|
868
|
+
})
|
|
869
|
+
var addManual=det.querySelector('.oc-add-manual')
|
|
870
|
+
if(addManual)addManual.addEventListener('click',function(){
|
|
871
|
+
var row=document.createElement('div')
|
|
872
|
+
row.className='pi-row'
|
|
873
|
+
row.style.gap='4px'
|
|
874
|
+
row.innerHTML='<input type="text" class="pi" placeholder="name" style="flex:1"><input type="text" class="pi" placeholder="value" style="flex:1"><button class="conv-btn" style="padding:2px 6px;font-size:10px;flex-shrink:0">OK</button>'
|
|
875
|
+
var saveArea=det.querySelector('#oc-save-btn')
|
|
876
|
+
if(saveArea&&saveArea.parentElement)saveArea.parentElement.before(row)
|
|
877
|
+
row.querySelector('input').focus()
|
|
878
|
+
row.querySelector('button').addEventListener('click',function(){
|
|
879
|
+
var ins=row.querySelectorAll('input')
|
|
880
|
+
var name=ins[0].value.trim(),val=ins[1].value
|
|
881
|
+
if(!name)return
|
|
882
|
+
ocState.props[name]=val
|
|
883
|
+
renderOcPanel(document.getElementById('det'))
|
|
884
|
+
})
|
|
885
|
+
})
|
|
886
|
+
var rb=det.querySelector('.rem-btn')
|
|
887
|
+
if(rb)rb.addEventListener('click',function(){remComp(this.getAttribute('data-comp'),parseInt(this.getAttribute('data-idx')||'0',10))})
|
|
888
|
+
var sb=det.querySelector('#oc-save-btn')
|
|
889
|
+
if(sb)sb.addEventListener('click',saveOmlComp)
|
|
890
|
+
var mvup=det.querySelector('.mv-up')
|
|
891
|
+
if(mvup)mvup.addEventListener('click',function(){movePanelNode(true,ocState.name,ocState.instanceIndex,'up')})
|
|
892
|
+
var mvdn=det.querySelector('.mv-dn')
|
|
893
|
+
if(mvdn)mvdn.addEventListener('click',function(){movePanelNode(true,ocState.name,ocState.instanceIndex,'down')})
|
|
894
|
+
}
|
|
895
|
+
function buildJsxFromObj(name,props){
|
|
896
|
+
var entries=Object.entries(props).filter(function(e){return typeof e[1]!=='function'})
|
|
897
|
+
if(!entries.length)return'<'+name+' />'
|
|
898
|
+
var lines=entries.map(function(e){
|
|
899
|
+
var k=e[0],v=e[1]
|
|
900
|
+
if(typeof v==='string')return' '+k+'='+JSON.stringify(v)
|
|
901
|
+
if(typeof v==='boolean')return' '+k+'={'+(v?'true':'false')+'}'
|
|
902
|
+
if(typeof v==='number')return' '+k+'={'+v+'}'
|
|
903
|
+
return' '+k+'={'+JSON.stringify(v)+'}'
|
|
904
|
+
})
|
|
905
|
+
return'<'+name+NL+lines.join(NL)+NL+'/>'
|
|
906
|
+
}
|
|
907
|
+
function saveOmlComp(){
|
|
908
|
+
if(!ocState)return
|
|
909
|
+
var st=document.getElementById('oc-st')
|
|
910
|
+
if(st){st.textContent='Saving...';st.className='ins-st'}
|
|
911
|
+
fetch('/_davaux/update-comp-props',{
|
|
912
|
+
method:'POST',headers:{'Content-Type':'application/json'},
|
|
913
|
+
body:JSON.stringify({page:pg.split('?')[0],component:ocState.name,newProps:ocState.props,instanceIndex:ocState.instanceIndex,childrenText:ocState.childrenText})
|
|
914
|
+
})
|
|
915
|
+
.then(function(r){return r.json()})
|
|
916
|
+
.then(function(res){
|
|
917
|
+
var s=document.getElementById('oc-st');if(!s)return
|
|
918
|
+
if(res.replaced){
|
|
919
|
+
s.textContent='Saved to '+res.file;s.className='ins-st ok'
|
|
920
|
+
var b=document.getElementById('oc-save-btn')
|
|
921
|
+
if(b){b.textContent='Saved!';b.classList.add('ok');setTimeout(function(){b.textContent='Save changes';b.classList.remove('ok')},2000)}
|
|
922
|
+
}else{s.textContent=res.error;s.className='ins-st err'}
|
|
923
|
+
})
|
|
924
|
+
.catch(function(){var s=document.getElementById('oc-st');if(s){s.textContent='Request failed';s.className='ins-st err'}})
|
|
925
|
+
}
|
|
926
|
+
window.saveOmlComp=saveOmlComp
|
|
927
|
+
|
|
928
|
+
function countTagBefore(root,target,tag){
|
|
929
|
+
var state={n:0,found:false}
|
|
930
|
+
walkTagCount(root,target,tag,state)
|
|
931
|
+
return state.found?state.n:0
|
|
932
|
+
}
|
|
933
|
+
function walkTagCount(node,target,tag,state){
|
|
934
|
+
if(state.found)return
|
|
935
|
+
if(node===target){state.found=true;return}
|
|
936
|
+
if(node.type==='element'){
|
|
937
|
+
if(node.tag===tag)state.n++
|
|
938
|
+
var kids=node.children||[]
|
|
939
|
+
for(var i=0;i<kids.length;i++)walkTagCount(kids[i],target,tag,state)
|
|
940
|
+
}else if(node.type==='#fragment'){
|
|
941
|
+
var kids=node.children||[]
|
|
942
|
+
for(var i=0;i<kids.length;i++)walkTagCount(kids[i],target,tag,state)
|
|
943
|
+
}else if(node.type==='#component'){
|
|
944
|
+
var sub=(node.output&&node.output.type!=='#raw')?[node.output]:(node.children||[])
|
|
945
|
+
for(var i=0;i<sub.length;i++)walkTagCount(sub[i],target,tag,state)
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function countNameBefore(root,target,name){
|
|
949
|
+
var state={n:0,found:false}
|
|
950
|
+
walkNameCount(root,target,name,state)
|
|
951
|
+
return state.found?state.n:0
|
|
952
|
+
}
|
|
953
|
+
function walkNameCount(node,target,name,state){
|
|
954
|
+
if(!node||state.found)return
|
|
955
|
+
if(node===target){state.found=true;return}
|
|
956
|
+
if(node.type==='#component'){
|
|
957
|
+
if(node.name===name)state.n++
|
|
958
|
+
var sub=(node.output&&node.output.type!=='#raw')?[node.output]:(node.children||[])
|
|
959
|
+
for(var i=0;i<sub.length;i++)walkNameCount(sub[i],target,name,state)
|
|
960
|
+
}else if(node.type==='element'||node.type==='#fragment'){
|
|
961
|
+
var kids=node.children||[]
|
|
962
|
+
for(var i=0;i<kids.length;i++)walkNameCount(kids[i],target,name,state)
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
function renderOePanel(det){
|
|
966
|
+
if(!oeState)return
|
|
967
|
+
var entries=Object.entries(oeState.props).filter(function(e){return typeof e[1]!=='function'})
|
|
968
|
+
var h='<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">'
|
|
969
|
+
h+='<span class="el-chip" style="margin:0"><'+esc(oeState.tag)+'></span>'
|
|
970
|
+
h+='<button class="cbtn mv-up" style="padding:2px 6px;font-size:12px;width:auto" title="Move up">↑</button>'
|
|
971
|
+
h+='<button class="cbtn mv-dn" style="padding:2px 6px;font-size:12px;width:auto" title="Move down">↓</button>'
|
|
972
|
+
h+='<button class="cbtn oe-rem-btn" style="background:#2a1a1a;border-color:#4a2a2a;color:#ff8888;padding:2px 8px;font-size:10px;width:auto;margin-left:auto">Remove</button>'
|
|
973
|
+
h+='</div>'
|
|
974
|
+
if(entries.length){
|
|
975
|
+
h+='<div class="d-lbl">ATTRIBUTES</div>'
|
|
976
|
+
entries.forEach(function(e){
|
|
977
|
+
var k=e[0],v=e[1]
|
|
978
|
+
h+='<div class="pi-row"><label class="pi-lbl"><span class="pn">'+esc(k)+'</span>'
|
|
979
|
+
if(typeof v==='boolean'){
|
|
980
|
+
h+='<span class="pt"> bool</span></label>'
|
|
981
|
+
h+='<input type="checkbox" class="pi pi-cb oe-inp" data-attr="'+esc(k)+'" data-type="boolean"'+(v?' checked':'')+'>'
|
|
982
|
+
}else if(typeof v==='object'&&v!==null){
|
|
983
|
+
h+='<span class="pt"> object</span></label>'
|
|
984
|
+
h+='<input type="text" class="pi oe-inp" data-attr="'+esc(k)+'" data-type="json" value="'+esc(JSON.stringify(v))+'">'
|
|
985
|
+
}else{
|
|
986
|
+
h+='<span class="pt"> string</span></label>'
|
|
987
|
+
h+='<input type="text" class="pi oe-inp" data-attr="'+esc(k)+'" data-type="string" value="'+esc(String(v!==null&&v!==undefined?v:''))+'">'
|
|
988
|
+
}
|
|
989
|
+
h+='</div>'
|
|
990
|
+
})
|
|
991
|
+
}else{
|
|
992
|
+
h+='<div class="no-sel" style="font-size:11px">No attributes</div>'
|
|
993
|
+
}
|
|
994
|
+
if(oeState.textContent!==null&&oeState.textContent!==undefined){
|
|
995
|
+
h+='<div class="d-lbl" style="margin-top:8px">TEXT</div>'
|
|
996
|
+
h+='<textarea class="pi oe-txt" rows="2" style="width:100%;resize:vertical;font-family:monospace;font-size:11px">'+esc(oeState.textContent)+'</textarea>'
|
|
997
|
+
}else{
|
|
998
|
+
h+='<div class="d-lbl" style="margin-top:8px">CHILDREN</div>'
|
|
999
|
+
h+='<button class="cbtn ibtn" style="font-size:10px;padding:3px 8px;width:auto" onclick="openElementFragmentModal()">✎ Edit Children</button>'
|
|
1000
|
+
}
|
|
1001
|
+
h+='<button class="cbtn oe-add-manual" style="margin-top:6px;font-size:10px;background:#1a1a2e;color:#666">+ Add attr</button>'
|
|
1002
|
+
h+='<div style="display:flex;gap:4px;margin-top:8px"><button class="cbtn" id="oe-save-btn">Save changes</button></div>'
|
|
1003
|
+
h+='<div id="oe-st" class="ins-st"></div>'
|
|
1004
|
+
h+='<div style="margin-top:8px">'
|
|
1005
|
+
h+='<button class="cbtn oe-add-sib-btn" style="font-size:10px;background:#1a2a1a;border-color:#2a4a2a;color:#88cc88;width:auto;padding:2px 8px">+ Add sibling</button>'
|
|
1006
|
+
h+='<div id="oe-sib-form" style="display:none;margin-top:6px">'
|
|
1007
|
+
h+='<div class="pi-row"><label class="pi-lbl"><span class="pn">tag</span></label>'
|
|
1008
|
+
h+='<select class="pi" id="sib-tag"><option>div</option><option>p</option><option>span</option><option>h1</option><option>h2</option><option>h3</option><option>section</option><option>article</option><option>button</option><option>a</option><option>ul</option><option>li</option></select>'
|
|
1009
|
+
h+='</div>'
|
|
1010
|
+
h+='<div class="pi-row"><label class="pi-lbl"><span class="pn">class</span></label><input type="text" class="pi" id="sib-cls" placeholder="optional"></div>'
|
|
1011
|
+
h+='<div class="pi-row"><label class="pi-lbl"><span class="pn">text</span></label><input type="text" class="pi" id="sib-txt" placeholder="optional"></div>'
|
|
1012
|
+
h+='<button class="cbtn" id="sib-create-btn" style="margin-top:4px">Create</button>'
|
|
1013
|
+
h+='</div></div>'
|
|
1014
|
+
det.innerHTML=h
|
|
1015
|
+
det.querySelectorAll('.oe-inp').forEach(function(inp){
|
|
1016
|
+
inp.addEventListener('input',function(){
|
|
1017
|
+
var a=this.dataset.attr,t=this.dataset.type
|
|
1018
|
+
if(t==='boolean')oeState.props[a]=this.checked
|
|
1019
|
+
else if(t==='json'){try{oeState.props[a]=JSON.parse(this.value)}catch(_){}}
|
|
1020
|
+
else oeState.props[a]=this.value
|
|
1021
|
+
})
|
|
1022
|
+
})
|
|
1023
|
+
var txtArea=det.querySelector('.oe-txt')
|
|
1024
|
+
if(txtArea)txtArea.addEventListener('input',function(){oeState.textContent=this.value})
|
|
1025
|
+
var addManual=det.querySelector('.oe-add-manual')
|
|
1026
|
+
if(addManual)addManual.addEventListener('click',function(){
|
|
1027
|
+
var row=document.createElement('div')
|
|
1028
|
+
row.className='pi-row'
|
|
1029
|
+
row.style.gap='4px'
|
|
1030
|
+
row.innerHTML='<input type="text" class="pi" placeholder="attr" style="flex:1"><input type="text" class="pi" placeholder="value" style="flex:1"><button class="conv-btn" style="padding:2px 6px;font-size:10px;flex-shrink:0">OK</button>'
|
|
1031
|
+
var saveArea=det.querySelector('#oe-save-btn')
|
|
1032
|
+
if(saveArea&&saveArea.parentElement)saveArea.parentElement.before(row)
|
|
1033
|
+
row.querySelector('input').focus()
|
|
1034
|
+
row.querySelector('button').addEventListener('click',function(){
|
|
1035
|
+
var ins=row.querySelectorAll('input')
|
|
1036
|
+
var name=ins[0].value.trim(),val=ins[1].value
|
|
1037
|
+
if(!name)return
|
|
1038
|
+
oeState.props[name]=val
|
|
1039
|
+
renderOePanel(document.getElementById('det'))
|
|
1040
|
+
})
|
|
1041
|
+
})
|
|
1042
|
+
var sb=det.querySelector('#oe-save-btn')
|
|
1043
|
+
if(sb)sb.addEventListener('click',saveOmlElem)
|
|
1044
|
+
var mvup=det.querySelector('.mv-up')
|
|
1045
|
+
if(mvup)mvup.addEventListener('click',function(){movePanelNode(false,oeState.tag,oeState.instanceIndex,'up')})
|
|
1046
|
+
var mvdn=det.querySelector('.mv-dn')
|
|
1047
|
+
if(mvdn)mvdn.addEventListener('click',function(){movePanelNode(false,oeState.tag,oeState.instanceIndex,'down')})
|
|
1048
|
+
var remBtn=det.querySelector('.oe-rem-btn')
|
|
1049
|
+
if(remBtn)remBtn.addEventListener('click',removeOmlElem)
|
|
1050
|
+
var addSibBtn=det.querySelector('.oe-add-sib-btn')
|
|
1051
|
+
if(addSibBtn)addSibBtn.addEventListener('click',function(){
|
|
1052
|
+
var form=document.getElementById('oe-sib-form')
|
|
1053
|
+
if(form)form.style.display=form.style.display==='none'?'block':'none'
|
|
1054
|
+
})
|
|
1055
|
+
var createSibBtn=det.querySelector('#sib-create-btn')
|
|
1056
|
+
if(createSibBtn)createSibBtn.addEventListener('click',insertSiblingElem)
|
|
1057
|
+
}
|
|
1058
|
+
function insertSiblingElem(){
|
|
1059
|
+
if(!oeState)return
|
|
1060
|
+
var tagEl=document.getElementById('sib-tag')
|
|
1061
|
+
var clsEl=document.getElementById('sib-cls')
|
|
1062
|
+
var txtEl=document.getElementById('sib-txt')
|
|
1063
|
+
var tag=tagEl?tagEl['value']:'div'
|
|
1064
|
+
var cls=clsEl?clsEl['value'].trim():''
|
|
1065
|
+
var txt=txtEl?txtEl['value'].trim():''
|
|
1066
|
+
var attrs=cls?' class="'+cls+'"':''
|
|
1067
|
+
var jsx=txt?'<'+tag+attrs+'>'+txt+'</'+tag+'>':'<'+tag+attrs+' />'
|
|
1068
|
+
var st=document.getElementById('oe-st')
|
|
1069
|
+
if(st){st.textContent='Creating...';st.className='ins-st'}
|
|
1070
|
+
fetch('/_davaux/insert-after',{
|
|
1071
|
+
method:'POST',headers:{'Content-Type':'application/json'},
|
|
1072
|
+
body:JSON.stringify({page:pg.split('?')[0],tag:oeState.tag,instanceIndex:oeState.instanceIndex,newJsx:jsx})
|
|
1073
|
+
})
|
|
1074
|
+
.then(function(r){return r.json()})
|
|
1075
|
+
.then(function(res){
|
|
1076
|
+
var s=document.getElementById('oe-st');if(!s)return
|
|
1077
|
+
if(res.inserted){
|
|
1078
|
+
s.textContent='Inserted into '+res.file;s.className='ins-st ok'
|
|
1079
|
+
var cb=document.getElementById('sib-create-btn')
|
|
1080
|
+
if(cb){cb['disabled']=true;cb.textContent='Done'}
|
|
1081
|
+
}else{s.textContent=res.error;s.className='ins-st err'}
|
|
1082
|
+
})
|
|
1083
|
+
.catch(function(){var s=document.getElementById('oe-st');if(s){s.textContent='Request failed';s.className='ins-st err'}})
|
|
1084
|
+
}
|
|
1085
|
+
function removeOmlElem(){
|
|
1086
|
+
if(!oeState)return
|
|
1087
|
+
var st=document.getElementById('oe-st')
|
|
1088
|
+
if(st){st.textContent='Removing...';st.className='ins-st'}
|
|
1089
|
+
fetch('/_davaux/remove',{
|
|
1090
|
+
method:'POST',headers:{'Content-Type':'application/json'},
|
|
1091
|
+
body:JSON.stringify({page:pg.split('?')[0],tag:oeState.tag,instanceIndex:oeState.instanceIndex})
|
|
1092
|
+
})
|
|
1093
|
+
.then(function(r){return r.json()})
|
|
1094
|
+
.then(function(res){
|
|
1095
|
+
var s=document.getElementById('oe-st');if(!s)return
|
|
1096
|
+
if(res.removed){
|
|
1097
|
+
s.textContent='Removed from '+res.file;s.className='ins-st ok'
|
|
1098
|
+
var rb=document.querySelector('.oe-rem-btn')
|
|
1099
|
+
if(rb){rb.disabled=true;rb.textContent='Removed'}
|
|
1100
|
+
}else{s.textContent=res.error;s.className='ins-st err'}
|
|
1101
|
+
})
|
|
1102
|
+
.catch(function(){var s=document.getElementById('oe-st');if(s){s.textContent='Request failed';s.className='ins-st err'}})
|
|
1103
|
+
}
|
|
1104
|
+
function saveOmlElem(){
|
|
1105
|
+
if(!oeState)return
|
|
1106
|
+
var st=document.getElementById('oe-st')
|
|
1107
|
+
if(st){st.textContent='Saving...';st.className='ins-st'}
|
|
1108
|
+
fetch('/_davaux/update-element',{
|
|
1109
|
+
method:'POST',headers:{'Content-Type':'application/json'},
|
|
1110
|
+
body:JSON.stringify({page:pg.split('?')[0],tag:oeState.tag,newProps:oeState.props,instanceIndex:oeState.instanceIndex,textContent:oeState.textContent})
|
|
1111
|
+
})
|
|
1112
|
+
.then(function(r){return r.json()})
|
|
1113
|
+
.then(function(res){
|
|
1114
|
+
var s=document.getElementById('oe-st');if(!s)return
|
|
1115
|
+
if(res.replaced){
|
|
1116
|
+
s.textContent='Saved to '+res.file;s.className='ins-st ok'
|
|
1117
|
+
var b=document.getElementById('oe-save-btn')
|
|
1118
|
+
if(b){b.textContent='Saved!';b.classList.add('ok');setTimeout(function(){b.textContent='Save changes';b.classList.remove('ok')},2000)}
|
|
1119
|
+
}else{s.textContent=res.error;s.className='ins-st err'}
|
|
1120
|
+
})
|
|
1121
|
+
.catch(function(){var s=document.getElementById('oe-st');if(s){s.textContent='Request failed';s.className='ins-st err'}})
|
|
1122
|
+
}
|
|
1123
|
+
window.saveOmlElem=saveOmlElem
|
|
1124
|
+
|
|
1125
|
+
function movePanelNode(isComp,name,instanceIndex,dir){
|
|
1126
|
+
var stId=isComp?'oc-st':'oe-st'
|
|
1127
|
+
var st=document.getElementById(stId)
|
|
1128
|
+
if(st){st.textContent='Moving...';st.className='ins-st'}
|
|
1129
|
+
fetch('/_davaux/move',{
|
|
1130
|
+
method:'POST',headers:{'Content-Type':'application/json'},
|
|
1131
|
+
body:JSON.stringify({page:pg.split('?')[0],name:name,isComponent:isComp,instanceIndex:instanceIndex,direction:dir})
|
|
1132
|
+
})
|
|
1133
|
+
.then(function(r){return r.json()})
|
|
1134
|
+
.then(function(res){
|
|
1135
|
+
var s=document.getElementById(stId);if(!s)return
|
|
1136
|
+
if(res.moved){s.textContent='';s.className='ins-st'}
|
|
1137
|
+
else{s.textContent=res.error;s.className='ins-st err'}
|
|
1138
|
+
})
|
|
1139
|
+
.catch(function(){var s=document.getElementById(stId);if(s){s.textContent='Request failed';s.className='ins-st err'}})
|
|
1140
|
+
}
|
|
1141
|
+
window.movePanelNode=movePanelNode
|
|
1142
|
+
|
|
1143
|
+
function findByName(node,name){
|
|
1144
|
+
if(!node)return null
|
|
1145
|
+
if(node.type==='#component'&&node.name===name)return node
|
|
1146
|
+
var kids
|
|
1147
|
+
if(node.type==='element'||node.type==='#fragment'){kids=node.children||[]}
|
|
1148
|
+
else if(node.type==='#component'){kids=(node.output&&node.output.type!=='#raw')?[node.output]:(node.children||[])}
|
|
1149
|
+
else{kids=[]}
|
|
1150
|
+
for(var i=0;i<kids.length;i++){var f=findByName(kids[i],name);if(f)return f}
|
|
1151
|
+
return null
|
|
1152
|
+
}
|
|
1153
|
+
function findByOmlComp(root,name,inst){
|
|
1154
|
+
var state={n:0,found:null}
|
|
1155
|
+
walkFindOmlComp(root,name,inst,state)
|
|
1156
|
+
return state.found
|
|
1157
|
+
}
|
|
1158
|
+
function walkFindOmlComp(node,name,inst,state){
|
|
1159
|
+
if(!node||state.found)return
|
|
1160
|
+
if(node.type==='#component'){
|
|
1161
|
+
if(node.name===name){
|
|
1162
|
+
if(state.n===inst){state.found=node;return}
|
|
1163
|
+
state.n++
|
|
1164
|
+
}
|
|
1165
|
+
var sub=(node.output&&node.output.type!=='#raw')?[node.output]:(node.children||[])
|
|
1166
|
+
for(var i=0;i<sub.length;i++)walkFindOmlComp(sub[i],name,inst,state)
|
|
1167
|
+
}else if(node.type==='element'||node.type==='#fragment'){
|
|
1168
|
+
var kids=node.children||[]
|
|
1169
|
+
for(var i=0;i<kids.length;i++)walkFindOmlComp(kids[i],name,inst,state)
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
function nodeMatchesCriteria(node,tag,txt,cls){
|
|
1173
|
+
if(node.tag!==tag)return false
|
|
1174
|
+
var nc=(node.props&&node.props.class)||''
|
|
1175
|
+
// Class must match when provided — prevents div.grid from matching div.card.
|
|
1176
|
+
if(cls&&nc!==cls)return false
|
|
1177
|
+
// Text is always checked as a tiebreaker — prevents the first div.card from
|
|
1178
|
+
// matching when clicking a different div.card that shares the same class.
|
|
1179
|
+
if(txt){var nt=extractText(node).trim();if(nt.slice(0,30)!==txt.slice(0,30))return false}
|
|
1180
|
+
return true
|
|
1181
|
+
}
|
|
1182
|
+
function findInTree(node,tag,txt,cls){
|
|
1183
|
+
if(!node)return null
|
|
1184
|
+
// Elements: check children FIRST so we always return the deepest (most specific) match.
|
|
1185
|
+
if(node.type==='element'){
|
|
1186
|
+
var ekids=node.children||[]
|
|
1187
|
+
for(var i=0;i<ekids.length;i++){var ef=findInTree(ekids[i],tag,txt,cls);if(ef)return ef}
|
|
1188
|
+
if(nodeMatchesCriteria(node,tag,txt,cls))return node
|
|
1189
|
+
return null
|
|
1190
|
+
}
|
|
1191
|
+
if(node.type==='#component'){
|
|
1192
|
+
// For #raw-returning components, search in JSX children (the hierarchy the user wrote)
|
|
1193
|
+
if(node.output&&node.output.type==='#raw'){
|
|
1194
|
+
var jkids=node.children||[]
|
|
1195
|
+
for(var i=0;i<jkids.length;i++){var jf=findInTree(jkids[i],tag,txt,cls);if(jf)return jf}
|
|
1196
|
+
return null
|
|
1197
|
+
}
|
|
1198
|
+
if(node.output){
|
|
1199
|
+
var r=node.output
|
|
1200
|
+
if(r.type==='element'&&nodeMatchesCriteria(r,tag,txt,cls))return node
|
|
1201
|
+
var fc=findInTree(r,tag,txt,cls)
|
|
1202
|
+
if(fc)return node
|
|
1203
|
+
}
|
|
1204
|
+
return null
|
|
1205
|
+
}
|
|
1206
|
+
if(node.type==='#fragment'){
|
|
1207
|
+
var fkids=node.children||[]
|
|
1208
|
+
for(var i=0;i<fkids.length;i++){var ff=findInTree(fkids[i],tag,txt,cls);if(ff)return ff}
|
|
1209
|
+
return null
|
|
1210
|
+
}
|
|
1211
|
+
return null
|
|
1212
|
+
}
|
|
1213
|
+
function extractText(node){
|
|
1214
|
+
if(!node)return''
|
|
1215
|
+
if(node.type==='#text')return node.value
|
|
1216
|
+
if(node.type==='element'||node.type==='#fragment')return(node.children||[]).map(extractText).join('')
|
|
1217
|
+
if(node.type==='#component'){
|
|
1218
|
+
if(node.output&&node.output.type!=='#raw')return extractText(node.output)
|
|
1219
|
+
return(node.children||[]).map(extractText).join('')
|
|
1220
|
+
}
|
|
1221
|
+
return''
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// ── Tree Panel ────────────────────────────────────────────────────────────────
|
|
1225
|
+
function renderLayoutButtons(){
|
|
1226
|
+
var container=document.getElementById('layout-btns')
|
|
1227
|
+
if(!container)return
|
|
1228
|
+
container.innerHTML=''
|
|
1229
|
+
if(!pageLayouts.length)return
|
|
1230
|
+
pageLayouts.forEach(function(l){
|
|
1231
|
+
var btn=document.createElement('button')
|
|
1232
|
+
btn.className='tool-btn'
|
|
1233
|
+
btn.title=l.filePath
|
|
1234
|
+
var parts=l.filePath.replace(/\\\\/g,'/').split('/')
|
|
1235
|
+
var label=pageLayouts.length===1?'Layout':(parts.length>2?parts.slice(-2).join('/'):l.filePath)
|
|
1236
|
+
btn.textContent='♰ '+label
|
|
1237
|
+
btn.onclick=function(){openLayoutModal(l.filePath)}
|
|
1238
|
+
container.appendChild(btn)
|
|
1239
|
+
})
|
|
1240
|
+
}
|
|
1241
|
+
function openLayoutModal(filePath){
|
|
1242
|
+
srcModalMode='file'
|
|
1243
|
+
pageFilePath=filePath
|
|
1244
|
+
fetch('/_davaux/get-source?file='+encodeURIComponent(filePath))
|
|
1245
|
+
.then(function(r){return r.json()})
|
|
1246
|
+
.then(function(d){pageSourceContent=d.content||'';openSrcModal('file')})
|
|
1247
|
+
.catch(function(){})
|
|
1248
|
+
}
|
|
1249
|
+
function openPageSrcModal(){
|
|
1250
|
+
if(!pageOwnFilePath)return
|
|
1251
|
+
pageFilePath=pageOwnFilePath
|
|
1252
|
+
srcModalMode='file'
|
|
1253
|
+
fetch('/_davaux/get-source?file='+encodeURIComponent(pageOwnFilePath))
|
|
1254
|
+
.then(function(r){return r.json()})
|
|
1255
|
+
.then(function(d){pageSourceContent=d.content||'';openSrcModal('file')})
|
|
1256
|
+
.catch(function(){})
|
|
1257
|
+
}
|
|
1258
|
+
function enterSourceMode(){
|
|
1259
|
+
var root=document.getElementById('tree-root')
|
|
1260
|
+
if(root)root.innerHTML='<div class="empty">'+esc(pageFileType.toUpperCase())+' page</div>'
|
|
1261
|
+
var det=document.getElementById('det')
|
|
1262
|
+
if(det)det.innerHTML='<div class="no-sel">Click <b>Edit Source</b> in the toolbar to edit this page.</div>'
|
|
1263
|
+
var btn=document.getElementById('edit-src-btn')
|
|
1264
|
+
if(btn)btn.style.display=''
|
|
1265
|
+
if(!pageFilePath)return
|
|
1266
|
+
fetch('/_davaux/get-source?file='+encodeURIComponent(pageFilePath))
|
|
1267
|
+
.then(function(r){return r.json()})
|
|
1268
|
+
.then(function(d){pageSourceContent=d.content||''})
|
|
1269
|
+
.catch(function(){})
|
|
1270
|
+
}
|
|
1271
|
+
function openSrcModal(mode){
|
|
1272
|
+
if(mode)srcModalMode=mode
|
|
1273
|
+
var modal=document.getElementById('src-modal')
|
|
1274
|
+
var title=document.getElementById('src-modal-title')
|
|
1275
|
+
var ta=document.getElementById('src-ta')
|
|
1276
|
+
var saveBtn=document.getElementById('src-save-btn')
|
|
1277
|
+
var st=document.getElementById('src-st')
|
|
1278
|
+
var hlBtn=document.getElementById('src-hl-btn')
|
|
1279
|
+
if(!modal||!ta)return
|
|
1280
|
+
if(title)title.textContent=srcModalMode==='fragment'?('<'+srcFragmentComp+'> children'):srcModalMode==='element-fragment'?('<'+srcFragmentTag+'> children'):(pageFilePath||'')
|
|
1281
|
+
if(st)st.textContent=''
|
|
1282
|
+
if(saveBtn){saveBtn.disabled=false;saveBtn.onclick=saveSource}
|
|
1283
|
+
if(hlBtn)hlBtn.classList.toggle('active',srcHighlight)
|
|
1284
|
+
if(cmEditor&&cmEditor._dvxMode!==srcModalMode){cmEditor.destroy();cmEditor=null}
|
|
1285
|
+
if(cmEditor){
|
|
1286
|
+
try{cmEditor.dispatch({changes:{from:0,to:cmEditor.state.doc.length,insert:pageSourceContent}})}
|
|
1287
|
+
catch(_){cmEditor.destroy();cmEditor=null}
|
|
1288
|
+
}
|
|
1289
|
+
if(!cmEditor){
|
|
1290
|
+
ta.value=pageSourceContent
|
|
1291
|
+
ta.onkeydown=function(e){
|
|
1292
|
+
if(e.key==='Tab'){e.preventDefault();var s=this.selectionStart,en=this.selectionEnd;this.value=this.value.substring(0,s)+' '+this.value.substring(en);this.selectionStart=this.selectionEnd=s+2;pageSourceContent=this.value}
|
|
1293
|
+
if(e.key==='Escape'){closeSrcModal()}
|
|
1294
|
+
}
|
|
1295
|
+
ta.oninput=function(){pageSourceContent=this.value}
|
|
1296
|
+
if(srcHighlight)enableHighlight()
|
|
1297
|
+
}
|
|
1298
|
+
modal.style.display='flex'
|
|
1299
|
+
if(!cmEditor)ta.focus()
|
|
1300
|
+
}
|
|
1301
|
+
function closeSrcModal(){
|
|
1302
|
+
var modal=document.getElementById('src-modal')
|
|
1303
|
+
if(modal)modal.style.display='none'
|
|
1304
|
+
}
|
|
1305
|
+
function saveSource(){
|
|
1306
|
+
var btn=document.getElementById('src-save-btn'),st=document.getElementById('src-st')
|
|
1307
|
+
if(btn)btn.disabled=true
|
|
1308
|
+
if(st)st.textContent='Saving…'
|
|
1309
|
+
var body=srcModalMode==='fragment'
|
|
1310
|
+
?JSON.stringify({file:pageFilePath,component:srcFragmentComp,instanceIndex:srcFragmentIdx,content:pageSourceContent})
|
|
1311
|
+
:srcModalMode==='element-fragment'
|
|
1312
|
+
?JSON.stringify({file:pageFilePath,tag:srcFragmentTag,instanceIndex:srcFragmentIdx,content:pageSourceContent})
|
|
1313
|
+
:JSON.stringify({file:pageFilePath,content:pageSourceContent})
|
|
1314
|
+
var url=srcModalMode==='fragment'?'/_davaux/update-fragment':srcModalMode==='element-fragment'?'/_davaux/update-element-fragment':'/_davaux/update-source'
|
|
1315
|
+
fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:body})
|
|
1316
|
+
.then(function(r){return r.json()})
|
|
1317
|
+
.then(function(){if(st){st.textContent='Saved';setTimeout(function(){st.textContent=''},2000)}if(btn)btn.disabled=false})
|
|
1318
|
+
.catch(function(){if(st)st.textContent='Error saving';if(btn)btn.disabled=false})
|
|
1319
|
+
}
|
|
1320
|
+
async function enableHighlight(){
|
|
1321
|
+
var hlBtn=document.getElementById('src-hl-btn')
|
|
1322
|
+
var ta=document.getElementById('src-ta')
|
|
1323
|
+
var host=document.getElementById('src-cm-host')
|
|
1324
|
+
if(!ta||!host)return
|
|
1325
|
+
if(hlBtn){hlBtn.disabled=true;hlBtn.textContent='⟳ Loading…'}
|
|
1326
|
+
if(!cmModules){
|
|
1327
|
+
try{
|
|
1328
|
+
var [view,cmds,lang,langMd,langJs,thm]=await Promise.all([
|
|
1329
|
+
import('https://esm.sh/@codemirror/view@6'),
|
|
1330
|
+
import('https://esm.sh/@codemirror/commands@6'),
|
|
1331
|
+
import('https://esm.sh/@codemirror/language@6'),
|
|
1332
|
+
import('https://esm.sh/@codemirror/lang-markdown@6'),
|
|
1333
|
+
import('https://esm.sh/@codemirror/lang-javascript@6'),
|
|
1334
|
+
import('https://esm.sh/@codemirror/theme-one-dark@6'),
|
|
1335
|
+
])
|
|
1336
|
+
cmModules={EditorView:view.EditorView,lineNumbers:view.lineNumbers,keymap:view.keymap,drawSelection:view.drawSelection,highlightActiveLine:view.highlightActiveLine,history:cmds.history,defaultKeymap:cmds.defaultKeymap,historyKeymap:cmds.historyKeymap,syntaxHighlighting:lang.syntaxHighlighting,defaultHighlightStyle:lang.defaultHighlightStyle,indentOnInput:lang.indentOnInput,LanguageDescription:lang.LanguageDescription,markdown:langMd.markdown,markdownLanguage:langMd.markdownLanguage,javascript:langJs.javascript,oneDark:thm.oneDark}
|
|
1337
|
+
}catch(e){
|
|
1338
|
+
console.error('CodeMirror load failed',e)
|
|
1339
|
+
srcHighlight=false
|
|
1340
|
+
localStorage.setItem('dvx-src-hl','0')
|
|
1341
|
+
if(hlBtn){hlBtn.disabled=false;hlBtn.textContent='✦ Highlight';hlBtn.classList.remove('active')}
|
|
1342
|
+
return
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
var isDark=document.documentElement.dataset.dvxTheme!=='light'
|
|
1346
|
+
var isFragment=srcModalMode==='fragment'||srcModalMode==='element-fragment'
|
|
1347
|
+
var isTsx=!isFragment&&pageFilePath&&/\\.tsx?$/.test(pageFilePath)
|
|
1348
|
+
var m=cmModules
|
|
1349
|
+
var LD=m.LanguageDescription
|
|
1350
|
+
var activeLang=(isFragment||isTsx)
|
|
1351
|
+
?m.javascript({jsx:true,typescript:true})
|
|
1352
|
+
:m.markdown({base:m.markdownLanguage,codeLanguages:[
|
|
1353
|
+
LD.of({name:'JavaScript',alias:['js','javascript'],extensions:['js'],support:m.javascript()}),
|
|
1354
|
+
LD.of({name:'TypeScript',alias:['ts','typescript'],extensions:['ts'],support:m.javascript({typescript:true})}),
|
|
1355
|
+
LD.of({name:'JSX',alias:['jsx'],extensions:['jsx'],support:m.javascript({jsx:true})}),
|
|
1356
|
+
LD.of({name:'TSX',alias:['tsx'],extensions:['tsx'],support:m.javascript({jsx:true,typescript:true})}),
|
|
1357
|
+
]})
|
|
1358
|
+
cmEditor=new m.EditorView({
|
|
1359
|
+
doc:pageSourceContent,
|
|
1360
|
+
extensions:[
|
|
1361
|
+
m.lineNumbers(),
|
|
1362
|
+
m.history(),
|
|
1363
|
+
m.keymap.of([...m.defaultKeymap,...m.historyKeymap]),
|
|
1364
|
+
m.drawSelection(),
|
|
1365
|
+
m.highlightActiveLine(),
|
|
1366
|
+
m.indentOnInput(),
|
|
1367
|
+
m.syntaxHighlighting(m.defaultHighlightStyle),
|
|
1368
|
+
...(isDark?[m.oneDark]:[]),
|
|
1369
|
+
activeLang,
|
|
1370
|
+
m.EditorView.updateListener.of(function(upd){if(upd.docChanged)pageSourceContent=upd.state.doc.toString()}),
|
|
1371
|
+
m.EditorView.domEventHandlers({keydown:function(e){if(e.key==='Escape'){closeSrcModal();return true}}}),
|
|
1372
|
+
],
|
|
1373
|
+
parent:host
|
|
1374
|
+
})
|
|
1375
|
+
cmEditor._dvxDark=isDark
|
|
1376
|
+
cmEditor._dvxMode=srcModalMode
|
|
1377
|
+
ta.style.display='none'
|
|
1378
|
+
host.style.display='flex'
|
|
1379
|
+
host.style.flexDirection='column'
|
|
1380
|
+
if(hlBtn){hlBtn.disabled=false;hlBtn.textContent='✦ Highlight';hlBtn.classList.add('active')}
|
|
1381
|
+
}
|
|
1382
|
+
function openFragmentModal(){
|
|
1383
|
+
if(!ocState||!pageOwnFilePath)return
|
|
1384
|
+
pageFilePath=pageOwnFilePath
|
|
1385
|
+
srcFragmentComp=ocState.name
|
|
1386
|
+
srcFragmentIdx=ocState.instanceIndex
|
|
1387
|
+
srcModalMode='fragment'
|
|
1388
|
+
var det=document.getElementById('det')
|
|
1389
|
+
var st=document.getElementById('src-st')
|
|
1390
|
+
if(st)st.textContent=''
|
|
1391
|
+
fetch('/_davaux/get-fragment?file='+encodeURIComponent(pageOwnFilePath)+'&component='+encodeURIComponent(srcFragmentComp)+'&instanceIndex='+srcFragmentIdx)
|
|
1392
|
+
.then(function(r){return r.json()})
|
|
1393
|
+
.then(function(d){
|
|
1394
|
+
if(!d.found){if(det)det.innerHTML='<div class="no-sel" style="color:#ff8888">'+esc(d.error)+'</div>';return}
|
|
1395
|
+
pageSourceContent=d.content||''
|
|
1396
|
+
openSrcModal('fragment')
|
|
1397
|
+
})
|
|
1398
|
+
.catch(function(){if(det)det.innerHTML='<div class="no-sel" style="color:#ff8888">Could not load fragment.</div>'})
|
|
1399
|
+
}
|
|
1400
|
+
function openElementFragmentModal(){
|
|
1401
|
+
if(!oeState||!pageOwnFilePath)return
|
|
1402
|
+
pageFilePath=pageOwnFilePath
|
|
1403
|
+
srcFragmentTag=oeState.tag
|
|
1404
|
+
srcFragmentIdx=oeState.instanceIndex
|
|
1405
|
+
srcModalMode='element-fragment'
|
|
1406
|
+
var det=document.getElementById('det')
|
|
1407
|
+
fetch('/_davaux/get-element-fragment?file='+encodeURIComponent(pageOwnFilePath)+'&tag='+encodeURIComponent(srcFragmentTag)+'&instanceIndex='+srcFragmentIdx)
|
|
1408
|
+
.then(function(r){return r.json()})
|
|
1409
|
+
.then(function(d){
|
|
1410
|
+
if(!d.found){if(det)det.innerHTML='<div class="no-sel" style="color:#ff8888">'+esc(d.error)+'</div>';return}
|
|
1411
|
+
pageSourceContent=d.content||''
|
|
1412
|
+
openSrcModal('element-fragment')
|
|
1413
|
+
})
|
|
1414
|
+
.catch(function(){if(det)det.innerHTML='<div class="no-sel" style="color:#ff8888">Could not load fragment.</div>'})
|
|
1415
|
+
}
|
|
1416
|
+
function disableHighlight(){
|
|
1417
|
+
var ta=document.getElementById('src-ta')
|
|
1418
|
+
var host=document.getElementById('src-cm-host')
|
|
1419
|
+
if(cmEditor){pageSourceContent=cmEditor.state.doc.toString();cmEditor.destroy();cmEditor=null}
|
|
1420
|
+
if(ta){ta.value=pageSourceContent;ta.style.display='';ta.focus()}
|
|
1421
|
+
if(host){host.style.display='none';host.innerHTML=''}
|
|
1422
|
+
var hlBtn=document.getElementById('src-hl-btn')
|
|
1423
|
+
if(hlBtn){hlBtn.classList.remove('active');hlBtn.textContent='✦ Highlight'}
|
|
1424
|
+
}
|
|
1425
|
+
function toggleHighlight(){
|
|
1426
|
+
srcHighlight=!srcHighlight
|
|
1427
|
+
localStorage.setItem('dvx-src-hl',srcHighlight?'1':'0')
|
|
1428
|
+
if(srcHighlight)enableHighlight()
|
|
1429
|
+
else disableHighlight()
|
|
1430
|
+
}
|
|
1431
|
+
function renderTreePanel(){
|
|
1432
|
+
var root=document.getElementById('tree-root')
|
|
1433
|
+
if(!root)return
|
|
1434
|
+
if(!omlTree){root.innerHTML='<div class="empty">Load a page to see the tree.</div>';return}
|
|
1435
|
+
root.innerHTML=''
|
|
1436
|
+
var dom=buildTreeNode(omlTree,0,true)
|
|
1437
|
+
if(dom)root.appendChild(dom)
|
|
1438
|
+
else root.innerHTML='<div class="empty">Empty page.</div>'
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function buildTreeNode(node,depth,isRoot){
|
|
1442
|
+
if(!node)return null
|
|
1443
|
+
if(node.type==='#raw')return null
|
|
1444
|
+
if(node.type==='#text'){
|
|
1445
|
+
var v=String(node.value||'').trim()
|
|
1446
|
+
if(!v)return null
|
|
1447
|
+
var w=document.createElement('div')
|
|
1448
|
+
var row=document.createElement('div')
|
|
1449
|
+
row.className='tr-row'
|
|
1450
|
+
row.style.paddingLeft=(depth*12+4)+'px'
|
|
1451
|
+
row.innerHTML='<span class="tr-tog"> </span><span class="tr-lbl"><span class="tr-txt">"'+esc(v.length>28?v.slice(0,28)+'…':v)+'"</span></span>'
|
|
1452
|
+
w.appendChild(row)
|
|
1453
|
+
return w
|
|
1454
|
+
}
|
|
1455
|
+
var cs
|
|
1456
|
+
if(node.type==='#fragment'){
|
|
1457
|
+
cs=(node.children||[]).filter(Boolean)
|
|
1458
|
+
if(isRoot||depth===0){
|
|
1459
|
+
var w=document.createElement('div')
|
|
1460
|
+
cs.forEach(function(c){var ch=buildTreeNode(c,depth,false);if(ch)w.appendChild(ch)})
|
|
1461
|
+
return w
|
|
1462
|
+
}
|
|
1463
|
+
}else if(node.type==='#component'){
|
|
1464
|
+
// If return is #raw (library component), show JSX children hierarchy instead
|
|
1465
|
+
if(node.output&&node.output.type!=='#raw'){cs=[node.output]}
|
|
1466
|
+
else{cs=(node.children||[]).filter(Boolean)}
|
|
1467
|
+
}else{
|
|
1468
|
+
var allCs=(node.children||[]).filter(Boolean)
|
|
1469
|
+
var hasElemKids=allCs.some(function(c){return c.type!=='#text'&&c.type!=='#raw'})
|
|
1470
|
+
cs=hasElemKids?allCs.filter(function(c){return c.type!=='#text'&&c.type!=='#raw'}):[]
|
|
1471
|
+
}
|
|
1472
|
+
var hk=cs&&cs.length>0
|
|
1473
|
+
var collapsed=false
|
|
1474
|
+
var w=document.createElement('div')
|
|
1475
|
+
var row=document.createElement('div')
|
|
1476
|
+
row.className='tr-row'
|
|
1477
|
+
row.style.paddingLeft=(depth*12+4)+'px'
|
|
1478
|
+
var togSpan=document.createElement('span')
|
|
1479
|
+
togSpan.className='tr-tog'
|
|
1480
|
+
togSpan.textContent=hk?'▼':' '
|
|
1481
|
+
var lblSpan=document.createElement('span')
|
|
1482
|
+
lblSpan.className='tr-lbl'
|
|
1483
|
+
if(node.type==='element'){
|
|
1484
|
+
var cls=(node.props&&node.props.class)||''
|
|
1485
|
+
var fc=cls?'.'+cls.split(' ')[0]:''
|
|
1486
|
+
lblSpan.innerHTML='<span class="tr-tag"><'+esc(node.tag+fc)+'></span>'
|
|
1487
|
+
row._omlNode=node
|
|
1488
|
+
row.addEventListener('click',function(e){if(e.target===togSpan)return;selectFromTree(node)})
|
|
1489
|
+
}else if(node.type==='#component'){
|
|
1490
|
+
lblSpan.innerHTML='<span class="tr-comp">♢ '+esc(node.name)+'</span>'
|
|
1491
|
+
row._omlNode=node
|
|
1492
|
+
row.addEventListener('click',function(e){if(e.target===togSpan)return;selectFromTree(node)})
|
|
1493
|
+
}else{
|
|
1494
|
+
lblSpan.innerHTML='<span class="tr-frag"><></span>'
|
|
1495
|
+
row.addEventListener('click',function(){if(!hk)return;collapsed=!collapsed;kidsDiv.style.display=collapsed?'none':'';togSpan.textContent=collapsed?'▶':'▼'})
|
|
1496
|
+
}
|
|
1497
|
+
row.appendChild(togSpan)
|
|
1498
|
+
row.appendChild(lblSpan)
|
|
1499
|
+
w.appendChild(row)
|
|
1500
|
+
if(hk){
|
|
1501
|
+
var kidsDiv=document.createElement('div')
|
|
1502
|
+
cs.forEach(function(c){var ch=buildTreeNode(c,depth+1,false);if(ch)kidsDiv.appendChild(ch)})
|
|
1503
|
+
w.appendChild(kidsDiv)
|
|
1504
|
+
togSpan.addEventListener('click',function(e){
|
|
1505
|
+
e.stopPropagation()
|
|
1506
|
+
collapsed=!collapsed
|
|
1507
|
+
kidsDiv.style.display=collapsed?'none':''
|
|
1508
|
+
togSpan.textContent=collapsed?'▶':'▼'
|
|
1509
|
+
})
|
|
1510
|
+
}
|
|
1511
|
+
return w
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function selectFromTree(node){
|
|
1515
|
+
if(!node)return
|
|
1516
|
+
var det=document.getElementById('det')
|
|
1517
|
+
if(!det)return
|
|
1518
|
+
if(selected){try{selected.style.outline='';selected.style.outlineOffset=''}catch(_){}selected=null}
|
|
1519
|
+
detMode='comp'
|
|
1520
|
+
document.querySelectorAll('.dtab').forEach(function(t){t.classList.toggle('active',t.dataset.mode==='comp')})
|
|
1521
|
+
sel=-1
|
|
1522
|
+
document.querySelectorAll('.bp-card').forEach(function(c){c.classList.remove('sel')})
|
|
1523
|
+
if(node.type==='#component'){
|
|
1524
|
+
var treeCompIdx=countNameBefore(omlTree,node,node.name)
|
|
1525
|
+
var treeRawProps=Object.assign({},node.props||{})
|
|
1526
|
+
var chi=compChildrenInfo(node)
|
|
1527
|
+
ocState={name:node.name,props:treeRawProps,instanceIndex:treeCompIdx,childrenText:chi.childrenText,hasChildren:chi.hasChildren}
|
|
1528
|
+
oeState=null
|
|
1529
|
+
renderOcPanel(det)
|
|
1530
|
+
}else if(node.type==='element'){
|
|
1531
|
+
var idx=countTagBefore(omlTree,node,node.tag)
|
|
1532
|
+
var elemText=null
|
|
1533
|
+
var allCs=(node.children||[]).filter(Boolean)
|
|
1534
|
+
var onlyTxt=allCs.length>0&&allCs.every(function(c){return c.type==='#text'})
|
|
1535
|
+
if(onlyTxt){var acc='';allCs.forEach(function(c){acc+=c.value||''});if(acc.trim())elemText=acc}
|
|
1536
|
+
oeState={tag:node.tag,props:Object.assign({},node.props||{}),instanceIndex:idx,textContent:elemText}
|
|
1537
|
+
ocState=null
|
|
1538
|
+
renderOePanel(det)
|
|
1539
|
+
}
|
|
1540
|
+
try{
|
|
1541
|
+
var domEl=findDomElForNode(node)
|
|
1542
|
+
if(domEl){
|
|
1543
|
+
selected=domEl
|
|
1544
|
+
domEl.style.outline='2px solid #7cc5f0'
|
|
1545
|
+
domEl.style.outlineOffset='-1px'
|
|
1546
|
+
domEl.scrollIntoView({behavior:'smooth',block:'nearest'})
|
|
1547
|
+
}
|
|
1548
|
+
}catch(_){}
|
|
1549
|
+
syncTreeRow(node)
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function findDomElForNode(node){
|
|
1553
|
+
if(!node)return null
|
|
1554
|
+
var doc=frame.contentDocument
|
|
1555
|
+
if(!doc)return null
|
|
1556
|
+
if(node.type==='#component'){
|
|
1557
|
+
var islandEl=doc.querySelector('[data-island="'+node.name+'"]')
|
|
1558
|
+
if(islandEl)return islandEl
|
|
1559
|
+
var compInst=countNameBefore(omlTree,node,node.name)
|
|
1560
|
+
var compEls=doc.querySelectorAll('[data-oml-comp="'+node.name+'"]')
|
|
1561
|
+
return compEls[compInst]||null
|
|
1562
|
+
}
|
|
1563
|
+
if(node.type==='element'){
|
|
1564
|
+
var tag=node.tag
|
|
1565
|
+
var cls=(node.props&&node.props.class)||''
|
|
1566
|
+
var txt=extractText(node).trim().slice(0,30)
|
|
1567
|
+
var els=Array.from(doc.querySelectorAll(tag))
|
|
1568
|
+
for(var i=0;i<els.length;i++){
|
|
1569
|
+
var el=els[i]
|
|
1570
|
+
var elCls=el.getAttribute?el.getAttribute('class')||'':''
|
|
1571
|
+
var elTxt=(el.textContent||'').trim().slice(0,30)
|
|
1572
|
+
if((!cls||elCls===cls)&&(!txt||elTxt===txt))return el
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
return null
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
function syncTreeRow(node){
|
|
1579
|
+
if(selectedTreeRow){selectedTreeRow.classList.remove('tr-sel');selectedTreeRow=null}
|
|
1580
|
+
if(!node)return
|
|
1581
|
+
var root=document.getElementById('tree-root')
|
|
1582
|
+
if(!root)return
|
|
1583
|
+
var rows=root.querySelectorAll('.tr-row')
|
|
1584
|
+
for(var i=0;i<rows.length;i++){
|
|
1585
|
+
if(rows[i]._omlNode===node){
|
|
1586
|
+
selectedTreeRow=rows[i]
|
|
1587
|
+
selectedTreeRow.classList.add('tr-sel')
|
|
1588
|
+
try{selectedTreeRow.scrollIntoView({block:'nearest'})}catch(_){}
|
|
1589
|
+
break
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// ── Route picker ─────────────────────────────────────────────────────────────
|
|
1595
|
+
fetch('/_davaux/routes').then(function(r){return r.json()}).then(function(routes){
|
|
1596
|
+
var dl=document.getElementById('route-list')
|
|
1597
|
+
routes.forEach(function(r){
|
|
1598
|
+
var opt=document.createElement('option')
|
|
1599
|
+
opt.value=r.urlPattern
|
|
1600
|
+
dl.appendChild(opt)
|
|
1601
|
+
})
|
|
1602
|
+
}).catch(function(){})
|
|
1603
|
+
|
|
1604
|
+
// ── Blueprint palette ─────────────────────────────────────────────────────────
|
|
1605
|
+
function refreshBps(){
|
|
1606
|
+
fetch('/_davaux/blueprints').then(function(r){return r.json()}).then(function(data){
|
|
1607
|
+
bps=data
|
|
1608
|
+
var list=document.getElementById('bplist')
|
|
1609
|
+
if(!bps.length){list.innerHTML='<div class="empty">No blueprints found.<br>Add .oml.json files to<br>src/blueprints/</div>';return}
|
|
1610
|
+
list.innerHTML=''
|
|
1611
|
+
bps.forEach(function(bp,i){
|
|
1612
|
+
var card=document.createElement('div')
|
|
1613
|
+
card.className='bp-card'
|
|
1614
|
+
if(bp.previewHtml){var pv=document.createElement('div');pv.className='bp-prev';pv.innerHTML=bp.previewHtml;card.appendChild(pv)}
|
|
1615
|
+
var lbl=document.createElement('div');lbl.className='bp-lbl';lbl.textContent=bp.name;card.appendChild(lbl)
|
|
1616
|
+
card.onclick=function(){pick(i)}
|
|
1617
|
+
list.appendChild(card)
|
|
1618
|
+
})
|
|
1619
|
+
}).catch(function(){document.getElementById('bplist').innerHTML='<div class="empty">Failed to load.</div>'})
|
|
1620
|
+
}
|
|
1621
|
+
refreshBps()
|
|
1622
|
+
|
|
1623
|
+
// ── Blueprint editor ──────────────────────────────────────────────────────────
|
|
1624
|
+
var editBp=null
|
|
1625
|
+
var ocState=null
|
|
1626
|
+
var oeState=null
|
|
1627
|
+
function startEdit(i){
|
|
1628
|
+
var bp=bps[i]
|
|
1629
|
+
editBp={idx:i,id:bp.id,name:bp.name,rows:Object.entries(bp.props||{}).map(function(e){
|
|
1630
|
+
var s=e[1];return{name:e[0],type:s.type||'string',required:!!s.required,def:s.default!==undefined?String(s.default):''}
|
|
1631
|
+
})}
|
|
1632
|
+
renderEditPanel()
|
|
1633
|
+
}
|
|
1634
|
+
window.startEdit=startEdit
|
|
1635
|
+
function renderEditPanel(){
|
|
1636
|
+
if(!editBp)return
|
|
1637
|
+
var det=document.getElementById('det')
|
|
1638
|
+
det.innerHTML=
|
|
1639
|
+
'<div style="display:flex;align-items:center;gap:6px;margin-bottom:10px">'
|
|
1640
|
+
+'<div id="ep-title" class="d-title" style="margin:0;flex:1">'+esc(editBp.name)+'</div>'
|
|
1641
|
+
+'</div>'
|
|
1642
|
+
+'<div class="d-lbl">NAME</div>'
|
|
1643
|
+
+'<input id="ep-bpname" class="pi" style="margin-bottom:8px" value="'+esc(editBp.name)+'">'
|
|
1644
|
+
+'<div class="d-lbl">PROPS</div>'
|
|
1645
|
+
+'<div id="ep-list"></div>'
|
|
1646
|
+
+'<button class="cbtn" style="margin-bottom:8px" onclick="epAdd()">+ Add prop</button>'
|
|
1647
|
+
+'<div style="display:flex;gap:4px;margin-bottom:4px">'
|
|
1648
|
+
+'<button class="cbtn ebtn" onclick="epSave()">Save</button>'
|
|
1649
|
+
+'<button class="cbtn" style="background:#1a1a2e;flex:1" onclick="epCancel()">Cancel</button>'
|
|
1650
|
+
+'</div>'
|
|
1651
|
+
+'<div id="ep-st" class="ins-st"></div>'
|
|
1652
|
+
renderEpRows()
|
|
1653
|
+
document.getElementById('ep-bpname').addEventListener('input',function(){
|
|
1654
|
+
editBp.name=this.value
|
|
1655
|
+
var t=document.getElementById('ep-title');if(t)t.textContent=this.value
|
|
1656
|
+
})
|
|
1657
|
+
}
|
|
1658
|
+
function renderEpRows(){
|
|
1659
|
+
var list=document.getElementById('ep-list');if(!list)return
|
|
1660
|
+
list.innerHTML=''
|
|
1661
|
+
editBp.rows.forEach(function(row,idx){
|
|
1662
|
+
var d=document.createElement('div');d.className='ep-row'
|
|
1663
|
+
var ni=document.createElement('input');ni.type='text';ni.className='pi ep-name';ni.value=row.name;ni.placeholder='name'
|
|
1664
|
+
ni.addEventListener('input',function(){editBp.rows[idx].name=this.value})
|
|
1665
|
+
var ts=document.createElement('select');ts.className='ep-type'
|
|
1666
|
+
;['string','number','boolean','function','node','array'].forEach(function(t){
|
|
1667
|
+
var o=document.createElement('option');o.value=t;o.textContent=t;if(t===row.type)o.selected=true;ts.appendChild(o)
|
|
1668
|
+
})
|
|
1669
|
+
ts.addEventListener('change',function(){editBp.rows[idx].type=this.value})
|
|
1670
|
+
var rl=document.createElement('label');rl.className='ep-req-lbl'
|
|
1671
|
+
var rc=document.createElement('input');rc.type='checkbox';rc.className='pi-cb';rc.checked=row.required
|
|
1672
|
+
rc.addEventListener('change',function(){editBp.rows[idx].required=this.checked})
|
|
1673
|
+
rl.appendChild(rc);rl.appendChild(Object.assign(document.createElement('span'),{textContent:'req'}))
|
|
1674
|
+
var di=document.createElement('input');di.type='text';di.className='pi ep-def';di.value=row.def;di.placeholder='default'
|
|
1675
|
+
di.addEventListener('input',function(){editBp.rows[idx].def=this.value})
|
|
1676
|
+
var rm=document.createElement('button');rm.className='ep-rm';rm.innerHTML='✕';rm.title='Remove'
|
|
1677
|
+
rm.addEventListener('click',function(){editBp.rows.splice(idx,1);renderEpRows()})
|
|
1678
|
+
d.appendChild(ni);d.appendChild(ts);d.appendChild(rl);d.appendChild(di);d.appendChild(rm)
|
|
1679
|
+
list.appendChild(d)
|
|
1680
|
+
})
|
|
1681
|
+
}
|
|
1682
|
+
window.epAdd=function(){
|
|
1683
|
+
editBp.rows.push({name:'',type:'string',required:false,def:''})
|
|
1684
|
+
renderEpRows()
|
|
1685
|
+
var list=document.getElementById('ep-list')
|
|
1686
|
+
if(list){var ins=list.querySelectorAll('.ep-name');if(ins.length)ins[ins.length-1].focus()}
|
|
1687
|
+
}
|
|
1688
|
+
window.epCancel=function(){
|
|
1689
|
+
editBp=null
|
|
1690
|
+
if(sel>=0)renderBpDet(bps[sel])
|
|
1691
|
+
else document.getElementById('det').innerHTML='<div class="no-sel">Select a component to see details.</div>'
|
|
1692
|
+
}
|
|
1693
|
+
window.epSave=function(){
|
|
1694
|
+
var st=document.getElementById('ep-st')
|
|
1695
|
+
if(st){st.textContent='Saving...';st.className='ins-st'}
|
|
1696
|
+
var props={}
|
|
1697
|
+
editBp.rows.forEach(function(row){
|
|
1698
|
+
var n=row.name.trim();if(!n)return
|
|
1699
|
+
var s={type:row.type,required:row.required}
|
|
1700
|
+
if(row.def!==''){
|
|
1701
|
+
if(row.type==='boolean')s.default=row.def==='true'
|
|
1702
|
+
else if(row.type==='number')s.default=Number(row.def)
|
|
1703
|
+
else s.default=row.def
|
|
1704
|
+
}
|
|
1705
|
+
props[n]=s
|
|
1706
|
+
})
|
|
1707
|
+
fetch('/_davaux/update-blueprint',{
|
|
1708
|
+
method:'POST',headers:{'Content-Type':'application/json'},
|
|
1709
|
+
body:JSON.stringify({id:editBp.id,name:editBp.name,props:props})
|
|
1710
|
+
})
|
|
1711
|
+
.then(function(r){return r.json()})
|
|
1712
|
+
.then(function(res){
|
|
1713
|
+
if(res.saved){
|
|
1714
|
+
editBp=null;sel=-1
|
|
1715
|
+
document.getElementById('det').innerHTML='<div class="no-sel">Blueprint saved.</div>'
|
|
1716
|
+
refreshBps()
|
|
1717
|
+
}else{if(st){st.textContent=res.error||'Save failed';st.className='ins-st err'}}
|
|
1718
|
+
})
|
|
1719
|
+
.catch(function(){if(st){st.textContent='Request failed';st.className='ins-st err'}})
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// ── Prop editing ──────────────────────────────────────────────────────────────
|
|
1723
|
+
function pick(i){
|
|
1724
|
+
detMode='comp'
|
|
1725
|
+
document.querySelectorAll('.dtab').forEach(function(t){t.classList.toggle('active',t.dataset.mode==='comp')})
|
|
1726
|
+
sel=i
|
|
1727
|
+
overrides={}
|
|
1728
|
+
ocState=null
|
|
1729
|
+
document.querySelectorAll('.bp-card').forEach(function(c,j){c.classList.toggle('sel',j===i)})
|
|
1730
|
+
renderBpDet(bps[i])
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function renderBpDet(bp){
|
|
1734
|
+
var pe=Object.entries(bp.props||{})
|
|
1735
|
+
var h='<div style="display:flex;align-items:center;gap:6px;margin-bottom:12px"><div class="d-title" style="margin:0;flex:1">'+esc(bp.name)+'</div><button class="cbtn ebtn" style="padding:2px 8px;font-size:10px;width:auto" onclick="startEdit('+sel+')">Edit</button></div>'
|
|
1736
|
+
if(pe.length){
|
|
1737
|
+
h+='<div class="d-lbl">PROPS</div>'
|
|
1738
|
+
pe.forEach(function(e){
|
|
1739
|
+
var name=e[0],schema=e[1]
|
|
1740
|
+
var def=schema.default!==undefined?schema.default:defVal(schema.type)
|
|
1741
|
+
h+='<div class="pi-row"><label class="pi-lbl"><span class="pn">'+esc(name)+'</span>'
|
|
1742
|
+
+'<span class="pt"> '+esc(schema.type)+'</span>'
|
|
1743
|
+
+(schema.required?'<span class="pr"> *</span>':'')+'</label>'
|
|
1744
|
+
if(schema.type==='boolean'){
|
|
1745
|
+
h+='<input type="checkbox" class="pi pi-cb" data-prop="'+esc(name)+'" data-type="boolean"'+(def?' checked':'')+'>'
|
|
1746
|
+
}else if(schema.type==='number'){
|
|
1747
|
+
h+='<input type="number" class="pi" data-prop="'+esc(name)+'" data-type="number" value="'+esc(String(def||0))+'">'
|
|
1748
|
+
}else{
|
|
1749
|
+
h+='<input type="text" class="pi" data-prop="'+esc(name)+'" data-type="string" value="'+esc(String(def||''))+'">'
|
|
1750
|
+
}
|
|
1751
|
+
h+='</div>'
|
|
1752
|
+
})
|
|
1753
|
+
}
|
|
1754
|
+
h+='<div class="d-lbl">JSX</div>'
|
|
1755
|
+
h+='<div class="jsx-block" id="jsx-out">'+esc(buildJsx(bp))+'</div>'
|
|
1756
|
+
h+='<button class="cbtn" id="cpbtn" onclick="cp()">Copy JSX</button>'
|
|
1757
|
+
var insLabel=insertTarget?('Insert after <'+(insertTarget.name||insertTarget.tag)+'>'):'Insert into page'
|
|
1758
|
+
h+='<button class="cbtn ibtn" id="ibtn" onclick="ins()">'+insLabel+'</button>'
|
|
1759
|
+
h+='<div id="ins-st" class="ins-st"></div>'
|
|
1760
|
+
var det=document.getElementById('det')
|
|
1761
|
+
det.innerHTML=h
|
|
1762
|
+
det.querySelectorAll('.pi').forEach(function(inp){
|
|
1763
|
+
inp.addEventListener('input',function(){
|
|
1764
|
+
var prop=this.dataset.prop,type=this.dataset.type
|
|
1765
|
+
overrides[prop]=type==='boolean'?this.checked:type==='number'?Number(this.value):this.value
|
|
1766
|
+
var out=document.getElementById('jsx-out')
|
|
1767
|
+
if(out)out.textContent=buildJsx(bps[sel])
|
|
1768
|
+
})
|
|
1769
|
+
})
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function buildJsx(bp){
|
|
1773
|
+
var pe=Object.entries(bp.props||{})
|
|
1774
|
+
if(!pe.length)return'<'+bp.name+' />'
|
|
1775
|
+
var lines=pe.map(function(e){
|
|
1776
|
+
var name=e[0],schema=e[1]
|
|
1777
|
+
var val=overrides[name]!==undefined?overrides[name]:(schema.default!==undefined?schema.default:defVal(schema.type))
|
|
1778
|
+
return' '+name+'='+fmtVal(schema.type,val)
|
|
1779
|
+
})
|
|
1780
|
+
return'<'+bp.name+NL+lines.join(NL)+NL+'/>'
|
|
1781
|
+
}
|
|
1782
|
+
function fmtVal(type,val){
|
|
1783
|
+
if(type==='string')return JSON.stringify(String(val))
|
|
1784
|
+
if(type==='boolean')return'{'+(val?'true':'false')+'}'
|
|
1785
|
+
if(type==='number')return'{'+Number(val)+'}'
|
|
1786
|
+
return'{'+JSON.stringify(val)+'}'
|
|
1787
|
+
}
|
|
1788
|
+
function defVal(type){
|
|
1789
|
+
return{string:'example',number:0,boolean:false,function:'() => {}',node:'content',array:[]}[type]??'example'
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
function switchMode(m){
|
|
1793
|
+
detMode=m
|
|
1794
|
+
document.querySelectorAll('.dtab').forEach(function(t){t.classList.toggle('active',t.dataset.mode===m)})
|
|
1795
|
+
if(m==='styles'){renderStylesPanel()}
|
|
1796
|
+
else{
|
|
1797
|
+
if(sel>=0)renderBpDet(bps[sel])
|
|
1798
|
+
else document.getElementById('det').innerHTML='<div class="no-sel">Select a component to see details.</div>'
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
function detectPageTheme(doc){
|
|
1802
|
+
try{
|
|
1803
|
+
var forced=window.__DVX_EDITOR__&&window.__DVX_EDITOR__.theme
|
|
1804
|
+
if(forced==='dark'||forced==='light'){document.documentElement.dataset.dvxTheme=forced;return}
|
|
1805
|
+
var html=doc&&doc.documentElement
|
|
1806
|
+
if(!html)return
|
|
1807
|
+
var scheme=html.dataset.mantineColorScheme||html.dataset.colorScheme||html.dataset.theme||''
|
|
1808
|
+
var dark=scheme==='dark'||(!scheme&&window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches)
|
|
1809
|
+
document.documentElement.dataset.dvxTheme=dark?'dark':'light'
|
|
1810
|
+
// Swap CodeMirror theme if active
|
|
1811
|
+
if(cmEditor&&cmModules){
|
|
1812
|
+
var wasDark=cmEditor._dvxDark
|
|
1813
|
+
var nowDark=dark
|
|
1814
|
+
if(wasDark!==nowDark){
|
|
1815
|
+
disableHighlight()
|
|
1816
|
+
if(srcHighlight)enableHighlight()
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
}catch(_){}
|
|
1820
|
+
}
|
|
1821
|
+
function scanCssVars(doc){
|
|
1822
|
+
var vars={}
|
|
1823
|
+
try{
|
|
1824
|
+
for(var i=0;i<doc.styleSheets.length;i++){
|
|
1825
|
+
var sheet=doc.styleSheets[i]
|
|
1826
|
+
try{
|
|
1827
|
+
var rules=sheet.cssRules
|
|
1828
|
+
for(var j=0;j<rules.length;j++){
|
|
1829
|
+
var rule=rules[j]
|
|
1830
|
+
if(rule.selectorText===':root'||rule.selectorText==='html'){
|
|
1831
|
+
var style=rule.style
|
|
1832
|
+
for(var k=0;k<style.length;k++){
|
|
1833
|
+
var prop=style[k]
|
|
1834
|
+
if(prop.charAt(0)==='-'&&prop.charAt(1)==='-'){
|
|
1835
|
+
var cv=style.getPropertyValue(prop).trim()
|
|
1836
|
+
vars[prop]={value:cv,type:detectVarType(cv)}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}catch(_){}
|
|
1842
|
+
}
|
|
1843
|
+
}catch(_){}
|
|
1844
|
+
cssVarMap=vars
|
|
1845
|
+
}
|
|
1846
|
+
function detectVarType(val){
|
|
1847
|
+
var v=val.trim()
|
|
1848
|
+
if(/^#[0-9a-fA-F]{3,8}$/.test(v))return'color'
|
|
1849
|
+
if(v.indexOf('rgb')===0||v.indexOf('hsl')===0)return'color-fn'
|
|
1850
|
+
if(/^-?[0-9]+([.][0-9]+)?(px|rem|em|%|vw|vh|ch|ex|pt|fr)$/.test(v))return'size'
|
|
1851
|
+
if(/^-?[0-9]+([.][0-9]+)?$/.test(v))return'number'
|
|
1852
|
+
return'text'
|
|
1853
|
+
}
|
|
1854
|
+
var STYLE_GROUPS=[
|
|
1855
|
+
{label:'TYPOGRAPHY',props:['color','font-size','font-weight','line-height','text-align']},
|
|
1856
|
+
{label:'SPACING',props:['padding','margin','gap']},
|
|
1857
|
+
{label:'BACKGROUND',props:['background-color']},
|
|
1858
|
+
{label:'BORDER',props:['border-radius','border-width','border-color']},
|
|
1859
|
+
{label:'LAYOUT',props:['display','width','height','max-width']},
|
|
1860
|
+
]
|
|
1861
|
+
function isVisibleColor(v){var t=v.trim();return(/^#/.test(t)||/^rgb/.test(t)||/^hsl/.test(t))&&t!=='rgba(0, 0, 0, 0)'&&t!=='transparent'}
|
|
1862
|
+
function renderStylePropRow(prop,computed,current){
|
|
1863
|
+
var showPicker=isVisibleColor(computed||'')&&/color|background/.test(prop)
|
|
1864
|
+
var pick=current&&isVisibleColor(current)?current:isVisibleColor(computed||'')?computed:''
|
|
1865
|
+
var h='<div class="cv-row">'
|
|
1866
|
+
h+='<div class="cv-name">'+esc(prop)+'</div>'
|
|
1867
|
+
if(showPicker){
|
|
1868
|
+
h+='<div class="cv-color-row">'
|
|
1869
|
+
h+='<input type="color" class="sty-inp cv-color" data-prop="'+esc(prop)+'" data-role="cp" value="'+esc(pick)+'">'
|
|
1870
|
+
h+='<input type="text" class="sty-inp cv-text" data-prop="'+esc(prop)+'" data-role="tx" placeholder="'+esc((computed||'').slice(0,24))+'" value="'+esc(current)+'">'
|
|
1871
|
+
h+='</div>'
|
|
1872
|
+
}else{
|
|
1873
|
+
h+='<input type="text" class="sty-inp cv-text" data-prop="'+esc(prop)+'" placeholder="'+esc((computed||'').slice(0,28))+'" value="'+esc(current)+'">'
|
|
1874
|
+
}
|
|
1875
|
+
h+='</div>'
|
|
1876
|
+
return h
|
|
1877
|
+
}
|
|
1878
|
+
function renderElementStylesSection(cs){
|
|
1879
|
+
var h='<div class="d-lbl">ELEMENT STYLES</div>'
|
|
1880
|
+
STYLE_GROUPS.forEach(function(g){
|
|
1881
|
+
var rows=g.props.map(function(p){
|
|
1882
|
+
var computed=''
|
|
1883
|
+
try{computed=cs.getPropertyValue(p)||''}catch(_){}
|
|
1884
|
+
var current=styleEdits[p]!==undefined?styleEdits[p]:(selected.style.getPropertyValue(p)||'')
|
|
1885
|
+
return renderStylePropRow(p,computed,current)
|
|
1886
|
+
}).join('')
|
|
1887
|
+
h+='<div class="d-lbl" style="font-size:9px;margin:8px 0 4px">'+g.label+'</div>'+rows
|
|
1888
|
+
})
|
|
1889
|
+
h+='<div style="display:flex;gap:4px;margin-top:8px">'
|
|
1890
|
+
h+='<button class="cbtn" id="style-save-btn" onclick="saveInlineStyles()">Apply Styles</button>'
|
|
1891
|
+
h+='</div>'
|
|
1892
|
+
h+='<div id="style-st" class="ins-st"></div>'
|
|
1893
|
+
h+='<div class="d-lbl" style="margin-top:16px">CSS VARIABLES</div>'
|
|
1894
|
+
return h
|
|
1895
|
+
}
|
|
1896
|
+
function renderStylesPanel(){
|
|
1897
|
+
var det=document.getElementById('det')
|
|
1898
|
+
var h=''
|
|
1899
|
+
if(selected){
|
|
1900
|
+
try{
|
|
1901
|
+
var cs=frame.contentDocument.defaultView.getComputedStyle(selected)
|
|
1902
|
+
h+=renderElementStylesSection(cs)
|
|
1903
|
+
}catch(_){}
|
|
1904
|
+
}
|
|
1905
|
+
var names=Object.keys(cssVarMap)
|
|
1906
|
+
if(!names.length&&!h){det.innerHTML='<div class="no-sel">Select an element to inspect its styles,<br>or add CSS custom properties on :root.</div>';return}
|
|
1907
|
+
if(names.length){
|
|
1908
|
+
if(!selected)h+='<div class="d-lbl">CSS VARIABLES</div>'
|
|
1909
|
+
var colors=names.filter(function(n){return cssVarMap[n].type==='color'})
|
|
1910
|
+
var colorFns=names.filter(function(n){return cssVarMap[n].type==='color-fn'})
|
|
1911
|
+
var sizes=names.filter(function(n){return cssVarMap[n].type==='size'||cssVarMap[n].type==='number'})
|
|
1912
|
+
var texts=names.filter(function(n){return cssVarMap[n].type==='text'})
|
|
1913
|
+
if(colors.length+colorFns.length){h+='<div class="d-lbl" style="font-size:9px;margin:8px 0 4px">COLORS</div>';colors.concat(colorFns).forEach(function(n){h+=renderVarRow(n)})}
|
|
1914
|
+
if(sizes.length){h+='<div class="d-lbl" style="font-size:9px;margin:8px 0 4px">SIZES</div>';sizes.forEach(function(n){h+=renderVarRow(n)})}
|
|
1915
|
+
if(texts.length){h+='<div class="d-lbl" style="font-size:9px;margin:8px 0 4px">OTHER</div>';texts.forEach(function(n){h+=renderVarRow(n)})}
|
|
1916
|
+
h+='<button class="cbtn" id="css-cpbtn" onclick="ccss()" style="margin-top:12px">Copy CSS</button>'
|
|
1917
|
+
}
|
|
1918
|
+
det.innerHTML=h
|
|
1919
|
+
// Wire CSS var inputs
|
|
1920
|
+
det.querySelectorAll('.cv-inp').forEach(function(inp){
|
|
1921
|
+
inp.addEventListener('input',function(){
|
|
1922
|
+
var name=this.dataset.var
|
|
1923
|
+
var val=this.value
|
|
1924
|
+
cssVarMap[name].value=val
|
|
1925
|
+
var companion=det.querySelector('[data-var="'+name+'"][data-role="'+(this.dataset.role==='cp'?'tx':'cp')+'"]')
|
|
1926
|
+
if(companion)companion.value=val
|
|
1927
|
+
try{frame.contentDocument.documentElement.style.setProperty(name,val)}catch(_){}
|
|
1928
|
+
})
|
|
1929
|
+
})
|
|
1930
|
+
// Wire element style inputs — live preview
|
|
1931
|
+
det.querySelectorAll('.sty-inp').forEach(function(inp){
|
|
1932
|
+
inp.addEventListener('input',function(){
|
|
1933
|
+
var prop=this.dataset.prop
|
|
1934
|
+
var val=this.value.trim()
|
|
1935
|
+
styleEdits[prop]=val
|
|
1936
|
+
if(this.dataset.role){
|
|
1937
|
+
var companion=det.querySelector('[data-prop="'+prop+'"][data-role="'+(this.dataset.role==='cp'?'tx':'cp')+'"]')
|
|
1938
|
+
if(companion)companion.value=val
|
|
1939
|
+
}
|
|
1940
|
+
try{if(val)selected.style.setProperty(prop,val);else selected.style.removeProperty(prop)}catch(_){}
|
|
1941
|
+
})
|
|
1942
|
+
})
|
|
1943
|
+
}
|
|
1944
|
+
function saveInlineStyles(){
|
|
1945
|
+
if(!oeState&&!ocState)return
|
|
1946
|
+
var det=document.getElementById('det')
|
|
1947
|
+
var st=document.getElementById('style-st')
|
|
1948
|
+
if(st){st.textContent='Saving...';st.className='ins-st'}
|
|
1949
|
+
// Build style string from edits
|
|
1950
|
+
var styleObj={}
|
|
1951
|
+
var existing=(oeState&&oeState.props&&oeState.props.style)||(ocState&&ocState.props&&ocState.props.style)||''
|
|
1952
|
+
if(typeof existing==='string'){existing.split(';').forEach(function(p){var i=p.indexOf(':');if(i>-1){var k=p.slice(0,i).trim(),v=p.slice(i+1).trim();if(k)styleObj[k]=v}})}
|
|
1953
|
+
det.querySelectorAll('.sty-inp[data-role="tx"],.sty-inp:not([data-role])').forEach(function(inp){
|
|
1954
|
+
var prop=inp.dataset.prop,val=inp.value.trim()
|
|
1955
|
+
if(val)styleObj[prop]=val;else delete styleObj[prop]
|
|
1956
|
+
})
|
|
1957
|
+
var styleStr=Object.entries(styleObj).map(function(e){return e[0]+': '+e[1]}).join('; ')
|
|
1958
|
+
var url,body
|
|
1959
|
+
if(oeState){
|
|
1960
|
+
var np=Object.assign({},oeState.props);if(styleStr)np.style=styleStr;else delete np.style
|
|
1961
|
+
url='/_davaux/update-element'
|
|
1962
|
+
body=JSON.stringify({page:pg.split('?')[0],tag:oeState.tag,newProps:np,instanceIndex:oeState.instanceIndex,textContent:oeState.textContent})
|
|
1963
|
+
}else{
|
|
1964
|
+
var np=Object.assign({},ocState.props);if(styleStr)np.style=styleStr;else delete np.style
|
|
1965
|
+
url='/_davaux/update-comp-props'
|
|
1966
|
+
body=JSON.stringify({page:pg.split('?')[0],component:ocState.name,newProps:np,instanceIndex:ocState.instanceIndex,childrenText:ocState.childrenText})
|
|
1967
|
+
}
|
|
1968
|
+
fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:body})
|
|
1969
|
+
.then(function(r){return r.json()})
|
|
1970
|
+
.then(function(res){
|
|
1971
|
+
var ok=res.replaced||res.inserted
|
|
1972
|
+
if(st){st.textContent=ok?'Saved':(res.error||'Error');st.className='ins-st '+(ok?'ok':'err');setTimeout(function(){st.textContent=''},2000)}
|
|
1973
|
+
})
|
|
1974
|
+
.catch(function(){if(st){st.textContent='Error';st.className='ins-st err'}})
|
|
1975
|
+
}
|
|
1976
|
+
window.saveInlineStyles=saveInlineStyles
|
|
1977
|
+
function renderVarRow(name){
|
|
1978
|
+
var v=cssVarMap[name]
|
|
1979
|
+
var h='<div class="cv-row"><div class="cv-name">'+esc(name)+'</div>'
|
|
1980
|
+
if(v.type==='color'){
|
|
1981
|
+
h+='<div class="cv-color-row">'
|
|
1982
|
+
h+='<input type="color" class="cv-inp cv-color" data-var="'+esc(name)+'" data-role="cp" value="'+esc(v.value)+'">'
|
|
1983
|
+
h+='<input type="text" class="cv-inp cv-text" data-var="'+esc(name)+'" data-role="tx" value="'+esc(v.value)+'">'
|
|
1984
|
+
h+='</div>'
|
|
1985
|
+
}else{
|
|
1986
|
+
h+='<input type="text" class="cv-inp cv-text" data-var="'+esc(name)+'" value="'+esc(v.value)+'">'
|
|
1987
|
+
}
|
|
1988
|
+
h+='</div>'
|
|
1989
|
+
return h
|
|
1990
|
+
}
|
|
1991
|
+
window.switchMode=switchMode
|
|
1992
|
+
|
|
1993
|
+
// ── Palette tabs ──────────────────────────────────────────────────────────────
|
|
1994
|
+
function switchPal(m){
|
|
1995
|
+
document.querySelectorAll('.ptab').forEach(function(t){t.classList.toggle('active',t.dataset.pal===m)})
|
|
1996
|
+
document.querySelectorAll('.pal-panel').forEach(function(p){p.classList.toggle('active',p.id==='pal-'+m)})
|
|
1997
|
+
}
|
|
1998
|
+
window.switchPal=switchPal
|
|
1999
|
+
|
|
2000
|
+
// ── Convert panel ─────────────────────────────────────────────────────────────
|
|
2001
|
+
var comps=[]
|
|
2002
|
+
function loadComponents(){
|
|
2003
|
+
var dir=document.getElementById('conv-dir').value.trim()||'src/components'
|
|
2004
|
+
var cl=document.getElementById('conv-list')
|
|
2005
|
+
cl.innerHTML='<div class="empty">Scanning…</div>'
|
|
2006
|
+
fetch('/_davaux/components?dir='+encodeURIComponent(dir))
|
|
2007
|
+
.then(function(r){return r.json()})
|
|
2008
|
+
.then(function(data){comps=data;renderConvList()})
|
|
2009
|
+
.catch(function(){cl.innerHTML='<div class="empty">Failed to scan.</div>'})
|
|
2010
|
+
}
|
|
2011
|
+
window.loadComponents=loadComponents
|
|
2012
|
+
function renderConvList(){
|
|
2013
|
+
var cl=document.getElementById('conv-list')
|
|
2014
|
+
if(!comps.length){cl.innerHTML='<div class="empty">No components found.<br>Create .tsx files starting with<br>an uppercase letter.</div>';return}
|
|
2015
|
+
cl.innerHTML=''
|
|
2016
|
+
comps.forEach(function(c){
|
|
2017
|
+
var div=document.createElement('div')
|
|
2018
|
+
div.className='conv-item'
|
|
2019
|
+
var nc=Object.keys(c.props||{}).length
|
|
2020
|
+
var nd=document.createElement('div');nd.className='conv-name';nd.textContent=c.name
|
|
2021
|
+
var md=document.createElement('div');md.className='conv-meta';md.textContent=c.file+(nc?' · '+nc+' prop'+(nc!==1?'s':''):'')
|
|
2022
|
+
var acts=document.createElement('div');acts.className='conv-acts'
|
|
2023
|
+
if(c.hasBlueprintAlready){
|
|
2024
|
+
var ex=document.createElement('span');ex.className='conv-exists';ex.textContent='✓ exists'
|
|
2025
|
+
var upd=document.createElement('button');upd.className='conv-btn conv-upd';upd.innerHTML='↻ Update'
|
|
2026
|
+
upd.onclick=(function(comp,b){return function(){saveBp(comp,b,true)}})(c,upd)
|
|
2027
|
+
acts.appendChild(ex);acts.appendChild(upd)
|
|
2028
|
+
}else{
|
|
2029
|
+
var btn=document.createElement('button');btn.className='conv-btn';btn.innerHTML='→ Blueprint'
|
|
2030
|
+
btn.onclick=(function(comp,b){return function(){saveBp(comp,b,false)}})(c,btn)
|
|
2031
|
+
acts.appendChild(btn)
|
|
2032
|
+
}
|
|
2033
|
+
div.appendChild(nd);div.appendChild(md);div.appendChild(acts)
|
|
2034
|
+
cl.appendChild(div)
|
|
2035
|
+
})
|
|
2036
|
+
}
|
|
2037
|
+
function saveBp(comp,btn,isUpdate){
|
|
2038
|
+
btn.disabled=true;btn.textContent='Saving...'
|
|
2039
|
+
fetch('/_davaux/save-blueprint',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:comp.name,props:comp.props,importPath:comp.importPath})})
|
|
2040
|
+
.then(function(r){return r.json()})
|
|
2041
|
+
.then(function(res){
|
|
2042
|
+
if(res.saved){
|
|
2043
|
+
if(isUpdate){
|
|
2044
|
+
btn.innerHTML='✓ Updated'
|
|
2045
|
+
setTimeout(function(){btn.innerHTML='↻ Update';btn.disabled=false},1500)
|
|
2046
|
+
}else{
|
|
2047
|
+
comps.forEach(function(c){if(c.name===comp.name)c.hasBlueprintAlready=true})
|
|
2048
|
+
renderConvList()
|
|
2049
|
+
refreshBps()
|
|
2050
|
+
}
|
|
2051
|
+
}else{btn.innerHTML='Error';btn.className='conv-btn conv-upd err';btn.disabled=false}
|
|
2052
|
+
})
|
|
2053
|
+
.catch(function(){btn.innerHTML='Error';btn.className='conv-btn conv-upd err';btn.disabled=false})
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
function buildCss(){
|
|
2057
|
+
var lines=Object.keys(cssVarMap).map(function(n){return' '+n+': '+cssVarMap[n].value+';'})
|
|
2058
|
+
return':root {'+NL+lines.join(NL)+NL+'}'
|
|
2059
|
+
}
|
|
2060
|
+
window.ccss=function(){
|
|
2061
|
+
navigator.clipboard.writeText(buildCss()).then(function(){
|
|
2062
|
+
var b=document.getElementById('css-cpbtn')
|
|
2063
|
+
if(!b)return
|
|
2064
|
+
b.textContent='Copied!'
|
|
2065
|
+
b.classList.add('ok')
|
|
2066
|
+
setTimeout(function(){b.textContent='Copy CSS';b.classList.remove('ok')},1500)
|
|
2067
|
+
})
|
|
2068
|
+
}
|
|
2069
|
+
window.cp=function(){
|
|
2070
|
+
if(sel<0)return
|
|
2071
|
+
navigator.clipboard.writeText(buildJsx(bps[sel])).then(function(){
|
|
2072
|
+
var b=document.getElementById('cpbtn')
|
|
2073
|
+
if(!b)return
|
|
2074
|
+
b.textContent='Copied!'
|
|
2075
|
+
b.classList.add('ok')
|
|
2076
|
+
setTimeout(function(){b.textContent='Copy JSX';b.classList.remove('ok')},1500)
|
|
2077
|
+
})
|
|
2078
|
+
}
|
|
2079
|
+
window.ins=function(){
|
|
2080
|
+
if(sel<0)return
|
|
2081
|
+
var bp=bps[sel]
|
|
2082
|
+
var st=document.getElementById('ins-st')
|
|
2083
|
+
if(st){st.textContent='Inserting...';st.className='ins-st'}
|
|
2084
|
+
var url,body
|
|
2085
|
+
if(insertTarget){
|
|
2086
|
+
url='/_davaux/insert-after'
|
|
2087
|
+
body=JSON.stringify({page:pg.split('?')[0],tag:insertTarget.name||insertTarget.tag,instanceIndex:insertTarget.instanceIndex,newJsx:buildJsx(bp),imports:bp.imports||{}})
|
|
2088
|
+
}else{
|
|
2089
|
+
url='/_davaux/insert'
|
|
2090
|
+
body=JSON.stringify({page:pg.split('?')[0],jsx:buildJsx(bp),imports:bp.imports||{}})
|
|
2091
|
+
}
|
|
2092
|
+
fetch(url,{
|
|
2093
|
+
method:'POST',
|
|
2094
|
+
headers:{'Content-Type':'application/json'},
|
|
2095
|
+
body:body
|
|
2096
|
+
})
|
|
2097
|
+
.then(function(r){return r.json()})
|
|
2098
|
+
.then(function(res){
|
|
2099
|
+
var st2=document.getElementById('ins-st')
|
|
2100
|
+
if(!st2)return
|
|
2101
|
+
if(res.inserted){
|
|
2102
|
+
st2.textContent=res.warning?res.warning:'Inserted into '+res.file
|
|
2103
|
+
st2.className='ins-st '+(res.warning?'warn':'ok')
|
|
2104
|
+
var b=document.getElementById('ibtn')
|
|
2105
|
+
if(b){b.textContent='Inserted!';b.classList.add('ok');setTimeout(function(){b.textContent='Insert into page';b.classList.remove('ok')},2000)}
|
|
2106
|
+
}else{
|
|
2107
|
+
st2.textContent=res.error
|
|
2108
|
+
st2.className='ins-st err'
|
|
2109
|
+
}
|
|
2110
|
+
})
|
|
2111
|
+
.catch(function(e){
|
|
2112
|
+
var st3=document.getElementById('ins-st')
|
|
2113
|
+
if(st3){st3.textContent='Request failed';st3.className='ins-st err'}
|
|
2114
|
+
})
|
|
2115
|
+
}
|
|
2116
|
+
window.remComp=function(name,instanceIndex){
|
|
2117
|
+
if(!name)return
|
|
2118
|
+
var st=document.getElementById('rem-st')
|
|
2119
|
+
if(st){st.textContent='Removing...';st.className='ins-st'}
|
|
2120
|
+
fetch('/_davaux/remove',{
|
|
2121
|
+
method:'POST',headers:{'Content-Type':'application/json'},
|
|
2122
|
+
body:JSON.stringify({page:pg.split('?')[0],component:name,instanceIndex:instanceIndex??0})
|
|
2123
|
+
})
|
|
2124
|
+
.then(function(r){return r.json()})
|
|
2125
|
+
.then(function(res){
|
|
2126
|
+
var s=document.getElementById('rem-st');if(!s)return
|
|
2127
|
+
if(res.removed){
|
|
2128
|
+
s.textContent='Removed from '+res.file
|
|
2129
|
+
s.className='ins-st ok'
|
|
2130
|
+
var rb=document.querySelector('.rem-btn')
|
|
2131
|
+
if(rb){rb.disabled=true;rb.textContent='Removed'}
|
|
2132
|
+
}else{
|
|
2133
|
+
s.textContent=res.error
|
|
2134
|
+
s.className='ins-st err'
|
|
2135
|
+
}
|
|
2136
|
+
})
|
|
2137
|
+
.catch(function(){var s=document.getElementById('rem-st');if(s){s.textContent='Request failed';s.className='ins-st err'}})
|
|
2138
|
+
}
|
|
2139
|
+
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
2140
|
+
})()
|
|
2141
|
+
</script>
|
|
2142
|
+
</body>
|
|
2143
|
+
</html>`
|
|
2144
|
+
|
|
2145
|
+
type ClientError = {
|
|
2146
|
+
text: string
|
|
2147
|
+
file?: string
|
|
2148
|
+
line?: number
|
|
2149
|
+
column?: number
|
|
2150
|
+
lineText?: string
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
const reloadPlugin: Plugin = {
|
|
2154
|
+
name: 'davaux-reload',
|
|
2155
|
+
setup(build) {
|
|
2156
|
+
build.onEnd((result) => {
|
|
2157
|
+
if (result.errors.length > 0) {
|
|
2158
|
+
console.error(`\n[davaux] Build error:\n${formatBuildErrors(result.errors)}`)
|
|
2159
|
+
if (serverReady) {
|
|
2160
|
+
const payload: ClientError[] = result.errors.map((e) => ({
|
|
2161
|
+
text: e.text,
|
|
2162
|
+
file: e.location?.file,
|
|
2163
|
+
line: e.location?.line,
|
|
2164
|
+
column: e.location?.column,
|
|
2165
|
+
lineText: e.location?.lineText?.trim(),
|
|
2166
|
+
}))
|
|
2167
|
+
sendToClients(`error:${JSON.stringify(payload)}`)
|
|
2168
|
+
}
|
|
2169
|
+
return
|
|
2170
|
+
}
|
|
2171
|
+
scheduleReload()
|
|
2172
|
+
})
|
|
2173
|
+
},
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
// ─── Dev server ───────────────────────────────────────────────────────────────
|
|
2177
|
+
|
|
2178
|
+
export interface DevOptions {
|
|
2179
|
+
cwd: string
|
|
2180
|
+
port?: number
|
|
2181
|
+
hostname?: string
|
|
2182
|
+
routesDir?: string
|
|
2183
|
+
publicDir?: string
|
|
2184
|
+
islandsDir?: string
|
|
2185
|
+
/** Path to the client-side entry file for custom client code. Default: <cwd>/src/client.ts */
|
|
2186
|
+
clientEntry?: string
|
|
2187
|
+
/** Path aliases forwarded to esbuild `alias` in every build context. Same glob syntax as tsconfig `paths`. */
|
|
2188
|
+
paths?: Record<string, string>
|
|
2189
|
+
/** Davaux plugins from davaux.config.ts — contribute esbuild transforms and scanner extensions. */
|
|
2190
|
+
plugins?: DavauxPlugin[]
|
|
2191
|
+
/**
|
|
2192
|
+
* Additional packages to exclude from esbuild bundling. Merged with the default
|
|
2193
|
+
* `['node:*', 'davaux']` externals. Useful for native or CJS-only packages.
|
|
2194
|
+
*/
|
|
2195
|
+
external?: string[]
|
|
2196
|
+
/** Absolute path to `src/middleware.ts` if it exists — compiled and run before route matching. */
|
|
2197
|
+
middlewareSrc?: string
|
|
2198
|
+
/** Visual editor configuration from davaux.config.ts. */
|
|
2199
|
+
editor?: import('../config.js').EditorConfig
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
export async function startDev(options: DevOptions): Promise<void> {
|
|
2203
|
+
const {
|
|
2204
|
+
cwd,
|
|
2205
|
+
port = 3000,
|
|
2206
|
+
hostname = 'localhost',
|
|
2207
|
+
routesDir = join(cwd, 'src', 'routes'),
|
|
2208
|
+
publicDir = join(cwd, 'public'),
|
|
2209
|
+
islandsDir = join(cwd, 'src', 'islands'),
|
|
2210
|
+
clientEntry = join(cwd, 'src', 'client.ts'),
|
|
2211
|
+
paths,
|
|
2212
|
+
plugins: davauxPlugins = [],
|
|
2213
|
+
external: userExternal = [],
|
|
2214
|
+
middlewareSrc,
|
|
2215
|
+
editor: editorConfig,
|
|
2216
|
+
} = options
|
|
2217
|
+
const editorEnabled = editorConfig?.enabled === true
|
|
2218
|
+
|
|
2219
|
+
const serverExternal = ['node:*', 'davaux', ...userExternal]
|
|
2220
|
+
|
|
2221
|
+
const userAlias = pathsToAlias(paths ?? {})
|
|
2222
|
+
const extraPlugins = collectEsbuildPlugins(davauxPlugins)
|
|
2223
|
+
const scannerSuffixes = collectScannerSuffixes(davauxPlugins)
|
|
2224
|
+
|
|
2225
|
+
const dauxDir = join(cwd, '.davaux')
|
|
2226
|
+
const outDir = join(dauxDir, 'routes')
|
|
2227
|
+
const clientOutFile = join(dauxDir, 'client.js')
|
|
2228
|
+
const islandsOutFile = join(dauxDir, 'islands.js')
|
|
2229
|
+
const stylesOutFile = join(dauxDir, 'styles.css')
|
|
2230
|
+
const hasClient = existsSync(clientEntry)
|
|
2231
|
+
|
|
2232
|
+
function toCompiledPath(src: string): string {
|
|
2233
|
+
return join(outDir, relative(routesDir, src).replace(/\.(tsx?|jsx?|mdx?)$/, '.js'))
|
|
2234
|
+
}
|
|
2235
|
+
function toCompiledDir(srcDir: string): string {
|
|
2236
|
+
return join(outDir, relative(routesDir, srcDir))
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
function collectSourceFiles(scan: ScanResult): string[] {
|
|
2240
|
+
return [
|
|
2241
|
+
...scan.routes.map((r) => r.filePath),
|
|
2242
|
+
...scan.layouts.map((l) => l.filePath),
|
|
2243
|
+
...scan.middlewares.map((m) => m.filePath),
|
|
2244
|
+
...(scan.errorPage ? [scan.errorPage] : []),
|
|
2245
|
+
]
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
function makeApp(scan: ScanResult, islandFiles: IslandFile[]) {
|
|
2249
|
+
const injectedScripts: string[] = []
|
|
2250
|
+
if (islandFiles.length > 0) injectedScripts.push('/_davaux/islands.js')
|
|
2251
|
+
if (hasClient) injectedScripts.push('/_davaux/client.js')
|
|
2252
|
+
// src/middleware.ts compiles to .davaux/middleware.js (one level above the routes outDir)
|
|
2253
|
+
const appMiddlewarePath = middlewareSrc ? join(dauxDir, 'middleware.js') : undefined
|
|
2254
|
+
return buildApp(
|
|
2255
|
+
{
|
|
2256
|
+
routes: scan.routes.map((r) => ({ ...r, filePath: toCompiledPath(r.filePath) })),
|
|
2257
|
+
layouts: scan.layouts.map((l) => ({
|
|
2258
|
+
filePath: toCompiledPath(l.filePath),
|
|
2259
|
+
dirPath: toCompiledDir(l.dirPath),
|
|
2260
|
+
})),
|
|
2261
|
+
middlewares: scan.middlewares.map((m) => ({
|
|
2262
|
+
filePath: toCompiledPath(m.filePath),
|
|
2263
|
+
dirPath: toCompiledDir(m.dirPath),
|
|
2264
|
+
})),
|
|
2265
|
+
errorPage: scan.errorPage ? toCompiledPath(scan.errorPage) : undefined,
|
|
2266
|
+
},
|
|
2267
|
+
true,
|
|
2268
|
+
injectedScripts,
|
|
2269
|
+
['/_davaux/styles.css'],
|
|
2270
|
+
appMiddlewarePath,
|
|
2271
|
+
'',
|
|
2272
|
+
editorEnabled,
|
|
2273
|
+
)
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
// Mutable state — updated on structural rebuild (file add/remove)
|
|
2277
|
+
let scan = await scanRoutes(routesDir, scannerSuffixes)
|
|
2278
|
+
let islands = await scanIslands(islandsDir)
|
|
2279
|
+
let sourceFiles = collectSourceFiles(scan)
|
|
2280
|
+
|
|
2281
|
+
if (sourceFiles.length === 0) {
|
|
2282
|
+
console.warn('[davaux] No routes found in', routesDir)
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
// Routes context — server JSX, islands aliased to server stubs + auto-wrapped
|
|
2286
|
+
let ctx = await context({
|
|
2287
|
+
entryPoints: sourceFiles,
|
|
2288
|
+
outdir: outDir,
|
|
2289
|
+
outbase: routesDir,
|
|
2290
|
+
format: 'esm',
|
|
2291
|
+
platform: 'node',
|
|
2292
|
+
target: 'node22',
|
|
2293
|
+
bundle: true,
|
|
2294
|
+
external: serverExternal,
|
|
2295
|
+
jsx: 'automatic',
|
|
2296
|
+
jsxImportSource: 'davaux/oml',
|
|
2297
|
+
sourcemap: 'inline',
|
|
2298
|
+
alias: { ...userAlias, 'davaux/client': 'davaux/signal' },
|
|
2299
|
+
plugins: [
|
|
2300
|
+
reloadPlugin,
|
|
2301
|
+
islandServerPlugin(islandsDir),
|
|
2302
|
+
cssCollectorPlugin(dauxDir, stylesOutFile),
|
|
2303
|
+
...extraPlugins,
|
|
2304
|
+
],
|
|
2305
|
+
})
|
|
2306
|
+
await ctx.rebuild()
|
|
2307
|
+
|
|
2308
|
+
// Islands client context — client JSX, one bundle with all islands + hydration
|
|
2309
|
+
let islandsCtx: Awaited<ReturnType<typeof context>> | undefined
|
|
2310
|
+
if (islands.length > 0) {
|
|
2311
|
+
islandsCtx = await context({
|
|
2312
|
+
stdin: {
|
|
2313
|
+
contents: generateIslandsEntry(islands),
|
|
2314
|
+
loader: 'ts',
|
|
2315
|
+
resolveDir: cwd,
|
|
2316
|
+
},
|
|
2317
|
+
outfile: islandsOutFile,
|
|
2318
|
+
format: 'esm',
|
|
2319
|
+
platform: 'browser',
|
|
2320
|
+
target: 'es2022',
|
|
2321
|
+
bundle: true,
|
|
2322
|
+
jsx: 'automatic',
|
|
2323
|
+
jsxImportSource: 'davaux/client',
|
|
2324
|
+
sourcemap: 'inline',
|
|
2325
|
+
alias: userAlias,
|
|
2326
|
+
tsconfigRaw: JSON.stringify({
|
|
2327
|
+
compilerOptions: { jsx: 'react-jsx', jsxImportSource: 'davaux/client' },
|
|
2328
|
+
}),
|
|
2329
|
+
plugins: [reloadPlugin, cssCollectorPlugin(dauxDir, stylesOutFile), ...extraPlugins],
|
|
2330
|
+
})
|
|
2331
|
+
await islandsCtx.rebuild()
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
// Optional user-authored client bundle (never needs a structural rebuild)
|
|
2335
|
+
let clientCtx: Awaited<ReturnType<typeof context>> | undefined
|
|
2336
|
+
if (hasClient) {
|
|
2337
|
+
clientCtx = await context({
|
|
2338
|
+
entryPoints: [clientEntry],
|
|
2339
|
+
outfile: clientOutFile,
|
|
2340
|
+
format: 'esm',
|
|
2341
|
+
platform: 'browser',
|
|
2342
|
+
target: 'es2022',
|
|
2343
|
+
bundle: true,
|
|
2344
|
+
jsx: 'automatic',
|
|
2345
|
+
jsxImportSource: 'davaux/client',
|
|
2346
|
+
sourcemap: 'inline',
|
|
2347
|
+
alias: userAlias,
|
|
2348
|
+
tsconfigRaw: JSON.stringify({
|
|
2349
|
+
compilerOptions: { jsx: 'react-jsx', jsxImportSource: 'davaux/client' },
|
|
2350
|
+
}),
|
|
2351
|
+
plugins: [reloadPlugin, ...extraPlugins],
|
|
2352
|
+
})
|
|
2353
|
+
await clientCtx.rebuild()
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
// src/middleware.ts — compiled separately so it lands at .davaux/middleware.js
|
|
2357
|
+
// (not in the routes context, which uses outbase: src/routes and would mis-place it)
|
|
2358
|
+
const middlewareOutFile = join(dauxDir, 'middleware.js')
|
|
2359
|
+
let middlewareCtx: Awaited<ReturnType<typeof context>> | undefined
|
|
2360
|
+
if (middlewareSrc) {
|
|
2361
|
+
middlewareCtx = await context({
|
|
2362
|
+
entryPoints: [middlewareSrc],
|
|
2363
|
+
outfile: middlewareOutFile,
|
|
2364
|
+
format: 'esm',
|
|
2365
|
+
platform: 'node',
|
|
2366
|
+
target: 'node22',
|
|
2367
|
+
bundle: true,
|
|
2368
|
+
external: serverExternal,
|
|
2369
|
+
jsx: 'automatic',
|
|
2370
|
+
jsxImportSource: 'davaux',
|
|
2371
|
+
sourcemap: 'inline',
|
|
2372
|
+
alias: userAlias,
|
|
2373
|
+
plugins: [reloadPlugin, ...extraPlugins],
|
|
2374
|
+
})
|
|
2375
|
+
await middlewareCtx.rebuild()
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// Mutable app — hot-swapped by the structural rebuild without restarting the server
|
|
2379
|
+
let app = makeApp(scan, islands)
|
|
2380
|
+
const staticHandler = serveStatic(publicDir)
|
|
2381
|
+
|
|
2382
|
+
startServer(() => app, {
|
|
2383
|
+
port,
|
|
2384
|
+
hostname,
|
|
2385
|
+
onRequest: async (req, res) => {
|
|
2386
|
+
const path = req.url?.split('?')[0]
|
|
2387
|
+
|
|
2388
|
+
// SSE live reload stream
|
|
2389
|
+
if (path === '/_davaux/livereload') {
|
|
2390
|
+
res.writeHead(200, {
|
|
2391
|
+
'Content-Type': 'text/event-stream',
|
|
2392
|
+
'Cache-Control': 'no-cache',
|
|
2393
|
+
Connection: 'keep-alive',
|
|
2394
|
+
})
|
|
2395
|
+
res.write('\n')
|
|
2396
|
+
sseClients.add(res)
|
|
2397
|
+
req.on('close', () => sseClients.delete(res))
|
|
2398
|
+
return Promise.resolve(true)
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
if (path === '/_davaux/livereload.js') {
|
|
2402
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript' })
|
|
2403
|
+
res.end(LIVERELOAD_SCRIPT)
|
|
2404
|
+
return Promise.resolve(true)
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
if (path === '/_davaux/livereload-worker.js') {
|
|
2408
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript' })
|
|
2409
|
+
res.end(SHARED_WORKER_SCRIPT)
|
|
2410
|
+
return Promise.resolve(true)
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
if (path === '/_davaux/partial-updates.js') {
|
|
2414
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript' })
|
|
2415
|
+
res.end(getPartialUpdatesScript())
|
|
2416
|
+
return Promise.resolve(true)
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
// Security gate — editor endpoints require editor.enabled: true.
|
|
2420
|
+
// Infrastructure paths (livereload, styles, islands, client) are always served.
|
|
2421
|
+
if (path?.startsWith('/_davaux/') && !editorEnabled) {
|
|
2422
|
+
const alwaysOn = [
|
|
2423
|
+
'/_davaux/livereload',
|
|
2424
|
+
'/_davaux/livereload.js',
|
|
2425
|
+
'/_davaux/livereload-worker.js',
|
|
2426
|
+
'/_davaux/partial-updates.js',
|
|
2427
|
+
'/_davaux/styles.css',
|
|
2428
|
+
'/_davaux/islands.js',
|
|
2429
|
+
'/_davaux/client.js',
|
|
2430
|
+
]
|
|
2431
|
+
if (!alwaysOn.includes(path)) {
|
|
2432
|
+
res.writeHead(404)
|
|
2433
|
+
res.end()
|
|
2434
|
+
return Promise.resolve(true)
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
if (path === '/_davaux/inspector.js') {
|
|
2439
|
+
res.writeHead(200, {
|
|
2440
|
+
'Content-Type': 'application/javascript',
|
|
2441
|
+
'Cache-Control': 'no-store',
|
|
2442
|
+
})
|
|
2443
|
+
const dvx = JSON.stringify({
|
|
2444
|
+
pos: editorConfig?.badge?.position ?? 'bottom-right',
|
|
2445
|
+
label: editorConfig?.badge?.label ?? 'Inspector',
|
|
2446
|
+
})
|
|
2447
|
+
res.end(`window.__DVX__=${dvx};\n${INSPECTOR_SCRIPT}`)
|
|
2448
|
+
return Promise.resolve(true)
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
if (path === '/_davaux/inspector') {
|
|
2452
|
+
const url = new URL(req.url ?? '/', 'http://x').searchParams.get('url') ?? '/'
|
|
2453
|
+
const node = app.devOmlStore?.get(url)
|
|
2454
|
+
const pageUrl = url.split('?')[0]
|
|
2455
|
+
const route = scan.routes.find((r) => r.urlPattern === pageUrl && r.type === 'page')
|
|
2456
|
+
const filePath = route?.filePath ? relative(cwd, route.filePath) : null
|
|
2457
|
+
const ext = route?.filePath ? extname(route.filePath) : ''
|
|
2458
|
+
const fileType = ext === '.mdx' ? 'mdx' : ext === '.md' ? 'md' : 'tsx'
|
|
2459
|
+
// Applicable layouts — same matching logic as findLayouts() in handler.ts
|
|
2460
|
+
const layouts = route?.filePath
|
|
2461
|
+
? scan.layouts
|
|
2462
|
+
.filter((l) => {
|
|
2463
|
+
const d = route.filePath.replace(/[\\/][^\\/]+$/, '')
|
|
2464
|
+
return d === l.dirPath || d.startsWith(`${l.dirPath}/`)
|
|
2465
|
+
})
|
|
2466
|
+
.sort((a, b) => b.dirPath.length - a.dirPath.length)
|
|
2467
|
+
.map((l) => ({ filePath: relative(cwd, l.filePath) }))
|
|
2468
|
+
: []
|
|
2469
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' })
|
|
2470
|
+
res.end(
|
|
2471
|
+
node !== undefined
|
|
2472
|
+
? JSON.stringify({ node, fileType, filePath, layouts })
|
|
2473
|
+
: JSON.stringify({ error: 'No OML tree for this URL. Load the page first.', fileType, filePath, layouts }),
|
|
2474
|
+
)
|
|
2475
|
+
return Promise.resolve(true)
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
if (path === '/_davaux/editor') {
|
|
2479
|
+
res.writeHead(200, {
|
|
2480
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
2481
|
+
'Cache-Control': 'no-store',
|
|
2482
|
+
})
|
|
2483
|
+
const dvxEditor = JSON.stringify({ theme: editorConfig?.theme ?? 'auto' })
|
|
2484
|
+
let cssContent = editorConfig?.css ?? ''
|
|
2485
|
+
if (editorConfig?.cssFile) {
|
|
2486
|
+
try { cssContent += readFileSync(join(cwd, editorConfig.cssFile), 'utf-8') } catch {}
|
|
2487
|
+
}
|
|
2488
|
+
const extras = `<script>window.__DVX_EDITOR__=${dvxEditor}</script>${cssContent ? `<style>${cssContent}</style>` : ''}`
|
|
2489
|
+
res.end(EDITOR_HTML.replace('</head>', `${extras}</head>`))
|
|
2490
|
+
return Promise.resolve(true)
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
if (path === '/_davaux/blueprints') {
|
|
2494
|
+
try {
|
|
2495
|
+
const entries = scanBlueprints(cwd)
|
|
2496
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' })
|
|
2497
|
+
res.end(JSON.stringify(entries))
|
|
2498
|
+
} catch (err) {
|
|
2499
|
+
console.error('[davaux] Failed to scan blueprints:', err)
|
|
2500
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' })
|
|
2501
|
+
res.end('[]')
|
|
2502
|
+
}
|
|
2503
|
+
return Promise.resolve(true)
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
if (path === '/_davaux/get-source' && req.method === 'GET') {
|
|
2507
|
+
const file = new URL(req.url ?? '/', 'http://x').searchParams.get('file') ?? ''
|
|
2508
|
+
const absPath = join(cwd, file)
|
|
2509
|
+
if (!file || !absPath.startsWith(`${cwd}/`)) {
|
|
2510
|
+
res.writeHead(403, { 'Content-Type': 'application/json' })
|
|
2511
|
+
res.end(JSON.stringify({ error: 'Forbidden' }))
|
|
2512
|
+
return Promise.resolve(true)
|
|
2513
|
+
}
|
|
2514
|
+
try {
|
|
2515
|
+
const content = readFileSync(absPath, 'utf-8')
|
|
2516
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' })
|
|
2517
|
+
res.end(JSON.stringify({ content }))
|
|
2518
|
+
} catch {
|
|
2519
|
+
res.writeHead(404, { 'Content-Type': 'application/json' })
|
|
2520
|
+
res.end(JSON.stringify({ error: 'File not found' }))
|
|
2521
|
+
}
|
|
2522
|
+
return Promise.resolve(true)
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
if (path === '/_davaux/update-source' && req.method === 'POST') {
|
|
2526
|
+
const body = await new Promise<string>((resolve) => {
|
|
2527
|
+
const chunks: Buffer[] = []
|
|
2528
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2529
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2530
|
+
req.on('error', () => resolve('{}'))
|
|
2531
|
+
})
|
|
2532
|
+
const { file, content } = JSON.parse(body) as { file: string; content: string }
|
|
2533
|
+
const absPath = join(cwd, file)
|
|
2534
|
+
if (!file || !absPath.startsWith(`${cwd}/`)) {
|
|
2535
|
+
res.writeHead(403, { 'Content-Type': 'application/json' })
|
|
2536
|
+
res.end(JSON.stringify({ error: 'Forbidden' }))
|
|
2537
|
+
return Promise.resolve(true)
|
|
2538
|
+
}
|
|
2539
|
+
writeFileSync(absPath, content, 'utf-8')
|
|
2540
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2541
|
+
res.end(JSON.stringify({ ok: true }))
|
|
2542
|
+
return Promise.resolve(true)
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
if (path === '/_davaux/get-fragment' && req.method === 'GET') {
|
|
2546
|
+
const params = new URL(req.url ?? '/', 'http://x').searchParams
|
|
2547
|
+
const file = params.get('file') ?? ''
|
|
2548
|
+
const component = params.get('component') ?? ''
|
|
2549
|
+
const instanceIndex = parseInt(params.get('instanceIndex') ?? '0', 10)
|
|
2550
|
+
const absPath = join(cwd, file)
|
|
2551
|
+
if (!file || !absPath.startsWith(`${cwd}/`)) {
|
|
2552
|
+
res.writeHead(403, { 'Content-Type': 'application/json' })
|
|
2553
|
+
res.end(JSON.stringify({ error: 'Forbidden' }))
|
|
2554
|
+
return Promise.resolve(true)
|
|
2555
|
+
}
|
|
2556
|
+
const result = getComponentFragment(absPath, component, instanceIndex)
|
|
2557
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' })
|
|
2558
|
+
res.end(JSON.stringify(result))
|
|
2559
|
+
return Promise.resolve(true)
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
if (path === '/_davaux/update-fragment' && req.method === 'POST') {
|
|
2563
|
+
const body = await new Promise<string>((resolve) => {
|
|
2564
|
+
const chunks: Buffer[] = []
|
|
2565
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2566
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2567
|
+
req.on('error', () => resolve('{}'))
|
|
2568
|
+
})
|
|
2569
|
+
const { file, component, instanceIndex, content } = JSON.parse(body) as {
|
|
2570
|
+
file: string; component: string; instanceIndex: number; content: string
|
|
2571
|
+
}
|
|
2572
|
+
const absPath = join(cwd, file)
|
|
2573
|
+
if (!file || !absPath.startsWith(`${cwd}/`)) {
|
|
2574
|
+
res.writeHead(403, { 'Content-Type': 'application/json' })
|
|
2575
|
+
res.end(JSON.stringify({ error: 'Forbidden' }))
|
|
2576
|
+
return Promise.resolve(true)
|
|
2577
|
+
}
|
|
2578
|
+
const result = replaceComponentFragment(absPath, component, instanceIndex, content)
|
|
2579
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2580
|
+
res.end(JSON.stringify(result))
|
|
2581
|
+
return Promise.resolve(true)
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
if (path === '/_davaux/get-element-fragment' && req.method === 'GET') {
|
|
2585
|
+
const params = new URL(req.url ?? '/', 'http://x').searchParams
|
|
2586
|
+
const file = params.get('file') ?? ''
|
|
2587
|
+
const tag = params.get('tag') ?? ''
|
|
2588
|
+
const instanceIndex = parseInt(params.get('instanceIndex') ?? '0', 10)
|
|
2589
|
+
const absPath = join(cwd, file)
|
|
2590
|
+
if (!file || !absPath.startsWith(`${cwd}/`)) {
|
|
2591
|
+
res.writeHead(403, { 'Content-Type': 'application/json' })
|
|
2592
|
+
res.end(JSON.stringify({ error: 'Forbidden' }))
|
|
2593
|
+
return Promise.resolve(true)
|
|
2594
|
+
}
|
|
2595
|
+
const result = getElementFragment(absPath, tag, instanceIndex)
|
|
2596
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' })
|
|
2597
|
+
res.end(JSON.stringify(result))
|
|
2598
|
+
return Promise.resolve(true)
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
if (path === '/_davaux/update-element-fragment' && req.method === 'POST') {
|
|
2602
|
+
const body = await new Promise<string>((resolve) => {
|
|
2603
|
+
const chunks: Buffer[] = []
|
|
2604
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2605
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2606
|
+
req.on('error', () => resolve('{}'))
|
|
2607
|
+
})
|
|
2608
|
+
const { file, tag, instanceIndex, content } = JSON.parse(body) as {
|
|
2609
|
+
file: string; tag: string; instanceIndex: number; content: string
|
|
2610
|
+
}
|
|
2611
|
+
const absPath = join(cwd, file)
|
|
2612
|
+
if (!file || !absPath.startsWith(`${cwd}/`)) {
|
|
2613
|
+
res.writeHead(403, { 'Content-Type': 'application/json' })
|
|
2614
|
+
res.end(JSON.stringify({ error: 'Forbidden' }))
|
|
2615
|
+
return Promise.resolve(true)
|
|
2616
|
+
}
|
|
2617
|
+
const result = replaceElementFragment(absPath, tag, instanceIndex, content)
|
|
2618
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2619
|
+
res.end(JSON.stringify(result))
|
|
2620
|
+
return Promise.resolve(true)
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
if (path === '/_davaux/routes') {
|
|
2624
|
+
const pageRoutes = scan.routes
|
|
2625
|
+
.filter((r) => r.type === 'page')
|
|
2626
|
+
.map((r) => ({ urlPattern: r.urlPattern }))
|
|
2627
|
+
.sort((a, b) => a.urlPattern.localeCompare(b.urlPattern))
|
|
2628
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' })
|
|
2629
|
+
res.end(JSON.stringify(pageRoutes))
|
|
2630
|
+
return Promise.resolve(true)
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
if (path === '/_davaux/insert' && req.method === 'POST') {
|
|
2634
|
+
const body = await new Promise<string>((resolve) => {
|
|
2635
|
+
const chunks: Buffer[] = []
|
|
2636
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2637
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2638
|
+
req.on('error', () => resolve('{}'))
|
|
2639
|
+
})
|
|
2640
|
+
const { page, jsx, imports } = JSON.parse(body) as {
|
|
2641
|
+
page: string
|
|
2642
|
+
jsx: string
|
|
2643
|
+
imports: Record<string, string>
|
|
2644
|
+
}
|
|
2645
|
+
const pageUrl = (page ?? '/').split('?')[0]
|
|
2646
|
+
const route = scan.routes.find((r) => r.urlPattern === pageUrl && r.type === 'page')
|
|
2647
|
+
if (!route) {
|
|
2648
|
+
res.writeHead(404, { 'Content-Type': 'application/json' })
|
|
2649
|
+
res.end(JSON.stringify({ inserted: false, error: `No page route found for: ${pageUrl}` }))
|
|
2650
|
+
return Promise.resolve(true)
|
|
2651
|
+
}
|
|
2652
|
+
const result = insertJsx(route.filePath, jsx, imports ?? {})
|
|
2653
|
+
const display = result.inserted ? { ...result, file: relative(cwd, result.file) } : result
|
|
2654
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2655
|
+
res.end(JSON.stringify(display))
|
|
2656
|
+
return Promise.resolve(true)
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
if (path === '/_davaux/insert-after' && req.method === 'POST') {
|
|
2660
|
+
const body = await new Promise<string>((resolve) => {
|
|
2661
|
+
const chunks: Buffer[] = []
|
|
2662
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2663
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2664
|
+
req.on('error', () => resolve('{}'))
|
|
2665
|
+
})
|
|
2666
|
+
const { page, tag, instanceIndex, newJsx, imports } = JSON.parse(body) as {
|
|
2667
|
+
page: string; tag: string; instanceIndex: number; newJsx: string
|
|
2668
|
+
imports?: Record<string, string>
|
|
2669
|
+
}
|
|
2670
|
+
const pageUrl = (page ?? '/').split('?')[0]
|
|
2671
|
+
const route = scan.routes.find((r) => r.urlPattern === pageUrl && r.type === 'page')
|
|
2672
|
+
if (!route) {
|
|
2673
|
+
res.writeHead(404, { 'Content-Type': 'application/json' })
|
|
2674
|
+
res.end(JSON.stringify({ inserted: false, error: `No page route found for: ${pageUrl}` }))
|
|
2675
|
+
return Promise.resolve(true)
|
|
2676
|
+
}
|
|
2677
|
+
const result = insertAfterElement(route.filePath, tag, instanceIndex ?? 0, newJsx, imports ?? {})
|
|
2678
|
+
const display = result.inserted ? { ...result, file: relative(cwd, result.file) } : result
|
|
2679
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2680
|
+
res.end(JSON.stringify(display))
|
|
2681
|
+
return Promise.resolve(true)
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
if (path === '/_davaux/components') {
|
|
2685
|
+
const qs = new URL(req.url ?? '/', 'http://x').searchParams
|
|
2686
|
+
const dir = qs.get('dir') || 'src/components'
|
|
2687
|
+
const absDir = dir.startsWith('/') ? dir : join(cwd, dir)
|
|
2688
|
+
const blueprintsDir = join(cwd, 'src', 'blueprints')
|
|
2689
|
+
try {
|
|
2690
|
+
const entries = scanComponents(absDir, cwd, blueprintsDir)
|
|
2691
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' })
|
|
2692
|
+
res.end(JSON.stringify(entries))
|
|
2693
|
+
} catch (err) {
|
|
2694
|
+
console.error('[davaux] Failed to scan components:', err)
|
|
2695
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' })
|
|
2696
|
+
res.end('[]')
|
|
2697
|
+
}
|
|
2698
|
+
return Promise.resolve(true)
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
if (path === '/_davaux/save-blueprint' && req.method === 'POST') {
|
|
2702
|
+
const body = await new Promise<string>((resolve) => {
|
|
2703
|
+
const chunks: Buffer[] = []
|
|
2704
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2705
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2706
|
+
req.on('error', () => resolve('{}'))
|
|
2707
|
+
})
|
|
2708
|
+
const { name, props, importPath } = JSON.parse(body) as {
|
|
2709
|
+
name: string
|
|
2710
|
+
props: Record<string, OmlPropSchema>
|
|
2711
|
+
importPath?: string
|
|
2712
|
+
}
|
|
2713
|
+
const blueprintsDir = join(cwd, 'src', 'blueprints')
|
|
2714
|
+
const imports = importPath ? { [name]: importPath } : {}
|
|
2715
|
+
const result = saveBlueprint(name, props, imports, blueprintsDir)
|
|
2716
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2717
|
+
res.end(JSON.stringify(result))
|
|
2718
|
+
return Promise.resolve(true)
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
if (path === '/_davaux/update-blueprint' && req.method === 'POST') {
|
|
2722
|
+
const body = await new Promise<string>((resolve) => {
|
|
2723
|
+
const chunks: Buffer[] = []
|
|
2724
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2725
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2726
|
+
req.on('error', () => resolve('{}'))
|
|
2727
|
+
})
|
|
2728
|
+
const { id, name, props } = JSON.parse(body) as {
|
|
2729
|
+
id: string
|
|
2730
|
+
name: string
|
|
2731
|
+
props: Record<string, OmlPropSchema>
|
|
2732
|
+
}
|
|
2733
|
+
const blueprintsDir = join(cwd, 'src', 'blueprints')
|
|
2734
|
+
const result = updateBlueprint(id, name, props, blueprintsDir)
|
|
2735
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2736
|
+
res.end(JSON.stringify(result))
|
|
2737
|
+
return Promise.resolve(true)
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
if (path === '/_davaux/remove' && req.method === 'POST') {
|
|
2741
|
+
const body = await new Promise<string>((resolve) => {
|
|
2742
|
+
const chunks: Buffer[] = []
|
|
2743
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2744
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2745
|
+
req.on('error', () => resolve('{}'))
|
|
2746
|
+
})
|
|
2747
|
+
const { page, component, tag, instanceIndex } = JSON.parse(body) as {
|
|
2748
|
+
page: string
|
|
2749
|
+
component?: string
|
|
2750
|
+
tag?: string
|
|
2751
|
+
instanceIndex?: number
|
|
2752
|
+
}
|
|
2753
|
+
const pageUrl = (page ?? '/').split('?')[0]
|
|
2754
|
+
const route = scan.routes.find((r) => r.urlPattern === pageUrl && r.type === 'page')
|
|
2755
|
+
if (!route) {
|
|
2756
|
+
res.writeHead(404, { 'Content-Type': 'application/json' })
|
|
2757
|
+
res.end(JSON.stringify({ removed: false, error: `No page route found for: ${pageUrl}` }))
|
|
2758
|
+
return Promise.resolve(true)
|
|
2759
|
+
}
|
|
2760
|
+
const result = tag !== undefined
|
|
2761
|
+
? removeElement(route.filePath, tag, instanceIndex ?? 0)
|
|
2762
|
+
: removeJsx(route.filePath, component ?? '', instanceIndex)
|
|
2763
|
+
const display = result.removed ? { ...result, file: relative(cwd, result.file) } : result
|
|
2764
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2765
|
+
res.end(JSON.stringify(display))
|
|
2766
|
+
return Promise.resolve(true)
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
if (path === '/_davaux/update-component' && req.method === 'POST') {
|
|
2770
|
+
const body = await new Promise<string>((resolve) => {
|
|
2771
|
+
const chunks: Buffer[] = []
|
|
2772
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2773
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2774
|
+
req.on('error', () => resolve('{}'))
|
|
2775
|
+
})
|
|
2776
|
+
const { page, component, jsx, instanceIndex } = JSON.parse(body) as {
|
|
2777
|
+
page: string
|
|
2778
|
+
component: string
|
|
2779
|
+
jsx: string
|
|
2780
|
+
instanceIndex?: number
|
|
2781
|
+
}
|
|
2782
|
+
const pageUrl = (page ?? '/').split('?')[0]
|
|
2783
|
+
const route = scan.routes.find((r) => r.urlPattern === pageUrl && r.type === 'page')
|
|
2784
|
+
if (!route) {
|
|
2785
|
+
res.writeHead(404, { 'Content-Type': 'application/json' })
|
|
2786
|
+
res.end(JSON.stringify({ replaced: false, error: `No page route found for: ${pageUrl}` }))
|
|
2787
|
+
return Promise.resolve(true)
|
|
2788
|
+
}
|
|
2789
|
+
const result = replaceJsx(route.filePath, component, jsx, instanceIndex ?? 0)
|
|
2790
|
+
const display = result.replaced ? { ...result, file: relative(cwd, result.file) } : result
|
|
2791
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2792
|
+
res.end(JSON.stringify(display))
|
|
2793
|
+
return Promise.resolve(true)
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
if (path === '/_davaux/update-element' && req.method === 'POST') {
|
|
2797
|
+
const body = await new Promise<string>((resolve) => {
|
|
2798
|
+
const chunks: Buffer[] = []
|
|
2799
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2800
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2801
|
+
req.on('error', () => resolve('{}'))
|
|
2802
|
+
})
|
|
2803
|
+
const { page, tag, newProps, instanceIndex, textContent } = JSON.parse(body) as {
|
|
2804
|
+
page: string
|
|
2805
|
+
tag: string
|
|
2806
|
+
newProps: Record<string, unknown>
|
|
2807
|
+
instanceIndex?: number
|
|
2808
|
+
textContent?: string | null
|
|
2809
|
+
}
|
|
2810
|
+
if (!page || !tag) {
|
|
2811
|
+
res.writeHead(400, { 'Content-Type': 'application/json' })
|
|
2812
|
+
res.end(JSON.stringify({ replaced: false, error: 'Missing page or tag' }))
|
|
2813
|
+
return Promise.resolve(true)
|
|
2814
|
+
}
|
|
2815
|
+
const pageUrl = (page ?? '/').split('?')[0]
|
|
2816
|
+
const route = scan.routes.find((r) => r.urlPattern === pageUrl && r.type === 'page')
|
|
2817
|
+
if (!route) {
|
|
2818
|
+
res.writeHead(404, { 'Content-Type': 'application/json' })
|
|
2819
|
+
res.end(JSON.stringify({ replaced: false, error: `No page route found for: ${pageUrl}` }))
|
|
2820
|
+
return Promise.resolve(true)
|
|
2821
|
+
}
|
|
2822
|
+
const attrsResult = replaceElementAttrs(route.filePath, tag, newProps, instanceIndex ?? 0)
|
|
2823
|
+
if (!attrsResult.replaced) {
|
|
2824
|
+
const display = attrsResult
|
|
2825
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2826
|
+
res.end(JSON.stringify(display))
|
|
2827
|
+
return Promise.resolve(true)
|
|
2828
|
+
}
|
|
2829
|
+
if (textContent != null) {
|
|
2830
|
+
const textResult = replaceTextContent(route.filePath, tag, textContent, instanceIndex ?? 0)
|
|
2831
|
+
if (!textResult.replaced) {
|
|
2832
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2833
|
+
res.end(JSON.stringify(textResult))
|
|
2834
|
+
return Promise.resolve(true)
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
const display = { ...attrsResult, file: relative(cwd, attrsResult.file) }
|
|
2838
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2839
|
+
res.end(JSON.stringify(display))
|
|
2840
|
+
return Promise.resolve(true)
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
if (path === '/_davaux/update-comp-props' && req.method === 'POST') {
|
|
2844
|
+
const body = await new Promise<string>((resolve) => {
|
|
2845
|
+
const chunks: Buffer[] = []
|
|
2846
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2847
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2848
|
+
req.on('error', () => resolve('{}'))
|
|
2849
|
+
})
|
|
2850
|
+
const { page, component, newProps, instanceIndex, childrenText } = JSON.parse(body) as {
|
|
2851
|
+
page: string
|
|
2852
|
+
component: string
|
|
2853
|
+
newProps: Record<string, unknown>
|
|
2854
|
+
instanceIndex?: number
|
|
2855
|
+
childrenText?: string | null
|
|
2856
|
+
}
|
|
2857
|
+
const pageUrl = (page ?? '/').split('?')[0]
|
|
2858
|
+
const route = scan.routes.find((r) => r.urlPattern === pageUrl && r.type === 'page')
|
|
2859
|
+
if (!route) {
|
|
2860
|
+
res.writeHead(404, { 'Content-Type': 'application/json' })
|
|
2861
|
+
res.end(JSON.stringify({ replaced: false, error: `No page route found for: ${pageUrl}` }))
|
|
2862
|
+
return Promise.resolve(true)
|
|
2863
|
+
}
|
|
2864
|
+
const attrsResult = replaceElementAttrs(route.filePath, component, newProps ?? {}, instanceIndex ?? 0)
|
|
2865
|
+
if (!attrsResult.replaced) {
|
|
2866
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2867
|
+
res.end(JSON.stringify(attrsResult))
|
|
2868
|
+
return Promise.resolve(true)
|
|
2869
|
+
}
|
|
2870
|
+
if (childrenText != null) {
|
|
2871
|
+
replaceTextContent(route.filePath, component, childrenText, instanceIndex ?? 0)
|
|
2872
|
+
}
|
|
2873
|
+
const display = { ...attrsResult, file: relative(cwd, attrsResult.file) }
|
|
2874
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2875
|
+
res.end(JSON.stringify(display))
|
|
2876
|
+
return Promise.resolve(true)
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
if (path === '/_davaux/move' && req.method === 'POST') {
|
|
2880
|
+
const body = await new Promise<string>((resolve) => {
|
|
2881
|
+
const chunks: Buffer[] = []
|
|
2882
|
+
req.on('data', (c: Buffer) => chunks.push(c))
|
|
2883
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
|
|
2884
|
+
req.on('error', () => resolve('{}'))
|
|
2885
|
+
})
|
|
2886
|
+
const { page, name, isComponent, instanceIndex, direction } = JSON.parse(body) as {
|
|
2887
|
+
page: string
|
|
2888
|
+
name: string
|
|
2889
|
+
isComponent: boolean
|
|
2890
|
+
instanceIndex?: number
|
|
2891
|
+
direction: 'up' | 'down'
|
|
2892
|
+
}
|
|
2893
|
+
if (!page || !name || !direction) {
|
|
2894
|
+
res.writeHead(400, { 'Content-Type': 'application/json' })
|
|
2895
|
+
res.end(JSON.stringify({ moved: false, error: 'Missing required fields' }))
|
|
2896
|
+
return Promise.resolve(true)
|
|
2897
|
+
}
|
|
2898
|
+
const pageUrl = (page ?? '/').split('?')[0]
|
|
2899
|
+
const route = scan.routes.find((r) => r.urlPattern === pageUrl && r.type === 'page')
|
|
2900
|
+
if (!route) {
|
|
2901
|
+
res.writeHead(404, { 'Content-Type': 'application/json' })
|
|
2902
|
+
res.end(JSON.stringify({ moved: false, error: `No page route found for: ${pageUrl}` }))
|
|
2903
|
+
return Promise.resolve(true)
|
|
2904
|
+
}
|
|
2905
|
+
const result = moveNode(route.filePath, name, isComponent ?? false, instanceIndex ?? 0, direction)
|
|
2906
|
+
const display = result.moved ? { ...result, file: relative(cwd, result.file) } : result
|
|
2907
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
2908
|
+
res.end(JSON.stringify(display))
|
|
2909
|
+
return Promise.resolve(true)
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
if (path === '/_davaux/styles.css') {
|
|
2913
|
+
res.writeHead(200, { 'Content-Type': 'text/css' })
|
|
2914
|
+
if (existsSync(stylesOutFile)) {
|
|
2915
|
+
createReadStream(stylesOutFile).pipe(res)
|
|
2916
|
+
} else {
|
|
2917
|
+
res.end()
|
|
2918
|
+
}
|
|
2919
|
+
return Promise.resolve(true)
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
if (path === '/_davaux/islands.js') {
|
|
2923
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript' })
|
|
2924
|
+
createReadStream(islandsOutFile).pipe(res)
|
|
2925
|
+
return Promise.resolve(true)
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
if (hasClient && path === '/_davaux/client.js') {
|
|
2929
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript' })
|
|
2930
|
+
createReadStream(clientOutFile).pipe(res)
|
|
2931
|
+
return Promise.resolve(true)
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
if (staticHandler) return Promise.resolve(staticHandler(req, res))
|
|
2935
|
+
return Promise.resolve(false)
|
|
2936
|
+
},
|
|
2937
|
+
})
|
|
2938
|
+
|
|
2939
|
+
serverReady = true
|
|
2940
|
+
|
|
2941
|
+
// ─── Config hot reload ────────────────────────────────────────────────────────
|
|
2942
|
+
const CONFIG_NAMES = ['davaux.config.ts', 'davaux.config.js', 'davaux.config.mjs']
|
|
2943
|
+
const activeConfigFile = CONFIG_NAMES.map((f) => join(cwd, f)).find(existsSync)
|
|
2944
|
+
|
|
2945
|
+
if (activeConfigFile) {
|
|
2946
|
+
let configReloadTimer: ReturnType<typeof setTimeout> | undefined
|
|
2947
|
+
fsWatch(activeConfigFile, () => {
|
|
2948
|
+
if (configReloadTimer) clearTimeout(configReloadTimer)
|
|
2949
|
+
configReloadTimer = setTimeout(async () => {
|
|
2950
|
+
configReloadTimer = undefined
|
|
2951
|
+
try {
|
|
2952
|
+
app = makeApp(scan, islands)
|
|
2953
|
+
console.log('[davaux] Config reloaded')
|
|
2954
|
+
scheduleReload()
|
|
2955
|
+
} catch (err) {
|
|
2956
|
+
console.error('[davaux] Failed to reload config:', err)
|
|
2957
|
+
}
|
|
2958
|
+
}, 300)
|
|
2959
|
+
})
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
await ctx.watch()
|
|
2963
|
+
if (islandsCtx) await islandsCtx.watch()
|
|
2964
|
+
if (clientCtx) await clientCtx.watch()
|
|
2965
|
+
if (middlewareCtx) await middlewareCtx.watch()
|
|
2966
|
+
console.log(' Watching for changes...')
|
|
2967
|
+
|
|
2968
|
+
// ─── Structural rebuild on file addition / removal ────────────────────────
|
|
2969
|
+
// esbuild watch() only tracks changes to existing entry points. Adding or
|
|
2970
|
+
// removing a route/island file requires disposing the old context and
|
|
2971
|
+
// creating a new one with the updated entry list.
|
|
2972
|
+
|
|
2973
|
+
let isRebuilding = false
|
|
2974
|
+
let rebuildTimer: ReturnType<typeof setTimeout> | undefined
|
|
2975
|
+
|
|
2976
|
+
async function handleStructuralChange(): Promise<void> {
|
|
2977
|
+
const newScan = await scanRoutes(routesDir, scannerSuffixes)
|
|
2978
|
+
const newIslands = await scanIslands(islandsDir)
|
|
2979
|
+
const newSourceFiles = collectSourceFiles(newScan)
|
|
2980
|
+
|
|
2981
|
+
// Skip if the file set is identical
|
|
2982
|
+
const same =
|
|
2983
|
+
newSourceFiles.length === sourceFiles.length &&
|
|
2984
|
+
newSourceFiles.every((f) => sourceFiles.includes(f)) &&
|
|
2985
|
+
newIslands.length === islands.length &&
|
|
2986
|
+
newIslands.every((i) => islands.some((j) => j.filePath === i.filePath))
|
|
2987
|
+
if (same) return
|
|
2988
|
+
|
|
2989
|
+
sourceFiles = newSourceFiles
|
|
2990
|
+
scan = newScan
|
|
2991
|
+
islands = newIslands
|
|
2992
|
+
|
|
2993
|
+
ctx = await context({
|
|
2994
|
+
entryPoints: sourceFiles,
|
|
2995
|
+
outdir: outDir,
|
|
2996
|
+
outbase: routesDir,
|
|
2997
|
+
format: 'esm',
|
|
2998
|
+
platform: 'node',
|
|
2999
|
+
target: 'node22',
|
|
3000
|
+
bundle: true,
|
|
3001
|
+
external: serverExternal,
|
|
3002
|
+
jsx: 'automatic',
|
|
3003
|
+
jsxImportSource: 'davaux/oml',
|
|
3004
|
+
sourcemap: 'inline',
|
|
3005
|
+
alias: { ...userAlias, 'davaux/client': 'davaux/signal' },
|
|
3006
|
+
plugins: [
|
|
3007
|
+
reloadPlugin,
|
|
3008
|
+
islandServerPlugin(islandsDir),
|
|
3009
|
+
cssCollectorPlugin(dauxDir, stylesOutFile),
|
|
3010
|
+
...extraPlugins,
|
|
3011
|
+
],
|
|
3012
|
+
})
|
|
3013
|
+
await ctx.rebuild()
|
|
3014
|
+
|
|
3015
|
+
if (islands.length > 0) {
|
|
3016
|
+
islandsCtx = await context({
|
|
3017
|
+
stdin: {
|
|
3018
|
+
contents: generateIslandsEntry(islands),
|
|
3019
|
+
loader: 'ts',
|
|
3020
|
+
resolveDir: cwd,
|
|
3021
|
+
},
|
|
3022
|
+
outfile: islandsOutFile,
|
|
3023
|
+
format: 'esm',
|
|
3024
|
+
platform: 'browser',
|
|
3025
|
+
target: 'es2022',
|
|
3026
|
+
bundle: true,
|
|
3027
|
+
jsx: 'automatic',
|
|
3028
|
+
jsxImportSource: 'davaux/client',
|
|
3029
|
+
sourcemap: 'inline',
|
|
3030
|
+
alias: userAlias,
|
|
3031
|
+
tsconfigRaw: JSON.stringify({
|
|
3032
|
+
compilerOptions: { jsx: 'react-jsx', jsxImportSource: 'davaux/client' },
|
|
3033
|
+
}),
|
|
3034
|
+
plugins: [reloadPlugin, cssCollectorPlugin(dauxDir, stylesOutFile), ...extraPlugins],
|
|
3035
|
+
})
|
|
3036
|
+
await islandsCtx.rebuild()
|
|
3037
|
+
} else {
|
|
3038
|
+
islandsCtx = undefined
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
app = makeApp(scan, islands)
|
|
3042
|
+
|
|
3043
|
+
await ctx.watch()
|
|
3044
|
+
if (islandsCtx) await islandsCtx.watch()
|
|
3045
|
+
|
|
3046
|
+
scheduleReload()
|
|
3047
|
+
console.log('[davaux] Routes updated')
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
function onStructuralChange(): void {
|
|
3051
|
+
// Immediately stop esbuild's watch so it cannot attempt a rebuild with the
|
|
3052
|
+
// now-stale entry list (e.g. an entry point file that was just deleted).
|
|
3053
|
+
// Errors are swallowed — the context may already be disposed.
|
|
3054
|
+
ctx.dispose().catch(() => {})
|
|
3055
|
+
islandsCtx?.dispose().catch(() => {})
|
|
3056
|
+
|
|
3057
|
+
if (rebuildTimer) clearTimeout(rebuildTimer)
|
|
3058
|
+
rebuildTimer = setTimeout(() => {
|
|
3059
|
+
if (isRebuilding) return
|
|
3060
|
+
isRebuilding = true
|
|
3061
|
+
handleStructuralChange()
|
|
3062
|
+
.catch((err) => console.error('[davaux] Route rebuild failed:', err))
|
|
3063
|
+
.finally(() => {
|
|
3064
|
+
isRebuilding = false
|
|
3065
|
+
})
|
|
3066
|
+
}, 200)
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
fsWatch(routesDir, { recursive: true }, (event, filename) => {
|
|
3070
|
+
if (event !== 'rename' || !filename) return
|
|
3071
|
+
if (!/\.(tsx?|jsx?|mdx?)$/.test(filename)) return
|
|
3072
|
+
onStructuralChange()
|
|
3073
|
+
})
|
|
3074
|
+
|
|
3075
|
+
if (existsSync(islandsDir)) {
|
|
3076
|
+
fsWatch(islandsDir, { recursive: true }, (event, filename) => {
|
|
3077
|
+
if (event !== 'rename' || !filename) return
|
|
3078
|
+
if (!/\.(tsx?|jsx?|mdx?)$/.test(filename)) return
|
|
3079
|
+
onStructuralChange()
|
|
3080
|
+
})
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
startTypeChecker(cwd)
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// ─── Type checker ─────────────────────────────────────────────────────────────
|
|
3087
|
+
|
|
3088
|
+
function startTypeChecker(cwd: string): void {
|
|
3089
|
+
const tscBin = join(cwd, 'node_modules', '.bin', 'tsc')
|
|
3090
|
+
if (!existsSync(tscBin) || !existsSync(join(cwd, 'tsconfig.json'))) return
|
|
3091
|
+
|
|
3092
|
+
const proc = spawn(tscBin, ['--noEmit', '--watch', '--preserveWatchOutput'], {
|
|
3093
|
+
cwd,
|
|
3094
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
3095
|
+
})
|
|
3096
|
+
proc.on('error', () => {})
|
|
3097
|
+
process.on('exit', () => proc.kill())
|
|
3098
|
+
}
|