Implement spaced anchor redirection

This commit is contained in:
Juno Takano 2026-01-05 23:12:10 -03:00
commit a98c87cdfc
3 changed files with 19 additions and 13 deletions

View file

@ -1,6 +1,6 @@
# Roadmap # Roadmap
- [x] Add tests - [x] Setup tests
- [ ] Improve content syntax parser coverage - [ ] Improve content syntax parser coverage
- [x] Redirects - [x] Redirects
- [ ] Strip/render some syntax in Tree text preview - [ ] Strip/render some syntax in Tree text preview
@ -28,7 +28,7 @@
- [x] Ignore trailing punctuation - [x] Ignore trailing punctuation
- [ ] Conjugation anchors (|will|ed -> will) - [ ] Conjugation anchors (|will|ed -> will)
- [ ] Configurable suffixes - [ ] Configurable suffixes
- [ ] Spaced node anchor (|red fox| -> redfox -> RedFox) - [x] Spaced node anchor (|red fox| -> redfox -> RedFox)
- [ ] Automatic connections from anchors - [ ] Automatic connections from anchors
- [ ] `#` syntax for header ID anchors - [ ] `#` syntax for header ID anchors
- [ ] Connection kinds - [ ] Connection kinds

View file

@ -1,3 +1,4 @@
use crate::prelude::*;
use axum::response::IntoResponse as _; use axum::response::IntoResponse as _;
use axum::{body::Body, extract::Path, http::Response, response::Redirect}; use axum::{body::Body, extract::Path, http::Response, response::Redirect};
@ -8,7 +9,8 @@ use crate::{syntax::serial::populate_graph, router::handlers, types::Node};
pub async fn node(Path(id): Path<String>) -> Response<Body> { pub async fn node(Path(id): Path<String>) -> Response<Body> {
let graph = populate_graph(); let graph = populate_graph();
let empty_node = Node::new(Some(format!("Could not find node ID {id}."))); let empty_node = Node::new(Some(format!("Could not find node ID {id}.")));
let node = graph.find_node(&id).unwrap_or(empty_node.clone()); let (node_match, exact) = graph.find_node(&id);
let node = node_match.unwrap_or(empty_node.clone());
if !node.redirect.is_empty() { if !node.redirect.is_empty() {
return Redirect::permanent( return Redirect::permanent(
@ -17,9 +19,8 @@ pub async fn node(Path(id): Path<String>) -> Response<Body> {
.into_response(); .into_response();
} }
if !graph.nodes.contains_key(&id) if !exact {
&& graph.lowercase_keymap.contains_key(&id) log!("Redirecting non-exact match to {}", node.id);
{
return Redirect::permanent(format!("/node/{}", node.id).as_str()) return Redirect::permanent(format!("/node/{}", node.id).as_str())
.into_response(); .into_response();
} }

View file

@ -130,13 +130,18 @@ impl Graph {
} }
} }
pub fn find_node(&self, query: &str) -> Option<Node> { pub fn find_node(&self, query: &str) -> (Option<Node>, bool) {
self.nodes.get(query).cloned().or_else(|| { let collapsed_query = query.trim().replace(" ", "");
self.lowercase_keymap
.get(query) if let Some(exact_match) = self.nodes.get(query) {
.and_then(|lower_key| self.nodes.get(lower_key)) (Some(exact_match.clone()), true)
.cloned() } else if let Some(lower_key) =
}) self.lowercase_keymap.get(&collapsed_query)
{
(self.nodes.get(lower_key).cloned(), false)
} else {
(None, false)
}
} }
pub fn get_root(&self) -> Option<Node> { pub fn get_root(&self) -> Option<Node> {