My personal-knowledge-system, with deeply integrated task tracking and long term goal planning capabilities.
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: wikilink parsing and validation

+63 -15
-3
src/tui/components/zk/mod.rs
··· 95 95 96 96 let kt = kh.read().await; 97 97 98 - info!("{selected_zettel:#?}"); 99 - info!("{kt:#?}"); 100 - 101 98 let zettel = zettels 102 99 .iter() 103 100 .find(|&z| &z.id == selected_zettel)
+62 -11
src/types/index.rs
··· 1 1 use std::{ 2 - collections::HashMap, 2 + collections::{HashMap, HashSet}, 3 3 path::{Path, PathBuf}, 4 4 }; 5 5 6 6 use color_eyre::eyre::Result; 7 7 use dto::{ 8 8 ActiveModelTrait, ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, 9 - QueryFilter, TagActiveModel, TagEntity, ZettelEntity, ZettelModelEx, ZettelTagActiveModel, 10 - ZettelTagColumns, ZettelTagEntity, 9 + NanoId, QueryFilter, TagActiveModel, TagEntity, ZettelEntity, ZettelModelEx, 10 + ZettelTagActiveModel, ZettelTagColumns, ZettelTagEntity, 11 11 }; 12 + use pulldown_cmark::{Event, Options, Parser}; 12 13 use rayon::iter::{ParallelBridge, ParallelIterator}; 13 14 use tracing::info; 14 15 15 - use crate::types::{FrontMatter, ZettelId, frontmatter::Body}; 16 + use crate::types::{FrontMatter, Link, ZettelId, frontmatter::Body}; 16 17 17 18 #[derive(Debug, Clone)] 18 19 pub struct Index { 19 20 pub(super) zods: HashMap<ZettelId, ZettelOnDisk>, 21 + pub(super) outgoing_links: HashMap<ZettelId, Vec<Link>>, 22 + // pub(super) incoming_links: HashMap<ZettelId, Vec<Link>>, 20 23 } 21 24 22 25 #[derive(Debug, Clone)] ··· 32 35 let root = root.canonicalize()?; 33 36 34 37 let mut zods = HashMap::new(); 38 + let mut possible_outgoing_links = HashMap::new(); 35 39 36 40 std::fs::read_dir(root)? 37 41 .par_bridge() ··· 44 48 .and_then(|ext| ext.to_str()) 45 49 .is_some_and(|ext| ext == "md") 46 50 }) 47 - .map(|entry| -> Result<(ZettelId, ZettelOnDisk)> { 51 + .map(|entry| -> Result<(ZettelId, ZettelOnDisk, Vec<Link>)> { 48 52 let path = entry.path(); 49 - let id: ZettelId = path.as_path().try_into()?; 53 + let zid: ZettelId = path.as_path().try_into()?; 50 54 let (fm, body) = FrontMatter::extract_from_file(&path)?; 51 55 56 + let outgoing_links = Self::parse_outgoing_links(&zid, &body); 57 + 52 58 Ok(( 53 - id, 59 + zid, 54 60 ZettelOnDisk { 55 61 fm, 56 62 body, 57 63 path: path.canonicalize()?, 58 64 }, 65 + outgoing_links, 59 66 )) 60 67 }) 61 68 .collect::<Result<Vec<_>>>()? 62 69 .into_iter() 63 70 // .par_bridge() 64 - .for_each(|(id, zod)| { 65 - zods.insert(id, zod); 71 + .for_each(|(id, zod, zettel_outgoing_links)| { 72 + zods.insert(id.clone(), zod); 73 + possible_outgoing_links.insert(id, zettel_outgoing_links); 66 74 }); 67 75 68 - Ok(Self { zods }) 76 + // simple validation step for links 77 + let zid_set = zods.keys().cloned().collect::<HashSet<_>>(); 78 + 79 + let outgoing_links = possible_outgoing_links 80 + .into_iter() 81 + .map(|(id, links)| { 82 + let valid_links = links 83 + .into_iter() 84 + .filter(|link| zid_set.contains(&link.source) && zid_set.contains(&link.dest)) 85 + .collect::<Vec<_>>(); 86 + 87 + (id, valid_links) 88 + }) 89 + .collect::<HashMap<_, _>>(); 90 + 91 + Ok(Self { 92 + zods, 93 + outgoing_links, 94 + }) 95 + } 96 + 97 + pub fn parse_outgoing_links(zid: &ZettelId, body: &str) -> Vec<Link> { 98 + let parser = Parser::new_ext(body, Options::ENABLE_WIKILINKS); 99 + 100 + // let mut links = vec![]; 101 + let mut links = vec![]; 102 + 103 + for event in parser { 104 + if let Event::Start(pulldown_cmark::Tag::Link { 105 + link_type: pulldown_cmark::LinkType::WikiLink { has_pothole: _ }, 106 + dest_url, 107 + .. 108 + }) = event 109 + { 110 + let nano_id = dest_url.as_ref(); 111 + 112 + // how do we validate that this is a proper link? 113 + // we are just going to trust them for now 114 + 115 + links.push(Link::new(zid.clone(), NanoId::from(nano_id))); 116 + } 117 + } 118 + 119 + links 69 120 } 70 121 71 122 pub fn update_path_for_zid(&mut self, zid: &ZettelId, new_path: PathBuf) { ··· 86 137 87 138 /// Sync's the curren title of the `Zettel` with the 88 139 /// provided `zid` with the `DB` 89 - pub async fn sync_title_with_db( 140 + pub async fn sync_zettel_title_with_db( 90 141 &mut self, 91 142 zid: &ZettelId, 92 143 db: &DatabaseConnection,
+1 -1
src/types/kasten.rs
··· 111 111 self.index.process_zid(&zid)?; 112 112 // and then we sync tags 113 113 self.index.sync_tags_with_db(&zid, &self.db).await?; 114 - self.index.sync_title_with_db(&zid, &self.db).await?; 114 + self.index.sync_zettel_title_with_db(&zid, &self.db).await?; 115 115 116 116 Ok(()) 117 117 }