diff --git a/assets/javascripts/app.js b/assets/javascripts/app.js index 348c0c7..bff3fcc 100644 --- a/assets/javascripts/app.js +++ b/assets/javascripts/app.js @@ -737,20 +737,20 @@ $(this).attr("style", newStyle); } }); - //表格scope - $("table").each(function() { - $(this).find("tr").each(function(rowIndex) { - $(this).find("th").each(function(colIndex) { - if (rowIndex === 0) { - // 第一列的 th,適用於該欄 - $(this).attr("scope", "col"); - } else if (colIndex === 0) { - // 其他列的第一個 th,適用於該行 - $(this).attr("scope", "row"); - } - }); - }); - }); + // //表格scope + // $("table").each(function() { + // $(this).find("tr").each(function(rowIndex) { + // $(this).find("th").each(function(colIndex) { + // if (rowIndex === 0) { + // // 第一列的 th,適用於該欄 + // $(this).attr("scope", "col"); + // } else if (colIndex === 0) { + // // 其他列的第一個 th,適用於該行 + // $(this).attr("scope", "row"); + // } + // }); + // }); + // }); $(".banner-pager button").addClass('banner-pagerbtn'); $(".banner-pager button").attr("type","button"); $(".banner-pager button").append('下一張'); @@ -794,13 +794,14 @@ $(document).ready(function() { removeEmptyTitles(); }); - $(".w-annc__img-wrap a").each(function () { - var $this = $(this); - // 確保 內沒有文字節點 (避免重複添加) - if ($this.text().trim() === "") { - $this.append('公告圖片'); - } - }); + + // $(".w-annc__img-wrap a").each(function () { + // var $this = $(this); + // // 確保 內沒有文字節點 (避免重複添加) + // if ($this.text().trim() === "") { + // $this.append('公告圖片'); + // } + // }); $(".widget-link__widget-title").each(function () { if ($(this).text().trim() === "") { $(this).append('公告標題'); @@ -858,264 +859,307 @@ /** * 圖片無障礙 AA 級補強 (人工檢測優化版) */ - function fixAccessibilityAll() { - // --- 0. 環境與語言偵測 --- - var urlParams = new URLSearchParams(window.location.search); - var isEditMode = urlParams.get('editmode') === 'on'; - - var pageLang = $('html').attr('lang') || 'zh-Hant'; - var isEn = pageLang.toLowerCase().startsWith('en'); - - var i18n = { - langBtn: isEn ? 'Open language menu in this window' : '在本視窗開啟語言選單', - newWin: isEn ? '(Open in new window)' : '(在新視窗開啟)', - selfWin: isEn ? '(Open in this window)' : '(在本視窗開啟)', - link: isEn ? 'Link' : '連結', - openImg: isEn ? 'Open image' : '開啟圖片內容' - }; - - // --- Part 1: 語言選單修正 --- - var $langBtn = $('#languagebutton'); - if ($langBtn.length > 0) { - $langBtn.attr('title', i18n.langBtn); - $langBtn.removeAttr('aria-label'); - } - - // --- Part 2: 全站連結與圖片邏輯處理 --- - $('a').each(function() { - var $a = $(this); - var href = ($a.attr('href') || '').toLowerCase(); - if (!href || href.startsWith('javascript:')) return; - - // 清理   與 空白 - var linkText = $a.text().replace(/\u00a0/g, '').trim(); - var $img = $a.find('img'); - var currentTitle = ($a.attr('title') || '').trim(); + function fixImageAccessibility() { + $('img').each(function() { + var $img = $(this); + var $parentA = $img.closest('a'); // 取得父層連結 + var currentAlt = ($img.attr('alt') || '').trim(); + var currentTitle = ($img.attr('title') || '').trim(); - // --- 【修正:隱藏空連結】 --- - if (!isEditMode && linkText === "" && $img.length === 0) { - $a.hide(); + // 判定該連結是否「僅靠圖片傳達目的」(連結內無文字) + var isLinkImage = $parentA.length > 0 && $parentA.text().replace(/\u00a0/g, '').trim() === ""; + + // --- 1. 處理「裝飾性圖片」標註 --- + if (currentAlt === "announcement image" || currentAlt === "裝飾圖片" || currentTitle === "裝飾圖片" || currentAlt === "這是一張圖片" || currentTitle === "這是一張圖片") { + $img.attr('alt', ''); + $img.removeAttr('title'); return; } - // A. 視窗動作規範 - var isNewWindow = ($a.attr('target') === '_blank'); - var windowTask = isNewWindow ? i18n.newWin : i18n.selfWin; - - // B. 偵測檔案類型 - var fileExt = ""; - var fileMatches = href.match(/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|odt|ods|odp|zip|rar|jpg|png|csv)$/); - if (fileMatches) { - fileExt = fileMatches[1].toLowerCase(); + // --- 2. 解決【alt 與 title 相同】的問題 --- + if (currentAlt !== '' && currentAlt === currentTitle) { + $img.removeAttr('title'); + currentTitle = ''; } - // C. 智慧修正標題 (移除冗餘資訊) - var hasBadSymbol = currentTitle.includes('()') || - currentTitle.includes('另開新視窗') || - currentTitle.includes('Open in new window') || - currentTitle.includes('/uploads/'); // 移除包含路徑的標題 + // --- 3. 處理「缺失 alt」或「空的 alt」補強邏輯 --- + // 修改點:若是具有連結用途的圖片,alt 不可為空 + if (currentAlt === '') { + let finalAlt = ""; - // D. 處理【HM1240400C】:圖片與文字毗鄰時,替代文字只能有一份 - // 判斷方式:連結內有圖片,且有文字內容(含隱藏文字) - if ($img.length > 0 && linkText !== "") { - // 1. 強制圖片設為裝飾性 (符合規範:鏈結文字只能有一份) - $img.attr('alt', ''); - - // 2. 針對「視覺隱藏但存在於 DOM」的文字 (如 display:none) - var $hiddenSpans = $a.find('span').filter(function() { - return $(this).css('display') === 'none' || $(this).attr('style')?.includes('display: none'); - }); - - if ($hiddenSpans.length > 0) { - $hiddenSpans.each(function() { - var $span = $(this); - // 如果隱藏文字包含檔案路徑,優化為有意義的文字 - if ($span.text().includes('/uploads/')) { - $span.text(i18n.openImg); - } - // 將 display:none 轉為「視覺隱藏、機器可讀」樣式,確保讀屏器可讀取 - $span.css({ - 'display': 'inline-block', - 'position': 'absolute', - 'width': '1px', - 'height': '1px', - 'padding': '0', - 'margin': '-1px', - 'overflow': 'hidden', - 'clip': 'rect(0, 0, 0, 0)', - 'white-space': 'nowrap', - 'border': '0' - }); - }); - } - } - - // E. 重新計算標題與替代文字 (針對不同情境) - if (linkText === "" && $img.length > 0) { - // 情境:純圖片連結 - var rawAlt = ($img.attr('alt') || '').trim(); - var forbidden = ['這是一張圖片', '圖片', 'image', 'photo', '圖', '']; - var imgAlt = forbidden.some(function(txt) { return rawAlt.toLowerCase() === txt; }) - ? ($a.attr('aria-label') || i18n.link) : rawAlt; + // A. 優先嘗試從父層 () 的 title 抓取 + var parentATitle = $parentA.attr('title'); - var fileInfo = fileExt ? '[' + fileExt.toUpperCase() + '] ' : ''; - $a.attr('title', (fileInfo + imgAlt + " " + windowTask).trim()); - $img.attr('alt', imgAlt); - } - else if (linkText !== "") { - // 情境:有文字連結 (無論文字是可見還是透過 Part D 視覺隱藏) - if (fileExt !== "" || hasBadSymbol || currentTitle === "" || currentTitle.includes('/uploads/')) { - if (fileExt !== "") { - $a.attr('title', linkText + " ." + fileExt + " " + windowTask); - } else if (linkText.length <= 8) { - $a.attr('title', linkText + " " + windowTask); - } else { - $a.attr('title', windowTask); - } + // B. 嘗試抓取同區域內的標題文字 + var nearbyTitle = $parentA.find('h1, h2, h3, h4, h5, h6').first().text().trim(); + + if (parentATitle && parentATitle.trim() !== '') { + // 移除 title 內的視窗提示字眼,只取純文字作為 alt + finalAlt = parentATitle.replace(/\(在本視窗開啟\)|\(在新視窗開啟\)/g, '').trim(); + } else if (nearbyTitle !== '') { + finalAlt = nearbyTitle; + } else if (isLinkImage) { + // 如果是連結圖片且都抓不到文字,給予預設值「連結」避免檢測失敗 + finalAlt = "連結"; + } + + $img.attr('alt', finalAlt); + + // --- 4. 二次清理 --- + if (currentTitle !== '' && currentTitle === finalAlt) { + $img.removeAttr('title'); } } - - // F. 最終清理:移除冗餘 label - $a.removeAttr('aria-label'); }); } - // 延遲執行確保 DOM 內容(如圖片路徑)載入完成 - setTimeout(function() { - fixAccessibilityAll(); - }, 800); + setTimeout(fixImageAccessibility, 500); }); - // 有連結目的之所有a標籤的無障礙處理 - $(function() { - /** - * 全站無障礙 AA 級補強 (多語系偵測版) - */ - function fixAccessibilityAll() { - // --- 0. 網址參數判斷 --- - var urlParams = new URLSearchParams(window.location.search); - var isEditMode = urlParams.get('editmode') === 'on'; - var pageLang = $('html').attr('lang') || 'zh-Hant'; - var isEn = pageLang.toLowerCase().startsWith('en'); +// 有連結目的之所有a標籤的無障礙處理 +$(function() { + // Bootstrap 會把原始 title 搬到 data-bs-original-title,先還原回來 + $('a[data-bs-original-title]').each(function() { + var $a = $(this); + var originalTitle = $a.attr('data-bs-original-title'); + if (originalTitle && originalTitle.trim() !== '') { + $a.attr('title', originalTitle); + } + }); - var i18n = { - langBtn: isEn ? 'Open language menu in this window' : '在本視窗開啟語言選單', - newWin: isEn ? '(Open in new window)' : '(在新視窗開啟)', - selfWin: isEn ? '(Open in this window)' : '(在本視窗開啟)', - link: isEn ? 'Link' : '連結' - }; + /** + * 全站無障礙 AA 級補強 (整合 HM1240400C 與 HM1240401C 規範) + */ + function fixAccessibilityAll() { + // --- 0. 環境與語言偵測 --- + //1200ms 後執行(確保所有其他 function 執行完畢後才處理) + //Part 1:修正語言選單按鈕 + //Part 2:掃描所有 + //├─ 排除 navbar-brand、javascript: 連結 + //├─ 空連結 → 隱藏 + //├─ 文字是路徑/空 → 補隱藏 span + //├─ 圖片+文字共存 → 圖片 alt 清空 + //├─ 無 title → 自動產出 title + //├─ 有 title 但與連結文字或圖片 alt 重複 → 只保留開窗提示 + //├─ 有 title 出現空括號 () → 清除並補上正確開窗提示 + //└─ 移除 aria-label + var urlParams = new URLSearchParams(window.location.search); + var isEditMode = urlParams.get('editmode') === 'on'; - // --- Part 1: 語言選單修正 --- - var $langBtn = $('#languagebutton'); - if ($langBtn.length > 0) { - $langBtn.attr('title', i18n.langBtn); - $langBtn.removeAttr('aria-label'); + var pageLang = $('html').attr('lang') || 'zh-Hant'; + var isEn = pageLang.toLowerCase().startsWith('en'); + + var i18n = { + langBtn: isEn ? 'Open language menu in this window' : '在本視窗開啟語言選單', + newWin: isEn ? 'Open in new window' : '在新視窗開啟', + selfWin: isEn ? 'Open in this window' : '在本視窗開啟', + link: isEn ? 'Link' : '連結', + openImg: isEn ? 'Open image' : '開啟圖片內容', + sort: isEn ? 'Sort' : '排序' + }; + + // --- Part 1: 語言選單修正 --- + var $langBtn = $('#languagebutton'); + if ($langBtn.length > 0) { + $langBtn.attr('title', i18n.langBtn); + $langBtn.removeAttr('aria-label'); + } + + // --- Part 2: 全站連結與圖片邏輯處理 --- + $('a').each(function() { + var $a = $(this); + + // 【條件 1:排除特定 class】 + if ($a.hasClass('navbar-brand')) { + return; + } + + var href = ($a.attr('href') || '').toLowerCase(); + if (!href || href.startsWith('javascript:')) return; + + // --- 關鍵修正:先偵測是否有原始 title --- + var rawTitleAttr = $a.attr('title'); + var hasOriginalTitle = (typeof rawTitleAttr !== 'undefined' && rawTitleAttr !== false && rawTitleAttr.trim() !== ""); + + // 清理   與 空白 + var linkText = $a.text().replace(/\u00a0/g, '').trim(); + var $img = $a.find('img'); + var $icons = $a.find('i, svg'); + var currentTitle = ($a.attr('title') || '').trim(); + + // ✅ 在 D 區塊清空 alt 之前,先記錄圖片原始 alt + var imgAltOriginal = $img.length > 0 ? ($img.attr('alt') || '').trim() : ''; + + // --- 【修正 1:隱藏空連結】 --- + if (!isEditMode && linkText === "" && $img.length === 0 && $icons.length === 0) { + $a.hide(); + return; + } + + // --- 【修正 2:文字語意防禦】 --- + if ((linkText === "" || linkText.startsWith('/') || linkText.includes('?')) && $img.length === 0 && $icons.length === 0) { + var accessibleText = i18n.link; + if (href.includes('sort')) { + accessibleText = i18n.sort; + } + + var $span = $a.find('span'); + if ($span.length > 0) { + $span.text(accessibleText); + } else { + $a.append('' + accessibleText + ''); + $span = $a.find('span').last(); } - // --- Part 2: 全站連結處理 --- - $('a').each(function() { - var $a = $(this); - var href = ($a.attr('href') || '').toLowerCase(); - if (!href || href.startsWith('javascript:')) return; - - // 清理   與 空白 - var linkText = $a.text().replace(/\u00a0/g, '').trim(); - var $img = $a.find('img'); - var currentTitle = ($a.attr('title') || '').trim(); - - // --- 【新增修正:隱藏空連結】 --- - // 若網址沒有 editmode=on 時,判定 a 裡面文字為空且沒有圖片,則隱藏 - if (!isEditMode && linkText === "" && $img.length === 0) { - $a.hide(); - return; // 跳過後續處理 - } - - // A. 視窗動作規範 - var isNewWindow = ($a.attr('target') === '_blank'); - var windowTask = isNewWindow ? i18n.newWin : i18n.selfWin; - - // B. 偵測檔案類型 - var fileExt = ""; - var fileMatches = href.match(/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|odt|ods|odp|zip|rar|jpg|png|csv)$/); - if (fileMatches) { - fileExt = fileMatches[1].toLowerCase(); - } - - // C. 智慧修正與標題補充 - var hasBadSymbol = currentTitle.includes('()') || - currentTitle.includes('另開新視窗') || - currentTitle.includes('Open in new window'); - - if (linkText === "" && $img.length > 0) { - // 【純圖片連結】替代文字邏輯 - var rawAlt = ($img.attr('alt') || '').trim(); - var forbidden = ['這是一張圖片', '圖片', 'image', 'photo', '圖', '']; - var imgAlt = forbidden.some(function(txt) { return rawAlt.toLowerCase() === txt; }) - ? ($a.attr('aria-label') || i18n.link) : rawAlt; - - var fileInfo = fileExt ? '[' + fileExt.toUpperCase() + '] ' : ''; - - // 確保具備標題與圖片 alt - $a.attr('title', (fileInfo + imgAlt + " " + windowTask).trim()); - $img.attr('alt', imgAlt); - } - else if (linkText !== "") { - // 【有文字連結】 - if (fileExt !== "" || hasBadSymbol || currentTitle === "") { - var isTag = $a.hasClass('label') || $a.hasClass('tag') || $a.find('[class*="tag"]').length > 0; - - if (fileExt !== "") { - $a.attr('title', linkText + " ." + fileExt + " " + windowTask); - } else if (linkText.length <= 6 || isTag) { - $a.attr('title', linkText + " " + windowTask); - } else { - $a.attr('title', windowTask); - } - } - } - - // --- 修正後的 Part D 最終清理 --- - $a.removeAttr('aria-label'); - - // 只有當「真正可見的文字」存在時,才將圖片設為裝飾性 - // 使用 clone() 移除隱藏元件後再計算文字,確保準確度 - var visibleText = $a.clone().find(':hidden, style, script').remove().end().text().replace(/\u00a0/g, '').trim(); - - if (visibleText !== "" && $img.length > 0) { - $img.attr('alt', ''); - } else { - // 如果文字是隱藏的 (如你的範例),則必須保留或補強圖片 alt - var rawAlt = ($img.attr('alt') || '').trim(); - if (rawAlt === "") { - // 如果連圖片 alt 都沒寫,就從 a 的 title 抓 - var cleanTitle = currentTitle.replace(/\(在本視窗開啟\)|\(在新視窗開啟\)/g, '').trim(); - $img.attr('alt', cleanTitle || i18n.link); - } - } + $span.css({ + 'display': 'inline-block', 'position': 'absolute', 'width': '1px', 'height': '1px', + 'padding': '0', 'margin': '-1px', 'overflow': 'hidden', 'clip': 'rect(0, 0, 0, 0)', + 'white-space': 'nowrap', 'border': '0' }); + linkText = accessibleText; } - setTimeout(function() { - fixAccessibilityAll(); - }, 600); + // A. 視窗動作規範 + var isNewWindow = ($a.attr('target') === '_blank'); + var windowTask = isNewWindow ? i18n.newWin : i18n.selfWin; + + // B. 偵測檔案類型 + var fileExt = ""; + var fileMatches = href.match(/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|odt|ods|odp|zip|rar|jpg|png|csv)$/); + if (fileMatches) { + fileExt = fileMatches[1].toLowerCase(); + } + + // C. 智慧修正標題判斷 + var hasBadSymbol = currentTitle.includes('()') || + currentTitle.includes('Open in new window') || + currentTitle.includes('/uploads/'); + + if (currentTitle.startsWith('/') || currentTitle.includes('?')) { + currentTitle = ""; + } + + // D. 處理【HM1240400C】:圖片與文字毗鄰時,替代文字只能有一份 + if (($img.length > 0 || $icons.length > 0) && linkText !== "") { + if ($img.length > 0) { + $img.attr('alt', ''); + } + + var $hiddenSpans = $a.find('span').filter(function() { + return $(this).css('display') === 'none' || $(this).attr('style')?.includes('display: none'); + }); + + if ($hiddenSpans.length > 0) { + $hiddenSpans.each(function() { + var $span = $(this); + if ($span.text().includes('/uploads/')) { + $span.text(i18n.openImg); + } + + $span.css({ + 'display': 'inline-block', + 'position': 'absolute', + 'width': '1px', + 'height': '1px', + 'padding': '0', + 'margin': '-1px', + 'overflow': 'hidden', + 'clip': 'rect(0, 0, 0, 0)', + 'white-space': 'nowrap', + 'border': '0' + }); + }); + } + } + + // E. 重新計算標題 (關鍵邏輯:若 hasOriginalTitle 為真,則不進入此區塊) + if (!hasOriginalTitle) { + if (linkText === "" && $img.length > 0) { + var rawAlt = ($img.attr('alt') || '').trim(); + var forbidden = ['這是一張圖片', '圖片', 'image', 'photo', '圖', '']; + var imgAlt = forbidden.some(function(txt) { return rawAlt.toLowerCase() === txt; }) + ? ($a.attr('aria-label') || i18n.link) : rawAlt; + + var fileInfo = fileExt ? '[' + fileExt.toUpperCase() + '] ' : ''; + $a.attr('title', (fileInfo + imgAlt + " " + windowTask).trim()); + $img.attr('alt', imgAlt); + } + else if (linkText !== "") { + if (fileExt !== "" || hasBadSymbol || currentTitle === "" || currentTitle.includes('/uploads/')) { + if (fileExt !== "") { + $a.attr('title', linkText + " ." + fileExt + " " + windowTask); + } else { + $a.attr('title', windowTask); + } + } + } + } + + // ✅ 符合 HM1240404E:title 不應與連結文字或圖片 alt 重複 + if (hasOriginalTitle && currentTitle !== "") { + var titleWithoutWindow = currentTitle + .replace('(在本視窗開啟)', '') + .replace('(在新視窗開啟)', '') + .replace('Open in this window', '') + .replace('Open in new window', '') + .replace('另開新視窗前往', '') + .replace('另開新視窗', '') + .trim(); + + // ✅ title 去掉開窗字串後,若與連結文字或圖片原始 alt 相同,則只保留開窗提示 + // ✅ 新增:若 linkText 包含副檔名且 title 也是同樣檔名,也只保留開窗提示 + if ( + (titleWithoutWindow === linkText && linkText !== i18n.link) || + (imgAltOriginal !== '' && titleWithoutWindow === imgAltOriginal) || + (fileExt !== '' && linkText.toLowerCase().endsWith('.' + fileExt) && titleWithoutWindow === linkText) + ) { + $a.attr('title', windowTask); + return; // ✅ 加上 return,避免繼續執行後面的清理邏輯造成二次污染 + } + + // ✅ 最終清理:若 title 出現空括號 () 則移除並補上正確開窗提示 + var finalTitle = ($a.attr('title') || '').trim(); + if (finalTitle.includes('()')) { + finalTitle = finalTitle.replace(/\(\)/g, '').trim(); + $a.attr('title', finalTitle + ' ' + windowTask); + } + } + + // F. 最終清理:移除冗餘 label,避免螢幕閱讀器唸兩次 + $a.removeAttr('aria-label'); }); - // if (location.href.search('editmode=on') === -1) { - // // 1. 處理圖片 alt 重複問題 (保持原邏輯但優化) - // $('a').has('img').each(function () { - // const $link = $(this); - // const linkText = $link.text().trim(); + // ✅ G. 補強特定連結(須在迴圈外,因為 javascript: 連結被迴圈跳過) + $('.btn-o a').each(function() { + var $a = $(this); + if (!$a.attr('target')) { + $a.attr('target', '_blank'); + $a.attr('rel', 'noopener noreferrer'); + } + $a.attr('title', isEn ? 'Share to X Open in new window' : '分享至 X 在新視窗開啟'); + }); - // $link.find('img').each(function () { - // const $img = $(this); - // const altText = $img.attr('alt')?.trim(); + $('.print-button a').each(function() { + var $a = $(this); + if (!$a.attr('title') || $a.attr('title').trim().toLowerCase() === 'print') { + $a.attr('title', isEn ? 'Print this page' : '列印此頁'); + } + }); - // if (altText && altText === linkText) { - // $img.attr('alt', ''); - // } - // }); - // }); - // } + $('a.i-archive__file-name').each(function () { + var $a = $(this); + var isNewWindow = ($a.attr('target') === '_blank'); + $a.attr('title', isNewWindow ? i18n.newWin : i18n.selfWin); + }); + } + + // ✅ 延遲 800ms 確保所有其他 function 執行完畢後才處理 + setTimeout(function() { + fixAccessibilityAll(); + }, 800); +}); + + $(function() { function smartFixIcons() { var isEn = $('html').attr('lang') === 'en'; @@ -1291,9 +1335,9 @@ $(function() { // 已有 show,不處理 } else { $('.nav-level-1').removeClass('show'); - $('.nav-level-0>li>.menu-drop').removeClass('opened'); // ✅ 先清除其他的 opened + $('.nav-level-0>li>.menu-drop').removeClass('opened'); // 先清除其他的 opened $(this).parent().find('.nav-level-1').addClass('show'); - $(this).parent().find('.menu-drop').addClass('opened'); // ✅ 加上 opened + $(this).parent().find('.menu-drop').addClass('opened'); // 加上 opened } }); @@ -1302,7 +1346,7 @@ $(function() { setTimeout(function() { if ($li.find(':focus').length === 0) { $li.find('.nav-level-1').removeClass('show'); - $li.find('.menu-drop').removeClass('opened'); // ✅ 移除 opened + $li.find('.menu-drop').removeClass('opened'); // 移除 opened } }, 50); }); @@ -1313,9 +1357,9 @@ $(function() { // 已有 show,不處理 } else { $('.nav-level-2').removeClass('show'); - $('.nav-level-1>li>.menu-drop').removeClass('opened'); // ✅ 先清除其他的 opened + $('.nav-level-1>li>.menu-drop').removeClass('opened'); // 先清除其他的 opened $(this).parent().find('.nav-level-2').addClass('show'); - $(this).parent().find('.menu-drop').addClass('opened'); // ✅ 加上 opened + $(this).parent().find('.menu-drop').addClass('opened'); // 加上 opened } }); @@ -1324,11 +1368,12 @@ $(function() { setTimeout(function() { if ($li.find(':focus').length === 0) { $li.find('.nav-level-2').removeClass('show'); - $li.find('.menu-drop').removeClass('opened'); // ✅ 移除 opened + $li.find('.menu-drop').removeClass('opened'); // 移除 opened } }, 50); }); + $('.controlplay a[role="radio"]').on('click', function () { // 取消所有的 aria-checked 並還原文字 $('.controlplay a[role="radio"]').attr('aria-checked', 'false'); @@ -1465,92 +1510,7 @@ $(function() { resizeTimer = setTimeout(fixAccessKeyM, 150); }); }); - //公告頁籤無障礙 - $(function() { - function fixTabLinearOrder() { - // ✅ 只處理內部含有 .tab_nav 的 .w-annc - var $targetAnnc = $('.w-annc').filter(function() { - return $(this).find('.tab_nav').length > 0; - }); - // 若頁面上沒有符合條件的 .w-annc,直接離開 - if ($targetAnnc.length === 0) return; - - var $tabs = $targetAnnc.find('.filter_tab'); - var $anncList = $targetAnnc.find('.w-annc__list'); - - // --- 1. 順向遊走:標籤 -> 對應內容 --- - $tabs.on('keydown', function(e) { - if (e.which === 9 && !e.shiftKey) { - var category = $(this).data('category'); - var $scope = $(this).closest('.w-annc'); // ✅ 限定在同一個 .w-annc - var $visibleItems = $scope.find('.w-annc__item[data-category="' + category + '"]:visible'); - - if ($visibleItems.length > 0) { - var $firstLink = $visibleItems.find('a').first(); - if ($firstLink.length > 0) { - e.preventDefault(); - $firstLink.focus(); - } - } - } - if (e.which === 13 || e.which === 32) { - e.preventDefault(); - $(this).click(); - } - }); - - // --- 2. 反向遊走:內容 -> 對應標籤 --- - $anncList.on('keydown', '.w-annc__item a', function(e) { - var $currentLink = $(this); - var $currentItem = $currentLink.closest('.w-annc__item'); - var $scope = $currentLink.closest('.w-annc'); // ✅ 限定在同一個 .w-annc - var category = $currentItem.data('category'); - var $visibleItems = $scope.find('.w-annc__item[data-category="' + category + '"]:visible'); - - if (e.which === 9 && e.shiftKey) { - if ($currentLink.is($visibleItems.find('a').first())) { - e.preventDefault(); - $scope.find('.filter_tab[data-category="' + category + '"]').focus(); - } - } - - if (e.which === 9 && !e.shiftKey) { - if ($currentLink.is($visibleItems.find('a').last())) { - var $currentTab = $scope.find('.filter_tab[data-category="' + category + '"]'); - var $nextTab = $currentTab.next('.filter_tab'); - if ($nextTab.length > 0) { - e.preventDefault(); - $nextTab.focus(); - $nextTab.click(); - } - } - } - }); - - // --- 3. 反向遊走:標籤 -> 上一個標籤的內容最後一個連結 --- - $tabs.on('keydown', function(e) { - if (e.which === 9 && e.shiftKey) { - var $scope = $(this).closest('.w-annc'); // ✅ 限定在同一個 .w-annc - var $prevTab = $(this).prev('.filter_tab'); - if ($prevTab.length > 0) { - var prevCategory = $prevTab.data('category'); - $prevTab.click(); - - var $prevItems = $scope.find('.w-annc__item[data-category="' + prevCategory + '"]:visible'); - var $lastLink = $prevItems.find('a').last(); - - if ($lastLink.length > 0) { - e.preventDefault(); - $lastLink.focus(); - } - } - } - }); - } - - fixTabLinearOrder(); - }); //導覽選單子選單無障礙狀態修正 (支援多層級) function fixNavMenuA11y() { const $navItems = $('#main-nav li'); @@ -1616,64 +1576,253 @@ $(function() { // 延遲執行,確保動態產生的選單也能被補強 setTimeout(fixEmptyHref, 500); }); - //頁籤無障礙 + //公告頁籤無障礙 $(function() { - /** - * 頁籤導覽 (Tab Navigation) 無障礙補強 - * 1. 容器加入 role="tablist" - * 2. 項目加入 role="tab" - * 3. 動態切換 aria-selected="true/false" - * 4. 確保 Enter 鍵可以觸發切換 (針對 tabindex="0" 的優化) - */ - function fixTabAccessibility() { - var $tabNav = $('.tab_nav'); - var $tabs = $tabNav.find('.filter_tab'); - - // --- 1. 初始化結構語意 --- - $tabNav.attr('role', 'tablist'); - - $tabs.each(function() { - var $this = $(this); - var isActive = $this.hasClass('active'); - - // 加入頁籤角色與選取狀態 - $this.attr({ - 'role': 'tab', - 'aria-selected': isActive ? 'true' : 'false' - }); - - // 修正 Title:讓使用者知道這是切換頁籤 - var tabName = $this.text().trim(); - $this.attr('title', '切換至 ' + tabName); + function fixTabLinearOrder() { + var $targetAnnc = $('.w-annc').filter(function() { + return $(this).find('.tab_nav').length > 0; }); - // --- 2. 監聽切換事件 (點擊或鍵盤) --- - // 假設原本已有切換邏輯,我們在此「補強」屬性的變更 - $tabs.on('click', function() { - var $clickedTab = $(this); + if ($targetAnnc.length === 0) return; - // 先將所有頁籤設為未選取 - $tabs.attr('aria-selected', 'false'); + var $tabs = $targetAnnc.find('.filter_tab'); + var $anncList = $targetAnnc.find('.w-annc__list'); - // 將當前點擊的頁籤設為已選取 - $clickedTab.attr('aria-selected', 'true'); - }); + // 智慧判斷:偵測 filter_tab 用哪個屬性來分類 + // 優先用 data-tags(若有值),否則用 data-category + function getTabKey($tab) { + var tags = $tab.data('tags'); + if (tags && tags !== '') return { attr: 'data-tags', value: tags }; + var category = $tab.data('category'); + if (category && category !== '') return { attr: 'data-category', value: category }; + return null; + } + + // 根據 key 找對應的可見 w-annc__item + function getVisibleItems($scope, key) { + if (!key) return $(); + return $scope.find('.w-annc__item[' + key.attr + '="' + key.value + '"]:visible'); + } + + // 根據 w-annc__item 反查對應的 filter_tab + function getTabFromItem($item, $scope) { + var tags = $item.data('tags'); + if (tags && tags !== '') { + var $tab = $scope.find('.filter_tab[data-tags="' + tags + '"]'); + if ($tab.length) return $tab; + } + var category = $item.data('category'); + if (category && category !== '') { + var $tab = $scope.find('.filter_tab[data-category="' + category + '"]'); + if ($tab.length) return $tab; + } + return $(); + } - // --- 3. 鍵盤操作補強 (針對 tabindex="0" 的非按鈕元素) --- $tabs.on('keydown', function(e) { - // 當按下 Enter (13) 或 Space (32) 時觸發點擊 + var $scope = $(this).closest('.w-annc'); + var key = getTabKey($(this)); + + // 順向 Tab:頁籤 -> 對應內容第一個連結 + if (e.which === 9 && !e.shiftKey) { + var $visibleItems = getVisibleItems($scope, key); + if ($visibleItems.length > 0) { + var $firstLink = $visibleItems.find('a').first(); + if ($firstLink.length > 0) { + e.preventDefault(); + $firstLink.focus(); + } + } + } + + // 反向 Shift+Tab:頁籤 -> 上一個頁籤的內容最後一個連結 + if (e.which === 9 && e.shiftKey) { + var $prevTab = $(this).prev('.filter_tab'); + if ($prevTab.length > 0) { + var prevKey = getTabKey($prevTab); + $prevTab.click(); + + var $prevItems = getVisibleItems($scope, prevKey); + var $lastLink = $prevItems.find('a').last(); + + if ($lastLink.length > 0) { + e.preventDefault(); + $lastLink.focus(); + } + } + } + + // Enter / Space:切換頁籤並移焦點到內容第一個連結 if (e.which === 13 || e.which === 32) { e.preventDefault(); - $(this).trigger('click'); + $(this).click(); + + var $visibleItems = getVisibleItems($scope, key); + var $firstLink = $visibleItems.find('a').first(); + if ($firstLink.length > 0) { + setTimeout(function() { $firstLink.focus(); }, 50); + } + } + }); + + // 反向遊走:內容 -> 對應標籤;Tab 到最後一個連結跳下一個頁籤 + $anncList.on('keydown', '.w-annc__item a', function(e) { + var $currentLink = $(this); + var $currentItem = $currentLink.closest('.w-annc__item'); + var $scope = $currentLink.closest('.w-annc'); + + // 找出當前 item 對應的 filter_tab + var $currentTab = getTabFromItem($currentItem, $scope); + if (!$currentTab.length) return; + + // 找出同類別的所有可見 item + var key = getTabKey($currentTab); + var $visibleItems = getVisibleItems($scope, key); + + // Shift+Tab:若是第一個連結,跳回對應頁籤 + if (e.which === 9 && e.shiftKey) { + if ($currentLink.is($visibleItems.find('a').first())) { + e.preventDefault(); + $currentTab.focus(); + } + } + + // Tab:若是最後一個連結,跳到下一個頁籤並切換 + if (e.which === 9 && !e.shiftKey) { + if ($currentLink.is($visibleItems.find('a').last())) { + var $nextTab = $currentTab.next('.filter_tab'); + if ($nextTab.length > 0) { + e.preventDefault(); + $nextTab.focus(); + $nextTab.click(); + } + } } }); } - // 延遲執行確保頁籤元件已渲染 + setTimeout(fixTabLinearOrder, 500); + }); + //頁籤無障礙 + $(function () { + function fixTabAccessibility() { + + // 各自獨立處理每組 .tab_nav,避免多組時跨區塊操作問題 + $('.tab_nav').each(function() { + var $tabNav = $(this); + var $tabs = $tabNav.find('li'); + + // --- 1. 初始化結構語意 --- + $tabNav.attr('role', 'tablist'); + + $tabs.each(function () { + var $this = $(this); + var isActive = $this.hasClass('active'); + + $this.attr({ + 'role': 'tab', + 'aria-selected': isActive ? 'true' : 'false', + 'tabindex': isActive ? '0' : '-1' + }); + + var tabName = $this.text().trim(); + $this.attr('title', '切換至 ' + tabName); + }); + + // --- 2. 點擊事件:更新 aria-selected 與 tabindex --- + $tabs.on('click', function () { + var $clickedTab = $(this); + + $tabs.attr('aria-selected', 'false').attr('tabindex', '-1'); + $clickedTab.attr('aria-selected', 'true').attr('tabindex', '0'); + }); + + // --- 3. 鍵盤操作補強 --- + $tabs.on('keydown', function (e) { + var $current = $(this); + var currentIndex = $tabs.index($current); + var lastIndex = $tabs.length - 1; + var $target = null; + + switch (e.which) { + case 13: // Enter + case 32: // Space + e.preventDefault(); + $current.trigger('click'); + break; + + case 37: // ← 左方向鍵:移到前一個頁籤 + case 38: // ↑ 上方向鍵 + e.preventDefault(); + $target = currentIndex > 0 + ? $tabs.eq(currentIndex - 1) + : $tabs.eq(lastIndex); + break; + + case 39: // → 右方向鍵:移到下一個頁籤 + case 40: // ↓ 下方向鍵 + e.preventDefault(); + $target = currentIndex < lastIndex + ? $tabs.eq(currentIndex + 1) + : $tabs.eq(0); + break; + + case 36: // Home:跳到第一個頁籤 + e.preventDefault(); + $target = $tabs.eq(0); + break; + + case 35: // End:跳到最後一個頁籤 + e.preventDefault(); + $target = $tabs.eq(lastIndex); + break; + } + + // 移動焦點並觸發點擊切換 + if ($target && $target.length) { + $tabs.attr('tabindex', '-1'); + $target.attr('tabindex', '0').focus().trigger('click'); + } + }); + }); + } + setTimeout(fixTabAccessibility, 500); }); + + + //移除 accesskey="W" 之後的 | 符號 + $(function() { + function removeSeparatorAfterAccesskeyW() { + $('a[accesskey="W"]').each(function() { + // 取得 a 標籤相鄰的下一個 DOM 節點 + var nextNode = this.nextSibling; + + // 檢查節點是否存在、是否為文字節點 (nodeType 3),且是否包含 '|' + if (nextNode && nextNode.nodeType === 3) { + var textValue = nextNode.nodeValue; + if (textValue.includes('|')) { + // 將 '|' 替換為空字串並更新節點值 + nextNode.nodeValue = textValue.replace('|', ''); + } + } + }); + } + + // 執行處理 + removeSeparatorAfterAccesskeyW(); + }); + if ($('#language-li').is(':empty') || $('#language-li').html().trim() === '') { + $('#language-li').remove(); + }; + $('.header-nav').each(function() { + const cleaned = $(this).text().replace(/[\s|]/g, ''); + if (cleaned === '') { + $(this).remove(); + } + }); } - forFreeGo(); + forFreeGo();