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