diff --git a/assets/javascripts/app.js b/assets/javascripts/app.js index a0a18c0..d78b0ff 100644 --- a/assets/javascripts/app.js +++ b/assets/javascripts/app.js @@ -760,13 +760,13 @@ $(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('公告標題'); @@ -835,7 +835,7 @@ var isLinkImage = $parentA.length > 0 && $parentA.text().replace(/\u00a0/g, '').trim() === ""; // --- 1. 處理「裝飾性圖片」標註 --- - if (currentAlt === "裝飾圖片" || currentTitle === "裝飾圖片") { + if (currentAlt === "announcement image" || currentAlt === "裝飾圖片" || currentTitle === "裝飾圖片") { $img.attr('alt', ''); $img.removeAttr('title'); return; @@ -883,10 +883,10 @@ // 有連結目的之所有a標籤的無障礙處理 $(function() { /** - * 全站無障礙 AA 級補強 (多語系偵測版) + * 全站無障礙 AA 級補強 (整合 HM1240400C 與 HM1240401C 規範) */ function fixAccessibilityAll() { - // --- 0. 網址參數判斷 --- + // --- 0. 環境與語言偵測 --- var urlParams = new URLSearchParams(window.location.search); var isEditMode = urlParams.get('editmode') === 'on'; @@ -897,7 +897,9 @@ langBtn: isEn ? 'Open language menu in this window' : '在本視窗開啟語言選單', newWin: isEn ? '(Open in new window)' : '(在新視窗開啟)', selfWin: isEn ? '(Open in this window)' : '(在本視窗開啟)', - link: isEn ? 'Link' : '連結' + link: isEn ? 'Link' : '連結', + openImg: isEn ? 'Open image' : '開啟圖片內容', + sort: isEn ? 'Sort' : '排序' }; // --- Part 1: 語言選單修正 --- @@ -907,22 +909,62 @@ $langBtn.removeAttr('aria-label'); } - // --- Part 2: 全站連結處理 --- + // --- Part 2: 全站連結與圖片邏輯處理 --- $('a').each(function() { var $a = $(this); + + // 【條件判斷:若 a 標籤的 class 等於或包含 navbar-brand,則不執行此 function】 + if ($a.hasClass('navbar-brand') || $a.hasClass('orbit-bar-logo')) { + return; + } 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 $icons = $a.find('i, svg'); // 偵測其他元素 (如 FontAwesome, SVG) var currentTitle = ($a.attr('title') || '').trim(); - // --- 【新增修正:隱藏空連結】 --- - // 若網址沒有 editmode=on 時,判定 a 裡面文字為空且沒有圖片,則隱藏 - if (!isEditMode && linkText === "" && $img.length === 0) { + // --- 【修正 1:隱藏空連結】 --- + if (!isEditMode && linkText === "" && $img.length === 0 && $icons.length === 0) { $a.hide(); - return; // 跳過後續處理 + return; + } + + // --- 【修正 2:符合 HM1240401C 的文字與標題防禦】 --- + // 若連結文字為空、或文字內容為無效的 URL 路徑,自動補上語意文字 + if (linkText === "" || linkText.startsWith('/') || linkText.includes('?')) { + 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(); + } + + // 套用視覺隱藏但螢幕閱讀器可讀取之樣式 + $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; // 更新變數以便後續比對 } // A. 視窗動作規範 @@ -936,32 +978,69 @@ fileExt = fileMatches[1].toLowerCase(); } - // C. 智慧修正與標題補充 + // C. 智慧修正標題 (移除冗餘資訊) var hasBadSymbol = currentTitle.includes('()') || - currentTitle.includes('另開新視窗') || - currentTitle.includes('Open in new window'); - + currentTitle.includes('另開新視窗') || + currentTitle.includes('Open in new window') || + currentTitle.includes('/uploads/'); + + // 如果 title 是 URL,視為壞標籤並重置 + if (currentTitle.startsWith('/') || currentTitle.includes('?')) { + currentTitle = ""; + } + + // D. 處理【HM1240400C】:圖片與文字毗鄰時,替代文字只能有一份 + if (($img.length > 0 || $icons.length > 0) && linkText !== "") { + if ($img.length > 0) { + // 強制圖片設為裝飾性 + $img.attr('alt', ''); + } + + // 針對「視覺隱藏但存在於 DOM」的文字 + 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. 重新計算標題與替代文字 (針對不同情境) 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 !== "" || hasBadSymbol || currentTitle === "" || currentTitle.includes('/uploads/')) { if (fileExt !== "") { $a.attr('title', linkText + " ." + fileExt + " " + windowTask); - } else if (linkText.length <= 6 || isTag) { + } else if (linkText.length <= 8) { $a.attr('title', linkText + " " + windowTask); } else { $a.attr('title', windowTask); @@ -969,30 +1048,22 @@ } } - // --- 修正後的 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); - } + // 【HM1240401C 補強】若連結包含其他多媒體內容 (如 i 標籤、SVG),需補齊 title 屬性 + var hasOtherContent = ($img.length > 0 || $icons.length > 0); + if (hasOtherContent && currentTitle === "") { + var newTitle = linkText + " " + windowTask; + $a.attr('title', newTitle.trim()); } + + // F. 最終清理:移除冗餘 label + $a.removeAttr('aria-label'); }); } + // 延遲執行確保 DOM 載入完成 setTimeout(function() { fixAccessibilityAll(); - }, 600); + }, 800); }); // if (location.href.search('editmode=on') === -1) { @@ -1011,66 +1082,87 @@ // }); // }); // } - $(function() { - function smartFixIcons() { - $('i').each(function() { - var $icon = $(this); - var $parent = $icon.parent(); + $(function() { + function smartFixIcons() { + var isEn = $('html').attr('lang') === 'en'; - // 1. 基本安全:所有圖示先對螢幕閱讀器隱藏 (避免讀出 Unicode 亂碼) - $icon.attr('aria-hidden', 'true'); + var labels = { + calendar : isEn ? 'Date: ' : '日期:', + user : isEn ? 'Author: ' : '發布者:', + tag : isEn ? 'Tag: ' : '標籤:', + clock : isEn ? 'Time: ' : '時間:', + location : isEn ? 'Location: ' : '地點:', + phone : isEn ? 'Phone: ' : '電話:', + envelope : isEn ? 'Email: ' : '信箱:', + folder : isEn ? 'Category: ' : '分類:', + eye : isEn ? 'Views: ' : '瀏覽次數:', + download : isEn ? 'Download: ' : '下載檔案:', + search : isEn ? 'Search' : '搜尋', + bars : isEn ? 'Menu' : '選單', + print : isEn ? 'Print this page': '列印此頁', + share : isEn ? 'Share' : '分享至社群', + close : isEn ? 'Close' : '關閉視窗' + }; - // 2. 檢查:如果已經有 aria-label 或旁邊已有 sr-only,就不重複處理 - if ($icon.attr('aria-label') || $icon.next('.sr-only').length > 0 || $parent.find('.sr-only').length > 0) { - return; - } + $('i').each(function() { + var $icon = $(this); + var $parent = $icon.parent(); - var cls = ($icon.attr('class') || '').toLowerCase(); - var labelText = ''; + // 1. 所有圖示先對螢幕閱讀器隱藏 + $icon.attr('aria-hidden', 'true'); - // --- 策略 A:常見資訊型圖示 (自動識別內容意義) --- - if (cls.includes('calendar')) labelText = '日期:'; - else if (cls.includes('user') || cls.includes('male')) labelText = '發布者:'; - else if (cls.includes('tag')) labelText = '標籤:'; - else if (cls.includes('clock') || cls.includes('time')) labelText = '時間:'; - else if (cls.includes('map-marker') || cls.includes('location')) labelText = '地點:'; - else if (cls.includes('phone')) labelText = '電話:'; - else if (cls.includes('envelope') || cls.includes('mail')) labelText = '信箱:'; - else if (cls.includes('folder')) labelText = '分類:'; - else if (cls.includes('eye')) labelText = '瀏覽次數:'; - else if (cls.includes('download')) labelText = '下載檔案:'; - - // --- 策略 B:功能性按鈕 (如果 i 放在