Spaces:
Running
Running
Restore static/app_v2.js to state at commit 2a3b11a
Browse files- static/app_v2.js +51 -84
static/app_v2.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
| 1 |
/**
|
| 2 |
* VNEWS Frontend v2 - Shorts Dantri/SKDS removed, VTV Digital CDN
|
| 3 |
-
* v2.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
*/
|
| 5 |
function _proxyImg(url){
|
| 6 |
if(!url || typeof url !== 'string') return '';
|
|
@@ -38,7 +42,6 @@ async function loadHome(){
|
|
| 38 |
+'<div id="home-after-wc"></div>';
|
| 39 |
|
| 40 |
const afterEl = homeEl.querySelector('#home-after-wc');
|
| 41 |
-
const newsEl = homeEl.querySelector('#home-news-grid');
|
| 42 |
|
| 43 |
loadLivescore('today');
|
| 44 |
loadHotTopics();
|
|
@@ -111,13 +114,10 @@ function _renderHLIn(afterEl){
|
|
| 111 |
// === WALL POST HELPERS ===
|
| 112 |
function makeWallItem(p,i){
|
| 113 |
var hasVideo = p.video && p.video.length > 0;
|
| 114 |
-
var hasSlides = p.slides && p.slides.length > 0;
|
| 115 |
var thumbContent = p.img
|
| 116 |
? '<img src="/api/proxy/img?url='+encodeURIComponent(p.img)+'" loading="lazy" onerror="this.style.display=\'none\'">'
|
| 117 |
: (hasVideo ? '<video src="'+esc(p.video)+'" muted></video>' : '');
|
| 118 |
-
var
|
| 119 |
-
if (hasVideo) badges += '<div class="wall-video-badge">🎬</div>';
|
| 120 |
-
if (hasSlides) badges += '<div class="wall-slides-badge" style="position:absolute;top:4px;left:4px;background:rgba(240,192,64,.9);color:#000;font-size:10px;padding:2px 6px;border-radius:6px;font-weight:700">📊 Slides</div>';
|
| 121 |
var vid = p.id||i;
|
| 122 |
var lang = p.language || detectLanguage(p.title + ' ' + (p.text||''));
|
| 123 |
var curVoice = p.voice || getAutoVoice(lang);
|
|
@@ -145,13 +145,10 @@ function makeWallItem(p,i){
|
|
| 145 |
+'<option value="1.35"'+(spd==='1.35'?' selected':'')+'>1.35x</option>'
|
| 146 |
+'</select>'
|
| 147 |
+'</div>';
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
? '<button class="wall-btn-slides" style="background:#c8b045;border:0;color:#000;border-radius:14px;padding:6px 8px;font-size:10px;cursor:pointer;font-weight:700" onclick="event.stopPropagation();readSlidePost('+i+')">📊 Slides</button>'
|
| 153 |
-
: '';
|
| 154 |
-
return '<div class="wall-item" id="wall-item-'+esc(vid)+'"><div class="wall-thumb">'+thumbContent+badges+'</div><div class="wall-title">'+esc(p.title)+'</div><div class="wall-text">'+esc((p.text||'').slice(0,180))+'</div>'+voiceBar+'<div class="wall-actions"><button class="primary" onclick="readWallPost('+i+')">Xem</button>'+slidesBtn+makeBtn+'</div></div>';
|
| 155 |
}
|
| 156 |
|
| 157 |
async function makeShortVideo(postId, btn, voice, speed, emotion){
|
|
@@ -425,84 +422,54 @@ function initTikTokFeed(){const feed=document.getElementById('tiktok-feed');if(!
|
|
| 425 |
async function openHighlightFeed(league,idx,forceUrl){showView('view-tiktok');const el=document.getElementById('view-tiktok');el.innerHTML='<div class="loading">Đang tải...</div>';let articles=(_hlLeagueData||{})[league]||[];if(!articles.length){try{articles=await fetch('/api/highlights/'+league).then(r=>r.json())}catch(e){articles=[]}}if(!articles.length){el.innerHTML='<div class="loading">Không có video</div>';return}const vids=[];const results=await Promise.all(articles.map(async(a,i)=>{try{const r=await fetch('/api/video_url?url='+encodeURIComponent(a.link)+'&img='+encodeURIComponent(a.img||''));const v=await r.json();if(v&&v.src){return{_idx:i,title:a.title||v.title||'',link:a.link||'',img:a.img||v.poster||'',src:v.src,type:v.type||'',poster:v.poster||a.img||''}}return null}catch(e){}return null}));results.forEach(r=>{if(r)vids.push(r)});vids.sort((a,b)=>a._idx-b._idx);if(!vids.length){el.innerHTML='<div class="loading">Không tìm thấy video</div>';return}let ti=vids.findIndex(v=>v._idx===idx);if(ti<0)ti=0;const ordered=ti>0?[...vids.slice(ti),...vids.slice(0,ti)]:vids;let h=`<button class="back-btn" onclick="switchCat('home')">← Highlight</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">`;ordered.forEach((v,i)=>{const isYT=v.type==='youtube',isHLS=!isYT&&v.src?.includes('.m3u8'),poster=v.poster?` poster="${esc(v.poster)}"`:'';const vtag=isYT?`<iframe data-yt-src="${v.src}" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture"></iframe>`:isHLS?`<video playsinline preload="none"${poster} data-hls="${v.src}" loop controls></video>`:`<video playsinline preload="none"${poster} loop controls><source src="${v.src}" type="video/mp4"></video>`;const videoId='hl-'+league+'-'+v._idx;h+=buildTikTokSlide({vtag,title:v.title,badge:'HL',badgeClass:'badge-fpt',videoId,idx:i,total:ordered.length,shareUrl:v.link||'',postId:'',extraBtn:`<button class="tiktok-right-btn" onclick="event.stopPropagation();openHighlightFeed('${league}',${(i+1)%ordered.length})"><div class="icon">⏭️</div></button>`})});h+='</div></div>';el.innerHTML=h;setTimeout(()=>initTikTokFeed(),200);}
|
| 426 |
async function openYTShortsFeed(idx){showView('view-tiktok');const el=document.getElementById('view-tiktok');el.innerHTML='<div class="loading">Đã xóa Shorts Dân trí/SKĐS</div>';}
|
| 427 |
async function openShortAIFeed(idx){showView('view-tiktok');const el=document.getElementById('view-tiktok');if(!_wallPosts||!_wallPosts.length){el.innerHTML='<div class="loading">Không có Short AI</div>';return}const aiPosts=_wallPosts.filter(p=>p.video);if(!aiPosts.length||idx>=aiPosts.length){el.innerHTML='<div class="loading">Không có Short AI</div>';return}const ordered=aiPosts.slice(idx).concat(aiPosts.slice(0,idx));let h=`<button class="back-btn" onclick="switchCat('home')">← Tường AI</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">`;ordered.forEach((p,i)=>{const baseIdx=_wallPosts.indexOf(p);const vtag=p.video?`<video playsinline preload="none" src="${esc(p.video)}" loop controls></video>`:'';h+=buildTikTokSlide({vtag,title:p.title,badge:'Short AI',badgeClass:'badge-ai',videoId:p.id||'ai-'+i,idx:i,total:ordered.length,shareUrl:p.video||'',postId:p.id||'',extraBtn:`<button class="tiktok-right-btn" onclick="event.stopPropagation();shareVideoToApps('${esc(p.video)}','${esc(p.title)}')"><div class="icon">📥</div></button>`})});h+='</div></div>';el.innerHTML=h;setTimeout(()=>initTikTokFeed(),200);}
|
| 428 |
-
function readWallPost(idx){
|
| 429 |
-
const p=_wallPosts&&_wallPosts[idx];
|
| 430 |
-
if(!p) return;
|
| 431 |
-
if(p.slides&&p.slides.length){readSlidePost(idx);return}
|
| 432 |
-
readArticle(p.url||'','','',p.title,p.text);
|
| 433 |
-
}
|
| 434 |
/** Show rewrite slide viewer - vertical slides with text+image */
|
| 435 |
-
function readSlidePost(idx){
|
| 436 |
-
const p=_wallPosts[idx];
|
| 437 |
-
if(!p||!p.slides) return;
|
| 438 |
-
showView('view-article');
|
| 439 |
-
const el=document.getElementById('view-article');
|
| 440 |
-
let h=`<button class="back-btn" onclick="switchCat('home')">← Tường AI</button><div class="slide-viewer" style="padding:12px;max-width:600px;margin:0 auto">`;
|
| 441 |
-
p.slides.forEach((s,i)=>{
|
| 442 |
-
h+=`<div class="slide-card" style="background:#1a1a1a;border:1px solid #2a2a2a;border-radius:12px;padding:16px;margin-bottom:12px"><div class="slide-num" style="color:#5cb87a;font-size:12px;font-weight:700;margin-bottom:6px">Slide ${s.index||i+1}/${p.slides.length}</div>${s.image?`<img src="${_proxyImg(s.image)}" style="width:100%;max-height:300px;object-fit:cover;border-radius:8px;margin-bottom:8px" loading="lazy" onerror="this.style.display=\'none\'">`:''}<p style="color:#ddd;font-size:14px;line-height:1.6;margin:0">${esc(s.text)}</p></div>`;
|
| 443 |
-
});
|
| 444 |
-
h+=`</div><div class="article-actions" style="padding:12px;text-align:center"><button onclick="doShare('${esc(p.title)}','${esc(p.url||'')}','${esc(p.img||'')}','${esc(p.id||'')}')" style="background:#2d8659;border:0;color:#fff;padding:8px 16px;border-radius:10px;cursor:pointer;font-size:12px">📤 Chia sẻ bài viết này</button></div>`;
|
| 445 |
-
el.innerHTML=h;
|
| 446 |
-
}
|
| 447 |
function readNewsTab(tab){loadNewsTab();}
|
| 448 |
function loadNewsTab(){const el=document.getElementById('view-cat');if(!el)return;el.innerHTML='<div class="loading">Đang tải tin tức...</div>';fetch('/api/homepage').then(r=>r.json()).then(articles=>{if(!articles||!articles.length){el.innerHTML='<div class="loading">Không có tin</div>';return}let h='<div class="grid">';articles.forEach(a=>{const src=a.source||'vne';const badge=a.group||a.source||'';h+=`<div class="card" onclick="readArticle('${esc(a.link)}')"><div class="card-img">${a.img?`<img src="${_proxyImg(a.img)}" loading="lazy" onerror="this.style.display=\'none\'">`:''}</div><div class="card-body"><span class="badge badge-${src}">${esc(badge)}</span><div class="card-title">${esc(a.title)}</div></div></div>`;});h+='</div>';el.innerHTML=h;}).catch(()=>{el.innerHTML='<div class="loading">Lỗi tải</div>';});}
|
| 449 |
|
| 450 |
-
function readArticle(url,title,img,presetTitle,presetText){showView('view-article');const el=document.getElementById('view-article');el.innerHTML='<div class="loading">Đang tải...</div><button class="back-btn" onclick="switchCat(\'home\')">← Quay lại</button>';if(presetTitle){el.innerHTML=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><h1 class="article-title">${esc(presetTitle)}</h1>${presetText?`<div class="article-summary" style="white-space:pre-wrap">${esc(presetText)}</div>`:''}</div><div class="article-actions"><button onclick="doShare('${esc(presetTitle)}','${esc(url||'')}','')">📤 Chia sẻ</button></div>`
|
|
|
|
|
|
|
|
|
|
| 451 |
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
}
|
| 458 |
-
window.rewriteSlide = async function rewriteSlide(url){
|
| 459 |
-
if(!url) return;
|
| 460 |
-
const btn = document.querySelector('.article-actions .primary');
|
| 461 |
-
if(btn){ btn.disabled=true; btn.textContent='⏳ Đang tạo slides...'; }
|
| 462 |
-
toast('⏳ Đang tạo slide rewrite...');
|
| 463 |
try{
|
| 464 |
-
|
| 465 |
-
const
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
if(j2.post&&j2.post.slides&&j2.post.slides.length){
|
| 475 |
-
showView('view-article');
|
| 476 |
-
const el = document.getElementById('view-article');
|
| 477 |
-
let h = `<button class="back-btn" onclick="switchCat('home')">← Tường AI</button><div class="slide-viewer" style="padding:12px;max-width:600px;margin:0 auto">`;
|
| 478 |
-
j2.post.slides.forEach(s=>{
|
| 479 |
-
h += `<div class="slide-card" style="background:#1a1a1a;border:1px solid #2a2a2a;border-radius:12px;padding:16px;margin-bottom:12px"><div class="slide-num" style="color:#5cb87a;font-size:12px;font-weight:700;margin-bottom:6px">Slide ${s.index}/${j2.post.slides.length}</div>${
|
| 480 |
-
s.image ? `<img src="${s.image.startsWith('/api/')?s.image:'/api/proxy/img?url='+encodeURIComponent(s.image)}" style="width:100%;max-height:300px;object-fit:cover;border-radius:8px;margin-bottom:8px" loading="lazy" onerror="this.style.display=\'none\'">` : ''}<p style="color:#ddd;font-size:14px;line-height:1.6;margin:0">${esc(s.text)}</p></div>`;
|
| 481 |
-
});
|
| 482 |
-
h += `</div>`;
|
| 483 |
-
el.innerHTML = h;
|
| 484 |
-
if(j2.post) prependWallPost(j2.post);
|
| 485 |
-
toast('✅ Slide đã được lưu!');
|
| 486 |
-
}
|
| 487 |
-
return;
|
| 488 |
-
}
|
| 489 |
-
toast('✅ Đã tạo slide!');
|
| 490 |
-
if(j.post && j.post.slides && j.post.slides.length){
|
| 491 |
-
showView('view-article');
|
| 492 |
-
const el = document.getElementById('view-article');
|
| 493 |
-
let h = `<button class="back-btn" onclick="switchCat('home')">← Tường AI</button><div class="slide-viewer" style="padding:12px;max-width:600px;margin:0 auto">`;
|
| 494 |
-
j.post.slides.forEach(s=>{
|
| 495 |
-
h += `<div class="slide-card" style="background:#1a1a1a;border:1px solid #2a2a2a;border-radius:12px;padding:16px;margin-bottom:12px"><div class="slide-num" style="color:#5cb87a;font-size:12px;font-weight:700;margin-bottom:6px">Slide ${s.index}/${j.post.slides.length}</div>${
|
| 496 |
-
s.image ? `<img src="${s.image.startsWith('/api/')?s.image:'/api/proxy/img?url='+encodeURIComponent(s.image)}" style="width:100%;max-height:300px;object-fit:cover;border-radius:8px;margin-bottom:8px" loading="lazy" onerror="this.style.display=\'none\'">` : ''}<p style="color:#ddd;font-size:14px;line-height:1.6;margin:0">${esc(s.text)}</p></div>`;
|
| 497 |
});
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
}
|
| 502 |
-
if(btn) btn.textContent='✅ Hoàn tất';
|
| 503 |
-
} catch(e){
|
| 504 |
-
toast('❌ '+e.message);
|
| 505 |
-
if(btn){ btn.disabled=false; btn.textContent='🤖 Slide Rewrite AI'; }
|
| 506 |
}
|
| 507 |
-
};
|
| 508 |
}
|
|
|
|
| 1 |
/**
|
| 2 |
* VNEWS Frontend v2 - Shorts Dantri/SKDS removed, VTV Digital CDN
|
| 3 |
+
* v2.8 - Changed short AI feed video share button icon (📥) to distinguish from article share (📤)
|
| 4 |
+
* v2.6 - Fixed share links: doShare now includes post_id for wall posts,
|
| 5 |
+
* readSlidePost has share button, /s + /s/{slug} render slides/video inline
|
| 6 |
+
* v2.5 - Added 'Tạo lại' button on wall cards with video
|
| 7 |
+
* v2.4 - Fixed: prependWallPost detached-element bug, makeShortVideo UI update, slide viewer for rewrite posts
|
| 8 |
*/
|
| 9 |
function _proxyImg(url){
|
| 10 |
if(!url || typeof url !== 'string') return '';
|
|
|
|
| 42 |
+'<div id="home-after-wc"></div>';
|
| 43 |
|
| 44 |
const afterEl = homeEl.querySelector('#home-after-wc');
|
|
|
|
| 45 |
|
| 46 |
loadLivescore('today');
|
| 47 |
loadHotTopics();
|
|
|
|
| 114 |
// === WALL POST HELPERS ===
|
| 115 |
function makeWallItem(p,i){
|
| 116 |
var hasVideo = p.video && p.video.length > 0;
|
|
|
|
| 117 |
var thumbContent = p.img
|
| 118 |
? '<img src="/api/proxy/img?url='+encodeURIComponent(p.img)+'" loading="lazy" onerror="this.style.display=\'none\'">'
|
| 119 |
: (hasVideo ? '<video src="'+esc(p.video)+'" muted></video>' : '');
|
| 120 |
+
var videoBadge = hasVideo ? '<div class="wall-video-badge">🎬</div>' : '';
|
|
|
|
|
|
|
| 121 |
var vid = p.id||i;
|
| 122 |
var lang = p.language || detectLanguage(p.title + ' ' + (p.text||''));
|
| 123 |
var curVoice = p.voice || getAutoVoice(lang);
|
|
|
|
| 145 |
+'<option value="1.35"'+(spd==='1.35'?' selected':'')+'>1.35x</option>'
|
| 146 |
+'</select>'
|
| 147 |
+'</div>';
|
| 148 |
+
var makeBtn = hasVideo
|
| 149 |
+
? '<button class="wall-btn-video" onclick="event.stopPropagation();openShortAIFeed('+i+')">▶ Xem Short</button><button class="wall-btn-make" style="margin-left:4px" onclick="event.stopPropagation();makeShortVideo(\''+esc(vid)+'\',this,_ttsSelections[\'inline-'+esc(vid)+'\'].voice,parseFloat(document.querySelector(\'.wss[data-selkey=inline-'+esc(vid)+']\').value)||1.2,_ttsSelections[\'inline-'+esc(vid)+'\'].emotion)">🎬 Tạo lại</button>'
|
| 150 |
+
: '<button class="wall-btn-make" onclick="event.stopPropagation();makeShortVideo(\''+esc(vid)+'\',this,_ttsSelections[\'inline-'+esc(vid)+'\'].voice,parseFloat(document.querySelector(\'.wss[data-selkey=inline-'+esc(vid)+']\').value)||1.2,_ttsSelections[\'inline-'+esc(vid)+'\'].emotion)">🎬 Tạo Short</button>';
|
| 151 |
+
return '<div class="wall-item" id="wall-item-'+esc(vid)+'"><div class="wall-thumb">'+thumbContent+videoBadge+'</div><div class="wall-title">'+esc(p.title)+'</div><div class="wall-text">'+esc((p.text||'').slice(0,180))+'</div>'+voiceBar+'<div class="wall-actions"><button class="primary" onclick="readWallPost('+i+')">Xem</button>'+makeBtn+'</div></div>';
|
|
|
|
|
|
|
|
|
|
| 152 |
}
|
| 153 |
|
| 154 |
async function makeShortVideo(postId, btn, voice, speed, emotion){
|
|
|
|
| 422 |
async function openHighlightFeed(league,idx,forceUrl){showView('view-tiktok');const el=document.getElementById('view-tiktok');el.innerHTML='<div class="loading">Đang tải...</div>';let articles=(_hlLeagueData||{})[league]||[];if(!articles.length){try{articles=await fetch('/api/highlights/'+league).then(r=>r.json())}catch(e){articles=[]}}if(!articles.length){el.innerHTML='<div class="loading">Không có video</div>';return}const vids=[];const results=await Promise.all(articles.map(async(a,i)=>{try{const r=await fetch('/api/video_url?url='+encodeURIComponent(a.link)+'&img='+encodeURIComponent(a.img||''));const v=await r.json();if(v&&v.src){return{_idx:i,title:a.title||v.title||'',link:a.link||'',img:a.img||v.poster||'',src:v.src,type:v.type||'',poster:v.poster||a.img||''}}return null}catch(e){}return null}));results.forEach(r=>{if(r)vids.push(r)});vids.sort((a,b)=>a._idx-b._idx);if(!vids.length){el.innerHTML='<div class="loading">Không tìm thấy video</div>';return}let ti=vids.findIndex(v=>v._idx===idx);if(ti<0)ti=0;const ordered=ti>0?[...vids.slice(ti),...vids.slice(0,ti)]:vids;let h=`<button class="back-btn" onclick="switchCat('home')">← Highlight</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">`;ordered.forEach((v,i)=>{const isYT=v.type==='youtube',isHLS=!isYT&&v.src?.includes('.m3u8'),poster=v.poster?` poster="${esc(v.poster)}"`:'';const vtag=isYT?`<iframe data-yt-src="${v.src}" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture"></iframe>`:isHLS?`<video playsinline preload="none"${poster} data-hls="${v.src}" loop controls></video>`:`<video playsinline preload="none"${poster} loop controls><source src="${v.src}" type="video/mp4"></video>`;const videoId='hl-'+league+'-'+v._idx;h+=buildTikTokSlide({vtag,title:v.title,badge:'HL',badgeClass:'badge-fpt',videoId,idx:i,total:ordered.length,shareUrl:v.link||'',postId:'',extraBtn:`<button class="tiktok-right-btn" onclick="event.stopPropagation();openHighlightFeed('${league}',${(i+1)%ordered.length})"><div class="icon">⏭️</div></button>`})});h+='</div></div>';el.innerHTML=h;setTimeout(()=>initTikTokFeed(),200);}
|
| 423 |
async function openYTShortsFeed(idx){showView('view-tiktok');const el=document.getElementById('view-tiktok');el.innerHTML='<div class="loading">Đã xóa Shorts Dân trí/SKĐS</div>';}
|
| 424 |
async function openShortAIFeed(idx){showView('view-tiktok');const el=document.getElementById('view-tiktok');if(!_wallPosts||!_wallPosts.length){el.innerHTML='<div class="loading">Không có Short AI</div>';return}const aiPosts=_wallPosts.filter(p=>p.video);if(!aiPosts.length||idx>=aiPosts.length){el.innerHTML='<div class="loading">Không có Short AI</div>';return}const ordered=aiPosts.slice(idx).concat(aiPosts.slice(0,idx));let h=`<button class="back-btn" onclick="switchCat('home')">← Tường AI</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">`;ordered.forEach((p,i)=>{const baseIdx=_wallPosts.indexOf(p);const vtag=p.video?`<video playsinline preload="none" src="${esc(p.video)}" loop controls></video>`:'';h+=buildTikTokSlide({vtag,title:p.title,badge:'Short AI',badgeClass:'badge-ai',videoId:p.id||'ai-'+i,idx:i,total:ordered.length,shareUrl:p.video||'',postId:p.id||'',extraBtn:`<button class="tiktok-right-btn" onclick="event.stopPropagation();shareVideoToApps('${esc(p.video)}','${esc(p.title)}')"><div class="icon">📥</div></button>`})});h+='</div></div>';el.innerHTML=h;setTimeout(()=>initTikTokFeed(),200);}
|
| 425 |
+
function readWallPost(idx){const p=_wallPosts&&_wallPosts[idx];if(!p)return;if(p.slides&&p.slides.length){readSlidePost(idx);return}readArticle(p.url||'','','',p.title,p.text);}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
/** Show rewrite slide viewer - vertical slides with text+image */
|
| 427 |
+
function readSlidePost(idx){const p=_wallPosts[idx];if(!p||!p.slides)return;showView('view-article');const el=document.getElementById('view-article');let h=`<button class="back-btn" onclick="switchCat('home')">← Tường AI</button><div class="slide-viewer" style="padding:12px;max-width:600px;margin:0 auto">`;p.slides.forEach((s,i)=>{h+=`<div class="slide-card" style="background:#1a1a1a;border:1px solid #2a2a2a;border-radius:12px;padding:16px;margin-bottom:12px"><div class="slide-num" style="color:#5cb87a;font-size:12px;font-weight:700;margin-bottom:6px">Slide ${s.index||i+1}/${p.slides.length}</div>${s.image?`<img src="${_proxyImg(s.image)}" style="width:100%;max-height:300px;object-fit:cover;border-radius:8px;margin-bottom:8px" loading="lazy" onerror="this.style.display=\'none\'">`:''}<p style="color:#ddd;font-size:14px;line-height:1.6;margin:0">${esc(s.text)}</p></div>`;});h+=`</div><div class="article-actions" style="padding:12px;text-align:center"><button onclick="doShare('${esc(p.title)}','${esc(p.url||'')}','${esc(p.img||'')}','${esc(p.id||'')}')" style="background:#2d8659;border:0;color:#fff;padding:8px 16px;border-radius:10px;cursor:pointer;font-size:12px">📤 Chia sẻ bài viết này</button></div>`;el.innerHTML=h;}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 428 |
function readNewsTab(tab){loadNewsTab();}
|
| 429 |
function loadNewsTab(){const el=document.getElementById('view-cat');if(!el)return;el.innerHTML='<div class="loading">Đang tải tin tức...</div>';fetch('/api/homepage').then(r=>r.json()).then(articles=>{if(!articles||!articles.length){el.innerHTML='<div class="loading">Không có tin</div>';return}let h='<div class="grid">';articles.forEach(a=>{const src=a.source||'vne';const badge=a.group||a.source||'';h+=`<div class="card" onclick="readArticle('${esc(a.link)}')"><div class="card-img">${a.img?`<img src="${_proxyImg(a.img)}" loading="lazy" onerror="this.style.display=\'none\'">`:''}</div><div class="card-body"><span class="badge badge-${src}">${esc(badge)}</span><div class="card-title">${esc(a.title)}</div></div></div>`;});h+='</div>';el.innerHTML=h;}).catch(()=>{el.innerHTML='<div class="loading">Lỗi tải</div>';});}
|
| 430 |
|
| 431 |
+
function readArticle(url,title,img,presetTitle,presetText){showView('view-article');const el=document.getElementById('view-article');el.innerHTML='<div class="loading">Đang tải...</div><button class="back-btn" onclick="switchCat(\'home\')">← Quay lại</button>';if(presetTitle){el.innerHTML=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><h1 class="article-title">${esc(presetTitle)}</h1>${presetText?`<div class="article-summary" style="white-space:pre-wrap">${esc(presetText)}</div>`:''}</div><div class="article-actions"><button onclick="doShare('${esc(presetTitle)}','${esc(url||'')}','')">📤 Chia sẻ</button></div>`;return;}if(!url)return;fetch('/api/article?url='+encodeURIComponent(url)).then(r=>r.json()).then(d=>{let h=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view">`;if(d.title)h+=`<h1 class="article-title">${esc(d.title)}</h1>`;if(d.summary)h+=`<div class="article-summary">${esc(d.summary)}</div>`;if(d.body)d.body.forEach(b=>{if(b.type==='p')h+=`<p class="article-p">${esc(b.text)}</p>`;else if(b.type==='heading')h+=`<h2 class="article-h2">${esc(b.text)}</h2>`;else if(b.type==='img'&&b.src)h+=`<img class="article-img" src="${_proxyImg(b.src)}" loading="lazy" onerror="this.style.display=\'none\'">`;});h+=`</div><div class="article-actions"><button onclick="doShare('${esc(d.title||'')}','${esc(url)}','${esc(d.og_image||'')}')">📤 Chia sẻ</button><button class="primary" onclick="rewriteSlide('${esc(url)}')">🤖 Slide Rewrite AI</button></div>`;el.innerHTML=h;}).catch(()=>{el.innerHTML=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><p>Không thể tải bài viết</p></div>`;});}
|
| 432 |
+
async function rewriteSlide(url){if(!url)return;const btn=document.querySelector('.article-actions .primary')||event?.target;if(btn){btn.disabled=true;btn.textContent='⏳ Đang tạo slides...';}toast('⏳ Đang tạo slide rewrite...');try{const r=await fetch('/api/rewrite_share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url})});const j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');toast('✅ Đã tạo slide! Đang tạo Short AI...');if(btn)btn.textContent='✅ Đang tạo Short AI...';if(j.post){j.post.slides=j.slides||[];prependWallPost(j.post);}// Show slides immediately if available
|
| 433 |
+
if(j.slides && j.slides.length){showView('view-article');const el=document.getElementById('view-article');let h=`<button class="back-btn" onclick="switchCat('home')">← Tường AI</button><div class="slide-viewer" style="padding:12px;max-width:600px;margin:0 auto">`;j.slides.forEach(s=>{h+=`<div class="slide-card" style="background:#1a1a1a;border:1px solid #2a2a2a;border-radius:12px;padding:16px;margin-bottom:12px"><div class="slide-num" style="color:#5cb87a;font-size:12px;font-weight:700;margin-bottom:6px">Slide ${s.index}/${j.slides.length}</div>${s.image?`<img src="${s.image.startsWith('/api/')?s.image:'/api/proxy/img?url='+encodeURIComponent(s.image)}" style="width:100%;max-height:300px;object-fit:cover;border-radius:8px;margin-bottom:8px" loading="lazy" onerror="this.style.display='none'">`:''}<p style="color:#ddd;font-size:14px;line-height:1.6;margin:0">${esc(s.text)}</p></div>`;});h+=`</div><div style="text-align:center;padding:12px"><div id="short-ai-status" style="color:#888;font-size:12px">⏳ Đang tạo video Short AI...</div></div>`;el.innerHTML=h;}
|
| 434 |
+
const postId=j.post&&j.post.id;if(postId){setTimeout(async()=>{const sr=await fetch('/api/ai/short/'+encodeURIComponent(postId),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({voice:'vi-VN-HoaiMyNeural',emotion:'neutral',speed:1.2})});const sj=await sr.json();if(sr.ok&&sj.video&&typeof _wallPosts!=='undefined'){const p=_wallPosts.find(x=>String(x.id)===String(postId));if(p){p.video=sj.video;const itemId='wall-item-'+postId;const el2=document.getElementById(itemId);if(el2){const idx=_wallPosts.indexOf(p);el2.insertAdjacentHTML('afterend',makeWallItem(p,idx));el2.remove();}}toast('✅ Short AI đã sẵn sàng!');const statusEl=document.getElementById('short-ai-status');if(statusEl)statusEl.innerHTML='✅ Short AI đẵn sàng! <button class="primary" onclick="openShortAIFeed(0)" style="background:#2d8659;border:0;color:#fff;padding:6px 12px;border-radius:10px;font-size:11px;cursor:pointer;margin-left:8px">▶ Xem ngay</button>';}},500);}if(btn)btn.textContent='✅ Hoàn tất';}catch(e){toast('❌ '+e.message);if(btn){btn.disabled=false;btn.textContent='🤖 Slide Rewrite AI';}}}
|
| 435 |
|
| 436 |
+
// ===== SHARE VIDEO TO OTHER APPS =====
|
| 437 |
+
async function shareVideoToApps(videoUrl, title){
|
| 438 |
+
if(!videoUrl){
|
| 439 |
+
toast('Không có video để chia sẻ');
|
| 440 |
+
return;
|
| 441 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
try{
|
| 443 |
+
// Fetch video as blob for native sharing
|
| 444 |
+
const response = await fetch(videoUrl);
|
| 445 |
+
const blob = await response.blob();
|
| 446 |
+
const file = new File([blob], (title||'video')+'.mp4', {type: 'video/mp4'});
|
| 447 |
+
|
| 448 |
+
if(navigator.share && navigator.canShare && navigator.canShare({files:[file]})){
|
| 449 |
+
await navigator.share({
|
| 450 |
+
files: [file],
|
| 451 |
+
title: title || 'Short AI Video',
|
| 452 |
+
text: 'Chia sẻ từ VNEWS'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 453 |
});
|
| 454 |
+
toast('✅ Đã chia sẻ video!');
|
| 455 |
+
} else {
|
| 456 |
+
// Fallback: copy video URL
|
| 457 |
+
await navigator.clipboard.writeText(videoUrl);
|
| 458 |
+
toast('✅ Đã sao chép link video!');
|
| 459 |
+
}
|
| 460 |
+
}catch(e){
|
| 461 |
+
// Fallback: try to download directly
|
| 462 |
+
try{
|
| 463 |
+
const a = document.createElement('a');
|
| 464 |
+
a.href = videoUrl;
|
| 465 |
+
a.download = (title||'video')+'.mp4';
|
| 466 |
+
a.style.display = 'none';
|
| 467 |
+
document.body.appendChild(a);
|
| 468 |
+
a.click();
|
| 469 |
+
document.body.removeChild(a);
|
| 470 |
+
toast('✅ Đang tải video xuống...');
|
| 471 |
+
}catch(err){
|
| 472 |
+
toast('❌ Không thể chia sẻ video');
|
| 473 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 474 |
}
|
|
|
|
| 475 |
}
|