Skip to main content

things3_cloud/wire/
notes.rs

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