Skip to content

Commit c741df7

Browse files
authored
Merge pull request #200 from hyperaudio/199-selection-range-fix
199 selection range fix
2 parents eadae45 + 719ef31 commit c741df7

File tree

1 file changed

+50
-82
lines changed

1 file changed

+50
-82
lines changed

js/hyperaudio-lite.js

Lines changed: 50 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*! (C) The Hyperaudio Project. MIT @license: en.wikipedia.org/wiki/MIT_License. */
2-
/*! Version 2.2.0 */
2+
/*! Version 2.2.3 */
33

44
'use strict';
55

@@ -420,7 +420,8 @@ class HyperaudioLite {
420420
//TODO convert to binary search for below for quicker startup
421421
if (this.start && this.end) {
422422
for (let i = 1; i < words.length; i++) {
423-
const wordStart = Math.round(words[i].getAttribute('data-m') / 100) / 10;
423+
let startTime = parseInt(words[i].getAttribute('data-m')) / 1000;
424+
let wordStart = (Math.round(startTime * 100) / 100).toFixed(2);
424425
if (wordStart >= parseFloat(this.start) && parseFloat(this.end) > wordStart) {
425426
words[i].classList.add('share-match');
426427
}
@@ -451,100 +452,67 @@ class HyperaudioLite {
451452
return wordArr;
452453
};
453454

454-
getSelectionMediaFragment = () => {
455-
let fragment = null;
456-
let selection = null;
457-
458-
if (window.getSelection) {
459-
selection = window.getSelection();
460-
} else if (document.selection) {
461-
selection = document.selection.createRange();
462-
}
455+
getSelectionRange = () => {
456+
const selection = window.getSelection();
457+
if (selection.rangeCount === 0) return null;
463458

464-
// check to see if selection is actually inside the transcript
465-
let insideTranscript = false;
466-
let parentElement = selection.focusNode;
467-
while (parentElement !== null) {
468-
if (parentElement.id === this.transcript.id) {
469-
insideTranscript = true;
470-
break;
459+
const range = selection.getRangeAt(0);
460+
461+
// Helper function to get the closest span
462+
function getClosestSpan(node) {
463+
while (node && node.nodeType !== Node.ELEMENT_NODE) {
464+
node = node.parentNode;
471465
}
472-
parentElement = parentElement.parentElement;
466+
return node.closest('span[data-m][data-d]');
473467
}
474468

475-
if (selection.toString() !== '' && insideTranscript === true && selection.focusNode !== null && selection.anchorNode !== null) {
476-
477-
let fNode = selection.focusNode.parentNode;
478-
let aNode = selection.anchorNode.parentNode;
469+
// Get all relevant spans
470+
const allSpans = Array.from(document.querySelectorAll('span[data-m][data-d]'));
479471

480-
if (aNode.tagName === "P") {
481-
aNode = selection.anchorNode.nextElementSibling;
482-
}
472+
// Find the first and last span that contain selected text
473+
let startSpan = null;
474+
let endSpan = null;
475+
let selectedText = range.toString();
476+
let trimmedSelectedText = selectedText.trim();
483477

484-
if (fNode.tagName === "P") {
485-
fNode = selection.focusNode.nextElementSibling;
478+
for (let span of allSpans) {
479+
if (range.intersectsNode(span) && span.textContent.trim() !== '') {
480+
if (!startSpan) startSpan = span;
481+
endSpan = span;
486482
}
483+
}
487484

488-
if (aNode.getAttribute('data-m') === null || aNode.className === 'speaker') {
489-
aNode = aNode.nextElementSibling;
490-
}
491-
492-
if (fNode.getAttribute('data-m') === null || fNode.className === 'speaker') {
493-
fNode = fNode.previousElementSibling;
494-
}
495-
496-
// if the selection starts with a space we want the next element
497-
if(selection.toString().charAt(0) == " " && aNode !== null) {
498-
aNode = aNode.nextElementSibling;
499-
}
500-
501-
if (aNode !== null) {
502-
let aNodeTime = parseInt(aNode.getAttribute('data-m'), 10);
503-
let aNodeDuration = parseInt(aNode.getAttribute('data-d'), 10);
504-
let fNodeTime;
505-
let fNodeDuration;
506-
507-
if (fNode !== null && fNode.getAttribute('data-m') !== null) {
508-
// if the selection ends in a space we want the previous element if it exists
509-
if(selection.toString().slice(-1) == " " && fNode.previousElementSibling !== null) {
510-
fNode = fNode.previousElementSibling;
511-
}
512-
513-
fNodeTime = parseInt(fNode.getAttribute('data-m'), 10);
514-
fNodeDuration = parseInt(fNode.getAttribute('data-d'), 10);
515-
516-
// if the selection starts with a space we want the next element
485+
if (!startSpan || !endSpan) return null;
517486

518-
}
487+
// Adjust start span if selection starts with a space
488+
let startIndex = allSpans.indexOf(startSpan);
489+
while (selectedText.startsWith(' ') && startIndex < allSpans.length - 1) {
490+
startIndex++;
491+
startSpan = allSpans[startIndex];
492+
selectedText = selectedText.slice(1);
493+
}
519494

520-
// 1 decimal place will do
521-
aNodeTime = Math.round(aNodeTime / 100) / 10;
522-
aNodeDuration = Math.round(aNodeDuration / 100) / 10;
523-
fNodeTime = Math.round(fNodeTime / 100) / 10;
524-
fNodeDuration = Math.round(fNodeDuration / 100) / 10;
495+
// Calculate start time
496+
let startTime = parseInt(startSpan.dataset.m) / 1000;
525497

526-
let nodeStart = aNodeTime;
527-
let nodeDuration = Math.round((fNodeTime + fNodeDuration - aNodeTime) * 10) / 10;
498+
// Calculate end time
499+
let endTime = (parseInt(endSpan.dataset.m) + parseInt(endSpan.dataset.d)) / 1000;
528500

529-
if (aNodeTime >= fNodeTime) {
530-
nodeStart = fNodeTime;
531-
nodeDuration = Math.round((aNodeTime + aNodeDuration - fNodeTime) * 10) / 10;
532-
}
501+
// Format to seconds at 2 decimal place precision
502+
let startTimeFormatted = (Math.round(startTime * 100) / 100).toFixed(2);
503+
let endTimeFormatted = (Math.round(endTime * 100) / 100).toFixed(2);
533504

534-
if (nodeDuration === 0 || nodeDuration === null || isNaN(nodeDuration)) {
535-
nodeDuration = 10; // arbitary for now
536-
}
505+
// Only return a range if there's actually selected text (excluding only spaces)
506+
return trimmedSelectedText ? `${startTimeFormatted},${endTimeFormatted}` : null;
507+
}
537508

538-
if (isNaN(parseFloat(nodeStart))) {
539-
fragment = null;
540-
} else {
541-
fragment = this.transcript.id + '=' + nodeStart + ',' + Math.round((nodeStart + nodeDuration) * 10) / 10;
542-
}
543-
}
509+
getSelectionMediaFragment = () => {
510+
let range = this.getSelectionRange();
511+
if (range === null) {
512+
return null;
544513
}
545-
546-
return fragment;
547-
};
514+
return (this.transcript.id + '=' +range);
515+
}
548516

549517
setPlayHead = e => {
550518
const target = e.target ? e.target : e.srcElement;
@@ -852,4 +820,4 @@ if (typeof module !== 'undefined' && module.exports) {
852820
module.exports = { HyperaudioLite };
853821
}
854822

855-
//export default HyperaudioLite;
823+
//export default HyperaudioLite;

0 commit comments

Comments
 (0)