diff options
| author | Ken D'Ambrosio <ken@jots.org> | 2026-05-14 04:14:31 +0000 |
|---|---|---|
| committer | Ken D'Ambrosio <ken@jots.org> | 2026-05-14 04:14:31 +0000 |
| commit | 9cebd2e909793e12f7b9e5125d4fba671b5b660d (patch) | |
| tree | b3c0187e8c5e701074d6c7e05c86773dfc86e592 | |
| parent | 7950acb21b22e7bc6f10c50e1427850de2834b24 (diff) | |
Add per-photo delete checkbox to admin edit form
Checking Delete and saving permanently removes the file and its
thumbnail; a JS confirm dialog gates the submit. Deleted files are
stripped from params before save_edits so they don't linger in
album.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | app.rb | 13 | ||||
| -rw-r--r-- | public/css/style.css | 5 | ||||
| -rw-r--r-- | views/admin/album.erb | 26 |
3 files changed, 39 insertions, 5 deletions
@@ -160,7 +160,7 @@ helpers do cover = data['cover'] return cover if cover && cover != '__random__' && File.exist?(File.join(dir, cover)) candidates = cover_candidates(dir) - cover == '__random__' ? candidates.sample : candidates.first + candidates.sample end def cover_candidates(dir) @@ -413,6 +413,17 @@ post '/admin/edit/*' do rel = params[:splat].first.chomp('/') dir = resolve_dir(rel) + # Handle file deletions before save so save_edits doesn't re-add them + to_delete = (params['file_delete'] || {}).select { |_, v| v == '1' }.keys + to_delete.each do |name| + full = File.join(dir, name) + thumb = File.join(CACHE_ROOT, rel.empty? ? "#{name}.th.jpg" : "#{rel}/#{name}.th.jpg") + File.unlink(full) if File.exist?(full) + File.unlink(thumb) if File.exist?(thumb) + params['file_visible']&.delete(name) + params['file_caption']&.delete(name) + end + unless rel.empty? new_name = params['folder_name'].to_s.strip new_name = '' if new_name.include?('/') || new_name.include?("\x00") || diff --git a/public/css/style.css b/public/css/style.css index e6ebff3..41dfd0a 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -379,6 +379,11 @@ legend { padding: 0 8px; color: var(--text-dim); font-size: .85rem; } .files-table { font-size: .78rem; } } +/* ── Admin delete ──────────────────────────────────────────────────────── */ +.delete-cell { text-align: center; } +.delete-check { accent-color: #c0392b; width: 16px; height: 16px; cursor: pointer; } +tr.delete-marked td { background: rgba(192,57,43,.08); } + /* ── Admin update panel ────────────────────────────────────────────────── */ .admin-update { margin-top: 32px; } .admin-update h2 { font-size: 1rem; color: var(--text-dim); margin-bottom: 6px; } diff --git a/views/admin/album.erb b/views/admin/album.erb index 49ec4ca..f28d515 100644 --- a/views/admin/album.erb +++ b/views/admin/album.erb @@ -63,6 +63,7 @@ <th>Caption</th> <th>Visible</th> <th>Cover</th> + <th>Delete</th> </tr> </thead> <tbody> @@ -80,6 +81,9 @@ <td class="cover-cell"> <input type="radio" name="album_cover_file" value="<%= name %>" class="cover-radio"<%= ' checked' if @data['cover'] == name %>> </td> + <td class="delete-cell"> + <input type="checkbox" name="file_delete[<%= name %>]" value="1" class="delete-check"> + </td> </tr> <% end %> </tbody> @@ -96,10 +100,24 @@ (function () { const randomCb = document.getElementById('cover-random'); const radios = () => document.querySelectorAll('.cover-radio'); - if (!randomCb) return; - radios().forEach(r => r.addEventListener('change', () => { randomCb.checked = false; })); - randomCb.addEventListener('change', function () { - if (this.checked) radios().forEach(r => { r.checked = false; }); + if (randomCb) { + radios().forEach(r => r.addEventListener('change', () => { randomCb.checked = false; })); + randomCb.addEventListener('change', function () { + if (this.checked) radios().forEach(r => { r.checked = false; }); + }); + } + + document.querySelectorAll('.delete-check').forEach(cb => { + cb.addEventListener('change', function () { + this.closest('tr').classList.toggle('delete-marked', this.checked); + }); + }); + + document.querySelector('form').addEventListener('submit', function (e) { + const count = document.querySelectorAll('.delete-check:checked').length; + if (count > 0 && !confirm(`Permanently delete ${count} photo${count !== 1 ? 's' : ''}? This cannot be undone.`)) { + e.preventDefault(); + } }); })(); </script> |
