bep40 commited on
Commit
d930ad2
·
verified ·
1 Parent(s): 2f6c478

Nâng lên 5 chủ đề HOT (AI Thế Giới + AI Việt Nam + top 3) + lazy load more button

Browse files

• hot_multi.js v3: lấy 5 chủ đề (prepend AI Thế Giới, AI Việt Nam + top 3 hot) thay vì 3
• Thêm nút 'Tải thêm' lazy loading dưới danh sách tin HOT
• Thêm auto_update.js vào index_v2.html
• Gộp logic AI_TOPICS từ hot_ai_prepend.js vào hot_multi.js

Files changed (2) hide show
  1. static/hot_multi.js +88 -16
  2. static/index_v2.html +2 -1
static/hot_multi.js CHANGED
@@ -1,17 +1,34 @@
1
  // === OVERRIDE: loadHotTopics loads from MULTIPLE hashtags ===
 
2
  // This file loaded AFTER app_v2.js, overrides the function
3
 
 
 
 
 
 
4
  async function loadHotTopics(){
5
  const j=await fetch('/api/hot_topics').then(r=>r.json()).catch(()=>({topics:[]}));
6
  const el=document.getElementById('hot-topics');if(!el)return;
7
  const topics=j.topics||[];
8
- el.innerHTML=topics.slice(0,18).map(t=>{
 
 
 
 
 
 
 
 
9
  const topicText=t.topic||t.label.replace(/^#/,'');
10
  return`<button class="hot-chip" onclick="searchTopic('${topicText.replace(/'/g,"\\'")}')">${esc(t.label)}</button>`;
11
  }).join('');
12
- // Load tin HOT = tổng hợp từ TOP 3 hashtag nóng nhất
13
- if(topics.length>=2){
14
- loadMultiHashtag(topics.slice(0,3).map(t=>t.topic||t.label.replace(/^#/,'')));
 
 
 
15
  }else if(topics.length){
16
  searchTopic(topics[0].topic||topics[0].label.replace(/^#/,''));
17
  }
@@ -19,6 +36,9 @@ async function loadHotTopics(){
19
 
20
  async function loadMultiHashtag(topicList){
21
  const box=document.getElementById('hashtag-box');if(!box)return;
 
 
 
22
  box.innerHTML=`<div class="hashtag-sources"><h3>🔥 Tin HOT tổng hợp</h3><div class="hashtag-loading"><div class="hashtag-spinner"></div>Tổng hợp từ ${topicList.length} chủ đề nóng nhất...</div></div>`;
23
  try{
24
  const results=await Promise.all(topicList.map(topic=>
@@ -33,17 +53,69 @@ async function loadMultiHashtag(topicList){
33
  if(src&&src.url&&!seen.has(src.url)){seen.add(src.url);src._topic=topicList[j];all.push(src);}
34
  }
35
  }
36
- if(!all.length){box.innerHTML=`<div class="hashtag-sources"><h3>🔥 Tin HOT</h3><div style="color:#888;padding:8px">Đang cập nhật...</div></div>`;return;}
37
- let h=`<div class="hashtag-sources"><h3>🔥 Tin HOT tổng hợp <span style="font-size:10px;color:#888">(${all.length} bài · ${topicList.length} chủ đề)</span></h3><div id="ht-list">`;
38
- all.slice(0,12).forEach((s,i)=>{
39
- h+=`<div class="hashtag-src-item" onclick="readArticle('${esc(s.url)}')"><div class="hashtag-src-img" id="ht-img-${i}"></div><div class="hashtag-src-text"><div class="hashtag-src-title">${esc(s.title)}</div><div class="hashtag-src-via">${esc(s.via||'')} · <span style="color:#f0c040;font-size:9px">#${esc(s._topic||'')}</span></div></div></div>`;
40
- });
41
- h+=`</div><div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:8px">`;
42
- topicList.forEach(t=>{h+=`<button class="hot-chip" onclick="searchTopic('${t.replace(/'/g,"\\'")}')" style="font-size:10px">🔍 #${esc(t)}</button>`;});
43
- h+=`</div></div>`;
44
- box.innerHTML=h;
45
- // Lazy load images
46
- all.slice(0,12).forEach((s,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-'+i);if(el)el.innerHTML=`<img src="${esc(d.og_image||d.img)}" onerror="this.style.display='none'">`;}}).catch(()=>{});});
47
- _htTopic=topicList[0];
48
  }catch(e){box.innerHTML=`<div class="hashtag-sources"><h3>🔥 Tin HOT</h3><div style="color:#e74c3c;padding:8px">Lỗi tải tin</div></div>`;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
 
1
  // === OVERRIDE: loadHotTopics loads from MULTIPLE hashtags ===
2
+ // v3: 5 topics (AI Thế Giới + AI Việt Nam + top 3 hot) + lazy load more
3
  // This file loaded AFTER app_v2.js, overrides the function
4
 
5
+ const HT_AI_TOPICS = ['AI Thế Giới', 'AI Việt Nam'];
6
+ let _htAllSources = [];
7
+ let _htTopics = [];
8
+ let _htPages = 0;
9
+
10
  async function loadHotTopics(){
11
  const j=await fetch('/api/hot_topics').then(r=>r.json()).catch(()=>({topics:[]}));
12
  const el=document.getElementById('hot-topics');if(!el)return;
13
  const topics=j.topics||[];
14
+
15
+ // Prepend AI topics that aren't already in the list
16
+ const prepend = HT_AI_TOPICS.filter(ai =>
17
+ !topics.some(t => (t.topic || '').toLowerCase().includes(ai.toLowerCase()))
18
+ ).map(ai => ({label: '#' + ai.replace(/\s+/g, ''), topic: ai, count: 0}));
19
+
20
+ const finalTopics = [...prepend, ...topics].slice(0, 18);
21
+
22
+ el.innerHTML=finalTopics.map(t=>{
23
  const topicText=t.topic||t.label.replace(/^#/,'');
24
  return`<button class="hot-chip" onclick="searchTopic('${topicText.replace(/'/g,"\\'")}')">${esc(t.label)}</button>`;
25
  }).join('');
26
+
27
+ // Load tin HOT = tổng hợp từ TOP 5 hashtag (AI Thế Giới + AI Việt Nam + top 3 hot)
28
+ const top5 = [...prepend, ...topics].slice(0, 5);
29
+ if(top5.length>=2){
30
+ _htTopics = top5.map(t=>t.topic||t.label.replace(/^#/,''));
31
+ loadMultiHashtag(_htTopics);
32
  }else if(topics.length){
33
  searchTopic(topics[0].topic||topics[0].label.replace(/^#/,''));
34
  }
 
36
 
37
  async function loadMultiHashtag(topicList){
38
  const box=document.getElementById('hashtag-box');if(!box)return;
39
+ _htAllSources = [];
40
+ _htPages = 0;
41
+
42
  box.innerHTML=`<div class="hashtag-sources"><h3>🔥 Tin HOT tổng hợp</h3><div class="hashtag-loading"><div class="hashtag-spinner"></div>Tổng hợp từ ${topicList.length} chủ đề nóng nhất...</div></div>`;
43
  try{
44
  const results=await Promise.all(topicList.map(topic=>
 
53
  if(src&&src.url&&!seen.has(src.url)){seen.add(src.url);src._topic=topicList[j];all.push(src);}
54
  }
55
  }
56
+ _htAllSources = all;
57
+ _renderHT();
 
 
 
 
 
 
 
 
 
 
58
  }catch(e){box.innerHTML=`<div class="hashtag-sources"><h3>🔥 Tin HOT</h3><div style="color:#e74c3c;padding:8px">Lỗi tải tin</div></div>`;}
59
+ }
60
+
61
+ function _renderHT(){
62
+ const box=document.getElementById('hashtag-box');if(!box)return;
63
+ const all = _htAllSources;
64
+ if(!all.length){box.innerHTML=`<div class="hashtag-sources"><h3>🔥 Tin HOT</h3><div style="color:#888;padding:8px">Đang cập nhật...</div></div>`;return;}
65
+
66
+ const perPage = 12;
67
+ const total = all.length;
68
+ const shown = Math.min((_htPages + 1) * perPage, total);
69
+ const visible = all.slice(0, shown);
70
+
71
+ let h=`<div class="hashtag-sources"><h3>🔥 Tin HOT tổng hợp <span style="font-size:10px;color:#888">(${total} bài · ${_htTopics.length} chủ đề)</span></h3><div id="ht-list">`;
72
+ visible.forEach((s,i)=>{
73
+ h+=`<div class="hashtag-src-item" onclick="readArticle('${esc(s.url)}')"><div class="hashtag-src-img" id="ht-img-${i}"></div><div class="hashtag-src-text"><div class="hashtag-src-title">${esc(s.title)}</div><div class="hashtag-src-via">${esc(s.via||'')} · <span style="color:#f0c040;font-size:9px">#${esc(s._topic||'')}</span></div></div></div>`;
74
+ });
75
+ h+=`</div><div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:8px">`;
76
+ _htTopics.forEach(t=>{h+=`<button class="hot-chip" onclick="searchTopic('${t.replace(/'/g,"\\'")}')" style="font-size:10px">🔍 #${esc(t)}</button>`;});
77
+ h+=`</div>`;
78
+ if(shown < total){
79
+ h+=`<button class="hashtag-load-more" id="ht-load-more" onclick="htLoadMore()">Tải thêm (${total - shown} tin) ▼</button>`;
80
+ }
81
+ h+=`</div>`;
82
+ box.innerHTML=h;
83
+
84
+ // Lazy load images
85
+ visible.forEach((s,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-'+i);if(el)el.innerHTML=`<img src="${esc(d.og_image||d.img)}" onerror="this.style.display='none'">`;}}).catch(()=>{});});
86
+ _htTopic = _htTopics[0];
87
+ }
88
+
89
+ async function htLoadMore(){
90
+ const btn=document.getElementById('ht-load-more');if(!btn)return;
91
+ btn.disabled=true;btn.textContent='Đang tải thêm...';
92
+ const nextPage = _htPages + 1;
93
+
94
+ try{
95
+ // Fetch next page from each topic
96
+ const results=await Promise.all(_htTopics.map(topic=>
97
+ fetch(`/api/hashtag/sources?topic=${encodeURIComponent(topic)}&page=${nextPage}`).then(r=>r.json()).catch(()=>({sources:[]}))
98
+ ));
99
+ _htPages = nextPage;
100
+
101
+ // Interleave new sources
102
+ const seen=new Set(_htAllSources.map(s=>s.url));
103
+ const mx=Math.max(...results.map(r=>(r.sources||[]).length));
104
+ const newItems=[];
105
+ for(let i=0;i<mx;i++){
106
+ for(let j=0;j<results.length;j++){
107
+ const src=(results[j].sources||[])[i];
108
+ if(src&&src.url&&!seen.has(src.url)){seen.add(src.url);src._topic=_htTopics[j];newItems.push(src);}
109
+ }
110
+ }
111
+
112
+ if(newItems.length){
113
+ _htAllSources = [..._htAllSources, ...newItems];
114
+ }
115
+
116
+ _renderHT();
117
+ }catch(e){
118
+ btn.disabled=false;btn.textContent='Tải thêm ▼';
119
+ toast('❌ Lỗi tải thêm');
120
+ }
121
  }
static/index_v2.html CHANGED
@@ -71,7 +71,8 @@ var SPACE=location.origin;
71
  <script src="/static/app_v2.js?v=20260711"></script>
72
  <script src="/static/yt_live.js"></script>
73
  <script src="/static/vtv_init.js"></script>
74
- <script src="/static/hot_multi.js?v=1"></script>
 
75
  <script src="/static/wc2026_v2.js"></script>
76
  <script src="/static/live_mode.js"></script>
77
  <script src="/static/match_detail_v6.js"></script>
 
71
  <script src="/static/app_v2.js?v=20260711"></script>
72
  <script src="/static/yt_live.js"></script>
73
  <script src="/static/vtv_init.js"></script>
74
+ <script src="/static/hot_multi.js?v=3"></script>
75
+ <script src="/static/auto_update.js"></script>
76
  <script src="/static/wc2026_v2.js"></script>
77
  <script src="/static/live_mode.js"></script>
78
  <script src="/static/match_detail_v6.js"></script>