diff --git a/assets/scripts/search.js b/assets/scripts/search.js index 64e5b2f..5670bde 100755 --- a/assets/scripts/search.js +++ b/assets/scripts/search.js @@ -2,45 +2,37 @@ let lunrIndex; let documents = []; let activeIndex = -1; let currentResults = []; -let isSyncingHeaderInput = false; -let suppressHeaderFocus = false; let searchReady = false; const searchInput = document.getElementById("search-input"); const searchBtn = document.getElementById("search-btn"); const searchForm = searchInput?.closest("form"); +const searchContainer = searchInput?.closest(".site-actions"); -const modal = document.createElement("div"); -modal.id = "search-modal"; -modal.setAttribute("aria-hidden", "true"); -modal.innerHTML = ` -
- +const searchPanel = document.createElement("section"); +searchPanel.id = "search-inline-panel"; +searchPanel.className = "search-inline-panel"; +searchPanel.setAttribute("aria-hidden", "true"); +searchPanel.setAttribute("aria-labelledby", "search-pane-title"); +searchPanel.innerHTML = ` +
+

Search

+ Start typing to search your notes. +
+
+ `; -document.body.appendChild(modal); -const pane = modal.querySelector(".search-modal-content"); -const paneInput = modal.querySelector("#search-pane-input"); -const modalResults = modal.querySelector("#search-modal-results"); -const paneCount = modal.querySelector("#search-pane-count"); +if (searchContainer) { + searchContainer.appendChild(searchPanel); +} + +const inlineResults = searchPanel.querySelector("#search-inline-results"); +const paneCount = searchPanel.querySelector("#search-pane-count"); function extractDocuments(node, currentPath = "") { if (node.type === "folder" && node.children) { @@ -80,7 +72,7 @@ async function initSearch() { extractDocuments(json); buildIndex(); searchReady = true; - if (isSearchOpen()) renderResults(paneInput.value); + if (isSearchOpen()) renderResults(searchInput.value); } catch (error) { console.warn("Search index could not be loaded", error); if (paneCount) paneCount.textContent = "Search is unavailable right now."; @@ -115,14 +107,14 @@ function buildSnippet(content, terms, radius = 90) { } if (index === -1) { - return content.slice(0, radius * 2) + "…"; + return content.slice(0, radius * 2) + "..."; } const start = Math.max(0, index - radius); const end = Math.min(content.length, index + matchedTerm.length + radius); - const prefix = start > 0 ? "…" : ""; - const suffix = end < content.length ? "…" : ""; + const prefix = start > 0 ? "..." : ""; + const suffix = end < content.length ? "..." : ""; return prefix + content.slice(start, end) + suffix; } @@ -179,66 +171,41 @@ function search(query) { .filter(Boolean); } -function setHeaderSearchValue(value, dispatchInput = false) { - if (!searchInput) return; +function isSearchOpen() { + return searchContainer?.classList.contains("search-inline-open"); +} - isSyncingHeaderInput = true; - searchInput.value = value; - isSyncingHeaderInput = false; +function openSearchPane(initialValue = null, focusInput = true) { + if (!searchContainer || !searchInput) return; - if (dispatchInput) { - searchInput.dispatchEvent(new Event("input", { bubbles: true })); + if (initialValue !== null) { + searchInput.value = initialValue; + } + + searchContainer.classList.add("search-inline-open"); + searchPanel.setAttribute("aria-hidden", "false"); + searchBtn?.setAttribute("aria-expanded", "true"); + renderResults(searchInput.value); + + if (focusInput) { + searchInput.focus({ preventScroll: true }); + try { + searchInput.setSelectionRange(searchInput.value.length, searchInput.value.length); + } catch (_) {} } } -function isSearchOpen() { - return modal.classList.contains("is-open"); -} - -function focusPaneInput() { - if (!isSearchOpen()) return; - paneInput.focus({ preventScroll: true }); - try { - paneInput.setSelectionRange(paneInput.value.length, paneInput.value.length); - } catch (_) {} -} - -function schedulePaneInputFocus() { - focusPaneInput(); - window.requestAnimationFrame(() => { - focusPaneInput(); - window.requestAnimationFrame(focusPaneInput); - }); -} - -function openSearchPane(initialValue = "") { - const value = initialValue || searchInput?.value || paneInput?.value || ""; - - modal.classList.add("is-open"); - modal.setAttribute("aria-hidden", "false"); - document.documentElement.classList.add("search-open"); - - paneInput.value = value; - setHeaderSearchValue(value); - renderResults(value); - - schedulePaneInputFocus(); -} - function closeSearchPane() { - modal.classList.remove("is-open"); - modal.setAttribute("aria-hidden", "true"); - document.documentElement.classList.remove("search-open"); + if (!searchContainer) return; + + searchContainer.classList.remove("search-inline-open"); + searchPanel.setAttribute("aria-hidden", "true"); + searchBtn?.setAttribute("aria-expanded", "false"); activeIndex = -1; - suppressHeaderFocus = true; - searchInput?.focus(); - window.setTimeout(() => { - suppressHeaderFocus = false; - }, 0); } function getResultItems() { - return Array.from(modalResults.querySelectorAll(".search-modal-result")); + return Array.from(inlineResults.querySelectorAll(".search-result")); } function updateActiveResult(focusActive = false) { @@ -255,7 +222,7 @@ function updateActiveResult(focusActive = false) { block: "nearest", behavior: "smooth", }); - if (focusActive) focusPaneInput(); + if (focusActive) searchInput?.focus({ preventScroll: true }); } } @@ -282,11 +249,11 @@ function renderResults(query) { const trimmed = query.trim(); currentResults = trimmed.length >= 2 ? search(trimmed) : []; activeIndex = currentResults.length ? 0 : -1; - modalResults.innerHTML = ""; + inlineResults.innerHTML = ""; if (!trimmed) { paneCount.textContent = "Start typing to search your notes."; - modalResults.innerHTML = ` + inlineResults.innerHTML = `
Try a topic, page title, or phrase from a note.
@@ -296,7 +263,7 @@ function renderResults(query) { if (trimmed.length < 2) { paneCount.textContent = "Keep typing."; - modalResults.innerHTML = ` + inlineResults.innerHTML = `
Use at least two characters.
@@ -306,7 +273,7 @@ function renderResults(query) { if (!searchReady) { paneCount.textContent = "Loading search index."; - modalResults.innerHTML = ` + inlineResults.innerHTML = `
Loading results...
@@ -316,7 +283,7 @@ function renderResults(query) { if (!currentResults.length) { paneCount.textContent = "No results found."; - modalResults.innerHTML = ` + inlineResults.innerHTML = `
No matches for "${escapeHtml(trimmed)}".
@@ -328,26 +295,28 @@ function renderResults(query) { currentResults.slice(0, 20).forEach((result, i) => { const item = document.createElement("a"); - item.className = "search-modal-result"; + item.className = "search-result"; item.href = result.path; item.dataset.index = i; item.setAttribute("role", "option"); item.setAttribute("aria-selected", String(i === activeIndex)); item.innerHTML = ` - ${result.title} - ${result.preview} - ${escapeHtml(result.path)} + ${result.title} + ${result.preview} + ${escapeHtml(result.path)} `; item.addEventListener("mouseenter", () => setActiveResult(i)); - modalResults.appendChild(item); + inlineResults.appendChild(item); }); updateActiveResult(); } function handleSearchKeydown(e) { + if (!isSearchOpen()) return; + const items = getResultItems(); switch (e.key) { @@ -376,13 +345,16 @@ function handleSearchKeydown(e) { break; case "Enter": - e.preventDefault(); - openActiveResult(); + if (items.length) { + e.preventDefault(); + openActiveResult(); + } break; case "Escape": e.preventDefault(); closeSearchPane(); + searchInput?.focus({ preventScroll: true }); break; } } @@ -397,69 +369,48 @@ function shouldUseSlashShortcut(event) { return event.key === "/" && !isEditable && !event.metaKey && !event.ctrlKey && !event.altKey; } -if (searchInput && searchBtn && paneInput && modalResults && paneCount) { - const keepFocusForModal = (e) => e.preventDefault(); - const openFromHeaderControl = (e) => { - e.preventDefault(); - openSearchPane(searchInput.value); - }; +if (searchInput && searchBtn && searchContainer && inlineResults && paneCount) { + searchBtn.setAttribute("aria-controls", "search-inline-panel"); + searchBtn.setAttribute("aria-expanded", "false"); - searchInput.addEventListener("pointerdown", keepFocusForModal); searchInput.addEventListener("focus", () => { - if (!suppressHeaderFocus) openSearchPane(searchInput.value); + openSearchPane(null, false); }); - searchInput.addEventListener("click", openFromHeaderControl); searchInput.addEventListener("input", () => { - if (isSyncingHeaderInput) return; - if (!isSearchOpen()) openSearchPane(searchInput.value); - paneInput.value = searchInput.value; - renderResults(searchInput.value); + openSearchPane(null, false); }); - paneInput.addEventListener("input", () => { - setHeaderSearchValue(paneInput.value, true); - renderResults(paneInput.value); + searchInput.addEventListener("keydown", handleSearchKeydown); + inlineResults.addEventListener("keydown", handleSearchKeydown); + + searchBtn.addEventListener("click", (e) => { + e.preventDefault(); + openSearchPane(null, true); }); - paneInput.addEventListener("keydown", handleSearchKeydown); - modalResults.addEventListener("keydown", handleSearchKeydown); - - searchBtn.addEventListener("pointerdown", keepFocusForModal); - searchBtn.addEventListener("click", openFromHeaderControl); - - modal.addEventListener("click", (e) => { - if (e.target.hasAttribute("data-search-close")) { - closeSearchPane(); - } - }); - - pane.addEventListener("click", (e) => { - if (e.target.closest("[data-search-close]")) closeSearchPane(); - e.stopPropagation(); + document.addEventListener("click", (e) => { + if (!isSearchOpen()) return; + if (!searchContainer.contains(e.target)) closeSearchPane(); }); document.addEventListener("keydown", (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); - openSearchPane(searchInput.value); + openSearchPane(null, true); return; } if (shouldUseSlashShortcut(e)) { e.preventDefault(); - openSearchPane(""); - } - - if (e.key === "Escape" && isSearchOpen()) { - closeSearchPane(); + openSearchPane("", true); } }); window.addEventListener("pageshow", () => { searchInput.value = ""; - paneInput.value = ""; renderResults(""); + closeSearchPane(); }); window.addEventListener("beforeunload", () => { @@ -469,7 +420,8 @@ if (searchInput && searchBtn && paneInput && modalResults && paneCount) { if (searchForm) { searchForm.addEventListener("submit", (e) => { e.preventDefault(); - openSearchPane(searchInput.value); + openSearchPane(null, true); + openActiveResult(); }); } } diff --git a/assets/styles/misc.css b/assets/styles/misc.css index cfb7218..37576ec 100755 --- a/assets/styles/misc.css +++ b/assets/styles/misc.css @@ -282,119 +282,30 @@ time.countdown.expired { } } -#search-modal { - position: fixed; - inset: 0; - display: grid; - place-items: start center; - padding: clamp(1rem, 7vh, 4.5rem) 1rem 1rem; - opacity: 0; - pointer-events: none; - visibility: hidden; - z-index: 10000; - transition: opacity 150ms ease, visibility 150ms ease; -} - -#search-modal.is-open { - opacity: 1; - pointer-events: auto; - visibility: visible; -} - -.search-open { - overflow: hidden; -} - -.search-modal-backdrop { - position: absolute; - inset: 0; - background: - color-mix(in oklab, var(--bg) 34%, transparent); - backdrop-filter: blur(10px); -} - -.search-modal-content { +.site-actions { position: relative; - width: min(720px, 100%); - max-height: min(74vh, 780px); - display: grid; - grid-template-rows: auto auto minmax(0, 1fr) auto; +} + +.search-inline-panel { + position: absolute; + top: calc(100% + 0.55rem); + right: 0; + width: min(38rem, calc(100vw - 2rem)); + max-height: min(64vh, 34rem); + display: none; + grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; background: color-mix(in oklab, var(--surface) 94%, var(--bg)); border: 1px solid color-mix(in oklab, var(--border) 78%, var(--fg)); border-radius: 8px; box-shadow: - 0 24px 80px rgba(0, 0, 0, 0.28), + 0 18px 56px rgba(0, 0, 0, 0.22), 0 2px 12px rgba(0, 0, 0, 0.12); + z-index: 1000; } -.search-pane-head { +.site-actions.search-inline-open .search-inline-panel { display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: center; - gap: 0.6rem; - padding: 0.8rem; - border-bottom: 1px solid var(--border); -} - -.search-pane-field { - display: grid; - grid-template-columns: 1.9rem minmax(0, 1fr); - align-items: center; - min-width: 0; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 7px; - color: var(--muted); -} - -.search-pane-field:focus-within { - border-color: color-mix(in oklab, var(--accent) 58%, var(--border)); - box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 16%, transparent); -} - -.search-pane-icon { - display: grid; - place-items: center; - min-height: 2.7rem; - font-size: 1.1rem; -} - -#search-pane-input { - width: 100%; - min-width: 0; - min-height: 2.7rem; - padding: 0.45rem 0.75rem 0.45rem 0; - border: 0; - outline: 0; - background: transparent; - color: var(--fg); - font: 500 1rem/1.4 var(--font-body); -} - -#search-pane-input::placeholder { - color: var(--muted); -} - -.search-pane-close { - display: inline-grid; - place-items: center; - width: 2.7rem; - height: 2.7rem; - border: 1px solid var(--border); - border-radius: 7px; - background: var(--surface-soft); - color: var(--fg); - cursor: pointer; - font-size: 1.45rem; - line-height: 1; -} - -.search-pane-close:hover, -.search-pane-close:focus-visible { - outline: 0; - border-color: color-mix(in oklab, var(--accent) 58%, var(--border)); - background: var(--accent-bg); } .search-pane-meta { @@ -420,13 +331,13 @@ time.countdown.expired { } .search-pane-results { - min-height: 10rem; + min-height: 8rem; overflow-y: auto; overscroll-behavior: contain; padding: 0.45rem; } -.search-modal-result { +.search-result { display: grid; gap: 0.26rem; padding: 0.7rem 0.8rem; @@ -437,27 +348,27 @@ time.countdown.expired { text-decoration: none; } -.search-modal-result:hover, -.search-modal-result.active { +.search-result:hover, +.search-result.active { background: var(--accent-bg); border-color: color-mix(in oklab, var(--accent) 28%, var(--border)); text-decoration: none; } -.search-modal-title { +.search-result-title { color: var(--fg); font-weight: 600; font-size: 0.95rem; line-height: 1.25; } -.search-modal-preview { +.search-result-preview { color: var(--muted); font-size: 0.82rem; line-height: 1.35; } -.search-modal-path { +.search-result-path { color: color-mix(in oklab, var(--muted) 82%, var(--accent)); font-size: 0.72rem; line-height: 1.25; @@ -501,20 +412,45 @@ mark { border-radius: 3px; } -.search-modal-result.active mark { +.search-result.active mark { background: rgba(255, 230, 150, 0.9); } +@media (max-width: 700px) { + .site-actions.search-inline-open { + flex-wrap: wrap; + } + + .site-actions.search-inline-open .site-search { + display: block; + order: 1; + flex: 1 1 calc(100% - 2.75rem); + min-width: 0; + } + + .site-actions.search-inline-open #search-input { + display: block; + } + + .site-actions.search-inline-open #search-btn, + .site-actions.search-inline-open .theme-toggle { + order: 2; + } + + .search-inline-panel { + position: static; + order: 3; + flex: 1 1 100%; + width: 100%; + max-height: min(58vh, 30rem); + } + + .search-pane-results { + min-height: 7rem; + } +} + @media (max-width: 560px) { - #search-modal { - place-items: start stretch; - padding: 0.75rem; - } - - .search-modal-content { - max-height: calc(100vh - 1.5rem); - } - .search-pane-meta { align-items: flex-start; flex-direction: column; diff --git a/gitea-build-monitor.log b/gitea-build-monitor.log index 7692ead..2cc8455 100644 --- a/gitea-build-monitor.log +++ b/gitea-build-monitor.log @@ -88,3 +88,9 @@ 2026-05-15T01:00:46.6855824+01:00 [INFO] Running authoring server tests with /home/zaine/master-folder/projects/authoring-service/.venv/bin/python. 2026-05-15T01:00:46.7588788+01:00 [INFO] Authoring server tests passed: ran=0, failures=0, errors=0, skipped=0, exit=0 2026-05-15T01:00:47.0488357+01:00 [INFO] Sent authoring server test notification. +2026-05-15T16:01:11.3347620+01:00 [INFO] Prepared current build status for zaine/org_web: severity=Info, status=success +2026-05-15T16:01:11.3427026+01:00 [INFO] Fetching recent Gitea Actions runs for zaine/org_web. +2026-05-15T16:01:12.5234162+01:00 [INFO] Sent build status notification with 1 embed(s). +2026-05-15T16:01:12.5446013+01:00 [INFO] Running authoring server tests with /home/zaine/master-folder/projects/authoring-service/.venv/bin/python. +2026-05-15T16:01:12.6157940+01:00 [INFO] Authoring server tests passed: ran=0, failures=0, errors=0, skipped=0, exit=0 +2026-05-15T16:01:14.0052719+01:00 [INFO] Sent authoring server test notification. diff --git a/posts/posts-list.org b/posts/posts-list.org index 24ea907..b677ef8 100755 --- a/posts/posts-list.org +++ b/posts/posts-list.org @@ -4,7 +4,7 @@ See the categories: @@html:Categories@@ * Posts: -- [[file:career/career-list.org][Career List]] @@html:15-05-2026 15:58@@ +- [[file:career/career-list.org][Career List]] @@html:15-05-2026 16:04@@ - [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:11-03-2026 17:18@@ @@html:learning@@ @@html:notes@@ - [[file:career/javascript.org][Understands the Javascript language]] @@html:11-03-2026 16:52@@ @@html:learning@@ @@html:notes@@ - [[file:career/restful-api.org][Restful API]] @@html:15-02-2026 23:00@@ @@html:learning@@ @@html:notes@@ diff --git a/sitemap.org b/sitemap.org index b082557..14b544d 100755 --- a/sitemap.org +++ b/sitemap.org @@ -47,12 +47,12 @@ - [[file:tags/notes.org][Tag: notes]] - [[file:tags/review.org][Tag: review]] - [[file:tags/website.org][Tag: website]] - - [[file:tags/life.org][Tag: life]] - [[file:tags/update.org][Tag: update]] - - [[file:tags/insights.org][Tag: insights]] - - [[file:tags/emacs.org][Tag: emacs]] + - [[file:tags/life.org][Tag: life]] - [[file:tags/education.org][Tag: education]] + - [[file:tags/insights.org][Tag: insights]] - [[file:tags/reading.org][Tag: reading]] + - [[file:tags/emacs.org][Tag: emacs]] - [[file:tags/maths.org][Tag: maths]] - posts - [[file:posts/posts-intro.org][Posts Introduction]]