@flowerforce/flowerbase 1.7.5 → 1.7.6-beta.1

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 (108) hide show
  1. package/README.md +125 -1
  2. package/dist/auth/controller.d.ts.map +1 -1
  3. package/dist/auth/controller.js +11 -10
  4. package/dist/auth/plugins/jwt.js +1 -1
  5. package/dist/auth/providers/anon-user/controller.js +1 -1
  6. package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
  7. package/dist/auth/providers/custom-function/controller.js +28 -7
  8. package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
  9. package/dist/auth/providers/local-userpass/controller.js +15 -14
  10. package/dist/auth/utils.d.ts +1 -0
  11. package/dist/auth/utils.d.ts.map +1 -1
  12. package/dist/constants.d.ts +11 -0
  13. package/dist/constants.d.ts.map +1 -1
  14. package/dist/constants.js +14 -3
  15. package/dist/features/encryption/interface.d.ts +36 -0
  16. package/dist/features/encryption/interface.d.ts.map +1 -0
  17. package/dist/features/encryption/interface.js +2 -0
  18. package/dist/features/encryption/utils.d.ts +9 -0
  19. package/dist/features/encryption/utils.d.ts.map +1 -0
  20. package/dist/features/encryption/utils.js +34 -0
  21. package/dist/features/rules/utils.d.ts.map +1 -1
  22. package/dist/features/rules/utils.js +1 -11
  23. package/dist/features/triggers/index.d.ts.map +1 -1
  24. package/dist/features/triggers/index.js +5 -1
  25. package/dist/features/triggers/utils.js +3 -3
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +9 -4
  29. package/dist/monitoring/plugin.d.ts.map +1 -1
  30. package/dist/monitoring/plugin.js +31 -0
  31. package/dist/monitoring/routes/users.d.ts.map +1 -1
  32. package/dist/monitoring/routes/users.js +7 -6
  33. package/dist/monitoring/utils.d.ts.map +1 -1
  34. package/dist/monitoring/utils.js +5 -4
  35. package/dist/services/api/index.d.ts +4 -0
  36. package/dist/services/api/index.d.ts.map +1 -1
  37. package/dist/services/api/utils.d.ts +1 -0
  38. package/dist/services/api/utils.d.ts.map +1 -1
  39. package/dist/services/index.d.ts +4 -0
  40. package/dist/services/index.d.ts.map +1 -1
  41. package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
  42. package/dist/services/mongodb-atlas/index.js +9 -7
  43. package/dist/services/mongodb-atlas/model.d.ts +2 -1
  44. package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
  45. package/dist/shared/handleUserDeletion.js +1 -1
  46. package/dist/shared/handleUserRegistration.js +2 -2
  47. package/dist/utils/context/helpers.d.ts +12 -0
  48. package/dist/utils/context/helpers.d.ts.map +1 -1
  49. package/dist/utils/index.d.ts +1 -0
  50. package/dist/utils/index.d.ts.map +1 -1
  51. package/dist/utils/index.js +14 -3
  52. package/dist/utils/initializer/exposeRoutes.js +1 -1
  53. package/dist/utils/initializer/mongodbCSFLE.d.ts +69 -0
  54. package/dist/utils/initializer/mongodbCSFLE.d.ts.map +1 -0
  55. package/dist/utils/initializer/mongodbCSFLE.js +131 -0
  56. package/dist/utils/initializer/registerPlugins.d.ts +5 -1
  57. package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
  58. package/dist/utils/initializer/registerPlugins.js +27 -5
  59. package/dist/utils/rules-matcher/interface.d.ts +5 -1
  60. package/dist/utils/rules-matcher/interface.d.ts.map +1 -1
  61. package/dist/utils/rules-matcher/interface.js +2 -0
  62. package/dist/utils/rules-matcher/utils.d.ts.map +1 -1
  63. package/dist/utils/rules-matcher/utils.js +51 -16
  64. package/package.json +4 -2
  65. package/src/auth/__tests__/controller.test.ts +1 -0
  66. package/src/auth/controller.ts +12 -11
  67. package/src/auth/plugins/jwt.ts +2 -2
  68. package/src/auth/providers/anon-user/__tests__/controller.test.ts +1 -0
  69. package/src/auth/providers/anon-user/controller.ts +2 -2
  70. package/src/auth/providers/custom-function/controller.ts +29 -8
  71. package/src/auth/providers/local-userpass/controller.ts +16 -15
  72. package/src/auth/utils.ts +1 -0
  73. package/src/constants.ts +14 -4
  74. package/src/features/encryption/interface.ts +46 -0
  75. package/src/features/encryption/utils.ts +22 -0
  76. package/src/features/rules/utils.ts +1 -11
  77. package/src/features/triggers/__tests__/index.test.ts +1 -0
  78. package/src/features/triggers/index.ts +6 -2
  79. package/src/features/triggers/utils.ts +4 -4
  80. package/src/index.ts +10 -2
  81. package/src/monitoring/plugin.ts +33 -0
  82. package/src/monitoring/routes/users.ts +8 -7
  83. package/src/monitoring/ui.collections.js +7 -10
  84. package/src/monitoring/ui.css +383 -1
  85. package/src/monitoring/ui.endpoints.js +5 -10
  86. package/src/monitoring/ui.events.js +4 -6
  87. package/src/monitoring/ui.functions.js +64 -71
  88. package/src/monitoring/ui.html +8 -0
  89. package/src/monitoring/ui.js +189 -0
  90. package/src/monitoring/ui.shared.js +239 -3
  91. package/src/monitoring/ui.triggers.js +2 -3
  92. package/src/monitoring/ui.users.js +5 -9
  93. package/src/monitoring/utils.ts +6 -5
  94. package/src/services/mongodb-atlas/index.ts +10 -13
  95. package/src/services/mongodb-atlas/model.ts +3 -1
  96. package/src/shared/handleUserDeletion.ts +2 -2
  97. package/src/shared/handleUserRegistration.ts +3 -3
  98. package/src/types/fastify-raw-body.d.ts +0 -9
  99. package/src/utils/__tests__/mongodbCSFLE.test.ts +105 -0
  100. package/src/utils/__tests__/operators.test.ts +24 -0
  101. package/src/utils/__tests__/rule.test.ts +39 -0
  102. package/src/utils/__tests__/rulesMatcherInterfaces.test.ts +2 -0
  103. package/src/utils/index.ts +12 -1
  104. package/src/utils/initializer/exposeRoutes.ts +2 -2
  105. package/src/utils/initializer/mongodbCSFLE.ts +224 -0
  106. package/src/utils/initializer/registerPlugins.ts +45 -10
  107. package/src/utils/rules-matcher/interface.ts +5 -1
  108. package/src/utils/rules-matcher/utils.ts +78 -32
@@ -15,6 +15,7 @@
15
15
  if (state.functionUserMap === undefined) state.functionUserMap = {};
16
16
  if (state.functionUserQuery === undefined) state.functionUserQuery = '';
17
17
  if (state.__functionUserTimer === undefined) state.__functionUserTimer = null;
18
+ if (state.functionCodeEditor === undefined) state.functionCodeEditor = null;
18
19
 
19
20
  dom.functionList = document.getElementById('functionList');
20
21
  dom.functionSelected = document.getElementById('functionSelected');
@@ -22,7 +23,6 @@
22
23
  dom.functionUserInput = document.getElementById('functionUserInput');
23
24
  dom.functionUserList = document.getElementById('functionUserList');
24
25
  dom.functionRunMode = document.getElementById('functionRunMode');
25
- dom.functionEditor = document.getElementById('functionEditor');
26
26
  dom.functionCode = document.getElementById('functionCode');
27
27
  dom.functionHighlight = document.getElementById('functionHighlight');
28
28
  dom.functionGutter = document.getElementById('functionGutter');
@@ -42,7 +42,6 @@
42
42
  functionUserInput,
43
43
  functionUserList,
44
44
  functionRunMode,
45
- functionEditor,
46
45
  functionCode,
47
46
  functionHighlight,
48
47
  functionGutter,
@@ -59,9 +58,31 @@
59
58
  const { setActiveTab } = helpers;
60
59
 
61
60
  const HISTORY_LIMIT = 30;
61
+ const getCodeEditor = () => state.functionCodeEditor;
62
+
63
+ const getFunctionCodeValue = () => {
64
+ const editor = getCodeEditor();
65
+ if (editor && typeof editor.getValue === 'function') {
66
+ return editor.getValue() || '';
67
+ }
68
+ return functionCode ? (functionCode.value || '') : '';
69
+ };
70
+
71
+ const setFunctionCodeValue = (value) => {
72
+ const next = value || '';
73
+ const editor = getCodeEditor();
74
+ if (editor && typeof editor.setValue === 'function') {
75
+ editor.setValue(next);
76
+ editor.execCommand('goDocStart');
77
+ return;
78
+ }
79
+ if (functionCode) {
80
+ functionCode.value = next;
81
+ }
82
+ };
62
83
 
63
84
  const syncFunctionEditorScroll = () => {
64
- if (!functionCode) return;
85
+ if (!functionCode || getCodeEditor()) return;
65
86
  if (functionHighlight) {
66
87
  functionHighlight.scrollTop = functionCode.scrollTop;
67
88
  functionHighlight.scrollLeft = functionCode.scrollLeft;
@@ -72,6 +93,11 @@
72
93
  };
73
94
 
74
95
  const updateFunctionEditor = () => {
96
+ const editor = getCodeEditor();
97
+ if (editor) {
98
+ if (typeof editor.refresh === 'function') editor.refresh();
99
+ return;
100
+ }
75
101
  if (!functionCode) return;
76
102
  const code = functionCode.value || '';
77
103
  if (functionHighlight) {
@@ -191,10 +217,10 @@
191
217
  };
192
218
 
193
219
  const isFunctionCodeModified = (name) => {
194
- if (!name || !functionCode) return false;
220
+ if (!name) return false;
195
221
  const base = state.functionCodeCache[name];
196
222
  if (typeof base !== 'string') return false;
197
- return functionCode.value !== base;
223
+ return getFunctionCodeValue() !== base;
198
224
  };
199
225
 
200
226
  const updateFunctionModifiedState = () => {
@@ -207,10 +233,10 @@
207
233
  };
208
234
 
209
235
  const loadFunctionCode = async () => {
210
- if (!functionCode) return;
236
+ if (!functionCode && !getCodeEditor()) return;
211
237
  const name = state.selectedFunction;
212
238
  if (!name) {
213
- functionCode.value = '';
239
+ setFunctionCodeValue('');
214
240
  updateFunctionEditor();
215
241
  updateFunctionModifiedState();
216
242
  return;
@@ -219,7 +245,7 @@
219
245
  const data = await api('/functions/' + encodeURIComponent(name));
220
246
  const baseCode = data && data.code ? data.code : '';
221
247
  state.functionCodeCache[name] = baseCode;
222
- functionCode.value = baseCode;
248
+ setFunctionCodeValue(baseCode);
223
249
  updateFunctionEditor();
224
250
  } catch (err) {
225
251
  console.error(err);
@@ -228,8 +254,8 @@
228
254
  };
229
255
 
230
256
  const applyFunctionOverride = (code) => {
231
- if (!functionCode) return;
232
- functionCode.value = code || '';
257
+ if (!functionCode && !getCodeEditor()) return;
258
+ setFunctionCodeValue(code || '');
233
259
  updateFunctionEditor();
234
260
  updateFunctionModifiedState();
235
261
  };
@@ -237,10 +263,8 @@
237
263
  const clearFunctionOverride = () => {
238
264
  const name = state.selectedFunction;
239
265
  if (!name) return;
240
- if (functionCode) {
241
- functionCode.value = state.functionCodeCache[name] || '';
242
- updateFunctionEditor();
243
- }
266
+ setFunctionCodeValue(state.functionCodeCache[name] || '');
267
+ updateFunctionEditor();
244
268
  updateFunctionModifiedState();
245
269
  };
246
270
 
@@ -396,10 +420,34 @@
396
420
  renderFunctionUserOptions(options);
397
421
  };
398
422
 
423
+ const initCodeEditor = () => {
424
+ if (!functionCode) return;
425
+ const codeEditor = typeof window !== 'undefined' ? window.CodeMirror : null;
426
+ if (!codeEditor || typeof codeEditor.fromTextArea !== 'function') return;
427
+ const editor = codeEditor.fromTextArea(functionCode, {
428
+ mode: 'javascript',
429
+ lineNumbers: true,
430
+ lineWrapping: false,
431
+ tabSize: 2,
432
+ indentUnit: 2,
433
+ foldGutter: true,
434
+ gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']
435
+ });
436
+ state.functionCodeEditor = editor;
437
+ const wrapper = editor.getWrapperElement();
438
+ if (wrapper) wrapper.classList.add('monit-code-editor');
439
+ const host = functionCode.closest('.code-editor');
440
+ if (host) host.classList.add('cm-enabled');
441
+ editor.on('change', () => {
442
+ updateFunctionModifiedState();
443
+ });
444
+ };
445
+
399
446
  const init = () => {
400
447
  if (refreshFunctions) refreshFunctions.addEventListener('click', loadFunctions);
448
+ initCodeEditor();
401
449
 
402
- if (functionCode) {
450
+ if (functionCode && !getCodeEditor()) {
403
451
  functionCode.addEventListener('input', () => {
404
452
  updateFunctionEditor();
405
453
  updateFunctionModifiedState();
@@ -407,61 +455,6 @@
407
455
  functionCode.addEventListener('scroll', () => {
408
456
  syncFunctionEditorScroll();
409
457
  });
410
- functionCode.addEventListener('keydown', (event) => {
411
- if (event.key !== 'Tab') return;
412
- event.preventDefault();
413
- const indent = ' ';
414
- const value = functionCode.value || '';
415
- const start = functionCode.selectionStart || 0;
416
- const end = functionCode.selectionEnd || 0;
417
- const hasSelection = start !== end;
418
- const lineStart = value.lastIndexOf('\n', start - 1) + 1;
419
- if (!hasSelection && !event.shiftKey) {
420
- const nextValue = value.slice(0, start) + indent + value.slice(end);
421
- functionCode.value = nextValue;
422
- const cursor = start + indent.length;
423
- functionCode.selectionStart = cursor;
424
- functionCode.selectionEnd = cursor;
425
- updateFunctionEditor();
426
- return;
427
- }
428
-
429
- const lineEndIndex = value.indexOf('\n', end);
430
- const blockEnd = lineEndIndex === -1 ? value.length : lineEndIndex;
431
- const block = value.slice(lineStart, blockEnd);
432
- const lines = block.split('\n');
433
- if (!event.shiftKey) {
434
- const newLines = lines.map((line) => indent + line);
435
- const newBlock = newLines.join('\n');
436
- const nextValue = value.slice(0, lineStart) + newBlock + value.slice(blockEnd);
437
- functionCode.value = nextValue;
438
- functionCode.selectionStart = start + indent.length;
439
- functionCode.selectionEnd = end + indent.length * lines.length;
440
- updateFunctionEditor();
441
- return;
442
- }
443
-
444
- const removedCounts = lines.map((line) => {
445
- if (line.startsWith(indent)) return indent.length;
446
- if (line.startsWith('\t')) return 1;
447
- if (line.startsWith(' ')) return 1;
448
- return 0;
449
- });
450
- const newLines = lines.map((line, index) => {
451
- const remove = removedCounts[index];
452
- return remove > 0 ? line.slice(remove) : line;
453
- });
454
- const newBlock = newLines.join('\n');
455
- const nextValue = value.slice(0, lineStart) + newBlock + value.slice(blockEnd);
456
- functionCode.value = nextValue;
457
- const removedTotal = removedCounts.reduce((acc, count) => acc + count, 0);
458
- const removedFirst = removedCounts[0] || 0;
459
- const nextStart = Math.max(lineStart, start - removedFirst);
460
- const nextEnd = Math.max(nextStart, end - removedTotal);
461
- functionCode.selectionStart = nextStart;
462
- functionCode.selectionEnd = nextEnd;
463
- updateFunctionEditor();
464
- });
465
458
  }
466
459
 
467
460
  if (functionList) {
@@ -606,7 +599,7 @@
606
599
  const userId = selectedUser
607
600
  ? String(selectedUser.id || (selectedUser.auth && selectedUser.auth._id) || '')
608
601
  : fallbackUserId;
609
- const liveCode = functionCode ? functionCode.value || '' : '';
602
+ const liveCode = getFunctionCodeValue();
610
603
  const overrideCode = liveCode.trim() ? liveCode : undefined;
611
604
  const data = await api('/functions/invoke', {
612
605
  method: 'POST',
@@ -5,6 +5,8 @@
5
5
  <meta charset="utf-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
  <title>Flowerbase monit</title>
8
+ <link rel="stylesheet" href="__MONIT_BASE__/vendor/codemirror/codemirror.css" />
9
+ <link rel="stylesheet" href="__MONIT_BASE__/vendor/codemirror/foldgutter.css" />
8
10
  <link rel="stylesheet" href="__MONIT_BASE__/ui.css" />
9
11
  </head>
10
12
 
@@ -441,6 +443,12 @@
441
443
  </section>
442
444
  </div>
443
445
  </main>
446
+ <script src="__MONIT_BASE__/vendor/codemirror/codemirror.js"></script>
447
+ <script src="__MONIT_BASE__/vendor/codemirror/javascript.js"></script>
448
+ <script src="__MONIT_BASE__/vendor/codemirror/foldcode.js"></script>
449
+ <script src="__MONIT_BASE__/vendor/codemirror/foldgutter.js"></script>
450
+ <script src="__MONIT_BASE__/vendor/codemirror/brace-fold.js"></script>
451
+ <script src="__MONIT_BASE__/vendor/codemirror/comment-fold.js"></script>
444
452
  <script src="__MONIT_BASE__/ui.shared.js"></script>
445
453
  <script src="__MONIT_BASE__/ui.events.js"></script>
446
454
  <script src="__MONIT_BASE__/ui.users.js"></script>
@@ -14,6 +14,194 @@
14
14
  const { api } = utils;
15
15
  const { setActiveTab } = helpers;
16
16
 
17
+ const RESIZE_MIN_PANE = 220;
18
+ const RESIZE_MIN_STACK = 140;
19
+ const RESIZE_MIN_RATIO = 0.2;
20
+ const RESIZE_MAX_RATIO = 0.8;
21
+
22
+ const getSplitDefault = (container) => {
23
+ if (container.classList.contains('functions-grid')) return '30%';
24
+ if (container.classList.contains('triggers-grid')) return '30%';
25
+ if (container.classList.contains('collections-grid')) return '30%';
26
+ return '66%';
27
+ };
28
+
29
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
30
+
31
+ const getHorizontalResizeBounds = (containerWidth, handleWidth) => {
32
+ const minByPixels = RESIZE_MIN_PANE;
33
+ const maxByPixels = Math.max(minByPixels, containerWidth - RESIZE_MIN_PANE - handleWidth);
34
+ const minByRatio = containerWidth * RESIZE_MIN_RATIO;
35
+ const maxByRatio = containerWidth * RESIZE_MAX_RATIO;
36
+ const min = Math.max(minByPixels, minByRatio);
37
+ const max = Math.min(maxByPixels, maxByRatio);
38
+ if (max < min) return { min: max, max };
39
+ return {
40
+ min,
41
+ max
42
+ };
43
+ };
44
+
45
+ const getVerticalResizeBounds = (totalHeight) => {
46
+ const minByPixels = RESIZE_MIN_STACK;
47
+ const maxByPixels = Math.max(minByPixels, totalHeight - RESIZE_MIN_STACK);
48
+ const minByRatio = totalHeight * RESIZE_MIN_RATIO;
49
+ const maxByRatio = totalHeight * RESIZE_MAX_RATIO;
50
+ const min = Math.max(minByPixels, minByRatio);
51
+ const max = Math.min(maxByPixels, maxByRatio);
52
+ if (max < min) return { min: max, max };
53
+ return {
54
+ min,
55
+ max
56
+ };
57
+ };
58
+
59
+ const bindSplitResize = (container, handle, leftPane, rightPane) => {
60
+ const onPointerDown = (event) => {
61
+ if (event.button !== 0) return;
62
+ event.preventDefault();
63
+
64
+ document.body.classList.add('is-resizing');
65
+ if (handle.setPointerCapture) handle.setPointerCapture(event.pointerId);
66
+
67
+ const onPointerMove = (moveEvent) => {
68
+ const bounds = container.getBoundingClientRect();
69
+ const handleWidth = handle.getBoundingClientRect().width || 10;
70
+ if (!bounds.width) return;
71
+
72
+ const limits = getHorizontalResizeBounds(bounds.width, handleWidth);
73
+ const offsetX = moveEvent.clientX - bounds.left - (handleWidth / 2);
74
+ const nextLeft = clamp(offsetX, limits.min, limits.max);
75
+ container.style.setProperty('--split-left-size', nextLeft + 'px');
76
+ };
77
+
78
+ const onPointerUp = (upEvent) => {
79
+ document.body.classList.remove('is-resizing');
80
+ if (handle.releasePointerCapture) {
81
+ try {
82
+ handle.releasePointerCapture(upEvent.pointerId);
83
+ } catch (err) {
84
+ // no-op: pointer might already be released
85
+ }
86
+ }
87
+ window.removeEventListener('pointermove', onPointerMove);
88
+ window.removeEventListener('pointerup', onPointerUp);
89
+ };
90
+
91
+ window.addEventListener('pointermove', onPointerMove);
92
+ window.addEventListener('pointerup', onPointerUp);
93
+ };
94
+
95
+ handle.addEventListener('pointerdown', onPointerDown);
96
+ handle.addEventListener('keydown', (event) => {
97
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
98
+ event.preventDefault();
99
+ const direction = event.key === 'ArrowLeft' ? -1 : 1;
100
+ const bounds = container.getBoundingClientRect();
101
+ const handleWidth = handle.getBoundingClientRect().width || 10;
102
+ const current = leftPane.getBoundingClientRect().width;
103
+ const limits = getHorizontalResizeBounds(bounds.width, handleWidth);
104
+ const nextLeft = clamp(current + (direction * 24), limits.min, limits.max);
105
+ container.style.setProperty('--split-left-size', nextLeft + 'px');
106
+ rightPane.offsetHeight;
107
+ });
108
+ };
109
+
110
+ const bindStackResize = (handle, topPane, bottomPane) => {
111
+ const onPointerDown = (event) => {
112
+ if (event.button !== 0) return;
113
+ event.preventDefault();
114
+
115
+ document.body.classList.add('is-resizing');
116
+ if (handle.setPointerCapture) handle.setPointerCapture(event.pointerId);
117
+
118
+ const topStart = topPane.getBoundingClientRect().height;
119
+ const bottomStart = bottomPane.getBoundingClientRect().height;
120
+ const total = topStart + bottomStart;
121
+
122
+ const onPointerMove = (moveEvent) => {
123
+ const topBounds = topPane.getBoundingClientRect();
124
+ const positionY = moveEvent.clientY - topBounds.top;
125
+ const handleHalf = (handle.getBoundingClientRect().height || 8) / 2;
126
+ const limits = getVerticalResizeBounds(total);
127
+ const boundedTop = clamp(positionY - handleHalf, limits.min, limits.max);
128
+ const boundedBottom = Math.max(limits.min, total - boundedTop);
129
+
130
+ topPane.style.flex = '0 0 ' + boundedTop + 'px';
131
+ bottomPane.style.flex = '0 0 ' + boundedBottom + 'px';
132
+ };
133
+
134
+ const onPointerUp = (upEvent) => {
135
+ document.body.classList.remove('is-resizing');
136
+ if (handle.releasePointerCapture) {
137
+ try {
138
+ handle.releasePointerCapture(upEvent.pointerId);
139
+ } catch (err) {
140
+ // no-op: pointer might already be released
141
+ }
142
+ }
143
+ window.removeEventListener('pointermove', onPointerMove);
144
+ window.removeEventListener('pointerup', onPointerUp);
145
+ };
146
+
147
+ window.addEventListener('pointermove', onPointerMove);
148
+ window.addEventListener('pointerup', onPointerUp);
149
+ };
150
+
151
+ handle.addEventListener('pointerdown', onPointerDown);
152
+ };
153
+
154
+ const initResizableSections = () => {
155
+ document.querySelectorAll('.split-grid').forEach((container) => {
156
+ if (container.dataset.resizableInit === '1') return;
157
+ const panes = Array.from(container.children).filter((element) =>
158
+ !element.classList.contains('split-resizer')
159
+ );
160
+ if (panes.length !== 2) return;
161
+
162
+ const [leftPane, rightPane] = panes;
163
+ const handle = document.createElement('div');
164
+ handle.className = 'split-resizer';
165
+ handle.tabIndex = 0;
166
+ handle.setAttribute('role', 'separator');
167
+ handle.setAttribute('aria-orientation', 'vertical');
168
+ handle.setAttribute('title', 'Resize columns');
169
+
170
+ container.classList.add('resizable-split-grid');
171
+ leftPane.classList.add('split-pane-left');
172
+ rightPane.classList.add('split-pane-right');
173
+ container.style.setProperty('--split-left-size', getSplitDefault(container));
174
+ container.insertBefore(handle, rightPane);
175
+ bindSplitResize(container, handle, leftPane, rightPane);
176
+ container.dataset.resizableInit = '1';
177
+ });
178
+
179
+ document.querySelectorAll('.column-stack').forEach((container) => {
180
+ if (container.dataset.stackResizableInit === '1') return;
181
+ const panels = Array.from(container.children).filter((element) =>
182
+ element.classList && element.classList.contains('subpanel')
183
+ );
184
+ if (panels.length < 2) return;
185
+
186
+ container.classList.add('resizable-stack');
187
+ panels.forEach((panel) => panel.classList.add('stack-pane'));
188
+
189
+ for (let index = 0; index < panels.length - 1; index += 1) {
190
+ const topPane = panels[index];
191
+ const bottomPane = panels[index + 1];
192
+ const handle = document.createElement('div');
193
+ handle.className = 'stack-resizer';
194
+ handle.setAttribute('role', 'separator');
195
+ handle.setAttribute('aria-orientation', 'horizontal');
196
+ handle.setAttribute('title', 'Resize sections');
197
+ container.insertBefore(handle, bottomPane);
198
+ bindStackResize(handle, topPane, bottomPane);
199
+ }
200
+
201
+ container.dataset.stackResizableInit = '1';
202
+ });
203
+ };
204
+
17
205
  const initModules = () => {
18
206
  if (root.events && root.events.init) root.events.init();
19
207
  if (root.users && root.users.init) root.users.init();
@@ -50,6 +238,7 @@
50
238
  });
51
239
  });
52
240
 
241
+ initResizableSections();
53
242
  initModules();
54
243
 
55
244
  updateClock();