diff --git a/js/searchView.js b/js/searchView.js index 7f9422d6..0a9e92a9 100644 --- a/js/searchView.js +++ b/js/searchView.js @@ -54,9 +54,17 @@ const extractRelayHints = (event) => { ); }; +// Helper to create message elements for empty/loading/error states +function createMessageElement(text, className) { + const p = document.createElement('p'); + p.className = className || "text-sm text-muted col-span-full"; + p.textContent = text; + return p; +} + function renderProfileCards(profiles, container) { if (!container) return; - container.innerHTML = ""; + container.replaceChildren(); // Reset the "Show More" button and container limit state const showMoreContainer = document.getElementById("searchChannelShowMore"); @@ -66,7 +74,7 @@ function renderProfileCards(profiles, container) { container.classList.remove("channel-grid-limit"); if (!profiles || profiles.length === 0) { - container.innerHTML = `

No matching channels found.

`; + container.appendChild(createMessageElement("No matching channels found.")); return; } @@ -93,7 +101,7 @@ function renderProfileCards(profiles, container) { const metadata = profile.metadata || {}; const name = metadata.name || metadata.display_name || "Unknown User"; const picture = sanitizeProfileMediaUrl(metadata.picture) || FALLBACK_AVATAR; - const about = metadata.about || ""; + // const about = metadata.about || ""; // Unused in original code let npub = ""; try { @@ -110,25 +118,37 @@ function renderProfileCards(profiles, container) { card.href = `#view=channel-profile&npub=${npub || pubkey}`; card.className = "flex items-center gap-3 p-3 rounded-lg bg-surface hover:bg-surface-elevated transition-colors border border-border group"; - card.innerHTML = ` -
- ${escapeHTML(name)} -
-
-

- ${escapeHTML(name)} -

-

- ${escapeHTML(shortNpub)} -

-
- `; + // Create Image Container + const imgContainer = document.createElement("div"); + imgContainer.className = "relative w-12 h-12 flex-shrink-0 rounded-full overflow-hidden bg-background"; + + const img = document.createElement("img"); + img.src = picture; + img.alt = name; + img.className = "w-full h-full object-cover"; + img.loading = "lazy"; + img.addEventListener("error", (e) => { + e.target.src = FALLBACK_AVATAR; + }); + + imgContainer.appendChild(img); + card.appendChild(imgContainer); + + // Create Text Container + const textContainer = document.createElement("div"); + textContainer.className = "flex-1 min-w-0"; + + const nameEl = document.createElement("h3"); + nameEl.className = "text-sm font-semibold text-text-strong truncate group-hover:text-primary transition-colors"; + nameEl.textContent = name; + + const npubEl = document.createElement("p"); + npubEl.className = "text-xs text-muted truncate"; + npubEl.textContent = shortNpub; + + textContainer.appendChild(nameEl); + textContainer.appendChild(npubEl); + card.appendChild(textContainer); fragment.appendChild(card); } @@ -164,15 +184,15 @@ export async function initSearchView() { const videoList = document.getElementById("searchVideoList"); if (channelList) { - channelList.innerHTML = `

Searching channels...

`; + channelList.replaceChildren(createMessageElement("Searching channels...", "text-sm text-muted animate-pulse col-span-full")); } if (videoList) { - videoList.innerHTML = `

Searching videos...

`; + videoList.replaceChildren(createMessageElement("Searching videos...", "text-sm text-muted animate-pulse col-span-full")); } if (!query) { - if (channelList) channelList.innerHTML = `

Please enter a search term.

`; - if (videoList) videoList.innerHTML = `

Please enter a search term.

`; + if (channelList) channelList.replaceChildren(createMessageElement("Please enter a search term.")); + if (videoList) videoList.replaceChildren(createMessageElement("Please enter a search term.")); return; } @@ -185,7 +205,7 @@ export async function initSearchView() { }).catch(err => { devLogger.warn("[Search] Profile search failed", err); if (channelList && searchToken === currentSearchToken) { - channelList.innerHTML = `

Failed to load channels.

`; + channelList.replaceChildren(createMessageElement("Failed to load channels.", "text-sm text-critical col-span-full")); } }); @@ -197,7 +217,7 @@ export async function initSearchView() { }).catch(err => { devLogger.warn("[Search] Video search failed", err); if (videoList && searchToken === currentSearchToken) { - videoList.innerHTML = `

Failed to load videos.

`; + videoList.replaceChildren(createMessageElement("Failed to load videos.", "text-sm text-critical col-span-full")); } }); } @@ -255,7 +275,7 @@ function renderActiveFilters(filters) { return; } - list.innerHTML = ""; + list.replaceChildren(); const pills = []; filters.authorPubkeys?.forEach((author) => { @@ -413,10 +433,10 @@ function renderActiveFilters(filters) { async function renderSearchVideos(videos, container, app) { if (!container) return; - container.innerHTML = ""; + container.replaceChildren(); if (!videos || videos.length === 0) { - container.innerHTML = `

No matching videos found.

`; + container.appendChild(createMessageElement("No matching videos found.")); return; } diff --git a/scripts/check-innerhtml.mjs b/scripts/check-innerhtml.mjs index d6ced3d1..f4890bf5 100644 --- a/scripts/check-innerhtml.mjs +++ b/scripts/check-innerhtml.mjs @@ -31,7 +31,6 @@ const BASELINE = { "js/historyView.js": 4, "js/index.js": 1, "js/kidsView.js": 1, - "js/searchView.js": 12, "js/subscriptions.js": 7, "js/ui/components/DeleteModal.js": 3, "js/ui/components/EditModal.js": 1, diff --git a/scripts/verify-search.py b/scripts/verify-search.py new file mode 100644 index 00000000..bd7c170e --- /dev/null +++ b/scripts/verify-search.py @@ -0,0 +1,48 @@ +from playwright.sync_api import sync_playwright +import os + +def run(playwright): + browser = playwright.chromium.launch(headless=True) + context = browser.new_context() + + # Inject localStorage to bypass disclaimer + page = context.new_page() + page.add_init_script(""" + localStorage.setItem("hasSeenDisclaimer", "true"); + """) + + # Capture console logs + page.on("console", lambda msg: print(f"Browser console: {msg.text}")) + + try: + # Navigate to the correct search URL format + page.goto("http://localhost:3000/#view=search&q=test", wait_until="networkidle") + print("Navigated to page") + + # Wait for #searchTitle + print("Waiting for #searchTitle") + page.wait_for_selector("#searchTitle", timeout=10000) + print("Found #searchTitle") + + # Wait for results or "no results" message + try: + # My refactor uses

with text content + page.wait_for_selector("#searchVideoList p", timeout=10000) + print("Found search results message") + except: + print("Timeout waiting for search results message") + + # Take a screenshot + os.makedirs("verification", exist_ok=True) + page.screenshot(path="verification/search-results.png") + print("Screenshot saved") + + except Exception as e: + print(f"Error: {e}") + page.screenshot(path="verification/error.png") + + finally: + browser.close() + +with sync_playwright() as playwright: + run(playwright) diff --git a/verification/error.png b/verification/error.png new file mode 100644 index 00000000..ac4268ce Binary files /dev/null and b/verification/error.png differ diff --git a/verification/search-results.png b/verification/search-results.png new file mode 100644 index 00000000..96417b5d Binary files /dev/null and b/verification/search-results.png differ