1use crate::graph::NodeContents;
2use crate::helper::{get_current_da_time, get_current_time};
3use crate::AuthoredMessage;
4use crate::{Channel, Node, Result, UrbitAPIError};
5
6pub struct Notebook<'a> {
8 pub channel: &'a mut Channel,
9}
10
11pub type Comment = AuthoredMessage;
13
14#[derive(Clone, Debug)]
16pub struct Note {
17 pub title: String,
18 pub author: String,
19 pub time_sent: String,
20 pub contents: String,
21 pub comments: Vec<Comment>,
22 pub index: String,
23}
24
25#[derive(Clone, Debug)]
27struct NotebookIndex<'a> {
28 pub index: &'a str,
29 pub index_split: Vec<&'a str>,
30}
31
32impl Note {
33 pub fn new(
35 title: &str,
36 author: &str,
37 time_sent: &str,
38 contents: &str,
39 comments: &Vec<Comment>,
40 index: &str,
41 ) -> Note {
42 Note {
43 title: title.to_string(),
44 author: author.to_string(),
45 time_sent: time_sent.to_string(),
46 contents: contents.to_string(),
47 comments: comments.clone(),
48 index: index.to_string(),
49 }
50 }
51
52 pub fn from_node(node: &Node, revision: Option<String>) -> Result<Note> {
54 let mut comments: Vec<Comment> = vec![];
55 let comments_node = node
57 .children
58 .iter()
59 .find(|c| c.index_tail() == "2")
60 .ok_or(UrbitAPIError::InvalidNoteGraphNode(node.to_json().dump()))?;
61 let content_node = node
63 .children
64 .iter()
65 .find(|c| c.index_tail() == "1")
66 .ok_or(UrbitAPIError::InvalidNoteGraphNode(node.to_json().dump()))?;
67
68 for comment_node in &comments_node.children {
70 let mut latest_comment_revision_node = comment_node.children[0].clone();
71 for revision_node in &comment_node.children {
72 if revision_node.index_tail() > latest_comment_revision_node.index_tail() {
73 latest_comment_revision_node = revision_node.clone();
74 }
75 }
76 comments.push(Comment::from_node(&latest_comment_revision_node));
77 }
78
79 let mut fetched_revision_node = content_node.children[0].clone();
80
81 match revision {
82 Some(idx) => {
83 for revision_node in &content_node.children {
85 if revision_node.index == idx {
86 fetched_revision_node = revision_node.clone();
87 }
88 }
89 }
90 None => {
91 for revision_node in &content_node.children {
93 if revision_node.index_tail() > fetched_revision_node.index_tail() {
94 fetched_revision_node = revision_node.clone();
95 }
96 }
97 }
98 }
99 let title = format!("{}", fetched_revision_node.contents.content_list[0]["text"]);
101 let contents = format!("{}", fetched_revision_node.contents.content_list[1]["text"]);
103 let author = fetched_revision_node.author.clone();
104 let time_sent = fetched_revision_node.time_sent_formatted();
105
106 Ok(Note::new(
108 &title,
109 &author,
110 &time_sent,
111 &contents,
112 &comments,
113 &fetched_revision_node.index,
114 ))
115 }
116
117 pub fn content_as_markdown(&self) -> Vec<String> {
120 let formatted_string = self.contents.clone();
121 formatted_string
122 .split("\\n")
123 .map(|l| l.to_string())
124 .collect()
125 }
126}
127
128impl<'a> Notebook<'a> {
129 pub fn export_notebook(
131 &mut self,
132 notebook_ship: &str,
133 notebook_name: &str,
134 ) -> Result<Vec<Note>> {
135 let graph = &self
136 .channel
137 .graph_store()
138 .get_graph(notebook_ship, notebook_name)?;
139
140 let mut notes = vec![];
142 for node in &graph.nodes {
143 let note = Note::from_node(node, None)?;
144 notes.push(note);
145 }
146
147 Ok(notes)
148 }
149
150 pub fn fetch_note(
154 &mut self,
155 notebook_ship: &str,
156 notebook_name: &str,
157 note_index: &str,
158 ) -> Result<Note> {
159 let index = NotebookIndex::new(note_index);
161 if !index.is_valid() {
162 return Err(UrbitAPIError::InvalidNoteGraphNodeIndex(
163 note_index.to_string(),
164 ));
165 }
166
167 let note_root_index = index.note_root_index();
169
170 let node =
172 &self
173 .channel
174 .graph_store()
175 .get_node(notebook_ship, notebook_name, ¬e_root_index)?;
176 let revision = match index.is_note_revision() {
177 true => Some(note_index.to_string()),
178 false => None,
179 };
180
181 return Ok(Note::from_node(node, revision)?);
182 }
183
184 pub fn fetch_note_with_comment_index(
188 &mut self,
189 notebook_ship: &str,
190 notebook_name: &str,
191 comment_index: &str,
192 ) -> Result<Note> {
193 self.fetch_note(notebook_ship, notebook_name, comment_index)
194 }
195
196 pub fn fetch_note_latest_revision_index(
199 &mut self,
200 notebook_ship: &str,
201 notebook_name: &str,
202 note_index: &str,
203 ) -> Result<String> {
204 let index = NotebookIndex::new(note_index);
206 if !index.is_valid() {
207 return Err(UrbitAPIError::InvalidNoteGraphNodeIndex(
208 note_index.to_string(),
209 ));
210 }
211
212 let note_root_index = index.note_root_index();
214
215 let node =
217 &self
218 .channel
219 .graph_store()
220 .get_node(notebook_ship, notebook_name, ¬e_root_index)?;
221 for pnode in &node.children {
222 if pnode.index_tail() == "1" {
223 let mut latestindex = NotebookIndex::new(&pnode.children[0].index);
224 for rev in &pnode.children {
225 let revindex = NotebookIndex::new(&rev.index);
226 if revindex.index_tail() > latestindex.index_tail() {
227 latestindex = revindex.clone();
228 }
229 }
230 return Ok(latestindex.index.to_string());
231 }
232 }
233
234 Err(UrbitAPIError::InvalidNoteGraphNodeIndex(
235 note_index.to_string(),
236 ))
237 }
238
239 pub fn fetch_comment(
243 &mut self,
244 notebook_ship: &str,
245 notebook_name: &str,
246 comment_index: &str,
247 ) -> Result<Comment> {
248 let index = NotebookIndex::new(comment_index);
250
251 if !index.is_valid_comment_index() {
252 return Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
253 comment_index.to_string(),
254 ));
255 }
256 let comment_root_index = index.comment_root_index()?;
257
258 let node = &self.channel.graph_store().get_node(
260 notebook_ship,
261 notebook_name,
262 &comment_root_index,
263 )?;
264
265 if index.is_comment_root() {
266 let mut newest = node.children[0].clone();
268 for rnode in &node.children {
269 if rnode.index_tail() > newest.index_tail() {
270 newest = rnode.clone();
271 }
272 }
273 return Ok(Comment::from_node(&newest));
274 } else {
275 for rnode in &node.children {
277 if rnode.index == comment_index {
278 return Ok(Comment::from_node(&rnode));
279 }
280 }
281 }
282
283 Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
284 comment_index.to_string(),
285 ))
286 }
287
288 pub fn fetch_comment_latest_revision_index(
291 &mut self,
292 notebook_ship: &str,
293 notebook_name: &str,
294 comment_index: &str,
295 ) -> Result<String> {
296 let index = NotebookIndex::new(comment_index);
298
299 if !index.is_valid_comment_index() {
300 return Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
301 comment_index.to_string(),
302 ));
303 }
304 let comment_root_index = index.comment_root_index()?;
305
306 let node = &self.channel.graph_store().get_node(
308 notebook_ship,
309 notebook_name,
310 &comment_root_index,
311 )?;
312
313 if node.children.len() > 0 {
314 let mut newestindex = NotebookIndex::new(&node.children[0].index);
315 for rnode in &node.children {
316 let revindex = NotebookIndex::new(&rnode.index);
317 if revindex.index_tail() > newestindex.index_tail() {
318 newestindex = revindex.clone();
319 }
320 }
321 return Ok(newestindex.index.to_string());
322 }
323
324 Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
325 comment_index.to_string(),
326 ))
327 }
328
329 pub fn add_note(
332 &mut self,
333 notebook_ship: &str,
334 notebook_name: &str,
335 title: &str,
336 body: &str,
337 ) -> Result<String> {
338 let mut gs = self.channel.graph_store();
339 let node_root = gs.new_node(&NodeContents::new());
341 let unix_time = node_root.time_sent;
343 let index = NotebookIndex::new(&node_root.index);
345
346 let node_root = node_root
350 .add_child(&gs.new_node_specified(
351 &index.note_content_node_index(),
352 unix_time,
353 &NodeContents::new(),
354 ))
355 .add_child(&gs.new_node_specified(
356 &index.note_comments_node_index(),
357 unix_time,
358 &NodeContents::new(),
359 ))
360 .add_child(&gs.new_node_specified(
361 &index.note_revision_index(1),
362 unix_time,
363 &NodeContents::new().add_text(title).add_text(body),
364 ));
365
366 if let Ok(_) = gs.add_node(notebook_ship, notebook_name, &node_root) {
367 Ok(index.note_revision_index(1))
368 } else {
369 Err(UrbitAPIError::FailedToCreateNote(
370 node_root.to_json().dump(),
371 ))
372 }
373 }
374
375 pub fn update_note(
379 &mut self,
380 notebook_ship: &str,
381 notebook_name: &str,
382 note_index: &str,
383 title: &str,
384 body: &str,
385 ) -> Result<String> {
386 let note_latest_index =
388 self.fetch_note_latest_revision_index(notebook_ship, notebook_name, note_index)?;
389 let index = NotebookIndex::new(¬e_latest_index);
391 let note_new_index = index.next_revision_index()?;
393
394 let mut gs = self.channel.graph_store();
395 let unix_time = get_current_time();
396
397 let node = gs.new_node_specified(
399 ¬e_new_index,
400 unix_time,
401 &NodeContents::new().add_text(title).add_text(body),
402 );
403
404 if let Ok(_) = gs.add_node(notebook_ship, notebook_name, &node) {
405 Ok(node.index.clone())
406 } else {
407 Err(UrbitAPIError::FailedToCreateNote(node.to_json().dump()))
408 }
409 }
410
411 pub fn add_comment(
414 &mut self,
415 notebook_ship: &str,
416 notebook_name: &str,
417 note_index: &str,
418 comment: &NodeContents,
419 ) -> Result<String> {
420 let index = NotebookIndex::new(note_index);
422 if !index.is_valid() {
423 return Err(UrbitAPIError::InvalidNoteGraphNodeIndex(
424 note_index.to_string(),
425 ));
426 }
427
428 let mut gs = self.channel.graph_store();
429 let unix_time = get_current_time();
430
431 let cmt_root_node = gs.new_node_specified(
433 &index.new_comment_root_index(),
434 unix_time,
435 &NodeContents::new(),
436 );
437 let index = NotebookIndex::new(&cmt_root_node.index);
439 let cmt_rev_index = index.comment_revision_index(1)?;
441 let cmt_rev_node = gs.new_node_specified(&cmt_rev_index, unix_time, comment);
442 let cmt_root_node = cmt_root_node.add_child(&cmt_rev_node);
444 if let Ok(_) = gs.add_node(notebook_ship, notebook_name, &cmt_root_node) {
446 Ok(cmt_rev_index.clone())
447 } else {
448 Err(UrbitAPIError::FailedToCreateComment(
449 cmt_root_node.to_json().dump(),
450 ))
451 }
452 }
453
454 pub fn update_comment(
458 &mut self,
459 notebook_ship: &str,
460 notebook_name: &str,
461 comment_index: &str,
462 comment: &NodeContents,
463 ) -> Result<String> {
464 let cmt_latest_index =
466 self.fetch_comment_latest_revision_index(notebook_ship, notebook_name, comment_index)?;
467 let index = NotebookIndex::new(&cmt_latest_index);
469 let cmt_new_index = index.next_revision_index()?;
471
472 let mut gs = self.channel.graph_store();
474 let unix_time = get_current_time();
475
476 let node = gs.new_node_specified(&cmt_new_index, unix_time, comment);
477
478 if let Ok(_) = gs.add_node(notebook_ship, notebook_name, &node) {
479 Ok(node.index.clone())
480 } else {
481 Err(UrbitAPIError::FailedToCreateComment(node.to_json().dump()))
482 }
483 }
484}
485
486impl<'a> NotebookIndex<'a> {
487 pub fn new(idx: &str) -> NotebookIndex {
489 NotebookIndex {
490 index: idx,
491 index_split: idx.split("/").collect(),
492 }
493 }
494
495 pub fn is_valid(&self) -> bool {
505 (self.index_split.len() >= 2) && (self.index_split[0].len() == 0)
506 }
507
508 pub fn is_note_root(&self) -> bool {
510 (self.index_split.len() == 2) && (self.index_split[0].len() == 0)
511 }
512
513 pub fn is_note_revision(&self) -> bool {
515 (self.index_split.len() == 4)
516 && (self.index_split[0].len() == 0)
517 && (self.index_split[2] == "1")
518 }
519
520 pub fn is_valid_comment_index(&self) -> bool {
522 (self.index_split.len() >= 4)
523 && (self.index_split[0].len() == 0)
524 && (self.index_split[2] == "2")
525 }
526
527 pub fn is_comment_root(&self) -> bool {
529 (self.index_split.len() == 4)
530 && (self.index_split[0].len() == 0)
531 && (self.index_split[2] == "2")
532 }
533
534 pub fn is_comment_revision(&self) -> bool {
536 (self.index_split.len() == 5)
537 && (self.index_split[0].len() == 0)
538 && (self.index_split[2] == "2")
539 }
540
541 pub fn note_root_index(&self) -> String {
543 format!("/{}", self.index_split[1])
544 }
545
546 pub fn note_content_node_index(&self) -> String {
548 format!("/{}/1", self.index_split[1])
549 }
550
551 pub fn note_comments_node_index(&self) -> String {
553 format!("/{}/2", self.index_split[1])
554 }
555
556 pub fn comment_root_index(&self) -> Result<String> {
559 if self.is_valid_comment_index() {
560 Ok(format!(
561 "/{}/2/{}",
562 self.index_split[1], self.index_split[3]
563 ))
564 } else {
565 Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
566 self.index.to_string(),
567 ))
568 }
569 }
570 pub fn new_comment_root_index(&self) -> String {
572 format!("/{}/2/{}", self.index_split[1], get_current_da_time())
573 }
574
575 pub fn index_tail(&self) -> &str {
577 self.index_split[self.index_split.len() - 1]
578 }
579
580 pub fn revision(&self) -> Result<u64> {
582 if self.is_note_revision() {
583 if let Ok(r) = self.index_split[3].parse::<u64>() {
584 return Ok(r);
585 }
586 } else if self.is_comment_revision() {
587 if let Ok(r) = self.index_split[4].parse::<u64>() {
588 return Ok(r);
589 }
590 }
591
592 Err(UrbitAPIError::InvalidNoteGraphNodeIndex(
593 self.index.to_string(),
594 ))
595 }
596
597 pub fn next_revision_index(&self) -> Result<String> {
599 let rev = self.revision()?;
600 let newrev = rev + 1;
601 if self.index_split.len() == 5 {
603 Ok(format!(
604 "/{}/2/{}/{}",
605 self.index_split[1],
606 self.index_split[3],
607 &newrev.to_string()
608 ))
609 } else {
610 Ok(format!(
611 "/{}/1/{}",
612 self.index_split[1],
613 &newrev.to_string()
614 ))
615 }
616 }
617
618 pub fn note_revision_index(&self, revision: u64) -> String {
620 format!("/{}/1/{}", self.index_split[1], revision.to_string())
621 }
622
623 pub fn comment_revision_index(&self, revision: u64) -> Result<String> {
625 if self.is_valid_comment_index() {
626 Ok(format!(
627 "/{}/2/{}/{}",
628 self.index_split[1],
629 self.index_split[3],
630 revision.to_string()
631 ))
632 } else {
633 Err(UrbitAPIError::InvalidCommentGraphNodeIndex(
634 self.index.to_string(),
635 ))
636 }
637 }
638}