feat: icon category grid + auto-hide seek buttons + video slide→TikTok fix
Browse files
app.py
CHANGED
|
@@ -37,6 +37,30 @@ CATEGORIES = {
|
|
| 37 |
"🇻🇳 Bóng Đá VN": "bdp::https://bongdaplus.vn/bong-da-viet-nam::Bóng Đá",
|
| 38 |
"🔄 Chuyển Nhượng": "bdp::https://bongdaplus.vn/tin-chuyen-nhuong::Bóng Đá",
|
| 39 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
HOMEPAGE_SOURCES = [
|
| 41 |
("vne","https://vnexpress.net/thoi-su","Thời Sự"),
|
| 42 |
("vne","https://vnexpress.net/the-gioi","Thế Giới"),
|
|
@@ -480,7 +504,7 @@ def render_video_carousel_html(videos):
|
|
| 480 |
link = v.get("link","#")
|
| 481 |
title = v.get("title","")
|
| 482 |
aid = make_id(link); sl = slug(title)
|
| 483 |
-
click_js = f"window.
|
| 484 |
items.append(f'''<div class="vslide-item" onclick="{click_js}">
|
| 485 |
<div class="vslide-thumb"><img src="{img}" alt="" class="bdp-lazy-img">
|
| 486 |
<div class="vslide-play">▶</div><span class="vslide-badge vslide-badge-24h">24h</span></div>
|
|
@@ -722,7 +746,7 @@ footer,.built-with{display:none!important}
|
|
| 722 |
.bdp-header h1{color:#fff;font-size:20px;margin:0;font-weight:800;text-shadow:0 2px 6px rgba(0,0,0,.4)}
|
| 723 |
.bdp-header p{color:rgba(255,255,255,.6);font-size:11px;margin:2px 0 0}
|
| 724 |
@media(min-width:768px){.bdp-header h1{font-size:26px}.bdp-header{padding:20px}}
|
| 725 |
-
.controls-row{padding:0
|
| 726 |
.controls-row>div{background:#111!important;border:none!important}
|
| 727 |
.controls-row label{color:#aaa!important;font-size:12px!important}
|
| 728 |
.controls-row .wrap{border-color:#333!important;background:#1a1a1a!important}
|
|
@@ -830,15 +854,27 @@ footer,.built-with{display:none!important}
|
|
| 830 |
.tiktok-unmute-hint{position:absolute;top:12px;right:12px;background:rgba(0,0,0,.6);color:#fff;font-size:12px;padding:6px 12px;border-radius:18px;cursor:pointer;z-index:4;backdrop-filter:blur(4px);transition:opacity .3s}
|
| 831 |
.tiktok-pause-icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:70px;height:70px;background:rgba(0,0,0,.5);border-radius:50%;color:#fff;font-size:28px;z-index:5;pointer-events:none;display:none;align-items:center;justify-content:center;line-height:70px;text-align:center}
|
| 832 |
.tiktok-slide.paused .tiktok-pause-icon{display:block}
|
| 833 |
-
.tiktok-seek-controls{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:flex;gap:40px;z-index:6;pointer-events:auto}
|
| 834 |
-
.tiktok-
|
|
|
|
| 835 |
.tiktok-seek-btn:hover{opacity:1}
|
| 836 |
.tiktok-seek-btn:active{transform:scale(.9)}
|
| 837 |
.vslide-badge{position:absolute;top:6px;left:6px;font-size:9px;padding:1px 6px;border-radius:3px;font-weight:700;z-index:2}
|
| 838 |
.vslide-badge-24h{background:#e67e22;color:#fff}
|
| 839 |
-
"""
|
| 840 |
|
| 841 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 842 |
HEAD_META = """
|
| 843 |
<meta name="description" content="Tin tức tổng hợp nhanh nhất - VnExpress, BongDaPlus, 24h">
|
| 844 |
<meta property="og:title" content="Tin Tức Việt Nam - Tổng Hợp">
|
|
@@ -882,6 +918,48 @@ window.bdpSlideScroll=function(dir,trackId){
|
|
| 882 |
if(track){track.scrollBy({left:dir*260,behavior:'smooth'});}
|
| 883 |
};
|
| 884 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 885 |
function gc(a){try{return JSON.parse(localStorage.getItem('bdp_cmt_'+a))||[];}catch(e){return[];}}
|
| 886 |
function sc(a,c){try{localStorage.setItem('bdp_cmt_'+a,JSON.stringify(c));}catch(e){}}
|
| 887 |
window.bdpRenderCmt=function(a){var l=document.getElementById('cmt-list-'+a);if(!l)return;var c=gc(a);if(!c.length){l.innerHTML='<div class="bdp-cmt-empty">Chưa có bình luận. Hãy là người đầu tiên!</div>';return;}var h='';for(var i=c.length-1;i>=0;i--){var x=c[i];h+='<div class="bdp-cmt-item"><span class="bdp-cmt-author">'+x.name+'</span><span class="bdp-cmt-date">'+x.date+'</span><div class="bdp-cmt-body">'+x.text.replace(/</g,'<').replace(/>/g,'>')+'</div></div>';}l.innerHTML=h;};
|
|
@@ -907,39 +985,32 @@ window.bdpSeek=function(btn,sec){
|
|
| 907 |
window.bdpOpenTikTok=function(url,aid){
|
| 908 |
/* Switch to Video tab and scroll TikTok feed to matching video */
|
| 909 |
try{localStorage.setItem('bdp_url_'+aid,url);}catch(e){}
|
| 910 |
-
/
|
| 911 |
-
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
setTimeout(function(){
|
| 922 |
-
var opts=document.querySelectorAll('[role="option"]');
|
| 923 |
-
opts.forEach(function(o){if(o.textContent.indexOf('Video')>-1)o.click();});
|
| 924 |
-
},200);
|
| 925 |
-
}
|
| 926 |
-
});
|
| 927 |
-
}
|
| 928 |
-
/* After switching, scroll to the matching video in TikTok feed */
|
| 929 |
-
setTimeout(function(){
|
| 930 |
var feed=document.querySelector('.tiktok-fullscreen-feed');
|
| 931 |
-
if(
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
| 939 |
-
|
|
|
|
|
|
|
| 940 |
}
|
| 941 |
-
|
| 942 |
-
},
|
| 943 |
};
|
| 944 |
window.bdpTikTokUnmute=function(hint){
|
| 945 |
var feed=hint.closest('.tiktok-fullscreen-feed')||hint.closest('.tiktok-feed');
|
|
@@ -1019,15 +1090,23 @@ function initTikTokFullscreen(container){
|
|
| 1019 |
/* Start first video after short delay for HLS init */
|
| 1020 |
setTimeout(function(){activateSlide(0);},800);
|
| 1021 |
|
| 1022 |
-
/* Tap to pause/play */
|
| 1023 |
slides.forEach(function(sl){
|
| 1024 |
var v=sl.querySelector('.tiktok-video');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1025 |
if(v){
|
| 1026 |
v.addEventListener('click',function(e){
|
| 1027 |
e.preventDefault();
|
| 1028 |
if(v.paused){tryPlay(v);sl.classList.remove('paused');}
|
| 1029 |
else{v.pause();sl.classList.add('paused');}
|
|
|
|
| 1030 |
});
|
|
|
|
| 1031 |
}
|
| 1032 |
});
|
| 1033 |
}
|
|
@@ -1049,31 +1128,54 @@ setInterval(function(){
|
|
| 1049 |
},1500);
|
| 1050 |
|
| 1051 |
var hh=window.location.hash;
|
| 1052 |
-
if(hh&&hh.startsWith('#/')){
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1053 |
}
|
| 1054 |
"""
|
| 1055 |
|
| 1056 |
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1057 |
with gr.Blocks(title="Tin Tức Việt Nam",css=CSS,head=HEAD_META,js=JS_FUNC,theme=gr.themes.Base(),fill_width=True) as demo:
|
| 1058 |
gr.HTML('<div class="bdp-header"><h1>📰 Tin Tức Việt Nam</h1><p>VnExpress · BongDaPlus · 24h · Thời sự · Thế giới · Kinh doanh · Công nghệ · Thể thao · Giải trí · Video</p></div>')
|
|
|
|
| 1059 |
article_url=gr.Textbox(value="",visible=False,elem_id="article-url-input")
|
| 1060 |
with gr.Row(elem_classes=["controls-row"]):
|
| 1061 |
-
cat=gr.Dropdown(choices=list(CATEGORIES.keys()),value="🏠 Trang Chủ (Nổi Bật)",label="Chuyên mục",scale=3,interactive=True)
|
| 1062 |
ref_btn=gr.Button("🔄 Làm mới",variant="primary",scale=1)
|
| 1063 |
-
|
| 1064 |
news_list=gr.HTML()
|
| 1065 |
article_view=gr.HTML(visible=False)
|
| 1066 |
read_btn=gr.Button("Đọc",visible=False,elem_id="btn-read-article")
|
| 1067 |
def show_article(url):
|
| 1068 |
if not url or url=="#" or len(url)<10:
|
| 1069 |
-
return gr.update(visible=True),gr.update(visible=False),gr.update(visible=False),
|
| 1070 |
-
return (gr.update(visible=False),gr.update(value=read_article(url),visible=True),gr.update(visible=
|
| 1071 |
def show_list(c):
|
| 1072 |
-
return (gr.update(value=fetch_news_list(c),visible=True),gr.update(visible=False),gr.update(visible=
|
| 1073 |
-
read_btn.click(fn=show_article,inputs=[article_url],outputs=[news_list,article_view,
|
| 1074 |
-
back_btn.click(fn=show_list,inputs=[cat],outputs=[news_list,article_view,
|
| 1075 |
-
ref_btn.click(fn=show_list,inputs=[cat],outputs=[news_list,article_view,
|
| 1076 |
-
cat.change(fn=show_list,inputs=[cat],outputs=[news_list,article_view,
|
| 1077 |
timer=gr.Timer(value=REFRESH_SECONDS,active=True)
|
| 1078 |
timer.tick(fn=fetch_news_list,inputs=cat,outputs=news_list)
|
| 1079 |
demo.load(fn=fetch_news_list,inputs=cat,outputs=news_list)
|
|
|
|
| 37 |
"🇻🇳 Bóng Đá VN": "bdp::https://bongdaplus.vn/bong-da-viet-nam::Bóng Đá",
|
| 38 |
"🔄 Chuyển Nhượng": "bdp::https://bongdaplus.vn/tin-chuyen-nhuong::Bóng Đá",
|
| 39 |
}
|
| 40 |
+
|
| 41 |
+
# Mapping for icon grid: (icon, short_label, hash_slug)
|
| 42 |
+
CAT_ICONS = [
|
| 43 |
+
("🏠","Trang Chủ","trang-chu"),
|
| 44 |
+
("🎬","Video","video"),
|
| 45 |
+
("📰","Thời Sự","thoi-su"),
|
| 46 |
+
("🌍","Thế Giới","the-gioi"),
|
| 47 |
+
("💰","Kinh Doanh","kinh-doanh"),
|
| 48 |
+
("💻","Công Nghệ","cong-nghe"),
|
| 49 |
+
("🔬","Khoa Học","khoa-hoc"),
|
| 50 |
+
("🎬","Giải Trí","giai-tri"),
|
| 51 |
+
("🏥","Sức Khỏe","suc-khoe"),
|
| 52 |
+
("🎓","Giáo Dục","giao-duc"),
|
| 53 |
+
("✈️","Du Lịch","du-lich"),
|
| 54 |
+
("⚽","Thể Thao","the-thao"),
|
| 55 |
+
("⚽","Bóng Đá QT","bong-da-qt"),
|
| 56 |
+
("🏴","Ngoại Hạng Anh","ngoai-hang-anh"),
|
| 57 |
+
("🇪🇸","La Liga","la-liga"),
|
| 58 |
+
("🏆","Champions League","champions-league"),
|
| 59 |
+
("🇻🇳","Bóng Đá VN","bong-da-vn"),
|
| 60 |
+
("🔄","Chuyển Nhượng","chuyen-nhuong"),
|
| 61 |
+
]
|
| 62 |
+
CAT_KEYS = list(CATEGORIES.keys())
|
| 63 |
+
CAT_HASH_TO_KEY = {ci[2]: CAT_KEYS[i] for i, ci in enumerate(CAT_ICONS)}
|
| 64 |
HOMEPAGE_SOURCES = [
|
| 65 |
("vne","https://vnexpress.net/thoi-su","Thời Sự"),
|
| 66 |
("vne","https://vnexpress.net/the-gioi","Thế Giới"),
|
|
|
|
| 504 |
link = v.get("link","#")
|
| 505 |
title = v.get("title","")
|
| 506 |
aid = make_id(link); sl = slug(title)
|
| 507 |
+
click_js = f"window.bdpOpenTikTok('{esc(link)}','{aid}')"
|
| 508 |
items.append(f'''<div class="vslide-item" onclick="{click_js}">
|
| 509 |
<div class="vslide-thumb"><img src="{img}" alt="" class="bdp-lazy-img">
|
| 510 |
<div class="vslide-play">▶</div><span class="vslide-badge vslide-badge-24h">24h</span></div>
|
|
|
|
| 746 |
.bdp-header h1{color:#fff;font-size:20px;margin:0;font-weight:800;text-shadow:0 2px 6px rgba(0,0,0,.4)}
|
| 747 |
.bdp-header p{color:rgba(255,255,255,.6);font-size:11px;margin:2px 0 0}
|
| 748 |
@media(min-width:768px){.bdp-header h1{font-size:26px}.bdp-header{padding:20px}}
|
| 749 |
+
.controls-row{padding:0!important;background:#111!important;height:0!important;overflow:hidden!important;margin:0!important;opacity:0;pointer-events:none;position:absolute}
|
| 750 |
.controls-row>div{background:#111!important;border:none!important}
|
| 751 |
.controls-row label{color:#aaa!important;font-size:12px!important}
|
| 752 |
.controls-row .wrap{border-color:#333!important;background:#1a1a1a!important}
|
|
|
|
| 854 |
.tiktok-unmute-hint{position:absolute;top:12px;right:12px;background:rgba(0,0,0,.6);color:#fff;font-size:12px;padding:6px 12px;border-radius:18px;cursor:pointer;z-index:4;backdrop-filter:blur(4px);transition:opacity .3s}
|
| 855 |
.tiktok-pause-icon{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:70px;height:70px;background:rgba(0,0,0,.5);border-radius:50%;color:#fff;font-size:28px;z-index:5;pointer-events:none;display:none;align-items:center;justify-content:center;line-height:70px;text-align:center}
|
| 856 |
.tiktok-slide.paused .tiktok-pause-icon{display:block}
|
| 857 |
+
.tiktok-seek-controls{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:flex;gap:40px;z-index:6;pointer-events:auto;opacity:0;transition:opacity .3s;pointer-events:none}
|
| 858 |
+
.tiktok-slide.show-controls .tiktok-seek-controls{opacity:1;pointer-events:auto}
|
| 859 |
+
.tiktok-seek-btn{background:rgba(0,0,0,.4);color:#fff;border:none;padding:8px 14px;border-radius:20px;font-size:12px;cursor:pointer;backdrop-filter:blur(4px);font-weight:600;transition:opacity .2s}
|
| 860 |
.tiktok-seek-btn:hover{opacity:1}
|
| 861 |
.tiktok-seek-btn:active{transform:scale(.9)}
|
| 862 |
.vslide-badge{position:absolute;top:6px;left:6px;font-size:9px;padding:1px 6px;border-radius:3px;font-weight:700;z-index:2}
|
| 863 |
.vslide-badge-24h{background:#e67e22;color:#fff}
|
|
|
|
| 864 |
|
| 865 |
+
/* ══ Category Icon Grid ══ */
|
| 866 |
+
.cat-grid-wrap{padding:6px 8px 2px;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;background:#111}
|
| 867 |
+
.cat-grid-wrap::-webkit-scrollbar{display:none}
|
| 868 |
+
.cat-grid{display:flex;gap:6px;min-width:max-content}
|
| 869 |
+
.cat-icon-btn{display:flex;flex-direction:column;align-items:center;justify-content:center;min-width:62px;padding:7px 4px 5px;border-radius:10px;cursor:pointer;transition:background .15s,transform .1s;background:#1a1a1a;border:1.5px solid transparent;user-select:none;-webkit-tap-highlight-color:transparent;text-decoration:none}
|
| 870 |
+
.cat-icon-btn:hover{background:#252525;transform:scale(1.04)}
|
| 871 |
+
.cat-icon-btn:active{transform:scale(.95)}
|
| 872 |
+
.cat-icon-btn.active{background:#1a3a2a;border-color:#5cb87a}
|
| 873 |
+
.cat-icon-emoji{font-size:22px;line-height:1.2}
|
| 874 |
+
.cat-icon-label{font-size:9.5px;color:#aaa;margin-top:2px;white-space:nowrap;font-weight:500;text-align:center;max-width:68px;overflow:hidden;text-overflow:ellipsis}
|
| 875 |
+
.cat-icon-btn.active .cat-icon-label{color:#5cb87a;font-weight:700}
|
| 876 |
+
@media(min-width:768px){.cat-icon-btn{min-width:72px;padding:8px 6px 6px}.cat-icon-emoji{font-size:25px}.cat-icon-label{font-size:10.5px;max-width:76px}}
|
| 877 |
+
"""
|
| 878 |
HEAD_META = """
|
| 879 |
<meta name="description" content="Tin tức tổng hợp nhanh nhất - VnExpress, BongDaPlus, 24h">
|
| 880 |
<meta property="og:title" content="Tin Tức Việt Nam - Tổng Hợp">
|
|
|
|
| 918 |
if(track){track.scrollBy({left:dir*260,behavior:'smooth'});}
|
| 919 |
};
|
| 920 |
|
| 921 |
+
/* ══ Category Icon Grid ══ */
|
| 922 |
+
window.bdpSelectCat=function(catKey,hashSlug){
|
| 923 |
+
window.location.hash='#cat/'+hashSlug;
|
| 924 |
+
/* Update active state visually */
|
| 925 |
+
document.querySelectorAll('.cat-icon-btn').forEach(function(b){
|
| 926 |
+
b.classList.toggle('active',b.getAttribute('data-cat')===catKey);
|
| 927 |
+
});
|
| 928 |
+
/* Trigger Gradio dropdown change */
|
| 929 |
+
window._bdpSetDropdown(catKey);
|
| 930 |
+
};
|
| 931 |
+
|
| 932 |
+
window._bdpSetDropdown=function(catKey){
|
| 933 |
+
var container=document.getElementById('cat-dropdown');
|
| 934 |
+
if(!container){
|
| 935 |
+
var all=document.querySelectorAll('.controls-row .wrap, .controls-row [data-testid]');
|
| 936 |
+
container=all.length?all[0].closest('[id]'):null;
|
| 937 |
+
}
|
| 938 |
+
if(!container) container=document.querySelector('.controls-row');
|
| 939 |
+
if(!container) return;
|
| 940 |
+
/* Gradio 4+ dropdown: find the input, set value, fire events */
|
| 941 |
+
var inp=container.querySelector('input');
|
| 942 |
+
if(!inp) return;
|
| 943 |
+
try{
|
| 944 |
+
var nativeSet=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
|
| 945 |
+
nativeSet.call(inp,catKey);
|
| 946 |
+
}catch(e){inp.value=catKey;}
|
| 947 |
+
inp.dispatchEvent(new Event('input',{bubbles:true}));
|
| 948 |
+
inp.dispatchEvent(new Event('change',{bubbles:true}));
|
| 949 |
+
/* Force open dropdown and click the matching option */
|
| 950 |
+
inp.focus();
|
| 951 |
+
inp.dispatchEvent(new KeyboardEvent('keydown',{key:'ArrowDown',bubbles:true}));
|
| 952 |
+
setTimeout(function(){
|
| 953 |
+
var opts=document.querySelectorAll('[role="option"], li.item');
|
| 954 |
+
for(var i=0;i<opts.length;i++){
|
| 955 |
+
if(opts[i].textContent.trim()===catKey||opts[i].innerText.trim()===catKey){
|
| 956 |
+
opts[i].click();
|
| 957 |
+
return;
|
| 958 |
+
}
|
| 959 |
+
}
|
| 960 |
+
},150);
|
| 961 |
+
};
|
| 962 |
+
|
| 963 |
function gc(a){try{return JSON.parse(localStorage.getItem('bdp_cmt_'+a))||[];}catch(e){return[];}}
|
| 964 |
function sc(a,c){try{localStorage.setItem('bdp_cmt_'+a,JSON.stringify(c));}catch(e){}}
|
| 965 |
window.bdpRenderCmt=function(a){var l=document.getElementById('cmt-list-'+a);if(!l)return;var c=gc(a);if(!c.length){l.innerHTML='<div class="bdp-cmt-empty">Chưa có bình luận. Hãy là người đầu tiên!</div>';return;}var h='';for(var i=c.length-1;i>=0;i--){var x=c[i];h+='<div class="bdp-cmt-item"><span class="bdp-cmt-author">'+x.name+'</span><span class="bdp-cmt-date">'+x.date+'</span><div class="bdp-cmt-body">'+x.text.replace(/</g,'<').replace(/>/g,'>')+'</div></div>';}l.innerHTML=h;};
|
|
|
|
| 985 |
window.bdpOpenTikTok=function(url,aid){
|
| 986 |
/* Switch to Video tab and scroll TikTok feed to matching video */
|
| 987 |
try{localStorage.setItem('bdp_url_'+aid,url);}catch(e){}
|
| 988 |
+
window.location.hash='#cat/video';
|
| 989 |
+
/* Update icon grid active state */
|
| 990 |
+
document.querySelectorAll('.cat-icon-btn').forEach(function(b){
|
| 991 |
+
b.classList.toggle('active',b.getAttribute('data-hash')==='video');
|
| 992 |
+
});
|
| 993 |
+
/* Trigger Gradio dropdown to Video Tổng Hợp */
|
| 994 |
+
window._bdpSetDropdown('\uD83C\uDFAC Video Tổng Hợp');
|
| 995 |
+
/* After content loads, scroll to the matching video in TikTok feed */
|
| 996 |
+
var attempts=0;
|
| 997 |
+
var finder=setInterval(function(){
|
| 998 |
+
attempts++;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 999 |
var feed=document.querySelector('.tiktok-fullscreen-feed');
|
| 1000 |
+
if(feed){
|
| 1001 |
+
clearInterval(finder);
|
| 1002 |
+
var slides=feed.querySelectorAll('.tiktok-slide');
|
| 1003 |
+
var targetIdx=-1;
|
| 1004 |
+
slides.forEach(function(sl,i){
|
| 1005 |
+
if(sl.getAttribute('data-aid')===aid) targetIdx=i;
|
| 1006 |
+
});
|
| 1007 |
+
if(targetIdx>=0 && slides[targetIdx]){
|
| 1008 |
+
slides[targetIdx].scrollIntoView({behavior:'smooth'});
|
| 1009 |
+
}
|
| 1010 |
+
window.scrollTo({top:0,behavior:'smooth'});
|
| 1011 |
}
|
| 1012 |
+
if(attempts>30) clearInterval(finder);
|
| 1013 |
+
},300);
|
| 1014 |
};
|
| 1015 |
window.bdpTikTokUnmute=function(hint){
|
| 1016 |
var feed=hint.closest('.tiktok-fullscreen-feed')||hint.closest('.tiktok-feed');
|
|
|
|
| 1090 |
/* Start first video after short delay for HLS init */
|
| 1091 |
setTimeout(function(){activateSlide(0);},800);
|
| 1092 |
|
| 1093 |
+
/* Tap to pause/play + show/hide seek controls */
|
| 1094 |
slides.forEach(function(sl){
|
| 1095 |
var v=sl.querySelector('.tiktok-video');
|
| 1096 |
+
var hideTimer=null;
|
| 1097 |
+
function showSeekControls(){
|
| 1098 |
+
sl.classList.add('show-controls');
|
| 1099 |
+
if(hideTimer) clearTimeout(hideTimer);
|
| 1100 |
+
hideTimer=setTimeout(function(){sl.classList.remove('show-controls');},3000);
|
| 1101 |
+
}
|
| 1102 |
if(v){
|
| 1103 |
v.addEventListener('click',function(e){
|
| 1104 |
e.preventDefault();
|
| 1105 |
if(v.paused){tryPlay(v);sl.classList.remove('paused');}
|
| 1106 |
else{v.pause();sl.classList.add('paused');}
|
| 1107 |
+
showSeekControls();
|
| 1108 |
});
|
| 1109 |
+
v.addEventListener('touchstart',function(){showSeekControls();},{passive:true});
|
| 1110 |
}
|
| 1111 |
});
|
| 1112 |
}
|
|
|
|
| 1128 |
},1500);
|
| 1129 |
|
| 1130 |
var hh=window.location.hash;
|
| 1131 |
+
if(hh&&hh.startsWith('#cat/')){
|
| 1132 |
+
/* Category hash: #cat/video, #cat/thoi-su, etc. */
|
| 1133 |
+
var catSlug=hh.slice(5);
|
| 1134 |
+
var catMap={""" + ",".join(f"'{ci[2]}':'{esc(CAT_KEYS[i])}'" for i,ci in enumerate(CAT_ICONS)) + """};
|
| 1135 |
+
if(catMap[catSlug]){
|
| 1136 |
+
setTimeout(function(){
|
| 1137 |
+
window._bdpSetDropdown(catMap[catSlug]);
|
| 1138 |
+
document.querySelectorAll('.cat-icon-btn').forEach(function(b){
|
| 1139 |
+
b.classList.toggle('active',b.getAttribute('data-hash')===catSlug);
|
| 1140 |
+
});
|
| 1141 |
+
},1500);
|
| 1142 |
+
}
|
| 1143 |
+
} else if(hh&&hh.startsWith('#/')){var ps=hh.slice(2).split('/');if(ps.length>=2){var aid=ps[ps.length-1];try{var url=localStorage.getItem('bdp_url_'+aid);if(url)setTimeout(function(){window.bdpOpen(url,aid,ps.slice(0,-1).join('/'));},2000);}catch(e){}}}
|
| 1144 |
}
|
| 1145 |
"""
|
| 1146 |
|
| 1147 |
# ══════════════════════════════════════════════════════════════════════════════
|
| 1148 |
+
def _build_cat_grid_html():
|
| 1149 |
+
"""Build the category icon grid HTML."""
|
| 1150 |
+
items = []
|
| 1151 |
+
for i, (icon, label, hslug) in enumerate(CAT_ICONS):
|
| 1152 |
+
cat_key = CAT_KEYS[i]
|
| 1153 |
+
active = " active" if i == 0 else ""
|
| 1154 |
+
click_js = f"window.bdpSelectCat('{esc(cat_key)}','{hslug}')"
|
| 1155 |
+
items.append(f'<div class="cat-icon-btn{active}" data-cat="{esc(cat_key)}" data-hash="{hslug}" onclick="{click_js}"><span class="cat-icon-emoji">{icon}</span><span class="cat-icon-label">{label}</span></div>')
|
| 1156 |
+
return f'<div class="cat-grid-wrap"><div class="cat-grid">{"".join(items)}</div></div>'
|
| 1157 |
+
|
| 1158 |
with gr.Blocks(title="Tin Tức Việt Nam",css=CSS,head=HEAD_META,js=JS_FUNC,theme=gr.themes.Base(),fill_width=True) as demo:
|
| 1159 |
gr.HTML('<div class="bdp-header"><h1>📰 Tin Tức Việt Nam</h1><p>VnExpress · BongDaPlus · 24h · Thời sự · Thế giới · Kinh doanh · Công nghệ · Thể thao · Giải trí · Video</p></div>')
|
| 1160 |
+
gr.HTML(_build_cat_grid_html())
|
| 1161 |
article_url=gr.Textbox(value="",visible=False,elem_id="article-url-input")
|
| 1162 |
with gr.Row(elem_classes=["controls-row"]):
|
| 1163 |
+
cat=gr.Dropdown(choices=list(CATEGORIES.keys()),value="🏠 Trang Chủ (Nổi Bật)",label="Chuyên mục",scale=3,interactive=True,elem_id="cat-dropdown")
|
| 1164 |
ref_btn=gr.Button("🔄 Làm mới",variant="primary",scale=1)
|
| 1165 |
+
back_btn=gr.Button("← Quay lại",variant="secondary",visible=False)
|
| 1166 |
news_list=gr.HTML()
|
| 1167 |
article_view=gr.HTML(visible=False)
|
| 1168 |
read_btn=gr.Button("Đọc",visible=False,elem_id="btn-read-article")
|
| 1169 |
def show_article(url):
|
| 1170 |
if not url or url=="#" or len(url)<10:
|
| 1171 |
+
return gr.update(visible=True),gr.update(visible=False),gr.update(visible=False),""
|
| 1172 |
+
return (gr.update(visible=False),gr.update(value=read_article(url),visible=True),gr.update(visible=True),"")
|
| 1173 |
def show_list(c):
|
| 1174 |
+
return (gr.update(value=fetch_news_list(c),visible=True),gr.update(visible=False),gr.update(visible=False))
|
| 1175 |
+
read_btn.click(fn=show_article,inputs=[article_url],outputs=[news_list,article_view,back_btn,article_url])
|
| 1176 |
+
back_btn.click(fn=show_list,inputs=[cat],outputs=[news_list,article_view,back_btn])
|
| 1177 |
+
ref_btn.click(fn=show_list,inputs=[cat],outputs=[news_list,article_view,back_btn])
|
| 1178 |
+
cat.change(fn=show_list,inputs=[cat],outputs=[news_list,article_view,back_btn])
|
| 1179 |
timer=gr.Timer(value=REFRESH_SECONDS,active=True)
|
| 1180 |
timer.tick(fn=fetch_news_list,inputs=cat,outputs=news_list)
|
| 1181 |
demo.load(fn=fetch_news_list,inputs=cat,outputs=news_list)
|