Stop click/drag-on-a-name from reassigning the wrong paragraph (v1.14.13)

The intended flow is: click the broken paragraph's speaker label to open
"Assign to", then supply the name (type, click it anywhere in the text,
or drag-select it). But clicking or drag-selecting a name with no popup
open yet used to open/target a popup for whichever paragraph that name
lived in, silently reassigning THAT paragraph instead of the one the
user meant to fix. Both paths now require an already-open popup before
they do anything — a paragraph is only ever selected via its own
speaker label (or the existing double-click fast-assign shortcut).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-07-09 02:33:56 +02:00
parent 3db3616830
commit cd20df315d
4 changed files with 31 additions and 35 deletions

View File

@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
--- ---
## [1.14.13] — 2026-07-09
### Fixed
- **Clicking or drag-selecting a name could reassign the wrong paragraph** — the intended flow is: click the broken (Unknown/Narrator/misattributed) paragraph's speaker label first to open "Assign to", *then* supply the name by typing, clicking it anywhere in the text, or drag-selecting it. But clicking/dragging a name with no popup open yet used to open (and target) the popup for whichever paragraph that name happened to live in — silently reassigning that paragraph instead of the one the user meant to fix. Clicking/dragging a name now only ever fills an already-open popup; a paragraph is only ever selected via its own speaker label (or the existing double-click-a-name fast-assign shortcut).
---
## [1.14.12] — 2026-07-09 ## [1.14.12] — 2026-07-09
### Added ### Added

View File

@ -1 +1 @@
1.14.12 1.14.13

View File

@ -10,7 +10,7 @@
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<meta name="color-scheme" content="light dark"> <meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#2563EB"> <meta name="theme-color" content="#2563EB">
<meta name="app-version" content="1.14.12"> <meta name="app-version" content="1.14.13">
<link rel="manifest" href="/manifest.webmanifest"> <link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml"> <link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/icon.svg"> <link rel="apple-touch-icon" href="/static/icon.svg">
@ -27,7 +27,7 @@
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── --> <!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css"> <link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/style.css?v=1.14.12"> <link rel="stylesheet" href="/static/style.css?v=1.14.13">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── --> <!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -365,7 +365,7 @@ window.toggleNavTree = function(treeId, chevronId) {
</script> </script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton --> <!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.14.12"></script> <script src="/static/loader.js?v=1.14.13"></script>
</body> </body>
</html> </html>

View File

@ -2797,28 +2797,26 @@ STRIKTE FORMAT- UND TEXTREGELN:
_abOpenAssignPopup(spk.closest('.ab-cv-row'), spk, ''); _abOpenAssignPopup(spk.closest('.ab-cv-row'), spk, '');
return; return;
} }
// Click a name/word inside the narration text itself — same popup, // This guard (rather than just isCollapsed) stops the click that follows
// pre-filled with the clicked word so you don't have to retype it. // a drag's mouseup from also firing the word-click branch below.
// Dragging across several words is handled by the native-selection
// listener below instead of a custom span-drag tracker, so it can't
// conflict with the existing "select text to split this segment" flow,
// which also reacts to window.getSelection() over the same text. That
// listener clears the selection once it acts, so this guard (rather than
// just isCollapsed) stops the click that follows a drag's mouseup from
// re-opening the popup with just the single word under the pointer.
if (_abJustHandledSelection) { _abJustHandledSelection = false; return; } if (_abJustHandledSelection) { _abJustHandledSelection = false; return; }
const txtEl = e.target.closest('.ab-cv-txt'); const txtEl = e.target.closest('.ab-cv-txt');
if (txtEl && window.getSelection().isCollapsed) { if (txtEl && window.getSelection().isCollapsed) {
// Clicking a name/word never opens or retargets a popup by itself —
// only the speaker label (handled above) selects a paragraph. This
// requires an assign popup to already be open (via that label click,
// or a double-click fast-assign): a click on a name anywhere in the
// text then just borrows that name into the search box, targeting
// whichever paragraph was actually selected. Without this, clicking a
// name that happens to live in some OTHER paragraph's text (the
// common way to "grab a name I see") silently reassigned THAT
// paragraph instead of the one the user meant to fix.
if (!assignModeRow) return;
const row = txtEl.closest('.ab-cv-row'); const row = txtEl.closest('.ab-cv-row');
const nameHit = e.target.closest('.ab-name-hit'); const nameHit = e.target.closest('.ab-name-hit');
const clickedWord = nameHit ? (nameHit.dataset.name || nameHit.textContent.trim()) const clickedWord = nameHit ? (nameHit.dataset.name || nameHit.textContent.trim())
: _abWordRangeAtPoint(e.clientX, e.clientY)?.word; : _abWordRangeAtPoint(e.clientX, e.clientY)?.word;
// If a popup is already open targeting a DIFFERENT paragraph, a click on if (assignModeRow !== row) {
// a name elsewhere in the text only borrows that name into the search
// box — it must not silently retarget/highlight the paragraph under the
// cursor, which used to make it look like THAT paragraph was being
// reassigned instead of the one the user actually opened the popup for.
if (assignModeRow && assignModeRow !== row) {
if (clickedWord) { if (clickedWord) {
const inp = assignPopup.querySelector('input'); const inp = assignPopup.querySelector('input');
inp.value = clickedWord; inp.value = clickedWord;
@ -3003,24 +3001,15 @@ STRIKTE FORMAT- UND TEXTREGELN:
// alone for the "Split text to Unknown Speaker" button (wired via // alone for the "Split text to Unknown Speaker" button (wired via
// selectionchange below) instead of hijacking them into the assign popup. // selectionchange below) instead of hijacking them into the assign popup.
if (/^[»„“"']/.test(text)) return; if (/^[»„“"']/.test(text)) return;
// Dragging to select a name only ever feeds an ALREADY-open popup (opened
// by clicking a paragraph's speaker label) — it must not open/retarget a
// popup for whichever paragraph happens to contain the dragged text,
// which used to reassign the wrong paragraph (the one you dragged a name
// out of, not the one you actually meant to fix).
if (!assignModeSeg) return;
_abJustHandledSelection = true; // stop the click that follows this mouseup from also firing _abJustHandledSelection = true; // stop the click that follows this mouseup from also firing
if (assignModeSeg) {
// Popup already open (e.g. from clicking the speaker label) — a short
// selection refines/confirms the assignment directly, as before.
assignName(text); assignName(text);
sel.removeAllRanges(); sel.removeAllRanges();
return;
}
// No popup yet: dragging across a name inside the narration text (e.g. a
// multi-word name like "Sharraz Garthai" the roster doesn't have) opens
// the popup pre-filled with the dragged text instead of doing nothing.
const anchorEl = sel.anchorNode.nodeType === 3 ? sel.anchorNode.parentNode : sel.anchorNode;
const txtSpan = anchorEl.closest('.ab-cv-txt');
if (!txtSpan) { _abJustHandledSelection = false; return; }
const row = txtSpan.closest('.ab-cv-row');
if (!row) { _abJustHandledSelection = false; return; }
_abOpenAssignPopup(row, anchorEl, text);
sel.removeAllRanges();
}); });
chars.addEventListener('click', e => { chars.addEventListener('click', e => {