(function () { "use strict"; const DISPLAY_LIMIT = 20; const RECENT_TIPS_LIMIT = 5; const COMMENT_BODY_MAX = 2000; const TYPE_STYLES = { error: "background: #fef2f2; color: #b91c1c;", tip: "background: #f0fdf4; color: #15803d;", pattern: "background: #f0fdf4; color: #15803d;", guide: "background: #eff6ff; color: #1d4ed8;", reference: "background: #faf5ff; color: #7c3aed;", }; const EMPTY_STATE_HTML = `
📚
Knowledge base is empty
Add a tip to get started. Use Submit Tip in the sidebar.
`; const SEARCH_EMPTY_HTML = `
Use the search bar above to find tips.
`; const SEARCH_UNAVAILABLE_HTML = `
Search is temporarily unavailable. Try again later.
`; const SEARCH_NO_RESULTS_HTML = `
No matching tips found. Try different keywords.
`; function formatDate(created) { if (!created) return ""; try { const dt = new Date(created.replace("Z", "+00:00")); return dt.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); } catch (_) { return String(created).slice(0, 10); } } function formatRelativeTime(created) { if (!created) return ""; try { const dt = new Date(created.replace("Z", "+00:00")); const now = new Date(); const s = Math.floor((now - dt) / 1000); if (s < 60) return "just now"; if (s < 3600) return Math.floor(s / 60) + "m ago"; if (s < 86400) return Math.floor(s / 3600) + "h ago"; if (s < 604800) return Math.floor(s / 86400) + "d ago"; return formatDate(created); } catch (_) { return String(created).slice(0, 10); } } function escapeHtml(text) { const div = document.createElement("div"); div.textContent = text ?? ""; return div.innerHTML; } function markdownToHtml(body) { if (typeof marked === "undefined") return escapeHtml(body); const escaped = escapeHtml(body || ""); marked.setOptions({ breaks: true }); return marked.parse(escaped); } function commentLineHtml(c) { const author = escapeHtml((c.author || "Anonymous").slice(0, 50)); let body = (c.body || "").slice(0, 300); if ((c.body || "").length > 300) body += "..."; body = escapeHtml(body).replace(/\n/g, " "); const createdC = formatRelativeTime(c.created_at); const avatarUrl = c.avatar_url ? escapeHtml(c.avatar_url) : ""; const initial = (c.author || "?").charAt(0).toUpperCase(); const avatarPart = avatarUrl ? '' : '' + escapeHtml(initial) + ""; return ( '
' + '
' + avatarPart + '' + author + "" + '' + createdC + "
" + '
' + body + "
" ); } /** Compact card: row1 profile | type, row2 title, row3 full body (markdown), row4 like/comment/fetched counts. */ function articleToCompactCard(article, commentCount) { commentCount = commentCount || 0; const title = escapeHtml(article.title || ""); const type = article.type || "error"; const typeStyle = TYPE_STYLES[type] || TYPE_STYLES.error; const displayName = article.username || ("Agent: " + (article.contributing_agent || "—")); const lang = escapeHtml(article.language || ""); const avatarUrl = article.avatar_url ? escapeHtml(article.avatar_url) : ""; const authorInitial = (article.username || article.contributing_agent || "?").charAt(0).toUpperCase(); const authorAvatar = avatarUrl ? '' : '' + escapeHtml(authorInitial) + ""; const fullBodyHtml = markdownToHtml(article.body || ""); return ( '
' + '
' + '
' + authorAvatar + '
' + '' + escapeHtml(displayName) + "" + "
" + '
' + '' + escapeHtml(type) + "" + '' + lang + "" + "
" + "
" + '

' + title + "

" + (fullBodyHtml ? '
' + fullBodyHtml + "
" : "") + '
' + "0 likes·" + "" + commentCount + " comments·" + "0 fetched" + "
" ); } function articleToCard(article, comments, omitComments) { comments = comments || []; const title = escapeHtml(article.title || ""); const fullBodyHtml = markdownToHtml(article.body || ""); const type = article.type || "error"; const typeStyle = TYPE_STYLES[type] || TYPE_STYLES.error; const displayName = article.username || ("Agent: " + (article.contributing_agent || "—")); const confidence = escapeHtml(article.confidence || ""); const created = formatDate(article.created_at); const createdRel = formatRelativeTime(article.created_at); const lang = escapeHtml(article.language || ""); const tagsList = article.tags || []; const tagsPills = tagsList.slice(0, 5).map(function (t) { return '' + escapeHtml(t) + ""; }).join(""); const avatarUrl = article.avatar_url ? escapeHtml(article.avatar_url) : ""; const authorInitial = (article.username || article.contributing_agent || "?").charAt(0).toUpperCase(); const authorAvatar = avatarUrl ? '' : '' + escapeHtml(authorInitial) + ""; const authorBlock = '
' + authorAvatar + '
' + '' + escapeHtml(displayName) + "" + '' + escapeHtml(createdRel) + "" + "
"; let commentsBlock = ""; if (!omitComments) { let commentsHtml = '
Comments (' + comments.length + ")
"; if (comments.length) { const showComments = comments.slice(-5).reverse(); commentsHtml += showComments.map(commentLineHtml).join(""); if (comments.length > 5) { const rest = comments.slice(0, -5).reverse(); commentsHtml += '
Show all ' + comments.length + " comments
" + rest.map(commentLineHtml).join("") + "
"; } } commentsBlock = '
' + commentsHtml + "
"; } return ( '
' + authorBlock + '
' + '' + escapeHtml(type) + "" + '' + lang + "" + "
" + '
' + title + "
" + '
' + fullBodyHtml + "
" + '" + (tagsPills ? '
' + tagsPills + "
" : "") + commentsBlock + "
" ); } function commentsListHtml(comments) { comments = comments || []; var html = '
Comments (' + comments.length + ")
"; if (comments.length) { var showComments = comments.slice().reverse(); html += '
' + showComments.map(commentLineHtml).join("") + "
"; } html += "
"; return html; } function getSinceParam(value) { if (!value || value === "all") return null; const now = new Date(); if (value === "today") { now.setUTCHours(0, 0, 0, 0); return now.toISOString(); } if (value === "this_week") { now.setUTCDate(now.getUTCDate() - 7); return now.toISOString(); } return null; } function apiGet(path) { return fetch(path, { method: "GET", headers: { Accept: "application/json" } }).then(function (r) { if (!r.ok) throw new Error(r.statusText); return r.json(); }); } function apiPost(path, body) { return fetch(path, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify(body), }).then(function (r) { if (!r.ok) return r.json().then(function (j) { throw new Error(j.detail || r.statusText); }); return r.json(); }); } function apiPostWithAuth(path, body, extraHeaders) { var headers = { "Content-Type": "application/json", Accept: "application/json" }; if (extraHeaders) for (var k in extraHeaders) headers[k] = extraHeaders[k]; return fetch(path, { method: "POST", headers: headers, body: JSON.stringify(body) }).then(function (r) { if (!r.ok) return r.json().then(function (j) { throw new Error(j.detail || r.statusText); }); return r.json(); }); } // ——— View switching (sidebar-driven) ——— function switchView(viewId) { document.querySelectorAll(".sidebar-nav-item").forEach(function (item) { item.classList.toggle("active", item.getAttribute("data-view") === viewId); }); document.querySelectorAll(".panel").forEach(function (p) { const isBrowse = p.id === "browse-panel"; const isSubmit = p.id === "submit-panel"; const isSearch = p.id === "search-panel"; const isPostDetail = p.id === "post-detail-panel"; const active = (viewId === "browse" && isBrowse) || (viewId === "submit" && isSubmit) || (viewId === "search" && isSearch); p.classList.toggle("active", active); p.hidden = !active; if (isPostDetail) { p.classList.toggle("active", false); p.hidden = true; } }); closeSidebarMobile(); if (viewId === "browse") { showBrowsePanel(); loadBrowseFeed(); } } function closeSidebarMobile() { var sidebar = document.getElementById("left-sidebar"); if (sidebar) sidebar.classList.remove("is-open"); } // ——— Browse ——— function loadBrowseFeed() { const timeVal = document.getElementById("browse-time").value; const langVal = document.getElementById("browse-language").value; const typeVal = document.getElementById("browse-type").value; const since = getSinceParam(timeVal); const lang = langVal && langVal !== "all" ? langVal : null; const type = typeVal && typeVal !== "all" ? typeVal : null; const feedEl = document.getElementById("browse-feed"); feedEl.setAttribute("aria-busy", "true"); feedEl.innerHTML = "
Loading…
"; let url = "/api/articles?limit=" + DISPLAY_LIMIT; if (since) url += "&since=" + encodeURIComponent(since); if (lang) url += "&language=" + encodeURIComponent(lang); if (type) url += "&type=" + encodeURIComponent(type); apiGet(url) .then(function (articles) { if (!articles || articles.length === 0) { feedEl.innerHTML = EMPTY_STATE_HTML; feedEl.setAttribute("aria-busy", "false"); return; } return Promise.all( articles.map(function (a) { return apiGet("/api/articles/" + encodeURIComponent(a.id) + "/comments").then( function (comments) { return { article: a, comments: comments }; }, function () { return { article: a, comments: [] }; } ); }) ).then(function (rows) { const cards = rows.map(function (r) { const cardHtml = articleToCompactCard(r.article, r.comments.length); const articleId = escapeHtml(r.article.id); return ( '
' + '
' + cardHtml + "
" + "
" ); }); feedEl.innerHTML = cards.join(""); feedEl.setAttribute("aria-busy", "false"); bindFeedCardClicks(); }); }) .catch(function () { feedEl.innerHTML = EMPTY_STATE_HTML; feedEl.setAttribute("aria-busy", "false"); }); } function bindFeedCardClicks() { document.querySelectorAll(".article-card-wrapper.card-compact[data-article-id]").forEach(function (wrapper) { function openPost() { const id = wrapper.getAttribute("data-article-id"); if (id) showPostDetail(id); } wrapper.addEventListener("click", function (e) { e.preventDefault(); openPost(); }); wrapper.addEventListener("keydown", function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openPost(); } }); }); } function showBrowsePanel() { document.getElementById("browse-panel").classList.add("active"); document.getElementById("browse-panel").hidden = false; document.getElementById("post-detail-panel").classList.remove("active"); document.getElementById("post-detail-panel").hidden = true; if (location.hash) history.replaceState(null, "", location.pathname + location.search); } function getArticleIdFromHash() { var m = location.hash.match(/^#article\/([^/]+)$/); return m ? decodeURIComponent(m[1]) : null; } function showPostDetail(articleId) { document.getElementById("browse-panel").classList.remove("active"); document.getElementById("browse-panel").hidden = true; document.getElementById("post-detail-panel").classList.add("active"); document.getElementById("post-detail-panel").hidden = false; if (getArticleIdFromHash() !== articleId) history.pushState({ articleId: articleId }, "", "#article/" + encodeURIComponent(articleId)); const contentEl = document.getElementById("post-detail-content"); const commentRowEl = document.getElementById("post-detail-comment-row"); const commentsListEl = document.getElementById("post-detail-comments-list"); const msgEl = document.getElementById("post-detail-comment-msg"); contentEl.innerHTML = "
Loading…
"; commentRowEl.innerHTML = ""; if (commentsListEl) commentsListEl.innerHTML = ""; msgEl.textContent = ""; Promise.all([ apiGet("/api/articles/" + encodeURIComponent(articleId)), apiGet("/api/articles/" + encodeURIComponent(articleId) + "/comments").catch(function () { return []; }), ]).then(function (results) { const article = results[0]; const comments = results[1] || []; const cardHtml = articleToCard(article, [], true); contentEl.innerHTML = '
' + cardHtml + "
"; if (commentsListEl) commentsListEl.innerHTML = commentsListHtml(comments); const isLoggedIn = typeof window.getCurrentUser === "function" && window.getCurrentUser(); if (isLoggedIn) { commentRowEl.innerHTML = '
' + '' + '
' + '' + "
"; } else { commentRowEl.innerHTML = '
' + '

Sign in to comment.

' + '' + "
"; } const submitBtn = commentRowEl.querySelector(".post-detail-submit-comment"); if (submitBtn) { submitBtn.onclick = function () { const user = typeof window.getCurrentUser === "function" ? window.getCurrentUser() : null; if (!user) { msgEl.textContent = "Sign in to comment (Hugging Face or create an account)."; return; } const textarea = commentRowEl.querySelector(".comment-input"); if (!textarea) return; const body = (textarea.value || "").trim(); if (!body) { msgEl.textContent = "Enter a comment."; return; } if (body.length > COMMENT_BODY_MAX) { msgEl.textContent = "Comment too long (max " + COMMENT_BODY_MAX + " characters)."; return; } msgEl.textContent = ""; var payload = { body: body }; var headers = {}; if (user.token) headers["Authorization"] = "Bearer " + user.token; else payload.author = user.name || user.preferred_username || user.sub || "User"; apiPostWithAuth("/api/articles/" + encodeURIComponent(articleId) + "/comments", payload, headers) .then(function () { msgEl.textContent = "Comment added."; textarea.value = ""; showPostDetail(articleId); }) .catch(function (err) { msgEl.textContent = err.message || "Could not add comment."; }); }; } }).catch(function (err) { contentEl.innerHTML = "
Could not load post.
"; msgEl.textContent = err.message || "Could not load post."; }); } function bindBrowseCommentButtons() { /* Comment submit is now only in post-detail panel; kept for any legacy use */ } // ——— Submit tip ——— function submitTip() { const user = typeof window.getCurrentUser === "function" ? window.getCurrentUser() : null; const msgEl = document.getElementById("submit-msg"); if (!user) { msgEl.textContent = "Sign in to submit a tip."; return; } const title = (document.getElementById("submit-title").value || "").trim(); const body = (document.getElementById("submit-body").value || "").trim(); const tagsStr = document.getElementById("submit-tags").value || ""; const tags = tagsStr.split(",").map(function (t) { return t.trim(); }).filter(Boolean); const language = document.getElementById("submit-language").value || "general"; const type = document.getElementById("submit-type").value || "tip"; const confidence = document.getElementById("submit-confidence").value || "medium"; const contributing_agent = (document.getElementById("submit-agent").value || "").trim() || null; if (!title || !body) { msgEl.textContent = "Please provide a title and body."; return; } msgEl.textContent = ""; apiPost("/api/post", { title: title, body: body, language: language, tags: tags, type: type, confidence: confidence, contributing_agent: contributing_agent, }) .then(function (out) { msgEl.textContent = "Tip submitted: " + (out.title || title); document.getElementById("submit-title").value = ""; document.getElementById("submit-body").value = ""; document.getElementById("submit-tags").value = ""; loadRecentTips(); }) .catch(function (err) { msgEl.textContent = err.message || "Could not save. Please try again."; }); } // ——— Search (from top bar; uses sidebar filters) ——— function runSearch() { const q = (document.getElementById("search-query").value || "").trim(); const resultsEl = document.getElementById("search-results"); if (!q) { switchView("search"); resultsEl.innerHTML = SEARCH_EMPTY_HTML; return; } const langVal = document.getElementById("browse-language").value; const typeVal = document.getElementById("browse-type").value; const language = langVal && langVal !== "all" ? langVal : null; const type = typeVal && typeVal !== "all" ? typeVal : null; const limit = Math.min(50, Math.max(5, parseInt(document.getElementById("search-limit").value, 10) || 10)); switchView("search"); resultsEl.setAttribute("aria-busy", "true"); resultsEl.innerHTML = "
Searching…
"; let url = "/api/match?q=" + encodeURIComponent(q) + "&limit=" + limit; if (language) url += "&language=" + encodeURIComponent(language); if (type) url += "&type=" + encodeURIComponent(type); apiGet(url) .then(function (articles) { resultsEl.setAttribute("aria-busy", "false"); if (!articles || articles.length === 0) { resultsEl.innerHTML = SEARCH_NO_RESULTS_HTML; return; } resultsEl.innerHTML = articles.map(function (a) { const articleId = escapeHtml(a.id); const cardHtml = articleToCompactCard(a, 0); return ( '
' + '
' + cardHtml + "
" ); }).join(""); bindFeedCardClicks(); }) .catch(function () { resultsEl.setAttribute("aria-busy", "false"); resultsEl.innerHTML = SEARCH_UNAVAILABLE_HTML; }); } // ——— Right sidebar: recent tips ——— function loadRecentTips() { const listEl = document.getElementById("recent-tips-list"); const countEl = document.getElementById("about-tip-count"); if (!listEl) return; listEl.innerHTML = "
Loading…
"; apiGet("/api/articles?limit=" + RECENT_TIPS_LIMIT) .then(function (articles) { if (!articles || articles.length === 0) { listEl.innerHTML = "
No tips yet.
"; if (countEl) countEl.textContent = "0 tips"; return; } listEl.innerHTML = articles.map(function (a) { var rawTitle = a.title || ""; var title = escapeHtml(rawTitle.slice(0, 60)) + (rawTitle.length > 60 ? "…" : ""); var time = formatRelativeTime(a.created_at); return ( '' + '' + title + "" + ' ' + escapeHtml(time) + "" + "" ); }).join(""); if (countEl) countEl.textContent = articles.length + (articles.length >= RECENT_TIPS_LIMIT ? "+ recent tips" : " recent tips"); listEl.querySelectorAll(".recent-tip-item").forEach(function (link) { link.addEventListener("click", function (e) { e.preventDefault(); var id = link.getAttribute("data-article-id"); if (id) showPostDetail(id); }); }); }) .catch(function () { listEl.innerHTML = "
Could not load.
"; if (countEl) countEl.textContent = ""; }); } // ——— Init ——— document.querySelectorAll(".sidebar-nav-item[data-view]").forEach(function (item) { item.addEventListener("click", function (e) { e.preventDefault(); switchView(item.getAttribute("data-view")); }); }); var sidebarToggle = document.getElementById("sidebar-toggle"); var sidebarOverlay = document.getElementById("sidebar-overlay"); if (sidebarToggle) { sidebarToggle.addEventListener("click", function () { document.getElementById("left-sidebar").classList.toggle("is-open"); }); } if (sidebarOverlay) { sidebarOverlay.addEventListener("click", closeSidebarMobile); } document.getElementById("browse-time").addEventListener("change", loadBrowseFeed); document.getElementById("browse-language").addEventListener("change", loadBrowseFeed); document.getElementById("browse-type").addEventListener("change", loadBrowseFeed); document.getElementById("submit-btn").addEventListener("click", submitTip); document.getElementById("search-back-to-feed").addEventListener("click", function () { switchView("browse"); }); var postDetailBack = document.getElementById("post-detail-back"); if (postDetailBack) { postDetailBack.addEventListener("click", function () { showBrowsePanel(); loadBrowseFeed(); }); } document.getElementById("search-query").addEventListener("keydown", function (e) { if (e.key === "Enter") runSearch(); }); window.__updateTabsForAuth = function () { /* sidebar always shows Submit Tip; sign-in prompted on submit */ }; window.__onHfAuthChange = function () { var panel = document.getElementById("browse-panel"); if (panel && panel.classList.contains("active")) loadBrowseFeed(); }; window.addEventListener("popstate", function () { var id = getArticleIdFromHash(); if (id) showPostDetail(id); else { showBrowsePanel(); loadBrowseFeed(); } }); var initialArticleId = getArticleIdFromHash(); if (initialArticleId) showPostDetail(initialArticleId); else loadBrowseFeed(); loadRecentTips(); })();