This commit is contained in:
pporcheret
2026-10-30 22:10:03 +01:00
parent 7f26f9b015
commit 90ffa635a0
161 changed files with 19042 additions and 111 deletions
+185
View File
@@ -0,0 +1,185 @@
/* DONA_YOUTUBE_V82: public YouTube tutorial viewer; no API key by default. */
(() => {
'use strict';
if (window.DonaYouTubeV82) return;
const ID = 'donaYoutubeV82';
const videoIdOK = id => typeof id === 'string' && /^[A-Za-z0-9_-]{11}$/.test(id);
const $ = id => document.getElementById(id);
const el = (tag, cls, value) => {
const n = document.createElement(tag);
if (cls) n.className = cls;
if (value !== undefined) n.textContent = String(value);
return n;
};
const button = (title, fn, cls='dy-btn') => {
const b = el('button', cls, title); b.type='button'; b.addEventListener('click',fn); return b;
};
let player=null, ready=false, active=false, items=[], chosen='', requestBusy=false;
let apiPromise=null, playerVersion=0;
const label = s => { if($('dyStatus')) $('dyStatus').textContent=s||''; };
const postState = state => fetch('/api/dona/youtube/player-state', {
method:'POST', credentials:'same-origin',
headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},
body:JSON.stringify({active:state})
}).catch(()=>{});
function initialize(){
const root = document.getElementById('alfredMainV1');
if(!root){setTimeout(initialize,200);return;}
const head = root.querySelector('.av-head-actions');
if(head){
const trigger=button('▶',()=>open(),'av-round dy-open-button');
trigger.title='Ouvrir YouTube';trigger.setAttribute('aria-label','Ouvrir le lecteur YouTube');
trigger.id='donaYouTubeOpen';head.prepend(trigger);
}
const overlay=el('div','dy-overlay');overlay.id=ID;overlay.hidden=true;
const panel=el('section','dy-panel');
panel.setAttribute('role','dialog');panel.setAttribute('aria-modal','true');panel.setAttribute('aria-label','Lecteur YouTube de Dona');
const top=el('header','dy-header');
const h=el('div');h.append(el('span','dy-eyebrow','DONA · YOUTUBE'),el('h1','','Tutoriels et vidéos'));
top.append(h,button('×',close,'dy-close'));panel.append(top);
const search=el('form','dy-search');search.id='dySearchForm';
const input=el('input','dy-input');input.id='dyQuery';input.type='search';input.maxLength=200;
input.placeholder='Chercher une vidéo YouTube…';input.setAttribute('aria-label','Rechercher une vidéo');input.required=true;
const submit=button('Rechercher',()=>{},'dy-btn dy-primary');submit.type='submit';submit.id='dySubmit';
search.append(input,submit);search.addEventListener('submit',e=>{e.preventDefault();searchFor(input.value);});panel.append(search);
const status=el('p','dy-status');status.id='dyStatus';status.setAttribute('role','status');status.setAttribute('aria-live','polite');panel.append(status);
const body=el('div','dy-body');
const viewer=el('div','dy-viewer');
const stage=el('div','dy-stage');stage.id='dyStage';
stage.append(el('div','dy-placeholder','Sélectionne un tutoriel pour afficher sa vidéo.'));
viewer.append(stage);
const controls=el('div','dy-controls');
controls.append(button('↶ 30 s',()=>control({action:'seek',seconds:-30})),
button('▶',()=>control({action:'play'})),button('Ⅱ',()=>control({action:'pause'})),
button('30 s ↷',()=>control({action:'seek',seconds:30})),
button('⛶',()=>control({action:'fullscreen'})));
viewer.append(controls);
const link=el('a','dy-external','Voir sur YouTube ↗');link.id='dyExternal';link.hidden=true;link.target='_blank';link.rel='noopener noreferrer';
viewer.append(link);
const results=el('div','dy-results');
results.append(el('h2','','Vidéos trouvées'));
const cards=el('div','dy-results-list');cards.id='dyResults';results.append(cards);
body.append(viewer,results);panel.append(body);
panel.append(el('p','dy-note','Vidéos publiques YouTube · Lecture depuis le site officiel · Sans connexion à un compte. Certaines vidéos peuvent refuser l’intégration.'));
overlay.append(panel);document.body.append(overlay);
overlay.addEventListener('click',e=>{if(e.target===overlay)close();});
window.addEventListener('keydown',e=>{if(e.key==='Escape'&&active)close();});
bindSocket();
}
function open(){active=true;$(ID).hidden=false;document.body.classList.add('dy-active');postState(true);}
function close(){
active=false;$(ID).hidden=true;document.body.classList.remove('dy-active');postState(false);
// Stop playback when leaving YouTube; avoid hidden audio.
try { if(player&&typeof player.stopVideo==='function')player.stopVideo(); } catch(_){}
}
function loadAPI(){
if(window.YT && typeof window.YT.Player==='function')return Promise.resolve();
if(apiPromise)return apiPromise;
apiPromise=new Promise((resolve,reject)=>{
const previous=window.onYouTubeIframeAPIReady;
window.onYouTubeIframeAPIReady=()=>{try{if(typeof previous==='function')previous();}catch(_){ } resolve();};
const script=document.createElement('script');script.src='https://www.youtube.com/iframe_api';
script.async=true;script.onerror=()=>reject(new Error('Lecteur YouTube inaccessible.'));
document.head.append(script);
setTimeout(()=>{if(!(window.YT&&window.YT.Player))reject(new Error('Le lecteur YouTube ne répond pas.'));},15000);
});
return apiPromise;
}
async function loadVideo(id){
if(!videoIdOK(id))return;
chosen=id;open();
const item=items.find(x=>x.id===id);
$('dyExternal').href='https://www.youtube.com/watch?v='+id;
$('dyExternal').hidden=false;
$('dyStage').replaceChildren();
const mount=el('div','dy-frame');mount.id='dyPlayer';$('dyStage').append(mount);
const version=++playerVersion;
ready=false;
try { if(player&&typeof player.destroy==='function')player.destroy(); }catch(_){ }
player=null;
label(item ? 'Vidéo sélectionnée : '+item.title : 'Chargement de la vidéo…');
try {
await loadAPI();if(version!==playerVersion)return;
player=new window.YT.Player('dyPlayer', {
width:'100%', height:'100%', videoId:id,
playerVars:{autoplay:0,controls:1,playsinline:1,rel:0,origin:window.location.origin},
events:{
onReady:()=>{ready=true;label('Appuie sur Lecture pour démarrer la vidéo.');},
onError:(event)=>label('Cette vidéo ne peut pas être lue ici (code '+event.data+'). Utilise « Voir sur YouTube ».')
}
});
} catch(err) {
if(version===playerVersion){
$('dyStage').replaceChildren(el('p','dy-placeholder','Le lecteur intégré est indisponible. Ouvre cette vidéo sur YouTube.'));
label(err.message||'Lecteur indisponible.');
}
}
}
function control(command){
if(!active||!command||!command.action)return;
if(command.action==='close'){close();return;}
if(!ready||!player){label('Le lecteur n’est pas encore prêt.');return;}
try {
switch(command.action){
case 'play':player.playVideo();break;
case 'pause':player.pauseVideo();break;
case 'seek':{
const offset=Number(command.seconds)||0;
player.seekTo(Math.max(0,(Number(player.getCurrentTime())||0)+offset),true);break;
}
case 'fullscreen':{
const frame=$('dyStage').querySelector('iframe');
if(frame&&frame.requestFullscreen)frame.requestFullscreen();break;
}
}
} catch(e){label('Commande vidéo indisponible : '+e.message);}
}
function showResults(payload){
if(!payload||payload.type!=='dona_youtube')return;
items=(Array.isArray(payload.videos)?payload.videos:[]).filter(v=>v&&videoIdOK(v.id)).slice(0,6);
$('dyQuery').value=String(payload.query||'').slice(0,200);
const list=$('dyResults');list.replaceChildren();
for(const v of items){
const b=button('',()=>loadVideo(v.id),'dy-video-item');b.textContent='';
const img=el('img');img.loading='lazy';img.alt='Miniature : '+String(v.title||'Vidéo');
img.src='https://i.ytimg.com/vi/'+v.id+'/hqdefault.jpg';
const txt=el('span','dy-video-title');txt.append(el('strong','',v.title||'Vidéo YouTube'));
if(v.channel)txt.append(el('small','',v.channel));b.append(img,txt);list.append(b);
}
open();
if(payload.error)label(payload.error);
else if(items.length)label(items.length+' vidéo(s) trouvée(s). Choisis celle qui t’intéresse.');
else label('Aucune vidéo exploitable trouvée. Essaie une autre formulation.');
if(items.length)loadVideo(items[0].id);
else {chosen='';ready=false;playerVersion++;try{player?.destroy?.()}catch(_){ }player=null;
$('dyStage').replaceChildren(el('p','dy-placeholder','Aucun résultat : tente une autre recherche.'));
$('dyExternal').hidden=true;
}
}
async function searchFor(query){
query=String(query||'').trim();
if(query.length<2||query.length>200){label('Saisis entre 2 et 200 caractères.');return;}
if(requestBusy)return;
requestBusy=true;$('dySubmit').disabled=true;open();label('Dona recherche des vidéos via Hermes…');
try {
const res=await fetch('/api/dona/youtube/search',{
method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},
body:JSON.stringify({query})
});
const data=await res.json();
if(!res.ok||!data.ok)throw new Error(data.error||'Recherche impossible');
showResults({type:'dona_youtube',query:data.query,videos:data.videos});
}catch(e){label(e.message||'Recherche indisponible.');}
finally{requestBusy=false;$('dySubmit').disabled=false;}
}
function bindSocket(){
const socket=window.alfredSocket;
if(!socket){setTimeout(bindSocket,250);return;}
socket.on('alfred_render',payload=>{if(payload?.type==='dona_youtube')showResults(payload);});
socket.on('dona_youtube_control',command=>control(command));
}
window.DonaYouTubeV82={open,close,control,showResults,search:searchFor};
initialize();
})();