domma-cms 0.55.1 → 0.69.3

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 (305) hide show
  1. package/CLAUDE.md +194 -3
  2. package/README.md +18 -14
  3. package/admin/css/admin.css +1 -1
  4. package/admin/css/contacts.css +1 -0
  5. package/admin/dist/domma/domma-tools.css +3 -3
  6. package/admin/dist/domma/domma-tools.min.js +3 -3
  7. package/admin/index.html +1 -0
  8. package/admin/js/api.js +1 -1
  9. package/admin/js/app.js +4 -4
  10. package/admin/js/lib/analytics-shape.js +1 -0
  11. package/admin/js/lib/contacts-arrange.js +1 -0
  12. package/admin/js/lib/help-popover.js +1 -0
  13. package/admin/js/lib/notes-arrange.js +1 -0
  14. package/admin/js/lib/page-picker.js +1 -0
  15. package/admin/js/lib/plugin-accent.js +1 -0
  16. package/admin/js/lib/plugin-chrome.js +1 -0
  17. package/admin/js/lib/shortcode-context-menu.js +2 -2
  18. package/admin/js/lib/sidebar-grouping.js +1 -1
  19. package/admin/js/lib/sidebar-grouping.test.js +1 -1
  20. package/admin/js/lib/sidebar-renderer.js +4 -4
  21. package/admin/js/lib/slideover-resizable.js +1 -0
  22. package/admin/js/lib/todo-arrange.js +1 -0
  23. package/admin/js/lib/tool-kit.js +1 -0
  24. package/admin/js/templates/analytics.html +138 -0
  25. package/admin/js/templates/contacts.html +156 -0
  26. package/admin/js/templates/context-menu-editor.html +212 -0
  27. package/admin/js/templates/context-menus.html +16 -0
  28. package/admin/js/templates/notes.html +137 -0
  29. package/admin/js/templates/plugin-code.html +1 -1
  30. package/admin/js/templates/plugin-marketplace.html +17 -4
  31. package/admin/js/templates/plugins.html +13 -7
  32. package/{plugins/site-search/admin/templates/site-search.html → admin/js/templates/search.html} +50 -4
  33. package/admin/js/templates/settings.html +16 -97
  34. package/admin/js/templates/theme.html +211 -0
  35. package/admin/js/templates/todo.html +113 -0
  36. package/admin/js/views/analytics.js +62 -0
  37. package/admin/js/views/contacts.js +174 -0
  38. package/admin/js/views/context-menu-editor.js +55 -0
  39. package/admin/js/views/context-menus.js +5 -0
  40. package/admin/js/views/form-editor.js +7 -7
  41. package/admin/js/views/index.js +1 -1
  42. package/admin/js/views/notes.js +104 -0
  43. package/admin/js/views/plugin-marketplace.js +1 -1
  44. package/admin/js/views/plugins.js +28 -24
  45. package/admin/js/views/search.js +1 -0
  46. package/admin/js/views/settings.js +3 -3
  47. package/admin/js/views/theme.js +1 -0
  48. package/admin/js/views/todo.js +96 -0
  49. package/bin/cli.js +6 -10
  50. package/bin/lib/plugin-version.js +28 -0
  51. package/bin/update.js +27 -2
  52. package/config/menus/admin-sidebar.json +129 -23
  53. package/config/plugins.json +5 -5
  54. package/config/search.json +13 -0
  55. package/config/server.json +3 -1
  56. package/package.json +14 -10
  57. package/plugins/_lib/admin/mail/compose-window.js +914 -0
  58. package/plugins/_lib/admin/mail/contacts.js +301 -0
  59. package/plugins/_lib/admin/mail/diagnostics-section.js +133 -0
  60. package/plugins/_lib/admin/mail/folder-tree.js +254 -0
  61. package/plugins/_lib/admin/mail/identity.js +480 -0
  62. package/plugins/_lib/admin/mail/image-senders-section.js +131 -0
  63. package/plugins/_lib/admin/mail/keyboard.js +136 -0
  64. package/plugins/_lib/admin/mail/mail.css +1 -0
  65. package/plugins/_lib/admin/mail/mail.html +71 -0
  66. package/plugins/_lib/admin/mail/panes.js +253 -0
  67. package/plugins/_lib/admin/mail/reader-view.js +4473 -0
  68. package/plugins/_lib/admin/mail/resizable.js +26 -0
  69. package/plugins/_lib/admin/mail/rules.js +343 -0
  70. package/plugins/_lib/admin/mail/scheduling.js +203 -0
  71. package/plugins/_lib/admin/mail/section-kit.js +277 -0
  72. package/plugins/_lib/admin/mail/templates.js +238 -0
  73. package/plugins/_lib/admin/mail/threads.js +200 -0
  74. package/plugins/_lib/admin/mail/vacation.js +269 -0
  75. package/plugins/_lib/admin/ui/help.css +1 -0
  76. package/plugins/_lib/admin/ui/help.js +174 -0
  77. package/plugins/_lib/admin/ui/resizable.js +151 -0
  78. package/plugins/_lib/dataStore.js +117 -0
  79. package/plugins/_lib/mail/accounts.js +919 -0
  80. package/plugins/_lib/mail/bodyTokens.js +101 -0
  81. package/plugins/_lib/mail/compose.js +256 -0
  82. package/plugins/_lib/mail/defaults.js +57 -0
  83. package/plugins/_lib/mail/diagnostics.js +154 -0
  84. package/plugins/_lib/mail/envelope.js +161 -0
  85. package/plugins/_lib/mail/folders.js +192 -0
  86. package/plugins/_lib/mail/handoff.js +274 -0
  87. package/plugins/_lib/mail/imapPool.js +293 -0
  88. package/plugins/_lib/mail/mbox.js +74 -0
  89. package/plugins/_lib/mail/pollSchedule.js +79 -0
  90. package/plugins/_lib/mail/poller.js +135 -0
  91. package/plugins/_lib/mail/priority.js +138 -0
  92. package/plugins/_lib/mail/readRoutes.js +680 -0
  93. package/plugins/_lib/mail/render.js +291 -0
  94. package/plugins/_lib/mail/ruleRunner.js +152 -0
  95. package/plugins/_lib/mail/scheduler.js +254 -0
  96. package/plugins/_lib/mail/secretbox.js +229 -0
  97. package/plugins/_lib/mail/send.js +396 -0
  98. package/plugins/_lib/mail/store.js +1022 -0
  99. package/plugins/_lib/mail/sync.js +292 -0
  100. package/plugins/_lib/mail/syncPlan.js +126 -0
  101. package/plugins/_lib/mail/syncSelection.js +82 -0
  102. package/plugins/_lib/mail/unsubscribe.js +183 -0
  103. package/plugins/_lib/mail/vacationRunner.js +114 -0
  104. package/plugins/_lib/mail/write.js +473 -0
  105. package/plugins/_lib/schemaSync.js +83 -0
  106. package/plugins/_template/admin/css/index.css +0 -0
  107. package/plugins/_template/admin/templates/index.html +4 -4
  108. package/plugins/_template/admin/views/index.js +7 -0
  109. package/plugins/blog/CLAUDE.md +237 -0
  110. package/plugins/blog/admin/css/index.css +178 -0
  111. package/plugins/blog/admin/templates/blog.html +35 -41
  112. package/plugins/blog/admin/templates/categories.html +19 -6
  113. package/plugins/blog/admin/templates/comments.html +26 -9
  114. package/plugins/blog/admin/templates/post-editor.html +63 -40
  115. package/plugins/blog/admin/templates/settings.html +3 -9
  116. package/plugins/blog/admin/views/blog.js +288 -159
  117. package/plugins/blog/admin/views/categories.js +212 -206
  118. package/plugins/blog/admin/views/comments.js +207 -158
  119. package/plugins/blog/admin/views/kit.js +372 -0
  120. package/plugins/blog/admin/views/post-editor.js +306 -39
  121. package/plugins/blog/admin/views/settings-cog.js +56 -0
  122. package/plugins/blog/admin/views/settings.js +16 -84
  123. package/plugins/blog/blocks/blog-card-compact.css +17 -0
  124. package/plugins/blog/blocks/blog-card-compact.html +8 -0
  125. package/plugins/blog/blocks/blog-card-row.css +22 -0
  126. package/plugins/blog/blocks/blog-card-row.html +11 -0
  127. package/plugins/blog/blocks/blog-card.css +35 -0
  128. package/plugins/blog/blocks/blog-card.html +16 -0
  129. package/plugins/blog/blocks/blog-post-classic.css +15 -0
  130. package/plugins/blog/blocks/blog-post-classic.html +28 -0
  131. package/plugins/blog/blocks/blog-post-feature.css +20 -0
  132. package/plugins/blog/blocks/blog-post-feature.html +30 -0
  133. package/plugins/blog/blocks/blog-post-minimal.css +10 -0
  134. package/plugins/blog/blocks/blog-post-minimal.html +23 -0
  135. package/plugins/blog/blocks/blog-post-sidebar.css +22 -0
  136. package/plugins/blog/blocks/blog-post-sidebar.html +36 -0
  137. package/plugins/blog/collections/categories/schema.json +7 -6
  138. package/plugins/blog/collections/comments/schema.json +11 -10
  139. package/plugins/blog/collections/posts/schema.json +16 -13
  140. package/plugins/blog/config.js +6 -0
  141. package/plugins/blog/lib/layouts.js +304 -0
  142. package/plugins/blog/lib/page.js +158 -0
  143. package/plugins/blog/lib/render.js +119 -0
  144. package/plugins/blog/lib/samples.js +234 -0
  145. package/plugins/blog/plugin.js +259 -26
  146. package/plugins/blog/plugin.json +21 -73
  147. package/plugins/blog/plugin.public.js +217 -0
  148. package/plugins/blog/public/blog.css +177 -0
  149. package/plugins/blog/public/blog.js +586 -0
  150. package/plugins/blog/public/samples/aurora.svg +7 -0
  151. package/plugins/blog/public/samples/dusk.svg +7 -0
  152. package/plugins/blog/public/samples/ember.svg +7 -0
  153. package/plugins/blog/public/samples/harbour.svg +7 -0
  154. package/plugins/blog/public/samples/meadow.svg +7 -0
  155. package/plugins/blog/public/samples/rosewood.svg +7 -0
  156. package/plugins/blog/templates/index.html +9 -8
  157. package/plugins/blog/templates/post.html +4 -17
  158. package/plugins/blog/tests/layouts.test.js +97 -0
  159. package/plugins/blog/tests/public.test.js +71 -0
  160. package/plugins/free-tier.lock.json +74 -0
  161. package/plugins/mail-reader/CLAUDE.md +78 -0
  162. package/plugins/mail-reader/admin/views/mail.js +19 -0
  163. package/plugins/mail-reader/config.js +7 -0
  164. package/plugins/mail-reader/plugin.js +48 -0
  165. package/plugins/mail-reader/plugin.json +34 -0
  166. package/public/css/forms.css +1 -1
  167. package/public/css/search.css +1 -0
  168. package/public/css/site.css +1 -1
  169. package/public/css/theme-switcher.css +1 -0
  170. package/public/js/analytics.js +1 -0
  171. package/public/js/collection-context.js +2 -2
  172. package/public/js/context-menus.js +1 -0
  173. package/public/js/form-logic-engine.js +1 -1
  174. package/public/js/forms.js +2 -2
  175. package/public/js/search.js +1 -0
  176. package/public/js/site.js +1 -1
  177. package/public/js/theme-switcher.js +1 -0
  178. package/scripts/build.js +77 -4
  179. package/scripts/copy-domma.js +48 -0
  180. package/scripts/run-tests.mjs +92 -0
  181. package/scripts/seed.js +1996 -0
  182. package/{plugins/analytics/plugin.js → server/routes/api/analytics.js} +89 -41
  183. package/server/routes/api/collections.js +34 -0
  184. package/server/routes/api/contacts.js +506 -0
  185. package/server/routes/api/context-menus.js +104 -0
  186. package/server/routes/api/dashboard.js +8 -2
  187. package/server/routes/api/forms.js +42 -3
  188. package/{plugins/notes/plugin.js → server/routes/api/notes.js} +61 -17
  189. package/server/routes/api/notifications.js +69 -19
  190. package/server/routes/api/plugin-marketplace.js +78 -23
  191. package/server/routes/api/plugins.js +198 -9
  192. package/server/routes/api/search.js +43 -0
  193. package/server/routes/api/theme.js +69 -0
  194. package/server/routes/api/todo.js +178 -0
  195. package/server/routes/public.js +42 -7
  196. package/server/server.js +135 -1
  197. package/server/services/adapters/FileAdapter.js +6 -1
  198. package/server/services/collections.js +13 -3
  199. package/server/services/content.js +26 -0
  200. package/server/services/contextMenus.js +477 -0
  201. package/server/services/managerClient.js +72 -136
  202. package/server/services/markdown.js +124 -10
  203. package/server/services/permissionRegistry.js +74 -0
  204. package/server/services/pluginEntitlement.js +171 -0
  205. package/server/services/pluginEnvelope.js +242 -0
  206. package/server/services/pluginFiles.js +52 -11
  207. package/server/services/pluginInstaller.js +246 -26
  208. package/server/services/pluginScaffold.js +5 -0
  209. package/server/services/plugins.js +340 -7
  210. package/server/services/presetCollections.js +344 -0
  211. package/server/services/promoted-plugins-migration.js +254 -0
  212. package/server/services/publicCors.js +46 -0
  213. package/server/services/renderer.js +224 -22
  214. package/server/services/roles.js +1 -1
  215. package/server/services/search-migration.js +82 -0
  216. package/server/services/search.js +413 -0
  217. package/server/services/sidebar-migration.js +1 -0
  218. package/server/services/sidebarBadges.js +37 -0
  219. package/server/services/siteGitignore.js +307 -0
  220. package/server/services/themeSettings.js +580 -0
  221. package/server/services/users.js +8 -0
  222. package/server/templates/page.html +4 -2
  223. package/plugins/analytics/admin/templates/analytics.html +0 -61
  224. package/plugins/analytics/admin/views/analytics.js +0 -176
  225. package/plugins/analytics/config.js +0 -14
  226. package/plugins/analytics/plugin.json +0 -57
  227. package/plugins/analytics/public/inject-body.html +0 -60
  228. package/plugins/analytics/public/inject-head.html +0 -1
  229. package/plugins/blog/templates/author.html +0 -9
  230. package/plugins/blog/templates/category.html +0 -9
  231. package/plugins/blog/templates/tag.html +0 -9
  232. package/plugins/contacts/admin/templates/contacts.html +0 -126
  233. package/plugins/contacts/admin/views/contacts.js +0 -710
  234. package/plugins/contacts/collections/user-contact-groups/schema.json +0 -35
  235. package/plugins/contacts/collections/user-contacts/schema.json +0 -71
  236. package/plugins/contacts/config.js +0 -6
  237. package/plugins/contacts/data/contacts.json +0 -20
  238. package/plugins/contacts/plugin.js +0 -291
  239. package/plugins/contacts/plugin.json +0 -33
  240. package/plugins/demo-viewer/config.js +0 -4
  241. package/plugins/demo-viewer/plugin.js +0 -36
  242. package/plugins/demo-viewer/plugin.json +0 -9
  243. package/plugins/invoice/admin/templates/editor.html +0 -129
  244. package/plugins/invoice/admin/templates/index.html +0 -43
  245. package/plugins/invoice/admin/templates/issuers.html +0 -5
  246. package/plugins/invoice/admin/templates/receivers.html +0 -5
  247. package/plugins/invoice/admin/views/editor.js +0 -267
  248. package/plugins/invoice/admin/views/index.js +0 -155
  249. package/plugins/invoice/admin/views/issuers.js +0 -23
  250. package/plugins/invoice/admin/views/party-view.js +0 -148
  251. package/plugins/invoice/admin/views/receivers.js +0 -22
  252. package/plugins/invoice/collections/invoice-issuers/schema.json +0 -16
  253. package/plugins/invoice/collections/invoice-receivers/schema.json +0 -15
  254. package/plugins/invoice/collections/invoices/schema.json +0 -27
  255. package/plugins/invoice/config.js +0 -16
  256. package/plugins/invoice/plugin.js +0 -283
  257. package/plugins/invoice/plugin.json +0 -85
  258. package/plugins/invoice/templates/invoice-print.html +0 -213
  259. package/plugins/notes/admin/templates/notes.html +0 -83
  260. package/plugins/notes/admin/views/notes.js +0 -281
  261. package/plugins/notes/collections/user-notes/schema.json +0 -54
  262. package/plugins/notes/config.js +0 -6
  263. package/plugins/notes/data/notes.json +0 -1
  264. package/plugins/notes/plugin.json +0 -33
  265. package/plugins/site-search/admin/views/site-search.js +0 -116
  266. package/plugins/site-search/config.js +0 -15
  267. package/plugins/site-search/plugin.js +0 -188
  268. package/plugins/site-search/plugin.json +0 -40
  269. package/plugins/site-search/public/inject-body.html +0 -17
  270. package/plugins/site-search/public/inject-head.html +0 -1
  271. package/plugins/site-search/public/search.css +0 -1
  272. package/plugins/site-search/public/search.js +0 -1
  273. package/plugins/surveys/admin/templates/audience.html +0 -47
  274. package/plugins/surveys/admin/templates/results.html +0 -58
  275. package/plugins/surveys/admin/templates/survey-editor.html +0 -141
  276. package/plugins/surveys/admin/templates/surveys.html +0 -25
  277. package/plugins/surveys/admin/views/audience.js +0 -301
  278. package/plugins/surveys/admin/views/results.js +0 -172
  279. package/plugins/surveys/admin/views/survey-editor.js +0 -211
  280. package/plugins/surveys/admin/views/surveys.js +0 -161
  281. package/plugins/surveys/collections/survey-contacts/schema.json +0 -13
  282. package/plugins/surveys/collections/survey-groups/schema.json +0 -11
  283. package/plugins/surveys/collections/survey-invites/schema.json +0 -16
  284. package/plugins/surveys/collections/surveys/schema.json +0 -23
  285. package/plugins/surveys/config.js +0 -8
  286. package/plugins/surveys/plugin.js +0 -174
  287. package/plugins/surveys/plugin.json +0 -65
  288. package/plugins/surveys/public/dist-shared.mjs +0 -1
  289. package/plugins/surveys/public/survey.css +0 -1
  290. package/plugins/surveys/public/survey.mjs +0 -1
  291. package/plugins/surveys/templates/survey-page.html +0 -63
  292. package/plugins/theme-switcher/admin/templates/theme-switcher.html +0 -86
  293. package/plugins/theme-switcher/admin/views/theme-switcher.js +0 -65
  294. package/plugins/theme-switcher/config.js +0 -10
  295. package/plugins/theme-switcher/plugin.js +0 -26
  296. package/plugins/theme-switcher/plugin.json +0 -74
  297. package/plugins/theme-switcher/public/inject-body.html +0 -153
  298. package/plugins/theme-switcher/public/inject-head.html +0 -260
  299. package/plugins/todo/admin/templates/todo.html +0 -158
  300. package/plugins/todo/admin/views/todo.js +0 -343
  301. package/plugins/todo/collections/todos/schema.json +0 -60
  302. package/plugins/todo/config.js +0 -7
  303. package/plugins/todo/data/todos.json +0 -1
  304. package/plugins/todo/plugin.js +0 -102
  305. package/plugins/todo/plugin.json +0 -33
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Making a slideover resizable.
3
+ *
4
+ * Domma's slideover takes a size from a fixed set, which suits a confirmation
5
+ * and not a settings panel - the width someone wants for a table of issuers or
6
+ * a folder list depends on their screen, and no preset is right for everyone.
7
+ * A drag handle on the leading edge, remembered per panel.
8
+ *
9
+ * This started life in `_lib/admin/mail`. It was never about mail, so it lives
10
+ * here and the mail copy is a thin wrapper that supplies its own handle class;
11
+ * the one thing a caller must bring is a stylesheet rule for that class, since
12
+ * a plugin's CSS is the only stylesheet the updater does not replace.
13
+ *
14
+ * @module _lib/admin/ui/resizable
15
+ */
16
+
17
+ /** Never narrower than this, whatever the drag says. */
18
+ const MIN_WIDTH = 360;
19
+
20
+ /**
21
+ * Add a drag handle to a slideover and remember the width it is given.
22
+ *
23
+ * @param {object} slideover - a Domma slideover instance
24
+ * @param {{storageKey: string, defaultWidth?: number, handleClass?: string}} options
25
+ * @returns {() => void} a teardown for the listeners this adds
26
+ */
27
+ export function makeResizable(slideover, {storageKey, defaultWidth = 640, handleClass = 'dm-ui-resizer'}) {
28
+ const panel = slideover?.element;
29
+ if (!panel) return () => {};
30
+
31
+ // A current admin fits EVERY slideover with a handle centrally, at the
32
+ // point E.slideover is called (admin/js/lib/slideover-resizable.js), so by
33
+ // the time a plugin gets here the panel is already resizable - under a key
34
+ // derived from its title. Adding a second handle would put two draggable
35
+ // edges 6px apart and have them fight over the stored width.
36
+ //
37
+ // The panel carries its state on `__dmResize` ({storageKey, defaultWidth,
38
+ // width, apply}), which is the whole contract between the two files: a
39
+ // plugin must not import from `admin/`, since the updater replaces it
40
+ // wholesale and a static import would take the view down on any CMS whose
41
+ // admin predates this. So the key is re-pointed in place instead.
42
+ const central = panel.__dmResize;
43
+ if (central) {
44
+ central.storageKey = storageKey;
45
+ central.defaultWidth = defaultWidth;
46
+ let width = defaultWidth;
47
+ try {
48
+ const stored = Number(S.get(storageKey));
49
+ if (Number.isFinite(stored) && stored >= MIN_WIDTH) width = stored;
50
+ } catch { /* Storage unavailable; the default is serviceable. */ }
51
+ central.width = width;
52
+ central.apply();
53
+ // The central handle outlives this caller and is torn down with the
54
+ // panel, so there is nothing here to undo.
55
+ return () => {};
56
+ }
57
+
58
+ // The panel is positioned from the right, so dragging its left edge
59
+ // leftwards makes it wider - the delta is inverted against the pointer.
60
+ let width = defaultWidth;
61
+ try {
62
+ const stored = Number(S.get(storageKey));
63
+ if (Number.isFinite(stored) && stored >= MIN_WIDTH) width = stored;
64
+ } catch {
65
+ // Storage unavailable; the default is serviceable.
66
+ }
67
+
68
+ /**
69
+ * @param {number} value
70
+ * @returns {number}
71
+ */
72
+ const clamp = value => Math.min(
73
+ Math.max(MIN_WIDTH, Math.round(value)),
74
+ Math.max(MIN_WIDTH, window.innerWidth - 80)
75
+ );
76
+
77
+ // admin.css sizes every slideover with `width: … !important`, so an
78
+ // ordinary inline style loses the cascade - and `admin/` is replaced
79
+ // wholesale by the updater, which makes an important inline the only door.
80
+ const apply = () => {
81
+ panel.style.setProperty('width', `${width}px`, 'important');
82
+ panel.style.setProperty('min-width', `${MIN_WIDTH}px`, 'important');
83
+ };
84
+ apply();
85
+
86
+ // A span, not a div: admin.css pads every direct DIV child of a slideover
87
+ // with !important at a specificity this file cannot outrank, which would
88
+ // inflate a 6px handle into a 48px bar.
89
+ const handle = document.createElement('span');
90
+ handle.className = handleClass;
91
+ handle.setAttribute('role', 'separator');
92
+ handle.setAttribute('aria-orientation', 'vertical');
93
+ handle.title = 'Drag to resize · double-click to reset';
94
+ panel.appendChild(handle);
95
+
96
+ const onDown = event => {
97
+ event.preventDefault();
98
+ const startX = event.clientX;
99
+ const startWidth = width;
100
+ handle.setPointerCapture?.(event.pointerId);
101
+ panel.classList.add('is-resizing');
102
+
103
+ const move = moveEvent => {
104
+ width = clamp(startWidth - (moveEvent.clientX - startX));
105
+ apply();
106
+ };
107
+ const end = () => {
108
+ handle.removeEventListener('pointermove', move);
109
+ handle.removeEventListener('pointerup', end);
110
+ handle.removeEventListener('pointercancel', end);
111
+ panel.classList.remove('is-resizing');
112
+ try {
113
+ S.set(storageKey, width);
114
+ } catch {
115
+ // The drag still applies for this panel.
116
+ }
117
+ };
118
+
119
+ handle.addEventListener('pointermove', move);
120
+ handle.addEventListener('pointerup', end);
121
+ handle.addEventListener('pointercancel', end);
122
+ };
123
+
124
+ const onReset = () => {
125
+ width = defaultWidth;
126
+ apply();
127
+ try {
128
+ S.set(storageKey, width);
129
+ } catch { /* nothing to do */ }
130
+ };
131
+
132
+ const onWindowResize = () => {
133
+ // A window narrower than the remembered width would push the panel off
134
+ // screen; the clamp keeps it reachable without forgetting the
135
+ // preference for when the window grows again.
136
+ panel.style.setProperty('width', `${clamp(width)}px`, 'important');
137
+ };
138
+
139
+ handle.addEventListener('pointerdown', onDown);
140
+ handle.addEventListener('dblclick', onReset);
141
+ window.addEventListener('resize', onWindowResize);
142
+
143
+ return () => {
144
+ panel.style.removeProperty('width');
145
+ panel.style.removeProperty('min-width');
146
+ handle.removeEventListener('pointerdown', onDown);
147
+ handle.removeEventListener('dblclick', onReset);
148
+ window.removeEventListener('resize', onWindowResize);
149
+ handle.remove();
150
+ };
151
+ }
@@ -0,0 +1,117 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Creates a JSON file-based data store.
6
+ *
7
+ * @param {string} filePath - Absolute path to the JSON file
8
+ * @returns {{ read: Function, write: Function, readArray: Function, appendItem: Function, updateItem: Function, deleteItem: Function }}
9
+ */
10
+ export function createStore(filePath) {
11
+ /**
12
+ * Read and parse JSON file.
13
+ * Returns `{}` if file is missing or contains invalid JSON.
14
+ *
15
+ * @returns {Promise<object>}
16
+ */
17
+ async function read() {
18
+ try {
19
+ const raw = await fs.readFile(filePath, 'utf8');
20
+ return JSON.parse(raw);
21
+ } catch {
22
+ return {};
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Atomically write data as formatted JSON.
28
+ * Writes to a `.tmp` file then renames to ensure atomicity.
29
+ *
30
+ * @param {*} data
31
+ * @returns {Promise<void>}
32
+ */
33
+ async function write(data) {
34
+ const dir = path.dirname(filePath);
35
+ await fs.mkdir(dir, { recursive: true });
36
+ const tmpPath = filePath + '.tmp';
37
+ await fs.writeFile(tmpPath, JSON.stringify(data, null, 2) + '\n', 'utf8');
38
+ await fs.rename(tmpPath, filePath);
39
+ }
40
+
41
+ /**
42
+ * Read and parse JSON file, expecting an array.
43
+ * Returns `[]` if file is missing, invalid JSON, or not an array.
44
+ *
45
+ * @returns {Promise<Array>}
46
+ */
47
+ async function readArray() {
48
+ try {
49
+ const raw = await fs.readFile(filePath, 'utf8');
50
+ const parsed = JSON.parse(raw);
51
+ return Array.isArray(parsed) ? parsed : [];
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Append an item to the array stored in the file.
59
+ *
60
+ * @param {object} item
61
+ * @returns {Promise<Array>} Updated array
62
+ */
63
+ async function appendItem(item) {
64
+ const arr = await readArray();
65
+ arr.push(item);
66
+ await write(arr);
67
+ return arr;
68
+ }
69
+
70
+ /**
71
+ * Update an item in the stored array by its `id` property.
72
+ * Merges `updates` into the matched item.
73
+ *
74
+ * @param {string|number} id
75
+ * @param {object} updates
76
+ * @returns {Promise<object|null>} Updated item, or null if not found
77
+ */
78
+ async function updateItem(id, updates) {
79
+ const arr = await readArray();
80
+ let updated = null;
81
+
82
+ const next = arr.map((item) => {
83
+ if (item.id === id) {
84
+ updated = { ...item, ...updates };
85
+ return updated;
86
+ }
87
+ return item;
88
+ });
89
+
90
+ if (updated === null) {
91
+ return null;
92
+ }
93
+
94
+ await write(next);
95
+ return updated;
96
+ }
97
+
98
+ /**
99
+ * Delete an item from the stored array by its `id` property.
100
+ *
101
+ * @param {string|number} id
102
+ * @returns {Promise<boolean>} True if an item was removed, false if not found
103
+ */
104
+ async function deleteItem(id) {
105
+ const arr = await readArray();
106
+ const next = arr.filter((item) => item.id !== id);
107
+
108
+ if (next.length === arr.length) {
109
+ return false;
110
+ }
111
+
112
+ await write(next);
113
+ return true;
114
+ }
115
+
116
+ return { read, write, readArray, appendItem, updateItem, deleteItem };
117
+ }