diff --git a/assets/javascripts/app.js b/assets/javascripts/app.js index 590da4e..db94897 100644 --- a/assets/javascripts/app.js +++ b/assets/javascripts/app.js @@ -789,38 +789,37 @@ // 延遲執行確保圖片與動態內容已渲染 setTimeout(fixImageAccessibility, 500); }); -function removeEmptyTitles() { - // 定義所有需要檢查的 class - const targetClasses = [ - '.w-annc__widget-title', - '.widget-title', - '.show-title', - '.event-annc-title', - '.annc-title', - '.sitemenu-title', - '.widget-link__widget-title' - ]; + 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(', '); + // 將陣列轉換為 jQuery 選擇器字串 + const selector = targetClasses.join(', '); - $(selector).each(function() { - // 使用 $.trim 移除空格、換行符號 - // 然後檢查裡面是否完全沒有文字 - const textContent = $.trim($(this).text()); + $(selector).each(function() { + // 使用 $.trim 移除空格、換行符號 + // 然後檢查裡面是否完全沒有文字 + const textContent = $.trim($(this).text()); - if (textContent === "") { - // 如果沒有文字,則將整個元素刪除 - $(this).remove(); - console.log('已移除空標題元素:', this.className); - } - }); -} + if (textContent === "") { + // 如果沒有文字,則將整個元素刪除 + $(this).remove(); + } + }); + } -// 執行 function -$(document).ready(function() { - removeEmptyTitles(); -}); + // 執行 function + $(document).ready(function() { + removeEmptyTitles(); + }); $(".w-annc__img-wrap a").each(function () { var $this = $(this); // 確保 內沒有文字節點 (避免重複添加) @@ -880,105 +879,99 @@ $(document).ready(function() { $button.attr('aria-label', '播放驗證碼語音'); } }); - //有連結目的之所有a標籤加上aria-label和title -$(function() { - /** - * 無障礙 AA 級補強最終整合版 (人工檢測完全合格) - * 1. 修正語言選單:Tab 到達時顯示「在本視窗開啟語言選單」。 - * 2. 智慧 Title 策略: - * - 長文字:僅保留動作提示 (如: (在本視窗開啟)),避免重複讀取。 - * - 短文字/標籤:組合 [文字內容] + [動作提示],增強目的識別。 - * 3. 圖文並存處理:符合 HM1240402E,當有文字時,圖片 alt 設為空值。 - * 4. 清理無效描述:攔截「這是一張圖片」等不合格內容。 - */ - function fixAccessibilityAll() { - // --- Part 1: 語言選單修正 (Language Button) --- - var $langBtn = $('#languagebutton'); - if ($langBtn.length > 0) { - $langBtn.attr('title', '在本視窗開啟語言選單'); - $langBtn.removeAttr('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; + + 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; + + if (fileExt !== "") { + // 檔案下載:連結文字 + .副檔名 + 動作 (語系化) + $a.attr('title', linkText + " ." + fileExt + " " + windowTask); + } else if (linkText.length <= 6 || isTag) { + // 短文字:補充完整語意 + $a.attr('title', linkText + " " + windowTask); + } else { + // 長文字:僅動作提示 + $a.attr('title', windowTask); + } + } + } + + // D. 最終清理 + $a.removeAttr('aria-label'); + if (linkText !== "" && $img.length > 0) { $img.attr('alt', ''); } + }); } - // --- Part 2: 全站連結補強 (Links) --- - $('a').each(function() { - var $a = $(this); - var href = $a.attr('href'); - - // 基礎攔截 - if (!href || href.startsWith('javascript:')) return; - - // 1. 獲取內容資訊 - var linkText = $a.text().replace(/\u00a0/g, '').trim(); - var $img = $a.find('img'); - var rawAlt = ($img.length > 0) ? ($img.attr('alt') || '').trim() : ''; - var forbiddenTexts = ['這是一張圖片', '圖片', 'image', 'photo', '圖示']; - var imgAlt = forbiddenTexts.includes(rawAlt) ? '' : rawAlt; - - var hasSrOnly = $a.find('.sr-only').length > 0 && $a.find('.sr-only').text().trim().length > 0; - var srOnlyText = hasSrOnly ? $a.find('.sr-only').text().trim() : ''; - - // 2. 移除空連結 - if (linkText === '' && $img.length === 0 && !hasSrOnly && !($a.attr('aria-label'))) { - $a.remove(); - return; - } - - // 3. 準備視窗提示 - let windowTask = ($a.attr('target') === '_blank') ? '(在新視窗開啟)' : '(在本視窗開啟)'; - - // 4. 核心邏輯判定 - - // 【情況 A:純圖片連結 (無文字)】 - if (linkText === "" && !hasSrOnly) { - if (imgAlt !== "") { - // 名字讓圖片 alt 說,title 只放動作提示 (避免重複) - $a.attr('title', windowTask); - $img.attr('alt', imgAlt); - } else { - let fallback = $a.attr('aria-label') || '連結'; - $a.attr('title', (fallback + ' ' + windowTask).trim()); - $img.attr('alt', fallback); - } - } - - // 【情況 B:有文字連結】 - else { - /** - * 智慧 Title 判斷: - * 1. 如果是標籤 (Class 含 label 或 tag) 或文字很短 (<= 6 字) - * -> Title 顯示:[文字內容] (動作提示) - * 2. 如果是長文章標題或一般長連結 - * -> Title 僅顯示:(動作提示) 以避免重複朗讀 - */ - var isTag = $a.hasClass('label') || $a.hasClass('tag') || $a.find('[class*="tag"]').length > 0 || $a.find('[class*="label"]').length > 0; - - if (linkText.length <= 6 || isTag) { - // 短文字或標籤:補全語意 - $a.attr('title', linkText + " " + windowTask); - } else { - // 長文字:精簡語意 - $a.attr('title', windowTask); - } - - // 符合規範:圖文並存時,圖片 alt 應為空值 - // if ($img.length > 0) { - // $img.attr('alt', ''); - // } - } - - // 5. 最終清理 - $a.removeAttr('aria-label'); - $a.removeAttr('alt'); // a 標籤不應有 alt - - if ($img.length > 0 && forbiddenTexts.includes($img.attr('alt'))) { - $img.attr('alt', ''); - } - }); - } - - // 延遲執行 - setTimeout(fixAccessibilityAll, 500); -}); + setTimeout(function() { + fixAccessibilityAll(); + }, 600); + }); // if (location.href.search('editmode=on') === -1) { // // 1. 處理圖片 alt 重複問題 (保持原邏輯但優化) @@ -1205,6 +1198,154 @@ $(function() { // 執行 function removeHiddenElementsInsideBrand(); + //修正 Alt+M 定位點位置 + $(function() { + function fixAccessKeyM() { + var $accessKeyM = $('#accesskey_menu'); + var $navbarHeader = $('.navbar-header'); + + if ($accessKeyM.length > 0 && $navbarHeader.length > 0) { + $accessKeyM.insertBefore($navbarHeader); + $accessKeyM.css({ + 'display': 'inline-block', + 'position': 'relative', + 'float': 'left', // 靠左對齊 + }); + // $accessKeyM.attr('title', '中央內容區塊導覽列 (Alt+M)'); + } + } + setTimeout(fixAccessKeyM, 500); + }); + //公告頁籤無障礙 + $(function() { + function fixTabLinearOrder() { + var $tabs = $('.filter_tab'); + var $anncList = $('.w-annc__list'); + + // --- 1. 順向遊走:標籤 -> 對應內容 --- + $tabs.on('keydown', function(e) { + // 當在標籤按下 Tab (排除 Shift+Tab) + if (e.which === 9 && !e.shiftKey) { + var category = $(this).data('category'); + var $visibleItems = $('.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(); + } + } + } + // 處理標籤的 Enter/Space 切換 + 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 category = $currentItem.data('category'); + var $visibleItems = $('.w-annc__item[data-category="' + category + '"]:visible'); + + // 如果是目前內容的第一個連結,按下 Shift+Tab 應回到對應標籤 + if (e.which === 9 && e.shiftKey) { + if ($currentLink.is($visibleItems.find('a').first())) { + e.preventDefault(); + $('.filter_tab[data-category="' + category + '"]').focus(); + } + } + + // 如果是目前內容的最後一個連結,按下 Tab 應跳往「下一個標籤」 + if (e.which === 9 && !e.shiftKey) { + if ($currentLink.is($visibleItems.find('a').last())) { + var $nextTab = $('.filter_tab[data-category="' + category + '"]').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 $prevTab = $(this).prev('.filter_tab'); + if ($prevTab.length > 0) { + var prevCategory = $prevTab.data('category'); + + // 先切換到上一個分頁 + $prevTab.click(); + + // 找出上一個分頁最後一個可見連結 + var $prevItems = $('.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'); + + $navItems.each(function() { + const $li = $(this); + const $toggle = $li.children('a'); + const $subMenu = $li.children('ul'); + + // 判斷是否有子選單 + if ($subMenu.length > 0) { + // 1. 初始化屬性 + // aria-haspopup="true" 表示有點擊後彈出選單的功能 + // aria-expanded="false" 預設為摺疊 + $toggle.attr({ + 'aria-haspopup': 'true', + 'aria-expanded': 'false' + }); + + // 如果 href 是 javascript:void(0);,建議補上 role="button" 讓語音讀出它是按鈕 + if ($toggle.attr('href').indexOf('javascript') !== -1) { + $toggle.attr('role', 'button'); + } + + // 2. 焦點移入事件 (Focus) + $toggle.on('focus', function() { + // 這裡假設你的選單是用 CSS 或其他 JS 控制顯示, + // 當焦點進入時,將狀態改為 true + $(this).attr('aria-expanded', 'true'); + // 如果需要透過 JS 強制顯示選單可在此加上:$subMenu.addClass('show'); (視你的 CSS 而定) + }); + + // 3. 處理失焦事件 (Focusout) + // 使用 focusout 並檢查焦點是否移出了整個 li 區塊 + $li.on('focusout', function(e) { + // 使用 setTimeout 確保能抓到下一個獲焦元素 + setTimeout(function() { + if (!$li.is(':focus-within')) { + $toggle.attr('aria-expanded', 'false'); + // 如果有 class 控制顯示:$subMenu.removeClass('show'); + } + }, 50); + }); + } + }); + } + // 執行修正 + fixNavMenuA11y(); } forFreeGo(); @@ -1863,118 +2004,188 @@ $(function() { init(); }); - //orbitbar無障礙js - $(document).ready(function() { - const isEnglish = document.documentElement.lang === 'en'; - if (isEnglish) { - $('.navbar-toggle').attr({ - title: 'menu', - 'aria-label': 'menu' - }); - $('label[for="open-orbit-nav"]').attr({ - title: 'Site search and language menu', - 'aria-label': 'Site search and language menu' - }); - $('.mobile-button').attr({ - title: 'Language menu', - 'aria-label': 'Language menu' - }); - } else { - $('label[for="open-orbit-nav"]').attr({ - title: '全站収尋及語言切換選單', - 'aria-label': '全站収尋及語言切換選單' - }); - $('.mobile-button').attr({ - title: '語言切換選單', - 'aria-label': '語言切換選單' - }); - } - +// 1. OrbitBar 基礎與搜尋選單邏輯 +$(document).ready(function() { + const isEn = document.documentElement.lang === 'en'; + const $orbitLabel = $('label[for="open-orbit-nav"]'); + const $searchArea = $('.orbit-bar-search-sign-language'); + const $firstInput = $('#q'); // 搜尋框通常是選單內第一個獲焦元素 - $('label[for="open-orbit-login"]').removeAttr('tabindex'); - $('#orbit-bar').find('li').each(function() { - var $li = $(this); + // 初始化屬性 + if (isEn) { + $orbitLabel.attr({ title: 'Site search and language menu', 'aria-label': 'Site search and language menu' }); + } else { + $orbitLabel.attr({ title: '全站搜尋及語言切換選單', 'aria-label': '全站搜尋及語言切換選單' }); + } + $orbitLabel.attr({ 'role': 'button', 'aria-haspopup': 'true', 'aria-expanded': 'false', 'tabindex': '0' }); - // 如果
  • 中没有 元素,则为
  • 设置 tabindex="0" - if ($li.children('a').length === 0) { - $li.attr('tabindex', '0'); - } else { - // 如果
  • 中有 元素,则确保 有 tabindex="0" - $li.find('a').attr('tabindex', '0'); - - // 确保
  • 不影响焦点管理,移除
  • 的 tabindex - $li.removeAttr('tabindex'); - - // 如果
  • 被聚焦,自动跳转到 元素 - $li.on('focus', function() { - $(this).find('a').focus(); - }); - } - }); - $('label[for="open-orbit-nav"]').attr('tabindex', '0'); - // 当屏幕宽度小于 768px 时,将 .orbit-bar-search-sign-language 移动到