diff --git a/src/build/assets/navigation.js b/src/build/assets/navigation.js
index 9009868..d5079da 100644
--- a/src/build/assets/navigation.js
+++ b/src/build/assets/navigation.js
@@ -1,3 +1,5 @@
+const chevronRightIcon = document.querySelector('#chevron_right_icon').content;
+
const browseButton = document.querySelector('button.browse');
const navigation = document.querySelector('header .navigation');
@@ -70,6 +72,10 @@ function initializeSiteTree() {
if (video.image) {
image = document.createElement('img');
image.src = rootPrefix + video.sitePath + video.image;
+
+ if (video.lqip) {
+ image.style.background = lqipToRadialGradient(video.lqip);
+ }
} else {
image = document.createElement('span');
image.classList.add('placeholder');
@@ -92,10 +98,24 @@ function initializeSiteTree() {
function initializePlaylist(playlist, ancestors = undefined) {
const traversal = ancestors ? [...ancestors, playlist] : [];
+ playlist.expandToggle = document.createElement('span');
+
const spanTitle = document.createElement('span');
- spanTitle.textContent = ancestors
- ? traversal.map(ancestor => ancestor.title).join(' > ')
- : playlist.title;
+ if (ancestors) {
+ for (const [index, ancestor] of traversal.entries()) {
+ if (index > 0) {
+ const separator = document.createElement('span');
+ separator.appendChild(chevronRightIcon.cloneNode(true));
+ separator.setAttribute('aria-label', '>');
+
+ spanTitle.appendChild(separator);
+ }
+
+ spanTitle.append(ancestor.title);
+ }
+ } else {
+ spanTitle.textContent = playlist.title;
+ }
const spanInfo = document.createElement('span');
spanInfo.textContent = NAVIGATION_JS_T.xxxVideos(playlist.videos.length);
@@ -103,9 +123,20 @@ function initializeSiteTree() {
playlist.element = document.createElement('a');
playlist.element.classList.add('playlist');
playlist.element.href = rootPrefix + playlist.sitePath + indexSuffix;
+ playlist.element.appendChild(playlist.expandToggle);
playlist.element.appendChild(spanTitle);
playlist.element.appendChild(spanInfo);
+ if (playlist.videos.length) {
+ playlist.expandToggle.appendChild(chevronRightIcon.cloneNode(true));
+ playlist.expandToggle.setAttribute('aria-label', NAVIGATION_JS_T.expand);
+ playlist.expandToggle.setAttribute('title', NAVIGATION_JS_T.expand);
+ playlist.expandToggle.addEventListener('click', event => {
+ collapseExpand(playlist.element);
+ event.preventDefault();
+ });
+ }
+
siteTreeElements.appendChild(playlist.element);
for (const video of playlist.videos) {
@@ -118,20 +149,44 @@ function initializeSiteTree() {
function initializeCollection(collection, ancestors = undefined) {
const traversal = ancestors ? [...ancestors, collection] : [];
- const spanText = document.createElement('span');
- spanText.textContent = ancestors
- ? traversal.map(ancestor => ancestor.title).join(' > ')
- : collection.title;
+ collection.expandToggle = document.createElement('span');
+
+ const spanTitle = document.createElement('span');
+ if (ancestors) {
+ for (const [index, ancestor] of traversal.entries()) {
+ if (index > 0) {
+ const separator = document.createElement('span');
+ separator.appendChild(chevronRightIcon.cloneNode(true));
+ separator.setAttribute('aria-label', '>');
+
+ spanTitle.appendChild(separator);
+ }
+
+ spanTitle.append(ancestor.title);
+ }
+ } else {
+ spanTitle.textContent = collection.title;
+ }
collection.element = document.createElement('a');
collection.element.classList.add('collection');
collection.element.href = rootPrefix + collection.sitePath + indexSuffix;
- collection.element.appendChild(spanText);
+ collection.element.appendChild(collection.expandToggle);
+
+ collection.element.appendChild(spanTitle);
if (collection.videos.length) {
const spanInfo = document.createElement('span');
spanInfo.textContent = NAVIGATION_JS_T.xxxVideos(collection.videos.length);
collection.element.appendChild(spanInfo);
+
+ collection.expandToggle.appendChild(chevronRightIcon.cloneNode(true));
+ collection.expandToggle.setAttribute('aria-label', NAVIGATION_JS_T.expand);
+ collection.expandToggle.setAttribute('title', NAVIGATION_JS_T.expand);
+ collection.expandToggle.addEventListener('click', event => {
+ collapseExpand(collection.element);
+ event.preventDefault();
+ });
}
siteTreeElements.appendChild(collection.element);
@@ -158,15 +213,13 @@ function initializeSiteTree() {
siteTreeInitialized = true;
}
-// TODO: Currently unused but kept around in case we want to pick it up again
-// (the lqip property is available as video.lqip in the siteContent tree structure).
-// function lqipToRadialGradient(lqip) {
-// const [ne, nw, se, sw] = lqip;
-// return `radial-gradient(ellipse at top right, rgb(${ne[0]},${ne[1]},${ne[2]}), transparent),` +
-// `radial-gradient(ellipse at top left, rgb(${nw[0]},${nw[1]},${nw[2]}), transparent),` +
-// `radial-gradient(ellipse at bottom right, rgb(${se[0]},${se[1]},${se[2]}), transparent),` +
-// `radial-gradient(ellipse at bottom left, rgb(${sw[0]},${sw[1]},${sw[2]}), transparent)`;
-// }
+function lqipToRadialGradient(lqip) {
+ const [ne, nw, se, sw] = lqip;
+ return `radial-gradient(ellipse at top right, rgb(${ne[0]},${ne[1]},${ne[2]}), transparent),` +
+ `radial-gradient(ellipse at top left, rgb(${nw[0]},${nw[1]},${nw[2]}), transparent),` +
+ `radial-gradient(ellipse at bottom right, rgb(${se[0]},${se[1]},${se[2]}), transparent),` +
+ `radial-gradient(ellipse at bottom left, rgb(${sw[0]},${sw[1]},${sw[2]}), transparent)`;
+}
function toggleBrowse() {
function updateVideo(video) {
@@ -179,6 +232,9 @@ function toggleBrowse() {
}
playlist.element.classList.add('visible');
+ playlist.element.classList.remove('expanded');
+ playlist.expandToggle.setAttribute('aria-label', NAVIGATION_JS_T.expand);
+ playlist.expandToggle.setAttribute('title', NAVIGATION_JS_T.expand);
}
function updateCollection(collection) {
@@ -195,6 +251,9 @@ function toggleBrowse() {
}
collection.element.classList.add('visible');
+ collection.element.classList.remove('expanded');
+ collection.expandToggle.setAttribute('aria-label', NAVIGATION_JS_T.expand);
+ collection.expandToggle.setAttribute('title', NAVIGATION_JS_T.expand);
}
siteTreeStatus.textContent = '';
@@ -240,6 +299,9 @@ function updateSearch() {
display = updateVideo(video) || display;
}
+ playlist.element.classList.remove('expanded');
+ playlist.expandToggle.setAttribute('aria-label', NAVIGATION_JS_T.collapse);
+ playlist.expandToggle.setAttribute('title', NAVIGATION_JS_T.collapse);
playlist.element.classList.toggle('visible', display);
}
@@ -259,6 +321,9 @@ function updateSearch() {
updateCollection(subcollection);
}
+ collection.element.classList.remove('expanded');
+ collection.expandToggle.setAttribute('aria-label', NAVIGATION_JS_T.collapse);
+ collection.expandToggle.setAttribute('title', NAVIGATION_JS_T.collapse);
collection.element.classList.toggle('visible', display);
}
@@ -351,7 +416,7 @@ navigation.addEventListener('keydown', event => {
} else if (event.key === ' ') {
if (event.target.classList.contains('collection') ||
event.target.classList.contains('playlist')) {
- collapseExpand(event.target, undefined);
+ collapseExpand(event.target);
event.preventDefault();
} else if (event.target.classList.contains('video')) {
event.preventDefault();
@@ -380,15 +445,27 @@ navigation.addEventListener('keydown', event => {
function collapseExpand(siteTreeElement, expand = undefined) {
function visitPlaylist(playlist) {
if (playlist.element === siteTreeElement) {
+ if (expand === undefined) {
+ expand = !playlist.element.classList.contains('expanded');
+ }
+
for (const video of playlist.videos) {
video.element.classList.toggle('visible', expand);
}
+ playlist.element.classList.toggle('expanded', expand);
+ const expandOrCollapse = NAVIGATION_JS_T[expand ? 'collapse' : 'expand'];
+ playlist.expandToggle.setAttribute('aria-label', expandOrCollapse);
+ playlist.expandToggle.setAttribute('title', expandOrCollapse);
+
return true;
} else if (!expand && playlist.videos.some(video => video.element === siteTreeElement)) {
for (const video of playlist.videos) {
video.element.classList.remove('visible');
}
+ playlist.element.classList.remove('expanded');
+ playlist.expandToggle.setAttribute('aria-label', NAVIGATION_JS_T.expand);
+ playlist.expandToggle.setAttribute('title', NAVIGATION_JS_T.expand);
playlist.element.focus();
return true;
@@ -399,15 +476,27 @@ function collapseExpand(siteTreeElement, expand = undefined) {
function visitCollection(collection) {
if (collection.element === siteTreeElement) {
+ if (expand === undefined) {
+ expand = !collection.element.classList.contains('expanded');
+ }
+
for (const video of collection.videos) {
video.element.classList.toggle('visible', expand);
}
+ collection.element.classList.toggle('expanded', expand);
+ const expandOrCollapse = NAVIGATION_JS_T[expand ? 'collapse' : 'expand'];
+ collection.expandToggle.setAttribute('aria-label', expandOrCollapse);
+ collection.expandToggle.setAttribute('title', expandOrCollapse);
+
return true;
} else if (!expand && collection.videos.some(video => video.element === siteTreeElement)) {
for (const video of collection.videos) {
video.element.classList.remove('visible');
}
+ collection.element.classList.remove('expanded');
+ collection.expandToggle.setAttribute('aria-label', NAVIGATION_JS_T.expand);
+ collection.expandToggle.setAttribute('title', NAVIGATION_JS_T.expand);
collection.element.focus();
return true;
diff --git a/src/build/assets/site.css b/src/build/assets/site.css
index 8d5c258..2204a78 100644
--- a/src/build/assets/site.css
+++ b/src/build/assets/site.css
@@ -506,6 +506,10 @@ img {
top: 3.5rem;
z-index: 2;
}
+.site_tree [role="status"]:not(:empty) {
+ font-weight: 500;
+ padding: .5rem;
+}
.site_tree .elements {
display: flex;
flex-direction: column;
@@ -514,9 +518,7 @@ img {
.site_tree .elements a {
align-items: center;
border-radius: .2rem;
- column-gap: .7rem;
display: flex;
- padding: .5rem;
}
.site_tree .elements a:focus-visible,
.site_tree .elements a:hover {
@@ -525,19 +527,58 @@ img {
outline: none;
}
.site_tree .elements a:not(.visible) { display: none; }
-.site_tree .elements .collection,
-.site_tree .elements .playlist {
- column-gap: .3rem;
+.site_tree .elements .collection > :nth-child(1),
+.site_tree .elements .playlist > :nth-child(1) {
+ align-items: center;
+ display: flex;
+ height: 1rem;
+ justify-content: center;
+ padding: .5rem;
+ width: 1rem;
+}
+.site_tree .elements .collection > :nth-child(1) svg,
+.site_tree .elements .playlist > :nth-child(1) svg {
+ border-radius: 50%;
+ color: var(--fg-1);
+ font-size: .6rem;
+ padding: .2rem;
+}
+.site_tree .elements .collection > :nth-child(1):hover svg,
+.site_tree .elements .playlist > :nth-child(1):hover svg {
+ background: var(--fg-3);
+ color: var(--bg-1);
+}
+.site_tree .elements .collection.expanded > :nth-child(1) svg,
+.site_tree .elements .playlist.expanded > :nth-child(1) svg {
+ transform: rotate(90deg);
}
.site_tree .elements .collection > :nth-child(2),
.site_tree .elements .playlist > :nth-child(2) {
- color: var(--fg-3);
+ align-items: center;
+ column-gap: .3rem;
+ display: flex;
}
+.site_tree .elements .collection > :nth-child(2) svg,
+.site_tree .elements .playlist > :nth-child(2) svg {
+ color: var(--mg);
+ font-size: .5rem;
+}
+.site_tree .elements .collection > :nth-child(3),
+.site_tree .elements .playlist > :nth-child(3) {
+ color: var(--fg-3);
+ margin-left: .3rem;
+}
+.site_tree .elements .video { padding: .5rem; }
.site_tree .elements .video > :nth-child(1) {
aspect-ratio: var(--poster-aspect);
+ border-radius: .2rem;
height: 3rem;
+ margin-left: 1.5rem;
+}
+.site_tree .elements .video > :nth-child(2) {
+ flex: 1;
+ margin-left: .7rem;
}
-.site_tree .elements .video > :nth-child(2) { flex: 1; }
.site_tree .elements .placeholder { background: var(--bg-3); }
.site_tree:not(.open) { display: none; }
#speed .x { font-size: .8em; }
diff --git a/src/build/layout.rs b/src/build/layout.rs
index ec959c6..a7f11a6 100644
--- a/src/build/layout.rs
+++ b/src/build/layout.rs
@@ -102,6 +102,8 @@ impl Layout {
String::new()
};
+ let mut templates = String::new();
+
let browse;
let search;
let navigation_script;
@@ -131,6 +133,15 @@ impl Layout {
"#);
navigation_script = format!(r#""#);
+
+ let chevron_right_icon = icons::CHEVRON_RIGHT;
+ let navigation_templates = formatdoc!(r#"
+
+ {chevron_right_icon}
+
+ "#);
+
+ templates.push_str(&navigation_templates);
} else {
browse = String::new();
search = String::new();
@@ -152,15 +163,6 @@ impl Layout {
"#);
- let breadcrumbs = traversal.parents(site)
- .iter()
- .map(|breadcrumb| {
- let breadcrumb_rendered = breadcrumb.step.breadcrumb(&breadcrumb.href, site);
- format!(r#"{breadcrumb_rendered} › "#)
- })
- .collect::>()
- .join("");
-
let dark_icon = icons::dark(translations.dark_color_scheme);
let light_icon = icons::light(translations.light_color_scheme);
let theme_toggle = match &site.theme.kind {
@@ -175,8 +177,6 @@ impl Layout {
let version_display = env!("HYPER8_VERSION_DISPLAY");
- let mut templates = String::new();
-
if self.clipboard_script {
templates.push_str(©_button_icon_templates(translations));
}
diff --git a/src/build/outline.rs b/src/build/outline.rs
index 9733583..9fc193d 100644
--- a/src/build/outline.rs
+++ b/src/build/outline.rs
@@ -731,8 +731,6 @@ impl TraversalStep<'_> {
// String::from(r#""#)
// };
- let label = playlist.title_or_slug_or_generic_label(translations);
-
if let Some(video) = video {
let index_suffix = site.index_suffix();
diff --git a/src/build/scripts.rs b/src/build/scripts.rs
index b34fc51..20e9044 100644
--- a/src/build/scripts.rs
+++ b/src/build/scripts.rs
@@ -142,11 +142,15 @@ pub fn generate_navigation_js(
};
if let Some(site_content) = site_content {
+ let t_collapse = translations.collapse;
+ let t_expand = translations.expand;
let t_nothing_found_for_xxx = js_escape_inside_single_quoted_string(translations.nothing_found_for_xxx);
let t_showing_xxx_results_for_xxx = js_escape_inside_single_quoted_string(translations.showing_xxx_results_for_xxx);
let t_xxx_videos = js_escape_inside_single_quoted_string(translations.xxx_videos);
let mut js = formatdoc!(r#"
const NAVIGATION_JS_T = {{
+ collapse: '{t_collapse}',
+ expand: '{t_expand}',
nothingFoundForXxx: query => '{t_nothing_found_for_xxx}'.replace('{{query}}', query),
showingXxxResultsForXxx: (count, query) => '{t_showing_xxx_results_for_xxx}'.replace('{{count}}', count).replace('{{query}}', query),
xxxVideos: count => '{t_xxx_videos}'.replace('{{count}}', count)
diff --git a/src/icons.rs b/src/icons.rs
index aad1607..a933319 100644
--- a/src/icons.rs
+++ b/src/icons.rs
@@ -20,6 +20,13 @@ pub const ATOM: &str = indoc!(r#"
"#);
+/// A rightwards-facing, perfectly perpendicular chevron (like a closing angled bracket with a 90 degrees angle)
+pub const CHEVRON_RIGHT: &str = indoc!(r#"
+
+"#);
+
/// The stylized string ">"
pub const CODE: &str = indoc!(r#"