Diff -- /var/www/vhosts/webstls.com/httpdocs/TEMPLATES/webstls_models.html
Diff

/var/www/vhosts/webstls.com/httpdocs/TEMPLATES/webstls_models.html

added on WebSTLs (webstls.com) at 2026-07-01 22:27:02

Added
+487
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 0eda6a258389
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1<!-- =====================================================================
2 Models management page. Cards per model with controls for:
3 - Feature (move to top of storefront)
4 - Show / Hide on storefront
5 - Move up / Move down in sort order
6 All write actions POST to /update_listing.cgi.
7===================================================================== -->
8
9<style>
10 .mod-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 18px; }
11 .mod-card {
12 position: relative;
13 background: var(--col-surface-1);
14 border: 1px solid var(--col-border);
15 border-radius: 12px;
16 overflow: hidden;
17 transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s;
18 display: flex; flex-direction: column;
19 }
20 .mod-card:hover { border-color: rgba(124,58,237,0.45); transform: translateY(-2px); box-shadow: 0 12px 30px rgba(0,0,0,0.4); }
21 .mod-card.is-hidden { opacity: 0.55; }
22 .mod-card.is-saving { pointer-events: none; opacity: 0.5; }
23 .mod-hero { aspect-ratio: 4/3; background-size: cover; background-position: center; border-bottom: 1px solid var(--col-border); }
24 .mod-body { padding: 14px 16px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
25 .mod-title { font-size: 15px; font-weight: 700; color: var(--col-text); line-height: 1.3; }
26 .mod-meta-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
27 .mod-price { font-size: 14px; font-weight: 700; color: var(--col-accent-bright); font-family: var(--font-mono, monospace); }
28 .mod-category { font-size: 11px; color: var(--col-text-3); letter-spacing: 1px; text-transform: uppercase; }
29 .mod-actions { padding: 10px 12px; border-top: 1px solid var(--col-border); display: flex; flex-direction: column; gap: 6px; background: var(--col-surface-2); }
30 .mod-act-row { display: grid; gap: 6px; }
31 .mod-act-row.r-2 { grid-template-columns: 1fr 1fr; }
32 .mod-act-row.r-4 { grid-template-columns: repeat(4, 1fr); }
33 .mod-act { background: none; border: 1px solid var(--col-border); border-radius: 6px; color: var(--col-text-2); cursor: pointer; padding: 7px 0; font-size: 11px; font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; font-family: inherit; transition: all 0.15s; text-decoration: none; display: flex; align-items: center; justify-content: center; gap: 4px; }
34 .mod-act:hover { border-color: var(--col-accent); color: var(--col-text); background: rgba(124,58,237,0.10); }
35 .mod-act:disabled { opacity: 0.35; cursor: not-allowed; }
36 .mod-act.is-feature { background: linear-gradient(130deg, #7c3aed, #3b82f6); color: #fff; border-color: transparent; }
37 .mod-act.is-feature:hover { filter: brightness(1.1); }
38 /* Make Edit Details visually weighted like Publish (the other
39 row-1 primary action). Higher background opacity + saturated
40 text color so it reads at a glance against the card surface
41 instead of blending in. */
42 .mod-act.is-edit { background: rgba(124,58,237,0.28); border-color: rgba(167,139,250,0.70); color: #c4b5fd; }
43 .mod-act.is-edit:hover { background: rgba(124,58,237,0.45); border-color: #a78bfa; color: #fff; }
44 .mod-act.is-publish { background: rgba(34,197,94,0.14); border-color: rgba(34,197,94,0.4); color: #4ade80; }
45 .mod-act.is-publish:hover { background: rgba(34,197,94,0.25); }
46 .mod-act.is-unpublish { background: rgba(245,158,11,0.12); border-color: rgba(245,158,11,0.35); color: #fbbf24; }
47 .mod-act.is-unpublish:hover { background: rgba(245,158,11,0.22); }
48 .mod-act.is-restore { background: rgba(34,197,94,0.14); border-color: rgba(34,197,94,0.45); color: #4ade80; }
49 .mod-act.is-restore:hover { background: rgba(34,197,94,0.26); }
50 .mod-card.is-trash { border-color: rgba(239,68,68,0.25); }
51 .mod-card.is-trash .mod-hero { filter: grayscale(0.6) brightness(0.7); }
52 .mod-corner-badge { position: absolute; top: 10px; left: 10px; z-index: 2; }
53 .mod-delete-btn {
54 position: absolute; top: 10px; right: 10px; z-index: 3;
55 width: 28px; height: 28px; border-radius: 50%;
56 background: rgba(0,0,0,0.55);
57 border: 1px solid rgba(255,255,255,0.12);
58 color: #fff; font-size: 14px; line-height: 1;
59 cursor: pointer; display: grid; place-items: center;
60 opacity: 0; transition: opacity 0.15s, background 0.15s, border-color 0.15s;
61 font-family: inherit;
62 }
63 .mod-card:hover .mod-delete-btn { opacity: 1; }
64 .mod-delete-btn:hover { background: #ef4444; border-color: #f87171; }
65 .mod-empty {
66 text-align: center; padding: 80px 32px; border: 1px dashed var(--col-border-2);
67 border-radius: 14px; color: var(--col-text-2);
68 }
69 .mod-empty h2 { font-size: 22px; margin-bottom: 8px; color: var(--col-text); }
70 .mod-empty p { color: var(--col-text-2); margin-bottom: 18px; }
71 .mod-import-cta {
72 margin-top: 18px;
73 padding: 22px 26px;
74 border: 1px solid var(--col-border);
75 background: linear-gradient(135deg, rgba(124,58,237,0.10), rgba(139,92,246,0.04));
76 border-radius: 14px;
77 display: flex;
78 align-items: center;
79 gap: 18px;
80 flex-wrap: wrap;
81 }
82 .mod-import-cta-icon {
83 width: 44px; height: 44px; flex-shrink: 0;
84 border-radius: 12px;
85 background: rgba(124,58,237,0.18);
86 display: grid; place-items: center;
87 color: #a78bfa;
88 }
89 .mod-import-cta-body { flex: 1; min-width: 200px; }
90 .mod-import-cta h3 { margin: 0 0 4px; font-size: 15px; color: var(--col-text); }
91 .mod-import-cta p { margin: 0; color: var(--col-text-2); font-size: 13px; line-height: 1.5; }
92 .mod-import-cta a.btn { flex-shrink: 0; }
93 .mod-stats { display: flex; gap: 14px; margin-bottom: 24px; flex-wrap: wrap; }
94 .mod-stat {
95 background: var(--col-surface-1);
96 border: 1px solid var(--col-border);
97 border-radius: 10px;
98 padding: 14px 18px;
99 min-width: 140px;
100 cursor: pointer;
101 transition: border-color 0.15s, transform 0.15s, background 0.15s;
102 text-decoration: none;
103 display: block;
104 }
105 .mod-stat:hover { border-color: rgba(124,58,237,0.55); transform: translateY(-2px); }
106 .mod-stat.is-active {
107 border-color: var(--col-accent);
108 background: linear-gradient(130deg, rgba(124,58,237,0.12), rgba(59,130,246,0.06));
109 box-shadow: 0 6px 18px rgba(124,58,237,0.18);
110 }
111 .mod-stat-lbl { font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--col-text-3); margin-bottom: 4px; font-weight: 700; }
112 .mod-stat-val { font-size: 24px; font-weight: 700; color: var(--col-text); }
113 .mod-stat-val.green { color: #4ade80; }
114 .mod-stat-val.amber { color: #fbbf24; }
115 .mod-stat-val.dim { color: var(--col-text-3); }
116 .mod-stat-val.red { color: #f87171; }
117
118 .mod-filter-bar {
119 display: flex; align-items: center; justify-content: space-between;
120 margin-bottom: 12px; gap: 12px;
121 font-size: 12px; color: var(--col-text-3);
122 letter-spacing: 1px; text-transform: uppercase;
123 }
124 .mod-filter-bar .mod-filter-name { color: var(--col-text-2); font-weight: 700; }
125 .mod-filter-bar a { color: var(--col-accent-bright); text-decoration: none; font-weight: 700; }
126</style>
127
128<div class="page-head">
129 <div>
130 <span class="page-eyebrow"><span class="dot"></span> Models</span>
131 <h1 class="page-title">Your designs</h1>
132 <p class="page-subtitle">Manage which models appear on your storefront, the order they show up, and which one is featured. $model_count total &middot; $listed_count on storefront &middot; $hidden_count hidden.</p>
133 </div>
134 <div class="page-actions">
135 <a href="/upload_model.cgi" class="btn btn-primary" id="modUploadCta" title="Open the upload form to add a new model (digital file, physical print, or both)">
136 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:16px;height:16px"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
137 Upload new model
138 </a>
139 </div>
140</div>
141
142<div class="mod-stats">
143 <a class="mod-stat[if:$is_filter_all] is-active[/if]" href="/models.cgi" title="All models you own (drafts + published + hidden, excludes trash)">
144 <div class="mod-stat-lbl">All models</div>
145 <div class="mod-stat-val">$model_count</div>
146 </a>
147 <a class="mod-stat[if:$is_filter_pub] is-active[/if]" href="/models.cgi?filter=published" title="Published AND currently visible on your storefront">
148 <div class="mod-stat-lbl">On storefront</div>
149 <div class="mod-stat-val green">$listed_count</div>
150 </a>
151 <a class="mod-stat[if:$is_filter_drafts] is-active[/if]" href="/models.cgi?filter=drafts" title="Saved as draft -- nobody can see these except you">
152 <div class="mod-stat-lbl">Drafts</div>
153 <div class="mod-stat-val amber">$drafts_count</div>
154 </a>
155 <a class="mod-stat[if:$is_filter_hidden] is-active[/if]" href="/models.cgi?filter=hidden" title="Published but you toggled them off your storefront with Hide">
156 <div class="mod-stat-lbl">Hidden</div>
157 <div class="mod-stat-val dim">$hidden_count</div>
158 </a>
159 [if:$has_trash]
160 <a class="mod-stat[if:$is_filter_trash] is-active[/if]" href="/models.cgi?filter=trash" title="Deleted models -- restore from here">
161 <div class="mod-stat-lbl">Trash</div>
162 <div class="mod-stat-val red">$trash_count</div>
163 </a>
164 [/if]
165</div>
166
167<div class="mod-filter-bar">
168 <span><span class="mod-filter-name">Showing:</span> $filter_label</span>
169 <span style="display:flex;gap:14px;align-items:center">
170 [if:$is_filter_trash][if:$has_models]
171 <button type="button" id="modEmptyTrash" style="background:rgba(239,68,68,0.10);border:1px solid rgba(239,68,68,0.35);color:#f87171;padding:6px 12px;border-radius:6px;cursor:pointer;font-size:11px;font-weight:700;letter-spacing:1px;text-transform:uppercase;font-family:inherit;transition:all 0.15s">
172 Empty trash
173 </button>
174 [/if][/if]
175 [if:!$is_filter_all]<a href="/models.cgi">Clear filter &times;</a>[/if]
176 </span>
177</div>
178
179[if:$has_models]
180
181<div class="mod-grid">
182 [loop:@models]
183 <div class="mod-card[if:!$loop1.is_visible] is-hidden[/if][if:$loop1.is_removed] is-trash[/if]" data-mid="$loop1.id" data-title="$loop1.title">
184 <div class="mod-corner-badge">$loop1.status_pill</div>
185 [ptag:edit_models]
186 [if:!$loop1.is_removed]
187 <button type="button" class="mod-delete-btn" data-model-action="delete" title="Delete this model (moves to Trash)">&times;</button>
188 [/if]
189 [/ptag:edit_models]
190 [ptag:delete_models]
191 [if:$loop1.is_removed]
192 <button type="button" class="mod-delete-btn" data-model-action="purge" title="Permanently delete this model. Removes it from your trash and you will not be able to restore it.">&times;</button>
193 [/if]
194 [/ptag:delete_models]
195 <div class="mod-hero" style="background-image:url('$loop1.hero_image');background-position:$loop1.hero_pos"></div>
196 <div class="mod-body">
197 <div class="mod-title">$loop1.title</div>
198 <div class="mod-meta-row">
199 <span class="mod-price">$loop1.price</span>
200 <span class="mod-category">$loop1.category</span>
201 </div>
202 </div>
203 <div class="mod-actions">
204 [if:$loop1.is_removed]
205 <div class="mod-act-row r-2">
206 [ptag:edit_models]
207 <button type="button" class="mod-act is-restore" data-model-action="restore" title="Restore this model -- moves it back to drafts">
208 &#8634; Restore to drafts
209 </button>
210 [/ptag:edit_models]
211 <a class="mod-act is-edit" href="/upload_model.cgi?model_id=$loop1.id" title="Open the editor to inspect what was in this model">
212 View details
213 </a>
214 </div>
215 [/if]
216 [if:!$loop1.is_removed]
217 <div class="mod-act-row r-2">
218 [ptag:edit_models]
219 <a class="mod-act is-edit" href="/upload_model.cgi?model_id=$loop1.id" title="Edit title, description, price, images, files, status, etc.">
220 <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5Z"/></svg>
221 Edit details
222 </a>
223 [if:$loop1.is_draft]
224 <button type="button" class="mod-act is-publish" data-model-action="status" data-value="published" title="Publish this model so it can appear on your storefront and other marketplaces">
225 &#9656; Publish
226 </button>
227 [/if]
228 [if:!$loop1.is_draft]
229 <button type="button" class="mod-act is-unpublish" data-model-action="status" data-value="draft" title="Move back to draft. The model stays in your account but is hidden from buyers everywhere.">
230 &#9646;&#9646; Unpublish
231 </button>
232 [/if]
233 [/ptag:edit_models]
234 </div>
235 <div class="mod-act-row r-4">
236 <button type="button" class="mod-act is-feature" data-listing-action="feature" title="Make this the spotlight featured product">&#9733; Feature</button>
237 <button type="button" class="mod-act" data-listing-action="toggle_visible" title="Show / hide on YOUR storefront (separate from publish status)">[if:$loop1.is_visible]Hide[/if][if:!$loop1.is_visible]Show[/if]</button>
238 <button type="button" class="mod-act" data-listing-action="move_up" title="Move up in storefront order">&#8593; Up</button>
239 <button type="button" class="mod-act" data-listing-action="move_down" title="Move down in storefront order">&#8595; Down</button>
240 </div>
241 [/if]
242 </div>
243 </div>
244 [/loop]
245</div>
246[/if]
247
248[if:!$has_models]
249[if:$is_filter_trash]
250<div class="mod-empty">
251 <h2>Trash is empty</h2>
252 <p>Deleted models would appear here. Nothing to restore right now.</p>
253 <a href="/models.cgi" class="btn btn-secondary">Back to all models</a>
254</div>
255[/if]
256[if:$is_filter_drafts]
257<div class="mod-empty">
258 <h2>No drafts</h2>
259 <p>Drafts are models you have saved but not yet published. You don't have any right now.</p>
260 <a href="/models.cgi" class="btn btn-secondary">Back to all models</a>
261</div>
262[/if]
263[if:$is_filter_pub]
264<div class="mod-empty">
265 <h2>Nothing on your storefront</h2>
266 <p>Published models that are visible on your storefront would appear here.</p>
267 <a href="/models.cgi" class="btn btn-secondary">Back to all models</a>
268</div>
269[/if]
270[if:$is_filter_hidden]
271<div class="mod-empty">
272 <h2>No hidden models</h2>
273 <p>This shows models you have published but toggled off your storefront with Hide. Nothing matches.</p>
274 <a href="/models.cgi" class="btn btn-secondary">Back to all models</a>
275</div>
276[/if]
277[if:$is_filter_all]
278<div class="mod-empty">
279 <h2>No models yet</h2>
280 <p>Upload your first design and it will appear here, automatically listed on your storefront.</p>
281 <a href="/upload_model.cgi" class="btn btn-primary">Upload your first model</a>
282</div>
283<div class="mod-import-cta">
284 <div class="mod-import-cta-icon">
285 <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
286 </div>
287 <div class="mod-import-cta-body">
288 <h3>Already selling on another marketplace?</h3>
289 <p>Pull your existing listings from Cults3D, Printables, Patreon, MakerWorld and more into WebSTLs in one click — titles, descriptions, images, and pricing come along for the ride.</p>
290 </div>
291 <a href="/marketplaces.cgi" class="btn btn-secondary">Import from a marketplace &rarr;</a>
292</div>
293[/if]
294[/if]
295
296
297<script>
298(function() {
299 /* ---- Empty trash (bulk purge). Visible only on the Trash filter. ---- */
300 var emptyBtn = document.getElementById('modEmptyTrash');
301 if (emptyBtn) {
302 emptyBtn.addEventListener('mouseenter', function(){ emptyBtn.style.background = 'rgba(239,68,68,0.22)'; });
303 emptyBtn.addEventListener('mouseleave', function(){ emptyBtn.style.background = 'rgba(239,68,68,0.10)'; });
304 emptyBtn.addEventListener('click', function() {
305 appConfirm({
306 title: 'Empty trash?',
307 message: 'Every model currently in your trash will be permanently deleted from your account. You will not be able to restore them after confirming.',
308 confirm_label: 'Empty trash',
309 confirm_style: 'danger'
310 }).then(function(ok) {
311 if (!ok) return;
312 emptyBtn.disabled = true;
313 emptyBtn.textContent = 'Emptying...';
314 var fd = new FormData();
315 fd.append('all', '1');
316 fetch('/purge_models.cgi', { method: 'POST', body: fd, credentials: 'same-origin' })
317 .then(function(r){
318 return r.text().then(function(body){
319 try { return JSON.parse(body); }
320 catch (e) {
321 var snippet = body ? body.replace(/<[^>]*>/g,' ').replace(/\s+/g,' ').trim().slice(0, 200) : '(empty body)';
322 throw new Error('server returned ' + r.status + ' ' + r.statusText + ' (' + snippet + ')');
323 }
324 });
325 })
326 .then(function(data){
327 if (!data || !data.success) throw new Error(data && data.error || 'empty failed');
328 window.location.href = '/models.cgi';
329 })
330 .catch(function(err){
331 emptyBtn.disabled = false;
332 emptyBtn.textContent = 'Empty trash';
333 appToast({ message: 'Empty trash failed: ' + err.message, type: 'error' });
334 });
335 });
336 });
337 }
338
339 function postJson(url, fd, card) {
340 card.classList.add('is-saving');
341 return fetch(url, { method: 'POST', body: fd, credentials: 'same-origin' })
342 .then(function(r){
343 return r.text().then(function(body){
344 try { return JSON.parse(body); }
345 catch (e) {
346 var snippet = body ? body.replace(/<[^>]*>/g,' ').replace(/\s+/g,' ').trim().slice(0, 200) : '(empty body)';
347 throw new Error('server returned ' + r.status + ' ' + r.statusText + ' (' + snippet + ')');
348 }
349 });
350 })
351 .then(function(data){
352 if (!data || !data.success) throw new Error(data && data.error || 'action failed');
353 return data;
354 });
355 }
356
357 /* All listing actions (feature, hide/show, reorder) -> update_listing.cgi */
358 document.addEventListener('click', function(e) {
359 var btn = e.target.closest('[data-listing-action]');
360 if (!btn) return;
361 var card = btn.closest('.mod-card');
362 if (!card) return;
363 var mid = card.getAttribute('data-mid');
364 var action = btn.getAttribute('data-listing-action');
365 if (!mid || !action) return;
366
367 var fd = new FormData();
368 fd.append('action', action);
369 fd.append('model_id', mid);
370
371 postJson('/update_listing.cgi', fd, card)
372 .then(function(){
373 if (action === 'feature' || action === 'move_up' || action === 'move_down') {
374 window.location.reload();
375 return;
376 }
377 if (action === 'toggle_visible') {
378 var visBtn = card.querySelector('[data-listing-action="toggle_visible"]');
379 var nowHidden = card.classList.toggle('is-hidden');
380 if (visBtn) visBtn.textContent = nowHidden ? 'Show' : 'Hide';
381 card.classList.remove('is-saving');
382 return;
383 }
384 card.classList.remove('is-saving');
385 })
386 .catch(function(err){
387 card.classList.remove('is-saving');
388 appToast({ message: 'Action failed: ' + err.message, type: 'error' });
389 });
390 });
391
392 /* Model-level actions (status flip, delete) -> update_model_field.cgi.
393 "delete" is special: it confirms first, then maps to status=removed
394 (soft delete -- the row stays in the DB, every query filters
395 status='removed' out so the model disappears from storefront,
396 models page, marketplaces, etc.). */
397 document.addEventListener('click', function(e) {
398 var btn = e.target.closest('[data-model-action]');
399 if (!btn) return;
400 var card = btn.closest('.mod-card');
401 if (!card) return;
402 var mid = card.getAttribute('data-mid');
403 var action = btn.getAttribute('data-model-action');
404 var value = btn.getAttribute('data-value');
405 if (!mid || !action) return;
406
407 var field, endpoint = '/update_model_field.cgi';
408 if (action === 'delete') {
409 var title = card.getAttribute('data-title') || 'this model';
410 appConfirm({
411 title: 'Delete "' + title + '"?',
412 message: 'It will disappear from your storefront and every marketplace push immediately. You can restore it later from the Trash tab on this page.',
413 confirm_label: 'Move to trash',
414 confirm_style: 'warning'
415 }).then(function(ok) {
416 if (!ok) return;
417 var fd = new FormData();
418 fd.append('field', 'status');
419 fd.append('value', 'removed');
420 fd.append('model_id', mid);
421 postJson(endpoint, fd, card)
422 .then(function(){ window.location.reload(); })
423 .catch(function(err){
424 card.classList.remove('is-saving');
425 appToast({ message: 'Action failed: ' + err.message, type: 'error' });
426 });
427 });
428 return;
429 } else if (action === 'restore') {
430 /* Non-destructive -- no confirm needed. */
431 field = 'status';
432 value = 'draft';
433 } else if (action === 'purge') {
434 /* Permanent (from the user's perspective). Big confirm required. */
435 var title2 = card.getAttribute('data-title') || 'this model';
436 appConfirm({
437 title: 'Permanently delete "' + title2 + '"?',
438 message: 'This removes it from your trash for good. You will not be able to restore it from the page after confirming.',
439 confirm_label: 'Delete forever',
440 confirm_style: 'danger'
441 }).then(function(ok) {
442 if (!ok) return;
443 var fdPurge = new FormData();
444 fdPurge.append('model_id', mid);
445 postJson('/purge_models.cgi', fdPurge, card)
446 .then(function(){
447 card.style.transition = 'opacity 0.25s, transform 0.25s';
448 card.style.opacity = '0';
449 card.style.transform = 'scale(0.95)';
450 setTimeout(function(){ window.location.reload(); }, 260);
451 })
452 .catch(function(err){
453 card.classList.remove('is-saving');
454 appToast({ message: 'Permanent delete failed: ' + err.message, type: 'error' });
455 });
456 });
457 return;
458 } else {
459 field = action;
460 }
461
462 var fd = new FormData();
463 fd.append('field', field);
464 fd.append('value', value || '');
465 fd.append('model_id', mid);
466
467 postJson(endpoint, fd, card)
468 .then(function(){
469 if (action === 'delete' || action === 'restore') {
470 /* Fade the card out -- on delete it moves to trash, on
471 restore it moves back to drafts. Either way the current
472 view changes, so a reload is the easiest correct state. */
473 card.style.transition = 'opacity 0.25s, transform 0.25s';
474 card.style.opacity = '0';
475 card.style.transform = 'scale(0.95)';
476 setTimeout(function(){ window.location.reload(); }, 260);
477 return;
478 }
479 window.location.reload();
480 })
481 .catch(function(err){
482 card.classList.remove('is-saving');
483 appToast({ message: 'Action failed: ' + err.message, type: 'error' });
484 });
485 });
486})();
487</script>
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help