summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKen D'Ambrosio <ken@jots.org>2026-06-12 14:01:48 +0000
committerKen D'Ambrosio <ken@jots.org>2026-06-12 14:01:48 +0000
commitcfb814470864785565f33e4bebd2aca7e67c16ae (patch)
tree4c9dff75a5e18bbeff964fef8d8856365633bbdf
parentb9a3ce6942e917c8e5046d652b7742cfe5f960ec (diff)
Move bulk selection to admin cluster detail page (correct page)
The bulk selection panel now lives on /admin/people/:uuid — the face crop grid page — which is what was actually requested. A sticky left panel shows the cluster name, the name form, a selection counter, and bulk action controls. Clicking a face crop toggles selection; clicking the photo link still opens the album. Bulk actions: move selected faces to a named person, move to pool, or blacklist. The per-face individual dropdowns are replaced by the panel. Merge-entire-cluster and Blacklist-cluster moved to collapsible/button in the panel too. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--app.rb40
-rw-r--r--public/css/style.css32
-rw-r--r--views/admin/person_detail.erb327
3 files changed, 243 insertions, 156 deletions
diff --git a/app.rb b/app.rb
index ece8b18..9f02422 100644
--- a/app.rb
+++ b/app.rb
@@ -1007,6 +1007,46 @@ post '/admin/people/:uuid/delete' do
redirect '/admin/people'
end
+post '/admin/people/:uuid/bulk_move' do
+ require_admin!
+ src = params[:uuid]
+ action = params['bulk_action'].to_s.strip
+ entries = Array(params['entries[]']).map { |e| JSON.parse(e) rescue nil }.compact
+
+ halt 400 if entries.empty?
+
+ data = load_people_data
+ people = data['people'] || {}
+ halt 404 unless people.key?(src)
+
+ to_move = entries.filter_map do |e|
+ rel = e['rel']; box = e['box'].map(&:to_i)
+ people[src]['members'].find { |m| m['rel'] == rel && m['box'].map(&:to_i) == box }
+ end
+
+ people[src]['members'] -= to_move
+
+ case action
+ when 'blacklist'
+ data['blacklist'] ||= []
+ data['blacklist'].concat(to_move)
+ when 'pool'
+ people['__pool__'] ||= { 'name' => '__pool__', 'slug' => nil, 'members' => [] }
+ people['__pool__']['members'].concat(to_move)
+ when /\A[0-9a-f-]{36}\z/
+ halt 404 unless people.key?(action)
+ people[action]['members'].concat(to_move)
+ else
+ halt 400
+ end
+
+ people.delete(src) if people[src]['members'].empty?
+ data['people'] = people
+ atomic_write(PEOPLE_PATH, JSON.pretty_generate(data))
+
+ people[src] ? redirect("/admin/people/#{src}") : redirect('/admin/people')
+end
+
post '/admin/people/:uuid/merge' do
require_admin!
src = params[:uuid]
diff --git a/public/css/style.css b/public/css/style.css
index 93345ad..a549143 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -548,30 +548,34 @@ tr.delete-marked td { background: rgba(192,57,43,.08); }
.person-name { padding: 7px 10px 2px; font-size: .9rem; font-weight: 500; color: var(--text); }
.person-count { padding: 0 10px 8px; font-size: .75rem; color: var(--text-dim); }
-/* ── Person page bulk selection ────────────────────────────────────────── */
+/* ── Shared bulk-action panel (person detail + person page) ────────────── */
+.person-detail-wrap,
.person-page-wrap { display: flex; align-items: flex-start; gap: 0; }
.bulk-panel {
- width: 220px; flex-shrink: 0; position: sticky; top: 16px;
+ width: 230px; flex-shrink: 0; position: sticky; top: 16px; max-height: calc(100vh - 32px);
+ overflow-y: auto;
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius);
padding: 14px 16px; margin-right: 20px;
}
-.bulk-panel-title { font-size: .95rem; font-weight: 600; margin: 0 0 8px; }
+.bulk-panel-title { font-size: .95rem; font-weight: 600; margin: 0 0 4px; }
.bulk-panel-hint { font-size: .82rem; color: var(--text-dim); margin: 0; }
-.bulk-selected-count { font-size: .9rem; margin: 0; }
.bulk-sep { border: none; border-top: 1px solid var(--border); margin: 12px 0; }
.bulk-label { display: block; font-size: .8rem; color: var(--text-dim); margin-bottom: 4px; }
.bulk-select, .bulk-input {
width: 100%; background: var(--bg3); border: 1px solid var(--border);
border-radius: var(--radius); color: var(--text); padding: 5px 8px;
- font-size: .82rem; margin-bottom: 8px; box-sizing: border-box;
+ font-size: .82rem; margin-bottom: 6px; box-sizing: border-box;
}
.bulk-select:focus, .bulk-input:focus { border-color: var(--accent); outline: none; }
.bulk-action-btn { width: 100%; text-align: center; }
+.person-detail-main,
.person-page-main { flex: 1; min-width: 0; }
-/* Selectable photo cards */
-.selectable-card { cursor: pointer; position: relative; }
-.selectable-card .thumb-wrap { position: relative; }
+/* Selectable cards (photo grid and face grid) */
+.selectable-card,
+.selectable-face { cursor: pointer; position: relative; }
+.selectable-card .thumb-wrap,
+.selectable-face .face-detail-thumb { position: relative; }
.select-checkbox {
position: absolute; top: 6px; left: 6px;
width: 20px; height: 20px; border-radius: 4px;
@@ -581,8 +585,10 @@ tr.delete-marked td { background: rgba(192,57,43,.08); }
opacity: 0; transition: opacity .12s;
pointer-events: none;
}
-.selectable-card:hover .select-checkbox { opacity: 1; }
-.selectable-card.selected .select-checkbox {
+.selectable-card:hover .select-checkbox,
+.selectable-face:hover .select-checkbox { opacity: 1; }
+.selectable-card.selected .select-checkbox,
+.selectable-face.selected .select-checkbox {
opacity: 1; background: var(--accent); border-color: var(--accent);
}
.selectable-card.selected .select-checkbox::after {
@@ -591,8 +597,10 @@ tr.delete-marked td { background: rgba(192,57,43,.08); }
border: 2px solid #fff; border-top: none; border-left: none;
transform: rotate(45deg) translate(-1px, -1px);
}
-.selectable-card.selected .thumb-wrap img { opacity: .75; }
-.selectable-card.selected { outline: 2px solid var(--accent); outline-offset: -2px; }
+.selectable-card.selected .thumb-wrap img,
+.selectable-face.selected .face-detail-thumb img { opacity: .75; }
+.selectable-card.selected,
+.selectable-face.selected { outline: 2px solid var(--accent); outline-offset: -2px; }
/* ── Admin upload ──────────────────────────────────────────────────────── */
.admin-upload { margin-top: 32px; }
diff --git a/views/admin/person_detail.erb b/views/admin/person_detail.erb
index 7a1ea0a..fbce8af 100644
--- a/views/admin/person_detail.erb
+++ b/views/admin/person_detail.erb
@@ -1,158 +1,208 @@
-<div class="admin-people">
- <div class="admin-nav">
- <a href="/admin/people" class="btn btn-sm">← All Clusters</a>
- <% if @name && !@is_pool %>
- <a href="/people/<%= ERB::Util.url_encode(@name.downcase.gsub(/[^a-z0-9]+/,'-').gsub(/^-+|-+$/,'')) %>"
- class="btn btn-sm" target="_blank">Public Page ↗</a>
- <% end %>
- </div>
+<div class="person-detail-wrap">
- <h1><%= ERB::Util.html_escape(@title) %></h1>
- <p class="update-hint"><%= @count %> photo<%= @count == 1 ? '' : 's' %> in this cluster</p>
+ <%# ── Left action panel ──────────────────────────────────────────────────── %>
+ <aside class="bulk-panel" id="bulk-panel">
+ <div class="admin-nav" style="margin-bottom:14px">
+ <a href="/admin/people" class="btn btn-sm">← All Clusters</a>
+ <% if @name && !@is_pool %>
+ <a href="/people/<%= ERB::Util.url_encode(@name.downcase.gsub(/[^a-z0-9]+/,'-').gsub(/^-+|-+$/,'')) %>"
+ class="btn btn-sm" target="_blank">↗</a>
+ <% end %>
+ </div>
- <div class="people-info-box">
- <% if @is_pool %>
- These faces were removed from other clusters and are waiting to be assigned.
- Use <strong>Assign to&hellip;</strong> to move a face to a named person, or
- <strong>Blacklist</strong> to permanently exclude it from future clustering.
- <% else %>
- <strong>Move to pool</strong> sends a face to a holding area where you can assign it later.
- <strong>Blacklist cluster</strong> permanently excludes all faces in this cluster from future clustering.
- <br>
- <em>No photo files are ever modified &mdash; only the clustering metadata is affected.</em>
- <% end %>
- </div>
+ <h2 class="bulk-panel-title"><%= ERB::Util.html_escape(@title) %></h2>
+ <p class="bulk-panel-hint"><%= @count %> face<%= @count == 1 ? '' : 's' %></p>
- <% unless @is_pool %>
- <%# ── Name ──────────────────────────────────────────────────────────────── %>
- <section style="margin-bottom:20px">
- <% if @count == 1 && !@name %>
- <% m = @members.first; rel = m['rel']; box = m['box'] %>
- <% parts = rel.split('/'); fname = parts.last; dir_rel = parts[0..-2].join('/') %>
- <% album_url = dir_rel.empty? ? '/browse/' : "/browse/#{ERB::Util.html_escape(dir_rel)}" %>
- <div class="new-person-hero">
- <div class="face-detail-thumb" data-thumb="/thumb/<%= ERB::Util.html_escape(rel) %>">
- <a href="<%= album_url %>?photo=<%= ERB::Util.url_encode(fname) %>" target="_blank">
- <img src="/face/<%= ERB::Util.html_escape(rel) %>?box=<%= ERB::Util.html_escape(box.join(',')) %>"
- width="140" height="140">
- </a>
+ <% unless @is_pool %>
+ <%# ── Name form ─────────────────────────────────────────────────────────── %>
+ <div style="margin-top:14px">
+ <% if @count == 1 && !@name %>
+ <% m = @members.first; rel = m['rel']; box = m['box'] %>
+ <% parts = rel.split('/'); fname = parts.last; dir_rel = parts[0..-2].join('/') %>
+ <% album_url = dir_rel.empty? ? '/browse/' : "/browse/#{ERB::Util.html_escape(dir_rel)}" %>
+ <div class="new-person-hero">
+ <div class="face-detail-thumb" data-thumb="/thumb/<%= ERB::Util.html_escape(rel) %>">
+ <a href="<%= album_url %>?photo=<%= ERB::Util.url_encode(fname) %>" target="_blank">
+ <img src="/face/<%= ERB::Util.html_escape(rel) %>?box=<%= ERB::Util.html_escape(box.join(',')) %>"
+ width="140" height="140">
+ </a>
+ </div>
+ <p class="update-hint" style="margin-top:6px;font-size:.75rem">Hover to preview · Click to open</p>
</div>
- <p class="update-hint" style="margin-top:8px">Hover to see full photo &middot; Click to open in album</p>
- </div>
+ <% end %>
+ <form id="name-form" method="post" action="/admin/people/<%= ERB::Util.url_encode(@uuid) %>" class="name-form">
+ <input type="text" name="name" id="name-input"
+ value="<%= ERB::Util.html_escape(@name.to_s) %>"
+ placeholder="Name this person…" class="name-input bulk-input" autocomplete="off">
+ <button type="submit" class="btn btn-sm bulk-action-btn" style="margin-top:4px">Save Name</button>
+ </form>
+ <form id="merge-into-form" method="post"
+ action="/admin/people/<%= ERB::Util.url_encode(@uuid) %>/merge" style="display:none">
+ <input type="hidden" name="into" id="merge-into-uuid">
+ </form>
+ </div>
<% end %>
- <form id="name-form" method="post" action="/admin/people/<%= ERB::Util.url_encode(@uuid) %>" class="name-form">
- <input type="text" name="name" id="name-input"
- value="<%= ERB::Util.html_escape(@name.to_s) %>"
- placeholder="Enter name…" class="name-input" style="max-width:300px"
- autocomplete="off">
- <button type="submit" class="btn">Save Name</button>
- </form>
- <form id="merge-into-form" method="post"
- action="/admin/people/<%= ERB::Util.url_encode(@uuid) %>/merge"
- style="display:none">
- <input type="hidden" name="into" id="merge-into-uuid">
- </form>
- </section>
-
- <%# ── Merge entire cluster ──────────────────────────────────────────────── %>
- <% unless @all_others.empty? %>
- <section class="admin-update" style="margin-bottom:28px">
- <h2>Merge entire cluster into another person</h2>
- <p class="update-hint">Moves all <%= @count %> photos and removes this cluster.</p>
- <form method="post" action="/admin/people/<%= ERB::Util.url_encode(@uuid) %>/merge"
- style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
- <select name="into" class="name-input" style="max-width:300px">
- <option value="">— Select person —</option>
- <% @all_others.each do |p| %>
- <option value="<%= ERB::Util.html_escape(p[:uuid]) %>"><%= ERB::Util.html_escape(p[:name]) %></option>
+
+ <hr class="bulk-sep">
+
+ <%# ── Selection actions ──────────────────────────────────────────────────── %>
+ <p class="bulk-panel-hint" id="bulk-hint">Click faces to select them.</p>
+ <p style="display:none;margin:0" id="bulk-count">
+ <strong id="bulk-n">0</strong> selected
+ &nbsp;<a href="#" id="bulk-clear" style="font-size:.8rem">Clear</a>
+ </p>
+
+ <form id="bulk-form" method="post"
+ action="/admin/people/<%= ERB::Util.url_encode(@uuid) %>/bulk_move"
+ style="display:none;margin-top:10px">
+ <div id="bulk-entries"></div>
+ <label class="bulk-label">Action</label>
+ <select name="bulk_action" id="bulk-action-select" class="bulk-select">
+ <option value="">— Choose action —</option>
+ <% @named_others.each do |p| %>
+ <option value="<%= ERB::Util.html_escape(p[:uuid]) %>">Move to: <%= ERB::Util.html_escape(p[:name]) %></option>
<% end %>
+ <option value="pool">Move to pool</option>
+ <option value="blacklist">Blacklist</option>
</select>
- <button type="submit" class="btn"
- onclick="return confirm('Merge all <%= @count %> photos into the selected person?')">Merge</button>
- </form>
- </section>
- <% end %>
-
- <%# ── Delete cluster (blacklists all members) ───────────────────────────── %>
- <section style="margin-bottom:28px">
- <form method="post" action="/admin/people/<%= ERB::Util.url_encode(@uuid) %>/delete"
- onsubmit="return confirm('Blacklist this entire cluster (<%= @count %> photo<%= @count == 1 ? '' : 's' %>)? These faces will never be re-clustered.')">
- <button type="submit" class="btn btn-danger">Blacklist cluster</button>
+ <button type="submit" class="btn btn-sm bulk-action-btn"
+ onclick="return confirmBulk()">Apply</button>
</form>
- </section>
- <% else %>
- <%# ── Pool: blacklist all ────────────────────────────────────────────────── %>
- <section style="margin-bottom:28px">
- <form method="post" action="/admin/people/__pool__/blacklist_all"
- onsubmit="return confirm('Blacklist all <%= @count %> faces in the pool? They will never be re-clustered.')">
- <button type="submit" class="btn btn-danger">Blacklist all in pool</button>
- </form>
- </section>
- <% end %>
- <%# ── Face grid ──────────────────────────────────────────────────────────── %>
- <p class="update-hint">
- Hover a face to see the full photo. Click to open in the album.
+ <hr class="bulk-sep" style="margin-top:16px">
+
+ <%# ── Cluster-level actions ──────────────────────────────────────────────── %>
+ <% unless @is_pool || @all_others.empty? %>
+ <details style="margin-bottom:10px">
+ <summary class="bulk-label" style="cursor:pointer">Merge entire cluster…</summary>
+ <form method="post" action="/admin/people/<%= ERB::Util.url_encode(@uuid) %>/merge"
+ style="margin-top:8px">
+ <select name="into" class="bulk-select">
+ <option value="">— Select person —</option>
+ <% @all_others.each do |p| %>
+ <option value="<%= ERB::Util.html_escape(p[:uuid]) %>"><%= ERB::Util.html_escape(p[:name]) %></option>
+ <% end %>
+ </select>
+ <button type="submit" class="btn btn-sm btn-danger bulk-action-btn"
+ style="margin-top:4px"
+ onclick="return confirm('Merge all <%= @count %> faces into the selected person?')">Merge</button>
+ </form>
+ </details>
+ <% end %>
+
<% if @is_pool %>
- Use the drop-down to assign a face to a named person, or blacklist it permanently.
- <% elsif !@named_others.empty? %>
- Use the drop-down to move a face to another person.
+ <form method="post" action="/admin/people/__pool__/blacklist_all"
+ onsubmit="return confirm('Blacklist all <%= @count %> faces in the pool?')">
+ <button type="submit" class="btn btn-sm btn-danger bulk-action-btn">Blacklist all in pool</button>
+ </form>
+ <% else %>
+ <form method="post" action="/admin/people/<%= ERB::Util.url_encode(@uuid) %>/delete"
+ onsubmit="return confirm('Blacklist all <%= @count %> faces in this cluster? They will never be re-clustered.')">
+ <button type="submit" class="btn btn-sm btn-danger bulk-action-btn">Blacklist cluster</button>
+ </form>
<% end %>
- </p>
-
- <div class="face-detail-grid">
- <% @members.each do |m| %>
- <% rel = m['rel']; box = m['box'] %>
- <% parts = rel.split('/'); fname = parts.last; dir_rel = parts[0..-2].join('/') %>
- <% album_url = dir_rel.empty? ? '/browse/' : "/browse/#{ERB::Util.html_escape(dir_rel)}" %>
- <div class="face-detail-card">
- <div class="face-detail-thumb" data-thumb="/thumb/<%= ERB::Util.html_escape(rel) %>">
- <a href="<%= album_url %>?photo=<%= ERB::Util.url_encode(fname) %>" target="_blank">
- <img src="/face/<%= ERB::Util.html_escape(rel) %>?box=<%= ERB::Util.html_escape(box.join(',')) %>"
- width="100" height="100" loading="lazy">
- </a>
+
+ <div class="people-info-box" style="margin-top:14px">
+ <% if @is_pool %>
+ Faces removed from other clusters. Assign them to a person or blacklist permanently.
+ <% else %>
+ <strong>Move to pool</strong> holds faces for later review.
+ <strong>Blacklist</strong> permanently excludes from clustering.
+ <em>No photo files are ever modified.</em>
+ <% end %>
+ </div>
+ </aside>
+
+ <%# ── Face grid ──────────────────────────────────────────────────────────── %>
+ <div class="person-detail-main">
+ <div class="face-detail-grid" id="face-grid">
+ <% @members.each do |m| %>
+ <% rel = m['rel']; box = m['box'] %>
+ <% parts = rel.split('/'); fname = parts.last; dir_rel = parts[0..-2].join('/') %>
+ <% album_url = dir_rel.empty? ? '/browse/' : "/browse/#{ERB::Util.html_escape(dir_rel)}" %>
+ <% entry_json = ERB::Util.html_escape({rel: rel, box: box}.to_json) %>
+ <div class="face-detail-card selectable-face" data-entry="<%= entry_json %>">
+ <div class="face-detail-thumb" data-thumb="/thumb/<%= ERB::Util.html_escape(rel) %>">
+ <a href="<%= album_url %>?photo=<%= ERB::Util.url_encode(fname) %>" target="_blank"
+ class="face-photo-link">
+ <img src="/face/<%= ERB::Util.html_escape(rel) %>?box=<%= ERB::Util.html_escape(box.join(',')) %>"
+ width="100" height="100" loading="lazy">
+ </a>
+ <span class="select-checkbox" aria-hidden="true"></span>
+ </div>
</div>
- <% if @is_pool || !@named_others.empty? %>
- <form method="post" action="/admin/people/<%= ERB::Util.url_encode(@uuid) %>/move"
- class="face-move-form">
- <input type="hidden" name="rel" value="<%= ERB::Util.html_escape(rel) %>">
- <input type="hidden" name="box" value="<%= ERB::Util.html_escape(box.to_json) %>">
- <select name="to" class="face-move-select"
- onchange="if(this.value) this.form.submit()">
- <option value=""><%= @is_pool ? 'Assign to…' : 'Move to…' %></option>
- <% @named_others.each do |p| %>
- <option value="<%= ERB::Util.html_escape(p[:uuid]) %>"><%= ERB::Util.html_escape(p[:name]) %></option>
- <% end %>
- <% unless @is_pool %>
- <option value="new">New person</option>
- <option value="remove">Move to pool</option>
- <% end %>
- <option value="blacklist">Blacklist</option>
- </select>
- </form>
- <% end %>
- </div>
- <% end %>
+ <% end %>
+ </div>
</div>
-</div>
+
+</div><%# .person-detail-wrap %>
<div id="face-hover-preview"><img alt=""></div>
<script>
(function () {
- const preview = document.getElementById('face-hover-preview');
- const pimg = preview.querySelector('img');
-
+ // ── Hover preview ────────────────────────────────────────────────────────
+ var preview = document.getElementById('face-hover-preview');
+ var pimg = preview.querySelector('img');
function reposition(e) {
- const pw = 312, ph = 312;
- let x = e.clientX + 18, y = e.clientY + 18;
+ var pw = 312, ph = 312;
+ var x = e.clientX + 18, y = e.clientY + 18;
if (x + pw > window.innerWidth) x = e.clientX - pw - 8;
if (y + ph > window.innerHeight) y = e.clientY - ph - 8;
- preview.style.left = x + 'px';
- preview.style.top = y + 'px';
+ preview.style.left = x + 'px'; preview.style.top = y + 'px';
+ }
+ document.querySelectorAll('.face-detail-thumb').forEach(function (el) {
+ el.addEventListener('mouseenter', function (e) {
+ pimg.src = el.dataset.thumb; preview.style.display = 'block'; reposition(e);
+ });
+ el.addEventListener('mousemove', reposition);
+ el.addEventListener('mouseleave', function () { preview.style.display = 'none'; });
+ });
+
+ // ── Selection ────────────────────────────────────────────────────────────
+ var selected = new Set();
+
+ function updatePanel() {
+ var n = selected.size;
+ document.getElementById('bulk-n').textContent = n;
+ document.getElementById('bulk-count').style.display = n ? '' : 'none';
+ document.getElementById('bulk-hint').style.display = n ? 'none' : '';
+ document.getElementById('bulk-form').style.display = n ? '' : 'none';
}
- // Duplicate name check
+ document.querySelectorAll('.selectable-face').forEach(function (card) {
+ card.addEventListener('click', function (e) {
+ if (e.target.closest('.face-photo-link') && !e.target.closest('.select-checkbox')) return;
+ e.preventDefault();
+ var key = card.dataset.entry;
+ if (selected.has(key)) { selected.delete(key); card.classList.remove('selected'); }
+ else { selected.add(key); card.classList.add('selected'); }
+ updatePanel();
+ });
+ });
+
+ document.getElementById('bulk-clear').addEventListener('click', function (e) {
+ e.preventDefault();
+ selected.clear();
+ document.querySelectorAll('.selectable-face.selected').forEach(function (c) { c.classList.remove('selected'); });
+ updatePanel();
+ });
+
+ window.confirmBulk = function () {
+ var action = document.getElementById('bulk-action-select').value;
+ if (!action) { alert('Please choose an action.'); return false; }
+ var c = document.getElementById('bulk-entries');
+ c.innerHTML = '';
+ selected.forEach(function (entry) {
+ var inp = document.createElement('input');
+ inp.type = 'hidden'; inp.name = 'entries[]'; inp.value = entry;
+ c.appendChild(inp);
+ });
+ var label = document.getElementById('bulk-action-select').selectedOptions[0].text;
+ return confirm(selected.size + ' face(s) → ' + label + '. Continue?');
+ };
+
+ // ── Duplicate name check ─────────────────────────────────────────────────
var existingNames = <%= @existing_names_json || '[]' %>;
var nameForm = document.getElementById('name-form');
var nameInput = document.getElementById('name-input');
@@ -168,7 +218,7 @@
if (match) {
e.preventDefault();
var msg = '"' + match.name + '" already exists.\n\n'
- + 'OK — add this photo to ' + match.name + "'s cluster\n"
+ + 'OK — add this face to ' + match.name + "'s cluster\n"
+ 'Cancel — create a new separate person named "' + val + '"';
if (confirm(msg)) {
document.getElementById('merge-into-uuid').value = match.uuid;
@@ -180,16 +230,5 @@
}
});
}
-
- document.querySelectorAll('.face-detail-thumb').forEach(function (el) {
- el.querySelector('img').style.cursor = 'zoom-in';
- el.addEventListener('mouseenter', function (e) {
- pimg.src = el.dataset.thumb;
- preview.style.display = 'block';
- reposition(e);
- });
- el.addEventListener('mousemove', reposition);
- el.addEventListener('mouseleave', function () { preview.style.display = 'none'; });
- });
})();
</script>