diff --git a/main.js b/main.js index 02082ad..df30a86 100644 --- a/main.js +++ b/main.js @@ -174,7 +174,7 @@ function toggleSearchAccordion(clickedButton, line) { // Pause autoplay if it's running const stopButton = document.getElementById('stopBtn'); - if (isPlaying && stopButton) { + if (stopButton) { stopButton.click(); } @@ -1094,6 +1094,13 @@ function showPronunciationPopup( if (playButton) { playButton.addEventListener('click', (e) => { e.stopPropagation(); // 保持 stopPropagation 以避免 headerBtn 也響應 + + stopSingleWordLoop(); // 【新增】 + g_mainPlaybackIndexBeforeLoop = null; // 【新增】 + if (isPlaying) { + stopPlayback(); + } + const header = playButton.closest('.accordion-header'); const panel = header ? header.nextElementSibling : null; if (header && panel && !header.classList.contains('active')) { @@ -2415,6 +2422,9 @@ function initializeAppUI() { } function performSearch(page = 1, itemsPerPage = 50) { + stopSingleWordLoop(); // 【新增】 + g_mainPlaybackIndexBeforeLoop = null; // 【新增】 + const selectedDialect = document.querySelector( '#search-popup input[name="dialect"]:checked', ).value; @@ -3444,6 +3454,24 @@ function initializeAppUI() { document.addEventListener('keydown', globalKeydownHandler); + // --- 【新增】全域音檔衝突處理:播放任何音檔時,若正在單詞循環則停止之 --- + document.addEventListener( + 'play', + (event) => { + if (event.target.tagName === 'AUDIO' && isSingleWordLooping) { + if ( + event.target !== singleLoopingAudio.word && + event.target !== singleLoopingAudio.sentence + ) { + console.log('偵測到其他音檔播放,停止單詞循環。'); + stopSingleWordLoop(); + g_mainPlaybackIndexBeforeLoop = null; + } + } + }, + true, + ); + document.addEventListener('click', (event) => { if (event.target.tagName === 'BUTTON' || event.target.closest('button')) { const button = @@ -3730,6 +3758,10 @@ function initializeAppUI() { // --- generate() 函式從這裡開始 --- function generate(content, initialCategory = null, targetRowId = null) { console.log('Generate called for:', content.name); + + stopSingleWordLoop(); // 【新增】 + g_mainPlaybackIndexBeforeLoop = null; // 【新增】 + currentActiveDialectLevelFullName = getFullLevelName(content.name); g_currentLevelData = [...content.content]; // Create a mutable copy to be sorted @@ -3876,6 +3908,9 @@ function initializeAppUI() { ) { const contentContainer = document.getElementById('generated'); + stopSingleWordLoop(); // 【新增】 + g_mainPlaybackIndexBeforeLoop = null; // 【新增】 + // 1. Reset global state for the new category g_currentDialectInfo = dialectInfo; g_currentCategory = category; @@ -4157,6 +4192,8 @@ function initializeAppUI() { // --- Auto Bookmark Mode: Add play event listener with proper cleanup --- const wordPlayHandler = () => { + stopSingleWordLoop(); // 【新增】 + g_mainPlaybackIndexBeforeLoop = null; // 【新增】 const autoBookmarkEnabled = localStorage.getItem('autoBookmarkMode') === 'true'; if (autoBookmarkEnabled && dialectInfo.腔 && dialectInfo.級) { @@ -4235,6 +4272,8 @@ function initializeAppUI() { // --- Auto Bookmark Mode: Add play event listener with proper cleanup --- const sentencePlayHandler = () => { + stopSingleWordLoop(); // 【新增】 + g_mainPlaybackIndexBeforeLoop = null; // 【新增】 const autoBookmarkEnabled = localStorage.getItem('autoBookmarkMode') === 'true'; if (autoBookmarkEnabled && dialectInfo.腔 && dialectInfo.級) { @@ -4396,6 +4435,9 @@ function initializeAppUI() { return; } + stopSingleWordLoop(); // 【新增】 + g_mainPlaybackIndexBeforeLoop = null; // 【新增】 + // 重設狀態 // 【新增此行】用當前時間戳記產生一個獨一無二的 ID playbackSessionId = Date.now(); @@ -4622,6 +4664,7 @@ function initializeAppUI() { currentAudio = null; currentAudioIndex = 0; removeNowPlaying(); + stopSingleWordLoop(); // 【新增】 updateMediaSession(null); // --- 【整合 Media Session】 --- const pauseResumeButton = document.getElementById('pauseResumeBtn'); @@ -4898,6 +4941,8 @@ function initializeAppUI() { if (stopButton) { stopButton.onclick = function () { + stopSingleWordLoop(); // 【新增】 + g_mainPlaybackIndexBeforeLoop = null; // 【新增】 if (isPlaying) { stopPlayback(); } @@ -5634,7 +5679,7 @@ function toggleAccordion(event, line, dialectInfo) { // Pause autoplay if it's running const stopButton = document.getElementById('stopBtn'); - if (isPlaying && stopButton) { + if (stopButton) { stopButton.click(); } diff --git a/server.log b/server.log deleted file mode 100644 index e69de29..0000000 diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000..fbace68 --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,7 @@ +{ + "status": "failed", + "failedTests": [ + "918302d1937b88b99808-4d1eeff0c71276426eba", + "918302d1937b88b99808-34f5346cfa950e8fc8e0" + ] +} \ No newline at end of file diff --git a/test-results/loop-Single-word-loop-should-stop-when-searching/error-context.md b/test-results/loop-Single-word-loop-should-stop-when-searching/error-context.md new file mode 100644 index 0000000..88c7f2a --- /dev/null +++ b/test-results/loop-Single-word-loop-should-stop-when-searching/error-context.md @@ -0,0 +1,144 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e2]: + - generic [ref=e3]: + - heading "logo客源翠 HakSpring" [level=2] [ref=e4]: + - link "logo客源翠 HakSpring" [ref=e5] [cursor=pointer]: + - /url: / + - img "logo" [ref=e6] + - text: 客源翠 HakSpring + - heading "你个客援隊/客研隊。詞典+分類學習" [level=4] [ref=e7] + - heading "擇進前个進度" [level=3] [ref=e8]: + - combobox [ref=e9] [cursor=pointer]: + - option "擇進前个進度" [disabled] [selected] + - button "看說明文件" [ref=e10] [cursor=pointer]: + - generic [ref=e11]:  + - button "客語羅馬字轉換工具" [ref=e12] [cursor=pointer]: + - generic [ref=e13]:  + - textbox "查語詞" [ref=e15] + - group [ref=e17]: + - generic "認證詞彙學習主控板" [ref=e18] [cursor=pointer] + - paragraph [ref=e19]: + - text: 先擇腔調摎級別, + - generic [ref=e20]: + - text: 四縣: + - link "基礎" [ref=e22] [cursor=pointer]: + - /url: "#" + - link "初級" [ref=e24] [cursor=pointer]: + - /url: "#" + - link "中級" [ref=e26] [cursor=pointer]: + - /url: "#" + - link "中高" [ref=e28] [cursor=pointer]: + - /url: "#" + - link "高級" [ref=e30] [cursor=pointer]: + - /url: "#" + - generic [ref=e31]: + - text: 海陸: + - link "基礎" [ref=e33] [cursor=pointer]: + - /url: "#" + - link "初級" [ref=e35] [cursor=pointer]: + - /url: "#" + - link "中級" [ref=e37] [cursor=pointer]: + - /url: "#" + - link "中高" [ref=e39] [cursor=pointer]: + - /url: "#" + - link "高級" [ref=e41] [cursor=pointer]: + - /url: "#" + - generic [ref=e42]: + - text: 大埔: + - link "基礎" [ref=e44] [cursor=pointer]: + - /url: "#" + - link "初級" [ref=e46] [cursor=pointer]: + - /url: "#" + - link "中級" [ref=e48] [cursor=pointer]: + - /url: "#" + - link "中高" [ref=e50] [cursor=pointer]: + - /url: "#" + - link "高級" [ref=e52] [cursor=pointer]: + - /url: "#" + - generic [ref=e53]: + - text: 饒平: + - link "基礎" [ref=e55] [cursor=pointer]: + - /url: "#" + - link "初級" [ref=e57] [cursor=pointer]: + - /url: "#" + - link "中級" [ref=e59] [cursor=pointer]: + - /url: "#" + - link "中高" [ref=e61] [cursor=pointer]: + - /url: "#" + - link "高級" [ref=e63] [cursor=pointer]: + - /url: "#" + - generic [ref=e64]: + - text: 詔安: + - link "基礎" [ref=e66] [cursor=pointer]: + - /url: "#" + - link "初級" [ref=e68] [cursor=pointer]: + - /url: "#" + - link "中級" [ref=e70] [cursor=pointer]: + - /url: "#" + - link "中高" [ref=e72] [cursor=pointer]: + - /url: "#" + - link "高級" [ref=e74] [cursor=pointer]: + - /url: "#" + - generic [ref=e75]: + - text: 再擇類別: + - generic [ref=e76] [cursor=pointer]: + - radio "人體與醫療" [ref=e77] + - text: 人體與醫療 + - generic [ref=e78] [cursor=pointer]: + - radio "心理活動與感覺" [ref=e79] + - text: 心理活動與感覺 + - generic [ref=e80] [cursor=pointer]: + - radio "代詞" [ref=e81] + - text: 代詞 + - generic [ref=e82] [cursor=pointer]: + - radio "外在活動與動作" [ref=e83] + - text: 外在活動與動作 + - generic [ref=e84] [cursor=pointer]: + - radio "生物" [ref=e85] + - text: 生物 + - generic [ref=e86] [cursor=pointer]: + - radio "自然與景觀" [ref=e87] + - text: 自然與景觀 + - generic [ref=e88] [cursor=pointer]: + - radio "事物狀態與變化" [ref=e89] + - text: 事物狀態與變化 + - generic [ref=e90] [cursor=pointer]: + - radio "居家生活" [ref=e91] + - text: 居家生活 + - generic [ref=e92] [cursor=pointer]: + - radio "抽象概念與形容" [ref=e93] + - text: 抽象概念與形容 + - generic [ref=e94] [cursor=pointer]: + - radio "法律、政治與軍事" [ref=e95] + - text: 法律、政治與軍事 + - generic [ref=e96] [cursor=pointer]: + - radio "社會關係與行為" [ref=e97] + - text: 社會關係與行為 + - generic [ref=e98] [cursor=pointer]: + - radio "時空與情狀副詞" [ref=e99] + - text: 時空與情狀副詞 + - generic [ref=e100] [cursor=pointer]: + - radio "特殊詞類" [ref=e101] + - text: 特殊詞類 + - generic [ref=e102] [cursor=pointer]: + - radio "通訊、建設與交通" [ref=e103] + - text: 通訊、建設與交通 + - generic [ref=e104] [cursor=pointer]: + - radio "歲時祭儀、習俗與宗教" [ref=e105] + - text: 歲時祭儀、習俗與宗教 + - generic [ref=e106] [cursor=pointer]: + - radio "數詞量詞" [ref=e107] + - text: 數詞量詞 + - generic [ref=e108] [cursor=pointer]: + - radio "職業與經濟" [ref=e109] + - text: 職業與經濟 + - generic [ref=e110] [cursor=pointer]: + - radio "藝文與教育" [ref=e111] + - text: 藝文與教育 + - text:    + - text:       + - text:   +``` \ No newline at end of file diff --git a/test-results/loop-Single-word-loop-should-stop-when-switching-category/error-context.md b/test-results/loop-Single-word-loop-should-stop-when-switching-category/error-context.md new file mode 100644 index 0000000..88c7f2a --- /dev/null +++ b/test-results/loop-Single-word-loop-should-stop-when-switching-category/error-context.md @@ -0,0 +1,144 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e2]: + - generic [ref=e3]: + - heading "logo客源翠 HakSpring" [level=2] [ref=e4]: + - link "logo客源翠 HakSpring" [ref=e5] [cursor=pointer]: + - /url: / + - img "logo" [ref=e6] + - text: 客源翠 HakSpring + - heading "你个客援隊/客研隊。詞典+分類學習" [level=4] [ref=e7] + - heading "擇進前个進度" [level=3] [ref=e8]: + - combobox [ref=e9] [cursor=pointer]: + - option "擇進前个進度" [disabled] [selected] + - button "看說明文件" [ref=e10] [cursor=pointer]: + - generic [ref=e11]:  + - button "客語羅馬字轉換工具" [ref=e12] [cursor=pointer]: + - generic [ref=e13]:  + - textbox "查語詞" [ref=e15] + - group [ref=e17]: + - generic "認證詞彙學習主控板" [ref=e18] [cursor=pointer] + - paragraph [ref=e19]: + - text: 先擇腔調摎級別, + - generic [ref=e20]: + - text: 四縣: + - link "基礎" [ref=e22] [cursor=pointer]: + - /url: "#" + - link "初級" [ref=e24] [cursor=pointer]: + - /url: "#" + - link "中級" [ref=e26] [cursor=pointer]: + - /url: "#" + - link "中高" [ref=e28] [cursor=pointer]: + - /url: "#" + - link "高級" [ref=e30] [cursor=pointer]: + - /url: "#" + - generic [ref=e31]: + - text: 海陸: + - link "基礎" [ref=e33] [cursor=pointer]: + - /url: "#" + - link "初級" [ref=e35] [cursor=pointer]: + - /url: "#" + - link "中級" [ref=e37] [cursor=pointer]: + - /url: "#" + - link "中高" [ref=e39] [cursor=pointer]: + - /url: "#" + - link "高級" [ref=e41] [cursor=pointer]: + - /url: "#" + - generic [ref=e42]: + - text: 大埔: + - link "基礎" [ref=e44] [cursor=pointer]: + - /url: "#" + - link "初級" [ref=e46] [cursor=pointer]: + - /url: "#" + - link "中級" [ref=e48] [cursor=pointer]: + - /url: "#" + - link "中高" [ref=e50] [cursor=pointer]: + - /url: "#" + - link "高級" [ref=e52] [cursor=pointer]: + - /url: "#" + - generic [ref=e53]: + - text: 饒平: + - link "基礎" [ref=e55] [cursor=pointer]: + - /url: "#" + - link "初級" [ref=e57] [cursor=pointer]: + - /url: "#" + - link "中級" [ref=e59] [cursor=pointer]: + - /url: "#" + - link "中高" [ref=e61] [cursor=pointer]: + - /url: "#" + - link "高級" [ref=e63] [cursor=pointer]: + - /url: "#" + - generic [ref=e64]: + - text: 詔安: + - link "基礎" [ref=e66] [cursor=pointer]: + - /url: "#" + - link "初級" [ref=e68] [cursor=pointer]: + - /url: "#" + - link "中級" [ref=e70] [cursor=pointer]: + - /url: "#" + - link "中高" [ref=e72] [cursor=pointer]: + - /url: "#" + - link "高級" [ref=e74] [cursor=pointer]: + - /url: "#" + - generic [ref=e75]: + - text: 再擇類別: + - generic [ref=e76] [cursor=pointer]: + - radio "人體與醫療" [ref=e77] + - text: 人體與醫療 + - generic [ref=e78] [cursor=pointer]: + - radio "心理活動與感覺" [ref=e79] + - text: 心理活動與感覺 + - generic [ref=e80] [cursor=pointer]: + - radio "代詞" [ref=e81] + - text: 代詞 + - generic [ref=e82] [cursor=pointer]: + - radio "外在活動與動作" [ref=e83] + - text: 外在活動與動作 + - generic [ref=e84] [cursor=pointer]: + - radio "生物" [ref=e85] + - text: 生物 + - generic [ref=e86] [cursor=pointer]: + - radio "自然與景觀" [ref=e87] + - text: 自然與景觀 + - generic [ref=e88] [cursor=pointer]: + - radio "事物狀態與變化" [ref=e89] + - text: 事物狀態與變化 + - generic [ref=e90] [cursor=pointer]: + - radio "居家生活" [ref=e91] + - text: 居家生活 + - generic [ref=e92] [cursor=pointer]: + - radio "抽象概念與形容" [ref=e93] + - text: 抽象概念與形容 + - generic [ref=e94] [cursor=pointer]: + - radio "法律、政治與軍事" [ref=e95] + - text: 法律、政治與軍事 + - generic [ref=e96] [cursor=pointer]: + - radio "社會關係與行為" [ref=e97] + - text: 社會關係與行為 + - generic [ref=e98] [cursor=pointer]: + - radio "時空與情狀副詞" [ref=e99] + - text: 時空與情狀副詞 + - generic [ref=e100] [cursor=pointer]: + - radio "特殊詞類" [ref=e101] + - text: 特殊詞類 + - generic [ref=e102] [cursor=pointer]: + - radio "通訊、建設與交通" [ref=e103] + - text: 通訊、建設與交通 + - generic [ref=e104] [cursor=pointer]: + - radio "歲時祭儀、習俗與宗教" [ref=e105] + - text: 歲時祭儀、習俗與宗教 + - generic [ref=e106] [cursor=pointer]: + - radio "數詞量詞" [ref=e107] + - text: 數詞量詞 + - generic [ref=e108] [cursor=pointer]: + - radio "職業與經濟" [ref=e109] + - text: 職業與經濟 + - generic [ref=e110] [cursor=pointer]: + - radio "藝文與教育" [ref=e111] + - text: 藝文與教育 + - text:    + - text:       + - text:   +``` \ No newline at end of file