Compare commits
No commits in common. "f192ac2a8ec18ac05164d6cf8828d03c0b89ace1" and "c87662419a8805831aa65fd6e113debc5e53e734" have entirely different histories.
f192ac2a8e
...
c87662419a
|
@ -81,7 +81,7 @@ pub struct DirEntry {
|
||||||
pub path: VPathBuf,
|
pub path: VPathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct ImageSize {
|
pub struct ImageSize {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use super::{walk_dir_rec, Dir, DirEntry, EditPath, ImageSize, VPath, VPathBuf};
|
||||||
|
|
||||||
pub struct ContentCache<T> {
|
pub struct ContentCache<T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
cache: DashMap<VPathBuf, Vec<u8>>,
|
cache: DashMap<VPathBuf, Option<Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ContentCache<T> {
|
impl<T> ContentCache<T> {
|
||||||
|
@ -48,13 +48,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn content(&self, path: &VPath) -> Option<Vec<u8>> {
|
fn content(&self, path: &VPath) -> Option<Vec<u8>> {
|
||||||
self.cache.get(path).map(|x| x.clone()).or_else(|| {
|
self.cache
|
||||||
let content = self.inner.content(path);
|
.entry(path.to_owned())
|
||||||
if let Some(content) = &content {
|
.or_insert_with(|| self.inner.content(path))
|
||||||
self.cache.insert(path.to_owned(), content.clone());
|
.clone()
|
||||||
}
|
|
||||||
content
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn content_version(&self, path: &VPath) -> Option<String> {
|
fn content_version(&self, path: &VPath) -> Option<String> {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use super::{Dir, DirEntry, EditPath, ImageSize, VPath, VPathBuf};
|
||||||
|
|
||||||
pub struct Blake3ContentVersionCache<T> {
|
pub struct Blake3ContentVersionCache<T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
cache: DashMap<VPathBuf, String>,
|
cache: DashMap<VPathBuf, Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Blake3ContentVersionCache<T> {
|
impl<T> Blake3ContentVersionCache<T> {
|
||||||
|
@ -31,16 +31,15 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn content_version(&self, path: &VPath) -> Option<String> {
|
fn content_version(&self, path: &VPath) -> Option<String> {
|
||||||
self.cache.get(path).map(|x| x.clone()).or_else(|| {
|
self.cache
|
||||||
let version = self.inner.content(path).map(|content| {
|
.entry(path.to_owned())
|
||||||
let hash = blake3::hash(&content).to_hex();
|
.or_insert_with(|| {
|
||||||
format!("b3-{}", &hash[0..8])
|
self.content(path).map(|content| {
|
||||||
});
|
let hash = blake3::hash(&content).to_hex();
|
||||||
if let Some(version) = &version {
|
format!("b3-{}", &hash[0..8])
|
||||||
self.cache.insert(path.to_owned(), version.clone());
|
})
|
||||||
}
|
})
|
||||||
version
|
.clone()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn image_size(&self, path: &VPath) -> Option<ImageSize> {
|
fn image_size(&self, path: &VPath) -> Option<ImageSize> {
|
||||||
|
|
|
@ -10,7 +10,7 @@ use super::{Dir, DirEntry, EditPath, ImageSize, VPath, VPathBuf};
|
||||||
|
|
||||||
pub struct ImageSizeCache<T> {
|
pub struct ImageSizeCache<T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
cache: DashMap<VPathBuf, ImageSize>,
|
cache: DashMap<VPathBuf, Option<ImageSize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ImageSizeCache<T> {
|
impl<T> ImageSizeCache<T> {
|
||||||
|
@ -59,17 +59,15 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn image_size(&self, path: &VPath) -> Option<ImageSize> {
|
fn image_size(&self, path: &VPath) -> Option<ImageSize> {
|
||||||
self.cache.get(path).map(|x| *x).or_else(|| {
|
self.cache
|
||||||
let image_size = self
|
.entry(path.to_owned())
|
||||||
.compute_image_size(path)
|
.or_insert_with(|| {
|
||||||
.inspect_err(|err| warn!("compute_image_size({path}) failed: {err:?}"))
|
self.compute_image_size(path)
|
||||||
.ok()
|
.inspect_err(|err| warn!("compute_image_size({path}) failed: {err:?}"))
|
||||||
.flatten();
|
.ok()
|
||||||
if let Some(image_size) = image_size {
|
.flatten()
|
||||||
self.cache.insert(path.to_owned(), image_size);
|
})
|
||||||
}
|
.clone()
|
||||||
image_size
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn anchor(&self, path: &VPath) -> Option<VPathBuf> {
|
fn anchor(&self, path: &VPath) -> Option<VPathBuf> {
|
||||||
|
|
|
@ -147,6 +147,20 @@ addSpell("b-linked", LinkedBranch);
|
||||||
|
|
||||||
/* Fragment navigation */
|
/* Fragment navigation */
|
||||||
|
|
||||||
|
let rehashing = false;
|
||||||
|
function rehash() {
|
||||||
|
// https://www.youtube.com/watch?v=Tv1SYqLllKI
|
||||||
|
if (!rehashing) {
|
||||||
|
rehashing = true;
|
||||||
|
let hash = window.location.hash;
|
||||||
|
if (hash.length > 0) {
|
||||||
|
window.location.hash = "";
|
||||||
|
window.location.hash = hash;
|
||||||
|
}
|
||||||
|
rehashing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function expandDetailsRecursively(element) {
|
function expandDetailsRecursively(element) {
|
||||||
while (element && element.tagName != "MAIN") {
|
while (element && element.tagName != "MAIN") {
|
||||||
if (element.tagName == "DETAILS") {
|
if (element.tagName == "DETAILS") {
|
||||||
|
@ -156,6 +170,69 @@ function expandDetailsRecursively(element) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function navigateToPage(page) {
|
||||||
|
window.location.pathname = `${page}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function navigateToBranch(fragment) {
|
||||||
|
if (fragment.length == 0) return;
|
||||||
|
|
||||||
|
let { navigationMap } = await import("/navmap.js");
|
||||||
|
|
||||||
|
let element = document.getElementById(fragment);
|
||||||
|
if (element !== null) {
|
||||||
|
// If the element is already loaded on the page, we're good.
|
||||||
|
expandDetailsRecursively(element);
|
||||||
|
rehash();
|
||||||
|
|
||||||
|
// NOTE(2024-03-31): Only scroll into view in the loaded case.
|
||||||
|
// This case happens very often with `/b`-navigated branches, and those serve the specific
|
||||||
|
// page that contains the provided branch.
|
||||||
|
// Hash-links are not used anymore so upgrading the second case is unnecessary.
|
||||||
|
// They were a thing before I linked to the treehouse very often so no need to update.
|
||||||
|
element.scrollIntoView();
|
||||||
|
} else {
|
||||||
|
// The element is not loaded, we need to load the tree that has it.
|
||||||
|
let parts = fragment.split(":");
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
let [page, _id] = parts;
|
||||||
|
let fullPath = navigationMap[page];
|
||||||
|
if (Array.isArray(fullPath)) {
|
||||||
|
// TODO: This logic will probably need to be upgraded at some point to support
|
||||||
|
// navigation maps with roots other than index. Currently though only index is
|
||||||
|
// generated so that doesn't matter.
|
||||||
|
let [_root, ...path] = fullPath;
|
||||||
|
if (path !== undefined) {
|
||||||
|
let isNotAtIndexHtml =
|
||||||
|
window.location.pathname != "" &&
|
||||||
|
window.location.pathname != "/" &&
|
||||||
|
window.location.pathname != "/index.html";
|
||||||
|
let lastBranch = null;
|
||||||
|
for (let linked of path) {
|
||||||
|
let branch = LinkedBranch.byLink.get(linked);
|
||||||
|
|
||||||
|
if (isNotAtIndexHtml && branch === undefined) {
|
||||||
|
navigateToPage("index");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await branch.loadTree("navigateToBranch");
|
||||||
|
lastBranch = branch;
|
||||||
|
}
|
||||||
|
if (lastBranch != null) {
|
||||||
|
expandDetailsRecursively(lastBranch.details);
|
||||||
|
}
|
||||||
|
rehash();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In case the navigation map does not contain the given page, we can try
|
||||||
|
// redirecting the user to a concrete page on the site.
|
||||||
|
navigateToPage(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getCurrentlyHighlightedBranch() {
|
function getCurrentlyHighlightedBranch() {
|
||||||
if (window.location.pathname == "/b" && window.location.search.length > 0) {
|
if (window.location.pathname == "/b" && window.location.search.length > 0) {
|
||||||
let shortID = window.location.search.substring(1);
|
let shortID = window.location.search.substring(1);
|
||||||
|
@ -165,12 +242,33 @@ function getCurrentlyHighlightedBranch() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function navigateToCurrentBranch() {
|
||||||
|
await navigateToBranch(getCurrentlyHighlightedBranch());
|
||||||
|
}
|
||||||
|
|
||||||
|
// When you click on a link, and the destination is within a <details> that is not expanded,
|
||||||
|
// expand the <details> recursively.
|
||||||
|
window.addEventListener("popstate", navigateToCurrentBranch);
|
||||||
|
addEventListener("DOMContentLoaded", navigateToCurrentBranch);
|
||||||
|
|
||||||
|
// When you enter the website through a link someone sent you, it would be nice if the linked branch
|
||||||
|
// got expanded by default.
|
||||||
|
async function expandLinkedBranch() {
|
||||||
|
let currentlyHighlightedBranch = getCurrentlyHighlightedBranch();
|
||||||
|
if (currentlyHighlightedBranch.length > 0) {
|
||||||
|
let linkedBranch = document.getElementById(currentlyHighlightedBranch);
|
||||||
|
if (linkedBranch.children.length > 0 && linkedBranch.children[0].tagName == "DETAILS") {
|
||||||
|
expandDetailsRecursively(linkedBranch.children[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener("DOMContentLoaded", expandLinkedBranch);
|
||||||
|
|
||||||
async function highlightCurrentBranch() {
|
async function highlightCurrentBranch() {
|
||||||
let branch = document.getElementById(getCurrentlyHighlightedBranch());
|
let branch = document.getElementById(getCurrentlyHighlightedBranch());
|
||||||
if (branch != null) {
|
if (branch != null) {
|
||||||
expandDetailsRecursively(branch);
|
|
||||||
branch.classList.add("target");
|
branch.classList.add("target");
|
||||||
branch.scrollIntoView();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue