דף html לטעינת הפורום - אולי יכול לעזור למקומות שחסום חדר"מ וכדומה
-
יכול לעזור למקומות שחסום חדר"מ וכדומה
גירסה חדשה ומשופרת
קוד הדף<!DOCTYPE html> <html lang="he" dir="rtl"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>פורום FreeIVR - ממשק מתקדם</title> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Rubik:wght@500;700;800;900&family=Assistant:wght@400;500;600;700&display=swap" rel="stylesheet"> <style> :root { --bg-color: #faf9f7; --text-color: #1c1917; --card-bg: #ffffff; --border-color: #e7e5e4; --accent: #d97706; --accent-dark: #b45309; --primary-900: #042f2e; --primary-800: #0f4c4a; --primary-700: #0e5f5c; } body { font-family: 'Assistant', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; background-color: var(--bg-color); color: var(--text-color); transition: background-color 0.25s ease, color 0.25s ease; } h1, h2, h3, h4, .font-display { font-family: 'Rubik', 'Assistant', system-ui, sans-serif; } pre, code { font-family: Consolas, Monaco, 'Courier New', Courier, monospace; direction: ltr; text-align: left; } pre { background-color: #0c1a1a; color: #e2f4f1; border-radius: 12px; padding: 20px; padding-top: 45px; overflow-x: auto; margin: 16px 0; font-size: 0.92rem; position: relative; box-shadow: inset 0 2px 4px rgba(0,0,0,0.25); } code { background-color: #f0efeb; border: 1px solid #e7e5e4; padding: 2px 6px; border-radius: 6px; font-size: 0.88rem; color: #0c1a1a; } pre code { background-color: transparent; border: none; padding: 0; color: inherit; } .post-content { word-wrap: break-word; overflow-wrap: break-word; } .post-content a { color: var(--accent-dark); text-decoration: underline; text-underline-offset: 2px; font-weight: 600; } .post-content a:hover { color: var(--accent); } .post-content img { max-width: 100%; border-radius: 10px; margin: 8px 0; } .post-content blockquote { border-right: 3px solid var(--accent); padding: 4px 14px; margin: 12px 0; background: rgba(217, 119, 6, 0.06); border-radius: 6px; color: inherit; } html { scroll-behavior: smooth; } :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; } } /* --- Spoiler / details enhancement (NodeBB spoiler plugin support) --- */ .post-content details { border: 1px dashed #a8a29e; border-radius: 10px; padding: 10px 14px; margin: 12px 0; background: rgba(0,0,0,0.02); } .post-content details summary { cursor: pointer; font-weight: 700; color: var(--accent-dark); list-style: none; display: flex; align-items: center; gap: 6px; } .post-content details summary::before { content: '👁️'; font-size: 0.85em; } .post-content details summary::-webkit-details-marker { display: none; } .post-content details[open] summary::after { content: ' (לחץ להסתרה)'; font-weight: 400; font-size: 0.75em; color: #78716c; } .post-content details:not([open]) summary::after { content: ' (לחץ לחשיפה)'; font-weight: 400; font-size: 0.75em; color: #78716c; } .spoiler-fallback { cursor: pointer; border: 1px dashed #a8a29e; border-radius: 10px; padding: 10px 14px; margin: 12px 0; background: rgba(0,0,0,0.02); position: relative; } .spoiler-fallback:not(.revealed) > * { filter: blur(6px); user-select: none; } .spoiler-fallback:not(.revealed)::after { content: '👁️ לחץ לחשיפת התוכן המוסתר'; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-weight: 700; color: var(--accent-dark); background: rgba(250,249,247,0.85); border-radius: 10px; } body.dark-mode .spoiler-fallback:not(.revealed)::after { background: rgba(28,25,23,0.85); } /* --- Skeleton loading shimmer --- */ .skeleton { background: linear-gradient(100deg, #eceae6 30%, #f6f5f2 45%, #eceae6 60%); background-size: 200% 100%; animation: shimmer 1.4s ease-in-out infinite; border-radius: 12px; } @keyframes shimmer { 0% { background-position: 150% 0; } 100% { background-position: -50% 0; } } body.dark-mode .skeleton { background: linear-gradient(100deg, #1f2937 30%, #263244 45%, #1f2937 60%); background-size: 200% 100%; } /* --- New reply badge for tracked topics --- */ .new-badge { background: var(--accent); color: white; font-weight: 800; border-radius: 999px; padding: 2px 9px; font-size: 0.72rem; box-shadow: 0 1px 3px rgba(0,0,0,0.2); } /* Dark Mode Variables & Overrides */ body.dark-mode { --bg-color: #14181a; --text-color: #f1f5f4; --card-bg: #1e2528; --border-color: #313b3e; } body.dark-mode .bg-white { background-color: #1e2528 !important; color: #f1f5f4 !important; border-color: #313b3e !important; } body.dark-mode .text-stone-800, body.dark-mode .text-stone-900, body.dark-mode .text-stone-700 { color: #f1f5f4 !important; } body.dark-mode .text-stone-500, body.dark-mode .text-stone-400 { color: #a3adae !important; } body.dark-mode .border-stone-200, body.dark-mode .border-stone-100 { border-color: #313b3e !important; } body.dark-mode .bg-stone-50, body.dark-mode .bg-stone-100 { background-color: #181f21 !important; color: #cbd5d4 !important; } body.dark-mode .divide-stone-100 > * + * { border-color: #313b3e !important; } /* Print & PDF Export Clean Styles */ @media print { header, footer, button, .no-print, #tabs-bar { display: none !important; } body { background: white !important; color: black !important; } main { max-width: 100% !important; padding: 0 !important; margin: 0 !important; } .bg-white { box-shadow: none !important; border: none !important; padding: 0 !important; margin-bottom: 20px !important; } } </style> </head> <body class="min-h-screen flex flex-col justify-between"> <div> <!-- Header --> <header class="bg-gradient-to-l from-teal-950 via-teal-900 to-cyan-950 text-white shadow-xl py-6 px-6 mb-8 border-b border-teal-800/60"> <div class="max-w-5xl mx-auto flex flex-col md:flex-row justify-between items-center gap-4"> <div class="flex items-center gap-3 cursor-pointer" onclick="loadHome()"> <div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-amber-500 to-orange-400 flex items-center justify-center shadow-lg font-display font-bold text-xl text-teal-950"> F </div> <div> <h1 class="text-2xl font-display font-extrabold tracking-tight hover:text-amber-300 transition"> פורום FreeIVR </h1> <p class="text-xs text-teal-200/80 font-medium">ממשק גלישה מתקדם ומהיר · ימות המשיח</p> </div> </div> <!-- Top Quick Actions --> <div class="flex flex-wrap items-center gap-2"> <button onclick="loadHome()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> 🏠 הבית </button> <button onclick="loadRecentPosts()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> 🔥 אחרונים </button> <button onclick="loadBookmarks()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> ⭐ מועדפים (<span id="bm-count">0</span>) </button> <button onclick="loadTracking()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> 📡 מעקב (<span id="tr-count">0</span>) </button> <button onclick="loadHistory()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> 📜 היסטוריה </button> <button onclick="toggleDarkMode()" id="dark-mode-btn" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 p-2 rounded-xl text-xs transition backdrop-blur-md cursor-pointer" title="מצב לילה / בהיר"> 🌙 </button> <div class="flex items-center bg-white/10 border border-white/20 rounded-xl overflow-hidden backdrop-blur-md"> <button onclick="changeFontSize('decrease')" class="px-2.5 py-2 hover:bg-white/20 text-xs font-bold transition cursor-pointer" title="הקטן טקסט">A-</button> <button onclick="changeFontSize('increase')" class="px-2.5 py-2 hover:bg-white/20 text-xs font-bold transition cursor-pointer" title="הגדל טקסט">A+</button> </div> </div> </div> <!-- Advanced Controls Bar (Search & Custom URL) --> <div class="max-w-5xl mx-auto mt-6 pt-6 border-t border-teal-800/60 grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="flex items-center gap-2 bg-white/10 backdrop-blur-md p-2 rounded-2xl border border-white/20"> <input type="text" id="search-input" placeholder="חיפוש נושאים בפורום..." onkeydown="if(event.key==='Enter') handleSearch()" class="bg-transparent text-white placeholder-teal-200 px-3 py-1.5 text-sm w-full focus:outline-none"> <button onclick="handleSearch()" class="bg-white text-teal-900 px-4 py-2 rounded-xl text-xs font-bold hover:bg-teal-50 transition shrink-0 cursor-pointer shadow-sm"> 🔍 חיפוש </button> </div> <div class="flex items-center gap-2 bg-white/10 backdrop-blur-md p-2 rounded-2xl border border-white/20"> <input type="text" id="url-input" placeholder="הדבק קישור לפורום כאן (לדוגמה: /post/36372)..." onkeydown="if(event.key==='Enter') handleCustomUrl()" class="bg-transparent text-white placeholder-teal-200 px-3 py-1.5 text-sm w-full focus:outline-none" dir="ltr"> <button onclick="handleCustomUrl()" class="bg-amber-500 text-white px-4 py-2 rounded-xl text-xs font-bold hover:bg-amber-600 transition shrink-0 cursor-pointer shadow-sm"> 🔗 טען קישור </button> </div> </div> </header> <!-- Main Container --> <main class="max-w-5xl mx-auto px-4 sm:px-6 pb-16"> <div id="tabs-bar" class="hidden flex items-center gap-2 overflow-x-auto pb-3 mb-4 no-print"></div> <div id="main-container"> <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="relative w-14 h-14"> <div class="absolute inset-0 rounded-full border-4 border-teal-200 animate-pulse"></div> <div class="absolute inset-0 rounded-full border-4 border-teal-700 border-t-transparent animate-spin"></div> </div> <span class="text-stone-500 font-medium text-sm tracking-wide">טוען את נתוני המערכת...</span> </div> </div> </main> </div> <!-- Footer --> <footer class="bg-white border-t border-stone-200 py-6 text-center text-xs text-stone-400"> <p>ממשק מותאם אישית לקהילת ימות המשיח / FreeIVR • גרסה מתקדמת</p> </footer> <script> const scriptUrl = 'כאן יש להזין את הפריסה'; let currentCid = ''; let currentCatPage = 1; let currentFontSizeLevel = parseInt(localStorage.getItem('forum_font_size') || '16'); // Multi-Tab State Management let openTabs = []; let activeTabId = null; let currentMainView = { type: 'home', data: null }; // Infinite-scroll state for the view currently on screen let loadMoreState = null; // { kind:'topic'|'category', id, nextPage, totalPages, loading:false } let loadMoreObserver = null; document.addEventListener('DOMContentLoaded', () => { if (localStorage.getItem('forum_dark_mode') === 'true') { document.body.classList.add('dark-mode'); document.getElementById('dark-mode-btn').innerHTML = '☀️'; } updateBookmarkCount(); updateTrackingCount(); applyFontSize(); }); // --------------------------------------------------------------- // Robust fetch wrapper: timeout + automatic retry + clear errors // --------------------------------------------------------------- async function fetchJSON(url, { timeoutMs = 12000, retries = 2 } = {}) { let lastError = null; for (let attempt = 0; attempt <= retries; attempt++) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(url, { signal: controller.signal }); clearTimeout(timer); if (!response.ok) { throw new Error(`השרת החזיר שגיאה (HTTP ${response.status})`); } const data = await response.json(); if (data && data.error) { throw new Error(data.message || 'שגיאה בשרת'); } return data; } catch (err) { clearTimeout(timer); lastError = err.name === 'AbortError' ? new Error('תם הזמן הקצוב לבקשה - הפורום לא הגיב') : err; if (attempt < retries) { await new Promise(r => setTimeout(r, 500 * (attempt + 1))); } } } throw lastError || new Error('שגיאה לא ידועה'); } function errorBlock(message, retryFn) { return ` <div class="bg-white p-10 rounded-2xl shadow-sm text-center border border-stone-200"> <div class="text-3xl mb-3">⚠️</div> <p class="text-red-500 font-semibold mb-4">${escapeHtml(message)}</p> <button onclick="${retryFn}" class="bg-teal-800 hover:bg-teal-700 text-white px-5 py-2.5 rounded-xl text-sm font-semibold transition cursor-pointer"> 🔄 נסה שוב </button> </div> `; } function skeletonList(count = 4) { let html = '<div class="space-y-3 mb-6">'; for (let i = 0; i < count; i++) { html += `<div class="skeleton h-20 w-full"></div>`; } return html + '</div>'; } function loadingSpinner(label) { return ` <div class="flex flex-col justify-center items-center py-20 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-teal-700 border-t-transparent animate-spin"></div> <span class="text-stone-500 font-medium text-sm">${escapeHtml(label)}</span> </div> `; } // --------------------------------------------------------------- // Dark mode / font size // --------------------------------------------------------------- function toggleDarkMode() { document.body.classList.toggle('dark-mode'); const isDark = document.body.classList.contains('dark-mode'); localStorage.setItem('forum_dark_mode', isDark); document.getElementById('dark-mode-btn').innerHTML = isDark ? '☀️' : '🌙'; } function changeFontSize(action) { if (action === 'increase' && currentFontSizeLevel < 22) { currentFontSizeLevel += 2; } else if (action === 'decrease' && currentFontSizeLevel > 12) { currentFontSizeLevel -= 2; } localStorage.setItem('forum_font_size', currentFontSizeLevel); applyFontSize(); } function applyFontSize() { document.querySelectorAll('.post-content').forEach(el => { el.style.fontSize = currentFontSizeLevel + 'px'; }); } // --------------------------------------------------------------- // Bookmarks // --------------------------------------------------------------- function getBookmarks() { try { return JSON.parse(localStorage.getItem('forum_bookmarks') || '[]'); } catch (e) { return []; } } function toggleBookmark(tid, title) { let bookmarks = getBookmarks(); const index = bookmarks.findIndex(b => b.tid === tid); if (index > -1) { bookmarks.splice(index, 1); } else { bookmarks.unshift({ tid, title: title || 'נושא ללא כותרת', date: new Date().toLocaleDateString('he-IL') }); } localStorage.setItem('forum_bookmarks', JSON.stringify(bookmarks)); updateBookmarkCount(); } function isBookmarked(tid) { return getBookmarks().some(b => b.tid === tid); } function updateBookmarkCount() { const badge = document.getElementById('bm-count'); if (badge) badge.innerText = getBookmarks().length; } // --------------------------------------------------------------- // Topic tracking ("מעקב נושאים") - client-side only, no login needed. // We remember the postcount we last saw; when the user reopens a // tracked topic, that becomes the new baseline (implicit "read"). // --------------------------------------------------------------- function getTracking() { try { return JSON.parse(localStorage.getItem('forum_tracking') || '[]'); } catch (e) { return []; } } function isTracked(tid) { return getTracking().some(t => t.tid === tid); } function toggleTracking(tid, title, currentPostcount) { let list = getTracking(); const idx = list.findIndex(t => t.tid === tid); if (idx > -1) { list.splice(idx, 1); } else { list.unshift({ tid, title: title || 'נושא ללא כותרת', lastPostcount: currentPostcount || 0, addedAt: new Date().toLocaleDateString('he-IL') }); } localStorage.setItem('forum_tracking', JSON.stringify(list)); updateTrackingCount(); } function markTrackedAsSeen(tid, postcount) { if (postcount === undefined || postcount === null) return; let list = getTracking(); const idx = list.findIndex(t => t.tid === tid); if (idx > -1) { list[idx].lastPostcount = postcount; localStorage.setItem('forum_tracking', JSON.stringify(list)); } } function updateTrackingCount() { const badge = document.getElementById('tr-count'); if (badge) badge.innerText = getTracking().length; } // --------------------------------------------------------------- // History // --------------------------------------------------------------- function addHistory(tid, title) { if (!tid) return; try { let history = JSON.parse(localStorage.getItem('forum_history') || '[]'); history = history.filter(h => h.tid !== tid); history.unshift({ tid, title: title || 'נושא ללא כותרת', time: new Date().toLocaleTimeString('he-IL', {hour: '2-digit', minute:'2-digit'}) }); if (history.length > 15) history.pop(); localStorage.setItem('forum_history', JSON.stringify(history)); } catch (e) {} } // --------------------------------------------------------------- // Shared render helpers // --------------------------------------------------------------- function jsAttr(str) { return String(str || '') .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(/"/g, '"') .replace(/\n/g, ' ') .replace(/</g, '<').replace(/>/g, '>'); } function escapeHtml(text) { if (!text) return ''; return String(text).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); } // --------------------------------------------------------------- // NodeBB stores uploaded images / emoji / attachments under paths // that are RELATIVE to the forum's own domain (e.g. "/assets/uploads/..."). // Since this app is hosted on a different origin, relative src/href // values resolve against the wrong server and silently fail to load. // This also catches spoiler text that some NodeBB configurations // leave as raw, unrendered [spoiler]...[/spoiler] bracket syntax // instead of real HTML. // --------------------------------------------------------------- function preprocessContent(html) { if (!html) return ''; let out = String(html); out = out.replace(/(src|href|poster)="\/(?!\/)([^"]*)"/gi, '$1="https://f2.freeivr.co.il/$2"'); out = out.replace(/(src|href|poster)='\/(?!\/)([^']*)'/gi, "$1='https://f2.freeivr.co.il/$2'"); out = out.replace(/srcset="([^"]*)"/gi, (m, val) => { const fixed = val.split(',').map(part => { const seg = part.trim().split(/\s+/); if (seg[0] && seg[0].startsWith('/') && !seg[0].startsWith('//')) { seg[0] = 'https://f2.freeivr.co.il' + seg[0]; } return seg.join(' '); }).join(', '); return `srcset="${fixed}"`; }); out = out.replace(/url\((['"]?)\/(?!\/)([^)'"]*)\1\)/gi, "url($1https://f2.freeivr.co.il/$2$1)"); out = out.replace(/\[spoiler(?:=([^\]]*))?\]([\s\S]*?)\[\/spoiler\]/gi, (m, title, inner) => { const label = (title || 'Spoiler').trim(); return `<details><summary>${escapeHtml(label)}</summary><div>${inner}</div></details>`; }); return out; } function timeAgo(iso) { if (!iso) return ''; const then = new Date(iso).getTime(); if (isNaN(then)) return ''; const diffMin = Math.round((Date.now() - then) / 60000); if (diffMin < 1) return 'ממש עכשיו'; if (diffMin < 60) return `לפני ${diffMin} דק'`; const diffHr = Math.round(diffMin / 60); if (diffHr < 24) return `לפני ${diffHr} שע'`; const diffDay = Math.round(diffHr / 24); return `לפני ${diffDay} ימים`; } function topicRowHTML(t, opts = {}) { const tid = t.tid; const title = t.title || 'נושא ללא כותרת'; const cid = opts.cid !== undefined ? opts.cid : (t.cid || ''); const catPage = opts.catPage || 1; const icon = opts.icon || '💬'; const meta = []; if (t.viewcount !== undefined) meta.push(`👁️ ${t.viewcount}`); if (t.postcount !== undefined) meta.push(`💬 ${t.postcount}`); let snippetHtml = ''; if (t.snippet) { const authorPart = t.authorName ? `<strong class="text-stone-600">${escapeHtml(t.authorName)}</strong> · ` : ''; const timePart = t.timestampISO ? ` · ${timeAgo(t.timestampISO)}` : ''; snippetHtml = `<p class="text-xs text-stone-400 mt-1 line-clamp-2">${authorPart}${escapeHtml(t.snippet)}${timePart}</p>`; } return ` <div class="p-4 sm:p-5 hover:bg-teal-50/50 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${tid}', '${cid}', ${catPage}, 1, true)" class="flex items-start gap-3 flex-1 cursor-pointer min-w-0"> <div class="w-9 h-9 rounded-xl bg-stone-100 group-hover:bg-teal-800 group-hover:text-white text-stone-500 flex items-center justify-center shrink-0 transition font-bold text-xs shadow-sm"> ${icon} </div> <div class="min-w-0"> <h4 class="font-bold text-stone-800 group-hover:text-teal-800 text-base transition leading-snug">${escapeHtml(title)}</h4> ${snippetHtml} </div> </div> <div class="flex items-center gap-2 self-start sm:self-center shrink-0"> <button onclick="openInTab('${tid}', '${jsAttr(title)}', '${cid}', ${catPage}, 1)" class="bg-teal-50 hover:bg-teal-100 text-teal-800 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 טאב </button> ${meta.map(m => `<span class="bg-stone-100 text-stone-600 px-3 py-1.5 rounded-lg text-xs font-medium">${m}</span>`).join('')} </div> </div> `; } // --------------------------------------------------------------- // Generic "load more" (infinite scroll + manual button + observer) // --------------------------------------------------------------- function teardownLoadMore() { if (loadMoreObserver) { loadMoreObserver.disconnect(); loadMoreObserver = null; } loadMoreState = null; } function setupLoadMore(currentPage, totalPages, isLast, loadPageFn) { teardownLoadMore(); const area = document.getElementById('load-more-area'); if (!area) return; if (isLast || currentPage >= totalPages || !totalPages) { area.innerHTML = totalPages > 1 ? `<div class="text-center text-xs text-stone-400 py-6">— הגעת לסוף הרשימה —</div>` : ''; return; } const nextPage = currentPage + 1; area.innerHTML = ` <div id="load-more-sentinel" class="text-center py-6"> <button onclick="__loadMoreClick()" class="bg-white hover:bg-stone-50 text-stone-700 border border-stone-200 px-6 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> טען עוד (עמוד ${nextPage} מתוך ${totalPages}) ⬇️ </button> </div> `; loadMoreState = { loading: false, run: () => loadPageFn(nextPage) }; const sentinel = document.getElementById('load-more-sentinel'); if (sentinel && 'IntersectionObserver' in window) { loadMoreObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) __loadMoreClick(); }); }, { rootMargin: '500px' }); loadMoreObserver.observe(sentinel); } } function __loadMoreClick() { if (!loadMoreState || loadMoreState.loading) return; loadMoreState.loading = true; const area = document.getElementById('load-more-area'); if (area) area.innerHTML = `<div class="text-center py-6"><div class="inline-block w-6 h-6 rounded-full border-4 border-teal-700 border-t-transparent animate-spin"></div></div>`; loadMoreState.run(); } // --------------------------------------------------------------- // Home // --------------------------------------------------------------- async function loadHome() { currentCid = ''; activeTabId = null; teardownLoadMore(); renderTabsBar(); currentMainView = { type: 'home' }; const container = document.getElementById('main-container'); container.innerHTML = skeletonList(6); try { const data = await fetchJSON(scriptUrl); if (data && data.categories && Array.isArray(data.categories) && data.categories.length > 0) { let html = ` <div class="mb-6"> <h2 class="text-xl font-display font-bold text-stone-800 tracking-tight">קטגוריות ראשיות</h2> <p class="text-sm text-stone-500">בחר קטגוריה לצפייה בנושאים ודיונים</p> </div> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> `; data.categories.forEach(cat => { html += ` <div onclick="loadCategory('${cat.cid}', 1)" class="group bg-white p-5 rounded-2xl shadow-sm hover:shadow-xl border border-stone-200/80 hover:border-amber-500/60 transition-all duration-300 cursor-pointer flex flex-col justify-between gap-4 relative overflow-hidden"> <div class="absolute top-0 right-0 w-1.5 h-full bg-amber-500 opacity-0 group-hover:opacity-100 transition-opacity"></div> <div class="flex items-start justify-between gap-3"> <h3 class="font-display font-bold text-stone-800 group-hover:text-teal-800 text-lg transition line-clamp-2">${escapeHtml(cat.name)}</h3> <span class="p-2.5 rounded-xl bg-teal-50 text-teal-800 group-hover:bg-teal-800 group-hover:text-white transition-all shadow-sm">📁</span> </div> <div class="flex items-center gap-2 pt-2 border-t border-stone-100 text-xs font-medium"> <span class="bg-stone-100 text-stone-600 px-3 py-1 rounded-lg">נושאים: <strong class="text-stone-800">${cat.topic_count || 0}</strong></span> <span class="bg-teal-50/70 text-teal-800 px-3 py-1 rounded-lg">הודעות: <strong class="text-teal-900">${cat.post_count || 0}</strong></span> </div> </div> `; }); html += `</div>`; container.innerHTML = html; } else { container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-stone-500 font-medium border border-stone-200">לא נמצאו קטגוריות להצגה.</div>`; } } catch (error) { console.error(error); container.innerHTML = errorBlock('שגיאה בטעינת דף הבית: ' + error.message, 'loadHome()'); } } // --------------------------------------------------------------- // Category (with infinite-scroll "load more" for topics) // --------------------------------------------------------------- async function loadCategory(cid, page = 1, append = false) { currentCid = cid; currentCatPage = page; if (!append) { activeTabId = null; teardownLoadMore(); renderTabsBar(); currentMainView = { type: 'category', cid, page }; } const container = document.getElementById('main-container'); if (!append) container.innerHTML = loadingSpinner('טוען נתונים מהקטגוריה...'); try { const data = await fetchJSON(`${scriptUrl}?cid=${encodeURIComponent(cid)}&page=${page}`); if (!append) { let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-stone-50 text-stone-700 border border-stone-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer flex items-center gap-2"> ← חזרה לדף הבית </button> <div class="flex items-center gap-2 bg-white border border-stone-200 p-1.5 rounded-xl shadow-sm"> <span class="text-xs text-stone-500 px-2 font-medium">קפוץ לעמוד:</span> <input type="number" id="jump-cat-page" min="1" value="${page}" class="w-16 bg-stone-50 border border-stone-200 text-center rounded-lg py-1 text-xs font-bold focus:outline-none" onkeydown="if(event.key==='Enter'){const p=parseInt(this.value);if(p>0) loadCategory('${cid}', p);}"> <button onclick="const p=parseInt(document.getElementById('jump-cat-page').value); if(p>0) loadCategory('${cid}', p);" class="bg-teal-800 text-white px-3 py-1 rounded-lg text-xs font-bold hover:bg-teal-700 transition cursor-pointer">מעבר</button> </div> </div> <div class="bg-gradient-to-br from-white to-stone-50 rounded-2xl shadow-sm border border-stone-200 p-6 sm:p-8 mb-6"> <h2 class="text-2xl font-display font-black text-stone-900 tracking-tight">${escapeHtml(data.name || 'קטגוריה')}</h2> </div> `; if (data.children && Array.isArray(data.children) && data.children.length > 0) { html += `<div class="mb-8"><h3 class="text-base font-bold text-stone-700 mb-3 px-1">תתי-קטגוריות</h3><div class="grid grid-cols-1 md:grid-cols-2 gap-3">`; data.children.forEach(sub => { html += ` <div onclick="loadCategory('${sub.cid}', 1)" class="group bg-white p-4 rounded-xl shadow-sm hover:shadow-md border border-stone-200/80 hover:border-amber-500 transition cursor-pointer flex items-center justify-between gap-3"> <span class="font-bold text-teal-900 group-hover:text-teal-700 text-sm flex-1">📁 ${escapeHtml(sub.name)}</span> <span class="bg-stone-100 text-stone-600 px-2.5 py-1 rounded-md font-medium text-xs">${sub.topic_count || 0} נושאים</span> </div> `; }); html += `</div></div>`; } const hasTopics = data.topics && Array.isArray(data.topics) && data.topics.length > 0; if (hasTopics) { html += `<h3 class="text-base font-bold text-stone-700 mb-3 px-1">נושאים בקטגוריה</h3>`; html += `<div id="topics-list" class="bg-white rounded-2xl shadow-sm border border-stone-200 divide-y divide-stone-100 overflow-hidden mb-6"></div>`; html += `<div id="load-more-area"></div>`; } else if (!data.children || data.children.length === 0) { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-stone-400 font-medium border border-stone-200">אין נושאים או תתי-קטגוריות בקטגוריה זו.</div>`; } container.innerHTML = html; } const listEl = document.getElementById('topics-list'); if (listEl && data.topics && Array.isArray(data.topics)) { data.topics.forEach(topic => { listEl.insertAdjacentHTML('beforeend', topicRowHTML(topic, { cid, catPage: page })); }); } const totalPages = data.pageCount || (data.pagination ? data.pagination.pageCount : 1) || 1; const isLast = !!data.isLastPage || page >= totalPages; if (document.getElementById('load-more-area')) { setupLoadMore(page, totalPages, isLast, (nextPage) => loadCategory(cid, nextPage, true)); } } catch (error) { console.error(error); if (append) { const area = document.getElementById('load-more-area'); if (area) area.innerHTML = errorBlock('שגיאה בטעינת העמוד הבא: ' + error.message, `loadCategory('${cid}', ${page}, true)`); } else { container.innerHTML = errorBlock('שגיאה בטעינת הקטגוריה: ' + error.message, `loadCategory('${cid}', ${page})`); } } } // --------------------------------------------------------------- // Recent posts (normalized items[] from the backend) // --------------------------------------------------------------- async function loadRecentPosts() { activeTabId = null; teardownLoadMore(); renderTabsBar(); currentMainView = { type: 'recent' }; const container = document.getElementById('main-container'); container.innerHTML = skeletonList(6); try { const data = await fetchJSON(`${scriptUrl}?recent=true`); const items = (data && data.items) || []; let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-stone-50 text-stone-700 border border-stone-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer">← חזרה לדף הבית</button> <h2 class="text-xl font-display font-bold text-stone-800">🔥 פוסטים ודיונים אחרונים</h2> </div> `; if (items.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-stone-200 divide-y divide-stone-100 overflow-hidden mb-6">`; items.forEach(item => { html += topicRowHTML(item, { icon: '🔥' }); }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-stone-400 font-medium border border-stone-200">לא נמצאו פוסטים אחרונים כרגע.</div>`; } container.innerHTML = html; } catch (error) { console.error(error); container.innerHTML = errorBlock('שגיאה בטעינת פוסטים אחרונים: ' + error.message, 'loadRecentPosts()'); } } // --------------------------------------------------------------- // Search (normalized items[] + Google site-search fallback) // --------------------------------------------------------------- async function handleSearch() { const query = document.getElementById('search-input').value.trim(); if (!query) return; activeTabId = null; teardownLoadMore(); renderTabsBar(); currentMainView = { type: 'search', query }; const container = document.getElementById('main-container'); container.innerHTML = loadingSpinner(`מחפש "${query}"...`); const googleFallback = `https://www.google.com/search?q=${encodeURIComponent('site:f2.freeivr.co.il ' + query)}`; try { const data = await fetchJSON(`${scriptUrl}?term=${encodeURIComponent(query)}`); const items = (data && data.items) || []; let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-stone-50 text-stone-700 border border-stone-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer">← חזרה לדף הבית</button> <h2 class="text-xl font-display font-bold text-stone-800">תוצאות חיפוש: "${escapeHtml(query)}"</h2> </div> `; if (items.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-stone-200 divide-y divide-stone-100 overflow-hidden mb-6">`; items.forEach(item => { html += topicRowHTML(item, { icon: '🔍' }); }); html += `</div>`; html += `<div class="text-center"><a href="${googleFallback}" target="_blank" rel="noopener" class="text-xs text-stone-400 hover:text-teal-700 underline">לא מוצאים את מה שחיפשתם? נסו חיפוש גוגל בתוך הפורום</a></div>`; } else { html += ` <div class="bg-white p-10 rounded-2xl shadow-sm text-center border border-stone-200"> <p class="text-stone-500 font-medium mb-4">לא נמצאו תוצאות בחיפוש הפנימי עבור "${escapeHtml(query)}".</p> <a href="${googleFallback}" target="_blank" rel="noopener" class="inline-block bg-teal-800 hover:bg-teal-700 text-white px-5 py-2.5 rounded-xl text-sm font-semibold transition">🔎 חפש בגוגל בתוך הפורום</a> </div> `; } container.innerHTML = html; } catch (error) { console.error(error); container.innerHTML = ` ${errorBlock('שגיאה בחיפוש: ' + error.message, 'handleSearch()')} <div class="text-center mt-4"> <a href="${googleFallback}" target="_blank" rel="noopener" class="inline-block bg-white hover:bg-stone-50 text-teal-800 border border-stone-200 px-5 py-2.5 rounded-xl text-sm font-semibold transition">🔎 נסה חיפוש בגוגל במקום</a> </div> `; } } // --------------------------------------------------------------- // Bookmarks // --------------------------------------------------------------- function loadBookmarks() { activeTabId = null; teardownLoadMore(); renderTabsBar(); currentMainView = { type: 'bookmarks' }; const container = document.getElementById('main-container'); const bookmarks = getBookmarks(); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-stone-50 text-stone-700 border border-stone-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer">← חזרה לדף הבית</button> <h2 class="text-xl font-display font-bold text-stone-800">הנושאים השמורים שלי (מועדפים)</h2> </div> `; if (bookmarks.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-stone-200 divide-y divide-stone-100 overflow-hidden mb-6">`; bookmarks.forEach(bm => { html += ` <div class="p-4 sm:p-5 hover:bg-teal-50/50 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${bm.tid}', '', 1, 1, false)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-amber-50 text-amber-600 flex items-center justify-center shrink-0 font-bold text-xs shadow-sm">⭐</div> <div> <h4 class="font-bold text-stone-800 group-hover:text-teal-800 text-base transition leading-snug">${escapeHtml(bm.title)}</h4> <span class="text-xs text-stone-400">נשמר בתאריך: ${bm.date}</span> </div> </div> <div class="flex items-center gap-2 self-start sm:self-center"> <button onclick="openInTab('${bm.tid}', '${jsAttr(bm.title)}', '', 1, 1)" class="bg-teal-50 hover:bg-teal-100 text-teal-800 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer">📑 פתח בטאב</button> <button onclick="toggleBookmark('${bm.tid}'); loadBookmarks();" class="bg-rose-50 hover:bg-rose-100 text-rose-600 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer">הסר</button> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-stone-400 font-medium border border-stone-200">אין עדיין נושאים שמורים במועדפים.</div>`; } container.innerHTML = html; } // --------------------------------------------------------------- // Topic tracking view // --------------------------------------------------------------- function loadTracking() { activeTabId = null; teardownLoadMore(); renderTabsBar(); currentMainView = { type: 'tracking' }; const container = document.getElementById('main-container'); const tracked = getTracking(); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-stone-50 text-stone-700 border border-stone-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer">← חזרה לדף הבית</button> <div class="flex items-center gap-3"> ${tracked.length ? `<button onclick="refreshTracking()" class="bg-teal-800 hover:bg-teal-700 text-white px-3.5 py-2 rounded-xl text-xs font-bold transition cursor-pointer">🔄 בדוק עדכונים</button>` : ''} <h2 class="text-xl font-display font-bold text-stone-800">📡 נושאים במעקב</h2> </div> </div> `; if (tracked.length === 0) { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-stone-400 font-medium border border-stone-200">אין נושאים במעקב עדיין. פתחו נושא ולחצו על "עקוב אחר נושא" כדי להתחיל לקבל התראה על תגובות חדשות.</div>`; } else { html += `<div id="tracking-list" class="bg-white rounded-2xl shadow-sm border border-stone-200 divide-y divide-stone-100 overflow-hidden mb-6">`; tracked.forEach(t => { html += ` <div class="p-4 sm:p-5 hover:bg-teal-50/50 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group" id="track-row-${t.tid}"> <div onclick="loadTopic('${t.tid}', '', 1, 1, true)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-teal-50 text-teal-800 flex items-center justify-center shrink-0 font-bold text-xs shadow-sm">📡</div> <div> <h4 class="font-bold text-stone-800 group-hover:text-teal-800 text-base transition leading-snug">${escapeHtml(t.title)}</h4> <span class="text-xs text-stone-400">נוסף למעקב בתאריך: ${t.addedAt}</span> </div> </div> <div class="flex items-center gap-2 self-start sm:self-center"> <span class="track-status text-xs text-stone-400"></span> <button onclick="toggleTracking('${t.tid}', '${jsAttr(t.title)}'); loadTracking();" class="bg-rose-50 hover:bg-rose-100 text-rose-600 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer">הסר ממעקב</button> </div> </div> `; }); html += `</div>`; } container.innerHTML = html; } async function refreshTracking() { const tracked = getTracking(); const results = await Promise.allSettled( tracked.map(t => fetchJSON(`${scriptUrl}?tid=${encodeURIComponent(t.tid)}&page=1`)) ); results.forEach((res, i) => { const t = tracked[i]; const statusEl = document.querySelector(`#track-row-${t.tid} .track-status`); if (!statusEl) return; if (res.status === 'fulfilled') { const val = res.value; const freshCount = val.postcount !== undefined ? val.postcount : (val.posts ? val.posts.length : t.lastPostcount); const diff = freshCount - (t.lastPostcount || 0); statusEl.innerHTML = diff > 0 ? `<span class="new-badge">+${diff} חדשות</span>` : `<span class="text-emerald-600 font-semibold text-xs">מעודכן ✓</span>`; } else { statusEl.innerHTML = `<span class="text-red-400">שגיאה בבדיקה</span>`; } }); } // --------------------------------------------------------------- // History // --------------------------------------------------------------- function loadHistory() { activeTabId = null; teardownLoadMore(); renderTabsBar(); currentMainView = { type: 'history' }; const container = document.getElementById('main-container'); let history = []; try { history = JSON.parse(localStorage.getItem('forum_history') || '[]'); } catch (e) {} let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-stone-50 text-stone-700 border border-stone-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer">← חזרה לדף הבית</button> <div class="flex items-center gap-3"> <button onclick="localStorage.removeItem('forum_history'); loadHistory();" class="bg-rose-50 hover:bg-rose-100 text-rose-600 border border-rose-200 px-3.5 py-2 rounded-xl text-xs font-bold transition cursor-pointer">🗑️ נקה היסטוריה</button> <h2 class="text-xl font-display font-bold text-stone-800">היסטוריית צפייה</h2> </div> </div> `; if (history.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-stone-200 divide-y divide-stone-100 overflow-hidden mb-6">`; history.forEach(item => { html += ` <div class="p-4 sm:p-5 hover:bg-teal-50/50 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${item.tid}', '', 1, 1, false)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-stone-100 group-hover:bg-teal-800 group-hover:text-white text-stone-500 flex items-center justify-center shrink-0 transition font-bold text-xs shadow-sm">📜</div> <h4 class="font-bold text-stone-800 group-hover:text-teal-800 text-base transition leading-snug">${escapeHtml(item.title)}</h4> </div> <div class="flex items-center gap-2 self-start sm:self-center"> <button onclick="openInTab('${item.tid}', '${jsAttr(item.title)}', '', 1, 1)" class="bg-teal-50 hover:bg-teal-100 text-teal-800 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer">📑 פתח בטאב</button> <span class="bg-stone-100 text-stone-600 px-3 py-1.5 rounded-lg text-xs font-medium">🕒 ${item.time}</span> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-stone-400 font-medium border border-stone-200">אין היסטוריית גלישה זמינה.</div>`; } container.innerHTML = html; } // --------------------------------------------------------------- // Tabs // --------------------------------------------------------------- function renderTabsBar() { const bar = document.getElementById('tabs-bar'); if (openTabs.length === 0) { bar.classList.add('hidden'); bar.innerHTML = ''; return; } bar.classList.remove('hidden'); let html = ''; openTabs.forEach(tab => { const isActive = tab.id === activeTabId; html += ` <div onclick="switchTab('${tab.id}')" class="shrink-0 flex items-center gap-2 pl-2 pr-3.5 py-2 rounded-xl border cursor-pointer transition text-xs font-bold ${isActive ? 'bg-teal-800 border-teal-800 text-white shadow-md' : 'bg-white border-stone-200 text-stone-600 hover:bg-stone-50'}"> <span class="max-w-[140px] truncate">${escapeHtml(tab.title)}</span> <span onclick="event.stopPropagation(); closeTab('${tab.id}')" class="w-4 h-4 rounded-full flex items-center justify-center ${isActive ? 'hover:bg-white/20' : 'hover:bg-stone-200'} transition">✕</span> </div> `; }); bar.innerHTML = html; } async function openInTab(tid, title, cid, catPage, postPage) { const tabId = 'tab_' + tid + '_' + Date.now(); openTabs.push({ id: tabId, tid, title: title || 'טוען...', cid, catPage, postPage: postPage || 1 }); activeTabId = tabId; renderTabsBar(); const container = document.getElementById('main-container'); teardownLoadMore(); container.innerHTML = loadingSpinner('פותח בטאב חדש...'); try { const data = await fetchJSON(`${scriptUrl}?tid=${encodeURIComponent(tid)}&page=${postPage || 1}`); const tab = openTabs.find(t => t.id === tabId); if (tab) tab.title = data.title || tab.title; if (activeTabId === tabId) { renderTabsBar(); addHistory(tid, data.title); if (isTracked(tid)) markTrackedAsSeen(tid, data.postcount !== undefined ? data.postcount : (data.posts ? data.posts.length : undefined)); renderTopicData(data, cid, catPage, postPage || 1, false, tid); } } catch (error) { console.error(error); if (activeTabId === tabId) { container.innerHTML = errorBlock('שגיאה בטעינת הטאב: ' + error.message, `openInTab('${tid}', '${jsAttr(title)}', '${cid}', ${catPage}, ${postPage || 1})`); } } } function switchTab(tabId) { const tab = openTabs.find(t => t.id === tabId); if (!tab) return; activeTabId = tabId; renderTabsBar(); loadTopic(tab.tid, tab.cid, tab.catPage, tab.postPage, false); } function closeTab(tabId) { const idx = openTabs.findIndex(t => t.id === tabId); if (idx === -1) return; const wasActive = activeTabId === tabId; openTabs.splice(idx, 1); renderTabsBar(); if (wasActive) { if (openTabs.length > 0) { switchTab(openTabs[openTabs.length - 1].id); } else { activeTabId = null; loadHome(); } } } // --------------------------------------------------------------- // Single post permalink (pid) - now properly resolved server-side // --------------------------------------------------------------- async function loadPost(pid) { activeTabId = null; teardownLoadMore(); renderTabsBar(); currentMainView = { type: 'post', pid }; const container = document.getElementById('main-container'); container.innerHTML = loadingSpinner('טוען את הפוסט המבוקש...'); try { const data = await fetchJSON(`${scriptUrl}?pid=${encodeURIComponent(pid)}`); if (data && Array.isArray(data.posts)) { const tid = String(data.tid || (data.posts[0] && data.posts[0].tid) || ''); addHistory(tid, data.title); if (isTracked(tid)) markTrackedAsSeen(tid, data.postcount); const postPage = (data.pagination && data.pagination.currentPage) || 1; renderTopicData(data, '', 1, postPage, false, tid); } else { container.innerHTML = errorBlock('לא ניתן היה לטעון את הפוסט המבוקש.', `loadPost('${pid}')`); } } catch (error) { console.error(error); container.innerHTML = errorBlock('שגיאה בטעינת הפוסט: ' + error.message, `loadPost('${pid}')`); } } // --------------------------------------------------------------- // Topic (with infinite-scroll "load more" for replies + highlight) // --------------------------------------------------------------- async function loadTopic(tid, cid, catPage, postPage = 1, openAsMain = false, append = false) { currentCid = cid; currentCatPage = catPage; if (!append) { if (openAsMain || activeTabId === null) { activeTabId = null; renderTabsBar(); } teardownLoadMore(); currentMainView = { type: 'topic', tid, cid, catPage, postPage }; } const container = document.getElementById('main-container'); if (!append) container.innerHTML = loadingSpinner('טוען תוכן הנושא...'); try { const data = await fetchJSON(`${scriptUrl}?tid=${encodeURIComponent(tid)}&page=${postPage}`); if (!append) { addHistory(tid, data.title); if (isTracked(tid)) { markTrackedAsSeen(tid, data.postcount !== undefined ? data.postcount : (data.posts ? data.posts.length : undefined)); } } renderTopicData(data, cid, catPage, postPage, append, tid); } catch (error) { console.error(error); if (append) { const area = document.getElementById('load-more-area'); if (area) area.innerHTML = errorBlock('שגיאה בטעינת העמוד הבא: ' + error.message, `loadTopic('${tid}', '${cid}', ${catPage}, ${postPage}, false, true)`); } else { container.innerHTML = errorBlock('שגיאה בטעינת הנושא: ' + error.message, `loadTopic('${tid}', '${cid}', ${catPage}, ${postPage})`); } } } function renderTopicData(data, cid, catPage, postPage, append, tid) { const container = document.getElementById('main-container'); const resolvedTid = String(data.tid || tid || ''); const bookmarked = isBookmarked(resolvedTid); const tracked = isTracked(resolvedTid); if (!append) { let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="${cid ? `loadCategory('${cid}', ${catPage})` : 'loadHome()'}" class="self-start bg-white hover:bg-stone-50 text-stone-700 border border-stone-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה ${cid ? 'לקטגוריה' : 'לדף הבית'} </button> <div class="flex flex-wrap items-center gap-2"> <button onclick="toggleBookmark('${resolvedTid}', '${jsAttr(data.title)}'); renderTopicToolbar('${resolvedTid}', '${jsAttr(data.title)}', ${data.postcount || 0});" id="bm-toggle-btn" class="${bookmarked ? 'bg-amber-500 text-white' : 'bg-white text-stone-700 border border-stone-200'} px-3.5 py-2 rounded-xl text-xs font-bold transition shadow-sm cursor-pointer"> ${bookmarked ? '⭐ במועדפים' : '☆ הוסף למועדפים'} </button> <button onclick="toggleTracking('${resolvedTid}', '${jsAttr(data.title)}', ${data.postcount || 0}); renderTopicToolbar('${resolvedTid}', '${jsAttr(data.title)}', ${data.postcount || 0});" id="tr-toggle-btn" class="${tracked ? 'bg-teal-800 text-white' : 'bg-white text-stone-700 border border-stone-200'} px-3.5 py-2 rounded-xl text-xs font-bold transition shadow-sm cursor-pointer"> ${tracked ? '📡 עוקב אחר הנושא' : '🔔 עקוב אחר נושא'} </button> <button onclick="window.print()" class="bg-white text-stone-700 border border-stone-200 px-3.5 py-2 rounded-xl text-xs font-bold hover:bg-stone-50 transition shadow-sm cursor-pointer"> 🖨️ הדפס / PDF </button> </div> </div> <div class="bg-gradient-to-br from-white to-stone-50 rounded-2xl shadow-sm border border-stone-200 p-6 sm:p-8 mb-6"> <h2 class="text-2xl font-display font-black text-stone-900 tracking-tight leading-snug">${escapeHtml(data.title || 'נושא')}</h2> </div> <div id="posts-list" class="space-y-5 mb-6"></div> <div id="load-more-area"></div> `; container.innerHTML = html; } const listEl = document.getElementById('posts-list'); if (listEl && data.posts && Array.isArray(data.posts)) { data.posts.forEach((post, idx) => { listEl.insertAdjacentHTML('beforeend', postCardHTML(post, idx, resolvedTid)); }); // Enhance the newly-inserted post content (spoilers, code copy buttons, etc.) const newNodes = listEl.querySelectorAll('.post-content:not([data-enhanced])'); newNodes.forEach(node => enhancePostContent(node)); applyFontSize(); } const totalPages = data.pageCount || (data.pagination ? data.pagination.pageCount : 1) || 1; const isLast = !!data.isLastPage || postPage >= totalPages; if (document.getElementById('load-more-area')) { setupLoadMore(postPage, totalPages, isLast, (nextPage) => loadTopic(resolvedTid, cid, catPage, nextPage, false, true)); } // Highlight & scroll to a specific post (used for /post/:pid deep links) if (data.highlightPid && !append) { setTimeout(() => { const target = document.getElementById('post-' + data.highlightPid); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'center' }); target.classList.add('ring-2', 'ring-amber-500', 'ring-offset-2'); setTimeout(() => target.classList.remove('ring-2', 'ring-amber-500', 'ring-offset-2'), 3000); } }, 150); } } // Refresh just the bookmark/tracking toggle buttons without a full re-render function renderTopicToolbar(tid, title, postcount) { const bmBtn = document.getElementById('bm-toggle-btn'); if (bmBtn) { const bookmarked = isBookmarked(tid); bmBtn.className = `${bookmarked ? 'bg-amber-500 text-white' : 'bg-white text-stone-700 border border-stone-200'} px-3.5 py-2 rounded-xl text-xs font-bold transition shadow-sm cursor-pointer`; bmBtn.innerHTML = bookmarked ? '⭐ במועדפים' : '☆ הוסף למועדפים'; } const trBtn = document.getElementById('tr-toggle-btn'); if (trBtn) { const tracked = isTracked(tid); trBtn.className = `${tracked ? 'bg-teal-800 text-white' : 'bg-white text-stone-700 border border-stone-200'} px-3.5 py-2 rounded-xl text-xs font-bold transition shadow-sm cursor-pointer`; trBtn.innerHTML = tracked ? '📡 עוקב אחר הנושא' : '🔔 עקוב אחר נושא'; } } function postCardHTML(post, index, tid) { const user = post.user || {}; const avatarColor = user['icon:bgColor'] || '#0e5f5c'; const avatarText = user['icon:text'] || (user.username ? user.username.charAt(0) : '?'); const displayName = user.displayname || user.username || 'אנונימי'; return ` <div id="post-${post.pid}" class="bg-white rounded-2xl shadow-sm border border-stone-200 p-5 sm:p-6 transition-shadow duration-500"> <div class="flex items-center gap-3 mb-4 pb-4 border-b border-stone-100"> <div class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold shrink-0 shadow-sm" style="background-color: ${escapeHtml(avatarColor)}"> ${escapeHtml(avatarText)} </div> <div class="min-w-0"> <p class="font-bold text-stone-800 text-sm truncate">${escapeHtml(displayName)}</p> <p class="text-xs text-stone-400">${post.timestampISO ? timeAgo(post.timestampISO) : ''} ${index === 0 ? '· פותח הנושא' : ''}</p> </div> <span class="mr-auto bg-stone-100 text-stone-500 px-2.5 py-1 rounded-lg text-xs font-mono">#${index + 1}</span> </div> <div class="post-content leading-relaxed text-[15px]">${preprocessContent(post.content)}</div> </div> `; } // --------------------------------------------------------------- // Post-content enhancement: spoiler reveal + copy-to-clipboard on // code blocks. Runs once per newly-inserted post. // --------------------------------------------------------------- function enhancePostContent(node) { node.setAttribute('data-enhanced', 'true'); // Copy-to-clipboard button on every code block node.querySelectorAll('pre').forEach(pre => { if (pre.querySelector('.copy-code-btn')) return; const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'copy-code-btn absolute top-2 left-2 bg-white/10 hover:bg-white/20 text-white text-xs px-2.5 py-1 rounded-lg transition cursor-pointer'; btn.innerText = '📋 העתק'; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const codeEl = pre.querySelector('code') || pre; navigator.clipboard.writeText(codeEl.innerText).then(() => { btn.innerText = '✓ הועתק'; setTimeout(() => { btn.innerText = '📋 העתק'; }, 1500); }).catch(() => {}); }); pre.appendChild(btn); }); // Generic spoiler support. NodeBB's spoiler plugin markup varies by // version/config, so instead of hard-coding one class name, we catch // anything whose class hints at "spoiler" and make it click-to-reveal. // Native <details>/<summary> already works via the CSS above, so we // skip anything already inside one of those. try { node.querySelectorAll('[class*="spoiler" i]').forEach(el => { if (el.closest('details')) return; if (el.dataset.spoilerReady) return; // Skip nested elements whose own ancestor already matched - // otherwise something like nbb-spoiler-wrapper > nbb-spoiler-text // would be wrapped twice (once for the wrapper, once for its child). if (el.parentElement && el.parentElement.closest('[class*="spoiler" i]')) return; el.dataset.spoilerReady = 'true'; el.classList.add('spoiler-fallback'); el.addEventListener('click', function handleReveal(e) { if (!el.classList.contains('revealed')) { e.preventDefault(); e.stopPropagation(); el.classList.add('revealed'); } }); }); } catch (e) { /* querySelectorAll with the "i" flag may not be supported everywhere - fail silently */ } } // --------------------------------------------------------------- // Custom URL loader (paste any link from the forum) // --------------------------------------------------------------- function handleCustomUrl() { const raw = document.getElementById('url-input').value.trim(); if (!raw) return; const postMatch = raw.match(/(?:^|\/)post\/(\d+)/); const tidMatch = raw.match(/(?:^|\/)topic\/(\d+)/); const cidMatch = raw.match(/(?:^|\/)category\/(\d+)/); if (postMatch) { loadPost(postMatch[1]); } else if (tidMatch) { const pageMatch = raw.match(/[?&]page=(\d+)/); const page = pageMatch ? parseInt(pageMatch[1], 10) : 1; loadTopic(tidMatch[1], '', 1, page, true); } else if (cidMatch) { loadCategory(cidMatch[1], 1); } else if (/^\d+$/.test(raw)) { loadTopic(raw, '', 1, 1, true); } else { alert('לא זוהה קישור תקין. יש להדביק קישור לנושא, לפוסט בודד או לקטגוריה מתוך הפורום.'); } document.getElementById('url-input').value = ''; } // --------------------------------------------------------------- // Internal link interception (delegated, single listener) // Handles: topic/category/post links inside post content, plus // user-mention links which now open the real forum in a new tab // instead of silently navigating the whole app away. // --------------------------------------------------------------- document.addEventListener('click', function (e) { const anchor = e.target.closest('a'); if (!anchor) return; const href = anchor.getAttribute('href'); if (!href || href.startsWith('#')) return; const postMatch = href.match(/(?:^|\/)post\/(\d+)/); const tidMatch = href.match(/(?:^|\/)topic\/(\d+)/); const cidMatch = href.match(/(?:^|\/)category\/(\d+)/); const userMatch = href.match(/(?:^|\/)(?:user|uid)\/([^\/?#]+)/); const toAbsolute = (h) => h.startsWith('http') ? h : ('https://f2.freeivr.co.il' + (h.startsWith('/') ? '' : '/') + h); if (postMatch) { e.preventDefault(); loadPost(postMatch[1]); return; } if (tidMatch) { e.preventDefault(); const pageMatch = href.match(/[?&]page=(\d+)/); const page = pageMatch ? parseInt(pageMatch[1], 10) : 1; loadTopic(tidMatch[1], currentCid, currentCatPage, page, true); return; } if (cidMatch) { e.preventDefault(); loadCategory(cidMatch[1], 1); return; } if (userMatch) { e.preventDefault(); window.open(toAbsolute(href), '_blank', 'noopener'); return; } if (href.startsWith('/') || /f2\.freeivr\.co\.il/.test(href)) { e.preventDefault(); window.open(toAbsolute(href), '_blank', 'noopener'); } // Anything else (external links the poster included) opens normally. }); // Kick things off loadHome(); </script> </body> </html>קוד סקריפט
/** * FreeIVR / ימות המשיח - פרוקסי לפורום (NodeBB) עבור ממשק ה-HTML המתקדם. * * שינויים עיקריים לעומת הגרסה הקודמת: * - תמיכה אמיתית ב-pid (קישור ישיר לפוסט בודד / ציטוטים) - הפרמטר הזה * פשוט לא טופל בכלל בגרסה הקודמת. * - ניסיונות חוזרים (retry) עם השהיה קצרה כשהשרת החיצוני נכשל באופן זמני, * כדי לצמצם כשלים אקראיים בטעינת דף הבית וכו'. * - נירמול אחיד לתוצאות "פוסטים אחרונים" ו"חיפוש": ל-NodeBB יש שתי צורות * החזרה שונות (רשימת "נושאים" שטוחה מול רשימת "פוסטים" עם topic מקונן * בפנים) - כאן הן תמיד מומרות לצורה אחידה כדי שהלקוח לא יצטרך לנחש. * - הודעות שגיאה עקביות בצורה {error:true, message:"..."} . */ var BASE_URL = 'https://f2.freeivr.co.il'; var FETCH_OPTIONS = { muteHttpExceptions: true, followRedirects: false, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } }; function doGet(e) { try { var params = (e && e.parameter) || {}; var tid = params.tid || ''; var cid = params.cid || ''; var pid = params.pid || ''; var page = params.page || ''; var recent = !!(params.recent || params.action === 'recent' || params.q === 'recent' || params.sort === 'recent'); var term = params.term || params.q || params.search || params.query || ''; var cache = CacheService.getScriptCache(); if (pid) { return handlePostRedirect(pid, cache); } if (tid) { return respondWithCache( BASE_URL + '/api/topic/' + encodeURIComponent(tid) + (page ? '?page=' + encodeURIComponent(page) : ''), 'topic_' + tid + '_p' + (page || 1), cache, false ); } if (cid) { return respondWithCache( BASE_URL + '/api/category/' + encodeURIComponent(cid) + (page ? '?page=' + encodeURIComponent(page) : ''), 'category_' + cid + '_p' + (page || 1), cache, false ); } if (recent) { return respondWithCache( BASE_URL + '/api/recent' + (page ? '?page=' + encodeURIComponent(page) : ''), 'recent_p' + (page || 1), cache, true ); } if (term) { return respondWithCache( BASE_URL + '/api/search?term=' + encodeURIComponent(term) + (page ? '&page=' + encodeURIComponent(page) : ''), 'search_' + term + '_p' + (page || 1), cache, true ); } // ברירת מחדל: דף הבית (רשימת קטגוריות) return respondWithCache(BASE_URL + '/api/', 'home_p1', cache, false); } catch (err) { return jsonOutput({ error: true, message: 'שגיאה כללית בשרת: ' + err.message }); } } /** * שולף מהמטמון אם קיים, אחרת שולף מהפורום, מנרמל אם צריך, ושומר במטמון. */ function respondWithCache(url, cacheKey, cache, normalize) { var cached = safeCacheGet(cache, cacheKey); if (cached) return jsonOutput(cached); var data = fetchJsonWithRetry(url); if (data && data.error) { return jsonOutput(data); } if (normalize) { data = { items: normalizeListItems(data) }; } safeCachePut(cache, cacheKey, data); return jsonOutput(data); } /** * מטפל בקישור ישיר לפוסט בודד (pid) - למשל ציטוטים או לינקים ל-/post/12345. * NodeBB לא מחזיק דף JSON נפרד לפוסט בודד; הוא מפנה (redirect) לעמוד המתאים * בתוך הנושא. אנחנו מנסים כמה דרכים כדי לתמוך בהתנהגויות אפשריות שונות: * 1. קריאה תחת /api/post/:pid - לפעמים מחזיר ישר את נתוני הנושא, ולפעמים * מחזיר תשובה עם path של הפניה. * 2. אם זה לא עבד - קריאה ל-/post/:pid הרגיל וקריאת כותרת ה-Location * מההפניה (301/302) ישירות, בלי לעקוב אחריה אוטומטית. * בסיום, טוענים את נתוני הנושא המלאים ומצרפים highlightPid כדי שהלקוח * יוכל לגלול/להדגיש את הפוסט הרלוונטי. */ function handlePostRedirect(pid, cache) { var cacheKey = 'post_' + pid; var cached = safeCacheGet(cache, cacheKey); if (cached) return jsonOutput(cached); var tid = ''; var page = 1; var targetPath = ''; try { var res = UrlFetchApp.fetch(BASE_URL + '/api/post/' + encodeURIComponent(pid), FETCH_OPTIONS); var code = res.getResponseCode(); if (code === 200) { var parsed = safeJsonParse(res.getContentText()); if (parsed && Array.isArray(parsed.posts)) { // כבר קיבלנו את כל נתוני הנושא ישירות parsed.highlightPid = pid; safeCachePut(cache, cacheKey, parsed); return jsonOutput(parsed); } if (parsed && parsed.tid) { tid = String(parsed.tid); page = parsed.page || 1; } else if (parsed && parsed.path) { targetPath = parsed.path; } } else if (code === 301 || code === 302) { targetPath = getLocationHeader(res); } } catch (e1) { /* ממשיכים לניסיון הבא */ } if (!tid && !targetPath) { try { var res2 = UrlFetchApp.fetch(BASE_URL + '/post/' + encodeURIComponent(pid), FETCH_OPTIONS); var code2 = res2.getResponseCode(); if (code2 === 301 || code2 === 302) { targetPath = getLocationHeader(res2); } } catch (e2) { /* אין מה לעשות יותר */ } } if (!tid && targetPath) { var tidMatch = targetPath.match(/topic\/(\d+)/); if (tidMatch) tid = tidMatch[1]; var pageMatch = targetPath.match(/[?&]page=(\d+)/); if (pageMatch) page = parseInt(pageMatch[1], 10); } if (!tid) { return jsonOutput({ error: true, message: 'לא נמצא נושא מתאים עבור הפוסט המבוקש (ID: ' + pid + ')' }); } var topicUrl = BASE_URL + '/api/topic/' + tid + (page > 1 ? '?page=' + page : ''); var data = fetchJsonWithRetry(topicUrl); if (data && !data.error) { data.highlightPid = pid; safeCachePut(cache, cacheKey, data); } return jsonOutput(data); } function getLocationHeader(response) { var headers = response.getAllHeaders() || {}; return headers['Location'] || headers['location'] || ''; } /** * שליפת JSON עם עד 3 ניסיונות והשהיה קצרה ביניהם, כדי לצמצם כשלים זמניים * (טיימאאוט / שגיאת שרת חולפת) שגורמים לתחושת "לפעמים זה נכשל". */ function fetchJsonWithRetry(url) { var attempts = 3; var lastMessage = 'שגיאה לא ידועה'; for (var i = 0; i < attempts; i++) { try { var response = UrlFetchApp.fetch(url, FETCH_OPTIONS); var code = response.getResponseCode(); if (code === 200) { var parsed = safeJsonParse(response.getContentText()); if (parsed) return parsed; lastMessage = 'הפורום החזיר תשובה שאינה JSON תקין'; } else if (code === 404) { return { error: true, message: 'התוכן המבוקש לא נמצא בפורום (404)' }; } else if (code === 301 || code === 302) { lastMessage = 'הפורום ביקש הפניה לא צפויה (HTTP ' + code + ')'; } else { lastMessage = 'שגיאת שרת חיצוני (HTTP ' + code + ')'; } } catch (err) { lastMessage = 'שגיאת רשת: ' + err.message; } if (i < attempts - 1) { Utilities.sleep(350 * (i + 1)); } } return { error: true, message: lastMessage }; } /** * ל-NodeBB יש שתי צורות עיקריות להחזרת רשימות: * - { topics: [ {tid, title, viewcount, postcount, ...} ] } (למשל קטגוריה) * - { posts: [ {tid, content, topic:{tid,title,slug}, user, ...} ] } (למשל חיפוש) * הפונקציה הזו הופכת את שתיהן לרשימה אחידה אחת כדי שהלקוח לא יצטרך לנחש. */ function normalizeListItems(raw) { var list = []; if (!raw) return list; if (Array.isArray(raw)) { list = raw; } else if (Array.isArray(raw.topics)) { list = raw.topics; } else if (Array.isArray(raw.posts)) { list = raw.posts; } var result = []; for (var i = 0; i < list.length; i++) { var item = list[i] || {}; var topic = item.topic || item; var user = item.user || (item.teaser && item.teaser.user) || null; var tid = item.tid || topic.tid; if (!tid) continue; result.push({ tid: String(tid), title: topic.title || item.title || 'נושא ללא כותרת', cid: item.cid || topic.cid || '', viewcount: (topic.viewcount !== undefined ? topic.viewcount : item.viewcount) || 0, postcount: (topic.postcount !== undefined ? topic.postcount : item.postcount) || 0, snippet: (item.content || '').toString().slice(0, 240), authorName: user ? (user.displayname || user.username || '') : '', timestampISO: item.timestampISO || item.timestamp || '' }); } return result; } function safeJsonParse(text) { try { return JSON.parse(text); } catch (e) { return null; } } function safeCacheGet(cache, key) { try { var val = cache.get(key); return val ? safeJsonParse(val) : null; } catch (e) { return null; } } function safeCachePut(cache, key, data) { try { var text = JSON.stringify(data); if (text.length < 90000) { cache.put(key, text, 180); } } catch (e) { // חורג ממגבלת הגודל של המטמון - פשוט לא שומרים, לא קריטי } } function jsonOutput(obj) { if (obj && typeof obj === 'object') { obj._backendVersion = 'v2-2026-08-02'; } return ContentService.createTextOutput(JSON.stringify(obj)) .setMimeType(ContentService.MimeType.JSON); }גירסא ישנה
דף הhtml<!DOCTYPE html> <html lang="he" dir="rtl"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>פורום FreeIVR - ממשק מתקדם</title> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> <style> body { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: var(--bg-color, #f8fafc); color: var(--text-color, #1e293b); transition: background-color 0.3s, color 0.3s; } pre, code { font-family: Consolas, Monaco, 'Courier New', Courier, monospace; direction: ltr; text-align: left; } pre { background-color: #0f172a; color: #e2e8f0; border-radius: 12px; padding: 20px; padding-top: 45px; overflow-x: auto; margin: 16px 0; font-size: 0.92rem; position: relative; box-shadow: inset 0 2px 4px rgba(0,0,0,0.2); } code { background-color: #f1f5f9; border: 1px solid #e2e8f0; padding: 2px 6px; border-radius: 6px; font-size: 0.88rem; color: #0f172a; } pre code { background-color: transparent; border: none; padding: 0; color: inherit; } .post-content a { color: #6366f1; text-decoration: underline; font-weight: 500; } .post-content a:hover { color: #4f46e5; } html { scroll-behavior: smooth; } /* Dark Mode Variables & Overrides */ body.dark-mode { --bg-color: #0f172a; --text-color: #f1f5f9; --card-bg: #1e293b; --border-color: #334155; --hover-bg: #26334d; } body.dark-mode .bg-white { background-color: #1e293b !important; color: #f1f5f9 !important; border-color: #334155 !important; } body.dark-mode .text-slate-800, body.dark-mode .text-slate-900, body.dark-mode .text-slate-700 { color: #f1f5f9 !important; } body.dark-mode .text-slate-500, body.dark-mode .text-slate-400 { color: #94a3b8 !important; } body.dark-mode .border-slate-200, body.dark-mode .border-slate-100 { border-color: #334155 !important; } body.dark-mode .bg-slate-50, body.dark-mode .bg-slate-100 { background-color: #0f172a !important; color: #cbd5e1 !important; } body.dark-mode .divide-slate-100 > * + * { border-color: #334155 !important; } /* Print & PDF Export Clean Styles */ @media print { header, footer, button, .no-print, #tabs-bar { display: none !important; } body { background: white !important; color: black !important; } main { max-width: 100% !important; padding: 0 !important; margin: 0 !important; } .bg-white { box-shadow: none !important; border: none !important; padding: 0 !important; margin-bottom: 20px !important; } } </style> </head> <body class="min-h-screen flex flex-col justify-between"> <div> <!-- Modern Header --> <header class="bg-gradient-to-r from-indigo-900 via-indigo-800 to-violet-900 text-white shadow-xl py-6 px-6 mb-8 border-b border-indigo-700/50"> <div class="max-w-5xl mx-auto flex flex-col md:flex-row justify-between items-center gap-4"> <div class="flex items-center gap-3 cursor-pointer" onclick="loadHome()"> <div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-indigo-500 to-pink-500 flex items-center justify-center shadow-lg font-bold text-xl text-white"> F </div> <div> <h1 class="text-2xl font-extrabold tracking-tight hover:text-indigo-200 transition"> פורום FreeIVR </h1> <p class="text-xs text-indigo-200/80 font-medium">ממשק גלישה מתקדם ומהיר</p> </div> </div> <!-- Top Quick Actions --> <div class="flex flex-wrap items-center gap-2"> <button onclick="loadHome()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> 🏠 הבית </button> <button onclick="loadBookmarks()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> ⭐ מועדפים (<span id="bm-count">0</span>) </button> <button onclick="loadHistory()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> 📜 היסטוריה </button> <!-- Dark Mode Toggle --> <button onclick="toggleDarkMode()" id="dark-mode-btn" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 p-2 rounded-xl text-xs transition backdrop-blur-md cursor-pointer" title="מצב לילה / בהיר"> 🌙 </button> <!-- Font Size Controls --> <div class="flex items-center bg-white/10 border border-white/20 rounded-xl overflow-hidden backdrop-blur-md"> <button onclick="changeFontSize('decrease')" class="px-2.5 py-2 hover:bg-white/20 text-xs font-bold transition cursor-pointer" title="הקטן טקסט">A-</button> <button onclick="changeFontSize('increase')" class="px-2.5 py-2 hover:bg-white/20 text-xs font-bold transition cursor-pointer" title="הגדל טקסט">A+</button> </div> </div> </div> <!-- Advanced Controls Bar (Search & Custom URL) --> <div class="max-w-5xl mx-auto mt-6 pt-6 border-t border-indigo-700/60 grid grid-cols-1 md:grid-cols-2 gap-4"> <!-- Search Box --> <div class="flex items-center gap-2 bg-white/10 backdrop-blur-md p-2 rounded-2xl border border-white/20"> <input type="text" id="search-input" placeholder="חיפוש נושאים בפורום..." onkeydown="if(event.key==='Enter') handleSearch()" class="bg-transparent text-white placeholder-indigo-200 px-3 py-1.5 text-sm w-full focus:outline-none"> <button onclick="handleSearch()" class="bg-white text-indigo-900 px-4 py-2 rounded-xl text-xs font-bold hover:bg-indigo-50 transition shrink-0 cursor-pointer shadow-sm"> 🔍 חיפוש </button> </div> <!-- Custom URL Loader Box --> <div class="flex items-center gap-2 bg-white/10 backdrop-blur-md p-2 rounded-2xl border border-white/20"> <input type="text" id="url-input" placeholder="הדבק קישור לפורום כאן (לדוגמה: /post/36372)..." onkeydown="if(event.key==='Enter') handleCustomUrl()" class="bg-transparent text-white placeholder-indigo-200 px-3 py-1.5 text-sm w-full focus:outline-none" dir="ltr"> <button onclick="handleCustomUrl()" class="bg-pink-500 text-white px-4 py-2 rounded-xl text-xs font-bold hover:bg-pink-600 transition shrink-0 cursor-pointer shadow-sm"> 🔗 טען קישור </button> </div> </div> </header> <!-- Main Container --> <main class="max-w-5xl mx-auto px-4 sm:px-6 pb-16"> <!-- Open Tabs Bar --> <div id="tabs-bar" class="hidden flex items-center gap-2 overflow-x-auto pb-3 mb-4 no-print"></div> <div id="main-container"> <!-- Loading State --> <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="relative w-14 h-14"> <div class="absolute inset-0 rounded-full border-4 border-indigo-200 animate-pulse"></div> <div class="absolute inset-0 rounded-full border-4 border-indigo-600 border-t-transparent animate-spin"></div> </div> <span class="text-slate-500 font-medium text-sm tracking-wide">טוען את נתוני המערכת...</span> </div> </div> </main> </div> <!-- Footer --> <footer class="bg-white border-t border-slate-200 py-6 text-center text-xs text-slate-400"> <p>ממשק מותאם אישית לקהילת ימות המשיח / FreeIVR • נבנה בעיצוב מודרני עם כל השיפורים</p> </footer> <script> const scriptUrl = 'כאן יש לכתוב את הפריסה'; let currentCid = ''; let currentCatPage = 1; let currentFontSizeLevel = parseInt(localStorage.getItem('forum_font_size') || '16'); // Multi-Tab State Management let openTabs = []; // { tid, title, data, cid, catPage, postPage } let activeTabId = null; let currentMainView = { type: 'home', data: null }; // Tracks what's in the background view (home or category) // Initialize Settings on load document.addEventListener('DOMContentLoaded', () => { if (localStorage.getItem('forum_dark_mode') === 'true') { document.body.classList.add('dark-mode'); document.getElementById('dark-mode-btn').innerHTML = '☀️'; } updateBookmarkCount(); applyFontSize(); }); // Dark Mode Toggle function toggleDarkMode() { document.body.classList.toggle('dark-mode'); const isDark = document.body.classList.contains('dark-mode'); localStorage.setItem('forum_dark_mode', isDark); document.getElementById('dark-mode-btn').innerHTML = isDark ? '☀️' : '🌙'; } // Font Size Controls function changeFontSize(action) { if (action === 'increase' && currentFontSizeLevel < 22) { currentFontSizeLevel += 2; } else if (action === 'decrease' && currentFontSizeLevel > 12) { currentFontSizeLevel -= 2; } localStorage.setItem('forum_font_size', currentFontSizeLevel); applyFontSize(); } function applyFontSize() { const contents = document.querySelectorAll('.post-content'); contents.forEach(el => { el.style.fontSize = currentFontSizeLevel + 'px'; }); } // Bookmarks Management function getBookmarks() { try { return JSON.parse(localStorage.getItem('forum_bookmarks') || '[]'); } catch (e) { return []; } } function toggleBookmark(tid, title) { let bookmarks = getBookmarks(); const index = bookmarks.findIndex(b => b.tid === tid); if (index > -1) { bookmarks.splice(index, 1); } else { bookmarks.unshift({ tid, title: title || 'נושא ללא כותרת', date: new Date().toLocaleDateString('he-IL') }); } localStorage.setItem('forum_bookmarks', JSON.stringify(bookmarks)); updateBookmarkCount(); } function isBookmarked(tid) { return getBookmarks().some(b => b.tid === tid); } function updateBookmarkCount() { const count = getBookmarks().length; const badge = document.getElementById('bm-count'); if (badge) badge.innerText = count; } function loadBookmarks() { activeTabId = null; renderTabsBar(); currentMainView = { type: 'bookmarks' }; const container = document.getElementById('main-container'); const bookmarks = getBookmarks(); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה לדף הבית </button> <h2 class="text-xl font-bold text-slate-800">הנושאים השמורים שלי (מועדפים)</h2> </div> `; if (bookmarks.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-slate-200 divide-y divide-slate-100 overflow-hidden mb-6">`; bookmarks.forEach(bm => { html += ` <div class="p-4 sm:p-5 hover:bg-indigo-50/40 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${bm.tid}', '', 1, 1, false)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-amber-50 text-amber-600 flex items-center justify-center shrink-0 font-bold text-xs shadow-sm"> ⭐ </div> <div> <h4 class="font-bold text-slate-800 group-hover:text-indigo-700 text-base transition leading-snug">${escapeHtml(bm.title)}</h4> <span class="text-xs text-slate-400">נשמר בתאריך: ${bm.date}</span> </div> </div> <div class="flex items-center gap-2 self-start sm:self-center"> <button onclick="openInTab('${bm.tid}', '${escapeHtml(bm.title)}', '', 1, 1)" class="bg-indigo-50 hover:bg-indigo-100 text-indigo-700 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 פתח בטאב </button> <button onclick="toggleBookmark('${bm.tid}'); loadBookmarks();" class="bg-rose-50 hover:bg-rose-100 text-rose-600 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> הסר </button> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">אין עדיין נושאים שמורים במועדפים.</div>`; } container.innerHTML = html; } // Browsing History Management function addHistory(tid, title) { if (!tid) return; try { let history = JSON.parse(localStorage.getItem('forum_history') || '[]'); history = history.filter(h => h.tid !== tid); history.unshift({ tid, title: title || 'נושא ללא כותרת', time: new Date().toLocaleTimeString('he-IL', {hour: '2-digit', minute:'2-digit'}) }); if (history.length > 15) history.pop(); localStorage.setItem('forum_history', JSON.stringify(history)); } catch (e) {} } function loadHistory() { activeTabId = null; renderTabsBar(); currentMainView = { type: 'history' }; const container = document.getElementById('main-container'); let history = []; try { history = JSON.parse(localStorage.getItem('forum_history') || '[]'); } catch (e) {} let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה לדף הבית </button> <div class="flex items-center gap-3"> <button onclick="localStorage.removeItem('forum_history'); loadHistory();" class="bg-rose-50 hover:bg-rose-100 text-rose-600 border border-rose-200 px-3.5 py-2 rounded-xl text-xs font-bold transition cursor-pointer"> 🗑️ נקה היסטוריה </button> <h2 class="text-xl font-bold text-slate-800">היסטוריית צפייה</h2> </div> </div> `; if (history.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-slate-200 divide-y divide-slate-100 overflow-hidden mb-6">`; history.forEach(item => { html += ` <div class="p-4 sm:p-5 hover:bg-indigo-50/40 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${item.tid}', '', 1, 1, false)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-slate-100 group-hover:bg-indigo-600 group-hover:text-white text-slate-500 flex items-center justify-center shrink-0 transition font-bold text-xs shadow-sm"> 📜 </div> <h4 class="font-bold text-slate-800 group-hover:text-indigo-700 text-base transition leading-snug">${escapeHtml(item.title)}</h4> </div> <div class="flex items-center gap-2 self-start sm:self-center"> <button onclick="openInTab('${item.tid}', '${escapeHtml(item.title)}', '', 1, 1)" class="bg-indigo-50 hover:bg-indigo-100 text-indigo-700 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 פתח בטאב </button> <span class="bg-slate-100 text-slate-600 px-3 py-1.5 rounded-lg text-xs font-medium">🕒 ${item.time}</span> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">אין היסטוריית גלישה זמינה.</div>`; } container.innerHTML = html; } // Tab Management Functions function openInTab(tid, title, cid, catPage, postPage) { const existingTab = openTabs.find(t => t.tid === tid); if (existingTab) { activeTabId = tid; renderTopicData(existingTab.data, existingTab.cid, existingTab.catPage, existingTab.postPage); renderTabsBar(); return; } // Fetch and open in new tab fetch(`${scriptUrl}?tid=${tid}&page=${postPage || 1}`).then(res => res.json()).then(data => { if (data && !data.error) { const newTab = { tid, title: data.title || title, data, cid, catPage, postPage: postPage || 1 }; openTabs.push(newTab); activeTabId = tid; renderTabsBar(); renderTopicData(data, cid, catPage, postPage || 1); addHistory(tid, data.title); } else { alert('שגיאה בטעינת הנושא לטאב.'); } }).catch(err => console.error(err)); } function switchTab(tid) { const tab = openTabs.find(t => t.tid === tid); if (tab) { activeTabId = tid; renderTabsBar(); renderTopicData(tab.data, tab.cid, tab.catPage, tab.postPage); } } function closeTab(event, tid) { event.stopPropagation(); openTabs = openTabs.filter(t => t.tid !== tid); if (activeTabId === tid) { if (openTabs.length > 0) { const lastTab = openTabs[openTabs.length - 1]; switchTab(lastTab.tid); } else { activeTabId = null; renderTabsBar(); if (currentMainView.type === 'category') { loadCategory(currentMainView.cid, currentMainView.page); } else if (currentMainView.type === 'bookmarks') { loadBookmarks(); } else if (currentMainView.type === 'history') { loadHistory(); } else { loadHome(); } } } else { renderTabsBar(); } } function renderTabsBar() { const bar = document.getElementById('tabs-bar'); if (openTabs.length === 0) { bar.classList.add('hidden'); bar.innerHTML = ''; return; } bar.classList.remove('hidden'); let html = `<span class="text-xs font-bold text-slate-400 shrink-0 pl-1">טאבים פתוחים:</span>`; openTabs.forEach(tab => { const isActive = tab.tid === activeTabId; html += ` <div onclick="switchTab('${tab.tid}')" class="flex items-center gap-2 px-3 py-1.5 rounded-xl text-xs font-semibold cursor-pointer transition shadow-xs shrink-0 ${isActive ? 'bg-indigo-600 text-white' : 'bg-white hover:bg-slate-100 text-slate-700 border border-slate-200'}"> <span class="max-w-[140px] truncate">${escapeHtml(tab.title)}</span> <span onclick="closeTab(event, '${tab.tid}')" class="hover:bg-black/20 rounded-full w-4 h-4 flex items-center justify-center text-[10px]">✕</span> </div> `; }); bar.innerHTML = html; } // Interception for all internal forum links document.addEventListener('click', function(e) { const anchor = e.target.closest('a'); if (!anchor) return; const href = anchor.getAttribute('href'); if (!href) return; const postMatch = href.match(/\/post\/([^\/\?#]+)/); const tidMatch = href.match(/\/topic\/([^\/\?#]+)/); const cidMatch = href.match(/\/category\/([^\/\?#]+)/); if (postMatch) { e.preventDefault(); loadPost(postMatch[1]); } else if (tidMatch) { e.preventDefault(); loadTopic(tidMatch[1], currentCid, currentCatPage, 1, true); } else if (cidMatch) { e.preventDefault(); loadCategory(cidMatch[1], 1); } }); function handleCustomUrl() { const urlInput = document.getElementById('url-input').value.trim(); if (!urlInput) return; const postMatch = urlInput.match(/\/post\/([^\/\?#]+)/); const tidMatch = urlInput.match(/\/topic\/([^\/\?#]+)/); const cidMatch = urlInput.match(/\/category\/([^\/\?#]+)/); if (postMatch) { loadPost(postMatch[1]); } else if (tidMatch) { loadTopic(tidMatch[1], '', 1, 1, true); } else if (cidMatch) { loadCategory(cidMatch[1], 1); } else { alert('הקישור שהוזן אינו זוהה כמועמד תקני לפוסט, נושא או קטגוריה בפורום.'); } } async function handleSearch() { const query = document.getElementById('search-input').value.trim(); if (!query) return; activeTabId = null; renderTabsBar(); currentMainView = { type: 'search' }; const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-indigo-600 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">מחפש "${escapeHtml(query)}"...</span> </div> `; try { const response = await fetch(`${scriptUrl}?term=${encodeURIComponent(query)}&q=${encodeURIComponent(query)}`); const data = await response.json(); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה לדף הבית </button> <h2 class="text-xl font-bold text-slate-800">תוצאות חיפוש: "${escapeHtml(query)}"</h2> </div> `; if (data && data.topics && Array.isArray(data.topics) && data.topics.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-slate-200 divide-y divide-slate-100 overflow-hidden mb-6">`; data.topics.forEach(topic => { html += ` <div class="p-4 sm:p-5 hover:bg-indigo-50/40 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${topic.tid}', '', 1, 1, true)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-slate-100 group-hover:bg-indigo-600 group-hover:text-white text-slate-500 flex items-center justify-center shrink-0 transition font-bold text-xs shadow-sm"> 🔍 </div> <h4 class="font-bold text-slate-800 group-hover:text-indigo-700 text-base transition leading-snug">${escapeHtml(topic.title)}</h4> </div> <div class="flex items-center gap-2 self-start sm:self-center shrink-0"> <button onclick="openInTab('${topic.tid}', '${escapeHtml(topic.title)}', '', 1, 1)" class="bg-indigo-50 hover:bg-indigo-100 text-indigo-700 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 פתח בטאב </button> <span class="bg-slate-100 text-slate-600 px-3 py-1.5 rounded-lg text-xs font-medium">👁️ ${topic.viewcount || 0}</span> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">לא נמצאו תוצאות חיפוש.</div>`; } container.innerHTML = html; } catch (error) { console.error(error); container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בביצוע החיפוש.</div>`; } } async function loadHome() { currentCid = ''; activeTabId = null; renderTabsBar(); currentMainView = { type: 'home' }; const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-indigo-600 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">טוען את דף הבית...</span> </div> `; try { const response = await fetch(scriptUrl); const data = await response.json(); if (data && data.categories && Array.isArray(data.categories) && data.categories.length > 0) { let html = ` <div class="mb-6"> <h2 class="text-xl font-bold text-slate-800 tracking-tight">קטגוריות ראשיות</h2> <p class="text-sm text-slate-500">בחר קטגוריה לצפייה בנושאים ודיונים</p> </div> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> `; data.categories.forEach(cat => { html += ` <div onclick="loadCategory('${cat.cid}', 1)" class="group bg-white p-5 rounded-2xl shadow-sm hover:shadow-xl border border-slate-200/80 hover:border-indigo-500/50 transition-all duration-300 cursor-pointer flex flex-col justify-between gap-4 relative overflow-hidden"> <div class="absolute top-0 right-0 w-1.5 h-full bg-indigo-500 opacity-0 group-hover:opacity-100 transition-opacity"></div> <div class="flex items-start justify-between gap-3"> <h3 class="font-bold text-slate-800 group-hover:text-indigo-600 text-lg transition line-clamp-2">${escapeHtml(cat.name)}</h3> <span class="p-2.5 rounded-xl bg-indigo-50 text-indigo-600 group-hover:bg-indigo-600 group-hover:text-white transition-all shadow-sm"> 📁 </span> </div> <div class="flex items-center gap-2 pt-2 border-t border-slate-100 text-xs font-medium"> <span class="bg-slate-100 text-slate-600 px-3 py-1 rounded-lg">נושאים: <strong class="text-slate-800">${cat.topic_count || 0}</strong></span> <span class="bg-indigo-50/70 text-indigo-700 px-3 py-1 rounded-lg">הודעות: <strong class="text-indigo-900">${cat.post_count || 0}</strong></span> </div> </div> `; }); html += `</div>`; container.innerHTML = html; } else { container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-500 font-medium border border-slate-200">לא נמצאו קטגוריות להצגה.</div>`; } } catch (error) { console.error(error); container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בטעינת דף הבית.</div>`; } } async function loadCategory(cid, page = 1) { currentCid = cid; currentCatPage = page; activeTabId = null; renderTabsBar(); currentMainView = { type: 'category', cid, page }; const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-indigo-600 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">טוען נתונים מהקטגוריה...</span> </div> `; try { const response = await fetch(`${scriptUrl}?cid=${cid}&page=${page}`); const data = await response.json(); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer flex items-center gap-2"> ← חזרה לדף הבית </button> <div class="bg-white border border-slate-200 px-4 py-2 rounded-xl text-xs font-semibold text-slate-500 shadow-sm"> עמוד ${page} </div> </div> <div class="bg-gradient-to-br from-white to-slate-50 rounded-2xl shadow-sm border border-slate-200 p-6 sm:p-8 mb-6"> <h2 class="text-2xl font-black text-slate-900 tracking-tight">${escapeHtml(data.name || 'קטגוריה')}</h2> </div> `; if (data && data.children && Array.isArray(data.children) && data.children.length > 0) { html += ` <div class="mb-8"> <h3 class="text-base font-bold text-slate-700 mb-3 px-1">תתי-קטגוריות</h3> <div class="grid grid-cols-1 md:grid-cols-2 gap-3"> `; data.children.forEach(sub => { html += ` <div onclick="loadCategory('${sub.cid}', 1)" class="group bg-white p-4 rounded-xl shadow-sm hover:shadow-md border border-slate-200/80 hover:border-indigo-500 transition cursor-pointer flex items-center justify-between gap-3"> <span class="font-bold text-indigo-900 group-hover:text-indigo-600 text-sm flex-1">📁 ${escapeHtml(sub.name)}</span> <div class="flex items-center gap-1.5 text-xs"> <span class="bg-slate-100 text-slate-600 px-2.5 py-1 rounded-md font-medium">${sub.topic_count || 0} נושאים</span> </div> </div> `; }); html += `</div></div>`; } if (data && data.topics && Array.isArray(data.topics) && data.topics.length > 0) { html += `<h3 class="text-base font-bold text-slate-700 mb-3 px-1">נושאים בקטגוריה</h3>`; html += `<div class="bg-white rounded-2xl shadow-sm border border-slate-200 divide-y divide-slate-100 overflow-hidden mb-6">`; data.topics.forEach(topic => { html += ` <div class="p-4 sm:p-5 hover:bg-indigo-50/40 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${topic.tid}', '${cid}', ${page}, 1, true)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-slate-100 group-hover:bg-indigo-600 group-hover:text-white text-slate-500 flex items-center justify-center shrink-0 transition font-bold text-xs shadow-sm"> 💬 </div> <h4 class="font-bold text-slate-800 group-hover:text-indigo-700 text-base transition leading-snug">${escapeHtml(topic.title)}</h4> </div> <div class="flex items-center gap-2 self-start sm:self-center shrink-0"> <button onclick="openInTab('${topic.tid}', '${escapeHtml(topic.title)}', '${cid}', ${page}, 1)" class="bg-indigo-50 hover:bg-indigo-100 text-indigo-700 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 פתח בטאב </button> <span class="bg-slate-100 text-slate-600 px-3 py-1.5 rounded-lg text-xs font-medium">👁️ ${topic.viewcount || 0}</span> <span class="bg-indigo-50 text-indigo-700 px-3 py-1.5 rounded-lg text-xs font-medium">💬 ${topic.postcount || 0}</span> </div> </div> `; }); html += `</div>`; const totalPages = data.pageCount || (data.pagination ? data.pagination.pageCount : 1); html += `<div class="flex flex-col sm:flex-row justify-between items-center gap-4 pt-2">`; if (page > 1) { html += `<button onclick="loadCategory('${cid}', ${page - 1})" class="bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-5 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer">← עמוד קודם</button>`; } else { html += `<div></div>`; } html += ` <div class="flex items-center gap-2 bg-white border border-slate-200 p-1.5 rounded-xl shadow-sm"> <span class="text-xs text-slate-500 px-2 font-medium">עבור לעמוד:</span> <input type="number" id="jump-cat-page" min="1" max="${totalPages || 999}" value="${page}" class="w-16 bg-slate-50 border border-slate-200 text-center rounded-lg py-1 text-xs font-bold focus:outline-none" onkeydown="if(event.key==='Enter') { const p = parseInt(this.value); if(p>0) loadCategory('${cid}', p); }"> <button onclick="const p = parseInt(document.getElementById('jump-cat-page').value); if(p>0) loadCategory('${cid}', p);" class="bg-indigo-600 text-white px-3 py-1 rounded-lg text-xs font-bold hover:bg-indigo-500 transition cursor-pointer">מעבר</button> </div> `; if (!data.isLastPage && (!totalPages || page < totalPages)) { html += `<button onclick="loadCategory('${cid}', ${page + 1})" class="bg-indigo-600 hover:bg-indigo-500 text-white px-5 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer">עמוד הבא →</button>`; } else { html += `<div></div>`; } html += `</div>`; } else if (!data.children || data.children.length === 0) { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">אין נושאים או תתי-קטגוריות בקטגוריה זו.</div>`; } container.innerHTML = html; } catch (error) { console.error(error); container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בטעינת הקטגוריה.</div>`; } } async function loadPost(pid) { const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-indigo-600 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">טוען את הפוסט (מזהה: ${pid})...</span> </div> `; try { const response = await fetch(`${scriptUrl}?pid=${pid}`); const data = await response.json(); if (data && data.tid) { loadTopic(data.tid, '', 1, 1, true); } else if (data && data.posts) { renderTopicData(data, '', 1, 1); } else { container.innerHTML = ` <button onclick="loadHome()" class="mb-6 bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה לדף הבית </button> <div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">לא ניתן לטעון את הפוסט הישיר.</div> `; } } catch (error) { console.error(error); container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בטעינת הפוסט.</div>`; } } async function loadTopic(tid, cid, catPage, postPage = 1, openAsMain = false) { currentCid = cid; currentCatPage = catPage; if (openAsMain) { activeTabId = null; renderTabsBar(); } const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-indigo-600 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">טוען תוכן הפוסט...</span> </div> `; try { const response = await fetch(`${scriptUrl}?tid=${tid}&page=${postPage}`); const data = await response.json(); if (data && data.error) { container.innerHTML = ` <button onclick="loadCategory('${cid}', ${catPage})" class="mb-6 bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה לקטגוריה </button> <div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בטעינת הפוסט: ${escapeHtml(data.error)}</div> `; return; } addHistory(tid, data.title); renderTopicData(data, cid, catPage, postPage); } catch (error) { console.error(error); container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בטעינת תוכן הפוסט.</div>`; } } function renderTopicData(data, cid, catPage, postPage) { const container = document.getElementById('main-container'); let backAction = cid ? `loadCategory('${cid}', ${catPage})` : `loadHome()`; const tid = data.tid || ''; const title = data.title || 'תוכן הפוסט'; const bookmarked = isBookmarked(tid); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6 no-print"> <button onclick="${backAction}" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer flex items-center gap-2"> ← חזרה אחורה </button> <div class="flex flex-wrap items-center gap-2"> <!-- Clean Print / PDF Export Button --> <button onclick="window.print()" class="bg-indigo-50 hover:bg-indigo-100 text-indigo-700 border border-indigo-200 px-3.5 py-2 rounded-xl text-xs font-bold transition shadow-sm cursor-pointer flex items-center gap-1.5"> 🖨️ ייצוא / הדפסה נקייה </button> <!-- Quick Share Button --> <button onclick="shareTopic('${tid}', '${escapeHtml(title)}')" class="bg-white hover:bg-slate-50 text-indigo-700 border border-slate-200 px-3.5 py-2 rounded-xl text-xs font-bold transition shadow-sm cursor-pointer flex items-center gap-1.5"> 🔗 שיתוף נושא </button> <!-- Bookmark Toggle Button --> <button onclick="toggleBookmark('${tid}', '${escapeHtml(title)}'); renderTopicData(dataForRender, '${cid}', ${catPage}, ${postPage});" class="bg-white hover:bg-slate-50 ${bookmarked ? 'text-amber-500 border-amber-300' : 'text-slate-600 border-slate-200'} px-3.5 py-2 rounded-xl text-xs font-bold transition shadow-sm cursor-pointer flex items-center gap-1.5"> ${bookmarked ? '⭐ מוסר ממועדפים' : '☆ שמור במועדפים'} </button> <div class="bg-white border border-slate-200 px-4 py-2 rounded-xl text-xs font-semibold text-slate-500 shadow-sm"> עמוד ${postPage} </div> </div> </div> <div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6 sm:p-8 mb-6"> <h2 class="text-2xl font-black text-slate-900 tracking-tight leading-snug">${escapeHtml(title)}</h2> </div> `; window.dataForRender = data; if (data && data.posts && Array.isArray(data.posts) && data.posts.length > 0) { data.posts.forEach((post, index) => { const authorName = (post.user && post.user.username) ? post.user.username : 'משתמש אנונימי'; const likesCount = (post.upvotes !== undefined) ? post.upvotes : (post.votes || 0); const initial = authorName.charAt(0).toUpperCase(); html += ` <div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6 sm:p-8 mb-4 relative overflow-hidden"> <div class="flex items-center justify-between mb-6 pb-4 border-b border-slate-100"> <div class="flex items-center gap-3"> <div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-indigo-600 to-violet-600 text-white font-bold flex items-center justify-center shadow-md text-sm"> ${escapeHtml(initial)} </div> <div> <h4 class="font-bold text-slate-800 text-sm">${escapeHtml(authorName)}</h4> <span class="text-xs text-slate-400">תגובה מספר #${index + 1}</span> </div> </div> <div class="bg-rose-50 text-rose-600 border border-rose-100 text-xs px-3.5 py-1.5 rounded-xl font-bold flex items-center gap-1.5 shadow-xs"> ❤️ ${likesCount} לייקים </div> </div> <div class="post-content text-slate-700 leading-relaxed space-y-4 text-base" style="font-size: ${currentFontSizeLevel}px;"> ${post.content || ''} </div> </div> `; }); const topicTotalPages = data.pageCount || (data.pagination ? data.pagination.pageCount : 1); html += `<div class="flex flex-col sm:flex-row justify-between items-center gap-4 pt-4 no-print">`; if (postPage > 1) { html += `<button onclick="loadTopic('${tid}', '${cid}', ${catPage}, ${postPage - 1}, ${!activeTabId})" class="bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-5 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer">← עמוד קודם</button>`; } else { html += `<div></div>`; } html += ` <div class="flex items-center gap-2 bg-white border border-slate-200 p-1.5 rounded-xl shadow-sm"> <span class="text-xs text-slate-500 px-2 font-medium">עבור לעמוד:</span> <input type="number" id="jump-topic-page" min="1" max="${topicTotalPages || 999}" value="${postPage}" class="w-16 bg-slate-50 border border-slate-200 text-center rounded-lg py-1 text-xs font-bold focus:outline-none" onkeydown="if(event.key==='Enter') { const p = parseInt(this.value); if(p>0) loadTopic('${tid}', '${cid}', ${catPage}, p, ${!activeTabId}); }"> <button onclick="const p = parseInt(document.getElementById('jump-topic-page').value); if(p>0) loadTopic('${tid}', '${cid}', ${catPage}, p, ${!activeTabId});" class="bg-indigo-600 text-white px-3 py-1 rounded-lg text-xs font-bold hover:bg-indigo-500 transition cursor-pointer">מעבר</button> </div> `; if (!data.isLastPage && (!topicTotalPages || postPage < topicTotalPages)) { html += `<button onclick="loadTopic('${tid}', '${cid}', ${catPage}, ${postPage + 1}, ${!activeTabId})" class="bg-indigo-600 hover:bg-indigo-500 text-white px-5 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer">עמוד הבא →</button>`; } else { html += `<div></div>`; } html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">אין תוכן או תגובות זמינות להצגה לפוסט זה.</div>`; } container.innerHTML = html; setupCopyButtons(); } function shareTopic(tid, title) { const shareText = `פורום FreeIVR - ${title}\nלצפייה בנושא: /topic/${tid}`; if (navigator.clipboard) { navigator.clipboard.writeText(shareText).then(() => { alert('קישור הנושא הועתק ללוח בהצלחה!'); }); } else { prompt('העתק את הקישור:', shareText); } } function setupCopyButtons() { const pres = document.querySelectorAll('main pre'); pres.forEach(pre => { if (pre.querySelector('.copy-code-btn')) return; const button = document.createElement('button'); button.className = 'copy-code-btn absolute top-3 left-3 bg-slate-700 hover:bg-slate-600 text-slate-200 text-xs px-3 py-1.5 rounded-lg font-medium transition shadow-sm cursor-pointer flex items-center gap-1 no-print'; button.innerHTML = '📋 העתק'; button.addEventListener('click', () => { const code = pre.querySelector('code') || pre; const textToCopy = code.innerText; navigator.clipboard.writeText(textToCopy).then(() => { button.innerHTML = '✅ הועתק!'; button.classList.add('bg-emerald-600'); button.classList.remove('bg-slate-700', 'hover:bg-slate-600'); setTimeout(() => { button.innerHTML = '📋 העתק'; button.classList.remove('bg-emerald-600'); button.classList.add('bg-slate-700', 'hover:bg-slate-600'); }, 2000); }).catch(err => { console.error('Failed to copy text: ', err); }); }); pre.appendChild(button); }); } function escapeHtml(text) { if (!text) return ''; return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); } loadHome(); </script> </body> </html>קוד הסקריפט
היכנס ל-Google Apps Script וצור פרויקט חדש.
והדבק את הקודfunction doGet(e) { try { var tid = e && e.parameter && e.parameter.tid ? e.parameter.tid : ''; var cid = e && e.parameter && e.parameter.cid ? e.parameter.cid : ''; var page = e && e.parameter && e.parameter.page ? e.parameter.page : ''; var path = ''; if (tid) { path = 'topic/' + tid; } else if (cid) { path = 'category/' + cid; } var url = 'https://f2.freeivr.co.il/api/' + path; var queryParams = []; if (page) { queryParams.push('page=' + page); } if (queryParams.length > 0) { url += '?' + queryParams.join('&'); } var cache = CacheService.getScriptCache(); var cacheKey = (tid ? 'topic_' + tid : (cid ? 'category_' + cid : 'home')) + '_p' + (page || 1); var cachedData = cache.get(cacheKey); if (cachedData) { return ContentService.createTextOutput(cachedData) .setMimeType(ContentService.MimeType.JSON); } var options = { 'muteHttpExceptions': true, 'headers': { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } }; var response = UrlFetchApp.fetch(url, options); var responseCode = response.getResponseCode(); var jsonText = response.getContentText(); if (responseCode !== 200) { jsonText = JSON.stringify({ error: "שגיאת שרת חיצוני (HTTP " + responseCode + ")" }); } else { // שמירה במטמון רק אם הנתונים קטנים ממגבלת ה-100KB של גוגל try { if (jsonText.length < 90000) { cache.put(cacheKey, jsonText, 180); } } catch (cacheErr) { // במידה וחורג, ממשיכים הלאה בלי לשמור במטמון כדי לא לתקוע את הצגת הפוסט } } return ContentService.createTextOutput(jsonText) .setMimeType(ContentService.MimeType.JSON); } catch(err) { return ContentService.createTextOutput(JSON.stringify({error: err.message})) .setMimeType(ContentService.MimeType.JSON); } }לחץ על כפתור פריסה (Deploy) בפינה הימנית העליונה בחבילה > פריסה חדשה (New deployment).
בחר סוג פריסה: ניתן להפעלה דרך API (מומלץ קודם לפרוס כאפליקציה ואח"כ כAPI)
הגדר את ההרשאות הבאות:
הפעל כ (Execute as): אני (Me)
מי יש גישה (Who has access): כולם (Anyone)
לחץ על פריסה, אשר את הרשאות החשבון שלך, והעתק את כתובת ה-URL שנוצרה.
הדבק את הכתובת בדף הhtml במקום "כאן יש לכתוב את הפריסה" -
יכול לעזור למקומות שחסום
איך יכול? דף html של חיפוש גוגל אמור להיות פתוח לגמרי? בסוף כל התעבורה עוברת דרך הספק.
-
@יעקב-יצחק
זה עובר דרך גוגל סקריפט
ואז הסינון לא אמור לקלוט שמדובר בפורום -
@הדיבל-המעופף
אם אתה יכול - נשמח -
@הדיבל-המעופף מסתמא אתה לא מתכוון לנטפרי אלא לסינונים יותר 'עצמאיים' - שאין בהם בדיקה ותיוג אנושי לכל אתר
-
העלאתי
זה עדיין בשיפור -
@הדיבל-המעופף אתה יכול להתאים את זה למתמחים טופ ?
-
@מ.ל.ל
אשתדל לבדוק ביממה הקרובה -
@הדיבל-המעופף מה הקטע בזה איך זה עובד אולי אני ינסה בעמצי עד שתגיע היממה הקרובה ?
-
@מ.ל.ל
זה גמיני
אבל הרעיון שבגלל שאין לפורום API צריך סקריפט שימשוך וישלח לך בAPI -
@מ.ל.ל
קח לא בדקתי יותר מידי
קוד סקריפטfunction doGet(e) { try { var tid = e && e.parameter && e.parameter.tid ? e.parameter.tid : ''; var cid = e && e.parameter && e.parameter.cid ? e.parameter.cid : ''; var page = e && e.parameter && e.parameter.page ? e.parameter.page : ''; var recent = e && e.parameter && (e.parameter.recent || e.parameter.action === 'recent' || e.parameter.q === 'recent' || e.parameter.sort === 'recent'); var top = e && e.parameter && (e.parameter.top || e.parameter.action === 'top' || e.parameter.sort === 'top' || e.parameter.popular); var term = e && e.parameter ? (e.parameter.term || e.parameter.q || e.parameter.search || e.parameter.query) : ''; var path = ''; var queryParams = []; if (tid) { path = 'topic/' + tid; } else if (cid) { path = 'category/' + cid; } else if (recent) { path = 'recent'; } else if (top) { path = 'top'; } else if (term) { path = 'search'; queryParams.push('term=' + encodeURIComponent(term)); } // כתובת ה-API של פורום מתמחים טופ var url = 'https://mitmachim.top/api/' + path; if (page) { queryParams.push('page=' + page); } if (queryParams.length > 0) { url += '?' + queryParams.join('&'); } var cache = CacheService.getScriptCache(); var cacheKey = (tid ? 'topic_' + tid : (cid ? 'category_' + cid : (recent ? 'recent' : (top ? 'top' : (term ? 'search_' + term : 'home'))))) + '_p' + (page || 1); var cachedData = cache.get(cacheKey); if (cachedData) { return ContentService.createTextOutput(cachedData) .setMimeType(ContentService.MimeType.JSON); } var options = { 'muteHttpExceptions': true, 'headers': { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } }; var response = UrlFetchApp.fetch(url, options); var responseCode = response.getResponseCode(); var jsonText = response.getContentText(); if (responseCode !== 200) { jsonText = JSON.stringify({ error: "שגיאת שרת חיצוני (HTTP " + responseCode + ")" }); } else { try { if (jsonText.length < 90000) { var cacheTime = tid ? 600 : (recent || top ? 120 : 300); cache.put(cacheKey, jsonText, cacheTime); } } catch (cacheErr) {} } return ContentService.createTextOutput(jsonText) .setMimeType(ContentService.MimeType.JSON); } catch(err) { return ContentService.createTextOutput(JSON.stringify({error: err.message})) .setMimeType(ContentService.MimeType.JSON); } }קוד דף
<!DOCTYPE html> <html lang="he" dir="rtl"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>פורום מתמחים טופ - ממשק מתקדם</title> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> <style> body { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: var(--bg-color, #f8fafc); color: var(--text-color, #1e293b); transition: background-color 0.3s, color 0.3s; } pre, code { font-family: Consolas, Monaco, 'Courier New', Courier, monospace; direction: ltr; text-align: left; } pre { background-color: #0f172a; color: #e2e8f0; border-radius: 12px; padding: 20px; padding-top: 45px; overflow-x: auto; margin: 16px 0; font-size: 0.92rem; position: relative; box-shadow: inset 0 2px 4px rgba(0,0,0,0.2); } code { background-color: #f1f5f9; border: 1px solid #e2e8f0; padding: 2px 6px; border-radius: 6px; font-size: 0.88rem; color: #0f172a; } pre code { background-color: transparent; border: none; padding: 0; color: inherit; } .post-content a { color: #6366f1; text-decoration: underline; font-weight: 500; } .post-content a:hover { color: #4f46e5; } html { scroll-behavior: smooth; } /* Dark Mode Variables & Overrides */ body.dark-mode { --bg-color: #0f172a; --text-color: #f1f5f9; --card-bg: #1e293b; --border-color: #334155; --hover-bg: #26334d; } body.dark-mode .bg-white { background-color: #1e293b !important; color: #f1f5f9 !important; border-color: #334155 !important; } body.dark-mode .text-slate-800, body.dark-mode .text-slate-900, body.dark-mode .text-slate-700 { color: #f1f5f9 !important; } body.dark-mode .text-slate-500, body.dark-mode .text-slate-400 { color: #94a3b8 !important; } body.dark-mode .border-slate-200, body.dark-mode .border-slate-100 { border-color: #334155 !important; } body.dark-mode .bg-slate-50, body.dark-mode .bg-slate-100 { background-color: #0f172a !important; color: #cbd5e1 !important; } @media print { header, footer, button, .no-print, #tabs-bar { display: none !important; } body { background: white !important; color: black !important; } main { max-width: 100% !important; padding: 0 !important; margin: 0 !important; } .bg-white { box-shadow: none !important; border: none !important; padding: 0 !important; margin-bottom: 20px !important; } } </style> </head> <body class="min-h-screen flex flex-col justify-between"> <div> <!-- Modern Header --> <header class="bg-gradient-to-r from-blue-900 via-indigo-900 to-slate-900 text-white shadow-xl py-6 px-6 mb-8 border-b border-indigo-700/50"> <div class="max-w-5xl mx-auto flex flex-col md:flex-row justify-between items-center gap-4"> <div class="flex items-center gap-3 cursor-pointer" onclick="loadHome()"> <div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-blue-500 to-indigo-500 flex items-center justify-center shadow-lg font-bold text-xl text-white"> מ </div> <div> <h1 class="text-2xl font-extrabold tracking-tight hover:text-blue-200 transition"> מתמחים טופ </h1> <p class="text-xs text-blue-200/80 font-medium">ממשק גלישה מתקדם ומהיר</p> </div> </div> <!-- Top Quick Actions --> <div class="flex flex-wrap items-center gap-2"> <button onclick="loadHome()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> 🏠 הבית </button> <button onclick="loadRecentPosts()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> 🔥 פוסטים אחרונים </button> <button onclick="loadPopularPosts()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> ⭐ פופולריים </button> <button onclick="loadBookmarks()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> 📌 מועדפים (<span id="bm-count">0</span>) </button> <button onclick="loadHistory()" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 px-3.5 py-2 rounded-xl text-xs font-semibold transition backdrop-blur-md cursor-pointer flex items-center gap-1.5"> 📜 היסטוריה </button> <!-- Dark Mode Toggle --> <button onclick="toggleDarkMode()" id="dark-mode-btn" class="bg-white/10 hover:bg-white/20 text-white border border-white/20 p-2 rounded-xl text-xs transition backdrop-blur-md cursor-pointer" title="מצב לילה / בהיר"> 🌙 </button> <!-- Font Size Controls --> <div class="flex items-center bg-white/10 border border-white/20 rounded-xl overflow-hidden backdrop-blur-md"> <button onclick="changeFontSize('decrease')" class="px-2.5 py-2 hover:bg-white/20 text-xs font-bold transition cursor-pointer" title="הקטן טקסט">A-</button> <button onclick="changeFontSize('increase')" class="px-2.5 py-2 hover:bg-white/20 text-xs font-bold transition cursor-pointer" title="הגדל טקסט">A+</button> </div> </div> </div> <!-- Advanced Controls Bar (Search & Custom URL) --> <div class="max-w-5xl mx-auto mt-6 pt-6 border-t border-indigo-700/60 grid grid-cols-1 md:grid-cols-2 gap-4"> <!-- Search Box --> <div class="flex items-center gap-2 bg-white/10 backdrop-blur-md p-2 rounded-2xl border border-white/20"> <input type="text" id="search-input" placeholder="חיפוש נושאים בפורום..." onkeydown="if(event.key==='Enter') handleSearch()" class="bg-transparent text-white placeholder-blue-200 px-3 py-1.5 text-sm w-full focus:outline-none"> <button onclick="handleSearch()" class="bg-white text-blue-900 px-4 py-2 rounded-xl text-xs font-bold hover:bg-blue-50 transition shrink-0 cursor-pointer shadow-sm"> 🔍 חיפוש </button> </div> <!-- Custom URL Loader Box --> <div class="flex items-center gap-2 bg-white/10 backdrop-blur-md p-2 rounded-2xl border border-white/20"> <input type="text" id="url-input" placeholder="הדבק קישור לפורום כאן (לדוגמה: /topic/1234)..." onkeydown="if(event.key==='Enter') handleCustomUrl()" class="bg-transparent text-white placeholder-blue-200 px-3 py-1.5 text-sm w-full focus:outline-none" dir="ltr"> <button onclick="handleCustomUrl()" class="bg-indigo-500 text-white px-4 py-2 rounded-xl text-xs font-bold hover:bg-indigo-600 transition shrink-0 cursor-pointer shadow-sm"> 🔗 טען קישור </button> </div> </div> </header> <!-- Main Container --> <main class="max-w-5xl mx-auto px-4 sm:px-6 pb-16"> <!-- Open Tabs Bar --> <div id="tabs-bar" class="hidden flex items-center gap-2 overflow-x-auto pb-3 mb-4 no-print"></div> <div id="main-container"> <!-- Loading State --> <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="relative w-14 h-14"> <div class="absolute inset-0 rounded-full border-4 border-blue-200 animate-pulse"></div> <div class="absolute inset-0 rounded-full border-4 border-blue-600 border-t-transparent animate-spin"></div> </div> <span class="text-slate-500 font-medium text-sm tracking-wide">טוען את נתוני המערכת...</span> </div> </div> </main> </div> <!-- Footer --> <footer class="bg-white border-t border-slate-200 py-6 text-center text-xs text-slate-400"> <p>ממשק מותאם אישית לקהילת מתמחים טופ • נבנה בעיצוב מודרני מהיר ונוח</p> </footer> <script> const scriptUrl = 'פריסה הדבק'; let currentCid = ''; let currentCatPage = 1; let currentFontSizeLevel = parseInt(localStorage.getItem('forum_font_size') || '16'); let openTabs = []; let activeTabId = null; let currentMainView = { type: 'home', data: null }; document.addEventListener('DOMContentLoaded', () => { if (localStorage.getItem('forum_dark_mode') === 'true') { document.body.classList.add('dark-mode'); document.getElementById('dark-mode-btn').innerHTML = '☀️'; } updateBookmarkCount(); applyFontSize(); }); function toggleDarkMode() { document.body.classList.toggle('dark-mode'); const isDark = document.body.classList.contains('dark-mode'); localStorage.setItem('forum_dark_mode', isDark); document.getElementById('dark-mode-btn').innerHTML = isDark ? '☀️' : '🌙'; } function changeFontSize(action) { if (action === 'increase' && currentFontSizeLevel < 22) { currentFontSizeLevel += 2; } else if (action === 'decrease' && currentFontSizeLevel > 12) { currentFontSizeLevel -= 2; } localStorage.setItem('forum_font_size', currentFontSizeLevel); applyFontSize(); } function applyFontSize() { const contents = document.querySelectorAll('.post-content'); contents.forEach(el => { el.style.fontSize = currentFontSizeLevel + 'px'; }); } function getBookmarks() { try { return JSON.parse(localStorage.getItem('forum_bookmarks') || '[]'); } catch (e) { return []; } } function toggleBookmark(tid, title) { let bookmarks = getBookmarks(); const index = bookmarks.findIndex(b => b.tid === tid); if (index > -1) { bookmarks.splice(index, 1); } else { bookmarks.unshift({ tid, title: title || 'נושא ללא כותרת', date: new Date().toLocaleDateString('he-IL') }); } localStorage.setItem('forum_bookmarks', JSON.stringify(bookmarks)); updateBookmarkCount(); } function isBookmarked(tid) { return getBookmarks().some(b => b.tid === tid); } function updateBookmarkCount() { const count = getBookmarks().length; const badge = document.getElementById('bm-count'); if (badge) badge.innerText = count; } function loadBookmarks() { activeTabId = null; renderTabsBar(); currentMainView = { type: 'bookmarks' }; const container = document.getElementById('main-container'); const bookmarks = getBookmarks(); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה לדף הבית </button> <h2 class="text-xl font-bold text-slate-800">הנושאים השמורים שלי (מועדפים)</h2> </div> `; if (bookmarks.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-slate-200 divide-y divide-slate-100 overflow-hidden mb-6">`; bookmarks.forEach(bm => { html += ` <div class="p-4 sm:p-5 hover:bg-blue-50/40 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${bm.tid}', '', 1, 1, false)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-amber-50 text-amber-600 flex items-center justify-center shrink-0 font-bold text-xs shadow-sm"> 📌 </div> <div> <h4 class="font-bold text-slate-800 group-hover:text-blue-700 text-base transition leading-snug">${escapeHtml(bm.title)}</h4> <span class="text-xs text-slate-400">נשמר בתאריך: ${bm.date}</span> </div> </div> <div class="flex items-center gap-2 self-start sm:self-center"> <button onclick="openInTab('${bm.tid}', '${escapeHtml(bm.title)}', '', 1, 1)" class="bg-blue-50 hover:bg-blue-100 text-blue-700 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 פתח בטאב </button> <button onclick="toggleBookmark('${bm.tid}'); loadBookmarks();" class="bg-rose-50 hover:bg-rose-100 text-rose-600 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> הסר </button> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">אין עדיין נושאים שמורים במועדפים.</div>`; } container.innerHTML = html; } function addHistory(tid, title) { if (!tid) return; try { let history = JSON.parse(localStorage.getItem('forum_history') || '[]'); history = history.filter(h => h.tid !== tid); history.unshift({ tid, title: title || 'נושא ללא כותרת', time: new Date().toLocaleTimeString('he-IL', {hour: '2-digit', minute:'2-digit'}) }); if (history.length > 15) history.pop(); localStorage.setItem('forum_history', JSON.stringify(history)); } catch (e) {} } function loadHistory() { activeTabId = null; renderTabsBar(); currentMainView = { type: 'history' }; const container = document.getElementById('main-container'); let history = []; try { history = JSON.parse(localStorage.getItem('forum_history') || '[]'); } catch (e) {} let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה לדף הבית </button> <div class="flex items-center gap-3"> <button onclick="localStorage.removeItem('forum_history'); loadHistory();" class="bg-rose-50 hover:bg-rose-100 text-rose-600 border border-rose-200 px-3.5 py-2 rounded-xl text-xs font-bold transition cursor-pointer"> 🗑️ נקה היסטוריה </button> <h2 class="text-xl font-bold text-slate-800">היסטוריית צפייה</h2> </div> </div> `; if (history.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-slate-200 divide-y divide-slate-100 overflow-hidden mb-6">`; history.forEach(item => { html += ` <div class="p-4 sm:p-5 hover:bg-blue-50/40 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${item.tid}', '', 1, 1, false)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-slate-100 group-hover:bg-blue-600 group-hover:text-white text-slate-500 flex items-center justify-center shrink-0 transition font-bold text-xs shadow-sm"> 📜 </div> <h4 class="font-bold text-slate-800 group-hover:text-blue-700 text-base transition leading-snug">${escapeHtml(item.title)}</h4> </div> <div class="flex items-center gap-2 self-start sm:self-center"> <button onclick="openInTab('${item.tid}', '${escapeHtml(item.title)}', '', 1, 1)" class="bg-blue-50 hover:bg-blue-100 text-blue-700 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 פתח בטאב </button> <span class="bg-slate-100 text-slate-600 px-3 py-1.5 rounded-lg text-xs font-medium">🕒 ${item.time}</span> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">אין היסטוריית גלישה זמינה.</div>`; } container.innerHTML = html; } async function loadRecentPosts() { activeTabId = null; renderTabsBar(); currentMainView = { type: 'recent' }; const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-blue-600 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">טוען פוסטים אחרונים...</span> </div> `; try { const response = await fetch(`${scriptUrl}?recent=true`); const data = await response.json(); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה לדף הבית </button> <h2 class="text-xl font-bold text-slate-800">🔥 פוסטים ודיונים אחרונים</h2> </div> `; const topicsList = data.topics || data.posts || (Array.isArray(data) ? data : []); if (topicsList.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-slate-200 divide-y divide-slate-100 overflow-hidden mb-6">`; topicsList.forEach(topic => { const tid = topic.tid || topic.topic_id; const title = topic.title || 'נושא ללא כותרת'; html += ` <div class="p-4 sm:p-5 hover:bg-blue-50/40 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${tid}', '', 1, 1, true)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-blue-50 text-blue-600 flex items-center justify-center shrink-0 font-bold text-xs shadow-sm"> 🔥 </div> <h4 class="font-bold text-slate-800 group-hover:text-blue-700 text-base transition leading-snug">${escapeHtml(title)}</h4> </div> <div class="flex items-center gap-2 self-start sm:self-center shrink-0"> <button onclick="openInTab('${tid}', '${escapeHtml(title)}', '', 1, 1)" class="bg-blue-50 hover:bg-blue-100 text-blue-700 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 פתח בטאב </button> <span class="bg-slate-100 text-slate-600 px-3 py-1.5 rounded-lg text-xs font-medium">👁️ ${topic.viewcount || 0}</span> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">לא נמצאו פוסטים אחרונים.</div>`; } container.innerHTML = html; } catch (err) { container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בטעינת פוסטים אחרונים.</div>`; } } async function loadPopularPosts() { activeTabId = null; renderTabsBar(); currentMainView = { type: 'popular' }; const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-amber-500 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">טוען נושאים פופולרים...</span> </div> `; try { const response = await fetch(`${scriptUrl}?top=true`); const data = await response.json(); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה לדף הבית </button> <h2 class="text-xl font-bold text-slate-800">⭐ נושאים פופולרים וחמים</h2> </div> `; const topicsList = data.topics || data.posts || (Array.isArray(data) ? data : []); if (topicsList.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-slate-200 divide-y divide-slate-100 overflow-hidden mb-6">`; topicsList.forEach(topic => { const tid = topic.tid || topic.topic_id; const title = topic.title || 'נושא ללא כותרת'; html += ` <div class="p-4 sm:p-5 hover:bg-amber-50/40 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${tid}', '', 1, 1, true)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-amber-50 text-amber-600 flex items-center justify-center shrink-0 font-bold text-xs shadow-sm"> ⭐ </div> <h4 class="font-bold text-slate-800 group-hover:text-amber-700 text-base transition leading-snug">${escapeHtml(title)}</h4> </div> <div class="flex items-center gap-2 self-start sm:self-center shrink-0"> <button onclick="openInTab('${tid}', '${escapeHtml(title)}', '', 1, 1)" class="bg-amber-50 hover:bg-amber-100 text-amber-700 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 פתח בטאב </button> <span class="bg-slate-100 text-slate-600 px-3 py-1.5 rounded-lg text-xs font-medium">👁️ ${topic.viewcount || 0}</span> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">לא נמצאו נושאים פופולרים.</div>`; } container.innerHTML = html; } catch (err) { container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בטעינת נושאים פופולרים.</div>`; } } function openInTab(tid, title, cid, catPage, postPage) { const existingTab = openTabs.find(t => t.tid === tid); if (existingTab) { activeTabId = tid; renderTopicData(existingTab.data, existingTab.cid, existingTab.catPage, existingTab.postPage); renderTabsBar(); return; } fetch(`${scriptUrl}?tid=${tid}&page=${postPage || 1}`).then(res => res.json()).then(data => { if (data && !data.error) { const newTab = { tid, title: data.title || title, data, cid, catPage, postPage: postPage || 1 }; openTabs.push(newTab); activeTabId = tid; renderTabsBar(); renderTopicData(data, cid, catPage, postPage || 1); addHistory(tid, data.title); } else { alert('שגיאה בטעינת הנושא לטאב.'); } }).catch(err => console.error(err)); } function switchTab(tid) { const tab = openTabs.find(t => t.tid === tid); if (tab) { activeTabId = tid; renderTabsBar(); renderTopicData(tab.data, tab.cid, tab.catPage, tab.postPage); } } function closeTab(event, tid) { event.stopPropagation(); openTabs = openTabs.filter(t => t.tid !== tid); if (activeTabId === tid) { if (openTabs.length > 0) { const lastTab = openTabs[openTabs.length - 1]; switchTab(lastTab.tid); } else { activeTabId = null; renderTabsBar(); if (currentMainView.type === 'category') { loadCategory(currentMainView.cid, currentMainView.page); } else if (currentMainView.type === 'bookmarks') { loadBookmarks(); } else if (currentMainView.type === 'history') { loadHistory(); } else if (currentMainView.type === 'recent') { loadRecentPosts(); } else if (currentMainView.type === 'popular') { loadPopularPosts(); } else { loadHome(); } } } else { renderTabsBar(); } } function renderTabsBar() { const bar = document.getElementById('tabs-bar'); if (openTabs.length === 0) { bar.classList.add('hidden'); bar.innerHTML = ''; return; } bar.classList.remove('hidden'); let html = `<span class="text-xs font-bold text-slate-400 shrink-0 pl-1">טאבים פתוחים:</span>`; openTabs.forEach(tab => { const isActive = tab.tid === activeTabId; html += ` <div onclick="switchTab('${tab.tid}')" class="flex items-center gap-2 px-3 py-1.5 rounded-xl text-xs font-semibold cursor-pointer transition shadow-xs shrink-0 ${isActive ? 'bg-blue-600 text-white' : 'bg-white hover:bg-slate-100 text-slate-700 border border-slate-200'}"> <span class="max-w-[140px] truncate">${escapeHtml(tab.title)}</span> <span onclick="closeTab(event, '${tab.tid}')" class="hover:bg-black/20 rounded-full w-4 h-4 flex items-center justify-center text-[10px]">✕</span> </div> `; }); bar.innerHTML = html; } document.addEventListener('click', function(e) { const anchor = e.target.closest('a'); if (!anchor) return; const href = anchor.getAttribute('href'); if (!href) return; const tidMatch = href.match(/(?:\/|^)topic\/([^\/\?#]+)/); const cidMatch = href.match(/(?:\/|^)category\/([^\/\?#]+)/); if (tidMatch) { e.preventDefault(); loadTopic(tidMatch[1], currentCid, currentCatPage, 1, true); } else if (cidMatch) { e.preventDefault(); loadCategory(cidMatch[1], 1); } }); function handleCustomUrl() { const urlInput = document.getElementById('url-input').value.trim(); if (!urlInput) return; const tidMatch = urlInput.match(/(?:\/|^)topic\/([^\/\?#]+)/); const cidMatch = urlInput.match(/(?:\/|^)category\/([^\/\?#]+)/); if (tidMatch) { loadTopic(tidMatch[1], '', 1, 1, true); } else if (cidMatch) { loadCategory(cidMatch[1], 1); } else { alert('הקישור שהוזן אינו זוהה כמועמד תקני לנושא או קטגוריה.'); } } async function handleSearch() { const query = document.getElementById('search-input').value.trim(); if (!query) return; activeTabId = null; renderTabsBar(); currentMainView = { type: 'search' }; const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-blue-600 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">מחפש "${escapeHtml(query)}"...</span> </div> `; try { const response = await fetch(`${scriptUrl}?term=${encodeURIComponent(query)}`); const data = await response.json(); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer"> ← חזרה לדף הבית </button> <h2 class="text-xl font-bold text-slate-800">תוצאות חיפוש: "${escapeHtml(query)}"</h2> </div> `; const results = data.topics || data.posts || (Array.isArray(data) ? data : []); if (results.length > 0) { html += `<div class="bg-white rounded-2xl shadow-sm border border-slate-200 divide-y divide-slate-100 overflow-hidden mb-6">`; results.forEach(topic => { const tid = topic.tid || topic.topic_id; const title = topic.title || 'נושא ללא כותרת'; html += ` <div class="p-4 sm:p-5 hover:bg-blue-50/40 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${tid}', '', 1, 1, true)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-slate-100 group-hover:bg-blue-600 group-hover:text-white text-slate-500 flex items-center justify-center shrink-0 transition font-bold text-xs shadow-sm"> 🔍 </div> <h4 class="font-bold text-slate-800 group-hover:text-blue-700 text-base transition leading-snug">${escapeHtml(title)}</h4> </div> <div class="flex items-center gap-2 self-start sm:self-center shrink-0"> <button onclick="openInTab('${tid}', '${escapeHtml(title)}', '', 1, 1)" class="bg-blue-50 hover:bg-blue-100 text-blue-700 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 פתח בטאב </button> <span class="bg-slate-100 text-slate-600 px-3 py-1.5 rounded-lg text-xs font-medium">👁️ ${topic.viewcount || 0}</span> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">לא נמצאו תוצאות חיפוש.</div>`; } container.innerHTML = html; } catch (err) { container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בביצוע החיפוש.</div>`; } } async function loadHome() { currentCid = ''; activeTabId = null; renderTabsBar(); currentMainView = { type: 'home' }; const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-blue-600 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">טוען את דף הבית...</span> </div> `; try { const response = await fetch(scriptUrl); const data = await response.json(); if (data && data.categories && Array.isArray(data.categories) && data.categories.length > 0) { let html = ` <div class="mb-6"> <h2 class="text-xl font-bold text-slate-800 tracking-tight">קטגוריות ראשיות</h2> <p class="text-sm text-slate-500">בחר קטגוריה לצפייה בנושאים ודיונים</p> </div> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> `; data.categories.forEach(cat => { html += ` <div onclick="loadCategory('${cat.cid}', 1)" class="group bg-white p-5 rounded-2xl shadow-sm hover:shadow-xl border border-slate-200/80 hover:border-blue-500/50 transition-all duration-300 cursor-pointer flex flex-col justify-between gap-4 relative overflow-hidden"> <div class="absolute top-0 right-0 w-1.5 h-full bg-blue-500 opacity-0 group-hover:opacity-100 transition-opacity"></div> <div class="flex items-start justify-between gap-3"> <h3 class="font-bold text-slate-800 group-hover:text-blue-600 text-lg transition line-clamp-2">${escapeHtml(cat.name)}</h3> <span class="p-2.5 rounded-xl bg-blue-50 text-blue-600 group-hover:bg-blue-600 group-hover:text-white transition-all shadow-sm"> 📁 </span> </div> <div class="flex items-center gap-2 pt-2 border-t border-slate-100 text-xs font-medium"> <span class="bg-slate-100 text-slate-600 px-3 py-1 rounded-lg">נושאים: <strong class="text-slate-800">${cat.topic_count || 0}</strong></span> <span class="bg-blue-50/70 text-blue-700 px-3 py-1 rounded-lg">הודעות: <strong class="text-blue-900">${cat.post_count || 0}</strong></span> </div> </div> `; }); html += `</div>`; container.innerHTML = html; } else { container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-500 font-medium border border-slate-200">לא נמצאו קטגוריות להצגה.</div>`; } } catch (error) { container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בטעינת דף הבית.</div>`; } } async function loadCategory(cid, page = 1) { currentCid = cid; currentCatPage = page; activeTabId = null; renderTabsBar(); currentMainView = { type: 'category', cid, page }; const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-blue-600 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">טוען נתונים מהקטגוריה...</span> </div> `; try { const response = await fetch(`${scriptUrl}?cid=${cid}&page=${page}`); const data = await response.json(); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6"> <button onclick="loadHome()" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer flex items-center gap-2"> ← חזרה לדף הבית </button> <div class="bg-white border border-slate-200 px-4 py-2 rounded-xl text-xs font-semibold text-slate-500 shadow-sm"> עמוד ${page} </div> </div> <div class="bg-gradient-to-br from-white to-slate-50 rounded-2xl shadow-sm border border-slate-200 p-6 sm:p-8 mb-6"> <h2 class="text-2xl font-black text-slate-900 tracking-tight">${escapeHtml(data.name || 'קטגוריה')}</h2> </div> `; if (data && data.topics && Array.isArray(data.topics) && data.topics.length > 0) { html += `<h3 class="text-base font-bold text-slate-700 mb-3 px-1">נושאים בקטגוריה</h3>`; html += `<div class="bg-white rounded-2xl shadow-sm border border-slate-200 divide-y divide-slate-100 overflow-hidden mb-6">`; data.topics.forEach(topic => { html += ` <div class="p-4 sm:p-5 hover:bg-blue-50/40 transition flex flex-col sm:flex-row sm:items-center justify-between gap-4 group"> <div onclick="loadTopic('${topic.tid}', '${cid}', ${page}, 1, true)" class="flex items-start gap-3 flex-1 cursor-pointer"> <div class="w-9 h-9 rounded-xl bg-slate-100 group-hover:bg-blue-600 group-hover:text-white text-slate-500 flex items-center justify-center shrink-0 transition font-bold text-xs shadow-sm"> 💬 </div> <h4 class="font-bold text-slate-800 group-hover:text-blue-700 text-base transition leading-snug">${escapeHtml(topic.title)}</h4> </div> <div class="flex items-center gap-2 self-start sm:self-center shrink-0"> <button onclick="openInTab('${topic.tid}', '${escapeHtml(topic.title)}', '${cid}', ${page}, 1)" class="bg-blue-50 hover:bg-blue-100 text-blue-700 px-3 py-1.5 rounded-lg text-xs font-bold transition cursor-pointer"> 📑 פתח בטאב </button> <span class="bg-slate-100 text-slate-600 px-3 py-1.5 rounded-lg text-xs font-medium">👁️ ${topic.viewcount || 0}</span> <span class="bg-blue-50 text-blue-700 px-3 py-1.5 rounded-lg text-xs font-medium">💬 ${topic.postcount || 0}</span> </div> </div> `; }); html += `</div>`; } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">אין נושאים להצגה בקטגוריה זו.</div>`; } container.innerHTML = html; } catch (error) { container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בטעינת הקטגוריה.</div>`; } } async function loadTopic(tid, cid, catPage, postPage = 1, openAsMain = false) { currentCid = cid; currentCatPage = catPage; if (openAsMain) { activeTabId = null; renderTabsBar(); } const container = document.getElementById('main-container'); container.innerHTML = ` <div class="flex flex-col justify-center items-center py-28 gap-4"> <div class="w-12 h-12 rounded-full border-4 border-blue-600 border-t-transparent animate-spin"></div> <span class="text-slate-500 font-medium text-sm">טוען תוכן הפוסט...</span> </div> `; try { const response = await fetch(`${scriptUrl}?tid=${tid}&page=${postPage}`); const data = await response.json(); if (data && data.error) { container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה: ${escapeHtml(data.error)}</div>`; return; } addHistory(tid, data.title); renderTopicData(data, cid, catPage, postPage); } catch (error) { container.innerHTML = `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-red-500 font-medium border border-slate-200">שגיאה בטעינת תוכן הפוסט.</div>`; } } function renderTopicData(data, cid, catPage, postPage) { const container = document.getElementById('main-container'); let backAction = cid ? `loadCategory('${cid}', ${catPage})` : `loadHome()`; const tid = data.tid || ''; const title = data.title || 'תוכן הפוסט'; const bookmarked = isBookmarked(tid); let html = ` <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6 no-print"> <button onclick="${backAction}" class="self-start bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 px-4 py-2.5 rounded-xl text-sm font-semibold transition shadow-sm cursor-pointer flex items-center gap-2"> ← חזרה אחורה </button> <div class="flex flex-wrap items-center gap-2"> <button onclick="window.print()" class="bg-blue-50 hover:bg-blue-100 text-blue-700 border border-blue-200 px-3.5 py-2 rounded-xl text-xs font-bold transition shadow-sm cursor-pointer"> 🖨️ הדפסה נקייה </button> <button onclick="toggleBookmark('${tid}', '${escapeHtml(title)}'); renderTopicData(dataForRender, '${cid}', ${catPage}, ${postPage});" class="bg-white hover:bg-slate-50 ${bookmarked ? 'text-amber-500 border-amber-300' : 'text-slate-600 border-slate-200'} px-3.5 py-2 rounded-xl text-xs font-bold transition shadow-sm cursor-pointer"> ${bookmarked ? '📌 מוסר ממועדפים' : '📍 שמור במועדפים'} </button> </div> </div> <div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6 sm:p-8 mb-6"> <h2 class="text-2xl font-black text-slate-900 tracking-tight leading-snug">${escapeHtml(title)}</h2> </div> `; window.dataForRender = data; if (data && data.posts && Array.isArray(data.posts) && data.posts.length > 0) { data.posts.forEach((post, index) => { const authorName = (post.user && post.user.username) ? post.user.username : 'משתמש אנונימי'; const likesCount = (post.upvotes !== undefined) ? post.upvotes : (post.votes || 0); const initial = authorName.charAt(0).toUpperCase(); html += ` <div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6 sm:p-8 mb-4 relative overflow-hidden"> <div class="flex items-center justify-between mb-6 pb-4 border-b border-slate-100"> <div class="flex items-center gap-3"> <div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-blue-600 to-indigo-600 text-white font-bold flex items-center justify-center shadow-md text-sm"> ${escapeHtml(initial)} </div> <div> <h4 class="font-bold text-slate-800 text-sm">${escapeHtml(authorName)}</h4> <span class="text-xs text-slate-400">תגובה מספר #${index + 1}</span> </div> </div> <div class="bg-rose-50 text-rose-600 border border-rose-100 text-xs px-3.5 py-1.5 rounded-xl font-bold flex items-center gap-1.5 shadow-xs"> ❤️ ${likesCount} לייקים </div> </div> <div class="post-content text-slate-700 leading-relaxed space-y-4 text-base" style="font-size: ${currentFontSizeLevel}px;"> ${post.content || ''} </div> </div> `; }); } else { html += `<div class="bg-white p-12 rounded-2xl shadow-sm text-center text-slate-400 font-medium border border-slate-200">אין תגובות זמינות להצגה.</div>`; } container.innerHTML = html; setupCopyButtons(); } function setupCopyButtons() { const pres = document.querySelectorAll('main pre'); pres.forEach(pre => { if (pre.querySelector('.copy-code-btn')) return; const button = document.createElement('button'); button.className = 'copy-code-btn absolute top-3 left-3 bg-slate-700 hover:bg-slate-600 text-slate-200 text-xs px-3 py-1.5 rounded-lg font-medium transition shadow-sm cursor-pointer no-print'; button.innerHTML = '📋 העתק'; button.addEventListener('click', () => { const code = pre.querySelector('code') || pre; navigator.clipboard.writeText(code.innerText).then(() => { button.innerHTML = '✅ הועתק!'; setTimeout(() => { button.innerHTML = '📋 העתק'; }, 2000); }); }); pre.appendChild(button); }); } function escapeHtml(text) { if (!text) return ''; return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); } loadHome(); </script> </body> </html> -
@מ.ל.ל
לדף למתמחים פון יש הרבה באגים אני יעבוד על זה בהמשך (אתה גם יכול) -
@הדיבל-המעופף תודה אני ינסה עם ה GPT
-
@מ.ל.ל
לדעתי קלוד עדיף נתתי לו לשפר את הדף של ימות ונראה שהוא מבין ענין(הוא עדיין באמצע אני על חינמי ועשיתי מדול מאוד חזק) -
-
@פלוס
אין לי בעיה
אבל אני פשוט עכשיו חייב להתנתק כי אני צריך לצאת למקום מסוים
אפשרי בערב? -
@שואל-שאלה
כן. -
ניסיתי לעשות את זה אבל בגוגל סקריפט אני מנסה שוב ושוב לעשות פריסה וכותב לי משהו השתבש צריך לטעון את הדף מחדש כדי לנסות שוב
וגם אם אני טוען מחדש שוב ושוב זה עדין כותב ככה בפריסה
מישהו יודע למה זה קורה?? -
@מ-ש אתה אם נטפרי ? אם כן זה חסימה שלהם צריך לבקש מהם לפתוח על ידי ההקלטת תעבורה והסרטת מסך
-
העלאתי גירסא חדשה עם טעינת פוסטים אחרונים, חיפוש, ועוד...
שלום! נראה שהשיחה הזו מעניינת אותך, אבל עדיין אין לך חשבון.
נמאס לכם לגלול בין אותם הפוסטים בכל ביקור? כשנרשמים לחשבון, תמיד תחזרו בדיוק למקום שבו הייתם קודם, ותוכלו לבחור לקבל התראות על תגובות חדשות (בין אם במייל, ובין אם בהתראת פוש). תוכלו גם לשמור סימניות ולפרגן ב-upvote לפוסטים כדי להביע הערכה לחברי קהילה אחרים.
בעזרת התרומה שלך, הפוסט הזה יכול להיות אפילו טוב יותר 💗
הרשמה התחברות