@nuraly/lumenjs 0.1.3 → 0.2.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.
Files changed (306) hide show
  1. package/README.md +62 -282
  2. package/dist/auth/config.d.ts +23 -0
  3. package/dist/auth/config.js +115 -0
  4. package/dist/auth/guard.d.ts +12 -0
  5. package/dist/auth/guard.js +28 -0
  6. package/dist/auth/index.d.ts +3 -0
  7. package/dist/auth/index.js +1 -0
  8. package/dist/auth/middleware.d.ts +23 -0
  9. package/dist/auth/middleware.js +89 -0
  10. package/dist/auth/native-auth.d.ts +82 -0
  11. package/dist/auth/native-auth.js +340 -0
  12. package/dist/auth/oidc-client.d.ts +17 -0
  13. package/dist/auth/oidc-client.js +123 -0
  14. package/dist/auth/providers/google.d.ts +23 -0
  15. package/dist/auth/providers/google.js +25 -0
  16. package/dist/auth/providers/index.d.ts +2 -0
  17. package/dist/auth/providers/index.js +1 -0
  18. package/dist/auth/routes/login.d.ts +8 -0
  19. package/dist/auth/routes/login.js +121 -0
  20. package/dist/auth/routes/logout.d.ts +4 -0
  21. package/dist/auth/routes/logout.js +79 -0
  22. package/dist/auth/routes/oidc-callback.d.ts +3 -0
  23. package/dist/auth/routes/oidc-callback.js +70 -0
  24. package/dist/auth/routes/password.d.ts +5 -0
  25. package/dist/auth/routes/password.js +149 -0
  26. package/dist/auth/routes/signup.d.ts +3 -0
  27. package/dist/auth/routes/signup.js +81 -0
  28. package/dist/auth/routes/token.d.ts +4 -0
  29. package/dist/auth/routes/token.js +70 -0
  30. package/dist/auth/routes/totp.d.ts +22 -0
  31. package/dist/auth/routes/totp.js +232 -0
  32. package/dist/auth/routes/utils.d.ts +7 -0
  33. package/dist/auth/routes/utils.js +35 -0
  34. package/dist/auth/routes/verify.d.ts +3 -0
  35. package/dist/auth/routes/verify.js +26 -0
  36. package/dist/auth/routes.d.ts +8 -0
  37. package/dist/auth/routes.js +124 -0
  38. package/dist/auth/session.d.ts +8 -0
  39. package/dist/auth/session.js +54 -0
  40. package/dist/auth/token.d.ts +33 -0
  41. package/dist/auth/token.js +90 -0
  42. package/dist/auth/types.d.ts +156 -0
  43. package/dist/auth/types.js +2 -0
  44. package/dist/build/build-client.d.ts +15 -0
  45. package/dist/build/build-client.js +45 -0
  46. package/dist/build/build-prerender.d.ts +11 -0
  47. package/dist/build/build-prerender.js +159 -0
  48. package/dist/build/build-server.d.ts +18 -0
  49. package/dist/build/build-server.js +107 -0
  50. package/dist/build/build.js +60 -123
  51. package/dist/build/scan.d.ts +18 -0
  52. package/dist/build/scan.js +77 -6
  53. package/dist/build/serve-api.js +8 -2
  54. package/dist/build/serve-loaders.d.ts +4 -4
  55. package/dist/build/serve-loaders.js +26 -18
  56. package/dist/build/serve-ssr.js +38 -11
  57. package/dist/build/serve-static.js +3 -3
  58. package/dist/build/serve.js +341 -18
  59. package/dist/cli.js +37 -6
  60. package/dist/communication/encryption.d.ts +35 -0
  61. package/dist/communication/encryption.js +90 -0
  62. package/dist/communication/handlers/context.d.ts +27 -0
  63. package/dist/communication/handlers/context.js +1 -0
  64. package/dist/communication/handlers/conversation.d.ts +24 -0
  65. package/dist/communication/handlers/conversation.js +113 -0
  66. package/dist/communication/handlers/file-upload.d.ts +17 -0
  67. package/dist/communication/handlers/file-upload.js +62 -0
  68. package/dist/communication/handlers/messaging.d.ts +30 -0
  69. package/dist/communication/handlers/messaging.js +237 -0
  70. package/dist/communication/handlers/presence.d.ts +15 -0
  71. package/dist/communication/handlers/presence.js +76 -0
  72. package/dist/communication/handlers.d.ts +5 -0
  73. package/dist/communication/handlers.js +5 -0
  74. package/dist/communication/index.d.ts +9 -0
  75. package/dist/communication/index.js +7 -0
  76. package/dist/communication/link-preview.d.ts +18 -0
  77. package/dist/communication/link-preview.js +115 -0
  78. package/dist/communication/schema.d.ts +10 -0
  79. package/dist/communication/schema.js +101 -0
  80. package/dist/communication/server.d.ts +86 -0
  81. package/dist/communication/server.js +212 -0
  82. package/dist/communication/signaling.d.ts +43 -0
  83. package/dist/communication/signaling.js +271 -0
  84. package/dist/communication/store.d.ts +71 -0
  85. package/dist/communication/store.js +289 -0
  86. package/dist/communication/types.d.ts +454 -0
  87. package/dist/communication/types.js +1 -0
  88. package/dist/create.d.ts +1 -0
  89. package/dist/create.js +55 -0
  90. package/dist/db/auto-migrate.d.ts +3 -0
  91. package/dist/db/auto-migrate.js +100 -0
  92. package/dist/db/client.d.ts +3 -0
  93. package/dist/db/client.js +18 -0
  94. package/dist/db/index.d.ts +17 -13
  95. package/dist/db/index.js +205 -26
  96. package/dist/db/seed.d.ts +12 -0
  97. package/dist/db/seed.js +88 -0
  98. package/dist/db/table.d.ts +10 -0
  99. package/dist/db/table.js +12 -0
  100. package/dist/dev-server/config.d.ts +11 -0
  101. package/dist/dev-server/config.js +40 -20
  102. package/dist/dev-server/index-html.d.ts +4 -0
  103. package/dist/dev-server/index-html.js +21 -6
  104. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  105. package/dist/dev-server/nuralyui-aliases.js +115 -94
  106. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  107. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  108. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  109. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  110. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  111. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  113. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  114. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  115. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  116. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  117. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  118. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  119. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  120. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  121. package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
  122. package/dist/dev-server/plugins/vite-plugin-routes.js +16 -5
  123. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  125. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  126. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  127. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  128. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  129. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +140 -3
  130. package/dist/dev-server/server.js +242 -70
  131. package/dist/dev-server/ssr-render.d.ts +2 -1
  132. package/dist/dev-server/ssr-render.js +117 -50
  133. package/dist/editor/ai/backend.d.ts +20 -0
  134. package/dist/editor/ai/backend.js +113 -0
  135. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  136. package/dist/editor/ai/claude-code-client.js +145 -0
  137. package/dist/editor/ai/deepseek-client.d.ts +7 -0
  138. package/dist/editor/ai/deepseek-client.js +113 -0
  139. package/dist/editor/ai/opencode-client.d.ts +14 -0
  140. package/dist/editor/ai/opencode-client.js +99 -0
  141. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  142. package/dist/editor/ai/snapshot-store.js +35 -0
  143. package/dist/editor/ai/types.d.ts +30 -0
  144. package/dist/editor/ai/types.js +136 -0
  145. package/dist/editor/ai-chat-panel.d.ts +13 -0
  146. package/dist/editor/ai-chat-panel.js +613 -0
  147. package/dist/editor/ai-markdown.d.ts +10 -0
  148. package/dist/editor/ai-markdown.js +70 -0
  149. package/dist/editor/ai-project-panel.d.ts +11 -0
  150. package/dist/editor/ai-project-panel.js +332 -0
  151. package/dist/editor/ast-modification.d.ts +11 -0
  152. package/dist/editor/ast-modification.js +1 -0
  153. package/dist/editor/ast-service.d.ts +30 -0
  154. package/dist/editor/ast-service.js +180 -0
  155. package/dist/editor/css-rules.d.ts +54 -0
  156. package/dist/editor/css-rules.js +423 -0
  157. package/dist/editor/editor-api-client.d.ts +51 -0
  158. package/dist/editor/editor-api-client.js +162 -0
  159. package/dist/editor/editor-bridge.d.ts +1 -0
  160. package/dist/editor/editor-bridge.js +18 -8
  161. package/dist/editor/editor-toolbar.d.ts +14 -0
  162. package/dist/editor/editor-toolbar.js +115 -0
  163. package/dist/editor/file-editor.d.ts +9 -0
  164. package/dist/editor/file-editor.js +236 -0
  165. package/dist/editor/file-service.d.ts +16 -0
  166. package/dist/editor/file-service.js +52 -0
  167. package/dist/editor/i18n-key-gen.d.ts +1 -0
  168. package/dist/editor/i18n-key-gen.js +7 -0
  169. package/dist/editor/inline-text-edit.d.ts +5 -0
  170. package/dist/editor/inline-text-edit.js +173 -92
  171. package/dist/editor/overlay-events.d.ts +5 -0
  172. package/dist/editor/overlay-events.js +364 -0
  173. package/dist/editor/overlay-hmr.d.ts +2 -0
  174. package/dist/editor/overlay-hmr.js +76 -0
  175. package/dist/editor/overlay-selection.d.ts +29 -0
  176. package/dist/editor/overlay-selection.js +148 -0
  177. package/dist/editor/overlay-utils.d.ts +12 -0
  178. package/dist/editor/overlay-utils.js +59 -0
  179. package/dist/editor/properties-panel-persist.d.ts +14 -0
  180. package/dist/editor/properties-panel-persist.js +70 -0
  181. package/dist/editor/properties-panel-rows.d.ts +10 -0
  182. package/dist/editor/properties-panel-rows.js +349 -0
  183. package/dist/editor/properties-panel-styles.d.ts +4 -0
  184. package/dist/editor/properties-panel-styles.js +174 -0
  185. package/dist/editor/properties-panel.d.ts +4 -0
  186. package/dist/editor/properties-panel.js +148 -0
  187. package/dist/editor/property-registry.d.ts +16 -0
  188. package/dist/editor/property-registry.js +303 -0
  189. package/dist/editor/standalone-file-panel.d.ts +0 -0
  190. package/dist/editor/standalone-file-panel.js +1 -0
  191. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  192. package/dist/editor/standalone-overlay-dom.js +1 -0
  193. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  194. package/dist/editor/standalone-overlay-styles.js +1 -0
  195. package/dist/editor/standalone-overlay.d.ts +1 -0
  196. package/dist/editor/standalone-overlay.js +76 -0
  197. package/dist/editor/syntax-highlighter.d.ts +4 -0
  198. package/dist/editor/syntax-highlighter.js +81 -0
  199. package/dist/editor/text-toolbar.d.ts +11 -0
  200. package/dist/editor/text-toolbar.js +327 -0
  201. package/dist/editor/toolbar-styles.d.ts +4 -0
  202. package/dist/editor/toolbar-styles.js +198 -0
  203. package/dist/email/index.d.ts +32 -0
  204. package/dist/email/index.js +154 -0
  205. package/dist/email/providers/resend.d.ts +2 -0
  206. package/dist/email/providers/resend.js +24 -0
  207. package/dist/email/providers/sendgrid.d.ts +2 -0
  208. package/dist/email/providers/sendgrid.js +31 -0
  209. package/dist/email/providers/smtp.d.ts +13 -0
  210. package/dist/email/providers/smtp.js +125 -0
  211. package/dist/email/template-engine.d.ts +18 -0
  212. package/dist/email/template-engine.js +116 -0
  213. package/dist/email/templates/base.d.ts +9 -0
  214. package/dist/email/templates/base.js +65 -0
  215. package/dist/email/templates/password-reset.d.ts +5 -0
  216. package/dist/email/templates/password-reset.js +15 -0
  217. package/dist/email/templates/verify-email.d.ts +5 -0
  218. package/dist/email/templates/verify-email.js +15 -0
  219. package/dist/email/templates/welcome.d.ts +5 -0
  220. package/dist/email/templates/welcome.js +13 -0
  221. package/dist/email/types.d.ts +49 -0
  222. package/dist/email/types.js +1 -0
  223. package/dist/llms/generate.d.ts +46 -0
  224. package/dist/llms/generate.js +185 -0
  225. package/dist/permissions/guard.d.ts +28 -0
  226. package/dist/permissions/guard.js +30 -0
  227. package/dist/permissions/index.d.ts +6 -0
  228. package/dist/permissions/index.js +3 -0
  229. package/dist/permissions/service.d.ts +80 -0
  230. package/dist/permissions/service.js +210 -0
  231. package/dist/permissions/tables.d.ts +5 -0
  232. package/dist/permissions/tables.js +68 -0
  233. package/dist/permissions/types.d.ts +33 -0
  234. package/dist/permissions/types.js +1 -0
  235. package/dist/runtime/app-shell.d.ts +1 -1
  236. package/dist/runtime/app-shell.js +164 -0
  237. package/dist/runtime/auth.d.ts +10 -0
  238. package/dist/runtime/auth.js +30 -0
  239. package/dist/runtime/communication.d.ts +137 -0
  240. package/dist/runtime/communication.js +228 -0
  241. package/dist/runtime/error-boundary.d.ts +23 -0
  242. package/dist/runtime/error-boundary.js +120 -0
  243. package/dist/runtime/i18n.d.ts +6 -1
  244. package/dist/runtime/i18n.js +42 -21
  245. package/dist/runtime/island.d.ts +16 -0
  246. package/dist/runtime/island.js +80 -0
  247. package/dist/runtime/router-data.d.ts +3 -0
  248. package/dist/runtime/router-data.js +102 -17
  249. package/dist/runtime/router-hydration.js +34 -2
  250. package/dist/runtime/router.d.ts +19 -2
  251. package/dist/runtime/router.js +237 -43
  252. package/dist/runtime/socket-client.d.ts +2 -0
  253. package/dist/runtime/socket-client.js +30 -0
  254. package/dist/runtime/webrtc.d.ts +91 -0
  255. package/dist/runtime/webrtc.js +428 -0
  256. package/dist/shared/dom-shims.js +4 -2
  257. package/dist/shared/graceful-shutdown.d.ts +8 -0
  258. package/dist/shared/graceful-shutdown.js +36 -0
  259. package/dist/shared/health.d.ts +8 -0
  260. package/dist/shared/health.js +25 -0
  261. package/dist/shared/llms-txt.d.ts +31 -0
  262. package/dist/shared/llms-txt.js +85 -0
  263. package/dist/shared/logger.d.ts +32 -0
  264. package/dist/shared/logger.js +93 -0
  265. package/dist/shared/meta.d.ts +27 -0
  266. package/dist/shared/meta.js +71 -0
  267. package/dist/shared/middleware-runner.d.ts +9 -0
  268. package/dist/shared/middleware-runner.js +29 -0
  269. package/dist/shared/rate-limit.d.ts +18 -0
  270. package/dist/shared/rate-limit.js +71 -0
  271. package/dist/shared/request-id.d.ts +5 -0
  272. package/dist/shared/request-id.js +18 -0
  273. package/dist/shared/route-matching.js +16 -1
  274. package/dist/shared/security-headers.d.ts +18 -0
  275. package/dist/shared/security-headers.js +38 -0
  276. package/dist/shared/socket-io-setup.d.ts +11 -0
  277. package/dist/shared/socket-io-setup.js +51 -0
  278. package/dist/shared/types.d.ts +15 -0
  279. package/dist/shared/utils.d.ts +33 -7
  280. package/dist/shared/utils.js +164 -27
  281. package/dist/storage/adapters/local.d.ts +44 -0
  282. package/dist/storage/adapters/local.js +85 -0
  283. package/dist/storage/adapters/s3.d.ts +32 -0
  284. package/dist/storage/adapters/s3.js +119 -0
  285. package/dist/storage/adapters/types.d.ts +53 -0
  286. package/dist/storage/adapters/types.js +1 -0
  287. package/dist/storage/index.d.ts +76 -0
  288. package/dist/storage/index.js +83 -0
  289. package/package.json +45 -7
  290. package/templates/blog/api/posts.ts +4 -18
  291. package/templates/blog/data/migrations/001_init.sql +6 -5
  292. package/templates/blog/lumenjs.config.ts +3 -0
  293. package/templates/blog/package.json +14 -0
  294. package/templates/blog/pages/_layout.ts +25 -0
  295. package/templates/blog/pages/index.ts +48 -22
  296. package/templates/blog/pages/posts/[slug].ts +45 -20
  297. package/templates/blog/pages/tag/[tag].ts +44 -0
  298. package/templates/dashboard/api/stats.ts +8 -5
  299. package/templates/dashboard/lumenjs.config.ts +3 -0
  300. package/templates/dashboard/package.json +14 -0
  301. package/templates/dashboard/pages/_layout.ts +25 -0
  302. package/templates/dashboard/pages/index.ts +54 -23
  303. package/templates/dashboard/pages/settings/index.ts +29 -0
  304. package/templates/default/lumenjs.config.ts +3 -0
  305. package/templates/default/package.json +14 -0
  306. package/templates/default/pages/index.ts +24 -0
@@ -0,0 +1,423 @@
1
+ /**
2
+ * CSS Rules — parsing, matching, rendering, and persistence of CSS rules
3
+ * from component source files (static styles = css`...`).
4
+ */
5
+ import { readFile, writeFile } from './editor-api-client.js';
6
+ /** CSS style properties with known enum values */
7
+ export const CSS_ENUMS = {
8
+ display: ['block', 'flex', 'grid', 'inline', 'inline-block', 'inline-flex', 'none'],
9
+ position: ['static', 'relative', 'absolute', 'fixed', 'sticky'],
10
+ overflow: ['visible', 'hidden', 'scroll', 'auto'],
11
+ 'overflow-x': ['visible', 'hidden', 'scroll', 'auto'],
12
+ 'overflow-y': ['visible', 'hidden', 'scroll', 'auto'],
13
+ 'text-align': ['left', 'center', 'right', 'justify'],
14
+ 'flex-direction': ['row', 'column', 'row-reverse', 'column-reverse'],
15
+ 'flex-wrap': ['nowrap', 'wrap', 'wrap-reverse'],
16
+ 'justify-content': ['flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly'],
17
+ 'align-items': ['flex-start', 'flex-end', 'center', 'baseline', 'stretch'],
18
+ 'align-self': ['auto', 'flex-start', 'flex-end', 'center', 'baseline', 'stretch'],
19
+ 'font-weight': ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
20
+ 'font-style': ['normal', 'italic', 'oblique'],
21
+ 'text-decoration': ['none', 'underline', 'overline', 'line-through'],
22
+ 'text-transform': ['none', 'uppercase', 'lowercase', 'capitalize'],
23
+ 'white-space': ['normal', 'nowrap', 'pre', 'pre-wrap', 'pre-line'],
24
+ 'word-break': ['normal', 'break-all', 'keep-all', 'break-word'],
25
+ visibility: ['visible', 'hidden', 'collapse'],
26
+ 'box-sizing': ['content-box', 'border-box'],
27
+ cursor: ['auto', 'default', 'pointer', 'wait', 'text', 'move', 'not-allowed', 'grab', 'grabbing'],
28
+ 'pointer-events': ['auto', 'none'],
29
+ float: ['none', 'left', 'right'],
30
+ clear: ['none', 'left', 'right', 'both'],
31
+ };
32
+ /** Common CSS properties for the "add style" autocomplete */
33
+ export const COMMON_CSS_PROPS = [
34
+ 'display', 'position', 'top', 'right', 'bottom', 'left',
35
+ 'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',
36
+ 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
37
+ 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
38
+ 'border', 'border-radius', 'border-color', 'border-width', 'border-style',
39
+ 'background', 'background-color', 'background-image',
40
+ 'color', 'font-size', 'font-weight', 'font-family', 'font-style',
41
+ 'text-align', 'text-decoration', 'text-transform', 'line-height', 'letter-spacing',
42
+ 'flex', 'flex-direction', 'flex-wrap', 'justify-content', 'align-items', 'align-self', 'gap',
43
+ 'grid-template-columns', 'grid-template-rows', 'grid-gap',
44
+ 'overflow', 'overflow-x', 'overflow-y',
45
+ 'opacity', 'z-index', 'cursor', 'pointer-events',
46
+ 'box-shadow', 'transition', 'transform',
47
+ 'white-space', 'word-break', 'visibility',
48
+ ];
49
+ /**
50
+ * Find the source file for the host custom element (the one with static styles).
51
+ * Walks up from the selected element through shadow DOM to find the host.
52
+ */
53
+ export function findHostSourceFile(element) {
54
+ // If the element itself has a source, check its host
55
+ const root = element.getRootNode();
56
+ if (root instanceof ShadowRoot) {
57
+ const host = root.host;
58
+ const src = host.getAttribute('data-nk-source');
59
+ if (src) {
60
+ const lastColon = src.lastIndexOf(':');
61
+ return lastColon !== -1 ? src.substring(0, lastColon) : null;
62
+ }
63
+ }
64
+ // Fallback: check the element itself
65
+ const src = element.getAttribute('data-nk-source');
66
+ if (src) {
67
+ const lastColon = src.lastIndexOf(':');
68
+ return lastColon !== -1 ? src.substring(0, lastColon) : null;
69
+ }
70
+ return null;
71
+ }
72
+ /**
73
+ * Extract CSS rules from a `static styles = css\`...\`` block in the source.
74
+ */
75
+ export function extractCssFromSource(source) {
76
+ const cssTagRegex = /css\s*`([\s\S]*?)`/;
77
+ const match = cssTagRegex.exec(source);
78
+ if (!match)
79
+ return null;
80
+ const cssContent = match[1];
81
+ const cssStart = match.index + match[0].indexOf('`') + 1;
82
+ const rules = parseCssRules(cssContent);
83
+ return { cssContent, cssStart, rules };
84
+ }
85
+ /**
86
+ * Simple CSS rule parser — extracts selector + properties from a CSS string.
87
+ * Handles nested @media by flattening rules inside.
88
+ */
89
+ export function parseCssRules(css) {
90
+ const rules = [];
91
+ let i = 0;
92
+ while (i < css.length) {
93
+ // Skip whitespace
94
+ while (i < css.length && /\s/.test(css[i]))
95
+ i++;
96
+ if (i >= css.length)
97
+ break;
98
+ // Check for @media or @keyframes — skip the outer block, parse inner rules
99
+ if (css[i] === '@') {
100
+ const atStart = i;
101
+ const braceIdx = css.indexOf('{', i);
102
+ if (braceIdx === -1)
103
+ break;
104
+ const mediaSelector = css.substring(atStart, braceIdx).trim();
105
+ i = braceIdx + 1;
106
+ let depth = 1;
107
+ const innerStart = i;
108
+ while (i < css.length && depth > 0) {
109
+ if (css[i] === '{')
110
+ depth++;
111
+ else if (css[i] === '}')
112
+ depth--;
113
+ i++;
114
+ }
115
+ const innerCss = css.substring(innerStart, i - 1);
116
+ const innerRules = parseCssRules(innerCss);
117
+ for (const r of innerRules) {
118
+ rules.push({
119
+ ...r,
120
+ selector: `${mediaSelector} { ${r.selector} }`,
121
+ startOffset: innerStart + r.startOffset,
122
+ endOffset: innerStart + r.endOffset,
123
+ });
124
+ }
125
+ continue;
126
+ }
127
+ // Regular rule: selector { ... }
128
+ const braceIdx = css.indexOf('{', i);
129
+ if (braceIdx === -1)
130
+ break;
131
+ const selector = css.substring(i, braceIdx).trim();
132
+ i = braceIdx + 1;
133
+ const startOffset = braceIdx;
134
+ const closeIdx = css.indexOf('}', i);
135
+ if (closeIdx === -1)
136
+ break;
137
+ const body = css.substring(i, closeIdx).trim();
138
+ const endOffset = closeIdx + 1;
139
+ i = closeIdx + 1;
140
+ const properties = [];
141
+ for (const decl of body.split(';')) {
142
+ const colonIdx = decl.indexOf(':');
143
+ if (colonIdx === -1)
144
+ continue;
145
+ const prop = decl.substring(0, colonIdx).trim();
146
+ const val = decl.substring(colonIdx + 1).trim();
147
+ if (prop && val)
148
+ properties.push([prop, val]);
149
+ }
150
+ if (selector) {
151
+ rules.push({ selector, properties, startOffset, endOffset });
152
+ }
153
+ }
154
+ return rules;
155
+ }
156
+ /**
157
+ * Check if a CSS selector matches the selected element.
158
+ */
159
+ export function selectorMatchesElement(selector, element) {
160
+ const tag = element.tagName.toLowerCase();
161
+ const classes = Array.from(element.classList);
162
+ const sel = selector.trim();
163
+ if (sel === ':host')
164
+ return false;
165
+ if (sel === tag)
166
+ return true;
167
+ if (sel.startsWith('.')) {
168
+ const selClasses = sel.split('.').filter(Boolean);
169
+ return selClasses.every(c => classes.includes(c));
170
+ }
171
+ if (sel.includes('.')) {
172
+ const dotIdx = sel.indexOf('.');
173
+ const selTag = sel.substring(0, dotIdx);
174
+ const selClasses = sel.substring(dotIdx).split('.').filter(Boolean);
175
+ if (selTag && selTag !== tag)
176
+ return false;
177
+ return selClasses.every(c => classes.includes(c));
178
+ }
179
+ return false;
180
+ }
181
+ export function isColorValue(name, value) {
182
+ if (/color/i.test(name))
183
+ return true;
184
+ if (typeof value === 'string' && (/^#[0-9a-f]{3,8}$/i.test(value) || /^rgb/i.test(value)))
185
+ return true;
186
+ return false;
187
+ }
188
+ export function normalizeToHex(value) {
189
+ if (/^#[0-9a-f]{6}$/i.test(value))
190
+ return value;
191
+ if (/^#[0-9a-f]{3}$/i.test(value)) {
192
+ return '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3];
193
+ }
194
+ const m = value.match(/(\d+)/g);
195
+ if (m && m.length >= 3) {
196
+ return '#' + [m[0], m[1], m[2]].map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
197
+ }
198
+ return '#000000';
199
+ }
200
+ /** Notify the overlay system that element layout may have changed */
201
+ export function notifyLayoutChange() {
202
+ window.dispatchEvent(new Event('resize'));
203
+ }
204
+ /**
205
+ * Render a CSS rule as a collapsible section in the panel.
206
+ */
207
+ export function renderCssRule(rule, extracted, fullSource, sourceFile, container, startOpen, currentElementRef, debounceTimers) {
208
+ const header = document.createElement('div');
209
+ header.className = 'nk-pp-rule-header';
210
+ const arrow = document.createElement('span');
211
+ arrow.className = 'nk-pp-toggle-arrow' + (startOpen ? ' open' : '');
212
+ arrow.textContent = '▶';
213
+ const selectorSpan = document.createElement('span');
214
+ selectorSpan.textContent = rule.selector;
215
+ header.appendChild(selectorSpan);
216
+ header.appendChild(arrow);
217
+ const body = document.createElement('div');
218
+ body.className = 'nk-pp-rule-body' + (startOpen ? ' open' : '');
219
+ for (const [prop, val] of rule.properties) {
220
+ const row = document.createElement('div');
221
+ row.className = 'nk-pp-row';
222
+ const label = document.createElement('div');
223
+ label.className = 'nk-pp-label';
224
+ label.textContent = prop;
225
+ label.title = prop;
226
+ row.appendChild(label);
227
+ const control = document.createElement('div');
228
+ control.className = 'nk-pp-control';
229
+ const enumVals = CSS_ENUMS[prop];
230
+ if (isColorValue(prop, val)) {
231
+ const wrap = document.createElement('div');
232
+ wrap.className = 'nk-pp-color-wrap';
233
+ const colorInput = document.createElement('input');
234
+ colorInput.type = 'color';
235
+ colorInput.value = normalizeToHex(val);
236
+ const textInput = document.createElement('input');
237
+ textInput.type = 'text';
238
+ textInput.value = val;
239
+ const sync = (newVal) => {
240
+ persistCssPropertyDebounced(sourceFile, fullSource, extracted, rule, prop, newVal, currentElementRef, debounceTimers);
241
+ };
242
+ colorInput.addEventListener('input', () => { textInput.value = colorInput.value; sync(colorInput.value); });
243
+ textInput.addEventListener('input', () => { sync(textInput.value); });
244
+ wrap.appendChild(colorInput);
245
+ wrap.appendChild(textInput);
246
+ control.appendChild(wrap);
247
+ }
248
+ else if (enumVals) {
249
+ const select = document.createElement('select');
250
+ const emptyOpt = document.createElement('option');
251
+ emptyOpt.value = '';
252
+ emptyOpt.textContent = '—';
253
+ select.appendChild(emptyOpt);
254
+ for (const v of enumVals) {
255
+ const opt = document.createElement('option');
256
+ opt.value = v;
257
+ opt.textContent = v;
258
+ select.appendChild(opt);
259
+ }
260
+ select.value = val;
261
+ select.addEventListener('change', () => {
262
+ persistCssPropertyDebounced(sourceFile, fullSource, extracted, rule, prop, select.value, currentElementRef, debounceTimers);
263
+ });
264
+ control.appendChild(select);
265
+ }
266
+ else {
267
+ const input = document.createElement('input');
268
+ input.type = 'text';
269
+ input.value = val;
270
+ input.addEventListener('input', () => {
271
+ persistCssPropertyDebounced(sourceFile, fullSource, extracted, rule, prop, input.value, currentElementRef, debounceTimers);
272
+ });
273
+ control.appendChild(input);
274
+ }
275
+ row.appendChild(control);
276
+ body.appendChild(row);
277
+ }
278
+ header.addEventListener('click', () => {
279
+ arrow.classList.toggle('open');
280
+ body.classList.toggle('open');
281
+ });
282
+ container.appendChild(header);
283
+ container.appendChild(body);
284
+ }
285
+ export function renderAllRules(rules, extracted, fullSource, sourceFile, container, currentElementRef, debounceTimers) {
286
+ const sep = document.createElement('div');
287
+ sep.className = 'nk-pp-group-header';
288
+ sep.textContent = 'ALL RULES';
289
+ sep.style.marginTop = '4px';
290
+ container.appendChild(sep);
291
+ for (const rule of rules) {
292
+ renderCssRule(rule, extracted, fullSource, sourceFile, container, false, currentElementRef, debounceTimers);
293
+ }
294
+ }
295
+ /**
296
+ * Load CSS rules from the component source file and render matching rules in the panel.
297
+ */
298
+ export async function loadCssRulesForElement(element, cssGroup, currentElementRef, debounceTimers) {
299
+ const loadingRow = cssGroup.querySelector('.nk-pp-row');
300
+ const sourceFile = findHostSourceFile(element);
301
+ if (!sourceFile) {
302
+ if (loadingRow)
303
+ loadingRow.innerHTML = '<span class="nk-pp-label" style="width:auto;color:#4a4662">No source file</span>';
304
+ return;
305
+ }
306
+ try {
307
+ const data = await readFile(sourceFile);
308
+ const extracted = extractCssFromSource(data.content);
309
+ if (!extracted || extracted.rules.length === 0) {
310
+ if (loadingRow)
311
+ loadingRow.innerHTML = '<span class="nk-pp-label" style="width:auto;color:#4a4662">No static styles</span>';
312
+ return;
313
+ }
314
+ if (loadingRow)
315
+ loadingRow.remove();
316
+ const matchingRules = extracted.rules.filter(r => {
317
+ const innerSel = r.selector.includes('{') ? r.selector.substring(r.selector.lastIndexOf('{') + 1).trim().replace('}', '').trim() : r.selector;
318
+ return selectorMatchesElement(innerSel, element);
319
+ });
320
+ const root = element.getRootNode();
321
+ const isHost = !(root instanceof ShadowRoot);
322
+ if (isHost) {
323
+ const hostRules = extracted.rules.filter(r => r.selector.trim() === ':host');
324
+ matchingRules.unshift(...hostRules);
325
+ }
326
+ if (matchingRules.length === 0) {
327
+ const noMatch = document.createElement('div');
328
+ noMatch.className = 'nk-pp-row';
329
+ noMatch.innerHTML = '<span class="nk-pp-label" style="width:auto;color:#4a4662">No matching rules</span>';
330
+ cssGroup.appendChild(noMatch);
331
+ renderAllRules(extracted.rules, extracted, data.content, sourceFile, cssGroup, currentElementRef, debounceTimers);
332
+ return;
333
+ }
334
+ for (const rule of matchingRules) {
335
+ renderCssRule(rule, extracted, data.content, sourceFile, cssGroup, true, currentElementRef, debounceTimers);
336
+ }
337
+ const otherRules = extracted.rules.filter(r => !matchingRules.includes(r));
338
+ if (otherRules.length > 0) {
339
+ renderAllRules(otherRules, extracted, data.content, sourceFile, cssGroup, currentElementRef, debounceTimers);
340
+ }
341
+ }
342
+ catch {
343
+ if (loadingRow)
344
+ loadingRow.innerHTML = '<span class="nk-pp-label" style="width:auto;color:#4a4662">Failed to load</span>';
345
+ }
346
+ }
347
+ /**
348
+ * Persist a CSS property change back to the source file.
349
+ */
350
+ function persistCssPropertyDebounced(sourceFile, fullSource, extracted, rule, prop, newVal, currentElementRef, debounceTimers) {
351
+ const key = `__css_${rule.selector}_${prop}`;
352
+ if (debounceTimers[key])
353
+ clearTimeout(debounceTimers[key]);
354
+ debounceTimers[key] = setTimeout(async () => {
355
+ try {
356
+ const latest = await readFile(sourceFile);
357
+ const latestExtracted = extractCssFromSource(latest.content);
358
+ if (!latestExtracted)
359
+ return;
360
+ const latestRule = latestExtracted.rules.find(r => r.selector === rule.selector);
361
+ if (!latestRule)
362
+ return;
363
+ const newProps = latestRule.properties.map(([p, v]) => p === prop ? `${p}: ${newVal}` : `${p}: ${v}`);
364
+ const newBody = ' ' + newProps.join('; ') + '; ';
365
+ const cssContent = latestExtracted.cssContent;
366
+ const openBrace = latestRule.startOffset;
367
+ const closeBrace = latestRule.endOffset - 1;
368
+ const newCss = cssContent.substring(0, openBrace + 1) + newBody + cssContent.substring(closeBrace);
369
+ const newSource = latest.content.substring(0, latestExtracted.cssStart) +
370
+ newCss +
371
+ latest.content.substring(latestExtracted.cssStart + cssContent.length);
372
+ if (currentElementRef.current) {
373
+ applyCssToShadowRoot(currentElementRef.current, rule.selector, prop, newVal);
374
+ notifyLayoutChange();
375
+ }
376
+ await writeFile(sourceFile, newSource);
377
+ }
378
+ catch { /* silent fail */ }
379
+ }, 300);
380
+ }
381
+ /**
382
+ * Apply a CSS property change directly to the shadow DOM stylesheet.
383
+ */
384
+ function applyCssToShadowRoot(element, ruleSelector, prop, value) {
385
+ const root = element.getRootNode();
386
+ if (!(root instanceof ShadowRoot))
387
+ return;
388
+ const sheets = [];
389
+ if (root.adoptedStyleSheets?.length) {
390
+ sheets.push(...root.adoptedStyleSheets);
391
+ }
392
+ for (const s of Array.from(root.styleSheets || [])) {
393
+ sheets.push(s);
394
+ }
395
+ const mediaMatch = ruleSelector.match(/^(@media\s+[^{]+)\{\s*(.+?)\s*\}$/);
396
+ try {
397
+ for (const sheet of sheets) {
398
+ if (mediaMatch) {
399
+ const mediaCondition = mediaMatch[1].trim();
400
+ const innerSelector = mediaMatch[2].trim();
401
+ for (const cssRule of Array.from(sheet.cssRules)) {
402
+ if (cssRule instanceof CSSMediaRule && mediaCondition.includes(cssRule.conditionText)) {
403
+ for (const inner of Array.from(cssRule.cssRules)) {
404
+ if (inner instanceof CSSStyleRule && inner.selectorText === innerSelector) {
405
+ inner.style.setProperty(prop, value);
406
+ return;
407
+ }
408
+ }
409
+ }
410
+ }
411
+ }
412
+ else {
413
+ for (const cssRule of Array.from(sheet.cssRules)) {
414
+ if (cssRule instanceof CSSStyleRule && cssRule.selectorText === ruleSelector.trim()) {
415
+ cssRule.style.setProperty(prop, value);
416
+ return;
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ catch { /* cross-origin or security restriction */ }
423
+ }
@@ -0,0 +1,51 @@
1
+ import { AstModification } from './ast-modification.js';
2
+ export declare function applyAstModification(filePath: string, mod: AstModification): Promise<{
3
+ content: string;
4
+ }>;
5
+ export declare function readFile(filePath: string): Promise<{
6
+ content: string;
7
+ }>;
8
+ export declare function writeFile(filePath: string, content: string): Promise<void>;
9
+ export declare function listFiles(): Promise<{
10
+ files: string[];
11
+ }>;
12
+ /**
13
+ * Update a translation value for a given locale and i18n key.
14
+ */
15
+ export declare function updateTranslation(locale: string, key: string, value: string): Promise<void>;
16
+ /**
17
+ * Make a text element translatable by extracting text to i18n key.
18
+ */
19
+ export declare function makeTranslatable(options: {
20
+ sourceFile: string;
21
+ elementSelector: string;
22
+ sourceLine: number;
23
+ i18nKey: string;
24
+ text: string;
25
+ locales: string[];
26
+ }): Promise<void>;
27
+ /**
28
+ * Stream an AI chat message. Returns an AbortController to cancel the request.
29
+ */
30
+ export declare function streamAiChat(mode: 'element' | 'project', prompt: string, context: Record<string, any>, sessionId: string | undefined, callbacks: {
31
+ onToken: (text: string) => void;
32
+ onDone: (result: {
33
+ sessionId: string;
34
+ turnId: string;
35
+ fullText: string;
36
+ }) => void;
37
+ onError: (message: string) => void;
38
+ }, model?: 'fast' | 'default'): AbortController;
39
+ /**
40
+ * Rollback an AI turn by restoring file snapshots.
41
+ */
42
+ export declare function rollbackAiTurn(turnId: string): Promise<{
43
+ restored: boolean;
44
+ files: string[];
45
+ }>;
46
+ /**
47
+ * Check if the AI backend (OpenCode) is configured and reachable.
48
+ */
49
+ export declare function checkAiStatus(): Promise<{
50
+ configured: boolean;
51
+ }>;
@@ -0,0 +1,162 @@
1
+ export async function applyAstModification(filePath, mod) {
2
+ const res = await fetch(`/__nk_editor/ast/${encodeFilePath(filePath)}`, {
3
+ method: 'POST',
4
+ headers: { 'Content-Type': 'application/json' },
5
+ body: JSON.stringify(mod),
6
+ });
7
+ if (!res.ok)
8
+ throw new Error(`AST modification failed: ${res.status}`);
9
+ return res.json();
10
+ }
11
+ export async function readFile(filePath) {
12
+ const res = await fetch(`/__nk_editor/files/${encodeFilePath(filePath)}`);
13
+ if (!res.ok)
14
+ throw new Error(`Read file failed: ${res.status}`);
15
+ return res.json();
16
+ }
17
+ export async function writeFile(filePath, content) {
18
+ const res = await fetch(`/__nk_editor/files/${encodeFilePath(filePath)}`, {
19
+ method: 'PUT',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify({ content }),
22
+ });
23
+ if (!res.ok)
24
+ throw new Error(`Write file failed: ${res.status}`);
25
+ }
26
+ export async function listFiles() {
27
+ const res = await fetch('/__nk_editor/files');
28
+ if (!res.ok)
29
+ throw new Error(`List files failed: ${res.status}`);
30
+ return res.json();
31
+ }
32
+ /**
33
+ * Update a translation value for a given locale and i18n key.
34
+ */
35
+ export async function updateTranslation(locale, key, value) {
36
+ const filePath = `locales/${locale}.json`;
37
+ try {
38
+ const { content } = await readFile(filePath);
39
+ const translations = JSON.parse(content);
40
+ translations[key] = value;
41
+ await writeFile(filePath, JSON.stringify(translations, null, 2));
42
+ }
43
+ catch {
44
+ // If file doesn't exist, create it
45
+ await writeFile(filePath, JSON.stringify({ [key]: value }, null, 2));
46
+ }
47
+ }
48
+ /**
49
+ * Make a text element translatable by extracting text to i18n key.
50
+ */
51
+ export async function makeTranslatable(options) {
52
+ const { sourceFile, elementSelector, sourceLine, i18nKey, text, locales } = options;
53
+ // Add translation to all locale files
54
+ for (const locale of locales) {
55
+ await updateTranslation(locale, i18nKey, text);
56
+ }
57
+ // Replace the text content with the i18n expression in the source file
58
+ await applyAstModification(sourceFile, {
59
+ type: 'setTextContent',
60
+ elementSelector,
61
+ sourceLine,
62
+ value: `\${t('${i18nKey}')}`,
63
+ });
64
+ }
65
+ function encodeFilePath(filePath) {
66
+ return filePath.split('/').map(encodeURIComponent).join('/');
67
+ }
68
+ /**
69
+ * Stream an AI chat message. Returns an AbortController to cancel the request.
70
+ */
71
+ export function streamAiChat(mode, prompt, context, sessionId, callbacks, model) {
72
+ const controller = new AbortController();
73
+ fetch('/__nk_editor/ai/chat', {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'application/json' },
76
+ body: JSON.stringify({ mode, prompt, context, sessionId, model }),
77
+ signal: controller.signal,
78
+ }).then(async (res) => {
79
+ if (!res.ok) {
80
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
81
+ callbacks.onError(err.error || `Request failed: ${res.status}`);
82
+ return;
83
+ }
84
+ const reader = res.body?.getReader();
85
+ if (!reader) {
86
+ callbacks.onError('No response body');
87
+ return;
88
+ }
89
+ const decoder = new TextDecoder();
90
+ let buffer = '';
91
+ while (true) {
92
+ const { done, value } = await reader.read();
93
+ if (done)
94
+ break;
95
+ buffer += decoder.decode(value, { stream: true });
96
+ const parts = buffer.split('\n\n');
97
+ buffer = parts.pop() || '';
98
+ for (const part of parts) {
99
+ const lines = part.split('\n');
100
+ let eventType = '';
101
+ let data = '';
102
+ for (const line of lines) {
103
+ if (line.startsWith('event: '))
104
+ eventType = line.slice(7);
105
+ else if (line.startsWith('data: '))
106
+ data = line.slice(6);
107
+ }
108
+ if (!data)
109
+ continue;
110
+ try {
111
+ const parsed = JSON.parse(data);
112
+ if (eventType === 'token') {
113
+ callbacks.onToken(parsed.text || '');
114
+ }
115
+ else if (eventType === 'done') {
116
+ callbacks.onDone(parsed);
117
+ }
118
+ else if (eventType === 'error') {
119
+ callbacks.onError(parsed.message || 'Unknown error');
120
+ }
121
+ }
122
+ catch {
123
+ // Skip malformed events
124
+ }
125
+ }
126
+ }
127
+ }).catch((err) => {
128
+ if (err?.name === 'AbortError')
129
+ return;
130
+ callbacks.onError(err?.message || 'Network error');
131
+ });
132
+ return controller;
133
+ }
134
+ /**
135
+ * Rollback an AI turn by restoring file snapshots.
136
+ */
137
+ export async function rollbackAiTurn(turnId) {
138
+ const res = await fetch('/__nk_editor/ai/rollback', {
139
+ method: 'POST',
140
+ headers: { 'Content-Type': 'application/json' },
141
+ body: JSON.stringify({ turnId }),
142
+ });
143
+ if (!res.ok) {
144
+ const err = await res.json().catch(() => ({ error: 'Rollback failed' }));
145
+ throw new Error(err.error || `Rollback failed: ${res.status}`);
146
+ }
147
+ return res.json();
148
+ }
149
+ /**
150
+ * Check if the AI backend (OpenCode) is configured and reachable.
151
+ */
152
+ export async function checkAiStatus() {
153
+ try {
154
+ const res = await fetch('/__nk_editor/ai/status');
155
+ if (!res.ok)
156
+ return { configured: false };
157
+ return res.json();
158
+ }
159
+ catch {
160
+ return { configured: false };
161
+ }
162
+ }
@@ -3,6 +3,7 @@ export interface NkEditorMessage {
3
3
  payload?: any;
4
4
  }
5
5
  export declare function isPreviewMode(): boolean;
6
+ export declare function setPreviewMode(value: boolean): void;
6
7
  export declare function sendToHost(message: NkEditorMessage): void;
7
8
  export declare function getElementAttributes(el: HTMLElement): Record<string, string>;
8
9
  export declare function getDynamicTexts(el: HTMLElement): Array<{