diff --git a/src/build/assets/search.js b/src/build/assets/search.js index 734868c..69160d0 100644 --- a/src/build/assets/search.js +++ b/src/build/assets/search.js @@ -18,26 +18,95 @@ let searchInitialized = false; let delayedUpdateInterval = null; function initializeResults() { - for (const item of SITE_ITEMS) { + // Create the search result element for the video inside the DOM, store a + // reference to it in the siteContent tree + function initializeVideo(video) { let image; - if (item.image) { + if (video.image) { image = document.createElement('img'); - image.src = rootPrefix + item.url + item.image; + image.src = rootPrefix + video.sitePath + video.image; } else { image = document.createElement('span'); image.classList.add('placeholder'); } const spanText = document.createElement('span'); - spanText.dataset.searchable = 'true'; - spanText.textContent = item.title; + spanText.textContent = video.title; - const aRow = document.createElement('a'); - aRow.href = rootPrefix + item.url + indexSuffix; - aRow.appendChild(image); - aRow.appendChild(spanText); + video.element = document.createElement('a'); + video.element.classList.add('video'); + video.element.href = rootPrefix + video.sitePath + indexSuffix; + video.element.appendChild(image); + video.element.appendChild(spanText); - searchResults.appendChild(aRow); + searchResults.appendChild(video.element); + } + + // Create the search result element for the playlist inside the DOM, store + // a reference to it in the siteContent tree + function initializePlaylist(playlist, ancestors = []) { + const traversal = [...ancestors, playlist]; + + const spanText = document.createElement('span'); + spanText.textContent = traversal.map(ancestor => ancestor.title).join(' > '); + + playlist.element = document.createElement('a'); + playlist.element.classList.add('playlist'); + playlist.element.href = rootPrefix + playlist.sitePath + indexSuffix; + playlist.element.appendChild(spanText); + + searchResults.appendChild(playlist.element); + + for (const video of playlist.videos) { + initializeVideo(video); + } + + } + + // Create the search result element for the collection inside the DOM, + // store a reference to it in the siteContent tree + function initializeCollection(collection, ancestors = []) { + const traversal = [...ancestors, collection]; + + const spanText = document.createElement('span'); + spanText.textContent = traversal.map(ancestor => ancestor.title).join(' > '); + + collection.element = document.createElement('a'); + collection.element.classList.add('collection'); + collection.element.href = rootPrefix + collection.sitePath + indexSuffix; + collection.element.appendChild(spanText); + + searchResults.appendChild(collection.element); + + for (const video of collection.videos) { + initializeVideo(video); + } + + for (const playlist of collection.playlists) { + initializePlaylist(playlist, traversal); + } + + for (const subcollection of collection.subcollections) { + initializeCollection(subcollection, traversal); + } + } + + if (siteContent.collection) { + for (const video of siteContent.collection.videos) { + initializeVideo(video); + } + + for (const playlist of siteContent.collection.playlists) { + initializePlaylist(playlist); + } + + for (const subcollection of siteContent.collection.subcollections) { + initializeCollection(subcollection); + } + } else /* if (siteContent.playlist) */ { + for (const video of siteContent.playlist.videos) { + initializeVideo(video); + } } searchInitialized = true; @@ -58,17 +127,69 @@ function updateResults() { const regexp = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); let shown = 0; - for (const row of searchResults.children) { - const title = row.querySelector('[data-searchable]').textContent; - const display = regexp.test(title); - row.style.setProperty('display', display ? null : 'none'); + function updateVideo(video) { + const display = regexp.test(video.title); + video.element.classList.toggle('visible', display); if (display) { shown += 1; } + return display; + } + + function updatePlaylist(playlist) { + let display = regexp.test(playlist.title); + if (display) { shown += 1; } + + for (const video of playlist.videos) { + display = updateVideo(video) || display; + } + + playlist.element.classList.toggle('visible', display); + return display; + } + + function updateCollection(collection) { + let display = regexp.test(collection.title); + if (display) { shown += 1; } + + for (const video of collection.videos) { + display = updateVideo(video) || display; + } + + for (const playlist of collection.playlists) { + updatePlaylist(playlist); + } + + for (const subcollection of collection.subcollections) { + updateCollection(subcollection); + } + + collection.element.classList.toggle('visible', display); + return display; + } + + if (siteContent.collection) { + for (const video of siteContent.collection.videos) { + updateVideo(video); + } + + for (const playlist of siteContent.collection.playlists) { + updatePlaylist(playlist); + } + + for (const subcollection of siteContent.collection.subcollections) { + updateCollection(subcollection); + } + } else { + for (const video of siteContent.playlist.videos) { + updateVideo(video); + } } if (shown === 0) { + searchResults.style.setProperty('display', 'none'); statusField.removeAttribute('aria-label'); statusField.textContent = SEARCH_JS_T.nothingFoundForXxx(query); } else { + searchResults.style.setProperty('display', null); statusField.setAttribute('aria-label', SEARCH_JS_T.showingXxxResultsForXxx(shown, query)); statusField.textContent = ''; } @@ -109,7 +230,7 @@ searchInput.addEventListener('input', () => { searchContainer.addEventListener('keydown', event => { if (event.key === 'ArrowUp') { - const visibleResults = [...searchResults.children].filter(row => row.style.display !== 'none'); + const visibleResults = [...searchResults.querySelectorAll('a.visible')]; if (visibleResults.length) { const targetRowIndex = visibleResults.indexOf(event.target); @@ -124,7 +245,7 @@ searchContainer.addEventListener('keydown', event => { } event.preventDefault(); } else if (event.key === 'ArrowDown') { - const visibleResults = [...searchResults.children].filter(row => row.style.display !== 'none'); + const visibleResults = [...searchResults.querySelectorAll('a.visible')]; if (visibleResults.length) { const targetRowIndex = visibleResults.indexOf(event.target); diff --git a/src/build/assets/site.css b/src/build/assets/site.css index 88c780f..958827a 100644 --- a/src/build/assets/site.css +++ b/src/build/assets/site.css @@ -331,27 +331,6 @@ img { @media (max-aspect-ratio: 2/3) { .player_wrapper:not(:fullscreen) video { max-height: 76dvh; } } -.playlist { - display: flex; - flex-direction: column; -} -.playlist > a > div { flex-shrink: 0; } -.playlist > * { - align-items: center; - border-radius: .5rem; - column-gap: 1rem; - display: flex; - outline: none; - padding: .5rem; -} -.playlist > *:focus-visible, -.playlist > *:hover { background: var(--bg-2); } -.playlist .description { - align-items: center; - color: var(--fg-1); - display: flex; - flex-grow: 1; -} .playlist_context { border: 1px solid var(--bg-2); border-radius: .5rem; @@ -431,6 +410,27 @@ img { color: var(--fg-1); display: block; } +.playlist_page { + display: flex; + flex-direction: column; +} +.playlist_page > a > div { flex-shrink: 0; } +.playlist_page > * { + align-items: center; + border-radius: .5rem; + column-gap: 1rem; + display: flex; + outline: none; + padding: .5rem; +} +.playlist_page > *:focus-visible, +.playlist_page > *:hover { background: var(--bg-2); } +.playlist_page .description { + align-items: center; + color: var(--fg-1); + display: flex; + flex-grow: 1; +} .reading_width { max-width: 32rem; } .release_date { color: var(--fg-3); @@ -471,6 +471,7 @@ img { max-height: 80dvh; min-width: 20rem; overflow-y: auto; + padding: 1rem; position: absolute; right: 0; z-index: 1; @@ -480,25 +481,24 @@ img { flex-direction: column; width: 100%; } -.search .results > a { - display: flex; - column-gap: .5rem; -} .search .results a { align-items: center; column-gap: .7rem; display: flex; + padding: .5rem; } -.search .results > a:focus-visible, -.search .results > a:hover { +.search .results a:focus-visible, +.search .results a:hover { background: var(--bg-2); + color: var(--fg-1); outline: none; } -.search .results a > :nth-child(1) { +.search .results a:not(.visible) { display: none; } +.search .results .video > :nth-child(1) { aspect-ratio: var(--poster-aspect); height: 3rem; } -.search .results a > :nth-child(2) { flex: 1; } +.search .results .video > :nth-child(2) { flex: 1; } .search .results .placeholder { background: var(--bg-3); } .search:not(.open) button, .search:not(.open) .pane { @@ -635,11 +635,6 @@ video { height: 100%; width: 100%; } -.video { - color: var(--fg-2); - display: flex; - flex-direction: column; -} .video_title { color: var(--fg-1); margin: 0; @@ -668,11 +663,11 @@ video { .videos_grid .thumbnail { height: auto; } @media (max-width: 33.999rem) { - .playlist > a > div { width: 50%; } - .playlist .thumbnail { height: auto; } + .playlist_page > a > div { width: 50%; } + .playlist_page .thumbnail { height: auto; } } @media (min-width: 34rem) { - .playlist .thumbnail { + .playlist_page .thumbnail { height: 8rem; width: auto; } diff --git a/src/build/assets/video.js b/src/build/assets/video.js index ea35fe9..a40a751 100644 --- a/src/build/assets/video.js +++ b/src/build/assets/video.js @@ -428,7 +428,7 @@ video.addEventListener('ended', () => { video.addEventListener('click', togglePlayback); window.addEventListener('keydown', event => { - if (searchContainer.contains(event.target)) return; + if (searchContainer?.contains(event.target)) return; if (event.key === ' ') { togglePlayback(); diff --git a/src/build/layout.rs b/src/build/layout.rs index 4126d56..61300e2 100644 --- a/src/build/layout.rs +++ b/src/build/layout.rs @@ -100,7 +100,30 @@ impl Layout { String::new() }; - let search_js_asset_hash = asset_hashes.search_js.as_ref().unwrap(); + let search; + let search_script; + if let Some(search_js_asset_hash) = &asset_hashes.search_js { + let search_icon = icons::search(translations.search); + let close_icon = icons::failure(translations.close); + let t_search = translations.search; + search = formatdoc!(r#" +