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
49 changes: 47 additions & 2 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -1094,6 +1094,13 @@ function showPronunciationPopup(
if (playButton) {
playButton.addEventListener('click', (e) => {
e.stopPropagation(); // 保持 stopPropagation 以避免 headerBtn 也響應

stopSingleWordLoop(); // 【新增】
g_mainPlaybackIndexBeforeLoop = null; // 【新增】
Comment on lines +1098 to +1099
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These two lines for resetting the single-word loop state are repeated in at least 8 other places in this file. To improve code maintainability and adhere to the DRY (Don't Repeat Yourself) principle, I recommend extracting this logic into a new helper function.

You could define a function like this, for example after the stopSingleWordLoop function definition:

function resetSingleWordLoopState() {
  stopSingleWordLoop();
  g_mainPlaybackIndexBeforeLoop = null;
}

Then, you can replace this block and all similar occurrences with a single call: resetSingleWordLoopState();.

Suggested change
stopSingleWordLoop(); // 【新增】
g_mainPlaybackIndexBeforeLoop = null; // 【新增】
resetSingleWordLoopState();

if (isPlaying) {
stopPlayback();
}

const header = playButton.closest('.accordion-header');
const panel = header ? header.nextElementSibling : null;
if (header && panel && !header.classList.contains('active')) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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('偵測到其他音檔播放,停止單詞循環。');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This console.log statement appears to be for debugging. It's best to remove it before merging to keep the browser console clean in the production environment.

stopSingleWordLoop();
g_mainPlaybackIndexBeforeLoop = null;
}
}
},
true,
);

document.addEventListener('click', (event) => {
if (event.target.tagName === 'BUTTON' || event.target.closest('button')) {
const button =
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.級) {
Expand Down Expand Up @@ -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.級) {
Expand Down Expand Up @@ -4396,6 +4435,9 @@ function initializeAppUI() {
return;
}

stopSingleWordLoop(); // 【新增】
g_mainPlaybackIndexBeforeLoop = null; // 【新增】

// 重設狀態
// 【新增此行】用當前時間戳記產生一個獨一無二的 ID
playbackSessionId = Date.now();
Expand Down Expand Up @@ -4622,6 +4664,7 @@ function initializeAppUI() {
currentAudio = null;
currentAudioIndex = 0;
removeNowPlaying();
stopSingleWordLoop(); // 【新增】
updateMediaSession(null); // --- 【整合 Media Session】 ---

const pauseResumeButton = document.getElementById('pauseResumeBtn');
Expand Down Expand Up @@ -4898,6 +4941,8 @@ function initializeAppUI() {

if (stopButton) {
stopButton.onclick = function () {
stopSingleWordLoop(); // 【新增】
g_mainPlaybackIndexBeforeLoop = null; // 【新增】
if (isPlaying) {
stopPlayback();
}
Expand Down Expand Up @@ -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();
}

Expand Down
Empty file removed server.log
Empty file.
7 changes: 7 additions & 0 deletions test-results/.last-run.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"status": "failed",
"failedTests": [
"918302d1937b88b99808-4d1eeff0c71276426eba",
"918302d1937b88b99808-34f5346cfa950e8fc8e0"
]
}
Original file line number Diff line number Diff line change
@@ -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:  
```
Loading