···7777 }
78787979 for component in &mut self.components {
8080- component.init(tui.size()?)?;
8080+ component.init(tui.size()?).await?;
8181 }
82828383 let signal_tx = self.signal_tx.clone();
+1-1
src/tui/components/mod.rs
···5858 /// # Returns
5959 ///
6060 /// * [`color_eyre::Result<()>`] - An Ok result or an error.
6161- fn init(&mut self, area: Size) -> color_eyre::Result<()> {
6161+ async fn init(&mut self, area: Size) -> color_eyre::Result<()> {
6262 let _ = area; // to appease clippy
6363 Ok(())
6464 }
+41-34
src/tui/components/zk/mod.rs
···22use color_eyre::eyre::Result;
33use ratatui::{
44 prelude::*,
55- widgets::{Block, List, ListState},
55+ widgets::{Block, ListState},
66};
77use tokio::sync::mpsc::UnboundedSender;
8899use crate::{
1010 tui::{Signal, components::Component},
1111- types::{KastenHandle, ZettelId},
1111+ types::KastenHandle,
1212};
13131414mod preview;
1515+mod zettel_list;
1516mod zettel_view;
16171718use preview::Preview;
1919+use zettel_list::ZettelList;
1820use zettel_view::ZettelView;
19212222+/// in theory we could do some fancy `type_state` encoding stuff
2323+/// to make this work cleanly (so we know when the widgets are properly
2424+/// initialized)
2025pub struct Zk<'text> {
2126 signal_tx: Option<UnboundedSender<Signal>>,
2227 kh: KastenHandle,
···3237 z_preview: Layout,
3338}
34393535-struct ZettelList<'text> {
3636- render_list: List<'text>,
3737- id_list: Vec<ZettelId>,
3838- state: ListState,
3939-}
4040-4140impl Default for Layouts {
4241 fn default() -> Self {
4342 Self {
···63626463 let nodes = kt.graph.nodes_iter().collect::<Vec<_>>();
65646666- let zettel_list = {
6767- let render_list = List::new(nodes.iter().map(|(_, n)| {
6868- let z = n.payload();
6969- let title = z.title.clone();
7070- let _tags = z.tags.clone();
7171- // let _last_modified = z.modified_at;
7272- Text::from(title)
7373- }))
7474- .style(Color::White)
7575- .highlight_style(Modifier::REVERSED)
7676- .highlight_symbol("> ");
7777-7878- let id_list = nodes
7979- .iter()
8080- .map(|(_, n)| n.payload().id.clone())
8181- .collect::<Vec<_>>();
8282-8383- let mut state = ListState::default();
8484- state.select_first();
8585-8686- ZettelList {
8787- render_list,
8888- id_list,
8989- state,
9090- }
9191- };
6565+ // in theory this is wasted compute, we should be initializing all our
6666+ // stuff inside the init function
6767+ let mut l_state = ListState::default();
6868+ l_state.select_first();
6969+ let zettel_list = ZettelList::new(nodes.as_slice(), l_state, 0);
92709371 let selected_zettel = zettel_list
9472 .id_list
···167145168146#[async_trait]
169147impl Component for Zk<'_> {
148148+ /// this tells us how big the space we have for this is
149149+ async fn init(&mut self, area: Size) -> color_eyre::Result<()> {
150150+ let total_width = area.width;
151151+152152+ let kt = self.kh.read().await;
153153+154154+ let nodes = kt.graph.nodes_iter().collect::<Vec<_>>();
155155+156156+ let mut l_state = ListState::default();
157157+ l_state.select_first();
158158+159159+ let zettel_list = ZettelList::new(nodes.as_slice(), l_state, total_width / 2);
160160+161161+ self.zettel_list = zettel_list;
162162+163163+ drop(kt);
164164+165165+ Ok(())
166166+ }
167167+170168 fn register_signal_handler(&mut self, tx: UnboundedSender<Signal>) -> Result<()> {
171169 self.signal_tx = Some(tx);
172170 Ok(())
···223221 .get_node_by_zettel_id(id)
224222 .expect("Invariant broken, this must exist.");
225223224224+ //TODO: we would actually want to create the new zettel_list_item
225225+ // and insert it into the list where the selected id is there, that
226226+ // is what makes the most sense imo
227227+ self.zettel_list = ZettelList::new(
228228+ // ideally we dont want to do this right?
229229+ kh.graph.nodes_iter().collect::<Vec<_>>().as_slice(),
230230+ self.zettel_list.state,
231231+ self.zettel_list.width,
232232+ );
226233 self.zettel_view = ZettelView::from(node.payload());
227234 self.preview = Preview::from(node.payload().content(&kh.ws).await?);
228235 drop(kh);
···11use dto::ColorDTO;
22+use ratatui::style::Color as RatColor;
2334/// Agnostic Color type,
45/// internally represented as rgb
55-#[expect(dead_code)]
66-#[derive(Debug, Clone)]
66+#[derive(Debug, Copy, Clone, Default)]
77pub struct Color(ColorDTO);
8899impl From<ColorDTO> for Color {
···1111 Self(value)
1212 }
1313}
1414+1515+impl From<Color> for RatColor {
1616+ fn from(value: Color) -> Self {
1717+ let rgb = value.0.to_rgb8();
1818+1919+ Self::Rgb(rgb.r, rgb.g, rgb.b)
2020+ }
2121+}
-1
src/types/tag.rs
···4455/// Represents a `Tag` in a `ZettelKasten` note taking method.
66/// Easy way to link multiple notes under one simple word.
77-#[expect(dead_code)]
87#[derive(Debug, Clone)]
98pub struct Tag {
109 /// Should only be constructed from models.
+86-7
src/types/zettel.rs
···11use dto::{
22 ActiveModelTrait, ActiveValue, ColumnTrait, DateTime, EntityTrait as _, IntoActiveModel,
33- QueryFilter, TagActiveModel, TagEntity, ZettelActiveModel, ZettelActiveModelEx, ZettelEntity,
33+ QueryFilter, TagActiveModel, TagEntity, ZettelActiveModel, ZettelEntity, ZettelModel,
44 ZettelModelEx, ZettelTagActiveModel, ZettelTagColumns, ZettelTagEntity,
55};
66use pulldown_cmark::{Event, Parser, Tag as MkTag};
···1515use dto::NanoId;
1616use tokio::{fs::File, io::AsyncWriteExt};
17171818-use crate::types::{FrontMatter, Kasten, Link, Tag, Workspace, frontmatter};
1818+use crate::types::{FrontMatter, Link, Tag, Workspace, frontmatter};
19192020/// A `Zettel` is a note about a single idea.
2121/// It can have many `Tag`s, just meaning it can fall under many
···8080 }
81818282 pub async fn sync_with_file(&mut self, ws: &Workspace) -> Result<()> {
8383- let (fm, content) = FrontMatter::extract_from_file(self.absolute_path(ws)).await?;
8383+ let (fm, _) = FrontMatter::extract_from_file(self.absolute_path(ws)).await?;
8484+8585+ let mut model = ZettelEntity::find_by_nano_id(self.id.clone())
8686+ .one(&ws.db)
8787+ .await?
8888+ .expect("this must exist")
8989+ .into_active_model();
9090+9191+ model.title = ActiveValue::Set(fm.title);
9292+9393+ let updated: ZettelModel = model.update(&ws.db).await?;
9494+9595+ self.title = updated.title;
9696+ self.modified_at = updated.modified_at;
9797+ self.created_at = updated.created_at;
9898+9999+ self.sync_tags(ws).await?;
100100+101101+ Ok(())
102102+ }
103103+104104+ /// Sync's `Tag`'s that are present in the frontmatter of this
105105+ /// `Zettel` to the database, and then updates the `Tag`s on the
106106+ /// `Zettel` to reflect the changes.
107107+ pub async fn sync_tags(&mut self, ws: &Workspace) -> Result<()> {
108108+ let mut fm = self.front_matter(ws).await?;
109109+ fm.tag_strings.sort();
110110+111111+ let mut tag_strings = fm.tag_strings;
112112+113113+ let Some(db_zettel): Option<ZettelModelEx> = ZettelEntity::load()
114114+ .with(TagEntity)
115115+ .filter_by_nano_id(self.id.clone())
116116+ .one(&ws.db)
117117+ .await?
118118+ else {
119119+ panic!("how the fuck was this deleted");
120120+ };
121121+122122+ for db_tag in db_zettel.tags {
123123+ if let Ok(idx) = tag_strings.binary_search(&db_tag.name) {
124124+ // we remove tags we have already processed
125125+ tag_strings.remove(idx);
126126+ } else {
127127+ // the db says the file has tag `x`, but that tag is missing from the
128128+ // front matter, we can assume its gone, lets delete that link
129129+ let to_remove = ZettelTagEntity::find()
130130+ .filter(ZettelTagColumns::ZettelNanoId.eq(self.id.0.clone()))
131131+ .filter(ZettelTagColumns::TagNanoId.eq(db_tag.nano_id))
132132+ .one(&ws.db)
133133+ .await?
134134+ .expect("this link must exist");
841358585- self.title = fm.title;
136136+ to_remove.into_active_model().delete(&ws.db).await?;
137137+ }
138138+ }
861398787- // it could have new tags and stuff...
140140+ // now any tags that are left inside zettel_tag_strings,
141141+ // we have to put them inside the db
142142+ for new_tag in tag_strings {
143143+ // create a new tag
144144+ let tag = TagActiveModel {
145145+ name: ActiveValue::Set(new_tag),
146146+ ..Default::default()
147147+ }
148148+ .insert(&ws.db)
149149+ .await?;
881508989- // todo!();
151151+ // this zettel has this tag now
152152+ let _ = ZettelTagActiveModel {
153153+ zettel_nano_id: ActiveValue::Set(self.id.to_string()),
154154+ tag_nano_id: ActiveValue::Set(tag.nano_id.to_string()),
155155+ }
156156+ .insert(&ws.db)
157157+ .await?;
158158+ }
159159+160160+ let entity = ZettelEntity::load()
161161+ .with(TagEntity)
162162+ .filter_by_nano_id(self.id.clone())
163163+ .one(&ws.db)
164164+ .await?
165165+ .expect("this exists");
166166+167167+ let temp_zettel: Self = entity.into();
168168+169169+ self.tags = temp_zettel.tags;
9017091171 Ok(())
92172 }
···118198 }
119199120200 /// uses the id and root to parse out of the root directory
121121- #[expect(dead_code)]
122201 pub async fn from_id(id: &ZettelId, ws: &Workspace) -> Result<Self> {
123202 let mut path = ws.root.clone();
124203 path.push(id.0.to_string());