Skip to main content

max / makenotwork

12.8 KB · 329 lines History Blame Raw
1 (function() {
2 var __cfg = document.getElementById('tab-project-content-cfg');
3 var PROJECT_SLUG = __cfg ? __cfg.dataset.projectSlug : '';
4
5 function toggleSelectAll(checked) {
6 var boxes = document.querySelectorAll('.bulk-check');
7 for (var i = 0; i < boxes.length; i++) boxes[i].checked = checked;
8 var selectAll = document.getElementById('select-all');
9 if (selectAll) selectAll.checked = checked;
10 updateBulkUI();
11 }
12
13 function updateBulkUI() {
14 var checked = document.querySelectorAll('.bulk-check:checked');
15 var bar = document.getElementById('bulk-action-bar');
16 var countEl = document.getElementById('bulk-count');
17 var selectAll = document.getElementById('select-all');
18 var allBoxes = document.querySelectorAll('.bulk-check');
19 if (!bar) return;
20 var hasSelection = checked.length > 0;
21 bar.classList.toggle('bulk-action-bar--active', hasSelection);
22 bar.dataset.active = hasSelection ? 'true' : 'false';
23 countEl.textContent = checked.length + ' selected';
24 var buttons = bar.querySelectorAll('button');
25 for (var i = 0; i < buttons.length; i++) buttons[i].disabled = !hasSelection;
26 if (selectAll) selectAll.checked = allBoxes.length > 0 && checked.length === allBoxes.length;
27 if (!hasSelection) {
28 document.getElementById('bulk-price-form').classList.add('hidden');
29 document.getElementById('bulk-tag-form').classList.add('hidden');
30 }
31 }
32
33 function bulkAction(action) {
34 var checked = document.querySelectorAll('.bulk-check:checked');
35 if (checked.length === 0) return;
36
37 var titles = [];
38 for (var i = 0; i < checked.length; i++) {
39 var row = checked[i].closest('tr');
40 var link = row ? row.querySelector('td:nth-child(3) a') : null;
41 if (link) titles.push(link.textContent.trim());
42 }
43
44 if (action === 'delete') {
45 var msg = 'Delete ' + checked.length + ' item(s)? This cannot be undone.\n\n' + titles.join('\n');
46 if (!confirm(msg)) return;
47 }
48
49 var count = checked.length;
50 var params = new URLSearchParams();
51 for (var i = 0; i < checked.length; i++) {
52 params.append('item_ids', checked[i].value);
53 }
54
55 fetch('/api/items/bulk/' + action, {
56 method: 'POST',
57 headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
58 body: params.toString()
59 })
60 .then(function(r) {
61 if (!r.ok) return r.text().then(function(t) {
62 var msg = 'Bulk ' + action + ' failed (' + r.status + ')';
63 try { var parsed = JSON.parse(t); if (parsed.error) msg = parsed.error; } catch (_) {}
64 throw new Error(msg);
65 });
66 var label = action === 'delete' ? 'deleted' : action === 'publish' ? 'published' : 'unpublished';
67 showToast(count + ' item(s) ' + label + '.', 'success');
68 htmx.ajax('GET', '/dashboard/project/' + PROJECT_SLUG + '/tabs/content', '#tab-content');
69 })
70 .catch(function(err) {
71 showToast(err.message || 'Bulk operation failed');
72 });
73 }
74
75 function quickPublish(itemId) {
76 var params = new URLSearchParams();
77 params.append('item_ids', itemId);
78 fetch('/api/items/bulk/publish', {
79 method: 'POST',
80 headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
81 body: params.toString()
82 }).then(function(r) {
83 if (!r.ok) return r.text().then(function(t) {
84 var msg = 'Publish failed';
85 try { var parsed = JSON.parse(t); if (parsed.error) msg = parsed.error; } catch (_) {}
86 throw new Error(msg);
87 });
88 showToast('Item published.', 'success');
89 htmx.ajax('GET', '/dashboard/project/' + PROJECT_SLUG + '/tabs/content', '#tab-content');
90 }).catch(function(err) {
91 showToast(err.message || 'Publish failed');
92 });
93 }
94
95 function showBulkPrice() {
96 document.getElementById('bulk-price-form').classList.remove('hidden');
97 document.getElementById('bulk-tag-form').classList.add('hidden');
98 document.getElementById('bulk-price-input').focus();
99 }
100
101 function showBulkTag() {
102 document.getElementById('bulk-tag-form').classList.remove('hidden');
103 document.getElementById('bulk-price-form').classList.add('hidden');
104 document.getElementById('bulk-tag-input').focus();
105 }
106
107 function submitBulkPrice() {
108 var checked = document.querySelectorAll('.bulk-check:checked');
109 if (checked.length === 0) return;
110 var price = document.getElementById('bulk-price-input').value;
111 if (price === '') { showToast('Enter a price'); return; }
112
113 var params = new URLSearchParams();
114 for (var i = 0; i < checked.length; i++) params.append('item_ids', checked[i].value);
115 params.append('price_dollars', price);
116
117 fetch('/api/items/bulk/price', {
118 method: 'POST',
119 headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
120 body: params.toString()
121 })
122 .then(function(r) {
123 if (!r.ok) return r.text().then(function(t) {
124 var msg = 'Could not update prices. Try again, or refresh the page.';
125 try { var p = JSON.parse(t); if (p.error) msg = p.error; } catch (_) {}
126 throw new Error(msg);
127 });
128 document.getElementById('bulk-price-form').classList.add('hidden');
129 htmx.ajax('GET', '/dashboard/project/' + PROJECT_SLUG + '/tabs/content', '#tab-content');
130 })
131 .catch(function(err) { showToast(err.message || 'Could not update prices. Try again, or refresh the page.'); });
132 }
133
134 function submitBulkTag() {
135 var checked = document.querySelectorAll('.bulk-check:checked');
136 if (checked.length === 0) return;
137 var tag = document.getElementById('bulk-tag-input').value.trim();
138 if (!tag) { showToast('Enter a tag.'); return; }
139
140 var params = new URLSearchParams();
141 for (var i = 0; i < checked.length; i++) params.append('item_ids', checked[i].value);
142 params.append('tag_slug', tag);
143
144 fetch('/api/items/bulk/tag', {
145 method: 'POST',
146 headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
147 body: params.toString()
148 })
149 .then(function(r) {
150 if (!r.ok) return r.text().then(function(t) {
151 var msg = 'Could not add the tag. Try again, or refresh the page.';
152 try { var p = JSON.parse(t); if (p.error) msg = p.error; } catch (_) {}
153 throw new Error(msg);
154 });
155 document.getElementById('bulk-tag-form').classList.add('hidden');
156 document.getElementById('bulk-tag-input').value = '';
157 htmx.ajax('GET', '/dashboard/project/' + PROJECT_SLUG + '/tabs/content', '#tab-content');
158 })
159 .catch(function(err) { showToast(err.message || 'Could not add the tag. Try again, or refresh the page.'); });
160 }
161
162 function toggleBundleChildren(bundleId) {
163 var rows = document.querySelectorAll('.bundle-child-' + bundleId);
164 var btn = document.querySelector('tr:has(.bulk-check[value="' + bundleId + '"]) .bundle-toggle');
165 var visible = rows.length > 0 && !rows[0].classList.contains('hidden');
166 for (var i = 0; i < rows.length; i++) {
167 rows[i].classList.toggle('hidden', visible);
168 }
169 if (btn) btn.innerHTML = visible ? '&#9654;' : '&#9660;';
170 }
171
172 function filterContentTable() {
173 var query = (document.getElementById('content-search').value || '').toLowerCase();
174 var status = document.getElementById('content-filter-status').value;
175 var type = document.getElementById('content-filter-type').value;
176 var rows = document.querySelectorAll('.data-table tbody > tr:not(.bundle-child)');
177 for (var i = 0; i < rows.length; i++) {
178 var row = rows[i];
179 var title = (row.querySelector('td:nth-child(3)') || {}).textContent || '';
180 var rowType = (row.querySelector('td:nth-child(4)') || {}).textContent || '';
181 var rowStatus = row.querySelector('.badge') ? row.querySelector('.badge').textContent.trim() : '';
182 var matchQuery = !query || title.toLowerCase().indexOf(query) !== -1;
183 var matchStatus = !status || rowStatus === status;
184 var matchType = !type || rowType.trim().indexOf(type) !== -1;
185 var visible = matchQuery && matchStatus && matchType;
186 row.classList.toggle('hidden', !visible);
187 var itemCheckbox = row.querySelector('.bulk-check');
188 if (itemCheckbox && !visible) {
189 var children = document.querySelectorAll('.bundle-child-' + itemCheckbox.value);
190 for (var j = 0; j < children.length; j++) {
191 children[j].classList.add('hidden');
192 }
193 }
194 }
195 }
196
197 (function() {
198 var seen = {};
199 var opts = document.querySelectorAll('.content-type-opt');
200 for (var i = 0; i < opts.length; i++) {
201 if (seen[opts[i].value]) { opts[i].remove(); } else { seen[opts[i].value] = true; }
202 }
203 })();
204
205 var contentSortState = { col: -1, asc: true };
206
207 function sortContentTable(colIndex, sortType) {
208 var table = document.querySelector('.data-table');
209 if (!table) return;
210 var tbody = table.querySelector('tbody');
211 var rows = Array.prototype.slice.call(tbody.querySelectorAll('tr:not(.bundle-child)'));
212
213 if (contentSortState.col === colIndex) {
214 contentSortState.asc = !contentSortState.asc;
215 } else {
216 contentSortState.col = colIndex;
217 contentSortState.asc = true;
218 }
219
220 function getValue(row) {
221 var cell = row.cells[colIndex];
222 if (!cell) return '';
223 var text = cell.textContent.trim();
224 if (sortType === 'num') {
225 return parseInt(text.replace(/[^0-9-]/g, ''), 10) || 0;
226 }
227 if (sortType === 'money') {
228 if (text.toLowerCase() === 'free') return 0;
229 return parseFloat(text.replace(/[^0-9.-]/g, '')) || 0;
230 }
231 return text.toLowerCase();
232 }
233
234 rows.sort(function(a, b) {
235 var va = getValue(a);
236 var vb = getValue(b);
237 var cmp = 0;
238 if (typeof va === 'number') {
239 cmp = va - vb;
240 } else {
241 cmp = va < vb ? -1 : va > vb ? 1 : 0;
242 }
243 return contentSortState.asc ? cmp : -cmp;
244 });
245
246 for (var i = 0; i < rows.length; i++) {
247 tbody.appendChild(rows[i]);
248 var checkbox = rows[i].querySelector('.bulk-check');
249 if (checkbox) {
250 var children = Array.prototype.slice.call(tbody.querySelectorAll('.bundle-child-' + checkbox.value));
251 for (var j = 0; j < children.length; j++) {
252 tbody.appendChild(children[j]);
253 }
254 }
255 }
256
257 var arrows = document.querySelectorAll('.sort-arrow');
258 for (var i = 0; i < arrows.length; i++) {
259 var col = parseInt(arrows[i].dataset.col, 10);
260 if (col === colIndex) {
261 arrows[i].textContent = contentSortState.asc ? '' : '';
262 arrows[i].classList.add('sort-arrow--active');
263 } else {
264 arrows[i].textContent = '';
265 arrows[i].classList.remove('sort-arrow--active');
266 }
267 }
268 }
269
270 function inlineRename(itemId) {
271 var container = document.getElementById('item-title-' + itemId);
272 var link = container.querySelector('a');
273 var currentTitle = link.textContent;
274 var href = link.getAttribute('href');
275
276 var input = document.createElement('input');
277 input.type = 'text';
278 input.value = currentTitle;
279 input.className = 'inline-rename-input';
280
281 function restore(newTitle) {
282 container.innerHTML = '';
283 var a = document.createElement('a');
284 a.href = href;
285 a.className = 'fw-bold';
286 a.textContent = newTitle;
287 container.appendChild(a);
288 }
289
290 function save() {
291 var newTitle = input.value.trim();
292 if (!newTitle || newTitle === currentTitle) { restore(currentTitle); return; }
293 var form = new FormData();
294 form.append('title', newTitle);
295 fetch('/api/items/' + itemId, { method: 'PUT', headers: csrfHeaders(), body: form })
296 .then(function(r) {
297 if (!r.ok) throw new Error('Failed');
298 return r.json();
299 })
300 .then(function(data) { restore(data.title); })
301 .catch(function() { restore(currentTitle); });
302 }
303
304 input.addEventListener('keydown', function(e) {
305 if (e.key === 'Enter') { e.preventDefault(); save(); }
306 if (e.key === 'Escape') { restore(currentTitle); }
307 });
308 input.addEventListener('blur', save);
309
310 container.innerHTML = '';
311 container.appendChild(input);
312 input.focus();
313 input.select();
314 }
315
316 window.toggleSelectAll = toggleSelectAll;
317 window.updateBulkUI = updateBulkUI;
318 window.bulkAction = bulkAction;
319 window.quickPublish = quickPublish;
320 window.showBulkPrice = showBulkPrice;
321 window.showBulkTag = showBulkTag;
322 window.submitBulkPrice = submitBulkPrice;
323 window.submitBulkTag = submitBulkTag;
324 window.toggleBundleChildren = toggleBundleChildren;
325 window.filterContentTable = filterContentTable;
326 window.sortContentTable = sortContentTable;
327 window.inlineRename = inlineRename;
328 })();
329