@mappoh/nova 0.2.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/README.md +180 -30
  2. package/dist/a11y/index.d.ts +40 -0
  3. package/dist/a11y/index.d.ts.map +1 -0
  4. package/dist/a11y/index.js +162 -0
  5. package/dist/a11y/index.js.map +1 -0
  6. package/dist/alert/alert.d.ts +32 -0
  7. package/dist/alert/alert.d.ts.map +1 -0
  8. package/dist/alert/alert.js +307 -0
  9. package/dist/alert/alert.js.map +1 -0
  10. package/dist/alert/index.d.ts +3 -0
  11. package/dist/alert/index.d.ts.map +1 -0
  12. package/dist/alert/index.js +2 -0
  13. package/dist/alert/index.js.map +1 -0
  14. package/dist/animation/flip.d.ts +28 -0
  15. package/dist/animation/flip.d.ts.map +1 -0
  16. package/dist/animation/flip.js +113 -0
  17. package/dist/animation/flip.js.map +1 -0
  18. package/dist/animation/spring.d.ts +34 -0
  19. package/dist/animation/spring.d.ts.map +1 -0
  20. package/dist/animation/spring.js +86 -0
  21. package/dist/animation/spring.js.map +1 -0
  22. package/dist/animation/stagger.d.ts +43 -0
  23. package/dist/animation/stagger.d.ts.map +1 -0
  24. package/dist/animation/stagger.js +150 -0
  25. package/dist/animation/stagger.js.map +1 -0
  26. package/dist/avatar/avatar.d.ts +27 -0
  27. package/dist/avatar/avatar.d.ts.map +1 -0
  28. package/dist/avatar/avatar.js +132 -0
  29. package/dist/avatar/avatar.js.map +1 -0
  30. package/dist/avatar/index.d.ts +3 -0
  31. package/dist/avatar/index.d.ts.map +1 -0
  32. package/dist/avatar/index.js +2 -0
  33. package/dist/avatar/index.js.map +1 -0
  34. package/dist/badge/badge.d.ts +27 -0
  35. package/dist/badge/badge.d.ts.map +1 -0
  36. package/dist/badge/badge.js +118 -0
  37. package/dist/badge/badge.js.map +1 -0
  38. package/dist/badge/index.d.ts +3 -0
  39. package/dist/badge/index.d.ts.map +1 -0
  40. package/dist/badge/index.js +2 -0
  41. package/dist/badge/index.js.map +1 -0
  42. package/dist/cache/cache.d.ts +28 -0
  43. package/dist/cache/cache.d.ts.map +1 -0
  44. package/dist/cache/cache.js +67 -0
  45. package/dist/cache/cache.js.map +1 -0
  46. package/dist/cache/index.d.ts +3 -0
  47. package/dist/cache/index.d.ts.map +1 -0
  48. package/dist/cache/index.js +2 -0
  49. package/dist/cache/index.js.map +1 -0
  50. package/dist/chart/chart.d.ts +31 -0
  51. package/dist/chart/chart.d.ts.map +1 -0
  52. package/dist/chart/chart.js +372 -0
  53. package/dist/chart/chart.js.map +1 -0
  54. package/dist/chart/index.d.ts +3 -0
  55. package/dist/chart/index.d.ts.map +1 -0
  56. package/dist/chart/index.js +2 -0
  57. package/dist/chart/index.js.map +1 -0
  58. package/dist/color-picker/color-picker.d.ts +18 -0
  59. package/dist/color-picker/color-picker.d.ts.map +1 -0
  60. package/dist/color-picker/color-picker.js +243 -0
  61. package/dist/color-picker/color-picker.js.map +1 -0
  62. package/dist/color-picker/index.d.ts +3 -0
  63. package/dist/color-picker/index.d.ts.map +1 -0
  64. package/dist/color-picker/index.js +2 -0
  65. package/dist/color-picker/index.js.map +1 -0
  66. package/dist/combobox/combobox.d.ts +26 -0
  67. package/dist/combobox/combobox.d.ts.map +1 -0
  68. package/dist/combobox/combobox.js +262 -0
  69. package/dist/combobox/combobox.js.map +1 -0
  70. package/dist/combobox/index.d.ts +3 -0
  71. package/dist/combobox/index.d.ts.map +1 -0
  72. package/dist/combobox/index.js +2 -0
  73. package/dist/combobox/index.js.map +1 -0
  74. package/dist/command-palette/command-palette.d.ts +31 -0
  75. package/dist/command-palette/command-palette.d.ts.map +1 -0
  76. package/dist/command-palette/command-palette.js +590 -0
  77. package/dist/command-palette/command-palette.js.map +1 -0
  78. package/dist/command-palette/index.d.ts +3 -0
  79. package/dist/command-palette/index.d.ts.map +1 -0
  80. package/dist/command-palette/index.js +2 -0
  81. package/dist/command-palette/index.js.map +1 -0
  82. package/dist/component/component.d.ts +20 -2
  83. package/dist/component/component.d.ts.map +1 -1
  84. package/dist/component/component.js +115 -5
  85. package/dist/component/component.js.map +1 -1
  86. package/dist/component/connect.d.ts +50 -0
  87. package/dist/component/connect.d.ts.map +1 -1
  88. package/dist/component/connect.js +135 -0
  89. package/dist/component/connect.js.map +1 -1
  90. package/dist/component/directives.d.ts +20 -0
  91. package/dist/component/directives.d.ts.map +1 -0
  92. package/dist/component/directives.js +42 -0
  93. package/dist/component/directives.js.map +1 -0
  94. package/dist/component/html.d.ts +8 -0
  95. package/dist/component/html.d.ts.map +1 -1
  96. package/dist/component/html.js +11 -0
  97. package/dist/component/html.js.map +1 -1
  98. package/dist/component/index.d.ts +9 -3
  99. package/dist/component/index.d.ts.map +1 -1
  100. package/dist/component/index.js +6 -2
  101. package/dist/component/index.js.map +1 -1
  102. package/dist/component/portal.d.ts +32 -0
  103. package/dist/component/portal.d.ts.map +1 -0
  104. package/dist/component/portal.js +59 -0
  105. package/dist/component/portal.js.map +1 -0
  106. package/dist/component/ref.d.ts +18 -0
  107. package/dist/component/ref.d.ts.map +1 -0
  108. package/dist/component/ref.js +17 -0
  109. package/dist/component/ref.js.map +1 -0
  110. package/dist/component/slot-styles.d.ts +18 -0
  111. package/dist/component/slot-styles.d.ts.map +1 -0
  112. package/dist/component/slot-styles.js +47 -0
  113. package/dist/component/slot-styles.js.map +1 -0
  114. package/dist/component/template.d.ts +2 -0
  115. package/dist/component/template.d.ts.map +1 -1
  116. package/dist/component/template.js +122 -4
  117. package/dist/component/template.js.map +1 -1
  118. package/dist/context/context.d.ts +39 -0
  119. package/dist/context/context.d.ts.map +1 -0
  120. package/dist/context/context.js +111 -0
  121. package/dist/context/context.js.map +1 -0
  122. package/dist/context/index.d.ts +3 -0
  123. package/dist/context/index.d.ts.map +1 -0
  124. package/dist/context/index.js +2 -0
  125. package/dist/context/index.js.map +1 -0
  126. package/dist/data-table/data-table.d.ts +34 -0
  127. package/dist/data-table/data-table.d.ts.map +1 -0
  128. package/dist/data-table/data-table.js +256 -0
  129. package/dist/data-table/data-table.js.map +1 -0
  130. package/dist/data-table/index.d.ts +3 -0
  131. package/dist/data-table/index.d.ts.map +1 -0
  132. package/dist/data-table/index.js +2 -0
  133. package/dist/data-table/index.js.map +1 -0
  134. package/dist/date-picker/date-picker.d.ts +22 -0
  135. package/dist/date-picker/date-picker.d.ts.map +1 -0
  136. package/dist/date-picker/date-picker.js +282 -0
  137. package/dist/date-picker/date-picker.js.map +1 -0
  138. package/dist/date-picker/index.d.ts +3 -0
  139. package/dist/date-picker/index.d.ts.map +1 -0
  140. package/dist/date-picker/index.js +2 -0
  141. package/dist/date-picker/index.js.map +1 -0
  142. package/dist/devtools/devtools.d.ts +54 -0
  143. package/dist/devtools/devtools.d.ts.map +1 -1
  144. package/dist/devtools/devtools.js +86 -0
  145. package/dist/devtools/devtools.js.map +1 -1
  146. package/dist/devtools/index.d.ts +2 -2
  147. package/dist/devtools/index.d.ts.map +1 -1
  148. package/dist/devtools/index.js +1 -1
  149. package/dist/devtools/index.js.map +1 -1
  150. package/dist/editor/editor.d.ts +40 -0
  151. package/dist/editor/editor.d.ts.map +1 -0
  152. package/dist/editor/editor.js +955 -0
  153. package/dist/editor/editor.js.map +1 -0
  154. package/dist/editor/index.d.ts +3 -0
  155. package/dist/editor/index.d.ts.map +1 -0
  156. package/dist/editor/index.js +2 -0
  157. package/dist/editor/index.js.map +1 -0
  158. package/dist/event-bus/event-bus.d.ts +20 -0
  159. package/dist/event-bus/event-bus.d.ts.map +1 -0
  160. package/dist/event-bus/event-bus.js +55 -0
  161. package/dist/event-bus/event-bus.js.map +1 -0
  162. package/dist/event-bus/index.d.ts +3 -0
  163. package/dist/event-bus/index.d.ts.map +1 -0
  164. package/dist/event-bus/index.js +2 -0
  165. package/dist/event-bus/index.js.map +1 -0
  166. package/dist/file-upload/file-upload.d.ts +24 -0
  167. package/dist/file-upload/file-upload.d.ts.map +1 -0
  168. package/dist/file-upload/file-upload.js +177 -0
  169. package/dist/file-upload/file-upload.js.map +1 -0
  170. package/dist/file-upload/index.d.ts +3 -0
  171. package/dist/file-upload/index.d.ts.map +1 -0
  172. package/dist/file-upload/index.js +2 -0
  173. package/dist/file-upload/index.js.map +1 -0
  174. package/dist/forms/form-engine.d.ts +19 -6
  175. package/dist/forms/form-engine.d.ts.map +1 -1
  176. package/dist/forms/form-engine.js +97 -11
  177. package/dist/forms/form-engine.js.map +1 -1
  178. package/dist/forms/validators.d.ts +4 -0
  179. package/dist/forms/validators.d.ts.map +1 -1
  180. package/dist/forms/validators.js +6 -0
  181. package/dist/forms/validators.js.map +1 -1
  182. package/dist/forms/wasm-validators.d.ts +19 -11
  183. package/dist/forms/wasm-validators.d.ts.map +1 -1
  184. package/dist/forms/wasm-validators.js +191 -31
  185. package/dist/forms/wasm-validators.js.map +1 -1
  186. package/dist/gesture/gesture.d.ts +2 -0
  187. package/dist/gesture/gesture.d.ts.map +1 -1
  188. package/dist/gesture/gesture.js +81 -0
  189. package/dist/gesture/gesture.js.map +1 -1
  190. package/dist/http/http.d.ts +8 -0
  191. package/dist/http/http.d.ts.map +1 -1
  192. package/dist/http/http.js +18 -4
  193. package/dist/http/http.js.map +1 -1
  194. package/dist/i18n/i18n.d.ts +6 -0
  195. package/dist/i18n/i18n.d.ts.map +1 -1
  196. package/dist/i18n/i18n.js +71 -9
  197. package/dist/i18n/i18n.js.map +1 -1
  198. package/dist/machine/index.d.ts +3 -0
  199. package/dist/machine/index.d.ts.map +1 -0
  200. package/dist/machine/index.js +2 -0
  201. package/dist/machine/index.js.map +1 -0
  202. package/dist/machine/machine.d.ts +26 -0
  203. package/dist/machine/machine.d.ts.map +1 -0
  204. package/dist/machine/machine.js +79 -0
  205. package/dist/machine/machine.js.map +1 -0
  206. package/dist/modal/modal.d.ts.map +1 -1
  207. package/dist/modal/modal.js +13 -29
  208. package/dist/modal/modal.js.map +1 -1
  209. package/dist/notification-center/index.d.ts +3 -0
  210. package/dist/notification-center/index.d.ts.map +1 -0
  211. package/dist/notification-center/index.js +2 -0
  212. package/dist/notification-center/index.js.map +1 -0
  213. package/dist/notification-center/notification-center.d.ts +55 -0
  214. package/dist/notification-center/notification-center.d.ts.map +1 -0
  215. package/dist/notification-center/notification-center.js +941 -0
  216. package/dist/notification-center/notification-center.js.map +1 -0
  217. package/dist/pagination/index.d.ts +3 -0
  218. package/dist/pagination/index.d.ts.map +1 -0
  219. package/dist/pagination/index.js +2 -0
  220. package/dist/pagination/index.js.map +1 -0
  221. package/dist/pagination/pagination.d.ts +31 -0
  222. package/dist/pagination/pagination.d.ts.map +1 -0
  223. package/dist/pagination/pagination.js +213 -0
  224. package/dist/pagination/pagination.js.map +1 -0
  225. package/dist/progress/progress.d.ts.map +1 -1
  226. package/dist/progress/progress.js +5 -7
  227. package/dist/progress/progress.js.map +1 -1
  228. package/dist/query/index.d.ts +3 -0
  229. package/dist/query/index.d.ts.map +1 -0
  230. package/dist/query/index.js +2 -0
  231. package/dist/query/index.js.map +1 -0
  232. package/dist/query/query.d.ts +31 -0
  233. package/dist/query/query.d.ts.map +1 -0
  234. package/dist/query/query.js +150 -0
  235. package/dist/query/query.js.map +1 -0
  236. package/dist/radio-group/index.d.ts +3 -0
  237. package/dist/radio-group/index.d.ts.map +1 -0
  238. package/dist/radio-group/index.js +2 -0
  239. package/dist/radio-group/index.js.map +1 -0
  240. package/dist/radio-group/radio-group.d.ts +37 -0
  241. package/dist/radio-group/radio-group.d.ts.map +1 -0
  242. package/dist/radio-group/radio-group.js +251 -0
  243. package/dist/radio-group/radio-group.js.map +1 -0
  244. package/dist/rating/index.d.ts +3 -0
  245. package/dist/rating/index.d.ts.map +1 -0
  246. package/dist/rating/index.js +2 -0
  247. package/dist/rating/index.js.map +1 -0
  248. package/dist/rating/rating.d.ts +31 -0
  249. package/dist/rating/rating.d.ts.map +1 -0
  250. package/dist/rating/rating.js +187 -0
  251. package/dist/rating/rating.js.map +1 -0
  252. package/dist/router/index.d.ts +1 -1
  253. package/dist/router/index.d.ts.map +1 -1
  254. package/dist/router/index.js +1 -1
  255. package/dist/router/index.js.map +1 -1
  256. package/dist/router/router.d.ts +30 -1
  257. package/dist/router/router.d.ts.map +1 -1
  258. package/dist/router/router.js +131 -16
  259. package/dist/router/router.js.map +1 -1
  260. package/dist/skeleton/index.d.ts +3 -0
  261. package/dist/skeleton/index.d.ts.map +1 -0
  262. package/dist/skeleton/index.js +2 -0
  263. package/dist/skeleton/index.js.map +1 -0
  264. package/dist/skeleton/skeleton.d.ts +24 -0
  265. package/dist/skeleton/skeleton.d.ts.map +1 -0
  266. package/dist/skeleton/skeleton.js +91 -0
  267. package/dist/skeleton/skeleton.js.map +1 -0
  268. package/dist/slider/index.d.ts +3 -0
  269. package/dist/slider/index.d.ts.map +1 -0
  270. package/dist/slider/index.js +2 -0
  271. package/dist/slider/index.js.map +1 -0
  272. package/dist/slider/slider.d.ts +33 -0
  273. package/dist/slider/slider.d.ts.map +1 -0
  274. package/dist/slider/slider.js +248 -0
  275. package/dist/slider/slider.js.map +1 -0
  276. package/dist/spinner/index.d.ts +3 -0
  277. package/dist/spinner/index.d.ts.map +1 -0
  278. package/dist/spinner/index.js +2 -0
  279. package/dist/spinner/index.js.map +1 -0
  280. package/dist/spinner/spinner.d.ts +23 -0
  281. package/dist/spinner/spinner.d.ts.map +1 -0
  282. package/dist/spinner/spinner.js +82 -0
  283. package/dist/spinner/spinner.js.map +1 -0
  284. package/dist/state/store.d.ts +4 -0
  285. package/dist/state/store.d.ts.map +1 -1
  286. package/dist/state/store.js +27 -7
  287. package/dist/state/store.js.map +1 -1
  288. package/dist/sw/sw.d.ts.map +1 -1
  289. package/dist/sw/sw.js +39 -7
  290. package/dist/sw/sw.js.map +1 -1
  291. package/dist/switch/index.d.ts +3 -0
  292. package/dist/switch/index.d.ts.map +1 -0
  293. package/dist/switch/index.js +2 -0
  294. package/dist/switch/index.js.map +1 -0
  295. package/dist/switch/switch.d.ts +27 -0
  296. package/dist/switch/switch.d.ts.map +1 -0
  297. package/dist/switch/switch.js +163 -0
  298. package/dist/switch/switch.js.map +1 -0
  299. package/dist/theme/index.d.ts +2 -0
  300. package/dist/theme/index.d.ts.map +1 -1
  301. package/dist/theme/index.js +1 -0
  302. package/dist/theme/index.js.map +1 -1
  303. package/dist/theme/scale.d.ts +40 -0
  304. package/dist/theme/scale.d.ts.map +1 -0
  305. package/dist/theme/scale.js +62 -0
  306. package/dist/theme/scale.js.map +1 -0
  307. package/dist/tree-view/index.d.ts +3 -0
  308. package/dist/tree-view/index.d.ts.map +1 -0
  309. package/dist/tree-view/index.js +2 -0
  310. package/dist/tree-view/index.js.map +1 -0
  311. package/dist/tree-view/tree-view.d.ts +29 -0
  312. package/dist/tree-view/tree-view.d.ts.map +1 -0
  313. package/dist/tree-view/tree-view.js +273 -0
  314. package/dist/tree-view/tree-view.js.map +1 -0
  315. package/dist/utils/index.d.ts +29 -0
  316. package/dist/utils/index.d.ts.map +1 -0
  317. package/dist/utils/index.js +114 -0
  318. package/dist/utils/index.js.map +1 -0
  319. package/dist/websocket/index.d.ts +3 -0
  320. package/dist/websocket/index.d.ts.map +1 -0
  321. package/dist/websocket/index.js +2 -0
  322. package/dist/websocket/index.js.map +1 -0
  323. package/dist/websocket/websocket.d.ts +31 -0
  324. package/dist/websocket/websocket.d.ts.map +1 -0
  325. package/dist/websocket/websocket.js +164 -0
  326. package/dist/websocket/websocket.js.map +1 -0
  327. package/package.json +135 -2
@@ -0,0 +1,955 @@
1
+ /**
2
+ * Nova Engine — Markdown Editor
3
+ *
4
+ * Interactive split-pane markdown editor with live preview,
5
+ * toolbar, keyboard shortcuts, and auto-list continuation.
6
+ * Uses Nova's internal markdown parser for rendering.
7
+ */
8
+ import { parse } from '../markdown/markdown';
9
+ // ── Style Injection ─────────────────────────────────────────────────
10
+ const STYLE_ID = 'nova-editor-styles';
11
+ function injectStyles() {
12
+ if (document.getElementById(STYLE_ID))
13
+ return;
14
+ const s = document.createElement('style');
15
+ s.id = STYLE_ID;
16
+ s.textContent = `
17
+ .nova-editor {
18
+ display: flex;
19
+ flex-direction: column;
20
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
21
+ border-radius: 8px;
22
+ background: var(--color-bg-surface, rgba(18, 18, 18, 0.95));
23
+ color: var(--color-text-primary, #e5e5e5);
24
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
25
+ overflow: hidden;
26
+ }
27
+
28
+ /* ── Toolbar ── */
29
+ .nova-editor__toolbar {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 2px;
33
+ padding: 6px 8px;
34
+ border-bottom: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
35
+ background: var(--color-bg-elevated, rgba(22, 22, 22, 0.98));
36
+ flex-wrap: wrap;
37
+ }
38
+ .nova-editor__toolbar-btn {
39
+ display: inline-flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ width: 32px;
43
+ height: 32px;
44
+ border: none;
45
+ border-radius: 6px;
46
+ background: transparent;
47
+ color: var(--color-text-secondary, rgba(255, 255, 255, 0.55));
48
+ cursor: pointer;
49
+ padding: 0;
50
+ transition: background 0.15s ease, color 0.15s ease;
51
+ }
52
+ .nova-editor__toolbar-btn:hover {
53
+ background: var(--color-bg-hover, rgba(255, 255, 255, 0.06));
54
+ color: var(--color-text-primary, #e5e5e5);
55
+ }
56
+ .nova-editor__toolbar-btn:focus-visible {
57
+ outline: 2px solid var(--color-focus-ring, rgba(99, 102, 241, 0.6));
58
+ outline-offset: -2px;
59
+ }
60
+ .nova-editor__toolbar-btn[aria-pressed="true"] {
61
+ background: var(--color-bg-active, rgba(255, 255, 255, 0.1));
62
+ color: var(--color-text-primary, #e5e5e5);
63
+ }
64
+ .nova-editor__toolbar-sep {
65
+ width: 1px;
66
+ height: 20px;
67
+ background: var(--color-border, rgba(255, 255, 255, 0.08));
68
+ margin: 0 4px;
69
+ flex-shrink: 0;
70
+ }
71
+
72
+ /* ── Body ── */
73
+ .nova-editor__body {
74
+ display: flex;
75
+ flex: 1;
76
+ min-height: 0;
77
+ }
78
+ .nova-editor__body--split {
79
+ flex-direction: row;
80
+ }
81
+ .nova-editor__body--write .nova-editor__pane-write {
82
+ flex: 1;
83
+ }
84
+ .nova-editor__body--write .nova-editor__pane-preview {
85
+ display: none;
86
+ }
87
+ .nova-editor__body--preview .nova-editor__pane-write {
88
+ display: none;
89
+ }
90
+ .nova-editor__body--preview .nova-editor__pane-preview {
91
+ flex: 1;
92
+ }
93
+
94
+ /* ── Write Pane ── */
95
+ .nova-editor__pane-write {
96
+ position: relative;
97
+ display: flex;
98
+ flex: 1;
99
+ min-width: 0;
100
+ }
101
+ .nova-editor__line-numbers {
102
+ padding: 12px 0;
103
+ width: 44px;
104
+ flex-shrink: 0;
105
+ text-align: right;
106
+ font: 13px/1.6 "SF Mono", "Fira Code", "Cascadia Code", Menlo, Monaco, Consolas, monospace;
107
+ color: var(--color-text-tertiary, rgba(255, 255, 255, 0.25));
108
+ background: var(--color-bg-inset, rgba(0, 0, 0, 0.15));
109
+ border-right: 1px solid var(--color-border, rgba(255, 255, 255, 0.06));
110
+ user-select: none;
111
+ overflow: hidden;
112
+ }
113
+ .nova-editor__line-numbers span {
114
+ display: block;
115
+ padding-right: 8px;
116
+ }
117
+ .nova-editor__textarea {
118
+ flex: 1;
119
+ resize: none;
120
+ border: none;
121
+ outline: none;
122
+ background: transparent;
123
+ color: var(--color-text-primary, #e5e5e5);
124
+ font: 14px/1.6 "SF Mono", "Fira Code", "Cascadia Code", Menlo, Monaco, Consolas, monospace;
125
+ padding: 12px 16px;
126
+ tab-size: 2;
127
+ white-space: pre-wrap;
128
+ word-wrap: break-word;
129
+ overflow-y: auto;
130
+ }
131
+ .nova-editor__textarea::placeholder {
132
+ color: var(--color-text-tertiary, rgba(255, 255, 255, 0.25));
133
+ }
134
+ .nova-editor__textarea:focus {
135
+ box-shadow: inset 0 0 0 1px var(--color-focus-ring, rgba(99, 102, 241, 0.4));
136
+ }
137
+
138
+ /* ── Divider ── */
139
+ .nova-editor__divider {
140
+ width: 1px;
141
+ background: var(--color-border, rgba(255, 255, 255, 0.08));
142
+ flex-shrink: 0;
143
+ }
144
+
145
+ /* ── Preview Pane ── */
146
+ .nova-editor__pane-preview {
147
+ flex: 1;
148
+ overflow-y: auto;
149
+ padding: 12px 20px;
150
+ min-width: 0;
151
+ }
152
+
153
+ /* ── Preview Typography ── */
154
+ .nova-editor__preview h1,
155
+ .nova-editor__preview h2,
156
+ .nova-editor__preview h3,
157
+ .nova-editor__preview h4,
158
+ .nova-editor__preview h5,
159
+ .nova-editor__preview h6 {
160
+ color: var(--color-text-primary, #e5e5e5);
161
+ font-weight: 600;
162
+ line-height: 1.3;
163
+ margin-top: 1.4em;
164
+ margin-bottom: 0.6em;
165
+ }
166
+ .nova-editor__preview h1:first-child,
167
+ .nova-editor__preview h2:first-child,
168
+ .nova-editor__preview h3:first-child {
169
+ margin-top: 0;
170
+ }
171
+ .nova-editor__preview h1 { font-size: 1.75em; }
172
+ .nova-editor__preview h2 { font-size: 1.4em; }
173
+ .nova-editor__preview h3 { font-size: 1.15em; }
174
+ .nova-editor__preview h4 { font-size: 1em; }
175
+ .nova-editor__preview p {
176
+ margin: 0 0 0.8em 0;
177
+ line-height: 1.65;
178
+ }
179
+ .nova-editor__preview a {
180
+ color: var(--color-accent, #6366f1);
181
+ text-decoration: underline;
182
+ text-decoration-color: var(--color-accent-muted, rgba(99, 102, 241, 0.4));
183
+ text-underline-offset: 2px;
184
+ }
185
+ .nova-editor__preview a:hover {
186
+ text-decoration-color: var(--color-accent, #6366f1);
187
+ }
188
+ .nova-editor__preview strong { color: var(--color-text-primary, #e5e5e5); }
189
+ .nova-editor__preview code {
190
+ font-family: "SF Mono", "Fira Code", "Cascadia Code", Menlo, Monaco, Consolas, monospace;
191
+ font-size: 0.875em;
192
+ background: var(--color-bg-inset, rgba(255, 255, 255, 0.06));
193
+ padding: 0.15em 0.4em;
194
+ border-radius: 4px;
195
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.06));
196
+ }
197
+ .nova-editor__preview pre {
198
+ background: var(--color-bg-inset, rgba(0, 0, 0, 0.25));
199
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.06));
200
+ border-radius: 6px;
201
+ padding: 14px 16px;
202
+ overflow-x: auto;
203
+ margin: 0 0 1em 0;
204
+ }
205
+ .nova-editor__preview pre code {
206
+ background: none;
207
+ border: none;
208
+ padding: 0;
209
+ font-size: 0.85em;
210
+ line-height: 1.6;
211
+ }
212
+ .nova-editor__preview blockquote {
213
+ border-left: 3px solid var(--color-accent-muted, rgba(99, 102, 241, 0.4));
214
+ margin: 0 0 1em 0;
215
+ padding: 0.4em 0 0.4em 16px;
216
+ color: var(--color-text-secondary, rgba(255, 255, 255, 0.55));
217
+ }
218
+ .nova-editor__preview ul,
219
+ .nova-editor__preview ol {
220
+ margin: 0 0 1em 0;
221
+ padding-left: 1.6em;
222
+ }
223
+ .nova-editor__preview ul { list-style: disc; }
224
+ .nova-editor__preview ol { list-style: decimal; }
225
+ .nova-editor__preview li {
226
+ margin-bottom: 0.3em;
227
+ line-height: 1.6;
228
+ }
229
+ .nova-editor__preview hr {
230
+ border: none;
231
+ border-top: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
232
+ margin: 1.5em 0;
233
+ }
234
+ .nova-editor__preview img {
235
+ max-width: 100%;
236
+ border-radius: 6px;
237
+ }
238
+ .nova-editor__preview del {
239
+ color: var(--color-text-tertiary, rgba(255, 255, 255, 0.35));
240
+ }
241
+
242
+ /* ── Responsive ── */
243
+ @media (max-width: 768px) {
244
+ .nova-editor__body--split {
245
+ flex-direction: column;
246
+ }
247
+ .nova-editor__body--split .nova-editor__divider {
248
+ width: auto;
249
+ height: 1px;
250
+ }
251
+ }
252
+
253
+ /* ── Reduced Motion ── */
254
+ @media (prefers-reduced-motion: reduce) {
255
+ .nova-editor__toolbar-btn { transition: none; }
256
+ }`;
257
+ document.head.appendChild(s);
258
+ }
259
+ // ── SVG Icon Helpers ────────────────────────────────────────────────
260
+ const SVG_NS = 'http://www.w3.org/2000/svg';
261
+ function svgIcon(paths, viewBox = '0 0 24 24', size = 16) {
262
+ const svg = document.createElementNS(SVG_NS, 'svg');
263
+ svg.setAttribute('width', String(size));
264
+ svg.setAttribute('height', String(size));
265
+ svg.setAttribute('viewBox', viewBox);
266
+ svg.setAttribute('fill', 'none');
267
+ svg.setAttribute('stroke', 'currentColor');
268
+ svg.setAttribute('stroke-width', '2');
269
+ svg.setAttribute('stroke-linecap', 'round');
270
+ svg.setAttribute('stroke-linejoin', 'round');
271
+ for (const d of paths) {
272
+ const path = document.createElementNS(SVG_NS, 'path');
273
+ path.setAttribute('d', d);
274
+ svg.appendChild(path);
275
+ }
276
+ return svg;
277
+ }
278
+ function svgIconRaw(children, viewBox = '0 0 24 24', size = 16) {
279
+ const svg = document.createElementNS(SVG_NS, 'svg');
280
+ svg.setAttribute('width', String(size));
281
+ svg.setAttribute('height', String(size));
282
+ svg.setAttribute('viewBox', viewBox);
283
+ svg.setAttribute('fill', 'none');
284
+ svg.setAttribute('stroke', 'currentColor');
285
+ svg.setAttribute('stroke-width', '2');
286
+ svg.setAttribute('stroke-linecap', 'round');
287
+ svg.setAttribute('stroke-linejoin', 'round');
288
+ children(svg);
289
+ return svg;
290
+ }
291
+ // ── Toolbar Icons ───────────────────────────────────────────────────
292
+ function iconBold() {
293
+ return svgIconRaw((svg) => {
294
+ const p1 = document.createElementNS(SVG_NS, 'path');
295
+ p1.setAttribute('d', 'M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z');
296
+ const p2 = document.createElementNS(SVG_NS, 'path');
297
+ p2.setAttribute('d', 'M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z');
298
+ svg.appendChild(p1);
299
+ svg.appendChild(p2);
300
+ });
301
+ }
302
+ function iconItalic() {
303
+ return svgIconRaw((svg) => {
304
+ const l1 = document.createElementNS(SVG_NS, 'line');
305
+ l1.setAttribute('x1', '19');
306
+ l1.setAttribute('y1', '4');
307
+ l1.setAttribute('x2', '10');
308
+ l1.setAttribute('y2', '4');
309
+ const l2 = document.createElementNS(SVG_NS, 'line');
310
+ l2.setAttribute('x1', '14');
311
+ l2.setAttribute('y1', '20');
312
+ l2.setAttribute('x2', '5');
313
+ l2.setAttribute('y2', '20');
314
+ const l3 = document.createElementNS(SVG_NS, 'line');
315
+ l3.setAttribute('x1', '15');
316
+ l3.setAttribute('y1', '4');
317
+ l3.setAttribute('x2', '9');
318
+ l3.setAttribute('y2', '20');
319
+ svg.appendChild(l1);
320
+ svg.appendChild(l2);
321
+ svg.appendChild(l3);
322
+ });
323
+ }
324
+ function iconStrikethrough() {
325
+ return svgIconRaw((svg) => {
326
+ const p = document.createElementNS(SVG_NS, 'path');
327
+ p.setAttribute('d', 'M16 4H9a3 3 0 0 0-3 3v0a3 3 0 0 0 3 3h6a3 3 0 0 1 3 3v0a3 3 0 0 1-3 3H8');
328
+ const l = document.createElementNS(SVG_NS, 'line');
329
+ l.setAttribute('x1', '4');
330
+ l.setAttribute('y1', '12');
331
+ l.setAttribute('x2', '20');
332
+ l.setAttribute('y2', '12');
333
+ svg.appendChild(p);
334
+ svg.appendChild(l);
335
+ });
336
+ }
337
+ function iconHeading() {
338
+ return svgIconRaw((svg) => {
339
+ const l1 = document.createElementNS(SVG_NS, 'line');
340
+ l1.setAttribute('x1', '4');
341
+ l1.setAttribute('y1', '4');
342
+ l1.setAttribute('x2', '4');
343
+ l1.setAttribute('y2', '20');
344
+ const l2 = document.createElementNS(SVG_NS, 'line');
345
+ l2.setAttribute('x1', '18');
346
+ l2.setAttribute('y1', '4');
347
+ l2.setAttribute('x2', '18');
348
+ l2.setAttribute('y2', '20');
349
+ const l3 = document.createElementNS(SVG_NS, 'line');
350
+ l3.setAttribute('x1', '4');
351
+ l3.setAttribute('y1', '12');
352
+ l3.setAttribute('x2', '18');
353
+ l3.setAttribute('y2', '12');
354
+ svg.appendChild(l1);
355
+ svg.appendChild(l2);
356
+ svg.appendChild(l3);
357
+ });
358
+ }
359
+ function iconLink() {
360
+ return svgIcon([
361
+ 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71',
362
+ 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',
363
+ ]);
364
+ }
365
+ function iconImage() {
366
+ return svgIconRaw((svg) => {
367
+ const rect = document.createElementNS(SVG_NS, 'rect');
368
+ rect.setAttribute('x', '3');
369
+ rect.setAttribute('y', '3');
370
+ rect.setAttribute('width', '18');
371
+ rect.setAttribute('height', '18');
372
+ rect.setAttribute('rx', '2');
373
+ rect.setAttribute('ry', '2');
374
+ const circle = document.createElementNS(SVG_NS, 'circle');
375
+ circle.setAttribute('cx', '8.5');
376
+ circle.setAttribute('cy', '8.5');
377
+ circle.setAttribute('r', '1.5');
378
+ circle.setAttribute('fill', 'currentColor');
379
+ circle.setAttribute('stroke', 'none');
380
+ const poly = document.createElementNS(SVG_NS, 'polyline');
381
+ poly.setAttribute('points', '21 15 16 10 5 21');
382
+ svg.appendChild(rect);
383
+ svg.appendChild(circle);
384
+ svg.appendChild(poly);
385
+ });
386
+ }
387
+ function iconCode() {
388
+ return svgIconRaw((svg) => {
389
+ const p1 = document.createElementNS(SVG_NS, 'polyline');
390
+ p1.setAttribute('points', '16 18 22 12 16 6');
391
+ const p2 = document.createElementNS(SVG_NS, 'polyline');
392
+ p2.setAttribute('points', '8 6 2 12 8 18');
393
+ svg.appendChild(p1);
394
+ svg.appendChild(p2);
395
+ });
396
+ }
397
+ function iconQuote() {
398
+ return svgIconRaw((svg) => {
399
+ const l1 = document.createElementNS(SVG_NS, 'line');
400
+ l1.setAttribute('x1', '3');
401
+ l1.setAttribute('y1', '6');
402
+ l1.setAttribute('x2', '3');
403
+ l1.setAttribute('y2', '18');
404
+ l1.setAttribute('stroke-width', '3');
405
+ const l2 = document.createElementNS(SVG_NS, 'line');
406
+ l2.setAttribute('x1', '9');
407
+ l2.setAttribute('y1', '8');
408
+ l2.setAttribute('x2', '21');
409
+ l2.setAttribute('y2', '8');
410
+ const l3 = document.createElementNS(SVG_NS, 'line');
411
+ l3.setAttribute('x1', '9');
412
+ l3.setAttribute('y1', '12');
413
+ l3.setAttribute('x2', '18');
414
+ l3.setAttribute('y2', '12');
415
+ const l4 = document.createElementNS(SVG_NS, 'line');
416
+ l4.setAttribute('x1', '9');
417
+ l4.setAttribute('y1', '16');
418
+ l4.setAttribute('x2', '15');
419
+ l4.setAttribute('y2', '16');
420
+ svg.appendChild(l1);
421
+ svg.appendChild(l2);
422
+ svg.appendChild(l3);
423
+ svg.appendChild(l4);
424
+ });
425
+ }
426
+ function iconUnorderedList() {
427
+ return svgIconRaw((svg) => {
428
+ const items = [
429
+ ['10', '6', '21', '6'],
430
+ ['10', '12', '21', '12'],
431
+ ['10', '18', '21', '18'],
432
+ ];
433
+ for (const [x1, y1, x2, y2] of items) {
434
+ const l = document.createElementNS(SVG_NS, 'line');
435
+ l.setAttribute('x1', x1);
436
+ l.setAttribute('y1', y1);
437
+ l.setAttribute('x2', x2);
438
+ l.setAttribute('y2', y2);
439
+ svg.appendChild(l);
440
+ }
441
+ const dots = [['4', '6'], ['4', '12'], ['4', '18']];
442
+ for (const [cx, cy] of dots) {
443
+ const c = document.createElementNS(SVG_NS, 'circle');
444
+ c.setAttribute('cx', cx);
445
+ c.setAttribute('cy', cy);
446
+ c.setAttribute('r', '1');
447
+ c.setAttribute('fill', 'currentColor');
448
+ c.setAttribute('stroke', 'none');
449
+ svg.appendChild(c);
450
+ }
451
+ });
452
+ }
453
+ function iconOrderedList() {
454
+ return svgIconRaw((svg) => {
455
+ const items = [
456
+ ['12', '6', '21', '6'],
457
+ ['12', '12', '21', '12'],
458
+ ['12', '18', '21', '18'],
459
+ ];
460
+ for (const [x1, y1, x2, y2] of items) {
461
+ const l = document.createElementNS(SVG_NS, 'line');
462
+ l.setAttribute('x1', x1);
463
+ l.setAttribute('y1', y1);
464
+ l.setAttribute('x2', x2);
465
+ l.setAttribute('y2', y2);
466
+ svg.appendChild(l);
467
+ }
468
+ const text = document.createElementNS(SVG_NS, 'text');
469
+ text.setAttribute('x', '4');
470
+ text.setAttribute('y', '8');
471
+ text.setAttribute('font-size', '7');
472
+ text.setAttribute('fill', 'currentColor');
473
+ text.setAttribute('stroke', 'none');
474
+ text.setAttribute('font-family', 'sans-serif');
475
+ text.textContent = '1';
476
+ const text2 = document.createElementNS(SVG_NS, 'text');
477
+ text2.setAttribute('x', '4');
478
+ text2.setAttribute('y', '14');
479
+ text2.setAttribute('font-size', '7');
480
+ text2.setAttribute('fill', 'currentColor');
481
+ text2.setAttribute('stroke', 'none');
482
+ text2.setAttribute('font-family', 'sans-serif');
483
+ text2.textContent = '2';
484
+ const text3 = document.createElementNS(SVG_NS, 'text');
485
+ text3.setAttribute('x', '4');
486
+ text3.setAttribute('y', '20');
487
+ text3.setAttribute('font-size', '7');
488
+ text3.setAttribute('fill', 'currentColor');
489
+ text3.setAttribute('stroke', 'none');
490
+ text3.setAttribute('font-family', 'sans-serif');
491
+ text3.textContent = '3';
492
+ svg.appendChild(text);
493
+ svg.appendChild(text2);
494
+ svg.appendChild(text3);
495
+ });
496
+ }
497
+ function iconWrite() {
498
+ return svgIcon([
499
+ 'M12 20h9',
500
+ 'M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z',
501
+ ]);
502
+ }
503
+ function iconSplit() {
504
+ return svgIconRaw((svg) => {
505
+ const rect = document.createElementNS(SVG_NS, 'rect');
506
+ rect.setAttribute('x', '3');
507
+ rect.setAttribute('y', '3');
508
+ rect.setAttribute('width', '18');
509
+ rect.setAttribute('height', '18');
510
+ rect.setAttribute('rx', '2');
511
+ const line = document.createElementNS(SVG_NS, 'line');
512
+ line.setAttribute('x1', '12');
513
+ line.setAttribute('y1', '3');
514
+ line.setAttribute('x2', '12');
515
+ line.setAttribute('y2', '21');
516
+ svg.appendChild(rect);
517
+ svg.appendChild(line);
518
+ });
519
+ }
520
+ function iconPreview() {
521
+ return svgIconRaw((svg) => {
522
+ const p1 = document.createElementNS(SVG_NS, 'path');
523
+ p1.setAttribute('d', 'M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z');
524
+ const c = document.createElementNS(SVG_NS, 'circle');
525
+ c.setAttribute('cx', '12');
526
+ c.setAttribute('cy', '12');
527
+ c.setAttribute('r', '3');
528
+ svg.appendChild(p1);
529
+ svg.appendChild(c);
530
+ });
531
+ }
532
+ // ── Factory ─────────────────────────────────────────────────────────
533
+ /** Create an interactive markdown editor. */
534
+ export function createEditor(container, options = {}) {
535
+ const { value: initialValue = '', mode: initialMode = 'split', placeholder = 'Write markdown...', toolbar: showToolbar = true, lineNumbers = false, minHeight = '300px', maxHeight = 'none', onChange, onSave, } = options;
536
+ injectStyles();
537
+ let destroyed = false;
538
+ let currentMode = initialMode;
539
+ let debounceTimer;
540
+ // Listener bookkeeping for cleanup
541
+ const listeners = [];
542
+ function on(target, evt, fn, capture) {
543
+ target.addEventListener(evt, fn, capture);
544
+ listeners.push([target, evt, fn, capture]);
545
+ }
546
+ // ── Root Element ──────────────────────────────────────────────────
547
+ const root = document.createElement('div');
548
+ root.className = 'nova-editor';
549
+ root.setAttribute('role', 'group');
550
+ root.setAttribute('aria-label', 'Markdown editor');
551
+ root.style.minHeight = minHeight;
552
+ if (maxHeight !== 'none')
553
+ root.style.maxHeight = maxHeight;
554
+ // ── Toolbar ───────────────────────────────────────────────────────
555
+ let toolbarEl = null;
556
+ const modeButtons = new Map();
557
+ function createToolbarButton(label, icon, action, shortcut) {
558
+ const btn = document.createElement('button');
559
+ btn.type = 'button';
560
+ btn.className = 'nova-editor__toolbar-btn';
561
+ const title = shortcut ? `${label} (${shortcut})` : label;
562
+ btn.setAttribute('aria-label', title);
563
+ btn.setAttribute('title', title);
564
+ btn.appendChild(icon);
565
+ on(btn, 'click', (e) => {
566
+ e.preventDefault();
567
+ action();
568
+ });
569
+ return btn;
570
+ }
571
+ function createSeparator() {
572
+ const sep = document.createElement('span');
573
+ sep.className = 'nova-editor__toolbar-sep';
574
+ sep.setAttribute('role', 'separator');
575
+ return sep;
576
+ }
577
+ function createModeButton(label, icon, mode) {
578
+ const btn = createToolbarButton(label, icon, () => setMode(mode));
579
+ btn.setAttribute('aria-pressed', String(currentMode === mode));
580
+ modeButtons.set(mode, btn);
581
+ return btn;
582
+ }
583
+ if (showToolbar) {
584
+ toolbarEl = document.createElement('div');
585
+ toolbarEl.className = 'nova-editor__toolbar';
586
+ toolbarEl.setAttribute('role', 'toolbar');
587
+ toolbarEl.setAttribute('aria-label', 'Formatting');
588
+ // Formatting buttons
589
+ toolbarEl.appendChild(createToolbarButton('Bold', iconBold(), () => wrapSelection('**', '**'), 'Ctrl+B'));
590
+ toolbarEl.appendChild(createToolbarButton('Italic', iconItalic(), () => wrapSelection('*', '*'), 'Ctrl+I'));
591
+ toolbarEl.appendChild(createToolbarButton('Strikethrough', iconStrikethrough(), () => wrapSelection('~~', '~~')));
592
+ toolbarEl.appendChild(createSeparator());
593
+ toolbarEl.appendChild(createToolbarButton('Heading', iconHeading(), () => insertAtLineStart('## ')));
594
+ toolbarEl.appendChild(createToolbarButton('Link', iconLink(), () => insertLink(), 'Ctrl+K'));
595
+ toolbarEl.appendChild(createToolbarButton('Image', iconImage(), () => insertImage()));
596
+ toolbarEl.appendChild(createToolbarButton('Code', iconCode(), () => insertCode()));
597
+ toolbarEl.appendChild(createToolbarButton('Quote', iconQuote(), () => insertAtLineStart('> ')));
598
+ toolbarEl.appendChild(createToolbarButton('Unordered list', iconUnorderedList(), () => insertAtLineStart('- ')));
599
+ toolbarEl.appendChild(createToolbarButton('Ordered list', iconOrderedList(), () => insertAtLineStart('1. ')));
600
+ toolbarEl.appendChild(createSeparator());
601
+ // Mode buttons
602
+ toolbarEl.appendChild(createModeButton('Write', iconWrite(), 'write'));
603
+ toolbarEl.appendChild(createModeButton('Split', iconSplit(), 'split'));
604
+ toolbarEl.appendChild(createModeButton('Preview', iconPreview(), 'preview'));
605
+ root.appendChild(toolbarEl);
606
+ }
607
+ // ── Body ──────────────────────────────────────────────────────────
608
+ const body = document.createElement('div');
609
+ body.className = 'nova-editor__body';
610
+ applyModeClass();
611
+ // ── Write Pane ────────────────────────────────────────────────────
612
+ const writePane = document.createElement('div');
613
+ writePane.className = 'nova-editor__pane-write';
614
+ // Line numbers gutter
615
+ let gutterEl = null;
616
+ if (lineNumbers) {
617
+ gutterEl = document.createElement('div');
618
+ gutterEl.className = 'nova-editor__line-numbers';
619
+ gutterEl.setAttribute('aria-hidden', 'true');
620
+ writePane.appendChild(gutterEl);
621
+ }
622
+ const textarea = document.createElement('textarea');
623
+ textarea.className = 'nova-editor__textarea';
624
+ textarea.value = initialValue;
625
+ textarea.placeholder = placeholder;
626
+ textarea.setAttribute('aria-label', 'Markdown input');
627
+ textarea.setAttribute('spellcheck', 'true');
628
+ writePane.appendChild(textarea);
629
+ // ── Divider ───────────────────────────────────────────────────────
630
+ const divider = document.createElement('div');
631
+ divider.className = 'nova-editor__divider';
632
+ // ── Preview Pane ──────────────────────────────────────────────────
633
+ const previewPane = document.createElement('div');
634
+ previewPane.className = 'nova-editor__pane-preview';
635
+ const previewContent = document.createElement('div');
636
+ previewContent.className = 'nova-editor__preview';
637
+ previewContent.setAttribute('role', 'document');
638
+ previewContent.setAttribute('aria-label', 'Markdown preview');
639
+ previewPane.appendChild(previewContent);
640
+ body.appendChild(writePane);
641
+ body.appendChild(divider);
642
+ body.appendChild(previewPane);
643
+ root.appendChild(body);
644
+ container.appendChild(root);
645
+ // ── Internal Helpers ──────────────────────────────────────────────
646
+ function applyModeClass() {
647
+ body.className = `nova-editor__body nova-editor__body--${currentMode}`;
648
+ // Toggle divider visibility
649
+ divider.style.display = currentMode === 'split' ? '' : 'none';
650
+ }
651
+ function updateModeButtons() {
652
+ for (const [mode, btn] of modeButtons) {
653
+ btn.setAttribute('aria-pressed', String(currentMode === mode));
654
+ }
655
+ }
656
+ function updatePreview() {
657
+ if (currentMode === 'write')
658
+ return;
659
+ // Rendered from trusted internal Nova markdown parser
660
+ previewContent.innerHTML = parse(textarea.value);
661
+ }
662
+ function updateLineNumbers() {
663
+ if (!gutterEl)
664
+ return;
665
+ const count = textarea.value.split('\n').length;
666
+ // Clear existing numbers
667
+ while (gutterEl.firstChild)
668
+ gutterEl.removeChild(gutterEl.firstChild);
669
+ for (let i = 1; i <= count; i++) {
670
+ const span = document.createElement('span');
671
+ span.textContent = String(i);
672
+ gutterEl.appendChild(span);
673
+ }
674
+ }
675
+ function syncGutterScroll() {
676
+ if (!gutterEl)
677
+ return;
678
+ gutterEl.scrollTop = textarea.scrollTop;
679
+ }
680
+ function schedulePreview() {
681
+ if (debounceTimer !== undefined)
682
+ clearTimeout(debounceTimer);
683
+ debounceTimer = setTimeout(updatePreview, 150);
684
+ }
685
+ function getLineStart(pos) {
686
+ const val = textarea.value;
687
+ const lineStart = val.lastIndexOf('\n', pos - 1);
688
+ return lineStart + 1;
689
+ }
690
+ function getCurrentLine(pos) {
691
+ const val = textarea.value;
692
+ const start = getLineStart(pos);
693
+ const end = val.indexOf('\n', pos);
694
+ return val.substring(start, end === -1 ? val.length : end);
695
+ }
696
+ // ── Editing Operations ────────────────────────────────────────────
697
+ function wrapSelection(before, after) {
698
+ if (destroyed)
699
+ return;
700
+ textarea.focus();
701
+ const start = textarea.selectionStart;
702
+ const end = textarea.selectionEnd;
703
+ const val = textarea.value;
704
+ const selected = val.substring(start, end);
705
+ const replacement = before + selected + after;
706
+ textarea.value = val.substring(0, start) + replacement + val.substring(end);
707
+ // Select the inner text (without the wrapping markers)
708
+ textarea.selectionStart = start + before.length;
709
+ textarea.selectionEnd = start + before.length + selected.length;
710
+ triggerChange();
711
+ }
712
+ function insertAtLineStart(prefix) {
713
+ if (destroyed)
714
+ return;
715
+ textarea.focus();
716
+ const start = textarea.selectionStart;
717
+ const val = textarea.value;
718
+ const lineStart = getLineStart(start);
719
+ textarea.value = val.substring(0, lineStart) + prefix + val.substring(lineStart);
720
+ textarea.selectionStart = start + prefix.length;
721
+ textarea.selectionEnd = start + prefix.length;
722
+ triggerChange();
723
+ }
724
+ function insertLink() {
725
+ if (destroyed)
726
+ return;
727
+ textarea.focus();
728
+ const start = textarea.selectionStart;
729
+ const end = textarea.selectionEnd;
730
+ const val = textarea.value;
731
+ const selected = val.substring(start, end);
732
+ if (selected) {
733
+ const replacement = `[${selected}](url)`;
734
+ textarea.value = val.substring(0, start) + replacement + val.substring(end);
735
+ // Select "url"
736
+ textarea.selectionStart = start + selected.length + 3;
737
+ textarea.selectionEnd = start + selected.length + 6;
738
+ }
739
+ else {
740
+ const replacement = '[text](url)';
741
+ textarea.value = val.substring(0, start) + replacement + val.substring(end);
742
+ // Select "text"
743
+ textarea.selectionStart = start + 1;
744
+ textarea.selectionEnd = start + 5;
745
+ }
746
+ triggerChange();
747
+ }
748
+ function insertImage() {
749
+ if (destroyed)
750
+ return;
751
+ textarea.focus();
752
+ const start = textarea.selectionStart;
753
+ const end = textarea.selectionEnd;
754
+ const val = textarea.value;
755
+ const selected = val.substring(start, end);
756
+ const alt = selected || 'alt';
757
+ const replacement = `![${alt}](url)`;
758
+ textarea.value = val.substring(0, start) + replacement + val.substring(end);
759
+ // Select "url"
760
+ textarea.selectionStart = start + alt.length + 4;
761
+ textarea.selectionEnd = start + alt.length + 7;
762
+ triggerChange();
763
+ }
764
+ function insertCode() {
765
+ if (destroyed)
766
+ return;
767
+ textarea.focus();
768
+ const start = textarea.selectionStart;
769
+ const end = textarea.selectionEnd;
770
+ const val = textarea.value;
771
+ const selected = val.substring(start, end);
772
+ // If selection spans multiple lines, use fenced code block
773
+ if (selected.includes('\n')) {
774
+ const replacement = '```\n' + selected + '\n```';
775
+ textarea.value = val.substring(0, start) + replacement + val.substring(end);
776
+ textarea.selectionStart = start + 4;
777
+ textarea.selectionEnd = start + 4 + selected.length;
778
+ }
779
+ else {
780
+ wrapSelection('`', '`');
781
+ return; // wrapSelection already calls triggerChange
782
+ }
783
+ triggerChange();
784
+ }
785
+ function insertText(text) {
786
+ if (destroyed)
787
+ return;
788
+ textarea.focus();
789
+ const start = textarea.selectionStart;
790
+ const end = textarea.selectionEnd;
791
+ const val = textarea.value;
792
+ textarea.value = val.substring(0, start) + text + val.substring(end);
793
+ textarea.selectionStart = start + text.length;
794
+ textarea.selectionEnd = start + text.length;
795
+ triggerChange();
796
+ }
797
+ function triggerChange() {
798
+ updateLineNumbers();
799
+ schedulePreview();
800
+ onChange?.(textarea.value);
801
+ }
802
+ // ── Event Handlers ────────────────────────────────────────────────
803
+ on(textarea, 'input', () => {
804
+ triggerChange();
805
+ });
806
+ on(textarea, 'scroll', () => {
807
+ syncGutterScroll();
808
+ });
809
+ // Tab key: insert 2 spaces
810
+ on(textarea, 'keydown', ((e) => {
811
+ if (e.key === 'Tab') {
812
+ e.preventDefault();
813
+ insertText(' ');
814
+ return;
815
+ }
816
+ // Enter: auto-continue lists
817
+ if (e.key === 'Enter') {
818
+ const pos = textarea.selectionStart;
819
+ const line = getCurrentLine(pos);
820
+ // Unordered list continuation
821
+ const ulMatch = line.match(/^(\s*[-*+]\s)/);
822
+ if (ulMatch) {
823
+ // If the line is just the prefix (empty item), remove it and break out
824
+ if (line.trim() === '-' || line.trim() === '*' || line.trim() === '+') {
825
+ e.preventDefault();
826
+ const lineStart = getLineStart(pos);
827
+ const lineEnd = textarea.value.indexOf('\n', pos);
828
+ const end = lineEnd === -1 ? textarea.value.length : lineEnd;
829
+ textarea.value = textarea.value.substring(0, lineStart) + textarea.value.substring(end);
830
+ textarea.selectionStart = lineStart;
831
+ textarea.selectionEnd = lineStart;
832
+ triggerChange();
833
+ return;
834
+ }
835
+ e.preventDefault();
836
+ insertText('\n' + ulMatch[1]);
837
+ return;
838
+ }
839
+ // Ordered list continuation
840
+ const olMatch = line.match(/^(\s*)(\d+)\.\s/);
841
+ if (olMatch) {
842
+ // If the line is just the number (empty item), remove it and break out
843
+ if (line.trim().match(/^\d+\.$/)) {
844
+ e.preventDefault();
845
+ const lineStart = getLineStart(pos);
846
+ const lineEnd = textarea.value.indexOf('\n', pos);
847
+ const end = lineEnd === -1 ? textarea.value.length : lineEnd;
848
+ textarea.value = textarea.value.substring(0, lineStart) + textarea.value.substring(end);
849
+ textarea.selectionStart = lineStart;
850
+ textarea.selectionEnd = lineStart;
851
+ triggerChange();
852
+ return;
853
+ }
854
+ e.preventDefault();
855
+ const nextNum = parseInt(olMatch[2], 10) + 1;
856
+ insertText('\n' + olMatch[1] + nextNum + '. ');
857
+ return;
858
+ }
859
+ }
860
+ }));
861
+ // Keyboard shortcuts
862
+ on(textarea, 'keydown', ((e) => {
863
+ const mod = e.metaKey || e.ctrlKey;
864
+ if (!mod)
865
+ return;
866
+ if (e.key === 'b') {
867
+ e.preventDefault();
868
+ wrapSelection('**', '**');
869
+ }
870
+ else if (e.key === 'i') {
871
+ e.preventDefault();
872
+ wrapSelection('*', '*');
873
+ }
874
+ else if (e.key === 'k') {
875
+ e.preventDefault();
876
+ insertLink();
877
+ }
878
+ else if (e.key === 's') {
879
+ e.preventDefault();
880
+ onSave?.(textarea.value);
881
+ }
882
+ else if (e.key === 'p' && e.shiftKey) {
883
+ e.preventDefault();
884
+ setMode(currentMode === 'preview' ? 'write' : 'preview');
885
+ }
886
+ }));
887
+ // Also handle Ctrl+Shift+P and Ctrl+S from anywhere in the editor
888
+ on(root, 'keydown', ((e) => {
889
+ const mod = e.metaKey || e.ctrlKey;
890
+ if (!mod)
891
+ return;
892
+ if (e.key === 'p' && e.shiftKey) {
893
+ e.preventDefault();
894
+ setMode(currentMode === 'preview' ? 'write' : 'preview');
895
+ }
896
+ if (e.key === 's' && !e.shiftKey) {
897
+ e.preventDefault();
898
+ onSave?.(textarea.value);
899
+ }
900
+ }));
901
+ // ── Mode Management ───────────────────────────────────────────────
902
+ function setMode(mode) {
903
+ if (destroyed)
904
+ return;
905
+ currentMode = mode;
906
+ applyModeClass();
907
+ updateModeButtons();
908
+ updatePreview();
909
+ updateLineNumbers();
910
+ }
911
+ // ── Initial Render ────────────────────────────────────────────────
912
+ updatePreview();
913
+ updateLineNumbers();
914
+ // ── Public Instance ───────────────────────────────────────────────
915
+ const instance = {
916
+ getValue() {
917
+ return textarea.value;
918
+ },
919
+ setValue(value) {
920
+ if (destroyed)
921
+ return;
922
+ textarea.value = value;
923
+ triggerChange();
924
+ },
925
+ getMode() {
926
+ return currentMode;
927
+ },
928
+ setMode,
929
+ focus() {
930
+ if (destroyed)
931
+ return;
932
+ textarea.focus();
933
+ },
934
+ insertText(text) {
935
+ insertText(text);
936
+ },
937
+ wrapSelection(before, after) {
938
+ wrapSelection(before, after);
939
+ },
940
+ destroy() {
941
+ if (destroyed)
942
+ return;
943
+ destroyed = true;
944
+ if (debounceTimer !== undefined)
945
+ clearTimeout(debounceTimer);
946
+ for (const [target, evt, fn, capture] of listeners) {
947
+ target.removeEventListener(evt, fn, capture);
948
+ }
949
+ listeners.length = 0;
950
+ root.remove();
951
+ },
952
+ };
953
+ return instance;
954
+ }
955
+ //# sourceMappingURL=editor.js.map