Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions __TEST__/hyperaudio-lite.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,49 @@ test("updateTranscriptVisualState self-heals after external class rewrites (#251
expect(spans[5].classList.contains("unread")).toBe(true);
});

test("updateTranscriptVisualState tolerates a detached cached word (#265)", () => {
ht.myPlayer = { paused: false };
const detachedWord = ht.wordArr[4].n;
const parent = detachedWord.parentNode;
const nextSibling = detachedWord.nextSibling;
const exactStart = parseInt(detachedWord.dataset.m) / 1000;

detachedWord.remove();
expect(() => ht.updateTranscriptVisualState(exactStart)).not.toThrow();

parent.insertBefore(detachedWord, nextSibling);
ht.refreshWords();
});

test("refreshWords rebuilds DOM caches after transcript edits (#265)", () => {
const firstWord = ht.wordArr[0].n;
const firstParent = firstWord.parentNode;
const firstNextSibling = firstWord.nextSibling;
const replacement = document.createElement("span");
replacement.dataset.m = "12000";
replacement.dataset.d = "500";
replacement.textContent = "replacement ";

firstWord.remove();
document.querySelector("#hypertranscript p:last-of-type").appendChild(replacement);
ht.prevWordIndex = 5;
ht.activeWordElement = ht.wordArr[4].n;
ht.activeParentElement = ht.wordArr[4].n.parentNode;

const refreshed = ht.refreshWords();

expect(refreshed.some(entry => entry.n === firstWord)).toBe(false);
expect(refreshed.some(entry => entry.n === replacement)).toBe(true);
expect(ht.prevWordIndex).toBe(0);
expect(ht.activeWordElement).toBeNull();
expect(ht.activeParentElement).toBeNull();
expect(Array.from(ht.parentElements)).toContain(replacement.parentNode);

replacement.remove();
firstParent.insertBefore(firstWord, firstNextSibling);
ht.refreshWords();
});

test("setPlayHead updates currentTime and plays if playOnClick is true", () => {
ht.playOnClick = true;
ht.myPlayer = { setTime: jest.fn(), play: jest.fn(), paused: true };
Expand Down
2 changes: 2 additions & 0 deletions js/hyperaudio-lite.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export declare class HyperaudioLite {
pauseAutoscroll(): void;
/** Re-enable autoscroll after pauseAutoscroll(). */
resumeAutoscroll(): void;
/** Rebuild cached word and parent references after transcript DOM edits. */
refreshWords(): Array<{ n: HTMLElement; m: number; p: Node }>;
/** Remove all listeners added by this instance and stop the polling loop. */
destroy(): void;
}
Expand Down
25 changes: 23 additions & 2 deletions js/hyperaudio-lite.js
Original file line number Diff line number Diff line change
Expand Up @@ -542,8 +542,24 @@ class HyperaudioLite {

// Setup the transcript words
setupTranscriptWords() {
this.refreshWords();
}

// Rebuild cached word and parent references after transcript DOM edits.
refreshWords() {
if (this.activeWordElement) {
this.activeWordElement.classList.remove('active');
}
if (this.activeParentElement) {
this.activeParentElement.classList.remove('active');
}
this.prevWordIndex = 0;
this.activeWordElement = null;
this.activeParentElement = null;

const words = this.transcript.querySelectorAll('[data-m]');
this.wordArr = this.createWordArray(words);
this.parentTag = null;

// check for elements with data-m attributes that are not directly below <section>
// these will contain <p> or similar that we can scroll to
Expand All @@ -556,7 +572,10 @@ class HyperaudioLite {
}
}

this.parentElements = this.transcript.getElementsByTagName(this.parentTag);
this.parentElements = this.parentTag
? this.transcript.getElementsByTagName(this.parentTag)
: [];
return this.wordArr;
}

setupAutoScroll(autoscroll, scrollContainer) {
Expand Down Expand Up @@ -1051,7 +1070,9 @@ class HyperaudioLite {
if (!this.myPlayer.paused || forceActiveWord) {
activeWord.classList.add('active');
}
activeParent.classList.add('active');
if (activeParent) {
activeParent.classList.add('active');
}
}

this.activeWordElement = activeWord;
Expand Down
25 changes: 23 additions & 2 deletions js/hyperaudio-lite.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -542,8 +542,24 @@ class HyperaudioLite {

// Setup the transcript words
setupTranscriptWords() {
this.refreshWords();
}

// Rebuild cached word and parent references after transcript DOM edits.
refreshWords() {
if (this.activeWordElement) {
this.activeWordElement.classList.remove('active');
}
if (this.activeParentElement) {
this.activeParentElement.classList.remove('active');
}
this.prevWordIndex = 0;
this.activeWordElement = null;
this.activeParentElement = null;

const words = this.transcript.querySelectorAll('[data-m]');
this.wordArr = this.createWordArray(words);
this.parentTag = null;

// check for elements with data-m attributes that are not directly below <section>
// these will contain <p> or similar that we can scroll to
Expand All @@ -556,7 +572,10 @@ class HyperaudioLite {
}
}

this.parentElements = this.transcript.getElementsByTagName(this.parentTag);
this.parentElements = this.parentTag
? this.transcript.getElementsByTagName(this.parentTag)
: [];
return this.wordArr;
}

setupAutoScroll(autoscroll, scrollContainer) {
Expand Down Expand Up @@ -1051,7 +1070,9 @@ class HyperaudioLite {
if (!this.myPlayer.paused || forceActiveWord) {
activeWord.classList.add('active');
}
activeParent.classList.add('active');
if (activeParent) {
activeParent.classList.add('active');
}
}

this.activeWordElement = activeWord;
Expand Down
Loading