From f37ae7ae14af1e5b16d64d2293ea84b73268f546 Mon Sep 17 00:00:00 2001 From: ken Date: Thu, 25 Jun 2026 16:30:57 +0800 Subject: [PATCH] update --- assets/javascripts/app.js | 1829 ++++++++++++++--- .../stylesheets/template/base/_accesskey.scss | 6 +- assets/stylesheets/template/base/_global.scss | 49 +- assets/stylesheets/template/base/_unity.scss | 4 +- .../stylesheets/template/base/_variables.scss | 3 + .../stylesheets/template/layout/content.scss | 6 +- .../stylesheets/template/layout/header.scss | 4 +- .../template/modules/ad_banner.scss | 33 +- .../template/modules/archives.scss | 21 +- .../stylesheets/template/modules/member.scss | 27 +- assets/stylesheets/template/modules/menu.scss | 34 +- .../template/modules/universal-table.scss | 22 +- assets/stylesheets/template/template.scss | 6 +- .../template/widget/breadcrumb.scss | 1 + .../stylesheets/template/widget/sitemenu.scss | 31 +- home/index.html.erb | 2 +- home/page.html.erb | 2 +- modules/ad_banner/_ad_banner_widget2.html.erb | 27 +- .../_ad_banner_widget8_video.html.erb | 15 +- modules/archive/archive_index1.html.erb | 8 +- modules/archive/archive_index12.html.erb | 2 +- modules/archive/archive_index14.html.erb | 2 +- modules/archive/archive_index16.html.erb | 4 +- modules/archive/archive_index2.html.erb | 6 +- modules/archive/archive_index3.html.erb | 4 +- modules/archive/archive_index4.html.erb | 2 +- modules/event_news_mod/show.html.erb | 2 +- modules/event_news_mod/show2.html.erb | 2 +- modules/gallery/gallery_index1.html.erb | 2 +- modules/personal_course/index.html.erb | 10 +- .../show_chapter_accordion.html.erb | 2 +- modules/publication/show_journal.html.erb | 2 +- modules/universal_table/index3.html.erb | 4 +- modules/universal_table/index4.html.erb | 4 +- 34 files changed, 1781 insertions(+), 397 deletions(-) diff --git a/assets/javascripts/app.js b/assets/javascripts/app.js index 1345cd3..c0731f6 100644 --- a/assets/javascripts/app.js +++ b/assets/javascripts/app.js @@ -1,7 +1,36 @@ ;(function($, win, undefined) { // ECMAScript 5 嚴格模式 'use strict'; + $(function() { + var $signLanguageBox = $('.orbit-bar-search-sign-language'); + var $headerNav = $('.header-nav'); + var $target = $('.header-nav [accesskey="U"], .header-nav [accesskey="W"]'); + // 移除 color: inherit !important + $target.each(function() { + $(this)[0].style.removeProperty('color'); + }); + + function handleNavMove() { + var windowWidth = window.innerWidth; + + if (windowWidth > 768) { + if ($signLanguageBox.length && $signLanguageBox.find('[accesskey="U"], [accesskey="W"]').length === 0) { + $signLanguageBox.prepend($target); + } + } else { + if ($headerNav.length && $headerNav.find('[accesskey="U"], [accesskey="W"]').length === 0) { + $headerNav.append($target); + } + } + } + + handleNavMove(); + + $(window).on('resize', function() { + handleNavMove(); + }); + }); //header fixed在上方 function initdata1() { if ( $('.homebanner').length != 0) { @@ -337,7 +366,7 @@ break; case 'member': orbit.member.removeEmptyRow(); - $('.index-member-3 .member-data-title-email').empty(); + break; case 'archive': orbit.archives.removeEmptyTitle('.i-archive__category-item'); @@ -399,6 +428,89 @@ // $('.header-buttom').appendTo($('.dropdowns')); // } function forFreeGo() { + setTimeout(function() { + $('h1, h2, h3, h4, h5, h6').each(function() { + var $heading = $(this); + + // 檢查是否為空 (去除空白後) + if ($.trim($heading.text()) === '' && $heading.children().length === 0) { + $heading.remove(); + } + }); + }, 500); + //zoom + $(function () { + $('.fnav a').each(function () { + const $a = $(this); + + // 若已經有可見文字(非 icon),直接通過 + const visibleText = $a.clone() + .children('.icon, i, svg, .caret') + .remove() + .end() + .text() + .trim(); + + if (visibleText.length > 0) return; + + // 取得可用標籤文字(優先順序) + let label = + $a.attr('aria-label') || + $a.attr('title') || + $a.find('i[title]').attr('title') || + ''; + + label = $.trim(label); + + // 若真的完全沒有,就給一個保底(避免空字串違規) + if (!label) { + label = '功能按鈕'; + } + + // aria-label 一定要有 + $a.attr('aria-label', label); + + // icon 對螢幕閱讀器隱藏 + $a.find('i, svg').attr('aria-hidden', 'true'); + + // 若沒有隱藏文字才補 + if ($a.find('.sr-only, .visually-hidden').length === 0) { + $a.append(`${label}`); + } + }); + }); + //播放驗證碼語音 + $(function () { + $('.ask-question') + .find('.controls button.fas.fa-volume-up') + .each(function () { + const $btn = $(this); + + // 已經補過就不重複處理 + if ($btn.attr('aria-label')) return; + + $btn.attr({ + 'aria-label': '播放驗證碼語音', + 'aria-controls': 'captcha_audio', + 'role': 'button' + }); + }); + }); + + //刪除td的id + $('td[id]').removeAttr('id'); + + //刪除空的title + $('[class*="title"]').each(function () { + const $el = $(this); + + // 取純文字(忽略 HTML tag、空白、換行) + const text = $.trim($el.text()); + + if (text === '') { + $el.remove(); + } + }); //表單legend $("fieldset").each(function () { const $fieldset = $(this); @@ -516,28 +628,40 @@ } }); - //li被hover function handleHover() { - if ($(window).width() > 769) { - $('li').off('mouseenter mouseleave').hover( - function() { - $(this).children('ul').addClass('show'); - }, - function() { - $(this).children('ul').removeClass('show'); - } - ); - } else { - $('li').off('mouseenter mouseleave'); // 移除 hover 事件 - } + // 先統一移除舊事件,避免重複綁定 + $('li').off('mouseenter mouseleave focusin focusout'); + + if ($(window).width() > 769) { + // 1. 處理滑鼠 Hover + $('li').hover( + function() { $(this).children('ul').addClass('show'); }, + function() { $(this).children('ul').removeClass('show'); } + ); + + // 2. 處理鍵盤 Tab 聚焦 (關鍵修正) + $('li').on('focusin', function() { + // 當焦點進入 li 或其子元素時,顯示選單 + $(this).children('ul').addClass('show'); + }); + + $('li').on('focusout', function() { + var $this = $(this); + // 延遲偵測焦點是否真的「離開」了這個 li 區塊 + setTimeout(function() { + // 如果新的焦點不在當前這個 li 裡面,才移除 show + if ($this.find(':focus').length === 0) { + $this.children('ul').removeClass('show'); + } + }, 10); + }); + } } - // 先執行一次 + // 初始化與 Resize 監聽保持不變 handleHover(); - - // 監聽視窗大小變化 $(window).on('resize', function() { - handleHover(); + handleHover(); }); //刪除空的h1 $('h1').each(function() { @@ -571,6 +695,16 @@ } }); //無障礙單位轉換 + $(function() { + $('style:contains("font-size")').each(function() { + let css = $(this).html(); + css = css.replace(/font-size\s*:\s*([\d.]+)px\s*;?/gi, (_, px) => + `font-size: ${(parseFloat(px) / 16).toFixed(3)}em;` + ); + $(this).html(css); + }); + }); + $("[style*='font-size']").each(function() { var fontSize = $(this).css("font-size"); if (fontSize.includes("px")) { @@ -623,38 +757,46 @@ if (!$this.attr('title') || $this.attr('title').trim() === '') { $this.attr('title', '按鈕'); } - }); - $('img').each(function() { - var $this = $(this); - if (!$this.attr('alt') || $this.attr('alt').trim() === '') { - $this.attr('alt', '這是一張圖片'); - } - }); - $('img').each(function() { - var $this = $(this); - if (!$this.attr('title') || $this.attr('title').trim() === '') { - $this.attr('title', '這是一張圖片'); - } }); - $('img').each(function() { - var $this = $(this); - - // 檢查 img 的 alt 屬性是否為 "裝飾圖片" - if ($this.attr('alt') === "裝飾圖片" || $this.attr('title') === "裝飾圖片") { - // 設定 alt 為空字串 - $this.attr('alt', ''); - - // 移除 title 屬性 - $this.removeAttr('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(', '); + + $(selector).each(function() { + // 使用 $.trim 移除空格、換行符號 + // 然後檢查裡面是否完全沒有文字 + const textContent = $.trim($(this).text()); + + if (textContent === "") { + // 如果沒有文字,則將整個元素刪除 + $(this).remove(); + } }); - $(".w-annc__img-wrap a").each(function () { - var $this = $(this); - // 確保 內沒有文字節點 (避免重複添加) - if ($this.text().trim() === "") { - $this.append('公告圖片'); - } + } + // 執行 function + $(document).ready(function() { + removeEmptyTitles(); }); + + // $(".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('公告標題'); @@ -707,94 +849,435 @@ $button.attr('aria-label', '播放驗證碼語音'); } }); - //有連結目的之所有a標籤加上aria-label和title - $('a').each(function () { - var $a = $(this); - var href = $a.attr('href'); - if (!href) return; + // 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 isLinkImage = $parentA.length > 0 && $parentA.text().replace(/\u00a0/g, '').trim() === ""; - var hasSrOnly = $a.find('.sr-only').length > 0; + // --- 1. 處理「裝飾性圖片」標註 --- + if (currentAlt === "announcement image" || currentAlt === "裝飾圖片" || currentTitle === "裝飾圖片" || currentAlt === "這是一張圖片" || currentTitle === "這是一張圖片") { + $img.attr('alt', ''); + $img.removeAttr('title'); + return; + } - // 若有 .sr-only,移除 aria-label 和 title 避免重複朗讀 - if (hasSrOnly) { - $a.removeAttr('aria-label'); - $a.removeAttr('title'); - return; // 有 .sr-only 就不做其他處理 - } + // --- 2. 解決【alt 與 title 相同】的問題 --- + if (currentAlt !== '' && currentAlt === currentTitle) { + $img.removeAttr('title'); + currentTitle = ''; + } - // ----- aria-label 邏輯 ----- - if (!$a.attr('aria-label')) { - let ariaLabel = ''; + // --- 3. 處理「缺失 alt」或「空的 alt」補強邏輯 --- + // 修改點:若是具有連結用途的圖片,alt 不可為空 + if (currentAlt === '') { + let finalAlt = ""; - // 加入開啟方式 - if ($a.attr('target') === '_blank') { - ariaLabel += '在新視窗開啟 '; - } else if ($a.attr('target') === '_self') { - ariaLabel += '在本視窗開啟 '; - } + // A. 優先嘗試從父層 () 的 title 抓取 + var parentATitle = $parentA.attr('title'); + + // B. 嘗試抓取同區域內的標題文字 + var nearbyTitle = $parentA.find('h1, h2, h3, h4, h5, h6').first().text().trim(); - // 如果包含圖片且有圖片 title - if ($a.find('img').length) { - const imgTitle = $a.find('img').attr('title'); - if (imgTitle) ariaLabel = imgTitle; - } else { - const text = $a.text().trim(); - if (text) { - ariaLabel += text; - } else if ($a.children('span').length === 1) { - ariaLabel += $a.children('span').text().trim(); - } - } + if (parentATitle && parentATitle.trim() !== '') { + // 移除 title 內的視窗提示字眼,只取純文字作為 alt + finalAlt = parentATitle.replace(/\(在本視窗開啟\)|\(在新視窗開啟\)/g, '').trim(); + } else if (nearbyTitle !== '') { + finalAlt = nearbyTitle; + } else if (isLinkImage) { + // 如果是連結圖片且都抓不到文字,給予預設值「連結」避免檢測失敗 + finalAlt = "連結"; + } - if (ariaLabel) { - $a.attr('aria-label', ariaLabel); - } - } + $img.attr('alt', finalAlt); - // ----- title 邏輯 ----- - if (!$a.attr('title')) { - let titleStr = ''; - - if ($a.attr('target') === '_blank') { - titleStr += '在新視窗開啟 '; - } else if ($a.attr('target') === '_self') { - titleStr += '在本視窗開啟 '; - } - - if ($a.find('img').length) { - titleStr = '這是一張照片'; - } else { - const text = $a.text().trim(); - if (text) { - titleStr += text; - } else if ($a.children('span').length === 1) { - titleStr += $a.children('span').text().trim(); - } - } - - if (titleStr) { - $a.attr('title', titleStr); - } - } - }); - $('a').each(function () { - const $link = $(this); - const linkText = $link.text().trim(); - - $link.find('img').each(function () { - const $img = $(this); - const altText = $img.attr('alt')?.trim(); - - // 如果圖片 alt 是空的,就略過(已符合) - if (altText === '') return; - - // 如果圖片 alt 等於文字內容,就將 alt 清空,避免重複 - if (altText === linkText) { - $img.attr('alt', ''); - } + // --- 4. 二次清理 --- + if (currentTitle !== '' && currentTitle === finalAlt) { + $img.removeAttr('title'); + } + } }); - }); + } + setTimeout(fixImageAccessibility, 500); + }); + +// 有連結目的之所有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); + } + }); + + /** + * 全站無障礙 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'; + + 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(); + } + + $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. 視窗動作規範 + 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 相同,則只保留開窗提示 + if ( + (titleWithoutWindow === linkText && linkText !== i18n.link) || + (imgAltOriginal !== '' && titleWithoutWindow === imgAltOriginal) + ) { + $a.attr('title', windowTask); + } + + // ✅ 最終清理:若 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'); + }); + + // ✅ 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 在新視窗開啟'); + }); + + $('.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' : '列印此頁'); + } + }); + + $('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'; + + 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 window' : '關閉視窗', + closePopup : isEn ? 'Close announcement' : '關閉公告視窗', + closeGallery: isEn ? 'Close gallery' : '關閉相簿', + toggleDesc : isEn ? 'Show or hide image description' : '顯示或隱藏圖片說明', + themeSwitch : isEn ? 'Switch gallery theme': '切換相簿主題', + showOriginal: isEn ? 'Show original image' : '顯示原始圖片' + }; + + $('i').each(function() { + var $icon = $(this); + var $parent = $icon.parent(); + + // 1. 所有圖示先對螢幕閱讀器隱藏 + $icon.attr('aria-hidden', 'true'); + + // 特判:.close-screen-btn 內的圖示,移除無關的 sr-only 並設正確 aria-label + if ($parent.hasClass('close-screen-btn')) { + $parent.find('.sr-only').remove(); + $parent.attr('aria-label', labels.closePopup); + if ($icon.next('.sr-only').length === 0) { + $icon.after('' + labels.closePopup + ''); + } + return; + } + + // 特判:.gallery-close 內的圖示,移除錯誤的 sr-only 並補正確說明 + if ($parent.closest('.gallery-close').length > 0) { + $parent.closest('.gallery-close').find('.sr-only').remove(); + $parent.closest('.gallery-close').attr('aria-label', labels.closeGallery); + $parent.closest('.gallery-close').attr('role', 'button'); + $parent.closest('.gallery-close').attr('tabindex', '0'); + if ($icon.next('.sr-only').length === 0) { + $icon.after('' + labels.closeGallery + ''); + } + return; + } + + // 特判:.gallery-toggle-desc 補說明 + if ($parent.closest('.gallery-toggle-desc').length > 0) { + var $toggleBtn = $parent.closest('.gallery-toggle-desc'); + if (!$toggleBtn.attr('aria-label')) { + $toggleBtn.attr('aria-label', labels.toggleDesc); + $toggleBtn.attr('role', 'button'); + $toggleBtn.attr('tabindex', '0'); + } + if ($icon.next('.sr-only').length === 0) { + $icon.after('' + labels.toggleDesc + ''); + } + return; + } + + // 特判:.gallery-theme-switch 補說明 + if ($parent.closest('.gallery-theme-switch').length > 0) { + var $themeBtn = $parent.closest('.gallery-theme-switch'); + if (!$themeBtn.attr('aria-label')) { + $themeBtn.attr('aria-label', labels.themeSwitch); + $themeBtn.attr('role', 'button'); + $themeBtn.attr('tabindex', '0'); + } + if ($icon.next('.sr-only').length === 0) { + $icon.after('' + labels.themeSwitch + ''); + } + return; + } + + // 2. 已有 aria-label 或 sr-only 就不重複處理 + if ($icon.attr('aria-label') || $icon.next('.sr-only').length > 0 || $parent.find('.sr-only').length > 0) { + return; + } + + var cls = ($icon.attr('class') || '').toLowerCase(); + var labelText = ''; + + // --- 策略 A:常見資訊型圖示 --- + if (cls.includes('calendar')) labelText = labels.calendar; + else if (cls.includes('user') || cls.includes('male')) labelText = labels.user; + else if (cls.includes('tag')) labelText = labels.tag; + else if (cls.includes('clock') || cls.includes('time')) labelText = labels.clock; + else if (cls.includes('map-marker') || cls.includes('location')) labelText = labels.location; + else if (cls.includes('phone')) labelText = labels.phone; + else if (cls.includes('envelope') || cls.includes('mail')) labelText = labels.envelope; + else if (cls.includes('folder')) labelText = labels.folder; + else if (cls.includes('eye')) labelText = labels.eye; + else if (cls.includes('download')) labelText = labels.download; + + // --- 策略 B:功能性按鈕 --- + var $container = $icon.closest('a, button'); + if ($container.length > 0 && $container.text().trim() === '') { + var btnTitle = $container.attr('title'); + if (btnTitle) labelText = btnTitle; + else if (cls.includes('search')) labelText = labels.search; + else if (cls.includes('bars') || cls.includes('navicon')) labelText = labels.bars; + else if (cls.includes('print')) labelText = labels.print; + else if (cls.includes('share')) labelText = labels.share; + else if (cls.includes('close') || cls.includes('times')) labelText = labels.close; + else if (cls.includes('image') || cls.includes('photo')) labelText = labels.showOriginal; + } + + // --- 3. 執行補強 --- + if (labelText !== '') { + $icon.after('' + labelText + ''); + } + }); + } + + setTimeout(smartFixIcons, 500); +}); // 刪除banner-slide的空連結和空連結目標 for(var i=0;i<$('.w-ba-banner__slide a').length;i++){ if($('.w-ba-banner__slide a').eq(i).attr('href')=="") @@ -836,27 +1319,503 @@ for(var i = 0;i < $('*[data-list] tr td a').length ; i++) if($('*[data-list] tr td a').eq(i).html().trim()=="") $('*[data-list] tr td a').eq(i).parent('td').parent('tr').remove(); - // tab按鍵選到menu,會顯示下層的menu(為了符合無障礙) + // tab按鍵選到menu,會顯示下層的menu(為了符合無障礙) $('.nav-level-0>li>a').focus(function(e) { - e.stopPropagation(); - $(this).parent().focus(); + e.stopPropagation(); + $(this).parent().focus(); if ($(this).parent().find('.nav-level-1').hasClass('show')) { + // 已有 show,不處理 } else { - $('.nav-level-1').removeClass('show'); - $(this).parent().find('.nav-level-1').addClass('show'); + $('.nav-level-1').removeClass('show'); + $('.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 } }); - $('.nav-level-1>li>a').focus(function(e) { - e.stopPropagation(); - if ($(this).parent().find('.nav-level-2').hasClass('show')) { - }else{ - $('.nav-level-2').removeClass('show'); - $(this).parent().find('.nav-level-2').addClass('show'); - } + + $('.nav-level-0>li').on('focusout', function(e) { + var $li = $(this); + setTimeout(function() { + if ($li.find(':focus').length === 0) { + $li.find('.nav-level-1').removeClass('show'); + $li.find('.menu-drop').removeClass('opened'); // 移除 opened + } + }, 50); }); - $('show').parent('li').focus(); + + $('.nav-level-1>li>a').focus(function(e) { + e.stopPropagation(); + if ($(this).parent().find('.nav-level-2').hasClass('show')) { + // 已有 show,不處理 + } else { + $('.nav-level-2').removeClass('show'); + $('.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 + } + }); + + $('.nav-level-1>li').on('focusout', function(e) { + var $li = $(this); + setTimeout(function() { + if ($li.find(':focus').length === 0) { + $li.find('.nav-level-2').removeClass('show'); + $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'); + $('.controlplay a[role="radio"] span').each(function () { + var text = $(this).text().replace('(已選取)', ''); + $(this).text(text); + }); + + // 設定目前被點擊的按鈕為已選取 + $(this).attr('aria-checked', 'true'); + var selectedText = $(this).text().trim(); + $(this).find('span').text(selectedText + '(已選取)'); + }); + $(function() { + /** + * jPlayer 控制項無障礙標籤補強 + * 1. 修正重複且無意義的 title="按鈕" + * 2. 解決鍵盤焦點無法進入隱藏介面的問題 + */ + function fixJPlayerAccessibility() { + var $playerContainer = $('#jp_container_video_file'); + + // --- A. 修正按鈕標題 (解決語義問題) --- + $playerContainer.find('button').each(function() { + var $btn = $(this); + var cls = $btn.attr('class') || ''; + var newTitle = ''; + + // 根據 class 判斷功能 + if (cls.includes('jp-video-play-icon')) newTitle = '播放影片'; + else if (cls.includes('jp-play')) newTitle = '播放'; + else if (cls.includes('jp-stop')) newTitle = '停止'; + else if (cls.includes('jp-mute')) newTitle = '靜音'; + else if (cls.includes('jp-volume-max')) newTitle = '最大音量'; + else if (cls.includes('jp-repeat')) newTitle = '重複播放'; + else if (cls.includes('jp-full-screen')) newTitle = '全螢幕'; + + if (newTitle !== '') { + // 修正 title 為具體語義 + $btn.attr('title', newTitle); + // 同時補上 aria-label 確保螢幕閱讀器讀取 + $btn.attr('aria-label', newTitle); + } + }); + + // --- B. 鍵盤焦點互動修正 (解決 GN1210101E) --- + // 當鍵盤 Tab 進入按鈕時,強制讓 UI 介面顯現,否則使用者看不到焦點在哪 + $playerContainer.on('focusin', 'button', function() { + $playerContainer.removeClass('hide-ui'); + }); + + // --- C. CSS 強制補強 (解決 display:none 無法獲取焦點問題) --- + // 這是必要的,因為 CSS 若設 display:none,鍵盤永遠進不去 + $("