@raketa-cloud/admin 1.7.4 → 1.7.6

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 (38) hide show
  1. package/dist/application.css +23 -0
  2. package/package.json +3 -3
  3. package/src/HonoAdapter.js +7 -3
  4. package/src/cloud/Articles.js +10 -3
  5. package/src/cloud/Events.js +9 -2
  6. package/src/cloud/Pages.js +13 -1
  7. package/src/cloud/documents/articlesDocuments.js +4 -2
  8. package/src/cloud/documents/eventsDocuments.js +4 -2
  9. package/src/cloud/documents/pagesDocuments.js +4 -2
  10. package/src/handlers/_shared/SettingsHandler.js +4 -0
  11. package/src/handlers/articles/ArticlesHandler.js +10 -4
  12. package/src/handlers/articles/ExportsHandler.js +35 -0
  13. package/src/handlers/events/EventsHandler.js +10 -4
  14. package/src/handlers/events/ExportsHandler.js +35 -0
  15. package/src/handlers/pages/ExportsHandler.js +35 -0
  16. package/src/handlers/pages/PagesHandler.js +10 -4
  17. package/src/helpers/CSVExportHelper.js +103 -0
  18. package/src/routeArticlesModule.js +13 -0
  19. package/src/routeEventsModule.js +13 -0
  20. package/src/routePagesModule.js +13 -0
  21. package/src/routeSettings.js +4 -0
  22. package/src/tests/RaketaClient.test.js +636 -0
  23. package/src/tests/handlers/articles/ArticlesHandler.test/index.test.js +18 -2
  24. package/src/tests/handlers/articles/ExportsHandler.test/index.test.js +217 -0
  25. package/src/tests/handlers/events/EventsHandler.test/index.test.js +17 -1
  26. package/src/tests/handlers/events/ExportsHandler.test/index.test.js +212 -0
  27. package/src/tests/handlers/pages/ExportsHandler.test/index.test.js +212 -0
  28. package/src/tests/handlers/pages/PagesHandler.test/index.test.js +17 -1
  29. package/src/tests/helpers/CSVExportHelper.test.js +364 -0
  30. package/src/views/articles/_shared/_export_button.ejs +9 -0
  31. package/src/views/articles/index.ejs +37 -1
  32. package/src/views/events/_shared/_export_button.ejs +9 -0
  33. package/src/views/events/index.ejs +37 -1
  34. package/src/views/layouts/application.ejs +2 -0
  35. package/src/views/pages/_shared/_export_button.ejs +9 -0
  36. package/src/views/pages/index.ejs +37 -1
  37. package/src/views/settings/_nav.ejs +7 -1
  38. package/src/views/settings/variables/edit_form.ejs +178 -0
@@ -139,6 +139,42 @@
139
139
  <input type="text" name="f:keyword" value="<%= _context.query["f:keyword"] %>" />
140
140
  </div>
141
141
 
142
+ <div class="mb-4">
143
+ <label for="widget" class="font-semibold hover:cursor-pointer body-3">Widget</label>
144
+ <input type="text" name="f:widget" value="<%= _context.query["f:widget"] %>" />
145
+ </div>
146
+
147
+ <div class="mb-4">
148
+ <label for="widget_keyword" class="font-semibold hover:cursor-pointer body-3">Widget text keyword</label>
149
+ <input type="text" name="f:widget_keyword" value="<%= _context.query["f:widget_keyword"] %>" />
150
+ </div>
151
+
152
+ <div class="mb-4">
153
+ <label for="last_updated_from" class="font-semibold hover:cursor-pointer body-3">Last Updated from</label>
154
+ <input type="datetime-local" name="f:last_updated_from" value="<%= _context.query["f:last_updated_from"] %>" />
155
+ </div>
156
+
157
+ <div class="mb-4">
158
+ <label for="last_updated_to" class="font-semibold hover:cursor-pointer body-3">Last Updated to</label>
159
+ <input type="datetime-local" name="f:last_updated_to" value="<%= _context.query["f:last_updated_to"] %>" />
160
+ </div>
161
+
162
+ <div class="mb-4">
163
+ <label for="" class="font-semibold hover:cursor-pointer body-3">Translations</label>
164
+ <div class="checkbox-group mb-4">
165
+ <% translations.filter(item => item.code !== _context.locale).forEach((item,idx)=> { %>
166
+ <div class="flex items-center gap-1">
167
+ <% if (utils.includedInCollection(item.code, _context.query["f:translations"])) { %>
168
+ <input type="checkbox" name="f:translations" id='locale-<%= item.code %>' checked value="<%= item.code %>" class="hover:cursor-pointer">
169
+ <% } else { %>
170
+ <input type="checkbox" name="f:translations" id='locale-<%= item.code %>' value="<%= item.code %>" class="hover:cursor-pointer">
171
+ <% } %>
172
+ <label for='locale-<%= item.code %>' class="body-2 hover:cursor-pointer"><%= item.name %></label>
173
+ </div>
174
+ <% }); %>
175
+ </div>
176
+ </div>
177
+
142
178
  <div class="mb-4">
143
179
  <label for="" class="font-semibold hover:cursor-pointer body-3">Tags</label>
144
180
  <div class="checkbox-group mb-4">
@@ -157,8 +193,8 @@
157
193
 
158
194
  <div class="btn-toolbar">
159
195
  <button type="submit" class="btn-primary">Search</button>
196
+ <%- include('./_shared/_export_button.ejs') %>
160
197
  <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}` %>" class="btn-text">Clear search</a>
161
198
  </div>
162
199
  </form>
163
200
  </div>
164
-
@@ -227,6 +227,8 @@
227
227
 
228
228
  // Setup datetime inputs
229
229
  document.querySelectorAll('input[type="datetime-local"]').forEach(input => {
230
+ if (input.dataset.noHints === 'true') return;
231
+
230
232
  enhanceDateTimeInputs(input);
231
233
 
232
234
  input.addEventListener("change", (event) => enhanceDateTimeInputs(event.target))
@@ -0,0 +1,9 @@
1
+ <% if (Object.keys(_context.query).some(k => k.startsWith('f:')) && items.list.length > 0) { %>
2
+ <%
3
+ const queryString = Object.entries(_context.query)
4
+ .filter(([key, value]) => value !== undefined && value !== null && value !== '')
5
+ .map(([key, value]) => Array.isArray(value) ? value.map(v => `${key}=${encodeURIComponent(v)}`).join('&') : `${key}=${encodeURIComponent(value)}`)
6
+ .join('&');
7
+ %>
8
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/exports${queryString ? '?' + queryString : ''}` %>" class="btn-secondary">Export</a>
9
+ <% } %>
@@ -134,6 +134,42 @@
134
134
  <input type="text" name="f:keyword" value="<%= _context.query["f:keyword"] %>" />
135
135
  </div>
136
136
 
137
+ <div class="mb-4">
138
+ <label for="widget" class="font-semibold hover:cursor-pointer body-3">Widget</label>
139
+ <input type="text" name="f:widget" value="<%= _context.query["f:widget"] %>" />
140
+ </div>
141
+
142
+ <div class="mb-4">
143
+ <label for="widget_keyword" class="font-semibold hover:cursor-pointer body-3">Widget text keyword</label>
144
+ <input type="text" name="f:widget_keyword" value="<%= _context.query["f:widget_keyword"] %>" />
145
+ </div>
146
+
147
+ <div class="mb-4">
148
+ <label for="last_updated_from" class="font-semibold hover:cursor-pointer body-3">Last Updated from</label>
149
+ <input type="datetime-local" name="f:last_updated_from" value="<%= _context.query["f:last_updated_from"] %>" />
150
+ </div>
151
+
152
+ <div class="mb-4">
153
+ <label for="last_updated_to" class="font-semibold hover:cursor-pointer body-3">Last Updated to</label>
154
+ <input type="datetime-local" name="f:last_updated_to" value="<%= _context.query["f:last_updated_to"] %>" />
155
+ </div>
156
+
157
+ <div class="mb-4">
158
+ <label for="" class="font-semibold hover:cursor-pointer body-3">Translations</label>
159
+ <div class="checkbox-group mb-4">
160
+ <% translations.filter(item => item.code !== _context.locale).forEach((item,idx)=> { %>
161
+ <div class="flex items-center gap-1">
162
+ <% if (utils.includedInCollection(item.code, _context.query["f:translations"])) { %>
163
+ <input type="checkbox" name="f:translations" id='locale-<%= item.code %>' checked value="<%= item.code %>" class="hover:cursor-pointer">
164
+ <% } else { %>
165
+ <input type="checkbox" name="f:translations" id='locale-<%= item.code %>' value="<%= item.code %>" class="hover:cursor-pointer">
166
+ <% } %>
167
+ <label for='locale-<%= item.code %>' class="body-2 hover:cursor-pointer"><%= item.name %></label>
168
+ </div>
169
+ <% }); %>
170
+ </div>
171
+ </div>
172
+
137
173
  <div class="mb-4">
138
174
  <label for="" class="font-semibold hover:cursor-pointer body-3">Tags</label>
139
175
  <div class="checkbox-group mb-4">
@@ -152,8 +188,8 @@
152
188
 
153
189
  <div class="btn-toolbar">
154
190
  <button type="submit" class="btn-primary">Search</button>
191
+ <%- include('./_shared/_export_button.ejs') %>
155
192
  <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}` %>" class="btn-text">Clear search</a>
156
193
  </div>
157
194
  </form>
158
195
  </div>
159
-
@@ -17,6 +17,12 @@
17
17
  Layout and Navigation
18
18
  </a>
19
19
  </div>
20
+
21
+ <div class="<%= active === 'variables' ? activeClass : inactiveClass %>">
22
+ <a href="<%= `/${_context.locale}/settings/variables/edit` %>" class="text-raketa-primary font-semibold">
23
+ Variables
24
+ </a>
25
+ </div>
20
26
  </div>
21
27
  </div>
22
- </div>
28
+ </div>
@@ -0,0 +1,178 @@
1
+ <div class="page-wrapper">
2
+ <div class="page-title">
3
+ <h1 class="heading-1">Global Variables</h1>
4
+ </div>
5
+
6
+ <%- render_partial('../_nav.ejs', { active: 'variables' }) %>
7
+
8
+ <% form_for({ resource }, errors, (f) => { %>
9
+ <form action="<%= `/${_context.locale}/settings/variables` %>" method="post" class="form" onsubmit="filterEmptyVariables()">
10
+
11
+ <div class="form-actions mb-6">
12
+ <div class="btn-toolbar">
13
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
14
+ </div>
15
+ </div>
16
+
17
+ <div class="flex gap-4">
18
+ <div class="flex-1">
19
+ <div class="mb-6">
20
+ <table class="data-grid w-full">
21
+ <thead>
22
+ <tr>
23
+ <th>Variable</th>
24
+ <th>Value</th>
25
+ <th class="actions"></th>
26
+ </tr>
27
+ </thead>
28
+ <tbody id="variables-tbody">
29
+ </tbody>
30
+ </table>
31
+
32
+ <div class="p-4">
33
+ <button type="button" class="btn-text" onclick="addVariable()">
34
+ Add new variable
35
+ </button>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="form-actions">
40
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
41
+ </div>
42
+ </div>
43
+
44
+ <div class="w-80">
45
+ <div class="help">
46
+ <h3 class="heading-3 mb-3">How to use the global variables?</h3>
47
+
48
+ <p class="body-2 mb-4">The variables are intended for short texts, their values can be only 30 characters long.</p>
49
+
50
+ <p class="body-2">Use them in page settings or widgets like this {{VARIABLE_NAME}}</p>
51
+
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ </form>
57
+ <% }) %>
58
+ </div>
59
+
60
+ <script>
61
+ let variables = <%- JSON.stringify(resource._global_variables || {}) %>;
62
+
63
+ function render() {
64
+ const tbody = document.getElementById('variables-tbody');
65
+ const entries = Object.entries(variables);
66
+
67
+ tbody.innerHTML = entries.map(([name, value]) => `
68
+ <tr class="variable-row">
69
+ <td class="w-1/2">
70
+ <input type="text"
71
+ data-key="${escapeHtml(name)}"
72
+ value="${escapeHtml(name)}"
73
+ placeholder="VARIABLE_NAME"
74
+ maxlength="30"
75
+ pattern="[A-Z_][A-Z0-9_]*"
76
+ title="Only uppercase letters, numbers and underscore are allowed"
77
+ class="border border-neutral-300 rounded px-3 py-2 shadow-inner w-full"
78
+ onchange="renameVariable('${escapeHtml(name)}', this.value)">
79
+ </td>
80
+ <td class="w-1/2">
81
+ <input type="text"
82
+ value="${escapeHtml(value)}"
83
+ placeholder="Value"
84
+ maxlength="30"
85
+ class="border border-neutral-300 rounded px-3 py-2 shadow-inner w-full"
86
+ oninput="updateValue('${escapeHtml(name)}', this.value)">
87
+ </td>
88
+ <td class="actions">
89
+ <button type="button" class="btn-danger btn-sm" onclick="removeVariable('${escapeHtml(name)}')">
90
+ Delete
91
+ </button>
92
+ </td>
93
+ </tr>
94
+ `).join('');
95
+ }
96
+
97
+ function updateValue(key, value) {
98
+ variables[key] = value;
99
+ }
100
+
101
+ function renameVariable(oldKey, newKey) {
102
+ newKey = newKey.trim();
103
+
104
+ if (!newKey) {
105
+ alert('Variable name cannot be empty');
106
+ render();
107
+ return;
108
+ }
109
+
110
+ if (oldKey === newKey) return;
111
+
112
+ if (variables[newKey] !== undefined) {
113
+ alert('Variable name already exists');
114
+ render();
115
+ return;
116
+ }
117
+
118
+ variables[newKey] = variables[oldKey];
119
+ delete variables[oldKey];
120
+ render();
121
+ }
122
+
123
+ function addVariable() {
124
+ let counter = 1;
125
+ let newKey = 'NEW_VAR';
126
+
127
+ while (variables[newKey] !== undefined) {
128
+ newKey = `NEW_VAR_${counter}`;
129
+ counter++;
130
+ }
131
+
132
+ variables[newKey] = '';
133
+ render();
134
+
135
+ const inputs = document.querySelectorAll(`input[data-key="${newKey}"]`);
136
+ if (inputs.length > 0) {
137
+ const row = inputs[0].closest('tr');
138
+ const valueInput = row.querySelector('input[placeholder="Value"]');
139
+ if (valueInput) {
140
+ valueInput.focus();
141
+ }
142
+ }
143
+ }
144
+
145
+ function removeVariable(key) {
146
+ delete variables[key];
147
+ render();
148
+ }
149
+
150
+ function filterEmptyVariables() {
151
+ for (const key in variables) {
152
+ if (!key.trim() || !variables[key].trim()) {
153
+ delete variables[key];
154
+ }
155
+ }
156
+
157
+ const form = document.querySelector('form');
158
+
159
+ form.querySelectorAll('input[name^="resource[_global_variables]"]').forEach(el => el.remove());
160
+
161
+ for (const [key, value] of Object.entries(variables)) {
162
+ const input = document.createElement('input');
163
+ input.type = 'hidden';
164
+ input.name = `resource[_global_variables][${key}]`;
165
+ input.value = value;
166
+ form.appendChild(input);
167
+ }
168
+ }
169
+
170
+ function escapeHtml(text) {
171
+ if (!text) return '';
172
+ const div = document.createElement('div');
173
+ div.textContent = text;
174
+ return div.innerHTML;
175
+ }
176
+
177
+ render();
178
+ </script>