';
- h += '
'+(hotMode?'🔥 Thêm Short HOT':'🎬 Tạo Short AI')+(hasPrev?' (Tạo lại)':'')+' ✕ ';
- if(hotMode){
- h += '
📝 Tiêu đề bài đăng (hiển thị trên Tường AI, cũng là nội dung giọng đọc nếu không chọn video): ';
- h += '
';
- }
-
- /* 0. Recreate mode (designed slides only, no text) */
- if(hasSlides){
- h += '
';
- h += '
🖼️ Tạo lại từ ảnh đã thiết kế (không đưa text vào short)';
- h += '
Dùng ảnh slide đã thiết kế làm nền + giữ nguyên audio của Short trước đó. Chữ KHÔNG xuất hiện trong video.
';
- h += '
';
- h += '
Chọn slide muốn đưa vào Short (tích = dùng):
';
- }
-
- /* 1. Background: image (default) / uploaded video / scraped video link */
- h += '
🖼️ Nền video: ';
- h += '
';
- h += '🖼️ Ảnh ';
- h += '🎞️ Video tải lên ';
- h += '🔗 Video từ link ';
- h += '
';
- h += '
';
- h += '
';
- h += '
';
- /* danh sách ảnh của bài rewrite để chọn 1 ảnh cho nhiều slide */
- var allImgs = [];
- if(p.images && p.images.length) allImgs = allImgs.concat(p.images);
- if(p.img && p.img && allImgs.indexOf(p.img) < 0) allImgs.push(p.img);
- if(p.slides && p.slides.length) p.slides.forEach(s=>{ if(s.image && allImgs.indexOf(s.image)<0) allImgs.push(s.image); });
- var _defaultPick = (p.short_images && p.short_images[0]) || p.img || "";
- if(!allImgs.includes(_defaultPick)) _defaultPick = "";
- h += '
';
- h += '
📋 Ảnh trong bài (bấm để chọn 1 ảnh cố định cho nhiều slide):
';
- h += '
';
- if(allImgs.length){
- allImgs.forEach(function(u){
- var sel = (u === _defaultPick);
- h += ' ';
- });
- } else {
- h += 'Chưa có ảnh trong bài. Tải lên hoặc nhập URL. ';
- }
- h += '
';
- h += '
✅ ảnh được chọn (vàng) áp dụng cho tất cả slide trong short. Để trống = dùng ảnh từng slide.
';
- h += '
';
- h += '
';
- h += '
';
- h += '
';
- h += '
';
- h += '
';
- h += ' ';
- h += '📥 Lấy video ';
- h += '
';
- h += '
Dán link YouTube / TikTok / video tin tức, bấm "Lấy video" để xem trước.
';
- h += '
';
- h += '
';
- h += '
🔇 Tắt tiếng video này';
- h += '
Short sẽ chạy video này. Bỏ trống nếu muốn giữ ảnh làm nền.
';
- h += '
';
- h += '
';
- h += '
';
-
- /* 2. Audio + 3. Voice — SKIPPED in HOT mode (video keeps its own audio) */
- if(!hotMode){
- /* 2. Audio: previous short audio / TikTok music / uploaded audio */
- h += '
🎵 Âm thanh: ';
- h += '
';
- if(hasPrev && p.video){
- h += '🔁 Giữ nguyên audio Short trước ';
- }
- h += '🎙️ Đọc lại bằng giọng AI (TTS) ';
- h += '🎵 Nhạc nền TikTok có sẵn (thêm vào) ';
- h += '📤 Tải audio lên ';
- h += '🔇 Không âm thanh ';
- h += ' ';
- /* Music chooser — luôn hiển thị để người dùng NGHE THỬ trước khi chọn */
- h += '
';
- h += '
';
- (music||[]).forEach(m=>{ h += ''+m.name+' '; });
- h += ' ';
- h += '▶ Nghe thử
';
- h += '
';
- h += '
▶ Nghe thử nhạc nền TikTok trước khi chọn. Nhạc sẽ được trộn vào video (nền) khi tạo.
';
- h += '
';
- h += '
';
- h += '
';
-
- /* 3. Voice + emotion + speed (TTS) */
- h += '
🎙️ Giọng đọc: ';
- h += '';
- VOICE_LIST.forEach(v=>{ h += ''+v.label+' '; });
- h += '
';
- h += '
';
- h += '
😊 Cảm xúc: ';
- EMOTION_LIST.forEach(e=>{ h += ''+e.label+' '; });
- h += '
';
- h += '
⚡ Tốc độ: ';
- ['0.85','1.0','1.2','1.35'].forEach(s=>{ h += ''+s+'x '; });
- h += '
';
- }
-
- h += '
';
- h += ''+(hotMode?'🔥 Đăng Short HOT':'🎬 Tạo Short AI')+' ';
- h += 'Hủy ';
- h += '
';
- h += '
';
- overlay.innerHTML=h; document.body.appendChild(overlay);
- // Cleanup HLS preview instance whenever the modal is removed (✕ / Hủy / success)
- (function(){
- const obs = new MutationObserver(function(muts){
- if(document.getElementById('sc-scrape-video')){
- const p = document.getElementById('sc-scrape-video');
- if(p._hls){ try{ p._hls.destroy(); }catch(e){} p._hls = null; }
- }
- });
- obs.observe(document.body, {childList:true, subtree:false});
- // stop after this overlay is gone
- setTimeout(()=>obs.disconnect(), 60*1000);
- })();
-
- // ---- mode toggles ----
- let bgMode = hotMode ? 'link' : 'img';
- function setBgMode(m){
- bgMode = m;
- document.getElementById('sc-bg-mode-img').style.background = m==='img' ? '#2d8659' : '#333';
- document.getElementById('sc-bg-mode-img').style.color = m==='img' ? '#fff' : '#ccc';
- document.getElementById('sc-bg-mode-video').style.background = m==='video' ? '#2d8659' : '#333';
- document.getElementById('sc-bg-mode-video').style.color = m==='video' ? '#fff' : '#ccc';
- document.getElementById('sc-bg-mode-link').style.background = m==='link' ? '#2d8659' : '#333';
- document.getElementById('sc-bg-mode-link').style.color = m==='link' ? '#fff' : '#ccc';
- document.getElementById('sc-bg-img-wrap').style.display = (m==='img' || m==='link') ? 'block' : 'none';
- document.getElementById('sc-bg-video-wrap').style.display = m==='video' ? 'block' : 'none';
- document.getElementById('sc-bg-link-wrap').style.display = m==='link' ? 'block' : 'none';
- }
- if(hotMode){
- // HOT mode: only video-from-link is offered; hide ảnh/upload-video buttons
- document.getElementById('sc-bg-mode-img').style.display = 'none';
- document.getElementById('sc-bg-mode-video').style.display = 'none';
- document.getElementById('sc-bg-mode-link').style.background = '#2d8659';
- document.getElementById('sc-bg-mode-link').style.color = '#fff';
- } else {
- document.getElementById('sc-bg-mode-img').addEventListener('click', ()=>setBgMode('img'));
- document.getElementById('sc-bg-mode-video').addEventListener('click', ()=>setBgMode('video'));
- document.getElementById('sc-bg-mode-link').addEventListener('click', ()=>setBgMode('link'));
- }
-
- // ---- scraped video link: fetch preview ----
- const scLinkInput = document.getElementById('sc-video-link');
- const scScrapeBtn = document.getElementById('sc-video-scrape');
- if(scScrapeBtn && scLinkInput){
- scScrapeBtn.addEventListener('click', async ()=>{
- const url = scLinkInput.value.trim();
- const st = document.getElementById('sc-video-scrape-status');
- const preview = document.getElementById('sc-video-preview');
- const meta = document.getElementById('sc-video-meta');
- if(!url){
- st.textContent = '⚠️ Dán link video trước.';
- st.style.color = '#e0a030';
- return;
- }
- st.textContent = '⏳ Đang lấy thông tin video...';
- st.style.color = '#888';
- try{
- const r = await fetch('/api/ai-short/scrape?url=' + encodeURIComponent(url));
- const j = await r.json();
- if(!r.ok || j.error) throw new Error(j.error || 'Không lấy được video');
- // metadata
- let metaHtml = '';
- if(j.title) metaHtml += '
' + esc(j.title) + '
';
- if(j.duration) metaHtml += '
⏱️ ' + Math.round(j.duration) + 's
';
- if(j.extractor) metaHtml += '
Nguồn: ' + esc(j.extractor) + '
';
- if(j.note) metaHtml += '
⚠️ ' + esc(j.note) + '
';
- meta.innerHTML = metaHtml;
- meta.style.display = 'block';
- // direct URL for the short; keep oEmbed fallback metadata too
- const scDirect = j.direct_url || '';
- let scEmbed = j.embed_url || '';
- // YouTube/other scrape often returns the oEmbed-style iframe URL via
- // direct_url (embed pages can't be downloaded as media). Normalize:
- // treat direct embed URLs as oEmbed sources so the submit path saves
- // an embeddable slide with auto title/thumb instead of erroring.
- if(!scEmbed && /youtube\.com\/embed|youtu\.be\/|tiktok\.com\/embed|facebook\.com\/plugins|instagram\.com\/p\/.*\/embed|player\.vimeo\.com|platform\.twitter\.com/.test(scDirect)){
- scEmbed = scDirect;
- }
- const scThumb = j.thumbnail || j.thumb || '';
- const scDesc = j.description || '';
- meta.setAttribute('data-title', j.title || '');
- meta.setAttribute('data-direct', scDirect || '');
- meta.setAttribute('data-embed', scEmbed || '');
- meta.setAttribute('data-thumb', scThumb || '');
- meta.setAttribute('data-desc', scDesc || '');
- // Show description (YouTube/other) under the title in the meta box
- if(scDesc){
- metaHtml += '
' + esc(scDesc) + '
';
- meta.innerHTML = metaHtml;
- }
- // Auto-fill the Short HOT title field with the real scraped title
- const hotTitle = document.getElementById('sc-hot-title');
- if(hotTitle){ hotTitle.setAttribute('data-autofilled', j.title ? '1' : '0'); }
- if(hotTitle && j.title && !hotTitle.value.trim()){
- hotTitle.value = j.title;
- }
- document.getElementById('sc-scrape-direct').value = scDirect;
- document.getElementById('sc-scrape-duration').value = j.duration ? String(j.duration) : '';
- // YouTube description client-side fallback: the user's browser (home
- // IP) can reach r.jina.ai even when the server cannot; fill the
- // description if the server didn't return one.
- if(!scDesc && /youtube\.com|youtu\.be/.test(url)){
- try{
- fetch('https://r.jina.ai/' + url, {headers:{'x-no-cache':'1'}})
- .then(resp => resp.text())
- .then(txt => {
- const m = txt.match(/(?:^|\n)Description:\s*([\s\S]*?)(?=\n[A-Z][a-z]+:|$)/);
- const t2 = txt.match(/(?:^|\n)Title:\s*(.+)/);
- if(m && m[1] && m[1].trim()){
- const d = m[1].trim().slice(0,2000);
- meta.setAttribute('data-desc', d);
- metaHtml += '
' + esc(d) + '
';
- meta.innerHTML = metaHtml;
- }
- if(t2 && t2[1] && hotTitle && !hotTitle.value.trim()){
- hotTitle.value = t2[1].trim().slice(0,250);
- }
- }).catch(()=>{});
- }catch(e2){}
- }
- // fallback note when only oEmbed metadata is available (no direct media)
- if(!scDirect && scEmbed){
- st.textContent = 'ℹ️ Video không tải trực tiếp được, sẽ chèn dạng oEmbed (tự lấy tiêu đề/mô tả).';
- st.style.color = '#e0a030';
- meta.style.display = 'block';
- const ebtn = document.getElementById('sc-video-scrape');
- if(ebtn) ebtn.style.display = 'none';
- }
- if(j.previewable && scDirect){
- const isYouTube = /youtube\.com\/embed|youtu\.be\/|youtube-nocookie/.test(scDirect);
- const isHls = !!j.is_hls || /\.m3u8/i.test(scDirect);
- const streamUrl = isHls
- ? '/api/ai-short/scrape/hls?url=' + encodeURIComponent(scDirect)
- : '/api/ai-short/scrape/preview?url=' + encodeURIComponent(scDirect);
- preview.style.display = 'block';
- preview.setAttribute('data-ishls', isHls ? '1' : '0');
- if(isYouTube){
- // YouTube embed — render as iframe (direct embed URL already has autoplay)
- preview.setAttribute('data-ishls', '0');
- preview.innerHTML = '
';
- st.textContent = '✅ Đã lấy video YouTube. Bấm play để xem trước.';
- st.style.color = '#8f8';
- } else {
- preview.innerHTML = '
';
- const v = preview.querySelector('#sc-scrape-video');
- if(isHls && v && typeof Hls !== 'undefined' && Hls.isSupported()){
- const hls = new Hls();
- hls.loadSource(streamUrl);
- hls.attachMedia(v);
- hls.on(Hls.Events.MANIFEST_PARSED, ()=>{ v.play().catch(()=>{}); });
- v._hls = hls;
- } else if(isHls && v && v.canPlayType('application/vnd.apple.mpegurl')){
- v.src = streamUrl; // native Safari HLS
- }
- st.textContent = '✅ Đã lấy video. Bấm play để xem trước.';
- st.style.color = '#8f8';
- }
- } else {
- preview.style.display = 'none';
- if(j.note){
- st.textContent = '⚠️ ' + j.note;
- st.style.color = '#e0a030';
- } else {
- st.textContent = '❌ Không phát trước được (có thể do nguồn chặn). Bạn vẫn có thể thử tạo short.';
- st.style.color = '#e05555';
- }
- }
- }catch(e){
- st.textContent = '❌ ' + e.message;
- st.style.color = '#e05555';
- preview.style.display = 'none';
- document.getElementById('sc-scrape-direct').value = '';
- document.getElementById('sc-scrape-duration').value = '';
- }
- });
- }
-
- // ---- rewrite image picker (chọn 1 ảnh cho nhiều slide) ----
- let _scChosenImg = '';
- document.getElementById('sc-rewrite-list').addEventListener('click', e=>{
- const btn = e.target.closest('.sc-rw-img');
- if(!btn) return;
- document.querySelectorAll('.sc-rw-img').forEach(b=>{ b.style.borderColor='#333'; b.dataset.sel='0'; });
- btn.style.borderColor='#ffd700'; btn.dataset.sel='1';
- _scChosenImg = btn.dataset.img || '';
- const hidden = document.getElementById('sc-chosen-img');
- if(hidden) hidden.value = _scChosenImg;
- });
-
- // ---- audio source toggles (skipped in HOT mode) ----
- if(!hotMode){
- function setAudioSrc(v){
- // nhạc nền TikTok luôn hiển thị để nghe thử; ẩn khi chọn "Không âm thanh"
- document.getElementById('sc-music-wrap').style.display = v==='none' ? 'none' : 'block';
- document.getElementById('sc-upload-wrap').style.display = v==='upload' ? 'block' : 'none';
- }
- document.getElementById('sc-audio-src').addEventListener('change', e=>setAudioSrc(e.target.value));
- if(hasPrev && p.video) setAudioSrc('prev'); else setAudioSrc('tts');
- }
-
- // ---- recreate slides: show/hide slide picker ----
- const scReuse = document.getElementById('sc-reuse-slides');
- if(scReuse){
- scReuse.addEventListener('change', function(){
- const pk = document.getElementById('sc-slide-picker');
- if(pk) pk.style.display = this.checked ? 'block' : 'none';
- });
- }
-
- // ---- music preview player (skipped in HOT mode) ----
- let _scAudio = null;
- const listenBtn = hotMode ? null : document.getElementById('sc-music-listen');
- const musicSel = hotMode ? null : document.getElementById('sc-music');
- const playerBox = hotMode ? null : document.getElementById('sc-music-playerbox');
- if(listenBtn && musicSel){
- listenBtn.addEventListener('click', function(){
- const opt = musicSel.options[musicSel.selectedIndex];
- const aurl = opt ? (opt.dataset.aurl || '') : '';
- if(!aurl){
- alert('Không có link nhạc cho lựa chọn này.');
- return;
- }
- // proxy để tránh CORS khi phát thử trong trình duyệt
- const streamUrl = '/api/ai-short/music/stream?url=' + encodeURIComponent(aurl);
- if(_scAudio && _scAudio.dataset.src === aurl && !_scAudio.paused){
- _scAudio.pause(); listenBtn.textContent = '▶ Nghe thử';
- return;
- }
- if(_scAudio) _scAudio.pause();
- if(playerBox) playerBox.style.display = 'block';
- if(playerBox) playerBox.innerHTML = '
🎧 '+esc(opt.text)+'
';
- _scAudio = playerBox ? playerBox.querySelector('audio') : null;
- if(_scAudio){
- _scAudio.dataset.src = aurl;
- _scAudio.addEventListener('ended', function(){ listenBtn.textContent = '▶ Nghe thử'; });
- _scAudio.addEventListener('error', function(){
- listenBtn.textContent = '▶ Nghe thử';
- toast('❌ Không phát được nhạc thử (mạng/vùng chặn). Bạn vẫn có thể chọn để trộn vào video.');
- });
- listenBtn.textContent = '⏹ Dừng';
- }
- });
- }
-
- // ---- create ----
- document.getElementById('sc-create-btn').addEventListener('click', async ()=>{
- const btn = document.getElementById('sc-create-btn');
- const st = document.getElementById('sc-status');
- btn.disabled = true; btn.textContent = '⏳ Đang tạo...';
- st.textContent = 'Đang chuẩn bị...';
-
- // upload files first
- try{
- // image file (not in HOT mode)
- let imgUrl = '';
- const imgUrlInput = document.getElementById('sc-img-url');
- if(imgUrlInput) imgUrl = imgUrlInput.value.trim() || '';
- const imgFile = hotMode ? null : document.getElementById('sc-img-file').files[0];
- if(imgFile){
- st.textContent = '⏳ Đang tải ảnh lên...';
- const fd = new FormData(); fd.append('file', imgFile, imgFile.name);
- const r = await fetch('/api/ai-short/upload', {method:'POST', body:fd});
- const j = await r.json();
- if(!r.ok || j.error) throw new Error(j.error || 'Lỗi upload ảnh');
- imgUrl = j.url;
- }
- // video file (not in HOT mode)
- let videoUrl = '';
- const videoFile = hotMode ? null : document.getElementById('sc-video-file').files[0];
- if(videoFile){
- st.textContent = '⏳ Đang tải video lên...';
- const fd = new FormData(); fd.append('file', videoFile, videoFile.name);
- const r = await fetch('/api/ai-short/upload', {method:'POST', body:fd});
- const j = await r.json();
- if(!r.ok || j.error) throw new Error(j.error || 'Lỗi upload video');
- videoUrl = j.url;
- }
- // audio file (not in HOT mode)
- let uploadAudioUrl = '';
- const audioFile = hotMode ? null : document.getElementById('sc-audio-file').files[0];
- if(audioFile){
- st.textContent = '⏳ Đang tải audio lên...';
- const fd = new FormData(); fd.append('file', audioFile, audioFile.name);
- const r = await fetch('/api/ai-short/upload', {method:'POST', body:fd});
- const j = await r.json();
- if(!r.ok || j.error) throw new Error(j.error || 'Lỗi upload audio');
- uploadAudioUrl = j.url;
- }
-
- const audioSrc = hotMode ? 'video' : (document.getElementById('sc-audio-src').value);
- const useSlides = hotMode ? false : (document.getElementById('sc-reuse-slides') ? document.getElementById('sc-reuse-slides').checked : false);
- // nhạc nền TikTok: giá trị != none được trộn vào video (kể cả tạo lại)
- let musicId = '';
- const musicSel = hotMode ? null : document.getElementById('sc-music');
- if(musicSel && musicSel.value && musicSel.value !== 'none' && audioSrc !== 'none'){
- musicId = musicSel.value;
- }
- if(audioSrc === 'music') musicId = musicSel ? musicSel.value : '';
- let audioUrl = '';
- if(hotMode){ audioUrl = ''; }
- else if(audioSrc==='upload') audioUrl = uploadAudioUrl;
- else if(audioSrc==='prev' && p.video){ audioUrl = p.short_audio_url || ''; }
- // reuse_audio explicitly: only when user chose "giữ nguyên audio short trước"
- const reuseAudio = hotMode ? false : (audioSrc === 'prev');
- // collect selected slide indices (recreate mode)
- let slideIndices = [];
- if(useSlides){
- document.querySelectorAll('#sc-slide-picker .sc-slide-chk:checked').forEach(chk=>{
- slideIndices.push(parseInt(chk.dataset.idx));
- });
- if(!slideIndices.length){
- st.textContent = '⚠️ Chọn ít nhất 1 slide để tạo lại.';
- throw new Error('Chọn ít nhất 1 slide');
- }
- }
- // reuse_audio is sent explicitly above; music (audio_music) is overlaid by the backend
- const body = {
- post_id: p.id,
- use_slides: useSlides,
- slide_indices: slideIndices,
- reuse_audio: reuseAudio,
- voice: hotMode ? 'vi-VN-HoaiMyNeural' : document.getElementById('sc-voice').value,
- emotion: hotMode ? 'neutral' : document.getElementById('sc-emotion').value,
- speed: hotMode ? 1.0 : (parseFloat(document.getElementById('sc-speed').value) || 1.2),
- audio_music: musicId,
- audio_url: audioUrl,
- };
- if(hotMode){
- // create a brand-new wall post on the AI wall
- body.create_new = true;
- const hotTitleEl = document.getElementById('sc-hot-title');
- body.title = hotTitleEl ? hotTitleEl.value.trim() : '';
- // scraped video title as fallback wall title
- if(!body.title){
- const meta = document.getElementById('sc-video-meta');
- body.title = (meta && meta.dataset.title) ? meta.dataset.title : '🔥 Short HOT';
- }
- body.text = '';
- body.language = 'vi';
- const srcEl = document.getElementById('sc-video-link');
- if(srcEl && srcEl.value.trim()) body.url = srcEl.value.trim();
- }
- // if uploaded video selected -> use it as background via images? No:
- if(bgMode==='video' && videoUrl){
- body.video_url = videoUrl; // backend handles
- const vmute = document.getElementById('sc-video-mute');
- if(vmute && vmute.checked) body.video_muted = true;
- } else if(bgMode==='link'){
- // scraped video from link: video-first + image tail completion
- const scDirect = document.getElementById('sc-scrape-direct') ? document.getElementById('sc-scrape-direct').value : '';
- const meta = document.getElementById('sc-video-meta');
- const scEmbed = meta ? (meta.getAttribute('data-embed') || '') : '';
- const scThumb = meta ? (meta.getAttribute('data-thumb') || '') : '';
- const scTitle = meta ? (meta.getAttribute('data-title') || '') : '';
- const scDesc = meta ? (meta.getAttribute('data-desc') || '') : '';
- // if the modal title was auto-filled from the scrape, prefer it for
- // the wall post (user can still edit it before submitting)
- const hotTitleVal = document.getElementById('sc-hot-title') ? document.getElementById('sc-hot-title').value.trim() : '';
- if(scDirect && !/youtube\.com\/embed|youtu\.be\/|tiktok\.com\/embed|facebook\.com\/plugins|instagram\.com\/p\/.*\/embed|player\.vimeo\.com|platform\.twitter\.com/.test(scDirect)){
- body.video_url = scDirect;
- body.scrape_mode = true; // video-first assembly
- const vdur = document.getElementById('sc-scrape-duration') ? document.getElementById('sc-scrape-duration').value : '';
- if(vdur) body.video_dur = parseFloat(vdur) || undefined;
- const lmute = document.getElementById('sc-link-mute');
- if(lmute && lmute.checked) body.video_muted = true;
- // still send the chosen images (they complete the short if the
- // video is shorter than the voice)
- const chosen = document.getElementById('sc-chosen-img') ? document.getElementById('sc-chosen-img').value : '';
- const imgs = [];
- if(chosen) imgs.push(chosen);
- if(imgUrl) imgs.push(imgUrl);
- if(imgs.length) body.images = imgs;
- if(chosen) body.fixed_image = chosen;
- } else if(scEmbed){
- // oEmbed fallback: media can't be downloaded (TikTok/FB/IG/YT block
- // datacenter IPs) -> save the short as an embeddable slide with
- // auto title/desc/thumb from the scrape metadata.
- body.embed_url = scEmbed;
- body.embed_title = hotTitleVal || scTitle || '';
- body.embed_thumb = scThumb || '';
- body.embed_desc = scDesc || '';
- const chosenImg = imgUrl || (document.getElementById('sc-chosen-img') ? document.getElementById('sc-chosen-img').value : '');
- if(chosenImg) body.images = [chosenImg];
- } else {
- // No direct media and no embed metadata from the scrape. If the
- // user pasted a social/link URL, still post it — the backend
- // auto-probes oEmbed and saves the short as an embed slide with
- // auto title/desc/thumb (no more "chưa lấy được video" error).
- const srcUrlEl = document.getElementById('sc-video-link');
- const srcUrl = srcUrlEl ? srcUrlEl.value.trim() : '';
- if(srcUrl && /^https?:\/\//i.test(srcUrl)){
- body.url = srcUrl;
- body.allow_oembed_fallback = true;
- st.textContent = 'ℹ️ Không lấy được video trực tiếp — sẽ đăng dạng oEmbed (backend tự lấy tiêu đề/mô tả/ảnh).';
- st.style.color = '#e0a030';
- } else {
- st.textContent = '⚠️ Bấm "📥 Lấy video" để lấy video từ link trước khi tạo.';
- throw new Error('Chưa lấy được video từ link');
- }
- }
- } else {
- // 1. ảnh chọn từ danh sách rewrite (áp dụng cho nhiều slide, fix nền cho cả short)
- // 2. ảnh URL nhập tay / tải lên
- const chosen = document.getElementById('sc-chosen-img') ? document.getElementById('sc-chosen-img').value : '';
- const imgs = [];
- if(chosen) imgs.push(chosen);
- if(imgUrl) imgs.push(imgUrl);
- if(imgs.length) body.images = imgs; // backend: first = fixed bg (normal mode)
- if(chosen) body.fixed_image = chosen; // applies to slides mode too (1 ảnh cho nhiều slide)
- }
- st.textContent = '⏳ Đang tạo video short (có thể mất 1-2 phút)...';
- const r2 = await fetch('/api/ai-short', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)});
- const j2 = await r2.json();
- if(!r2.ok || j2.error) throw new Error(j2.error || 'Lỗi tạo video');
- toast('✅ Đã tạo Short AI!');
- const pp = _wallPosts.find(x => String(x.id) === String(p.id));
- if(pp){
- pp.video = j2.video;
- pp.voice = j2.voice; pp.emotion = j2.emotion; pp.short_speed = j2.speed;
- pp.short_music = j2.music; pp.short_audio_url = audioUrl; pp.short_use_slides = j2.use_slides;
- pp.short_images = j2.images;
- }
- if(hotMode && j2.post){
- // 🆕 brand-new wall post created by the backend -> prepend on the AI wall
- prependWallPost(j2.post);
- } else {
- const itemId = 'wall-item-'+p.id;
- const el = document.getElementById(itemId);
- if(el){
- const idx = _wallPosts.indexOf(pp);
- el.insertAdjacentHTML('afterend', makeWallItem(pp, idx));
- el.remove();
- }
- }
- document.getElementById('short-creator-overlay').remove();
- }catch(e){
- st.textContent = '❌ ' + e.message;
- btn.disabled = false; btn.textContent = hotMode ? '🔥 Thêm Short HOT' : '🎬 Tạo Short AI';
- toast('❌ ' + e.message);
- }
- });
- });
-}
-
-/* legacy fast path — used by any remaining tts-create-btn handlers */
-async function makeShortVideo(postId, btn, voice, speed, emotion){
- openShortCreator(postId);
- return;
-}
-
-var VOICE_LIST = [
- {id:'vi-VN-HoaiMyNeural', label:'🎙️ Hoài My (VI)', lang:'vi'},
- {id:'vi-VN-NamMinhNeural', label:'🎙️ Nam Minh (VI)', lang:'vi'},
- {id:'en-US-AndrewMultilingualNeural', label:'🎙️ Andrew (EN)', lang:'en'},
- {id:'en-AU-WilliamMultilingualNeural', label:'🎙️ William (EN)', lang:'en'},
- {id:'pt-BR-ThalitaMultilingualNeural', label:'🎙️ Thalita (PT)', lang:'pt'},
- {id:'fr-FR-VivienneMultilingualNeural', label:'🎙️ Vivienne (FR)', lang:'fr'},
- {id:'fr-FR-RemyMultilingualNeural', label:'🎙️ Rémy (FR)', lang:'fr'},
- {id:'de-DE-SeraphinaMultilingualNeural', label:'🎙️ Seraphina (DE)', lang:'de'},
- {id:'de-DE-FlorianMultilingualNeural', label:'🎙️ Florian (DE)', lang:'de'},
- {id:'ko-KR-HyunsuMultilingualNeural', label:'🎙️ Hyunsu (KO)', lang:'ko'},
- {id:'it-IT-GiuseppeMultilingualNeural', label:'🎙️ Giuseppe (IT)', lang:'it'},
-];
-var EMOTION_LIST = [
- {id:'neutral', label:'😐 Trung tính'},
- {id:'happy', label:'😊 Vui vẻ'},
- {id:'excited', label:'🔥 Hào hứng'},
- {id:'sad', label:'😢 Buồn'},
- {id:'humorous', label:'😂 Hài hước'},
- {id:'serious', label:'⚠️ Nghiêm túc'},
- {id:'urgent', label:'🚨 Khẩn cấp'},
- {id:'warm', label:'💖 Ấm áp'},
-];
-
-document.addEventListener('click',function(e){
- var btn = e.target.closest('.tts-voice-btn');
- if(btn){
- var container = btn.closest('.tts-selector');
- if(container){
- var selKey = 'inline-'+container.dataset.postId;
- if(!_ttsSelections[selKey]) _ttsSelections[selKey]={voice:btn.dataset.voice,emotion:'neutral'};
- var allBtns = container.querySelectorAll('.tts-voice-btn');
- for(var i=0;i
p.video);
+ let shortAISection = document.getElementById('short-ai-section');
+ if(aiShorts.length === 0){
+ if(shortAISection) shortAISection.remove();
return;
}
- var ebtn = e.target.closest('.tts-emotion-btn');
- if(ebtn){
- var container = ebtn.closest('.tts-selector');
- if(container){
- var selKey = 'inline-'+container.dataset.postId;
- if(!_ttsSelections[selKey]) _ttsSelections[selKey]={voice:'vi-VN-HoaiMyNeural',emotion:ebtn.dataset.emotion};
- var allBtns = container.querySelectorAll('.tts-emotion-btn');
- for(var i=0;i{
+ h+=``;
+ });
+ track.innerHTML = h;
}
- return;
}
-});
-function detectLanguage(text){
- if(!text) return 'vi';
- var t=text.toLowerCase(), chars=new Set(t);
- var vnChars='đăâêôơưàảãạáằẳẵặắầẩẫậấèẻẽẹéềễểệếìỉĩịíòỏõọóồổỗộốờởỡợớùủũụúừửữựứỳỷỹỵý';
- var vnCount=0; for(var c of vnChars){if(chars.has(c)) vnCount++;}
- if(vnCount>=2) return 'vi';
- if(chars.has('ñ')||chars.has('¿')||chars.has('¡')) return 'es';
- if(chars.has('ã')||chars.has('õ')) return 'pt';
- var words=t.split(/\s+/);
- var enWords=['the','is','at','which','on','and','or','but','this','that','with','from','have','been'];
- var enCount=words.filter(function(w){return enWords.indexOf(w)>=0;}).length;
- if(enCount>=2) return 'en';
- return 'vi';
}
-function detectEmotion(text){
- if(!text) return 'neutral';
- var t=text.toLowerCase();
- var kws={
- happy:['vui','hạnh phúc','tuyệt','thành công','chiến thắng','feliz','maravilloso','happy','joy','wonderful','great','amazing','love','excellent'],
- excited:['hào hứng','phấn khích','đột phá','kỷ lục','đỉnh cao','emocionante','increíble','excited','thrilling','unbelievable','awesome','breakthrough'],
- sad:['buồn','đau','mất','thảm họa','khủng hoảng','triste','terrible','sad','unhappy','tragic','painful','death'],
- humorous:['hài hước','buồn cười','haha','đùa','engraçado','gracioso','funny','hilarious','joke','lol'],
- serious:['nghiêm trọng','khẩn cấp','quan trọng','lo ngại','sério','crítico','serious','critical','urgent','severe','crisis'],
- urgent:['khẩn cấp','báo động','ngay lập tức','urgent','breaking','alert','emergency'],
- warm:['ấm áp','tình cảm','yêu thương','warm','love','heart','touching']
- };
- var bestScore=0, bestEmotion='neutral';
- for(var em in kws){var score=0; for(var kw of kws[em]){if(t.indexOf(kw)>=0) score++;} if(score>bestScore){bestScore=score;bestEmotion=em;}}
- return bestEmotion;
-}
-function getAutoVoice(lang){var map={vi:'vi-VN-HoaiMyNeural',pt:'pt-BR-ThalitaMultilingualNeural',en:'en-US-AndrewMultilingualNeural',fr:'fr-FR-VivienneMultilingualNeural',de:'de-DE-SeraphinaMultilingualNeural',ko:'ko-KR-HyunsuMultilingualNeural',it:'it-IT-GiuseppeMultilingualNeural'};return map[lang]||'vi-VN-HoaiMyNeural';}
-function buildVoiceEmotionSelector(post){
- var lang=post.language||detectLanguage(post.title+' '+(post.text||''));
- var _oldVoiceMap = {'hoaimy':'vi-VN-HoaiMyNeural','namminh':'vi-VN-NamMinhNeural','andrew':'en-US-AndrewMultilingualNeural','jenny':'en-US-AndrewMultilingualNeural','thalita':'pt-BR-ThalitaMultilingualNeural','pt_thalita':'pt-BR-ThalitaMultilingualNeural','vivienne':'fr-FR-VivienneMultilingualNeural','remy':'fr-FR-RemyMultilingualNeural','seraphina':'de-DE-SeraphinaMultilingualNeural','florian':'de-DE-FlorianMultilingualNeural','sunhee':'ko-KR-HyunsuMultilingualNeural','hyunsu':'ko-KR-HyunsuMultilingualNeural','giuseppe':'it-IT-GiuseppeMultilingualNeural','ela':'en-US-AndrewMultilingualNeural','denise':'fr-FR-VivienneMultilingualNeural','katja':'de-DE-SeraphinaMultilingualNeural','nanami':'en-US-AndrewMultilingualNeural','xiaochen':'en-US-AndrewMultilingualNeural','es_carlos':'en-US-AndrewMultilingualNeural','pt_francisco':'pt-BR-ThalitaMultilingualNeural'};
- var _postVoice = post.voice ? (_oldVoiceMap[post.voice] || post.voice) : '';
- var autoVoice= _postVoice || getAutoVoice(lang);
- var autoEmotion=post.emotion||detectEmotion(post.title+' '+(post.text||''));
- var selKey = 'inline-'+post.id;
- if(!_ttsSelections[selKey]){_ttsSelections[selKey] = {voice: autoVoice, emotion: autoEmotion};}
- var h='';
- h+='
🎙️ Giọng đọc (ngôn ngữ: '+lang.toUpperCase()+'):
';
- VOICE_LIST.forEach(function(v){var sel=v.id===_ttsSelections[selKey].voice?'border-color:#5cb87a;background:#1a2a1f':'border-color:#333;background:#222';h+=''+v.label+' ';});
- h+='
😊 Cảm xúc:
';
- EMOTION_LIST.forEach(function(e){var sel=e.id===_ttsSelections[selKey].emotion?'border-color:#5cb87a;background:#1a2a1f':'border-color:#333;background:#222';h+=''+e.label+' ';});
- h+='
⚡ Tốc độ: ';
- h+='0.85x Chậm 1.0x Bình thường 1.2x Nhanh 1.35x Rất nhanh
';
- h+='
🎬 Tạo Short AI ';
- return h;
-}
-window.showVoiceEmotionSelector=function(postId,title,text){
- var overlay=document.createElement('div');
- overlay.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:99999;display:flex;align-items:center;justify-content:center;padding:16px';
- var box=document.createElement('div');box.style.cssText='background:#1a1a1a;border:2px solid #2d8659;border-radius:16px;padding:20px;max-width:400px;width:100%;max-height:80vh;overflow-y:auto';
- var lang=detectLanguage(title+' '+text);var autoEmotion=detectEmotion(title+' '+text);
- var h='🎬 Tạo Short AI (ngôn ngữ: '+lang.toUpperCase()+') ';
- h+='🎙️ Chọn giọng đọc:
';
- VOICE_LIST.forEach(function(v){var sel=v.id===getAutoVoice(lang)?'border-color:#5cb87a;background:#1a2a1f':'border-color:#333;background:#222';h+='
'+v.label+' ';});
- h+='
😊 Chọn cảm xúc:
';
- EMOTION_LIST.forEach(function(e){var sel=e.id===autoEmotion?'border-color:#5cb87a;background:#1a2a1f':'border-color:#333;background:#222';h+='
'+e.label+' ';});
- h+='
⚡ Tốc độ:
';
- h+='
0.85x Chậm 1.0x Bình thường 1.2x Nhanh 1.35x Rất nhanh ';
- h+='🎬 Tạo Short ';
- h+='✕
';
- h+='
';
- box.innerHTML=h;overlay.appendChild(box);document.body.appendChild(overlay);
- var selectedVoice=getAutoVoice(lang),selectedEmotion=autoEmotion;
- box.querySelectorAll('.ve-voice-btn').forEach(function(btn){btn.addEventListener('click',function(){box.querySelectorAll('.ve-voice-btn').forEach(function(b){b.style.borderColor='#333';b.style.background='#222';});this.style.borderColor='#5cb87a';this.style.background='#1a2a1f';selectedVoice=this.dataset.voice;});});
- box.querySelectorAll('.ve-emotion-btn').forEach(function(btn){btn.addEventListener('click',function(){box.querySelectorAll('.ve-emotion-btn').forEach(function(b){b.style.borderColor='#333';b.style.background='#222';});this.style.borderColor='#5cb87a';this.style.background='#1a2a1f';selectedEmotion=this.dataset.emotion;});});
- box.querySelector('#ve-cancel-btn').addEventListener('click',function(){overlay.remove();});
- box.querySelector('#ve-create-btn').addEventListener('click',async function(){
- this.disabled=true;this.textContent='⏳ Đang tạo...';
- box.querySelector('#ve-status').style.display='block';box.querySelector('#ve-status').textContent='Đang tạo video shorts...';
- try{
- var speed=parseFloat(box.querySelector('#ve-speed').value)||1.2;
- if(!_ttsSelections["inline-"+postId]) _ttsSelections["inline-"+postId]={voice:"vi-VN-HoaiMyNeural",emotion:"neutral"};
- _ttsSelections["inline-"+postId].voice=selectedVoice;_ttsSelections["inline-"+postId].emotion=selectedEmotion;
- var r=await fetch('/api/ai/short/'+encodeURIComponent(postId),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({voice:selectedVoice,emotion:selectedEmotion,speed:speed})});
- var j=await r.json();
- if(!r.ok||j.error) throw new Error(j.error||'Lỗi tạo video');
- toast('✅ Đã tạo Short AI!');overlay.remove();
- var p=_wallPosts.find(function(x){return String(x.id)===String(postId);});
- if(p){p.video=j.video;p.voice=j.voice;p.emotion=j.emotion;}
- }catch(e){this.disabled=false;this.textContent='🎬 Tạo Short';box.querySelector('#ve-status').textContent='❌ '+e.message;}
- });
-};
function prependWallPost(post){
_wallPosts.unshift(post);
const track=document.getElementById('ai-wall-track');
const wrap=document.getElementById('ai-wall-wrap');
- const target=document.getElementById('ai-wall-under-compose');
- if(target && (!track||!wrap)){
- const newWrap=document.createElement('div');
- newWrap.className='slider-wrap';newWrap.id='ai-wall-wrap';
- newWrap.innerHTML=`${makeWallItem(post,0)}
`;
- target.appendChild(newWrap);
- const firstItem=newWrap.querySelector('.wall-item');
- if(firstItem)firstItem.className='wall-item wall-item-new';
- // re-interleave YouTube feed items after new wall content
- if(typeof _ytFeedVideos !== 'undefined') _renderYTFeedInWall();
+ const homeEl=document.getElementById('view-home');
+ if(!track||!wrap){
+ if(homeEl){
+ let insertBefore=homeEl.querySelector('.slider-wrap');
+ const newWrap=document.createElement('div');
+ newWrap.className='slider-wrap';
+ newWrap.id='ai-wall-wrap';
+ newWrap.innerHTML=`${makeWallItem(post,0)}
`;
+ if(insertBefore) homeEl.insertBefore(newWrap,insertBefore);
+ else homeEl.appendChild(newWrap);
+ const firstItem=newWrap.querySelector('.wall-item');
+ if(firstItem)firstItem.className='wall-item wall-item-new';
+ }
return;
}
- if(track){
- track.insertAdjacentHTML('afterbegin', makeWallItem(post, 0));
- track.scrollTo({left:0,behavior:'smooth'});
- // re-interleave YouTube feed items after new wall content
- if(typeof _renderYTFeedInWall === 'function') _renderYTFeedInWall();
- }
-}
-
+ const div=document.createElement('div');
+ div.className='wall-item wall-item-new';
+ div.id='wall-item-'+(post.id||'new-'+Date.now());
+ const hasVideo = post.video && post.video.length > 0;
+ const thumbContent = post.img
+ ? ` `
+ : (hasVideo ? ` ` : '');
+ const videoBadge = hasVideo ? `🎬
` : '';
+ const videoBtn = hasVideo
+ ? `▶ Xem Short `
+ : `🎬 Tạo Video `;
+ div.innerHTML=`${thumbContent}${videoBadge}
${esc(post.title)}
${esc((post.text||'').slice(0,180))}
Xem ${videoBtn}
`;
+ track.prepend(div);
+ track.scrollTo({left:0,behavior:'smooth'});
+ if(hasVideo) refreshShortAISlider();
+}
+
+// === REST OF FUNCTIONS ===
+let _shortsData=[];
let _wallPosts=[];
let _currentView='home';
let _currentEventId=null;
let _currentMatchUrl=null;
+function interleaveShorts(shorts){const dt=shorts.filter(s=>s.channel==='baodantri7941');const sk=shorts.filter(s=>s.channel==='baosuckhoedoisongboyte');const result=[];let i=0,j=0;while(i{const topicText=t.topic||t.label.replace(/^#/,'');return`${esc(t.label)} `;}).join('');}
+async function loadHotTopics(){const j=await fetch('/api/hot_topics').then(r=>r.json()).catch(()=>({topics:[]}));const el=document.getElementById('hot-topics');if(!el)return;el.innerHTML=(j.topics||[]).slice(0,18).map(t=>{const topicText=t.topic||t.label.replace(/^#/,'');return`${esc(t.label)} `;}).join('');if(j.topics&&j.topics[0]){const firstTopic=j.topics[0].topic||j.topics[0].label.replace(/^#/,'');setTimeout(()=>searchTopic(firstTopic),800);}}
function searchTopic(topic){if(!topic){topic=document.getElementById('topic-input')?.value.trim();if(!topic){alert('Nhập chủ đề');return;}}document.getElementById('topic-input').value='';_htTopic=topic;_htPage=0;showHashtagSources(topic,0);}
-async function showHashtagSources(topic,page){const box=document.getElementById('hashtag-box');if(!box)return;if(page===0)box.innerHTML=``;try{const r=await fetch(`/api/hashtag/sources?topic=${encodeURIComponent(topic)}&page=${page}`);const j=await r.json();const sources=j.sources||[];if(!sources.length&&page===0){box.innerHTML=`🔍 ${esc(topic)} Không tìm được bài viết liên quan
`;return;}let h='';if(page===0)h=`🔍 ${esc(topic)} (${j.total} bài từ 8 nguồn) `;sources.forEach((s,i)=>{const idx=page*8+i;h+=`
${esc(s.title)}
${esc(s.via||'')}
`;});if(page===0){h+=`
🤖 Rewrite AI tổng hợp & đăng tường `;if(j.has_more)h+=`
Tải thêm ▼ `;h+=`
`;box.innerHTML=h;}else{document.getElementById('ht-list')?.insertAdjacentHTML('beforeend',h);const btn=document.getElementById('ht-more');if(btn){if(!j.has_more)btn.remove();else{btn.disabled=false;btn.textContent='Tải thêm ▼';}}}sources.forEach((s,i)=>{const idx=page*8+i;if(!s.url)return;const ctrl=new AbortController();setTimeout(()=>ctrl.abort(),4000);fetch('/api/article?url='+encodeURIComponent(s.url),{signal:ctrl.signal}).then(r=>r.json()).then(d=>{if(d&&(d.og_image||d.img)){const el=document.getElementById('ht-img-'+idx);if(el)el.innerHTML=` `;}}).catch(()=>{});});}catch(e){box.innerHTML=`🔍 ${esc(topic)} Lỗi: ${esc(e.message)}
`;}}
+async function showHashtagSources(topic,page){const box=document.getElementById('hashtag-box');if(!box)return;if(page===0)box.innerHTML=``;try{const r=await fetch(`/api/hashtag/sources?topic=${encodeURIComponent(topic)}&page=${page}`);const j=await r.json();const sources=j.sources||[];if(!sources.length&&page===0){box.innerHTML=`🔍 ${esc(topic)} Không tìm được bài viết liên quan
`;return;}let h='';if(page===0)h=`🔍 ${esc(topic)} (${j.total} bài từ 8 nguồn) `;sources.forEach((s,i)=>{const idx=page*8+i;h+=`
${esc(s.title)}
${esc(s.via||'')}
`;});if(page===0){h+=`
🤖 Rewrite AI tổng hợp & đăng tường `;if(j.has_more)h+=`
Tải thêm ▼ `;h+=`
`;box.innerHTML=h;}else{document.getElementById('ht-list')?.insertAdjacentHTML('beforeend',h);const btn=document.getElementById('ht-more');if(btn){if(!j.has_more)btn.remove();else{btn.disabled=false;btn.textContent='Tải thêm ▼';}}}sources.forEach((s,i)=>{const idx=page*8+i;if(!s.url)return;fetch('/api/article?url='+encodeURIComponent(s.url)).then(r=>r.json()).then(d=>{if(d&&(d.og_image||d.img)){const el=document.getElementById('ht-img-'+idx);if(el)el.innerHTML=` `;}}).catch(()=>{});});}catch(e){box.innerHTML=`🔍 ${esc(topic)} Lỗi: ${esc(e.message)}
`;}}
function loadMoreHashtag(){_htPage++;const btn=document.getElementById('ht-more');if(btn){btn.disabled=true;btn.textContent='Đang tải...';}showHashtagSources(_htTopic,_htPage);}
async function rewriteHashtag(topic){const btn=event?.target;if(btn){btn.disabled=true;btn.textContent='Đang tổng hợp...';}try{const r=await fetch('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic})});const j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');toast('✅ Đã đăng Tường AI!');if(btn)btn.textContent='✅ Đăng thành công!';if(j.post)prependWallPost(j.post);}catch(e){toast('❌ '+e.message);if(btn){btn.disabled=false;btn.textContent='🤖 Rewrite AI';}}}
-async function loadLivescore(tab){document.querySelectorAll('.ls-tab').forEach(t=>t.classList.remove('active'));document.querySelector(`.ls-tab[data-tab="${tab}"]`)?.classList.add('active');const el=document.getElementById('ls-content');if(!el)return;el.innerHTML='Đang tải...
';let ep='/api/livescore/'+tab;if(tab.startsWith('bxh_'))ep='/api/livescore/standings/'+tab.replace('bxh_','');try{const d=await _fetchWithTimeout(ep,12000);el.innerHTML=d.html&&d.html.length>50?d.html:'Không có dữ liệu
';bindMatchClicks(el);if(tab.startsWith('bxh_'))_decorateStandings(el);}catch(e){el.innerHTML='Lỗi tải hoặc hết thời gian
';}}
-function _decorateStandings(el){
- try{
- const items=el.querySelectorAll('.leaderboard-item');
- if(!items.length)return;
- // Build header row: # | Đội bóng | Tr T H B BT BB +/- Đ
- const head=document.createElement('div');
- head.className='ls-bxh-head';
- head.innerHTML='';
- el.insertBefore(head, items[0]);
- // Make each row use flex layout with the copy cells as a fixed-width grid
- items.forEach(function(it){
- it.classList.add('ls-bxh-row');
+async function loadLivescore(tab){document.querySelectorAll('.ls-tab').forEach(t=>t.classList.remove('active'));document.querySelector(`.ls-tab[data-tab="${tab}"]`)?.classList.add('active');const el=document.getElementById('ls-content');if(!el)return;el.innerHTML='Đang tải...
';let ep='/api/livescore/'+tab;if(tab.startsWith('bxh_'))ep='/api/livescore/standings/'+tab.replace('bxh_','');try{const r=await fetch(ep);const d=await r.json();el.innerHTML=d.html&&d.html.length>50?d.html:'Không có dữ liệu
';bindMatchClicks(el);}catch(e){el.innerHTML='Lỗi
';}}
+function bindMatchClicks(el){
+ el.querySelectorAll('.match-detail').forEach(md=>{
+ md.style.cursor='pointer';
+ md.addEventListener('click',function(e){
+ const statusA=this.querySelector('.status a');
+ const teamA=this.querySelector('.teams a[href*="/tran-dau/"]');
+ const a = statusA || teamA;
+ if(a){
+ e.preventDefault();
+ e.stopPropagation();
+ const href=a.getAttribute('href')||'';
+ const m=href.match(/\/tran-dau\/(\d+)\//);
+ if(m){
+ const fullUrl=href.startsWith('http')?href:'https://bongda.com.vn'+href;
+ openMatch(m[1],fullUrl);
+ }
+ }
});
- }catch(e){}
+ });
+ el.querySelectorAll('a').forEach(a=>{
+ a.addEventListener('click',e=>{e.preventDefault();e.stopPropagation()});
+ });
}
-function bindMatchClicks(el){el.querySelectorAll('.match-detail').forEach(md=>{md.style.cursor='pointer';md.addEventListener('click',function(e){const statusA=this.querySelector('.status a');const teamA=this.querySelector('.teams a[href*="/tran-dau/"]');const a = statusA || teamA;if(a){e.preventDefault();e.stopPropagation();const href=a.getAttribute('href')||'';const m=href.match(/\/tran-dau\/(\d+)\//);if(m){const fullUrl=href.startsWith('http')?href:'https://bongda.com.vn'+href;openMatch(m[1],fullUrl);}}});});el.querySelectorAll('a').forEach(a=>{a.addEventListener('click',e=>{e.preventDefault();e.stopPropagation()});});}
function openMatch(id,url){if(!id)return;_currentEventId=id;if(url)_currentMatchUrl=url;document.getElementById('match-overlay').classList.add('active');document.body.style.overflow='hidden';loadMatchTab('detail')}
function closeMatch(){document.getElementById('match-overlay').classList.remove('active');document.body.style.overflow=''}
async function loadMatchTab(tab){document.querySelectorAll('.mo-tab').forEach(t=>t.classList.remove('active'));document.querySelectorAll('.mo-tab').forEach(t=>{if((tab==='comm'&&t.textContent==='Diễn biến')||(tab==='stats'&&t.textContent==='Thống kê')||(tab==='detail'&&t.textContent.includes('Chi tiết')))t.classList.add('active')});const el=document.getElementById('mo-body');if(!el)return;el.innerHTML='Đang tải...
';try{let apiUrl;if(tab==='stats')apiUrl=`/api/match/${_currentEventId}/stats`;else if(tab==='comm')apiUrl=`/api/match/${_currentEventId}/commentaries`;else{apiUrl=`/api/match/${_currentEventId}/detail`;if(_currentMatchUrl)apiUrl+='?url='+encodeURIComponent(_currentMatchUrl)}const r=await fetch(apiUrl);if(!r.ok){el.innerHTML='Lỗi máy chủ ('+r.status+')
';return}const d=await r.json();if(d.error){el.innerHTML=''+esc(d.error)+'
';return}if(tab==='detail'&&typeof renderMatchDetail==='function'){renderMatchDetail(el,d);return}el.innerHTML=d.html||'Không có dữ liệu
'}catch(e){el.innerHTML='Lỗi
'}}
-function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]))}
-function showView(id){document.querySelectorAll('.view').forEach(v=>v.classList.remove('active'));document.getElementById(id)?.classList.add('active')}
-function switchCat(id){document.querySelectorAll('.cat').forEach(c=>c.classList.remove('active'));document.querySelector(`[data-cat="${id}"]`)?.classList.add('active');document.querySelectorAll('.view').forEach(v=>v.classList.remove('active'));document.querySelectorAll('video').forEach(v=>{v.pause();if(v._hls){v._hls.destroy();v._hls=null}});document.querySelectorAll('iframe[data-yt-src]').forEach(f=>{f.src='';const tw=f.closest('.yt-thumb-wrap');if(tw)tw.style.backgroundImage='none'});if(id==='home')document.getElementById('view-home').classList.add('active');else if(id==='news-all'){document.getElementById('view-cat').classList.add('active');loadNewsTab()}else{document.getElementById('view-cat').classList.add('active');loadCat(id)}}
-function toast(msg){let t=document.getElementById('progress-toast');if(t){t.textContent=msg;t.style.display='block';setTimeout(()=>{t.style.display='none'},3500)}}
-
-// ===== doShare: COPY link to clipboard, then try native share as bonus =====
- function doShare(title,url,img,postId){
- var shareUrl;
- var _base = (typeof SPACE!=='undefined' && SPACE) ? SPACE : location.origin;
- if(postId){
- shareUrl = _base+'/s?post_id='+encodeURIComponent(postId)+'&title='+encodeURIComponent(title);
- } else {
- shareUrl = _base+'/s?url='+encodeURIComponent(url)+'&title='+encodeURIComponent(title)+'&img='+encodeURIComponent(img||'');
- }
- // Try clipboard API first (modern, works on HTTPS)
- if(navigator.clipboard && navigator.clipboard.writeText){
- navigator.clipboard.writeText(shareUrl).then(function(){
- toast('📋 Đã sao chép link!');
- try{if(navigator.share)navigator.share({title:title||'',url:shareUrl}).catch(function(){});}catch(e){}
- }).catch(function(){
- // Fallback: execCommand (deprecated but works on some browsers)
- try{
- var ta=document.createElement('textarea');
- ta.value=shareUrl;ta.style.position='fixed';ta.style.left='-9999px';ta.style.top='-9999px';ta.style.opacity='0';
- document.body.appendChild(ta);ta.select();ta.setSelectionRange(0,99999);
- if(document.execCommand('copy')){toast('📋 Đã sao chép link!');}
- else{prompt('📋 Sao chép link:', shareUrl);}
- document.body.removeChild(ta);
- }catch(e){prompt('📋 Sao chép link:', shareUrl);}
- try{if(navigator.share)navigator.share({title:title||'',url:shareUrl}).catch(function(){});}catch(e){}
- });
- } else {
- // execCommand approach
- try{
- var ta=document.createElement('textarea');
- ta.value=shareUrl;ta.style.position='fixed';ta.style.left='-9999px';ta.style.top='-9999px';ta.style.opacity='0';
- document.body.appendChild(ta);ta.select();ta.setSelectionRange(0,99999);
- if(document.execCommand('copy')){toast('📋 Đã sao chép link!');}
- else{prompt('📋 Sao chép link:', shareUrl);}
- document.body.removeChild(ta);
- }catch(e){prompt('📋 Sao chép link:', shareUrl);}
- try{if(navigator.share)navigator.share({title:title||'',url:shareUrl}).catch(function(){});}catch(e){}
- }
-}
-
async function doInteract(videoId,type){try{const r=await fetch('/api/v2/interact',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:videoId,type})});return await r.json();}catch(e){return null;}}
async function getInteractions(videoId){try{return await fetch('/api/v2/interactions?id='+encodeURIComponent(videoId)).then(r=>r.json());}catch(e){return{views:0,likes:0,comments:0};}}
async function getComments(videoId){try{const j=await fetch('/api/v2/comments?id='+encodeURIComponent(videoId)).then(r=>r.json());return j.comments||[];}catch(e){return[];}}
async function postComment(videoId,text){try{const j=await fetch('/api/v2/comment',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:videoId,text})}).then(r=>r.json());return j.comments||[];}catch(e){return[];}}
-function buildTikTokSlide(opts){return`
${opts.vtag}
${opts.badge||''} ${esc(opts.title)}
${opts.desc?`
${esc(opts.desc)}
`:''}
👁
0
❤️
0
💬
0
📤
🖥️
${opts.extraBtn||''}
⛶
${opts.idx+1}/${opts.total} `;}
-function toggleFeedFullscreen(videoId){
- const slide = videoId ? document.querySelector('.tiktok-slide[data-vid="'+videoId+'"]') : null;
- const feedEl = slide ? slide.closest('.tiktok-container') : document.querySelector('.tiktok-container');
- if(!feedEl) return;
- const on = feedEl.classList.toggle('feed-fullscreen');
- document.body.style.overflow = on ? 'hidden' : '';
- const icons = feedEl.querySelectorAll('[id^="fs-toggle-"]');
- icons.forEach(ic => { ic.textContent = on ? '✕' : '⛶'; });
- if(on){
- // keep the active slide centered & playable in full height
- setTimeout(()=>{ slide && slide.scrollIntoView({block:'nearest'}); }, 60);
- } else if(slide){
- slide.scrollIntoView({block:'nearest'});
- }
-}
+function buildTikTokSlide(opts){return`${opts.vtag}
${opts.badge||''} ${esc(opts.title)}
👁
0
❤️
0
💬
0
📤
${opts.extraBtn||''}
${opts.idx+1}/${opts.total} `;}
async function doView(videoId,btn){const j=await doInteract(videoId,'view');if(j){const c=btn.querySelector('.count');if(c)c.textContent=fmtNum(j.views);}}
async function doLike(videoId,btn){const j=await doInteract(videoId,'like');if(j){const c=btn.querySelector('.count');if(c)c.textContent=fmtNum(j.likes);}}
function fmtNum(n){if(!n)return'0';if(n>=1000000)return(n/1000000).toFixed(1)+'M';if(n>=1000)return(n/1000).toFixed(1)+'K';return String(n);}
async function loadCounters(videoIds){for(let i=0;iĐang tải... ';const cmts=await getComments(videoId);renderInlineComments(panel,videoId,idx,cmts);/* Remove snap so feed can scroll to show comment input */var sl=panel.closest('.tiktok-slide');if(sl)sl.style.scrollSnapAlign='';setTimeout(function(){var feed=document.getElementById('tiktok-feed');if(feed){var slidePos=sl?sl.offsetTop:0;feed.scrollTop=slidePos+200;}/* Focus input */var inp=document.getElementById('cmt-input-'+idx);if(inp)inp.focus();},300);}
-function renderInlineComments(panel,videoId,idx,cmts){let h='=0)act(best)},150)});setTimeout(()=>act(0),400);slides.forEach(sl=>{const g=sl.querySelector('.slide-gesture');const v=sl.querySelector('video');const fr=sl.querySelector('iframe');const tapFn=e=>{if(g&&e.target!==g)return;if(v){e.preventDefault();if(e.detail>2)return;v.paused?v.play().catch(()=>{}):v.pause()}else if(fr&&fr.dataset.ytSrc&&sl._active){const src=fr.dataset.ytSrc;if(/tiktok\.com\/embed|facebook\.com\/plugins|instagram\.com\/p\/.*\/embed|platform\.twitter\.com|player\.vimeo\.com|youtube\.com\/embed/.test(src)){window.open(src.replace(/&/g,'&'),'_blank')}}};if(g)g.addEventListener('click',tapFn);if(v)v.addEventListener('click',e=>{e.preventDefault();v.paused?v.play().catch(()=>{}):v.pause()})});const ids=[...slides].map(sl=>sl.dataset.vid||'');loadCounters(ids)}
-async function openHighlightFeed(league,idx,link,forceUrl){showView('view-tiktok');const el=document.getElementById('view-tiktok');el.innerHTML='Đang tải...
';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='Không có video
';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='Không tìm thấy video
';return}let ti=-1;if(link){ti=vids.findIndex(v=>(v.link||'')===(link||''));}if(ti<0)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=`← Highlight `;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?`
`:isHLS?`
`:`
`;const videoId='hl-'+league+'-'+v._idx;const nv=(i+1)%ordered.length;h+=buildTikTokSlide({vtag,title:v.title,badge:'HL',badgeClass:'badge-fpt',videoId,idx:i,total:ordered.length,shareUrl:v.link||'',postId:'',extraBtn:`
⏭️
`})});h+='
';el.innerHTML=h;setTimeout(()=>initTikTokFeed(),200);}
-async function openYTShortsFeed(idx){showView('view-tiktok');const el=document.getElementById('view-tiktok');el.innerHTML='Đã xóa Shorts Dân trí/SKĐS
';}
-async function openShortAIFeed(idx){showView('view-tiktok');const el=document.getElementById('view-tiktok');if(!_wallPosts||!_wallPosts.length){el.innerHTML='Không có Short AI
';return}const aiPosts=_wallPosts.filter(p=>p.video);if(!aiPosts.length||idx>=aiPosts.length){el.innerHTML='Không có Short AI
';return}const ordered=aiPosts.slice(idx).concat(aiPosts.slice(0,idx));let h=`← Tường AI `;ordered.forEach((p,i)=>{const baseIdx=_wallPosts.indexOf(p);const isYT=/youtube\.com\/embed|youtu\.be\/|youtube-nocookie|tiktok\.com\/embed|facebook\.com\/plugins|instagram\.com\/p\/.*\/embed|platform\.twitter\.com|player\.vimeo\.com/.test(p.video||'');const vtag=p.video?isYT?`
`:`
`:'';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||'',desc:(p.text&&p.text!==p.title?p.text:'').slice(0,400),extraBtn:`
📥
`})});h+='
';el.innerHTML=h;setTimeout(()=>initTikTokFeed(),200);}
-function readWallPost(idx){const p=_wallPosts&&_wallPosts[idx];if(!p)return;if(p.slides&&p.slides.length){readSlidePost(idx);return}const isEmb=p.embed_oembed||/(tiktok\.com\/embed|facebook\.com\/plugins|instagram\.com\/p\/.*\/embed|youtube\.com\/embed|platform\.twitter\.com|player\.vimeo\.com)/.test(p.video||'');if(isEmb||(p.video&&p.video.indexOf('/api/ai/short-file/')===0&&!p.url)){openShortAIFeed(_wallPosts.filter(x=>x.video).indexOf(p));return}readArticle(p.url||'','','',p.title,p.text);}
-/** Show rewrite slide viewer - vertical slides with text+image */
-function readSlidePost(idx){const p=_wallPosts[idx];if(!p||!p.slides)return;showView('view-article');const el=document.getElementById('view-article');let h=`← Tường AI `;p.slides.forEach((s,i)=>{h+=`
Slide ${s.index||i+1}/${p.slides.length}
${s.image?`
`:''}
${esc(s.text)}
`;});h+=`
📤 Chia sẻ bài viết này 🎨 Thiết kế ảnh
`;el.innerHTML=h;}
-
-/** Slide Designer Modal - design a slide image with high-contrast text on background */
-function designerGetRatio(){const v=document.getElementById('designer-ratio')?.value||'3:4';const m=v.split(':');return m.length===2?{w:parseInt(m[0]),h:parseInt(m[1])}:1;}
-function designerGetLayout(){return document.getElementById('designer-layout')?.value||'solid';}
-function designerDebounce(fn,d){clearTimeout(fn._t);fn._t=setTimeout(fn,d);}
-function openSlideDesigner(idx){
- designerSlideIdx=idx;
- const p=_wallPosts[idx];if(!p||!p.slides)return;
- const overlay=document.createElement('div');overlay.id='slide-designer-overlay';overlay.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.92);z-index:99999;display:flex;align-items:center;justify-content:center;padding:16px;overflow-y:auto';
- let h='';
- h+='
🎨 Thiết kế ảnh slide ✕ ';
- h+='
Chọn slide: ';
- p.slides.forEach((s,i)=>{h+=`Slide ${s.index||i+1}: ${(s.text||'').slice(0,60)}... `;});
- h+='
';
- h+='
Nội dung trên ảnh:
';
- h+='
Tỉ lệ cắt ảnh nền: ';
- h+='📐 3:4 (dọc) 📐 1:1 (vuông) 📐 4:3 (ngang) ';
- h+='
';
- h+='
Bố cục nền: ';
- h+='🎨 Nền đẳng (phủ màu) ';
- h+='🌙 Vignette (viền tối dần) ';
- h+='✂️ Chia đôi (trên mờ, dưới rõ) ';
- h+='🎯 Spotlight (ánh sáng giữa) ';
- h+='↘️ Đường chéo (góc trái-trên→phải-dưới) ';
- h+='
';
- h+='
Màu chữ: ';
- ['#ffffff','#000000','#ff4444','#44ff44','#4444ff','#ffff00','#ff00ff','#00ffff','#ff8800','#88ff00'].forEach(c=>{h+=` `;});
- h+='
';
- h+='
Ảnh nền (URL hoặc upload):
';
- h+='
👁 Xem trưởng 💾 Lưu ảnh đăng tường
';
- h+='
';
- h+='
';
- overlay.innerHTML=h;document.body.appendChild(overlay);
- document.getElementById('designer-slide-select').addEventListener('change',function(){updateDesignerText(this.value);previewDesignerSlide();});
- document.getElementById('designer-preview-btn').addEventListener('click',function(){previewDesignerSlide();});
- document.getElementById('designer-save-btn').addEventListener('click',function(){saveDesignerSlide(p.title,(p.id||''),idx);});
- ['change','input'].forEach(function(evt){
- const txt=document.getElementById('designer-text');if(txt)txt.addEventListener(evt,function(){designerDebounce(previewDesignerSlide,150);});
- const bg=document.getElementById('designer-bg-url');if(bg)bg.addEventListener(evt,function(){designerDebounce(previewDesignerSlide,150);});
- const ratio=document.getElementById('designer-ratio');if(ratio)ratio.addEventListener(evt,function(){previewDesignerSlide();});
- const layout=document.getElementById('designer-layout');if(layout)layout.addEventListener(evt,function(){previewDesignerSlide();});
- });
- previewDesignerSlide();
-}
-function updateDesignerText(slideIdx){const p=_wallPosts[designerSlideIdx];if(!p||!p.slides)return;const s=p.slides[parseInt(slideIdx)];if(s)document.getElementById('designer-text').value=s.text||'';document.getElementById('designer-bg-url').value=s.image||p.img||'';designerBgDataUrl=null;}
-function handleDesignerBgFile(input){
- const file=input.files&&input.files[0];if(!file)return;
- const reader=new FileReader();
- reader.onload=function(e){designerBgDataUrl=e.target.result;document.getElementById('designer-bg-url').value='[uploaded]';};
- reader.readAsDataURL(file);
-}
-function previewDesignerSlide(){
- const slideIdx=parseInt(document.getElementById('designer-slide-select')?.value||'0');
- const p=_wallPosts[designerSlideIdx];if(!p||!p.slides)return;
- const s=p.slides[slideIdx];if(!s)return;
- const text=document.getElementById('designer-text')?.value||s.text||'';
- const bgUrlInput=document.getElementById('designer-bg-url')?.value||'';
- const bgUrl=designerBgDataUrl||bgUrlInput||s.image||p.img||'';
- const textColor=document.getElementById('designer-text')?.style.color||'#ffffff';
- const ratio=designerGetRatio();
- const layout=designerGetLayout();
- const previewArea=document.getElementById('designer-preview-area');if(!previewArea)return;
- previewArea.style.display='block';previewArea.innerHTML='
⏳ Đang tạo xem trưởng...
';
- const canvas=document.createElement('canvas');
- const vw=Math.max(document.documentElement.clientWidth||540, document.body.clientWidth||540);
- const maxW=Math.min(540, Math.floor(vw*0.9));
- canvas.width=maxW;canvas.height=Math.round(maxW*ratio.h/ratio.w);
- canvas.style.maxWidth='100%';canvas.style.width='100%';canvas.style.height='auto';
- canvas.style.borderRadius='12px';canvas.style.boxShadow='0 8px 24px rgba(0,0,0,.5)';
- canvas.style.display='block';canvas.style.margin='0 auto';
- const ctx=canvas.getContext('2d');const img=new Image();img.crossOrigin='anonymous';
- function drawLayout(){
- if(layout==='vignette'){const g=ctx.createRadialGradient(canvas.width/2,canvas.height/2,0,canvas.width/2,canvas.height/2,Math.max(canvas.width,canvas.height)*0.6);g.addColorStop(0,'rgba(0,0,0,0)');g.addColorStop(1,'rgba(0,0,0,0.7)');ctx.fillStyle=g;ctx.fillRect(0,0,canvas.width,canvas.height);}
- else if(layout==='split'){ctx.fillStyle='rgba(0,0,0,0)';ctx.fillRect(0,0,canvas.width,canvas.height/2);ctx.fillStyle='rgba(0,0,0,0.7)';ctx.fillRect(0,canvas.height/2,canvas.width,canvas.height/2);}
- else if(layout==='spotlight'){const cx=canvas.width/2,cy=canvas.height/2,r=Math.max(canvas.width,canvas.height)*0.35;ctx.save();ctx.fillStyle='rgba(0,0,0,0.7)';ctx.fillRect(0,0,canvas.width,canvas.height);ctx.globalCompositeOperation='destination-out';ctx.fillStyle='white';ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.fill();ctx.globalCompositeOperation='source-over';}
- else if(layout==='diagonal'){ctx.fillStyle='rgba(0,0,0,0.55)';ctx.fillRect(0,0,canvas.width,canvas.height);ctx.fillStyle='rgba(0,0,0,0.75)';ctx.beginPath();ctx.moveTo(0,0);ctx.lineTo(canvas.width,0);ctx.lineTo(0,canvas.height);ctx.closePath();ctx.fill();}
- else {ctx.fillStyle='rgba(0,0,0,0.55)';ctx.fillRect(0,0,canvas.width,canvas.height);}
- }
- function drawText(){
- ctx.fillStyle=textColor;ctx.font='bold '+Math.round(canvas.width*28/540)+'px sans-serif';
- ctx.textAlign='center';ctx.textBaseline='middle';
- const maxW=Math.round(canvas.width*0.82);
- const words=text.split(' ');let line='';let y=Math.round(canvas.height/2);
- const fontSize=Math.round(canvas.width*28/540);const lineHeight=Math.round(fontSize*1.3);
- for(let i=0;i
maxW&&i>0){ctx.fillText(line.trim(),canvas.width/2,y);y+=lineHeight;line=words[i];}else{line=test;}}
- ctx.fillText(line.trim(),canvas.width/2,y);
- }
- function renderAll(){
- ctx.fillStyle='#1a1a1a';ctx.fillRect(0,0,canvas.width,canvas.height);
- drawLayout();
- drawText();
- previewArea.innerHTML='';previewArea.appendChild(canvas);
- }
- img.onload=function(){
- const iw=img.naturalWidth||1,ih=img.naturalHeight||1;
- const scale=Math.max(canvas.width/iw,canvas.height/ih);
- const dw=iw*scale,dh=ih*scale;
- const dx=(canvas.width-dw)/2,dy=(canvas.height-dh)/2;
- ctx.drawImage(img,0,0,iw,ih,dx,dy,dw,dh);
- drawLayout();drawText();
- previewArea.innerHTML='';previewArea.appendChild(canvas);
- };
- img.onerror=function(){renderAll();};
- if(bgUrl){img.src=bgUrl.startsWith('/api/')?bgUrl:bgUrl.startsWith('data:')?bgUrl:'/api/proxy/img?url='+encodeURIComponent(bgUrl);}else{renderAll();}
-}
-async function saveDesignerSlide(title,postId,idx){
- const slideIdx=parseInt(document.getElementById('designer-slide-select')?.value||'0');
- const p=_wallPosts[idx];if(!p||!p.slides)return;
- const s=p.slides[slideIdx];if(!s)return;
- const text=document.getElementById('designer-text')?.value||s.text||'';
- const bgUrlInput=document.getElementById('designer-bg-url')?.value||'';
- const bgUrl=designerBgDataUrl||bgUrlInput||s.image||p.img||'';
- const textColor=document.getElementById('designer-text')?.style.color||'#ffffff';
- const ratio=designerGetRatio();
- const layout=designerGetLayout();
- const statusEl=document.getElementById('designer-status');if(statusEl)statusEl.textContent='⏳ Đang tạo ảnh...';
- const FINAL_W=ratio.w>=ratio.h?1800:1350;
- const FINAL_H=Math.round(FINAL_W*ratio.h/ratio.w);
- const canvas=document.createElement('canvas');canvas.width=FINAL_W;canvas.height=FINAL_H;
- const ctx=canvas.getContext('2d');
- const img=new Image();img.crossOrigin='anonymous';
- function drawLayoutF(){
- if(layout==='vignette'){const g=ctx.createRadialGradient(FINAL_W/2,FINAL_H/2,0,FINAL_W/2,FINAL_H/2,Math.max(FINAL_W,FINAL_H)*0.6);g.addColorStop(0,'rgba(0,0,0,0)');g.addColorStop(1,'rgba(0,0,0,0.7)');ctx.fillStyle=g;ctx.fillRect(0,0,FINAL_W,FINAL_H);}
- else if(layout==='split'){ctx.fillStyle='rgba(0,0,0,0)';ctx.fillRect(0,0,FINAL_W,FINAL_H/2);ctx.fillStyle='rgba(0,0,0,0.7)';ctx.fillRect(0,FINAL_H/2,FINAL_W,FINAL_H/2);}
- else if(layout==='spotlight'){const cx=FINAL_W/2,cy=FINAL_H/2,r=Math.max(FINAL_W,FINAL_H)*0.35;ctx.save();ctx.fillStyle='rgba(0,0,0,0.7)';ctx.fillRect(0,0,FINAL_W,FINAL_H);ctx.globalCompositeOperation='destination-out';ctx.fillStyle='white';ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.fill();ctx.globalCompositeOperation='source-over';}
- else if(layout==='diagonal'){ctx.fillStyle='rgba(0,0,0,0.55)';ctx.fillRect(0,0,FINAL_W,FINAL_H);ctx.fillStyle='rgba(0,0,0,0.75)';ctx.beginPath();ctx.moveTo(0,0);ctx.lineTo(FINAL_W,0);ctx.lineTo(0,FINAL_H);ctx.closePath();ctx.fill();}
- else {ctx.fillStyle='rgba(0,0,0,0.55)';ctx.fillRect(0,0,FINAL_W,FINAL_H);}
- }
- function drawTextF(){
- ctx.fillStyle=textColor;ctx.font='bold '+Math.round(FINAL_W*48/1080)+'px sans-serif';
- ctx.textAlign='center';ctx.textBaseline='middle';
- const maxW=Math.round(FINAL_W*0.82);const words=text.split(' ');let line='';let y=Math.round(FINAL_H/2);
- const fontSize=Math.round(FINAL_W*48/1080);const lineHeight=Math.round(fontSize*1.3);
- for(let i=0;imaxW&&i>0){ctx.fillText(line.trim(),FINAL_W/2,y);y+=lineHeight;line=words[i];}else{line=test;}}ctx.fillText(line.trim(),FINAL_W/2,y);
- }
- function render(){
- ctx.fillStyle='#1a1a1a';ctx.fillRect(0,0,FINAL_W,FINAL_H);
- drawLayoutF();drawTextF();
- canvas.toBlob(async function(blob){
- const formData=new FormData();formData.append('file',blob,'slide_'+slideIdx+'.png');formData.append('post_id',postId||p.id||'');
- try{const r=await fetch('/api/wall/img',{method:'POST',body:formData});const j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi upload');
- s.image=j.url;
- const post={id:p.id,title:title||p.title,text:p.text,img:j.url,url:p.url,slides:p.slides,images:p.images,voice:p.voice,emotion:p.emotion,language:p.language,ts:p.ts,kind:p.kind||'slide_summary'};
- const wr=await fetch('/api/wall',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(post)});
- const wj=await wr.json();if(wr.ok&&wj.post){prependWallPost(wj.post);toast('✅ Đã lưu ảnh đăng lên Tường AI!');}else{toast('✅ Đã lưu ảnh slide!');}
- document.getElementById('slide-designer-overlay').remove();
- }catch(e){if(statusEl)statusEl.textContent='❌ '+e.message;toast('❌ '+e.message);}
- },'image/png');
- }
- img.onload=function(){
- const iw=img.naturalWidth||1,ih=img.naturalHeight||1;
- const scale=Math.max(FINAL_W/iw,FINAL_H/ih);
- const dw=iw*scale,dh=ih*scale;
- const dx=(FINAL_W-dw)/2,dy=(FINAL_H-dh)/2;
- ctx.drawImage(img,0,0,iw,ih,dx,dy,dw,dh);
- drawLayoutF();drawTextF();
- canvas.toBlob(async function(blob){
- const formData=new FormData();formData.append('file',blob,'slide_'+slideIdx+'.png');formData.append('post_id',postId||p.id||'');
- try{const r=await fetch('/api/wall/img',{method:'POST',body:formData});const j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi upload');
- s.image=j.url;
- const post={id:p.id,title:title||p.title,text:p.text,img:j.url,url:p.url,slides:p.slides,images:p.images,voice:p.voice,emotion:p.emotion,language:p.language,ts:p.ts,kind:p.kind||'slide_summary'};
- const wr=await fetch('/api/wall',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(post)});
- const wj=await wr.json();if(wr.ok&&wj.post){prependWallPost(wj.post);toast('✅ Đã lưu ảnh đăng lên Tường AI!');}else{toast('✅ Đã lưu ảnh slide!');}
- document.getElementById('slide-designer-overlay').remove();
- }catch(e){if(statusEl)statusEl.textContent='❌ '+e.message;toast('❌ '+e.message);}
- },'image/png');
- };
- img.onerror=function(){if(statusEl)statusEl.textContent='⚠️ Không tải được ảnh nền, dùng nền mặc định';ctx.fillStyle='#1a1a1a';ctx.fillRect(0,0,FINAL_W,FINAL_H);drawLayoutF();drawTextF();canvas.toBlob(function(blob){
- const formData=new FormData();formData.append('file',blob,'slide_'+slideIdx+'.png');formData.append('post_id',postId||p.id||'');
- fetch('/api/wall/img',{method:'POST',body:formData}).then(r=>r.json()).then(j=>{
- if(j.ok){s.image=j.url;const post={id:p.id,title:title||p.title,text:p.text,img:j.url,url:p.url,slides:p.slides,images:p.images,voice:p.voice,emotion:p.emotion,language:p.language,ts:p.ts,kind:p.kind||'slide_summary'};
- fetch('/api/wall',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(post)}).then(r=>r.json()).then(wj=>{if(wj.post)prependWallPost(wj.post);toast('✅ Đã lưu ảnh đăng lên Tường AI!');document.getElementById('slide-designer-overlay').remove();});}
- }).catch(e=>{if(statusEl)statusEl.textContent='❌ '+e.message;toast('❌ '+e.message);});
- },'image/png');};
- if(bgUrl){img.src=bgUrl.startsWith('/api/')?bgUrl:bgUrl.startsWith('data:')?bgUrl:'/api/proxy/img?url='+encodeURIComponent(bgUrl);}else{render();}
-}
-
-function readNewsTab(tab){loadNewsTab();}
-function loadNewsTab(){const el=document.getElementById('view-cat');if(!el)return;el.innerHTML='Đang tải tin tức...
';fetch('/api/homepage').then(r=>r.json()).then(articles=>{if(!articles||!articles.length){el.innerHTML='Không có tin
';return}let h='';articles.forEach(a=>{const src=a.source||'vne';const badge=a.group||a.source||'';h+=`
${a.img?`
`:''}
${esc(badge)} ${esc(a.title)}
`;});h+='
';el.innerHTML=h;}).catch(()=>{el.innerHTML='Lỗi tải
';});}
-
-function readArticle(url,title,img,presetTitle,presetText){showView('view-article');const el=document.getElementById('view-article');el.innerHTML='Đang tải...
← Quay lại ';if(presetTitle){el.innerHTML=`← Quay lại ${esc(presetTitle)} ${presetText?`
${esc(presetText)}
`:''}
📤 Chia sẻ
`;return;}if(!url)return;fetch('/api/article?url='+encodeURIComponent(url)).then(r=>r.json()).then(d=>{let h=`← Quay lại `;if(d.title)h+=`
${esc(d.title)} `;if(d.summary)h+=`
${esc(d.summary)}
`;if(d.body)d.body.forEach(b=>{if(b.type==='p')h+=`
${esc(b.text)}
`;else if(b.type==='heading')h+=`
${esc(b.text)} `;else if(b.type==='img'&&b.src)h+=`
`;});h+=`
📤 Chia sẻ 🤖 Slide Rewrite AI
`;el.innerHTML=h;}).catch(()=>{el.innerHTML=`← Quay lại `;});}
-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
-if(j.slides && j.slides.length){showView('view-article');const el=document.getElementById('view-article');let h=`← Tường AI `;j.slides.forEach(s=>{h+=`
Slide ${s.index}/${j.slides.length}
${s.image?`
`:''}
${esc(s.text)}
`;});h+=`
🎨 Thiết kế ảnh ⏳ Đang tạo video Short AI...
`;el.innerHTML=h;}
-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! ▶ Xem ngay ';}},500);}if(btn)btn.textContent='✅ Hoàn tất';}catch(e){toast('❌ '+e.message);if(btn){btn.disabled=false;btn.textContent='🤖 Slide Rewrite AI';}}}
-
-function downloadVideo(url, title){
- var a = document.createElement('a');
- a.href = url;
- a.download = (title||'video').toString().replace(/[^a-zA-Z0-9_\-\p{L}]/gu,'_').substring(0,60)+'.mp4';
- a.target = '_blank';
- a.rel = 'noopener';
- a.style.display = 'none';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- toast('📥 Đang tải video xuống...');
-}
-
-// ===== PERSONAL OPINION POST: Viết bài dựa trên quan điểm cá nhân + tin HOT =====
-async function openPersonalPostPreview() {
- const opinion = document.getElementById('opinion-input')?.value.trim();
- if (!opinion || opinion.length < 10) {
- alert('Vui lòng nhập quan điểm cá nhân (ít nhất 10 ký tự)');
- return;
- }
-
- // Get selected hot topics from UI (if any)
- const selectedTopics = [];
- if (window._htTopic) {
- selectedTopics.push(window._htTopic);
- }
-
- // Get selected sources from hashtag view
- const selectedSources = [];
- document.querySelectorAll('.hashtag-src-item.selected').forEach(el => {
- const idx = parseInt(el.dataset.idx || '0');
- // Would need source tracking
- });
-
- const btn = event?.target;
- const origText = btn ? btn.textContent : '';
- if (btn) { btn.disabled = true; btn.textContent = '⏳ Đang tạo preview...'; }
-
- try {
- const resp = await fetch('/api/personal_post/preview', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- opinion: opinion,
- selected_topics: selectedTopics,
- selected_sources: selectedSources
- })
- });
- const data = await resp.json();
- if (!resp.ok || data.error) throw new Error(data.error || 'Lỗi tạo preview');
-
- showPersonalPostModal(data.preview, opinion, selectedTopics, selectedSources);
- } catch (e) {
- toast('❌ ' + e.message);
- } finally {
- if (btn) { btn.disabled = false; btn.textContent = origText; }
- }
-}
-
-function showPersonalPostModal(preview, originalOpinion, selectedTopics, selectedSources) {
- // Remove existing modal
- const existing = document.getElementById('personal-post-modal');
- if (existing) existing.remove();
-
- const modal = document.createElement('div');
- modal.id = 'personal-post-modal';
- modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.9);z-index:99999;display:flex;align-items:center;justify-content:center;padding:16px;overflow-y:auto';
-
- // Each slide renders EXACTLY like the designer's final PNG (same ratio,
- // same bg draw, same text draw) — preview = pixel-identical to wall post image.
- const slidesHtml = (preview.slides || []).map((s, i) => {
- const ratio = {w:3, h:4}; // preview always 3:4 (same as designer final)
- const slideStyle = 'width:100%;max-width:280px;margin:0 auto;display:block;border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.5)';
- return ''
- + '
Slide ' + (i+1) + '
'
- + '
'
- + '
'
- + '
';
- }).join('');
-
- // Background image source: uploaded data-URL (set later) or slide.image / first source image.
- // We keep a small state object so text edits re-render live.
- window._pvState = { slides: (preview.slides||[]).map(function(s,i){
- var bg = s.image || (preview.images && preview.images[0]) || '';
- return { text: s.text||'', bg: bg };
- }), ratio: 3/4, layout: 'solid', filter: 'none', glow: 'white', pos: 'bottom', color: '#ffffff' };
-
- const sourcesHtml = (preview.sources || []).map((s, i) => {
- const imgSrc = (preview.images && preview.images[i+1]) ? preview.images[i+1] : '';
- return ''
- + '
'
- + (imgSrc ? '
' : '')
- + '
'
- + '
' + (s.title || '') + '
'
- + '
'
- + '
';
- }).join('');
-
- modal.innerHTML = ''
- + '
'
- + '
📝 Xem trước bài quan điểm cá nhân '
- + '✕ '
- + ''
- + '
'
- + 'Tiêu đề: '
- + ' '
- + '
'
- + '
'
- + 'Quan điểm gốc: '
- + ''
- + '
'
- + '
'
- + '
🖼️ Các slide — bản xem trước hiển thị ĐÚNG 100% như ảnh thiết kế (chữ + nền): '
- + '
'
- + '🎨 Thiết kế ảnh (canvas) '
- + '🔄 Vẽ lại '
- + '
'
- + '
' + slidesHtml + '
'
- + '
'
- + '
'
- + '
Nguồn tin tham khảo (bỏ chọn để loại): '
- + '
'
- + (sourcesHtml || '
Không có nguồn tin
')
- + '
'
- + '
'
- + '✅ Đăng lên Tường AI '
- + 'Hủy '
- + '
';
-
- document.body.appendChild(modal);
-
- // draw every slide preview canvas
- previewRedrawAllSlides();
-
- // text edits re-render live
- modal.querySelectorAll('.preview-slide-text').forEach(ta => {
- ta.addEventListener('input', function(){
- const idx = parseInt(this.dataset.idx || '0', 10);
- if(window._pvState && window._pvState.slides[idx]) window._pvState.slides[idx].text = this.value;
- previewDrawSlide(idx);
- });
- });
-
- // Store preview data for publishing
- window._personalPostPreview = preview;
- window._personalPostOpinion = originalOpinion;
-}
-
-/* Live-edit slide preview canvas (exact same rendering style as designer output) */
-function previewDrawSlide(idx){
- const canvas = document.querySelector('.preview-slide-canvas[data-idx="'+idx+'"]');
- if(!canvas || !window._pvState) return;
- const st = window._pvState.slides[idx];
- if(!st) return;
- const W = 600, H = Math.round(W * window._pvState.ratio); // 3:4 -> 800
- canvas.width = W; canvas.height = H;
- const ctx = canvas.getContext('2d');
- const opts = { text: st.text, pos: window._pvState.pos, glow: window._pvState.glow,
- color: window._pvState.color, layout: window._pvState.layout,
- filter: window._pvState.filter, bgUrl: st.bg };
- function draw(img){
- // base dark
- ctx.fillStyle='#141414'; ctx.fillRect(0,0,W,H);
- if(img){
- ctx.save();
- let f='';
- if(opts.filter==='grayscale') f='grayscale(1)';
- else if(opts.filter==='sepia') f='sepia(0.85)';
- else if(opts.filter==='saturate') f='saturate(2.4)';
- else if(opts.filter==='warm') f='sepia(0.45) saturate(1.5) hue-rotate(-15deg)';
- else if(opts.filter==='cool') f='saturate(1.2) hue-rotate(15deg) brightness(1.05)';
- else if(opts.filter==='invert') f='invert(1)';
- else if(opts.filter==='noir') f='grayscale(1) contrast(1.6) brightness(0.9)';
- const iw=img.naturalWidth||1, ih=img.naturalHeight||1;
- const scale=Math.max(W/iw, H/ih);
- const dw=iw*scale, dh=ih*scale;
- ctx.drawImage(img,0,0,iw,ih,(W-dw)/2,(H-dh)/2,dw,dh);
- ctx.filter='none';
- ctx.restore();
- }
- // solid overlay like designer
- ctx.fillStyle='rgba(0,0,0,0.6)'; ctx.fillRect(0,0,W,H);
- drawPreviewText(ctx,W,H,opts);
- }
- const bgSrc = (typeof _proxyImg==='function') ? _proxyImg(st.bg||'') : (st.bg || '');
- if(bgSrc){
- const im = new Image(); im.crossOrigin='anonymous';
- im.onload = function(){ draw(im); };
- im.onerror = function(){ draw(null); };
- im.src = bgSrc;
- } else draw(null);
-}
-function drawPreviewText(ctx,W,H,opts){
- const text = opts.text || '';
- if(!text.trim()) return;
- const fs = Math.min(52, Math.round(W*0.09));
- ctx.font = 'bold '+fs+'px "Segoe UI","Arial","Noto Sans",sans-serif';
- ctx.textAlign='center';
- const lines=[]; let line='';
- const maxW=Math.round(W*0.82);
- text.split(' ').forEach(w=>{
- const test = line? line+' '+w : w;
- if(ctx.measureText(test).width>maxW && line){ lines.push(line.trim()); line=w; } else { line=test; }
+async function toggleComments(videoId,idx){const panel=document.getElementById('cmt-inline-'+idx);if(!panel)return;if(panel.style.display!=='none'){panel.style.display='none';return;}panel.style.display='block';panel.innerHTML='Đang tải...
';const cmts=await getComments(videoId);renderInlineComments(panel,videoId,idx,cmts);}
+function renderInlineComments(panel,videoId,idx,cmts){let h='';if(cmts.length){cmts.slice(-30).forEach(c=>{h+=`
${c.time||''} ${esc(c.text)}
`;});}else{h+='
Chưa có bình luận
';}h+=`
Gửi
`;panel.innerHTML=h;const list=panel.querySelector('.inline-cmt-list');if(list)list.scrollTop=list.scrollHeight;}
+async function submitInlineCmt(videoId,idx){const inp=document.getElementById('cmt-input-'+idx);if(!inp)return;const text=inp.value.trim();if(!text)return;inp.value='';inp.disabled=true;const cmts=await postComment(videoId,text);inp.disabled=false;const panel=document.getElementById('cmt-inline-'+idx);if(panel)renderInlineComments(panel,videoId,idx,cmts);const cc=document.getElementById('cc-'+idx);if(cc)cc.textContent=fmtNum(cmts.length);}
+function initTikTokFeed(){const feed=document.getElementById('tiktok-feed');if(!feed)return;const slides=feed.querySelectorAll('.tiktok-slide');let cur=-1;function act(i){if(i===cur)return;slides.forEach((sl,idx)=>{const v=sl.querySelector('video');const fr=sl.querySelector('iframe');if(idx===i){if(v&&v.dataset.hls&&!v._hls&&typeof Hls!=='undefined'&&Hls.isSupported()){const hls=new Hls();hls.loadSource(v.dataset.hls);hls.attachMedia(v);hls.on(Hls.Events.MANIFEST_PARSED,()=>v.play().catch(()=>{}));v._hls=hls}else if(v)v.play().catch(()=>{});if(fr&&!fr.src&&fr.dataset.ytSrc)fr.src=fr.dataset.ytSrc;const vid=sl.dataset.vid;if(vid&&!sl._viewed){sl._viewed=true;doInteract(vid,'view').then(j=>{if(j){const vc=document.getElementById('vc-'+idx);if(vc)vc.textContent=fmtNum(j.views);}});}}else{if(v){v.pause();if(v._hls){v._hls.destroy();v._hls=null}}if(fr&&fr.src)fr.src=''}});cur=i}let sT;feed.addEventListener('scroll',()=>{clearTimeout(sT);sT=setTimeout(()=>{const rect=feed.getBoundingClientRect(),ctr=rect.top+rect.height/2;let best=-1,bestD=1e9;slides.forEach((sl,i)=>{const d=Math.abs(sl.getBoundingClientRect().top+sl.getBoundingClientRect().height/2-ctr);if(d=0)act(best)},150)});setTimeout(()=>act(0),400);slides.forEach(sl=>{const v=sl.querySelector('video');if(v)v.addEventListener('click',e=>{e.preventDefault();v.paused?v.play().catch(()=>{}):v.pause()})});const ids=[...slides].map(sl=>sl.dataset.vid||'');loadCounters(ids)}
+async function openHighlightFeed(league,idx,forceUrl){showView('view-tiktok');const el=document.getElementById('view-tiktok');el.innerHTML='Đang tải...
';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='Không có video
';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));const v=await r.json();if(v&&v.src)return{...a,...v,_idx:i}}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='Không tìm thấy video
';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=`← Highlight `;ordered.forEach((v,i)=>{const isYT=v.type==='youtube',isHLS=!isYT&&v.src?.includes('.m3u8'),poster=v.poster?` poster="${v.poster}"`:'';const vtag=isYT?`
`:isHLS?`
`:`
`;const videoId='hl-'+league+'-'+(v.id||v._idx);h+=buildTikTokSlide({vtag,title:v.title,badge:'HL',badgeClass:'badge-fpt',videoId,idx:i,total:ordered.length,shareUrl:v.link||'',extraBtn:`
⬜
16:9
`});});h+='
';el.innerHTML=h;initTikTokFeed();}
+async function openYTShortsFeed(startIdx){showView('view-tiktok');const el=document.getElementById('view-tiktok');el.innerHTML='Đang tải...
';const arts=_shortsData.length?_shortsData:await fetch('/api/shorts').then(r=>r.json()).catch(()=>[]);if(!arts.length){el.innerHTML='Không có shorts
';return}const ordered=startIdx>0?[...arts.slice(startIdx),...arts.slice(0,startIdx)]:arts;let h=`← Shorts `;ordered.forEach((v,i)=>{const id=v.id||'';const src=`https://www.youtube.com/embed/${id}?autoplay=1&rel=0&playsinline=1`;const vtag=``;const badge=v.channel==='baosuckhoedoisongboyte'?'SKĐS':'Dân trí';const videoId='yt-'+id;h+=buildTikTokSlide({vtag,title:v.title,badge,badgeClass:'badge-fpt',videoId,idx:i,total:ordered.length,shareUrl:'https://youtube.com/watch?v='+id});});h+='
';el.innerHTML=h;initTikTokFeed();}
+async function openShortAIFeed(startIdx){showView('view-tiktok');const el=document.getElementById('view-tiktok');el.innerHTML='Đang tải...
';const wall=(await fetch('/api/wall').then(r=>r.json()).catch(()=>({posts:[]}))).posts||[];const vids=wall.filter(p=>p.video);if(!vids.length){el.innerHTML='Chưa có Short AI
';return}const ordered=startIdx>0?[...vids.slice(startIdx),...vids.slice(0,startIdx)]:vids;let h=`← Short AI `;ordered.forEach((p,i)=>{const vtag=` `;const videoId='ai-'+(p.id||i);h+=buildTikTokSlide({vtag,title:p.title,badge:'AI',badgeClass:'badge-ai',videoId,idx:i,total:ordered.length,shareUrl:SPACE});});h+='
';el.innerHTML=h;initTikTokFeed();}
+async function readArticle(url){showView('view-article');const el=document.getElementById('view-article');el.innerHTML='Đang tải...
';try{const r=await fetch('/api/article?url='+encodeURIComponent(url));const data=await r.json();if(data&&!data.error&&data.body&&data.body.length){_currentArticle={url,data};let h=`← Quay lại ${esc(data.title)} `;if(data.summary)h+=`
${esc(data.summary)}
`;const seen={};data.body.forEach(b=>{if(b.type==='p')h+=`
${b.text}
`;else if(b.type==='img'&&b.src&&!seen[b.src]){seen[b.src]=1;h+=`
`}else if(b.type==='heading')h+=`
${esc(b.text)} `});h+=`
🤖 Rewrite AI đăng tường 📤 🔗 Gốc
`;el.innerHTML=h;window.scrollTo(0,0);return;}}catch(e){}el.innerHTML=`← Quay lại `;}
+async function rewriteArticle(){const url=_currentArticle?.url;if(!url)return;toast('⏳ Đang rewrite...');try{const r=await fetch('/api/rewrite_share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url,context:document.querySelector('.article-view')?.innerText?.slice(0,14000)||''})});const j=await r.json();if(!r.ok||j.error)throw new Error(j.error);toast('✅ Đã đăng Tường AI!');if(j.post)prependWallPost(j.post);}catch(e){toast('❌ '+e.message)}}
+async function rewriteUrl(){const url=document.getElementById('url-input')?.value.trim();if(!url)return alert('Dán URL');toast('⏳ Đang rewrite...');try{const r=await fetch('/api/url_wall',{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);toast('✅ Đã đăng!');document.getElementById('url-input').value='';if(j.post)prependWallPost(j.post);}catch(e){toast('❌ '+e.message)}}
+async function askAI(){const q=document.getElementById('ask-q')?.value.trim();if(!q)return alert('Nhập câu hỏi');const a=document.getElementById('ask-a');a.textContent='Đang hỏi...';try{const r=await fetch('/api/article/ask',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:_currentArticle?.url||'',question:q,context:document.querySelector('.article-view')?.innerText?.slice(0,12000)||''})});const j=await r.json();a.textContent=j.answer||'Không trả lời được';}catch(e){a.textContent='Lỗi: '+e.message}}
+async function readWallPost(i){const p=_wallPosts[i];if(!p)return;showView('view-article');
+ const images = p.images || [];
+ let imgGallery = '';
+ if(images.length > 0){
+ imgGallery = '';
+ images.forEach((imgUrl, imgIdx) => {
+ if(imgIdx === 0){
+ imgGallery += `
`;
+ } else {
+ if(imgIdx === 1) imgGallery += '
';
+ imgGallery += `
`;
+ }
});
- if(line) lines.push(line.trim());
- const lh=Math.round(fs*1.25);
- const blockH=lines.length*lh;
- let y;
- if(opts.pos==='top') y=Math.round(H*0.14)+lh/2;
- else if(opts.pos==='bottom') y=Math.round(H*0.88)-blockH+lh/2;
- else y=Math.round(H/2)-blockH/2+lh/2;
- if(opts.glow==='white'){ ctx.shadowColor='rgba(255,255,255,0.95)'; ctx.shadowBlur=Math.max(8,fs*0.5); }
- else if(opts.glow==='color'){ ctx.shadowColor=opts.color; ctx.shadowBlur=Math.max(10,fs*0.6); }
- else if(opts.glow==='neon'){ ctx.shadowColor='#00ffff'; ctx.shadowBlur=Math.max(16,fs*0.9); }
- ctx.fillStyle=opts.color;
- lines.forEach((l,li)=>{ ctx.fillText(l, W/2, y+li*lh); });
- ctx.shadowBlur=0;
-}
-function previewRedrawAllSlides(){
- if(!window._pvState) return;
- window._pvState.slides.forEach(function(_, i){ previewDrawSlide(i); });
-}
-function openPreviewDesigner(){
- // opens the real slide designer on the FIRST preview slide as a background.
- // The designer modal already renders canvas + uploads PNGs; we reuse it by
- // temporarily creating a wall-post-like object from the preview slides.
- if(!window._personalPostPreview || !window._pvState) return;
- const slides = window._pvState.slides.map(function(s,i){
- return { text: s.text, image: s.bg, index: i+1 };
+ if(images.length > 1) imgGallery += '
';
+ imgGallery += '
';
+ }
+ const hasVideo = p.video && p.video.length > 0;
+ const voiceOptions = [
+ {id:'hoaimy', label:'🎙️ Nữ — Hoài My'},
+ {id:'namminh', label:'🎙️ Nam — Nam Minh'},
+ ];
+ let voiceSelector = '';
+ if(!hasVideo){
+ voiceSelector = `🎙️ Chọn giọng đọc:
`;
+ voiceOptions.forEach(v=>{
+ voiceSelector += `${v.label} `;
});
- const fakePost = {
- id: 'preview-fake', title: document.getElementById('preview-title')?.value || 'Bài quan điểm',
- slides: slides, kind: 'personal_opinion', img: slides[0]?.image||'', url: '', video: ''
- };
- if(typeof openSlideDesigner === 'function'){
- // designer expects index into _wallPosts; temporarily append so designer works
- const existed = _wallPosts && _wallPosts.some(p => p.id === 'preview-fake');
- if(!existed && Array.isArray(_wallPosts)) _wallPosts.unshift(fakePost);
- const idx = _wallPosts.findIndex(p => p.id === 'preview-fake');
- openSlideDesigner(idx >= 0 ? idx : 0);
- // the fake post is only a designer context; the designer's own save posts
- // the designed slides directly to the wall. Remove the fake entry on close.
+ voiceSelector += `
Tốc độ: 1.0x — Bình thường 1.2x — Nhanh 1.5x — Rất nhanh 0.8x — Chậm
`;
+ voiceSelector += `
`;
+ }
+ document.getElementById('view-article').innerHTML=`← Quay lại AI ${esc(p.title)} ${imgGallery}
${esc(p.text)}
${hasVideo?`
`:''}
${hasVideo?`🎬 Xem Short ${voiceSelector}🔄 Tạo lại Short `:`${voiceSelector}🎬 Tạo Video Shorts `}📤
`;
+ const firstVoiceBtn = document.querySelector('.tts-voice-btn');
+ if(firstVoiceBtn) firstVoiceBtn.classList.add('active');
+ window.scrollTo(0,0)}
+async function loadNewsTab(){const el=document.getElementById('view-cat');el.innerHTML='Đang tải...
';try{const r=await fetch('/api/homepage');const news=await r.json();if(!news.length){el.innerHTML='Không có tin
';return}const groups={};news.forEach(a=>{if(!groups[a.group])groups[a.group]=[];groups[a.group].push(a)});let h='';for(const[g,arts]of Object.entries(groups)){h+=`${g}
`;arts.slice(0,6).forEach(a=>{h+=`
${a.img?`
`:''}
${esc(a.source||'VnE')} ${esc(a.title)}
`});h+='
'}el.innerHTML=h}catch(e){el.innerHTML='Lỗi
'}}
+async function loadCat(id){const el=document.getElementById('view-cat');el.innerHTML='Đang tải...
';const arts=await fetch('/api/category/'+id).then(r=>r.json()).catch(()=>[]);if(!arts.length){el.innerHTML='Không có tin
';return}let h='';arts.forEach(a=>{h+=`
${a.img?`
`:''}
${esc(a.source||'')} ${esc(a.title)}
`});h+='
';el.innerHTML=h}
+fetch('/api/storage_status').then(r=>r.json()).then(j=>{if(!j.persistent){const home=document.getElementById('view-home');if(home){const w=document.createElement('div');w.className='storage-warn';w.innerHTML='⚠️ Persistent Storage chưa bật.';home.prepend(w)}}}).catch(()=>{});
+
+(function(){
+ try{
+ var hash = window.location.hash;
+ if(hash && hash.length > 1){
+ var articleUrl = decodeURIComponent(hash.substring(1));
+ if(articleUrl.startsWith('http')){
+ history.replaceState(null, '', window.location.pathname);
setTimeout(function(){
- const ov = document.getElementById('slide-designer-overlay');
- if(!ov && Array.isArray(_wallPosts)){
- _wallPosts = _wallPosts.filter(p => p.id !== 'preview-fake');
- }
- }, 30000);
- } else {
- toast('Không có công cụ thiết kế!');
+ if(typeof readArticle==='function') readArticle(articleUrl);
+ }, 1500);
+ }
}
-}
-
-function toggleSourceSelection(el, idx) {
- const checkbox = el.querySelector('.source-checkbox');
- checkbox.checked = !checkbox.checked;
- el.style.border = checkbox.checked ? '1px solid #2d8659' : '1px solid transparent';
- el.style.background = checkbox.checked ? '#1a2a1f' : '#202020';
-}
+ }catch(e){}
+})();
-async function publishPersonalPostFromModal() {
- const title = document.getElementById('preview-title')?.value.trim();
- if (!title) {
- alert('Vui lòng nhập tiêu đề');
- return;
- }
-
- // Collect edited slides (text + designed/bg image from _pvState)
- const slides = [];
- if (window._pvState && window._pvState.slides) {
- window._pvState.slides.forEach((s, i) => {
- const text = (s.text || '').trim();
- if (text) slides.push({ text, image: s.bg || '', index: slides.length + 1 });
- });
- } else {
- document.querySelectorAll('.preview-slide-text').forEach(ta => {
- const text = ta.value.trim();
- if (text) slides.push({ text, image: '', index: slides.length + 1 });
- });
- }
-
- // Collect selected sources
- const sources = [];
- document.querySelectorAll('[data-source-idx]').forEach((el, i) => {
- const checkbox = el.querySelector('.source-checkbox');
- if (checkbox.checked && window._personalPostPreview?.sources?.[i]) {
- sources.push(window._personalPostPreview.sources[i]);
- }
- });
-
- const btn = event.target;
- const origText = btn.textContent;
- btn.disabled = true;
- btn.textContent = '⏳ Đang đăng...';
-
- try {
- const resp = await fetch('/api/personal_post', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- opinion: window._personalPostOpinion,
- selected_topics: [],
- selected_sources: sources,
- custom_title: title,
- custom_slides: slides
- })
- });
- const data = await resp.json();
- if (!resp.ok || data.error) throw new Error(data.error || 'Lỗi đăng bài');
-
- toast('✅ Đã đăng bài quan điểm lên Tường AI!');
-
- // Prepend to wall
- if (data.post && typeof prependWallPost === 'function') {
- prependWallPost(data.post);
- }
-
- // Close modal
- document.getElementById('personal-post-modal')?.remove();
-
- // Clear opinion input
- document.getElementById('opinion-input').value = '';
- } catch (e) {
- toast('❌ ' + e.message);
- } finally {
- btn.disabled = false;
- btn.textContent = origText;
+(function(){
+ try{
+ const pa=localStorage.getItem('pending_article');
+ const pv=localStorage.getItem('pending_video');
+ if(pa){
+ localStorage.removeItem('pending_article');
+ setTimeout(()=>{
+ if(typeof readArticle==='function') readArticle(pa);
+ },1500);
+ }
+ if(pv){
+ localStorage.removeItem('pending_video');
+ try{
+ const v=JSON.parse(pv);
+ if(v&&v.url) setTimeout(()=>{window.open(v.url,'_blank')},1500);
+ }catch(e){}
}
-}
+ }catch(e){}
+})();
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', function() {
+ if (typeof loadHome === 'function') loadHome();
+ });
+} else {
+ if (typeof loadHome === 'function') loadHome();
+}