Created
December 8, 2025 13:14
-
-
Save bangdragon/fecfcf3bdbb5d124dc43b420cb2954e3 to your computer and use it in GitHub Desktop.
list view js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| document.addEventListener("DOMContentLoaded", function () { | |
| if (!document.body.classList.contains("list-view")) return; | |
| // ========== CACHE MANAGER (IN-MEMORY ONLY) ========== | |
| const CACHE_VERSION = 'v3'; | |
| const imageCache = new Map(); | |
| const postCache = { | |
| maxSize: 15, | |
| cache: new Map(), | |
| get(url) { | |
| return this.cache.get(url) || null; | |
| }, | |
| set(url, data) { | |
| const keys = Array.from(this.cache.keys()); | |
| if (keys.length >= this.maxSize) { | |
| const oldest = keys[0]; | |
| this.cache.delete(oldest); | |
| } | |
| this.cache.set(url, data); | |
| }, | |
| clear() { | |
| this.cache.clear(); | |
| } | |
| }; | |
| // ========== FETCH POST DATA ========== | |
| async function fetchPostData(url) { | |
| const cached = postCache.get(url); | |
| if (cached) return cached; | |
| try { | |
| const res = await fetch(url); | |
| const html = await res.text(); | |
| const parser = new DOMParser(); | |
| const doc = parser.parseFromString(html, 'text/html'); | |
| const images = []; | |
| const separators = doc.querySelectorAll('.separator a[href]'); | |
| separators.forEach(link => { | |
| let imgUrl = link.href; | |
| if (imgUrl && !imgUrl.includes('blogger.googleusercontent.com/tracker')) { | |
| images.push(imgUrl); | |
| } | |
| }); | |
| const postBody = doc.querySelector('.post-body'); | |
| let textContent = ''; | |
| if (postBody) { | |
| const clone = postBody.cloneNode(true); | |
| clone.querySelectorAll('img, .separator').forEach(el => el.remove()); | |
| textContent = clone.innerHTML; | |
| } | |
| let commentsUrl = null; | |
| const commentsFrame = doc.querySelector('iframe[src*="blogger.com/comment"]'); | |
| if (commentsFrame) { | |
| commentsUrl = commentsFrame.src; | |
| } | |
| if (!commentsUrl) { | |
| const scripts = doc.querySelectorAll('script'); | |
| scripts.forEach(script => { | |
| const content = script.textContent; | |
| if (content.includes('commentIframeUrl')) { | |
| const match = content.match(/commentIframeUrl["'\s:]+([^"']+)/); | |
| if (match) commentsUrl = match[1]; | |
| } | |
| }); | |
| } | |
| if (!commentsUrl) { | |
| const blogIdMatch = html.match(/blogId[=:"'\s]+(\d+)/); | |
| const postIdMatch = html.match(/postId[=:"'\s]+(\d+)/); | |
| if (blogIdMatch && postIdMatch) { | |
| commentsUrl = `https://www.blogger.com/comment-iframe.g?blogID=${blogIdMatch[1]}&postID=${postIdMatch[1]}`; | |
| } | |
| } | |
| const data = { | |
| images, | |
| textContent, | |
| commentsUrl | |
| }; | |
| postCache.set(url, data); | |
| return data; | |
| } catch (e) { | |
| console.error('Fetch error:', e); | |
| return { images: [], textContent: '', commentsUrl: null }; | |
| } | |
| } | |
| // ========== PRELOAD IMAGES ========== | |
| function preloadImages(urls) { | |
| urls.forEach(url => { | |
| if (!imageCache.has(url)) { | |
| const img = new Image(); | |
| img.src = url; | |
| imageCache.set(url, img); | |
| } | |
| }); | |
| } | |
| // ========== HISTORY MANAGER ========== | |
| const historyManager = { | |
| state: { drawer: null, gallery: false }, | |
| push(type) { | |
| if (type === 'gallery') { | |
| this.state.gallery = true; | |
| history.pushState({ galleryOpen: true }, ''); | |
| } else if (type === 'drawer') { | |
| this.state.drawer = type; | |
| history.pushState({ drawerOpen: type }, ''); | |
| } | |
| }, | |
| pop() { | |
| if (this.state.drawer) { | |
| closeDrawer(); | |
| this.state.drawer = null; | |
| return true; | |
| } | |
| if (this.state.gallery) { | |
| closeGallery(); | |
| this.state.gallery = false; | |
| return true; | |
| } | |
| return false; | |
| } | |
| }; | |
| window.addEventListener('popstate', (e) => { | |
| historyManager.pop(); | |
| }); | |
| // ========== DRAWER MANAGER ========== | |
| let currentDrawer = null; | |
| function createDrawer(type, content, postUrl) { | |
| closeDrawer(); | |
| const drawer = document.createElement('div'); | |
| drawer.className = 'custom-drawer'; | |
| const heightClass = type === 'content' ? 'drawer-90' : ''; | |
| drawer.innerHTML = ` | |
| <div class="drawer-overlay"></div> | |
| <div class="drawer-content ${heightClass}"> | |
| <div class="drawer-header"> | |
| <button class="drawer-close">✕</button> | |
| </div> | |
| <div class="drawer-body">${content}</div> | |
| </div> | |
| `; | |
| document.body.appendChild(drawer); | |
| currentDrawer = drawer; | |
| requestAnimationFrame(() => { | |
| drawer.classList.add('active'); | |
| }); | |
| const overlay = drawer.querySelector('.drawer-overlay'); | |
| const drawerContent = drawer.querySelector('.drawer-content'); | |
| const closeBtn = drawer.querySelector('.drawer-close'); | |
| let startY = 0; | |
| let currentY = 0; | |
| drawerContent.addEventListener('touchstart', (e) => { | |
| startY = e.touches[0].clientY; | |
| }, { passive: true }); | |
| drawerContent.addEventListener('touchmove', (e) => { | |
| currentY = e.touches[0].clientY; | |
| const diff = currentY - startY; | |
| if (diff > 0) { | |
| drawerContent.style.transform = `translateY(${diff}px)`; | |
| } | |
| }, { passive: true }); | |
| drawerContent.addEventListener('touchend', () => { | |
| const diff = currentY - startY; | |
| if (diff > 100) { | |
| closeDrawer(); | |
| } else { | |
| drawerContent.style.transform = ''; | |
| } | |
| }); | |
| overlay.addEventListener('click', closeDrawer); | |
| closeBtn.addEventListener('click', closeDrawer); | |
| historyManager.push('drawer'); | |
| } | |
| function closeDrawer() { | |
| if (!currentDrawer) return; | |
| currentDrawer.classList.remove('active'); | |
| setTimeout(() => { | |
| currentDrawer?.remove(); | |
| currentDrawer = null; | |
| }, 300); | |
| } | |
| // ========== LOADING INDICATOR ========== | |
| function showLoading() { | |
| const loading = document.createElement('div'); | |
| loading.id = 'gallery-loading'; | |
| loading.innerHTML = ` | |
| <div class="loading-overlay"></div> | |
| <div class="loading-spinner"> | |
| <div class="spinner"></div> | |
| <p>Đang tải...</p> | |
| </div> | |
| `; | |
| document.body.appendChild(loading); | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| #gallery-loading { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: 99999; | |
| } | |
| #gallery-loading .loading-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.8); | |
| } | |
| #gallery-loading .loading-spinner { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| text-align: center; | |
| } | |
| #gallery-loading .spinner { | |
| width: 24px; | |
| height: 24px; | |
| margin: 0 auto 10px; | |
| border: 3px solid rgba(255, 255, 255, 0.3); | |
| border-top-color: #fff; | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| #gallery-loading p { | |
| color: #fff; | |
| font-size: 14px; | |
| margin: 0; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .gallery-custom-ui { | |
| pointer-events: auto !important; | |
| z-index: 99999 !important; | |
| } | |
| .gallery-custom-ui .ui-btn { | |
| pointer-events: auto !important; | |
| } | |
| /* TikTok-style transition */ | |
| .lg-outer.transitioning { | |
| pointer-events: none; | |
| } | |
| .next-post-preview { | |
| position: fixed; | |
| top: 100%; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: #000; | |
| z-index: 99998; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: none; | |
| } | |
| .next-post-preview.active { | |
| top: 0; | |
| } | |
| .next-post-preview img { | |
| max-width: 90%; | |
| max-height: 90%; | |
| object-fit: contain; | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| } | |
| function hideLoading() { | |
| const loading = document.getElementById('gallery-loading'); | |
| if (loading) { | |
| loading.remove(); | |
| } | |
| } | |
| // ========== ARTICLE NAVIGATION ========== | |
| function getNextArticle(currentArticle) { | |
| const articles = Array.from(document.querySelectorAll('article')); | |
| const currentIndex = articles.indexOf(currentArticle); | |
| if (currentIndex === -1 || currentIndex === articles.length - 1) { | |
| return null; | |
| } | |
| return articles[currentIndex + 1]; | |
| } | |
| function getPrevArticle(currentArticle) { | |
| const articles = Array.from(document.querySelectorAll('article')); | |
| const currentIndex = articles.indexOf(currentArticle); | |
| if (currentIndex === -1 || currentIndex === 0) { | |
| return null; | |
| } | |
| return articles[currentIndex - 1]; | |
| } | |
| // ========== SWIPE TO CLOSE & NAVIGATE MANAGER ========== | |
| let swipeState = { | |
| startY: 0, | |
| currentY: 0, | |
| isDragging: false, | |
| startX: 0, | |
| currentX: 0, | |
| isTransitioning: false | |
| }; | |
| let nextPostPreview = null; | |
| function createNextPostPreview(nextArticle) { | |
| // Xóa preview cũ nếu có | |
| if (nextPostPreview) { | |
| nextPostPreview.remove(); | |
| } | |
| const preview = document.createElement('div'); | |
| preview.className = 'next-post-preview'; | |
| // Lấy ảnh đầu tiên từ article | |
| const firstImg = nextArticle.querySelector('img'); | |
| if (firstImg) { | |
| const img = document.createElement('img'); | |
| img.src = firstImg.src; | |
| preview.appendChild(img); | |
| } | |
| document.body.appendChild(preview); | |
| nextPostPreview = preview; | |
| return preview; | |
| } | |
| function removeNextPostPreview() { | |
| if (nextPostPreview) { | |
| nextPostPreview.remove(); | |
| nextPostPreview = null; | |
| } | |
| } | |
| async function transitionToNextPost(nextArticle) { | |
| if (swipeState.isTransitioning) return; | |
| swipeState.isTransitioning = true; | |
| const lgOuter = document.querySelector('.lg-outer'); | |
| if (lgOuter) { | |
| lgOuter.classList.add('transitioning'); | |
| } | |
| // Animation slide up | |
| if (lgOuter) { | |
| lgOuter.style.transition = 'transform 0.4s cubic-bezier(0.4, 0, 0.2, 1)'; | |
| lgOuter.style.transform = 'translateY(-100%)'; | |
| } | |
| if (nextPostPreview) { | |
| nextPostPreview.style.transition = 'transform 0.4s cubic-bezier(0.4, 0, 0.2, 1)'; | |
| nextPostPreview.style.transform = 'translateY(-100%)'; | |
| } | |
| // Đợi animation xong | |
| await new Promise(resolve => setTimeout(resolve, 400)); | |
| // Đóng gallery hiện tại | |
| removeSwipeToClose(); | |
| if (lgInstance) { | |
| lgInstance.destroy(); | |
| lgInstance = null; | |
| } | |
| const galleryEl = document.getElementById('lightgallery'); | |
| if (galleryEl) galleryEl.remove(); | |
| const customUI = document.querySelector('.gallery-custom-ui'); | |
| if (customUI) customUI.remove(); | |
| removeNextPostPreview(); | |
| // Mở gallery mới | |
| await openGallery(nextArticle); | |
| swipeState.isTransitioning = false; | |
| } | |
| function initSwipeToClose() { | |
| const lgOuter = document.querySelector('.lg-outer'); | |
| if (!lgOuter) return; | |
| const swipeOverlay = document.createElement('div'); | |
| swipeOverlay.className = 'lg-swipe-overlay'; | |
| swipeOverlay.style.cssText = ` | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: #000; | |
| opacity: 1; | |
| pointer-events: none; | |
| z-index: 1; | |
| transition: none; | |
| `; | |
| lgOuter.insertBefore(swipeOverlay, lgOuter.firstChild); | |
| const handleTouchStart = (e) => { | |
| if (swipeState.isTransitioning) return; | |
| const target = e.target; | |
| if (target.closest('.gallery-custom-ui') || target.closest('.lg-toolbar')) { | |
| return; | |
| } | |
| swipeState.startY = e.touches[0].clientY; | |
| swipeState.startX = e.touches[0].clientX; | |
| swipeState.isDragging = false; | |
| lgOuter.style.transition = 'none'; | |
| swipeOverlay.style.transition = 'none'; | |
| }; | |
| const handleTouchMove = (e) => { | |
| if (swipeState.startY === 0 || swipeState.isTransitioning) return; | |
| swipeState.currentY = e.touches[0].clientY; | |
| swipeState.currentX = e.touches[0].clientX; | |
| const diffY = swipeState.currentY - swipeState.startY; | |
| const diffX = Math.abs(swipeState.currentX - swipeState.startX); | |
| if (Math.abs(diffY) > diffX && Math.abs(diffY) > 10) { | |
| swipeState.isDragging = true; | |
| e.preventDefault(); | |
| // Vuốt xuống - đóng gallery | |
| if (diffY > 0) { | |
| const scale = Math.max(0.85, 1 - (diffY / 1000)); | |
| const opacity = Math.max(0, 1 - (diffY / 400)); | |
| lgOuter.style.transform = `translateY(${diffY}px) scale(${scale})`; | |
| swipeOverlay.style.opacity = opacity; | |
| } | |
| // Vuốt lên - chuyển bài viết tiếp theo | |
| else if (diffY < 0) { | |
| const nextArticle = getNextArticle(currentPostData.article); | |
| if (nextArticle) { | |
| // Tạo preview nếu chưa có | |
| if (!nextPostPreview) { | |
| createNextPostPreview(nextArticle); | |
| } | |
| const absY = Math.abs(diffY); | |
| const progress = Math.min(absY / window.innerHeight, 1); | |
| lgOuter.style.transform = `translateY(${diffY}px)`; | |
| // Preview slide up theo | |
| if (nextPostPreview) { | |
| const previewY = window.innerHeight - absY; | |
| nextPostPreview.style.transform = `translateY(${previewY}px)`; | |
| } | |
| } else { | |
| // Không có bài viết tiếp theo - hiệu ứng bounce nhẹ | |
| const boundedY = diffY * 0.3; | |
| lgOuter.style.transform = `translateY(${boundedY}px)`; | |
| } | |
| } | |
| } | |
| }; | |
| const handleTouchEnd = async () => { | |
| if (!swipeState.isDragging || swipeState.isTransitioning) { | |
| swipeState.startY = 0; | |
| swipeState.currentY = 0; | |
| return; | |
| } | |
| const diffY = swipeState.currentY - swipeState.startY; | |
| lgOuter.style.transition = 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease'; | |
| swipeOverlay.style.transition = 'opacity 0.3s ease'; | |
| // Vuốt xuống > 150px - đóng gallery | |
| if (diffY > 150) { | |
| lgOuter.style.transform = `translateY(100vh) scale(0.8)`; | |
| lgOuter.style.opacity = '0'; | |
| swipeOverlay.style.opacity = '0'; | |
| setTimeout(() => { | |
| closeGallery(); | |
| }, 300); | |
| } | |
| // Vuốt lên > 150px - chuyển bài viết tiếp theo | |
| else if (diffY < -150) { | |
| const nextArticle = getNextArticle(currentPostData.article); | |
| if (nextArticle) { | |
| await transitionToNextPost(nextArticle); | |
| } else { | |
| // Bounce back nếu không có bài tiếp theo | |
| lgOuter.style.transform = ''; | |
| removeNextPostPreview(); | |
| } | |
| } | |
| // Reset về vị trí ban đầu | |
| else { | |
| lgOuter.style.transform = ''; | |
| swipeOverlay.style.opacity = '1'; | |
| removeNextPostPreview(); | |
| } | |
| swipeState.startY = 0; | |
| swipeState.currentY = 0; | |
| swipeState.isDragging = false; | |
| }; | |
| lgOuter.addEventListener('touchstart', handleTouchStart, { passive: true }); | |
| lgOuter.addEventListener('touchmove', handleTouchMove, { passive: false }); | |
| lgOuter.addEventListener('touchend', handleTouchEnd); | |
| lgOuter._swipeHandlers = { | |
| touchstart: handleTouchStart, | |
| touchmove: handleTouchMove, | |
| touchend: handleTouchEnd | |
| }; | |
| } | |
| function removeSwipeToClose() { | |
| const lgOuter = document.querySelector('.lg-outer'); | |
| if (!lgOuter || !lgOuter._swipeHandlers) return; | |
| const handlers = lgOuter._swipeHandlers; | |
| lgOuter.removeEventListener('touchstart', handlers.touchstart); | |
| lgOuter.removeEventListener('touchmove', handlers.touchmove); | |
| lgOuter.removeEventListener('touchend', handlers.touchend); | |
| delete lgOuter._swipeHandlers; | |
| const swipeOverlay = lgOuter.querySelector('.lg-swipe-overlay'); | |
| if (swipeOverlay) swipeOverlay.remove(); | |
| removeNextPostPreview(); | |
| } | |
| // ========== GALLERY MANAGER ========== | |
| let lgInstance = null; | |
| let currentPostData = null; | |
| let uiVisible = false; | |
| async function openGallery(article) { | |
| const postUrl = article.querySelector('a[data-post-url]')?.dataset.postUrl; | |
| if (!postUrl) { | |
| return; | |
| } | |
| uiVisible = false; | |
| const cached = postCache.get(postUrl); | |
| if (!cached && !swipeState.isTransitioning) { | |
| showLoading(); | |
| } | |
| const postData = await fetchPostData(postUrl); | |
| if (!postData.images.length) { | |
| hideLoading(); | |
| if (!swipeState.isTransitioning) { | |
| alert('Không tìm thấy ảnh trong bài viết'); | |
| } | |
| return; | |
| } | |
| currentPostData = { ...postData, url: postUrl, article }; | |
| preloadImages(postData.images); | |
| const galleryEl = document.createElement('div'); | |
| galleryEl.id = 'lightgallery'; | |
| galleryEl.style.display = 'none'; | |
| document.body.appendChild(galleryEl); | |
| const items = postData.images.map(src => ({ | |
| src, | |
| thumb: src | |
| })); | |
| lgInstance = lightGallery(galleryEl, { | |
| dynamic: true, | |
| dynamicEl: items, | |
| thumbnail: false, | |
| download: false, | |
| counter: false, | |
| loop: true, | |
| swipeToClose: false, | |
| escKey: false | |
| }); | |
| addCustomUI(postUrl, article); | |
| requestAnimationFrame(() => { | |
| lgInstance.openGallery(0); | |
| setTimeout(() => { | |
| hideLoading(); | |
| initSwipeToClose(); | |
| }, 300); | |
| }); | |
| if (!swipeState.isTransitioning) { | |
| historyManager.push('gallery'); | |
| } | |
| } | |
| function closeGallery() { | |
| hideLoading(); | |
| removeSwipeToClose(); | |
| if (lgInstance) { | |
| lgInstance.destroy(); | |
| lgInstance = null; | |
| } | |
| const galleryEl = document.getElementById('lightgallery'); | |
| if (galleryEl) galleryEl.remove(); | |
| const customUI = document.querySelector('.gallery-custom-ui'); | |
| if (customUI) customUI.remove(); | |
| closeDrawer(); | |
| currentPostData = null; | |
| uiVisible = true; | |
| } | |
| function addCustomUI(postUrl, article) { | |
| const uiContainer = document.createElement('div'); | |
| uiContainer.className = 'gallery-custom-ui'; | |
| const reloadBtn = document.createElement('button'); | |
| reloadBtn.className = 'ui-btn ui-reload'; | |
| reloadBtn.title = 'Tải lại'; | |
| reloadBtn.innerHTML = | |
| '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>'; | |
| const commentBtn = document.createElement('button'); | |
| commentBtn.className = 'ui-btn ui-comment'; | |
| commentBtn.title = 'Bình luận'; | |
| commentBtn.innerHTML = | |
| '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>'; | |
| const linkBtn = document.createElement('a'); | |
| linkBtn.className = 'ui-btn ui-link'; | |
| linkBtn.href = postUrl; | |
| linkBtn.title = 'Mở bài viết'; | |
| linkBtn.target = "_blank"; | |
| linkBtn.innerHTML = | |
| '<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 1 0-7l2-2a5 5 0 1 1 7 7l-1.5 1.5"/><path d="M14 11a5 5 0 0 1 0 7l-2 2a5 5 0 1 1-7-7l1.5-1.5"/></svg>'; | |
| const contentBtn = document.createElement('button'); | |
| contentBtn.className = 'ui-btn ui-post-content'; | |
| contentBtn.title = 'Nội dung bài viết'; | |
| contentBtn.innerHTML = | |
| '<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 4h16v16H4z"/><path d="M8 8h8M8 12h6M8 16h4"/></svg>'; | |
| const toggleBtn = document.createElement('button'); | |
| toggleBtn.className = 'ui-btn ui-toggle-visibility'; | |
| toggleBtn.title = 'Ẩn/Hiện UI'; | |
| toggleBtn.innerHTML = | |
| '<svg class="icon-eye" width="24" style="display:none" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>' + | |
| '<svg class="icon-eye-slash" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><line x1="1" y1="1" x2="23" y2="23"></line></svg>'; | |
| uiContainer.appendChild(reloadBtn); | |
| uiContainer.appendChild(commentBtn); | |
| uiContainer.appendChild(linkBtn); | |
| uiContainer.appendChild(contentBtn); | |
| uiContainer.appendChild(toggleBtn); | |
| document.body.appendChild(uiContainer); | |
| reloadBtn.style.display = 'none'; | |
| commentBtn.style.display = 'none'; | |
| linkBtn.style.display = 'none'; | |
| contentBtn.style.display = 'none'; | |
| reloadBtn.addEventListener('click', async () => { | |
| postCache.cache.delete(postUrl); | |
| const newData = await fetchPostData(postUrl); | |
| currentPostData = { ...newData, url: postUrl, article: currentPostData.article }; | |
| alert("Đã tải lại bài viết"); | |
| }); | |
| commentBtn.addEventListener('click', () => { | |
| window.location.href = postUrl + "#comments"; | |
| }); | |
| contentBtn.addEventListener("click", () => { | |
| let html = currentPostData?.textContent?.trim() || ""; | |
| const tmp = document.createElement("div"); | |
| tmp.innerHTML = html; | |
| if (tmp.textContent.trim().length === 0) { | |
| html = `<p style="text-align:center;opacity:.6;padding:25px">Chưa có nội dung</p>`; | |
| } | |
| createDrawer("content", html); | |
| }); | |
| const iconEye = toggleBtn.querySelector('.icon-eye'); | |
| const iconEyeSlash = toggleBtn.querySelector('.icon-eye-slash'); | |
| const buttons = [reloadBtn, commentBtn, linkBtn, contentBtn]; | |
| toggleBtn.addEventListener('mousedown', (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| e.stopImmediatePropagation(); | |
| uiVisible = !uiVisible; | |
| if (uiVisible) { | |
| iconEye.style.display = 'block'; | |
| iconEyeSlash.style.display = 'none'; | |
| buttons.forEach(b => { | |
| b.style.display = 'flex'; | |
| }); | |
| } else { | |
| iconEye.style.display = 'none'; | |
| iconEyeSlash.style.display = 'block'; | |
| buttons.forEach(b => { | |
| b.style.display = 'none'; | |
| }); | |
| } | |
| return false; | |
| }, true); | |
| toggleBtn.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| e.stopImmediatePropagation(); | |
| uiVisible = !uiVisible; | |
| if (uiVisible) { | |
| iconEye.style.display = 'block'; | |
| iconEyeSlash.style.display = 'none'; | |
| buttons.forEach(b => b.style.display = 'flex'); | |
| } else { | |
| iconEye.style.display = 'none'; | |
| iconEyeSlash.style.display = 'block'; | |
| buttons.forEach(b => b.style.display = 'none'); | |
| } | |
| return false; | |
| }, true); | |
| } | |
| // ========== EVENT LISTENER ========== | |
| function attachArticleEvents() { | |
| const articles = document.querySelectorAll('article'); | |
| console.log('Found articles:', articles.length); | |
| articles.forEach(article => { | |
| if (article.dataset.galleryAttached) return; | |
| article.dataset.galleryAttached = 'true'; | |
| const links = article.querySelectorAll('a'); | |
| links.forEach(link => { | |
| if (link.href && !link.dataset.postUrl) { | |
| link.dataset.postUrl = link.href; | |
| } | |
| link.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| e.stopImmediatePropagation(); | |
| console.log('Link clicked, opening gallery...'); | |
| openGallery(article); | |
| return false; | |
| }, true); | |
| }); | |
| }); | |
| } | |
| setTimeout(attachArticleEvents, 500); | |
| const observer = new MutationObserver((mutations) => { | |
| let hasNewArticles = false; | |
| mutations.forEach(mutation => { | |
| mutation.addedNodes.forEach(node => { | |
| if (node.nodeType === 1) { | |
| if (node.tagName === 'ARTICLE' || node.querySelector('article')) { | |
| hasNewArticles = true; | |
| } | |
| } | |
| }); | |
| }); | |
| if (hasNewArticles) { | |
| console.log('New articles detected, attaching events...'); | |
| setTimeout(attachArticleEvents, 300); | |
| } | |
| }); | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment