basic wiki tree

This commit is contained in:
Maciej Jur 2024-04-21 23:02:30 +02:00
parent b66f168c42
commit 3e0112c206
Signed by: kamov
GPG key ID: 191CBFF5F72ECAFD
6 changed files with 246 additions and 68 deletions

View file

@ -1,32 +1,45 @@
use std::fmt::Debug;
use std::fs::{self, File}; use std::fs::{self, File};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::io::Write; use std::io::Write;
use crate::html::LinkableData; use crate::html::Linkable;
use crate::Everything; use crate::Sack;
pub enum AssetKind { pub enum AssetKind {
Html(Box<dyn Fn(&Everything) -> String>), Html(Box<dyn Fn(&Sack) -> String>),
Image, Image,
Unknown, Unknown,
Bib(hayagriva::Library), Bib(hayagriva::Library),
} }
impl Debug for AssetKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Html(ptr) => f.debug_tuple("Html").field(&"ptr").finish(),
Self::Image => write!(f, "Image"),
Self::Unknown => write!(f, "Unknown"),
Self::Bib(arg0) => f.debug_tuple("Bib").field(arg0).finish(),
}
}
}
#[derive(Debug)]
pub struct Asset { pub struct Asset {
pub kind: AssetKind, pub kind: AssetKind,
pub out: PathBuf, pub out: PathBuf,
pub link: Option<LinkableData>, pub link: Option<Linkable>,
pub meta: super::Source, pub meta: super::Source,
} }
pub struct Virtual(pub PathBuf, pub Box<dyn Fn(&Everything) -> String>); pub struct Virtual(pub PathBuf, pub Box<dyn Fn(&Sack) -> String>);
impl Virtual { impl Virtual {
pub fn new<P, F>(path: P, call: F) -> Self pub fn new<P, F>(path: P, call: F) -> Self
where where
P: AsRef<Path>, P: AsRef<Path>,
F: Fn(&Everything) -> String + 'static F: Fn(&Sack) -> String + 'static
{ {
Self(path.as_ref().into(), Box::new(call)) Self(path.as_ref().into(), Box::new(call))
} }
@ -59,7 +72,7 @@ pub fn render(items: &[Item]) {
}) })
.collect(); .collect();
let everything = Everything { assets: &assets }; let everything = Sack { assets: &assets };
for item in items { for item in items {
match item { match item {
@ -70,7 +83,7 @@ pub fn render(items: &[Item]) {
} }
fn render_real(item: &Asset, assets: &Everything) { fn render_real(item: &Asset, assets: &Sack) {
match &item.kind { match &item.kind {
AssetKind::Html(render) => { AssetKind::Html(render) => {
let i = &item.meta.path; let i = &item.meta.path;
@ -101,7 +114,7 @@ fn render_real(item: &Asset, assets: &Everything) {
} }
} }
fn render_fake(item: &Virtual, assets: &Everything) { fn render_fake(item: &Virtual, assets: &Sack) {
let Virtual(out, render) = item; let Virtual(out, render) = item;
let o = Path::new("dist").join(&out); let o = Path::new("dist").join(&out);

View file

@ -15,4 +15,4 @@ pub use show::show;
pub use special::{map, search}; pub use special::{map, search};
pub use wiki::wiki; pub use wiki::wiki;
pub use list::LinkableData; pub use list::{Linkable, Link, LinkDate};

View file

@ -3,18 +3,29 @@ use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable};
use crate::html::page; use crate::html::page;
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct LinkableData { pub struct Link {
pub path: String, pub path: String,
pub name: String, pub name: String,
pub date: DateTime<Utc>,
pub desc: Option<String>, pub desc: Option<String>,
} }
#[derive(Debug, Clone)]
pub struct LinkDate {
pub link: Link,
pub date: DateTime<Utc>,
}
#[derive(Debug, Clone)]
pub enum Linkable {
Link(Link),
Date(LinkDate),
}
pub fn list<'data, 'list>( pub fn list<'data, 'list>(
title: &'data str, title: &'data str,
groups: &'data [(i32, Vec<LinkableData>)] groups: &'data [(i32, Vec<LinkDate>)]
) -> impl Renderable + 'list ) -> impl Renderable + 'list
where where
'data: 'list 'data: 'list
@ -36,7 +47,7 @@ pub fn list<'data, 'list>(
page(title, list) page(title, list)
} }
fn section(year: i32, group: &[LinkableData]) -> impl Renderable + '_ { fn section(year: i32, group: &[LinkDate]) -> impl Renderable + '_ {
maud_move!( maud_move!(
section .page-list-year { section .page-list-year {
header .page-list-year__header { header .page-list-year__header {
@ -49,19 +60,19 @@ fn section(year: i32, group: &[LinkableData]) -> impl Renderable + '_ {
) )
} }
fn link(data: &LinkableData) -> impl Renderable + '_ { fn link(data: &LinkDate) -> impl Renderable + '_ {
let time = data.date.format("%m/%d"); let time = data.date.format("%m/%d");
maud_move!( maud_move!(
a .page-item href=(&data.path) { a .page-item href=(&data.link.path) {
div .page-item__header { div .page-item__header {
h3 { h3 {
(&data.name) (&data.link.name)
} }
time datetime=(data.date.to_rfc3339()) { time datetime=(data.date.to_rfc3339()) {
(time.to_string()) (time.to_string())
} }
} }
@if let Some(ref desc) = data.desc { @if let Some(ref desc) = data.link.desc {
div .page-item__desc { div .page-item__desc {
(desc) (desc)
} }

View file

@ -4,6 +4,28 @@ use crate::html::page;
use crate::text::md::Outline; use crate::text::md::Outline;
pub fn tree(outline: Outline) -> impl Renderable {
maud_move!(
section .link-tree {
h2 .link-tree__heading {
a .link-tree__heading-text href="#top" { "Content" }
}
nav #table-of-contents .link-tree__nav {
ul .link-tree__nav-list {
@for (title, id) in outline.0 {
li .link-tree__nav-list-item {
a .link-tree__nav-list-text.link href=(format!("#{id}")) {
(title)
}
}
}
}
}
}
)
}
pub fn post<'fm, 'md, 'post, T>( pub fn post<'fm, 'md, 'post, T>(
fm: &'fm Post, fm: &'fm Post,
content: T, content: T,
@ -25,22 +47,7 @@ pub fn post<'fm, 'md, 'post, T>(
label .wiki-aside__slider for="wiki-aside-shown" { label .wiki-aside__slider for="wiki-aside-shown" {
img .wiki-icon src="/static/svg/double-arrow.svg" width="24" height="24"; img .wiki-icon src="/static/svg/double-arrow.svg" width="24" height="24";
} }
section .link-tree { (tree(outline))
h2 .link-tree__heading {
a .link-tree__heading-text href="#top" { "Content" }
}
nav #table-of-contents .link-tree__nav {
ul .link-tree__nav-list {
@for (title, id) in outline.0 {
li .link-tree__nav-list-item {
a .link-tree__nav-list-text.link href=(format!("#{id}")) {
(title)
}
}
}
}
}
}
} }
article .wiki-article /*class:list={classlist)*/ { article .wiki-article /*class:list={classlist)*/ {

View file

@ -1,15 +1,111 @@
use std::collections::HashMap;
use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable}; use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable};
use crate::md::Wiki; use crate::md::Wiki;
use crate::html::page; use crate::html::page;
use crate::text::md::Outline; use crate::text::md::Outline;
use crate::Sack;
use super::Link;
pub fn wiki<'data, 'html, T>( #[derive(Debug)]
struct TreeNode {
pub name: String,
pub children: HashMap<String, TreeNode>,
}
impl TreeNode {
fn new(name: &str) -> Self {
TreeNode {
name: name.to_string(),
children: HashMap::new(),
}
}
fn add_link(&mut self, link: &Link) {
let mut current_node = self;
for component in link.path.split('/').filter(|s| !s.is_empty()) {
current_node = current_node.children.entry(component.to_string())
.or_insert(TreeNode::new(component));
}
}
}
fn tree(sack: &Sack) -> impl Renderable {
let mut tree = TreeNode::new("wiki");
for link in sack.get_links_2("wiki/**/*.html") {
tree.add_link(&link);
};
maud_move!(
h2 .link-tree__heading {
// {pages.chain(x => x.prefix)
// .map(pathify)
// .mapOrDefault(href =>
// <a class="link-tree__heading-text" href={href}>{heading}</a>,
// <span class="link-tree__heading-text">{heading}</span>
// )}
}
nav .link-tree__nav {
// {pages.map(pages => <PagesList {...pages} />).extract()}
(level(&tree))
}
)
}
fn level(tree: &TreeNode) -> impl Renderable + '_ {
for (key, next) in tree.children.iter() {
println!("{key}");
level(next);
};
maud_move!(
ul .link-tree__nav-list {
@for (key, next) in tree.children.iter() {
li .link-tree__nav-list-item {
span .link-tree__nav-list-text { (key) }
@if next.children.len() > 0 {
(level(next))
}
}
}
}
)
}
// {tree.children
// .map(m => Object.values(m))
// .filter(xs => xs.length > 0)
// .map(pages =>
// <ul class="link-tree__nav-list">
// {pages
// .sort(compare)
// .map(page => ({...page, current: checkCurrent(page.slug) }))
// .map(page =>
// <li class="link-tree__nav-list-item">
// {page.slug
// .chain(slug => prefix.map(prefix => pathify(prefix, slug)))
// .map(href => (page.current)
// ? <button id="current-page-button" class="link-tree__nav-list-text current">{page.title}</button>
// : <a class="link-tree__nav-list-text link" href={href}>{page.title}</a>
// )
// .orDefault(<span class="link-tree__nav-list-text">{page.title}</span>)}
// <Astro.self tree={page} slug={slug} prefix={prefix} />
// </li>
// )}
// </ul>
// ).extract()}
pub fn wiki<'data, 'html, 'sack, T>(
fm: &'data Wiki, fm: &'data Wiki,
content: T, content: T,
outline: Outline, outline: Outline,
sack: &'sack Sack,
) -> impl Renderable + 'html ) -> impl Renderable + 'html
where where
'sack: 'html,
'data: 'html, 'data: 'html,
T: Renderable + 'data T: Renderable + 'data
{ {
@ -25,7 +121,11 @@ pub fn wiki<'data, 'html, T>(
img .wiki-icon src="/static/svg/double-arrow.svg" width="24" height="24"; img .wiki-icon src="/static/svg/double-arrow.svg" width="24" height="24";
} }
// Navigation tree // Navigation tree
// <Tree heading="Personal Wiki" pages={pages} headings={headings} /> section .link-tree {
div {
(tree(sack))
}
}
} }
article .wiki-article /*class:list={classlist)*/ { article .wiki-article /*class:list={classlist)*/ {

View file

@ -3,7 +3,7 @@ use std::{collections::HashMap, path::Path};
use std::fs; use std::fs;
use chrono::Datelike; use chrono::Datelike;
use grass; use grass;
use html::LinkableData; use html::{Link, LinkDate, Linkable};
use hypertext::{Raw, Renderable}; use hypertext::{Raw, Renderable};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use text::md::Outline; use text::md::Outline;
@ -46,49 +46,78 @@ static REPO: Lazy<BuildInfo> = Lazy::new(|| {
}); });
/// This struct allows for querying the website hierarchy /// This struct allows for querying the website hierarchy
struct Everything<'a> { #[derive(Debug)]
struct Sack<'a> {
assets: &'a [&'a gen::Asset], assets: &'a [&'a gen::Asset],
} }
impl Everything<'_> { impl Sack<'_> {
fn get_linkable(&self, path: &str) -> Vec<LinkableData> { fn get_links(&self, path: &str) -> Vec<LinkDate> {
let pattern = glob::Pattern::new(path).unwrap(); let pattern = glob::Pattern::new(path).unwrap();
self.assets.iter() self.assets.iter()
.filter(|f| pattern.matches_path(&f.out)) .filter(|f| pattern.matches_path(&f.out))
.filter_map(|f| f.link.clone()) .filter_map(|f| match &f.link {
Some(Linkable::Date(link)) => Some(link.clone()),
_ => None,
})
.collect()
}
fn get_links_2(&self, path: &str) -> Vec<Link> {
let pattern = glob::Pattern::new(path).unwrap();
self.assets.iter()
.filter(|f| pattern.matches_path(&f.out))
.filter_map(|f| match &f.link {
Some(Linkable::Link(link)) => Some(link.clone()),
_ => None,
})
.collect() .collect()
} }
} }
trait Transformable { trait Transformable {
fn transform<'f, 'm, 'html, T>(&'f self, content: T, outline: Outline) -> impl Renderable + 'html fn transform<'f, 'm, 'html, 'sack, T>(
&'f self,
content: T,
outline: Outline,
sack: &'sack Sack,
) -> impl Renderable + 'html
where where
'f: 'html, 'f: 'html,
'm: 'html, 'm: 'html,
'sack: 'html,
T: Renderable + 'm; T: Renderable + 'm;
fn as_link(&self, path: String) -> Option<html::LinkableData>; fn as_link(&self, path: String) -> Option<Linkable>;
fn render(data: &str) -> (Outline, String); fn render(data: &str) -> (Outline, String);
} }
impl Transformable for md::Post { impl Transformable for md::Post {
fn transform<'f, 'm, 'html, T>(&'f self, content: T, outline: Outline) -> impl Renderable + 'html fn transform<'f, 'm, 'html, 'sack, T>(
&'f self,
content: T,
outline: Outline,
sack: &'sack Sack,
) -> impl Renderable + 'html
where where
'f: 'html, 'f: 'html,
'm: 'html, 'm: 'html,
'sack: 'html,
T: Renderable + 'm { T: Renderable + 'm {
html::post(self, content, outline) html::post(self, content, outline)
} }
fn as_link(&self, path: String) -> Option<html::LinkableData> { fn as_link(&self, path: String) -> Option<Linkable> {
Some(html::LinkableData { Some(Linkable::Date(LinkDate {
path: path.strip_suffix("index.html").unwrap().to_owned(), link: Link {
name: self.title.to_owned(), path: path.strip_suffix("index.html").unwrap().to_owned(),
name: self.title.to_owned(),
desc: self.desc.to_owned(),
},
date: self.date.to_owned(), date: self.date.to_owned(),
desc: self.desc.to_owned(), }))
})
} }
fn render(data: &str) -> (Outline, String) { fn render(data: &str) -> (Outline, String) {
@ -97,21 +126,29 @@ impl Transformable for md::Post {
} }
impl Transformable for md::Slide { impl Transformable for md::Slide {
fn transform<'f, 'm, 'html, T>(&'f self, content: T, _: Outline) -> impl Renderable + 'html fn transform<'f, 'm, 'html, 'sack, T>(
&'f self,
content: T,
_: Outline,
sack: &'sack Sack,
) -> impl Renderable + 'html
where where
'f: 'html, 'f: 'html,
'm: 'html, 'm: 'html,
'sack: 'html,
T: Renderable + 'm { T: Renderable + 'm {
html::show(self, content) html::show(self, content)
} }
fn as_link(&self, path: String) -> Option<html::LinkableData> { fn as_link(&self, path: String) -> Option<Linkable> {
Some(html::LinkableData { Some(Linkable::Date(LinkDate {
path: path.strip_suffix("index.html").unwrap().to_owned(), link: Link {
name: self.title.to_owned(), path: path.strip_suffix("index.html").unwrap().to_owned(),
name: self.title.to_owned(),
desc: self.desc.to_owned(),
},
date: self.date.to_owned(), date: self.date.to_owned(),
desc: self.desc.to_owned(), }))
})
} }
fn render(data: &str) -> (Outline, String) { fn render(data: &str) -> (Outline, String) {
@ -128,16 +165,26 @@ impl Transformable for md::Slide {
} }
impl Transformable for md::Wiki { impl Transformable for md::Wiki {
fn transform<'f, 'm, 'html, T>(&'f self, content: T, outline: Outline) -> impl Renderable + 'html fn transform<'f, 'm, 'html, 'sack, T>(
&'f self,
content: T,
outline: Outline,
sack: &'sack Sack,
) -> impl Renderable + 'html
where where
'f: 'html, 'f: 'html,
'm: 'html, 'm: 'html,
'sack: 'html,
T: Renderable + 'm { T: Renderable + 'm {
html::wiki(self, content, outline) html::wiki(self, content, outline, sack)
} }
fn as_link(&self, _: String) -> Option<html::LinkableData> { fn as_link(&self, path: String) -> Option<Linkable> {
None Some(Linkable::Link(Link {
path: path.strip_suffix("index.html").unwrap().to_owned(),
name: self.title.to_owned(),
desc: None,
}))
} }
fn render(data: &str) -> (Outline, String) { fn render(data: &str) -> (Outline, String) {
@ -146,7 +193,7 @@ impl Transformable for md::Wiki {
} }
fn to_list(list: Vec<LinkableData>) -> String { fn to_list(list: Vec<LinkDate>) -> String {
let mut groups = HashMap::<i32, Vec<_>>::new(); let mut groups = HashMap::<i32, Vec<_>>::new();
for page in list { for page in list {
@ -183,9 +230,9 @@ fn transform<T>(meta: gen::Source) -> gen::Asset
let (fm, md) = md::preflight::<T>(&data); let (fm, md) = md::preflight::<T>(&data);
let link = T::as_link(&fm, Path::new("/").join(&loc).to_str().unwrap().to_owned()); let link = T::as_link(&fm, Path::new("/").join(&loc).to_str().unwrap().to_owned());
let call = move |_: &Everything| { let call = move |everything: &Sack| {
let (outline, html) = T::render(&md); let (outline, html) = T::render(&md);
T::transform(&fm, Raw(html), outline).render().into() T::transform(&fm, Raw(html), outline, everything).render().into()
}; };
gen::Asset { gen::Asset {
@ -248,7 +295,7 @@ fn main() {
gen::Asset { gen::Asset {
kind: gen::AssetKind::Html(Box::new(|_| { kind: gen::AssetKind::Html(Box::new(|_| {
let data = std::fs::read_to_string("content/index.md").unwrap(); let data = std::fs::read_to_string("content/index.md").unwrap();
let (outline, html) = text::md::parse(&data); let (_, html) = text::md::parse(&data);
html::home(Raw(html)).render().to_owned().into() html::home(Raw(html)).render().to_owned().into()
})), })),
out: "index.html".into(), out: "index.html".into(),
@ -261,10 +308,10 @@ fn main() {
} }
}.into(), }.into(),
gen::Virtual("posts/index.html".into(), Box::new(|all| gen::Virtual("posts/index.html".into(), Box::new(|all|
to_list(all.get_linkable("posts/**/*.html")) to_list(all.get_links("posts/**/*.html"))
)).into(), )).into(),
gen::Virtual("slides/index.html".into(), Box::new(|all| gen::Virtual("slides/index.html".into(), Box::new(|all|
to_list(all.get_linkable("slides/**/*.html")) to_list(all.get_links("slides/**/*.html"))
)).into(), )).into(),
], ],
gen::gather("content/about.md", &["md"].into()) gen::gather("content/about.md", &["md"].into())