From e64d5ffc4f13969c516d38104fe23114f369a58d Mon Sep 17 00:00:00 2001 From: ken Date: Wed, 22 Apr 2026 16:54:21 +0800 Subject: [PATCH] 'js' --- assets/javascripts/app.js | 377 ++++++++++++++++++++------------------ 1 file changed, 202 insertions(+), 175 deletions(-) diff --git a/assets/javascripts/app.js b/assets/javascripts/app.js index db94897..13e12b7 100644 --- a/assets/javascripts/app.js +++ b/assets/javascripts/app.js @@ -729,97 +729,37 @@ $this.attr('title', '按鈕'); } }); - //img無障礙 - $(function() { - /** - * 圖片無障礙 AA 級補強 (人工檢測優化版) - * 1. 處理裝飾性圖片:清空 alt 並移除 title。 - * 2. 解決資訊冗餘:若 alt 與 title 內容相同,移除 title,避免重複朗讀。 - * 3. 自動補強缺失 alt:從父層 title 或鄰近標題文字擷取。 - */ - function fixImageAccessibility() { - $('img').each(function() { - var $img = $(this); - var currentAlt = ($img.attr('alt') || '').trim(); - var currentTitle = ($img.attr('title') || '').trim(); - // --- 1. 處理「裝飾性圖片」標註 (最高優先權) --- - if (currentAlt === "裝飾圖片" || currentTitle === "裝飾圖片") { - $img.attr('alt', ''); - $img.removeAttr('title'); - return; // 跳過此圖 - } + function removeEmptyTitles() { + // 定義所有需要檢查的 class + const targetClasses = [ + '.w-annc__widget-title', + '.widget-title', + '.show-title', + '.event-annc-title', + '.annc-title', + '.sitemenu-title', + '.widget-link__widget-title' + ]; - // --- 2. 解決【alt 與 title 相同】的問題 (人工檢測重點) --- - // 只要兩者內容一致且不為空,就移除 title,保留 alt - if (currentAlt !== '' && currentAlt === currentTitle) { - $img.removeAttr('title'); - // 重新更新變數值供後續邏輯使用 - currentTitle = ''; - } + // 將陣列轉換為 jQuery 選擇器字串 + const selector = targetClasses.join(', '); - // --- 3. 處理「缺失 alt」或「空的 alt」補強邏輯 --- - if (currentAlt === '') { - let finalAlt = ""; + $(selector).each(function() { + // 使用 $.trim 移除空格、換行符號 + // 然後檢查裡面是否完全沒有文字 + const textContent = $.trim($(this).text()); - // A. 嘗試從父層 (如 ) 的 title 抓取描述 - var parentTitle = $img.parent().attr('title'); - - // B. 嘗試抓取同區域內的標題文字 - var nearbyTitle = $img.closest('a').find('h1, h2, h3, h4, h5, h6').first().text().trim(); - - if (parentTitle && parentTitle.trim() !== '') { - finalAlt = parentTitle.trim(); - } else if (nearbyTitle !== '') { - finalAlt = nearbyTitle; - } - - // 寫入最終 alt 結果 - // 如果 finalAlt 仍為空,則設為 "" (符合 AA 規範,視為裝飾圖) - $img.attr('alt', finalAlt); - - // --- 4. 二次清理:補強後的 alt 如果又跟原本的 title 一樣,則移除 title --- - if (currentTitle !== '' && currentTitle === finalAlt) { - $img.removeAttr('title'); - } - } - }); - } - - // 延遲執行確保圖片與動態內容已渲染 - setTimeout(fixImageAccessibility, 500); + if (textContent === "") { + // 如果沒有文字,則將整個元素刪除 + $(this).remove(); + } + }); + } + // 執行 function + $(document).ready(function() { + removeEmptyTitles(); }); - function removeEmptyTitles() { - // 定義所有需要檢查的 class - const targetClasses = [ - '.w-annc__widget-title', - '.widget-title', - '.show-title', - '.event-annc-title', - '.annc-title', - '.sitemenu-title', - '.widget-link__widget-title' - ]; - - // 將陣列轉換為 jQuery 選擇器字串 - const selector = targetClasses.join(', '); - - $(selector).each(function() { - // 使用 $.trim 移除空格、換行符號 - // 然後檢查裡面是否完全沒有文字 - const textContent = $.trim($(this).text()); - - if (textContent === "") { - // 如果沒有文字,則將整個元素刪除 - $(this).remove(); - } - }); - } - - // 執行 function - $(document).ready(function() { - removeEmptyTitles(); - }); $(".w-annc__img-wrap a").each(function () { var $this = $(this); // 確保 內沒有文字節點 (避免重複添加) @@ -879,99 +819,181 @@ $button.attr('aria-label', '播放驗證碼語音'); } }); - //有連結目的之所有a標籤的無障礙處理 - $(function() { - - /** - * 全站無障礙 AA 級補強 (多語系偵測版) - * 支援功能:語系自動偵測、HM1240404E 修正、檔案預知類型、圖片替代文字 - */ - function fixAccessibilityAll() { - // 1. 偵測目前頁面語系 (優先抓取 html lang,若無則預設為 zh-Hant) - var pageLang = $('html').attr('lang') || 'zh-Hant'; - var isEn = pageLang.toLowerCase().startsWith('en'); - - // 2. 定義語系對應字典 - 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' : '連結' - }; - - // --- 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(); - - // 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. 智慧修正與標題補充 (解決 HM1240404E 不明符號問題) - // 檢查是否包含括號 () 或 中英文的另開視窗關鍵字 - 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; + // img無障礙 + $(function() { + /** + * 圖片無障礙 AA 級補強 (人工檢測優化版) + */ + 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(); - var fileInfo = fileExt ? '[' + fileExt.toUpperCase() + '] ' : ''; - $a.attr('title', (fileInfo + 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; + // 判定該連結是否「僅靠圖片傳達目的」(連結內無文字) + var isLinkImage = $parentA.length > 0 && $parentA.text().replace(/\u00a0/g, '').trim() === ""; + + // --- 1. 處理「裝飾性圖片」標註 --- + if (currentAlt === "裝飾圖片" || currentTitle === "裝飾圖片") { + $img.attr('alt', ''); + $img.removeAttr('title'); + return; + } + + // --- 2. 解決【alt 與 title 相同】的問題 --- + if (currentAlt !== '' && currentAlt === currentTitle) { + $img.removeAttr('title'); + currentTitle = ''; + } + + // --- 3. 處理「缺失 alt」或「空的 alt」補強邏輯 --- + // 修改點:若是具有連結用途的圖片,alt 不可為空 + if (currentAlt === '') { + let finalAlt = ""; + + // A. 優先嘗試從父層 () 的 title 抓取 + var parentATitle = $parentA.attr('title'); - if (fileExt !== "") { - // 檔案下載:連結文字 + .副檔名 + 動作 (語系化) - $a.attr('title', linkText + " ." + fileExt + " " + windowTask); - } else if (linkText.length <= 6 || isTag) { - // 短文字:補充完整語意 - $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'); } } + }); + } + + 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'); + + 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' : '連結' + }; + + // --- Part 1: 語言選單修正 --- + var $langBtn = $('#languagebutton'); + if ($langBtn.length > 0) { + $langBtn.attr('title', i18n.langBtn); + $langBtn.removeAttr('aria-label'); } - // D. 最終清理 - $a.removeAttr('aria-label'); - if (linkText !== "" && $img.length > 0) { $img.attr('alt', ''); } - }); - } + // --- Part 2: 全站連結處理 --- + $('a').each(function() { + var $a = $(this); + var href = ($a.attr('href') || '').toLowerCase(); + if (!href || href.startsWith('javascript:')) return; - setTimeout(function() { - fixAccessibilityAll(); - }, 600); - }); + // 清理   與 空白 + 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); + } + } + }); + } + + setTimeout(function() { + fixAccessibilityAll(); + }, 600); + }); // if (location.href.search('editmode=on') === -1) { // // 1. 處理圖片 alt 重複問題 (保持原邏輯但優化) @@ -1189,15 +1211,20 @@ setTimeout(fixJPlayerAccessibility, 500); }); + // 移除 .navbar-brand 內部 display:none 元素的補強 function removeHiddenElementsInsideBrand() { // 選取 .navbar-brand 內部所有隱藏的元素 $('.navbar-brand *').filter(function() { + // 同時判定 inline style 與 CSS class 造成的 display: none return $(this).css('display') === 'none'; - }).remove(); // 徹底從 DOM 中移除該標籤 + }).remove(); // 徹底從 DOM 中移除該標籤,避免無障礙檢測工具誤判 } - // 執行 function - removeHiddenElementsInsideBrand(); + // 使用 setTimeout 確保內容渲染後執行 + // 建議設定 500~800 毫秒,避開大部分動態載入的時間差 + setTimeout(function() { + removeHiddenElementsInsideBrand(); + }, 600); //修正 Alt+M 定位點位置 $(function() { function fixAccessKeyM() {