@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,327 @@
1
+ /**
2
+ * Text Toolbar — floating toolbar for text formatting (font size, weight, color, alignment, i18n).
3
+ */
4
+ import { applyAstModification, makeTranslatable } from './editor-api-client.js';
5
+ import { generateI18nKey } from './i18n-key-gen.js';
6
+ const TEXT_TAGS = new Set(['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'SPAN', 'A', 'LABEL', 'LI', 'BUTTON']);
7
+ let textToolbar;
8
+ let selectedElementRef = { current: null };
9
+ export function setTextToolbarSelectedElement(ref) {
10
+ selectedElementRef = ref;
11
+ }
12
+ export function getTextToolbar() {
13
+ return textToolbar;
14
+ }
15
+ export { TEXT_TAGS };
16
+ export function createTextToolbar() {
17
+ const tb = document.createElement('div');
18
+ tb.id = 'nk-text-toolbar';
19
+ tb.innerHTML = `
20
+ <div class="nk-tt-row">
21
+ <select class="nk-tt-select nk-tt-font-size" title="Font size">
22
+ <option value="">Size</option>
23
+ <option value="12px">12</option>
24
+ <option value="14px">14</option>
25
+ <option value="16px">16</option>
26
+ <option value="18px">18</option>
27
+ <option value="20px">20</option>
28
+ <option value="24px">24</option>
29
+ <option value="28px">28</option>
30
+ <option value="32px">32</option>
31
+ <option value="36px">36</option>
32
+ <option value="48px">48</option>
33
+ <option value="64px">64</option>
34
+ </select>
35
+ <select class="nk-tt-select nk-tt-font-weight" title="Font weight">
36
+ <option value="">Weight</option>
37
+ <option value="300">Light</option>
38
+ <option value="400">Normal</option>
39
+ <option value="500">Medium</option>
40
+ <option value="600">Semi</option>
41
+ <option value="700">Bold</option>
42
+ <option value="800">Extra</option>
43
+ </select>
44
+ <span class="nk-tt-sep"></span>
45
+ <button class="nk-tt-btn" data-style="fontStyle:italic" title="Italic"><em>I</em></button>
46
+ <button class="nk-tt-btn" data-style="textDecoration:underline" title="Underline"><u>U</u></button>
47
+ <span class="nk-tt-sep"></span>
48
+ <button class="nk-tt-btn" data-style="textAlign:left" title="Align left">
49
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="15" y2="12"/><line x1="3" y1="18" x2="18" y2="18"/></svg>
50
+ </button>
51
+ <button class="nk-tt-btn" data-style="textAlign:center" title="Align center">
52
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="6" y1="12" x2="18" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>
53
+ </button>
54
+ <button class="nk-tt-btn" data-style="textAlign:right" title="Align right">
55
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="9" y1="12" x2="21" y2="12"/><line x1="6" y1="18" x2="21" y2="18"/></svg>
56
+ </button>
57
+ <span class="nk-tt-sep"></span>
58
+ <label class="nk-tt-color-wrap" title="Text color">
59
+ <span class="nk-tt-color-label">A</span>
60
+ <input type="color" class="nk-tt-color" value="#000000">
61
+ </label>
62
+ <span class="nk-tt-sep nk-tt-i18n-sep" style="display:none"></span>
63
+ <button class="nk-tt-btn nk-tt-translate" title="Make translatable" style="display:none">
64
+ <span style="font-size:11px;font-family:system-ui">T<sub>i</sub></span>
65
+ </button>
66
+ </div>
67
+ `;
68
+ const style = document.createElement('style');
69
+ style.textContent = `
70
+ #nk-text-toolbar {
71
+ position: fixed; z-index: 100000; display: none;
72
+ background: #1e1b2e; border: 1px solid #334155; border-radius: 8px;
73
+ box-shadow: 0 4px 20px rgba(0,0,0,0.4); padding: 6px 8px;
74
+ font-family: system-ui, -apple-system, sans-serif; font-size: 12px;
75
+ user-select: none; -webkit-user-select: none;
76
+ }
77
+ #nk-text-toolbar::after {
78
+ content: ''; position: absolute; bottom: -6px; left: 50%; transform: translateX(-50%);
79
+ border-left: 6px solid transparent; border-right: 6px solid transparent;
80
+ border-top: 6px solid #1e1b2e;
81
+ }
82
+ .nk-tt-row { display: flex; align-items: center; gap: 4px; }
83
+ .nk-tt-select {
84
+ background: #0f0d1a; color: #e2e8f0; border: 1px solid #334155; border-radius: 4px;
85
+ padding: 4px 6px; font-size: 11px; font-family: inherit; cursor: pointer; outline: none;
86
+ }
87
+ .nk-tt-select:hover { border-color: #475569; }
88
+ .nk-tt-btn {
89
+ display: inline-flex; align-items: center; justify-content: center;
90
+ width: 28px; height: 28px; background: transparent; border: 1px solid transparent;
91
+ border-radius: 4px; color: #e2e8f0; cursor: pointer; font-size: 13px;
92
+ font-family: Georgia, serif; transition: all 0.1s;
93
+ -webkit-tap-highlight-color: transparent; touch-action: manipulation;
94
+ }
95
+ .nk-tt-btn:hover { background: #334155; border-color: #475569; }
96
+ .nk-tt-btn.active { background: #7c3aed; border-color: #7c3aed; }
97
+ .nk-tt-sep { width: 1px; height: 20px; background: #334155; margin: 0 2px; }
98
+ .nk-tt-translate { font-family: system-ui !important; }
99
+ .nk-tt-color-wrap {
100
+ display: inline-flex; align-items: center; justify-content: center;
101
+ width: 28px; height: 28px; cursor: pointer; position: relative;
102
+ border: 1px solid transparent; border-radius: 4px;
103
+ }
104
+ .nk-tt-color-wrap:hover { background: #334155; border-color: #475569; }
105
+ .nk-tt-color-label {
106
+ font-size: 14px; font-weight: 700; color: #e2e8f0; pointer-events: none;
107
+ font-family: Georgia, serif;
108
+ }
109
+ .nk-tt-color {
110
+ position: absolute; bottom: 0; left: 2px; width: 24px; height: 4px;
111
+ border: none; padding: 0; cursor: pointer; opacity: 0;
112
+ }
113
+ .nk-tt-color-bar {
114
+ position: absolute; bottom: 2px; left: 4px; right: 4px; height: 3px;
115
+ background: #e2e8f0; border-radius: 1px; pointer-events: none;
116
+ }
117
+ @media (max-width: 640px) {
118
+ #nk-text-toolbar { padding: 4px 6px; }
119
+ .nk-tt-select { padding: 3px 4px; font-size: 10px; }
120
+ .nk-tt-btn { width: 26px; height: 26px; font-size: 12px; }
121
+ }
122
+ `;
123
+ document.head.appendChild(style);
124
+ // Add color bar indicator under the "A" label
125
+ const colorWrap = tb.querySelector('.nk-tt-color-wrap');
126
+ const colorBar = document.createElement('span');
127
+ colorBar.className = 'nk-tt-color-bar';
128
+ colorWrap.appendChild(colorBar);
129
+ document.body.appendChild(tb);
130
+ textToolbar = tb;
131
+ return tb;
132
+ }
133
+ export function positionTextToolbar(el) {
134
+ const rect = el.getBoundingClientRect();
135
+ const tbRect = textToolbar.getBoundingClientRect();
136
+ const tbWidth = tbRect.width || 360;
137
+ const tbHeight = tbRect.height || 40;
138
+ let left = rect.left + rect.width / 2 - tbWidth / 2;
139
+ let top = rect.top - tbHeight - 10;
140
+ // Keep within viewport
141
+ if (left < 8)
142
+ left = 8;
143
+ if (left + tbWidth > window.innerWidth - 8)
144
+ left = window.innerWidth - 8 - tbWidth;
145
+ if (top < 52) {
146
+ // Show below element instead (44px toolbar + 8px gap)
147
+ top = rect.bottom + 10;
148
+ }
149
+ Object.assign(textToolbar.style, {
150
+ display: 'block',
151
+ left: `${left}px`,
152
+ top: `${top}px`,
153
+ });
154
+ }
155
+ export function hideTextToolbar() {
156
+ textToolbar.style.display = 'none';
157
+ }
158
+ function rgbToHex(rgb) {
159
+ const m = rgb.match(/(\d+)/g);
160
+ if (!m || m.length < 3)
161
+ return '#000000';
162
+ return '#' + [m[0], m[1], m[2]].map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
163
+ }
164
+ function readCurrentStyles(el) {
165
+ const cs = window.getComputedStyle(el);
166
+ // Font size select
167
+ const sizeSelect = textToolbar.querySelector('.nk-tt-font-size');
168
+ sizeSelect.value = '';
169
+ const currentSize = cs.fontSize;
170
+ for (const opt of Array.from(sizeSelect.options)) {
171
+ if (opt.value === currentSize) {
172
+ sizeSelect.value = currentSize;
173
+ break;
174
+ }
175
+ }
176
+ // Font weight select
177
+ const weightSelect = textToolbar.querySelector('.nk-tt-font-weight');
178
+ weightSelect.value = '';
179
+ const currentWeight = cs.fontWeight;
180
+ for (const opt of Array.from(weightSelect.options)) {
181
+ if (opt.value === currentWeight) {
182
+ weightSelect.value = currentWeight;
183
+ break;
184
+ }
185
+ }
186
+ // Toggle buttons
187
+ textToolbar.querySelectorAll('.nk-tt-btn[data-style]').forEach(btn => {
188
+ const [prop, val] = btn.dataset.style.split(':');
189
+ const camelProp = prop;
190
+ btn.classList.toggle('active', cs[camelProp] === val);
191
+ });
192
+ // Color
193
+ const colorInput = textToolbar.querySelector('.nk-tt-color');
194
+ const colorBar = textToolbar.querySelector('.nk-tt-color-bar');
195
+ const rgb = cs.color;
196
+ const hex = rgbToHex(rgb);
197
+ colorInput.value = hex;
198
+ colorBar.style.background = hex;
199
+ }
200
+ function applyStyleToSelected(prop, value) {
201
+ const selectedElement = selectedElementRef.current;
202
+ if (!selectedElement)
203
+ return;
204
+ // Apply visually immediately
205
+ selectedElement.style[prop] = value;
206
+ // Persist via AST modification
207
+ const sourceAttr = selectedElement.getAttribute('data-nk-source');
208
+ if (!sourceAttr)
209
+ return;
210
+ const lastColon = sourceAttr.lastIndexOf(':');
211
+ const sourceFile = sourceAttr.substring(0, lastColon);
212
+ const line = parseInt(sourceAttr.substring(lastColon + 1), 10);
213
+ // Build full inline style string from the element
214
+ const styleStr = selectedElement.getAttribute('style') || '';
215
+ // Clean up internal editor styles before saving
216
+ const cleanStyle = styleStr
217
+ .split(';')
218
+ .map(s => s.trim())
219
+ .filter(s => s && !s.startsWith('outline') && !s.startsWith('outline-offset') && !s.startsWith('border-radius') && !s.startsWith('min-width'))
220
+ .join('; ');
221
+ applyAstModification(sourceFile, {
222
+ type: 'setAttribute',
223
+ elementSelector: selectedElement.tagName.toLowerCase(),
224
+ sourceLine: line,
225
+ attributeName: 'style',
226
+ attributeValue: cleanStyle || undefined,
227
+ }).catch(() => {
228
+ // Silent fail — visual change still applied
229
+ });
230
+ }
231
+ export function setupTextToolbarHandlers() {
232
+ // Font size
233
+ textToolbar.querySelector('.nk-tt-font-size').addEventListener('change', (e) => {
234
+ const val = e.target.value;
235
+ if (val)
236
+ applyStyleToSelected('fontSize', val);
237
+ });
238
+ // Font weight
239
+ textToolbar.querySelector('.nk-tt-font-weight').addEventListener('change', (e) => {
240
+ const val = e.target.value;
241
+ if (val)
242
+ applyStyleToSelected('fontWeight', val);
243
+ });
244
+ // Toggle buttons (italic, underline, alignment)
245
+ textToolbar.querySelectorAll('.nk-tt-btn[data-style]').forEach(btn => {
246
+ btn.addEventListener('click', (e) => {
247
+ e.stopPropagation();
248
+ const [prop, val] = (btn.dataset.style).split(':');
249
+ const cs = selectedElementRef.current ? window.getComputedStyle(selectedElementRef.current) : null;
250
+ const camelProp = prop;
251
+ const current = cs ? cs[camelProp] : '';
252
+ // For italic/underline: toggle. For alignment: always set.
253
+ if (prop === 'fontStyle' || prop === 'textDecoration') {
254
+ const newVal = current === val ? 'normal' : val;
255
+ applyStyleToSelected(prop, newVal);
256
+ btn.classList.toggle('active', newVal === val);
257
+ }
258
+ else {
259
+ // Alignment — deactivate siblings, activate this one
260
+ textToolbar.querySelectorAll('.nk-tt-btn[data-style^="textAlign"]').forEach(b => b.classList.remove('active'));
261
+ applyStyleToSelected(prop, val);
262
+ btn.classList.add('active');
263
+ }
264
+ });
265
+ });
266
+ // Color picker
267
+ const colorInput = textToolbar.querySelector('.nk-tt-color');
268
+ const colorBar = textToolbar.querySelector('.nk-tt-color-bar');
269
+ const colorLabel = textToolbar.querySelector('.nk-tt-color-label');
270
+ colorInput.addEventListener('input', () => {
271
+ const val = colorInput.value;
272
+ colorBar.style.background = val;
273
+ colorLabel.style.color = val;
274
+ applyStyleToSelected('color', val);
275
+ });
276
+ // Make translatable button
277
+ textToolbar.querySelector('.nk-tt-translate').addEventListener('click', (e) => {
278
+ e.stopPropagation();
279
+ const selectedElement = selectedElementRef.current;
280
+ if (!selectedElement)
281
+ return;
282
+ const isAlready = !!selectedElement.getAttribute('data-nk-i18n-key') || !!selectedElement.closest('[data-nk-i18n-key]') || selectedElement.hasAttribute('data-nk-dynamic');
283
+ if (isAlready)
284
+ return;
285
+ const sourceAttr = selectedElement.getAttribute('data-nk-source');
286
+ if (!sourceAttr)
287
+ return;
288
+ const text = selectedElement.textContent || '';
289
+ if (!text.trim())
290
+ return;
291
+ const lastColon = sourceAttr.lastIndexOf(':');
292
+ const sourceFile = sourceAttr.substring(0, lastColon);
293
+ const line = parseInt(sourceAttr.substring(lastColon + 1), 10);
294
+ const tag = selectedElement.tagName.toLowerCase();
295
+ const i18nKey = generateI18nKey(sourceFile, tag, text);
296
+ const config = window.__nk_i18n_config__;
297
+ const locales = config?.locales || ['en'];
298
+ const translateBtnEl = textToolbar.querySelector('.nk-tt-translate');
299
+ makeTranslatable({ sourceFile, elementSelector: tag, sourceLine: line, i18nKey, text, locales })
300
+ .then(() => {
301
+ translateBtnEl.classList.add('active');
302
+ })
303
+ .catch((err) => console.error('[editor] Make translatable failed:', err));
304
+ });
305
+ // Prevent toolbar clicks from deselecting
306
+ textToolbar.addEventListener('click', (e) => e.stopPropagation());
307
+ textToolbar.addEventListener('mousedown', (e) => e.stopPropagation());
308
+ }
309
+ export function showTextToolbarForElement(el) {
310
+ if (TEXT_TAGS.has(el.tagName)) {
311
+ readCurrentStyles(el);
312
+ // Show/hide translate button based on i18n availability
313
+ const translateBtn = textToolbar.querySelector('.nk-tt-translate');
314
+ const translateSep = textToolbar.querySelector('.nk-tt-i18n-sep');
315
+ const hasI18nConfig = !!window.__nk_i18n_config__;
316
+ const isI18n = !!el.getAttribute('data-nk-i18n-key') || !!el.closest('[data-nk-i18n-key]');
317
+ const isDynamic = el.hasAttribute('data-nk-dynamic');
318
+ const alreadyTranslatable = isI18n || isDynamic;
319
+ translateBtn.style.display = hasI18nConfig ? '' : 'none';
320
+ translateSep.style.display = hasI18nConfig ? '' : 'none';
321
+ translateBtn.classList.toggle('active', alreadyTranslatable);
322
+ positionTextToolbar(el);
323
+ }
324
+ else {
325
+ hideTextToolbar();
326
+ }
327
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Toolbar and file panel CSS styles.
3
+ */
4
+ export declare function injectToolbarStyles(): void;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Toolbar and file panel CSS styles.
3
+ */
4
+ export function injectToolbarStyles() {
5
+ const style = document.createElement('style');
6
+ style.textContent = `
7
+ #nk-editor-toolbar {
8
+ position: fixed; top: 0; left: 0; right: 0; height: 44px;
9
+ background: #1e1b2e; color: #e2e8f0; font-family: system-ui, -apple-system, sans-serif;
10
+ font-size: 12px; z-index: 99999; box-shadow: 0 2px 12px rgba(0,0,0,0.3);
11
+ user-select: none; -webkit-user-select: none; touch-action: manipulation;
12
+ }
13
+ .nk-toolbar-inner {
14
+ display: flex; align-items: center; height: 44px; padding: 0 12px; gap: 8px;
15
+ }
16
+ .nk-toolbar-left, .nk-toolbar-right { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
17
+ .nk-toolbar-center { flex: 1; text-align: center; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
18
+ .nk-tb-hint { color: #64748b; }
19
+ .nk-tb-mode { color: #7c3aed; font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
20
+ .nk-tb-divider { width: 1px; height: 16px; background: #334155; }
21
+ .nk-tb-btn {
22
+ display: inline-flex; align-items: center; gap: 4px; padding: 6px 10px;
23
+ background: transparent; border: 1px solid #334155; border-radius: 6px;
24
+ color: #e2e8f0; cursor: pointer; font-size: 11px; font-family: inherit;
25
+ transition: all 0.15s; -webkit-tap-highlight-color: transparent;
26
+ touch-action: manipulation;
27
+ }
28
+ .nk-tb-btn:hover { background: #334155; border-color: #475569; }
29
+ .nk-tb-btn:active { background: #475569; }
30
+ .nk-tb-btn.active { background: #7c3aed; border-color: #7c3aed; }
31
+ .nk-tb-project-ai-label { font-size: 11px; font-weight: 600; }
32
+ .nk-tb-page-ai {
33
+ display: flex; align-items: center; gap: 0;
34
+ background: #0f0d1a; border: 1px solid #334155; border-radius: 8px;
35
+ padding: 0 2px 0 8px; height: 30px; transition: border-color 0.15s;
36
+ }
37
+ .nk-tb-page-ai:focus-within { border-color: #7c3aed; }
38
+ .nk-tb-page-ai-icon { color: #7c3aed; font-size: 12px; flex-shrink: 0; margin-right: 4px; }
39
+ .nk-tb-page-ai-input {
40
+ background: transparent; border: none; color: #e2e8f0; font-size: 12px;
41
+ font-family: inherit; outline: none; width: 180px; padding: 0;
42
+ }
43
+ .nk-tb-page-ai-input::placeholder { color: #64748b; }
44
+ .nk-tb-page-ai-send {
45
+ background: #7c3aed; border: none; color: #fff; width: 24px; height: 24px;
46
+ border-radius: 6px; cursor: pointer; font-size: 10px; flex-shrink: 0;
47
+ display: flex; align-items: center; justify-content: center; transition: opacity 0.15s;
48
+ }
49
+ .nk-tb-page-ai-send:disabled { opacity: 0.3; cursor: default; }
50
+ .nk-tb-page-ai-send:not(:disabled):hover { background: #6d28d9; }
51
+ @media (max-width: 640px) {
52
+ .nk-tb-project-ai-label { display: none; }
53
+ .nk-tb-page-ai-input { width: 100px; }
54
+ }
55
+ .nk-tb-toggle {
56
+ display: inline-flex; align-items: center; padding: 2px; gap: 0;
57
+ background: #0f0d1a; border: 1px solid #334155; border-radius: 6px;
58
+ cursor: pointer; font-family: inherit;
59
+ -webkit-tap-highlight-color: transparent; touch-action: manipulation;
60
+ }
61
+ .nk-tb-toggle span {
62
+ padding: 4px 10px; border-radius: 4px; font-size: 11px; font-weight: 600;
63
+ color: #64748b; transition: all 0.15s; letter-spacing: 0.03em;
64
+ }
65
+ .nk-tb-toggle span.active { background: #7c3aed; color: #fff; }
66
+ .nk-tb-tag { color: #67e8f9; font-family: 'SF Mono', ui-monospace, monospace; }
67
+ .nk-tb-source { color: #86efac; font-family: 'SF Mono', ui-monospace, monospace; font-size: 11px; }
68
+ .nk-tb-attrs { color: #94a3b8; font-size: 11px; }
69
+
70
+ /* File panel — sidebar on desktop, full-width sheet on mobile */
71
+ #nk-file-panel {
72
+ position: fixed; top: 44px; left: 0; width: 320px; max-height: calc(100vh - 44px);
73
+ background: #1e1b2e; border-right: 1px solid #334155; border-bottom: 1px solid #334155;
74
+ z-index: 99999; display: none; flex-direction: column; font-family: system-ui, -apple-system, sans-serif;
75
+ box-shadow: 4px 0 16px rgba(0,0,0,0.3);
76
+ padding-bottom: env(safe-area-inset-bottom, 0); touch-action: manipulation;
77
+ }
78
+ #nk-file-panel.open { display: flex; }
79
+ .nk-fp-header {
80
+ display: flex; align-items: center; justify-content: space-between;
81
+ padding: 10px 12px; border-bottom: 1px solid #334155; color: #e2e8f0; font-size: 13px; font-weight: 600;
82
+ }
83
+ .nk-fp-close-btn {
84
+ display: none; background: none; border: none; color: #94a3b8; cursor: pointer;
85
+ padding: 4px; -webkit-tap-highlight-color: transparent;
86
+ }
87
+ .nk-fp-list {
88
+ flex: 1; overflow-y: auto; padding: 4px 0;
89
+ -webkit-overflow-scrolling: touch;
90
+ }
91
+ .nk-fp-item {
92
+ display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer;
93
+ color: #94a3b8; font-size: 12px; font-family: 'SF Mono', ui-monospace, monospace;
94
+ transition: background 0.1s; -webkit-tap-highlight-color: transparent;
95
+ touch-action: manipulation;
96
+ }
97
+ .nk-fp-item:hover { background: #262338; color: #e2e8f0; }
98
+ .nk-fp-item:active { background: #334155; }
99
+ .nk-fp-item.active { background: #7c3aed22; color: #c084fc; }
100
+ .nk-fp-icon { width: 14px; text-align: center; flex-shrink: 0; }
101
+
102
+ /* Tabs in file panel header */
103
+ .nk-fp-tabs {
104
+ display: flex; gap: 2px; background: #0f0d1a; border-radius: 6px; padding: 2px;
105
+ }
106
+ .nk-fp-tab {
107
+ padding: 4px 12px; border: none; border-radius: 4px; font-size: 11px; font-weight: 600;
108
+ font-family: inherit; color: #64748b; background: transparent; cursor: pointer;
109
+ transition: all 0.15s; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
110
+ }
111
+ .nk-fp-tab:hover { color: #e2e8f0; }
112
+ .nk-fp-tab.active { background: #7c3aed; color: #fff; }
113
+
114
+ /* Pages view */
115
+ .nk-fp-pages {
116
+ flex: 1; overflow-y: auto; padding: 4px 0; -webkit-overflow-scrolling: touch;
117
+ }
118
+ .nk-fp-layout-group { margin-bottom: 4px; }
119
+ .nk-fp-layout-label {
120
+ padding: 8px 12px 4px; font-size: 10px; font-weight: 700; text-transform: uppercase;
121
+ letter-spacing: 0.05em; color: #7c3aed; font-family: system-ui, sans-serif;
122
+ }
123
+ .nk-fp-route {
124
+ display: flex; align-items: center; gap: 8px; padding: 6px 12px 6px 24px; cursor: pointer;
125
+ color: #94a3b8; font-size: 12px; font-family: 'SF Mono', ui-monospace, monospace;
126
+ transition: background 0.1s; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
127
+ }
128
+ .nk-fp-route:hover { background: #262338; color: #e2e8f0; }
129
+ .nk-fp-route:active { background: #334155; }
130
+ .nk-fp-route.active { background: #7c3aed22; color: #c084fc; }
131
+
132
+ /* File editor — right of sidebar on desktop, full-width on mobile */
133
+ #nk-file-editor {
134
+ position: fixed; top: 44px; left: 320px; right: 0; max-height: calc(100vh - 44px);
135
+ background: #0f0d1a; border-bottom: 1px solid #334155; z-index: 99999;
136
+ display: none; flex-direction: column;
137
+ padding-bottom: env(safe-area-inset-bottom, 0);
138
+ }
139
+ #nk-file-editor.open { display: flex; }
140
+ .nk-fe-header {
141
+ display: flex; align-items: center; justify-content: space-between;
142
+ padding: 8px 12px; border-bottom: 1px solid #334155; color: #e2e8f0; font-size: 12px;
143
+ gap: 8px; flex-wrap: wrap;
144
+ }
145
+ .nk-fe-header .nk-fe-path {
146
+ font-family: 'SF Mono', ui-monospace, monospace; font-size: 11px; color: #86efac;
147
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1;
148
+ }
149
+ .nk-fe-btns { display: flex; gap: 6px; flex-shrink: 0; }
150
+ .nk-fe-editor {
151
+ flex: 1; width: 100%; background: #0f0d1a; color: #e2e8f0; border: none; padding: 12px;
152
+ font-family: 'SF Mono', ui-monospace, monospace; font-size: 13px; line-height: 1.6;
153
+ overflow: auto; outline: none; tab-size: 2; min-height: 250px;
154
+ -webkit-overflow-scrolling: touch;
155
+ }
156
+ .nk-hl-k { color: #ff7b72; }
157
+ .nk-hl-s { color: #a5d6ff; }
158
+ .nk-hl-c { color: #8b949e; font-style: italic; }
159
+ .nk-hl-n, .nk-hl-l { color: #79c0ff; }
160
+ .nk-fe-save {
161
+ padding: 6px 14px; background: #7c3aed; color: white; border: none; border-radius: 6px;
162
+ cursor: pointer; font-size: 12px; font-family: inherit; font-weight: 500;
163
+ -webkit-tap-highlight-color: transparent; touch-action: manipulation;
164
+ }
165
+ .nk-fe-save:hover { background: #6d28d9; }
166
+ .nk-fe-save:active { background: #5b21b6; }
167
+ .nk-fe-cancel {
168
+ padding: 6px 14px; background: transparent; color: #94a3b8; border: 1px solid #334155;
169
+ border-radius: 6px; cursor: pointer; font-size: 12px; font-family: inherit;
170
+ -webkit-tap-highlight-color: transparent; touch-action: manipulation;
171
+ }
172
+ .nk-fe-cancel:hover { background: #334155; color: #e2e8f0; }
173
+
174
+ /* Mobile responsive (<640px) */
175
+ @media (max-width: 640px) {
176
+ .nk-tb-files-label { display: none; }
177
+ .nk-tb-source { display: none; }
178
+ .nk-tb-attrs { display: none; }
179
+ .nk-tb-hint { font-size: 11px; }
180
+
181
+ #nk-file-panel {
182
+ width: 100%; right: 0; border-right: none;
183
+ max-height: 50vh;
184
+ }
185
+ .nk-fp-close-btn { display: block; }
186
+ .nk-fp-item { padding: 10px 12px; font-size: 13px; }
187
+
188
+ #nk-file-editor {
189
+ left: 0; max-height: 60vh;
190
+ }
191
+ .nk-fe-editor { font-size: 14px; min-height: 200px; }
192
+ }
193
+
194
+ /* Push page content down so toolbar doesn't cover it */
195
+ body { padding-top: 44px !important; }
196
+ `;
197
+ document.head.appendChild(style);
198
+ }
@@ -0,0 +1,32 @@
1
+ import type { EmailConfig, EmailMessage, TemplateData, TemplateRenderer } from './types.js';
2
+ export type { EmailConfig, EmailMessage, EmailProvider, EmailTemplates, TemplateData, TemplateRenderer } from './types.js';
3
+ export { renderVerifyEmail } from './templates/verify-email.js';
4
+ export { renderPasswordReset } from './templates/password-reset.js';
5
+ export { renderWelcome } from './templates/welcome.js';
6
+ export { renderTemplate, renderButton, escapeHtml } from './templates/base.js';
7
+ export { compileTemplate } from './template-engine.js';
8
+ /** Set the project directory for file-based template loading */
9
+ export declare function setEmailProjectDir(dir: string): void;
10
+ /**
11
+ * Get a template renderer. Resolution order:
12
+ * 1. HTML file in emails/ directory (compiled with {{variable}} engine)
13
+ * 2. Custom function in config.templates
14
+ * 3. Built-in template
15
+ */
16
+ export declare function getTemplate(config: EmailConfig, name: string): TemplateRenderer | undefined;
17
+ /**
18
+ * Render a named template with data. Returns HTML string or null if template not found.
19
+ */
20
+ export declare function renderEmailTemplate(config: EmailConfig, name: string, data: TemplateData): string | null;
21
+ /**
22
+ * Send an email using the configured provider.
23
+ */
24
+ export declare function sendEmail(config: EmailConfig, message: EmailMessage): Promise<void>;
25
+ /**
26
+ * Create a reusable email sender function.
27
+ */
28
+ export declare function createEmailSender(config: EmailConfig): (message: EmailMessage) => Promise<void>;
29
+ /**
30
+ * Load email config from lumenjs.email.ts.
31
+ */
32
+ export declare function loadEmailConfig(projectDir: string, ssrLoadModule?: (id: string) => Promise<any>): Promise<EmailConfig | null>;