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.

fix: proper order in explorer

+180 -94
+22 -21
.config/config.ron
··· 1 1 ( 2 2 directory: "/Users/suri/dev/projects/filaments/ZettelKasten", 3 3 global_key_binds: { 4 - "ctrl-z": Suspend, 5 4 "up": MoveUp, 6 - "down": MoveDown, 7 5 "ctrl-c": Quit, 6 + "ctrl-z": Suspend, 7 + "down": MoveDown, 8 8 }, 9 9 zk: ( 10 10 keybinds: { 11 + "ctrl-n": NewZettel, 12 + "enter": OpenZettel, 11 13 "tab": SwitchTo( 12 14 page: Todo(Explorer), 13 15 ), 14 - "ctrl-n": NewZettel, 15 - "enter": OpenZettel, 16 16 }, 17 17 ), 18 18 todo: ( 19 19 explorer: ( 20 20 keybinds: { 21 + "g": NewSubGroup, 21 22 "tab": SwitchTo( 22 23 page: Zk, 23 24 ), 24 - "3": SwitchTo( 25 - page: Todo(TaskList), 26 - ), 27 - "g": NewSubGroup, 28 - "k": MoveUp, 29 25 "2": SwitchTo( 30 26 page: Todo(Inspector), 31 27 ), 32 - "j": MoveDown, 33 - "shift-g": NewGroup, 28 + "3": SwitchTo( 29 + page: Todo(TaskList), 30 + ), 34 31 "1": SwitchTo( 35 32 page: Todo(Explorer), 36 33 ), 34 + "k": MoveUp, 35 + "t": NewTask, 36 + "shift-g": NewGroup, 37 + "j": MoveDown, 37 38 }, 38 39 ), 39 40 inspector: ( 40 41 keybinds: { 41 - "3": SwitchTo( 42 - page: Todo(TaskList), 42 + "tab": SwitchTo( 43 + page: Zk, 43 44 ), 44 45 "1": SwitchTo( 45 46 page: Todo(Explorer), 46 47 ), 48 + "3": SwitchTo( 49 + page: Todo(TaskList), 50 + ), 47 51 "2": SwitchTo( 48 52 page: Todo(Inspector), 49 - ), 50 - "tab": SwitchTo( 51 - page: Zk, 52 53 ), 53 54 }, 54 55 ), 55 56 tasklist: ( 56 57 keybinds: { 57 - "k": MoveUp, 58 58 "1": SwitchTo( 59 59 page: Todo(Explorer), 60 60 ), 61 + "tab": SwitchTo( 62 + page: Zk, 63 + ), 64 + "j": MoveDown, 65 + "k": MoveUp, 61 66 "2": SwitchTo( 62 67 page: Todo(Inspector), 63 68 ), 64 69 "3": SwitchTo( 65 70 page: Todo(TaskList), 66 - ), 67 - "j": MoveDown, 68 - "tab": SwitchTo( 69 - page: Zk, 70 71 ), 71 72 }, 72 73 ),
+1
.config/default_config.ron
··· 24 24 "k": MoveUp, 25 25 "shift-g": NewGroup, 26 26 "g": NewSubGroup, 27 + "t": NewTask, 27 28 }, 28 29 ), 29 30 inspector: (
+10
crates/tree/src/node.rs
··· 1 + use std::cmp::Ordering; 2 + 1 3 use serde::{Deserialize, Serialize}; 2 4 3 5 use crate::NodeId; ··· 108 110 /// ``` 109 111 pub const fn children(&self) -> &Vec<NodeId> { 110 112 &self.children 113 + } 114 + 115 + /// Are able to sort the children of this `Node` 116 + pub fn sort_children_by<F>(&mut self, mut compare: F) 117 + where 118 + F: FnMut(&NodeId, &NodeId) -> Ordering, 119 + { 120 + self.children.sort_by(|a, b| compare(a, b)); 111 121 } 112 122 113 123 pub(crate) const fn children_mut(&mut self) -> &mut Vec<NodeId> {
+9 -9
flake.lock
··· 2 2 "nodes": { 3 3 "crane": { 4 4 "locked": { 5 - "lastModified": 1775839657, 6 - "narHash": "sha256-SPm9ck7jh3Un9nwPuMGbRU04UroFmOHjLP56T10MOeM=", 5 + "lastModified": 1776533550, 6 + "narHash": "sha256-8mTHsQ8cB0jGlXE4WWKqpQFQPM/VotDnr2uzfrOGNKI=", 7 7 "owner": "ipetkov", 8 8 "repo": "crane", 9 - "rev": "7cf72d978629469c4bd4206b95c402514c1f6000", 9 + "rev": "e24d86e91348e3d44014974fa24c9a22cfd663b5", 10 10 "type": "github" 11 11 }, 12 12 "original": { ··· 23 23 "rust-analyzer-src": "rust-analyzer-src" 24 24 }, 25 25 "locked": { 26 - "lastModified": 1776326782, 27 - "narHash": "sha256-QzTHb5vhPVensbkL7+WhemxFYXINcdZB1SQ5EMjG2AU=", 26 + "lastModified": 1776497206, 27 + "narHash": "sha256-Em+RSdFnwyyKPGUBFtQYtVjm+1UvIc9gOR91Y22zlzg=", 28 28 "owner": "nix-community", 29 29 "repo": "fenix", 30 - "rev": "47ece0146691d625f10a3d2ec4a2c04fca29a35b", 30 + "rev": "df2295365fb081fe0745449762a771290782c22d", 31 31 "type": "github" 32 32 }, 33 33 "original": { ··· 60 60 "rust-analyzer-src": { 61 61 "flake": false, 62 62 "locked": { 63 - "lastModified": 1776280158, 64 - "narHash": "sha256-0uGgwgFVPQ28cVx7onaB1iTCHGi20MNx54e6KZbYnMs=", 63 + "lastModified": 1776441750, 64 + "narHash": "sha256-1rVfG+mj8R4ze+lSYCa4iAv7FzrB03Cprtxmd1MfZak=", 65 65 "owner": "rust-lang", 66 66 "repo": "rust-analyzer", 67 - "rev": "94c2f68935467a6381a4f3504ae6c709b5fd3c61", 67 + "rev": "251df518d73abb5c5d573c4d5d266a3edae9ca5a", 68 68 "type": "github" 69 69 }, 70 70 "original": {
+1
src/tui/components/todo/explorer.rs
··· 8 8 9 9 use crate::types::{Group, TodoNode, TodoNodeKind, TodoTree}; 10 10 11 + #[derive(Debug)] 11 12 pub struct Explorer<'text> { 12 13 pub render_list: ratatui::widgets::List<'text>, 13 14 #[allow(dead_code)]
+24 -4
src/tui/components/todo/mod.rs
··· 12 12 13 13 use crate::{ 14 14 tui::{Page, Signal, components::Component}, 15 - types::{Group, KastenHandle}, 15 + types::{Group, KastenHandle, Priority, Task}, 16 16 }; 17 17 18 18 mod explorer; ··· 83 83 let kt = self.kh.read().await; 84 84 let tree = &kt.todo_tree; 85 85 86 + debug!("tree after refresh {tree:#?}"); 86 87 let splits = self 87 88 .layouts 88 89 .split(Rect::new(0, 0, self.area.width, self.area.height)); ··· 93 94 let mut explorer = Explorer::new(tree, &tree.root_id, l_state, splits.explorer.width); 94 95 let mut task_list = TaskList::new(tree, &tree.root_id, l_state, splits.task_list.width); 95 96 97 + debug!("explorer constructed after refresh {explorer:#?}"); 98 + 96 99 drop(kt); 97 100 98 101 let explorer_selection_idx = ··· 242 245 Ok(()) 243 246 } 244 247 248 + #[allow(clippy::too_many_lines)] 245 249 async fn update(&mut self, signal: Signal) -> color_eyre::Result<Option<Signal>> { 246 250 let explorer = self 247 251 .explorer ··· 317 321 return Ok(None); 318 322 } 319 323 320 - todo!() 324 + let mut kt = self.kh.write().await; 325 + let Some(parent) = explorer 326 + .group_of_current_selection(&kt.todo_tree) 327 + // .cloned() 328 + .map(|parent| parent.id.clone()) 329 + else { 330 + return Ok(None); 331 + }; 332 + let task = Task::new( 333 + NanoId::default().to_string(), 334 + parent, 335 + &mut kt, 336 + None, 337 + Priority::default(), 338 + ) 339 + .await?; 321 340 322 - // let ancestors = kt.todo_tree.tree.ancestors(node_id) = todo!(); 323 - // let task = Task::new("wahoo"); 341 + drop(kt); 342 + debug!("created task: {task:#?}"); 343 + return Ok(Some(Signal::Refresh)); 324 344 } 325 345 326 346 Signal::NewSubGroup => {
+6 -10
src/tui/components/zk/mod.rs
··· 320 320 self.refresh().await?; 321 321 } 322 322 323 - Signal::MoveDown => { 324 - if self.active { 325 - zettel_list.state.select_next(); 326 - self.update_views_from_zettel_list_selection().await?; 327 - } 323 + Signal::MoveDown if self.active => { 324 + zettel_list.state.select_next(); 325 + self.update_views_from_zettel_list_selection().await?; 328 326 } 329 - Signal::MoveUp => { 330 - if self.active { 331 - zettel_list.state.select_previous(); 332 - self.update_views_from_zettel_list_selection().await?; 333 - } 327 + Signal::MoveUp if self.active => { 328 + zettel_list.state.select_previous(); 329 + self.update_views_from_zettel_list_selection().await?; 334 330 } 335 331 336 332 Signal::OpenZettel => {
+1 -1
src/tui/components/zk/search.rs
··· 107 107 }) 108 108 .collect(); 109 109 110 - results.sort_by(|a, b| b.1.cmp(&a.1)); 110 + results.sort_by_key(|b| std::cmp::Reverse(b.1)); 111 111 112 112 results.into_iter().map(|(i, _)| i).collect() 113 113 }
+2 -38
src/types/group.rs
··· 3 3 DateTime, GroupActiveModel, GroupEntity, GroupModelEx, IntoActiveModel as _, NanoId, 4 4 TagActiveModel, TagEntity, ZettelEntity, 5 5 }; 6 - use tree::Node; 7 6 8 - use crate::types::{Kasten, Priority, Tag, TodoNode, Zettel, frontmatter}; 7 + use crate::types::{Kasten, Priority, Tag, Zettel, frontmatter}; 9 8 10 9 /// A `Group` which contains tasks! 11 10 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] ··· 91 90 .expect("We just inserted it") 92 91 .into(); 93 92 94 - // we should also insert the group into the kasten 95 - 96 - let parent_node_id = group 97 - .parent_id 98 - .clone() 99 - .and_then(|id| kt.todo_tree.nanoid_to_nodeid.get(&id)) 100 - .unwrap_or(&kt.todo_tree.root_id); 101 - 102 - let my_depth = if *parent_node_id == kt.todo_tree.root_id { 103 - 0 104 - } else { 105 - kt.todo_tree 106 - .tree 107 - .get(parent_node_id) 108 - .expect("Must exist inside tree") 109 - .data() 110 - .depth 111 - + 1 112 - }; 113 - 114 - let inserted_node_id = kt 115 - .todo_tree 116 - .tree 117 - .insert( 118 - Node::new(TodoNode::new( 119 - super::TodoNodeKind::Group(Box::new(group.clone())), 120 - my_depth, 121 - )), 122 - tree::InsertBehavior::UnderNode(parent_node_id), 123 - ) 124 - .expect("Insertion of group should not error!"); 125 - 126 - kt.todo_tree 127 - .nanoid_to_nodeid 128 - .insert(group.id.clone(), inserted_node_id); 129 - 93 + kt.todo_tree.insert_group(&group); 130 94 Ok(group) 131 95 } 132 96 }
+1 -1
src/types/kasten/index/mod.rs
··· 41 41 .par_bridge() 42 42 .flatten() 43 43 .filter(|entry| { 44 - entry.file_type().map(|ft| ft.is_file()).unwrap_or(false) 44 + entry.file_type().is_ok_and(|ft| ft.is_file()) 45 45 && entry 46 46 .path() 47 47 .extension()
+98 -1
src/types/kasten/todo_tree.rs
··· 1 - use std::collections::HashMap; 1 + use std::{cmp::Ordering, collections::HashMap}; 2 2 3 3 use color_eyre::eyre::{Context, Result}; 4 4 use dto::{ ··· 136 136 } 137 137 138 138 Ok(()) 139 + } 140 + 141 + pub fn insert_group(&mut self, group: &Group) { 142 + let parent_node_id = group 143 + .parent_id 144 + .clone() 145 + .and_then(|id| self.nanoid_to_nodeid.get(&id)) 146 + .unwrap_or(&self.root_id) 147 + .clone(); 148 + 149 + let my_depth = if parent_node_id == self.root_id { 150 + 0 151 + } else { 152 + self.tree 153 + .get(&parent_node_id) 154 + .expect("Must exist inside tree") 155 + .data() 156 + .depth 157 + + 1 158 + }; 159 + 160 + let inserted_node_id = self 161 + .tree 162 + .insert( 163 + Node::new(TodoNode::new( 164 + super::TodoNodeKind::Group(Box::new(group.clone())), 165 + my_depth, 166 + )), 167 + tree::InsertBehavior::UnderNode(&parent_node_id), 168 + ) 169 + .expect("Insertion of group should not error!"); 170 + 171 + self.reorder_chidren(&parent_node_id); 172 + 173 + self.nanoid_to_nodeid 174 + .insert(group.id.clone(), inserted_node_id); 175 + } 176 + 177 + pub fn insert_task(&mut self, task: &Task) { 178 + let parent_node_id = self 179 + .nanoid_to_nodeid 180 + .get(&task.group_id) 181 + .expect("The group must already be in the lookup hashmap") 182 + .clone(); 183 + 184 + let my_depth = self 185 + .tree 186 + .get(&parent_node_id) 187 + .expect("Must exist inside tree") 188 + .data() 189 + .depth 190 + + 1; 191 + 192 + let inserted_node_id = self 193 + .tree 194 + .insert( 195 + Node::new(TodoNode::new( 196 + super::TodoNodeKind::Task(Box::new(task.clone())), 197 + my_depth, 198 + )), 199 + tree::InsertBehavior::UnderNode(&parent_node_id), 200 + ) 201 + .expect("Insertion of Task should not error!"); 202 + 203 + self.reorder_chidren(&parent_node_id); 204 + 205 + self.nanoid_to_nodeid 206 + .insert(task.id.clone(), inserted_node_id); 207 + } 208 + 209 + fn reorder_chidren(&mut self, parent_node_id: &NodeId) { 210 + let children = self 211 + .tree 212 + .children(parent_node_id) 213 + .expect("Must be valid") 214 + .zip( 215 + self.tree 216 + .children_ids(parent_node_id) 217 + .expect("Must be valid"), 218 + ) 219 + .map(|(a, b)| (b.clone(), matches!(a.data().kind, TodoNodeKind::Task(_)))) 220 + .collect::<HashMap<_, _>>(); 221 + 222 + let parent = self 223 + .tree 224 + .get_mut(parent_node_id) 225 + .expect("parent must exist"); 226 + 227 + parent.sort_children_by(|a, _| { 228 + let a = children.get(a).expect("must exist"); 229 + 230 + if *a { 231 + return Ordering::Less; 232 + } 233 + 234 + Ordering::Equal 235 + }); 139 236 } 140 237 }
+5 -9
src/types/task.rs
··· 64 64 .into_active_model(), 65 65 ) 66 66 .set_due(due) 67 - // .set_due(Some(DateTime::new( 68 - // Date::from_ymd_opt(2026, 1, 31).unwrap(), 69 - // Time::from_hms_opt(10, 10, 10).unwrap(), 70 - // ))) 71 67 .insert(&kt.db) 72 68 .await?; 73 69 ··· 79 75 .await? 80 76 .expect("We just inserted it"); 81 77 82 - let mut task = TaskEntity::load() 78 + let mut task_am = TaskEntity::load() 83 79 .with((ZettelEntity, TagEntity)) 84 80 .filter_by_nano_id(inserted.nano_id) 85 81 .one(&kt.db) 86 82 .await? 87 83 .expect("We just inserted it"); 88 84 89 - task.group = HasOne::Loaded(Box::new(group)); 85 + task_am.group = HasOne::Loaded(Box::new(group)); 90 86 91 - println!("task: {task:#?}"); 87 + let task: Self = task_am.into(); 92 88 93 - // Ok(task.into()) 89 + kt.todo_tree.insert_task(&task); 94 90 95 - todo!() 91 + Ok(task) 96 92 } 97 93 98 94 pub fn due(&self) -> Option<String> {