Skip to main content

things3_cloud/wire/
notes.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::BTreeMap;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
6#[serde(untagged)]
7pub enum TaskNotes {
8    Plain(String),
9    Structured(StructuredTaskNotes),
10    Unknown(Value),
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct StructuredTaskNotes {
15    #[serde(rename = "_t", default)]
16    pub object_type: Option<String>,
17    #[serde(rename = "t")]
18    pub format_type: i32,
19    #[serde(default)]
20    pub ch: Option<u32>,
21    #[serde(default)]
22    pub v: Option<String>,
23    #[serde(default)]
24    pub ps: Vec<StructuredTaskNoteParagraph>,
25    #[serde(flatten)]
26    pub unknown_fields: BTreeMap<String, Value>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
30pub struct StructuredTaskNoteParagraph {
31    #[serde(default)]
32    pub r: Option<String>,
33    #[serde(flatten)]
34    pub unknown_fields: BTreeMap<String, Value>,
35}
36
37impl TaskNotes {
38    pub fn to_plain_text(&self) -> Option<String> {
39        match self {
40            Self::Plain(s) => {
41                let normalized = s.replace('\u{2028}', "\n").replace('\u{2029}', "\n");
42                let trimmed = normalized.trim();
43                if trimmed.is_empty() {
44                    None
45                } else {
46                    Some(trimmed.to_string())
47                }
48            }
49            Self::Structured(structured) => match structured.format_type {
50                1 => structured.v.as_ref().and_then(|s| {
51                    let normalized = s.replace('\u{2028}', "\n").replace('\u{2029}', "\n");
52                    let trimmed = normalized.trim();
53                    if trimmed.is_empty() {
54                        None
55                    } else {
56                        Some(trimmed.to_string())
57                    }
58                }),
59                2 => {
60                    let lines: Vec<String> =
61                        structured.ps.iter().filter_map(|p| p.r.clone()).collect();
62                    let joined = lines.join("\n");
63                    if joined.trim().is_empty() {
64                        None
65                    } else {
66                        Some(joined)
67                    }
68                }
69                _ => None,
70            },
71            Self::Unknown(_) => None,
72        }
73    }
74}