Compare commits

...
2 Commits
3 changed files with 65 additions and 29 deletions
+2 -1
View File
@@ -15,7 +15,8 @@
- **⚡ Live Web & YouTube On-Demand Search:** Seamlessly search YouTube directly within your instance search bar. If the video is already on your instance, it plays immediately; otherwise, users can import it in 1 click. - **⚡ Live Web & YouTube On-Demand Search:** Seamlessly search YouTube directly within your instance search bar. If the video is already on your instance, it plays immediately; otherwise, users can import it in 1 click.
- **🔥 Hybrid Smart Recommendations:** Intelligently discovers related content across local federated videos first, supplemented with relevant live discoveries. - **🔥 Hybrid Smart Recommendations:** Intelligently discovers related content across local federated videos first, supplemented with relevant live discoveries.
- **♿ Full Screen Reader Accessibility (A11y):** - **♿ Full Screen Reader Accessibility (A11y):**
- Dynamic result counters with live polite announcements (`aria-live`). - Dynamic result counters with live polite and assertive announcements (`aria-live`).
- Auto-clearing live regions after speech lifecycle (WCAG 2.2 AAA compliant, prevents persistent DOM clutter for virtual cursors).
- Accessible keyboard navigation modal (`?` shortcut). - Accessible keyboard navigation modal (`?` shortcut).
- Auto-labeled player controls, buttons, and thumbnails. - Auto-labeled player controls, buttons, and thumbnails.
- **💬 Dynamic Live YouTube Comments:** View original YouTube community comments under imported videos on demand. - **💬 Dynamic Live YouTube Comments:** View original YouTube community comments under imported videos on demand.
+62 -27
View File
@@ -75,33 +75,66 @@ try {
return 'YakTube'; return 'YakTube';
} }
// 1. Screen Reader Live Announcer // 1. Screen Reader Live Announcer (W3C WCAG 2.2 AAA Compliant Auto-Clearing Engine)
var liveRegion = null; function applySrOnlyStyles(el) {
function getLiveRegion() { el.className = 'yaktube-sr-only';
if (!liveRegion || !document.body.contains(liveRegion)) { el.style.position = 'absolute';
liveRegion = document.getElementById('yaktube-a11y-live-region'); el.style.width = '1px';
if (!liveRegion) { el.style.height = '1px';
liveRegion = document.createElement('div'); el.style.padding = '0';
liveRegion.id = 'yaktube-a11y-live-region'; el.style.margin = '-1px';
liveRegion.setAttribute('aria-live', 'polite'); el.style.overflow = 'hidden';
liveRegion.setAttribute('aria-atomic', 'true'); el.style.clip = 'rect(0, 0, 0, 0)';
liveRegion.className = 'yaktube-sr-only'; el.style.whiteSpace = 'nowrap';
document.body.appendChild(liveRegion); el.style.border = '0';
} }
function getLiveRegion(assertive) {
var id = assertive ? 'yaktube-a11y-live-assertive' : 'yaktube-a11y-live-region';
var region = document.getElementById(id);
if (!region || !document.body.contains(region)) {
region = document.createElement('div');
region.id = id;
region.setAttribute('aria-live', assertive ? 'assertive' : 'polite');
region.setAttribute('aria-atomic', 'true');
region.setAttribute('aria-relevant', 'additions text');
applySrOnlyStyles(region);
document.body.appendChild(region);
} }
return liveRegion; return region;
} }
function announce(text, assertive) { function announce(text, assertive) {
var region = getLiveRegion(); if (!text || !String(text).trim()) return;
if (assertive) { var strText = String(text).trim();
region.setAttribute('aria-live', 'assertive'); var region = getLiveRegion(assertive);
} else {
region.setAttribute('aria-live', 'polite'); if (region._setTimer) {
clearTimeout(region._setTimer);
region._setTimer = null;
} }
if (region._clearTimer) {
clearTimeout(region._clearTimer);
region._clearTimer = null;
}
// Screen reader mutation trigger: clear first so repeat announcements are re-announced
region.textContent = ''; region.textContent = '';
setTimeout(function () {
region.textContent = text; region._setTimer = setTimeout(function () {
region.textContent = strText;
// Seslendirme yapıldıktan veya ekran okuyucu kuyruğuna alındıktan sonra
// sayfanın en altında ya da sanal imleç gezintisinde eski metin kalmaması için
// DOM'daki duyuru metnini temizle (Metin uzunluğuna göre 3 ile 7 saniye arası)
var clearDelay = Math.min(7000, Math.max(3000, strText.length * 50));
region._clearTimer = setTimeout(function () {
if (region && document.body.contains(region)) {
region.textContent = '';
}
region._clearTimer = null;
}, clearDelay);
region._setTimer = null;
}, 50); }, 50);
} }
@@ -1772,7 +1805,7 @@ try {
clearInterval(pollInterval); clearInterval(pollInterval);
hideModal(); hideModal();
btn.disabled = false; btn.disabled = false;
btn.innerHTML = "📥 YakTube'a Aktar & İzle"; btn.innerHTML = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
alert('Video indirme sırasında bir hata oluştu.'); alert('Video indirme sırasında bir hata oluştu.');
announce('Video indirme sırasında bir hata oluştu.', true); announce('Video indirme sırasında bir hata oluştu.', true);
} }
@@ -1785,7 +1818,7 @@ try {
clearInterval(pollInterval); clearInterval(pollInterval);
hideModal(); hideModal();
btn.disabled = false; btn.disabled = false;
btn.innerHTML = "📥 YakTube'a Aktar & İzle"; btn.innerHTML = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
alert( alert(
'İndirme arka planda tamamlanıyor. Ana sayfada veya Videolarım sayfasında kısa süre içinde hazır olacaktır.' 'İndirme arka planda tamamlanıyor. Ana sayfada veya Videolarım sayfasında kısa süre içinde hazır olacaktır.'
); );
@@ -1796,7 +1829,7 @@ try {
.catch(function (err) { .catch(function (err) {
hideModal(); hideModal();
btn.disabled = false; btn.disabled = false;
btn.innerHTML = "📥 YakTube'a Aktar & İzle"; btn.innerHTML = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
alert('İndirme hatası: ' + err.message); alert('İndirme hatası: ' + err.message);
announce('İndirme hatası: ' + err.message, true); announce('İndirme hatası: ' + err.message, true);
}); });
@@ -1859,7 +1892,7 @@ try {
container.id = 'yaktube-ondemand-container'; container.id = 'yaktube-ondemand-container';
container.className = 'yaktube-ondemand-section'; container.className = 'yaktube-ondemand-section';
container.setAttribute('role', 'region'); container.setAttribute('role', 'region');
container.setAttribute('aria-label', 'Canlı YouTube ve YakTube Arama Sonuçları'); container.setAttribute('aria-label', 'Canlı YouTube ve ' + getInstanceName() + ' Arama Sonuçları');
var mainContent = var mainContent =
document.querySelector('my-search, my-search-results, router-outlet, main, .main-col') || document.body; document.querySelector('my-search, my-search-results, router-outlet, main, .main-col') || document.body;
@@ -2091,7 +2124,7 @@ try {
getInstanceName(); getInstanceName();
var lWatchUrl = lItem.url || ('/videos/watch/' + (lItem.shortUUID || lItem.uuid)); var lWatchUrl = lItem.url || ('/videos/watch/' + (lItem.shortUUID || lItem.uuid));
var lViews = (lItem.views || 0) + ' görüntüleme'; var lViews = (lItem.views || 0) + ' görüntüleme';
var lLabel = lItem.name + ', ' + lChannel + ', YakTube yerel sunucusunda mevcut, Süre: ' + lDuration; var lLabel = lItem.name + ', ' + lChannel + ', ' + getInstanceName() + ' yerel sunucusunda mevcut, Süre: ' + lDuration;
localCardsHtml += localCardsHtml +=
'<div class="yaktube-card yaktube-local-card" role="article" tabindex="0" aria-label="' + '<div class="yaktube-card yaktube-local-card" role="article" tabindex="0" aria-label="' +
@@ -2926,7 +2959,9 @@ try {
item.localVideo.url + item.localVideo.url +
'" class="yaktube-play-btn yaktube-related-play-link" style="font-size:11px; padding:6px 10px;" aria-label="' + '" class="yaktube-play-btn yaktube-related-play-link" style="font-size:11px; padding:6px 10px;" aria-label="' +
item.title.replace(/"/g, '&quot;') + item.title.replace(/"/g, '&quot;') +
' videosunu YakTube\'dan hemen izle">▶️ Hemen İzle</a>' ' videosunu ' +
escapeHtml(getInstanceName()) +
'\'dan hemen izle">▶️ Hemen İzle</a>'
: '<button class="yaktube-import-btn yaktube-related-import" data-url="' + : '<button class="yaktube-import-btn yaktube-related-import" data-url="' +
item.url + item.url +
'" data-title="' + '" data-title="' +
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "peertube-plugin-yaktube-ondemand", "name": "peertube-plugin-yaktube-ondemand",
"version": "1.4.29", "version": "1.4.31",
"description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations and A11y Engine for PeerTube", "description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations and A11y Engine for PeerTube",
"main": "main.js", "main": "main.js",
"library": "./main.js", "library": "./main.js",