things3_cloud/wire/
notes.rs1use 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}