1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_with::skip_serializing_none;
4
5use super::{
6 emoji::Emoji, file::File, native_icon::NativeIcon, parent::Parent, rich_text::RichText,
7 user::User,
8};
9
10#[skip_serializing_none]
11#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
12pub struct Block {
13 pub object: Option<String>,
14 pub id: Option<String>,
15 pub parent: Option<Parent>,
16 #[serde(flatten)]
17 pub block_type: BlockType,
18 pub created_time: Option<DateTime<Utc>>,
19 pub created_by: Option<User>,
20 pub last_edited_time: Option<DateTime<Utc>>,
21 pub last_edited_by: Option<User>,
22 pub in_trash: Option<bool>,
23 pub has_children: Option<bool>,
24}
25
26#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
27#[serde(tag = "type", rename_all = "snake_case")]
28pub enum BlockType {
29 #[default]
30 None,
31 Bookmark {
32 bookmark: BookmarkValue,
33 },
34 Breadcrumb {
35 breadcrump: BreadcrumpValue,
36 },
37 BulletedListItem {
38 bulleted_list_item: BulletedListItemValue,
39 },
40 Callout {
41 callout: CalloutValue,
42 },
43 ChildDatabase {
44 child_database: ChildDatabaseValue,
45 },
46 ChildPage {
47 child_page: ChildPageValue,
48 },
49 Code {
50 code: CodeValue,
51 },
52 ColumnList {
53 column_list: ColumnListValue,
54 },
55 Column {
56 column: ColumnValue,
57 },
58 Divider {
59 divider: DividerValue,
60 },
61 Embed {
62 embed: EmbedValue,
63 },
64 Equation {
65 equation: EquationValue,
66 },
67 File {
68 file: FileValue,
69 },
70 #[serde(rename = "heading_1")]
71 Heading1 {
72 heading_1: HeadingsValue,
73 },
74 #[serde(rename = "heading_2")]
75 Heading2 {
76 heading_2: HeadingsValue,
77 },
78 #[serde(rename = "heading_3")]
79 Heading3 {
80 heading_3: HeadingsValue,
81 },
82 Image {
83 image: ImageValue,
84 },
85 LinkPreview {
86 link_preview: LinkPreviewValue,
87 },
88 NumberedListItem {
89 numbered_list_item: NumberedListItemValue,
90 },
91 Paragraph {
92 paragraph: ParagraphValue,
93 },
94 Pdf {
95 pdf: PdfValue,
96 },
97 Quote {
98 quote: QuoteValue,
99 },
100 SyncedBlock {
101 synced_block: SyncedBlockValue,
102 },
103 Table {
104 table: TableValue,
105 },
106 TableOfContents {
107 table_of_contents: TableOfContentsValue,
108 },
109 TableRow {
110 table_row: TableRowsValue,
111 },
112 Template {
113 template: TemplateValue,
114 },
115 ToDo {
116 to_do: ToDoValue,
117 },
118 Toggle {
119 toggle: ToggleValue,
120 },
121 Video {
122 video: VideoValue,
123 },
124 LinkToPage {
125 link_to_page: Parent,
126 },
127 MeetingNotes {
128 meeting_notes: MeetingNotesValue,
129 },
130 Unsupported,
131}
132
133#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
134pub struct BookmarkValue {
135 pub caption: Vec<RichText>,
136 pub url: String,
137}
138
139#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
140pub struct BreadcrumpValue {}
141
142#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
143pub struct BulletedListItemValue {
144 pub rich_text: Vec<RichText>,
145 pub color: TextColor,
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub children: Option<Vec<Block>>,
148}
149
150#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
151pub struct CalloutValue {
152 pub rich_text: Vec<RichText>,
153 pub icon: Option<Icon>,
154 pub color: TextColor,
155}
156
157#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
158pub struct ChildDatabaseValue {
159 pub title: String,
160}
161
162#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
163pub struct ChildPageValue {
164 pub title: String,
165}
166
167#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
168pub struct CodeValue {
169 pub caption: Vec<RichText>,
170 pub rich_text: Vec<RichText>,
171 pub language: Language,
172}
173
174#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
175pub struct ColumnListValue {}
176
177#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
178pub struct ColumnValue {}
179
180#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
181pub struct DividerValue {}
182
183#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
184pub struct EmbedValue {
185 pub url: String,
186}
187
188#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
189pub struct EquationValue {
190 pub expression: String,
191}
192
193#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
194pub struct FileValue {
195 pub caption: Vec<RichText>,
196 #[serde(flatten)]
197 pub file_type: File,
198 pub name: String,
199}
200
201#[skip_serializing_none]
202#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
203pub struct HeadingsValue {
204 pub rich_text: Vec<RichText>,
205 pub color: Option<TextColor>,
206 pub is_toggleable: Option<bool>,
207}
208
209#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
210pub struct ImageValue {
211 #[serde(flatten)]
212 pub file_type: File,
213}
214
215#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
216pub struct LinkPreviewValue {
217 pub url: String,
218}
219
220#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
221pub struct NumberedListItemValue {
222 pub rich_text: Vec<RichText>,
223 pub color: TextColor,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 pub children: Option<Vec<Block>>,
226}
227
228#[skip_serializing_none]
229#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
230pub struct ParagraphValue {
231 pub rich_text: Vec<RichText>,
232 pub color: Option<TextColor>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub children: Option<Vec<Block>>,
235}
236
237#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
238pub struct PdfValue {
239 pub caption: Vec<RichText>,
240 #[serde(flatten)]
241 pub file_type: File,
242}
243
244#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
245pub struct QuoteValue {
246 pub rich_text: Vec<RichText>,
247 pub color: TextColor,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub children: Option<Vec<Block>>,
250}
251
252#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
253pub struct SyncedBlockValue {
254 pub synced_from: Option<SyncedFrom>,
255 #[serde(skip_serializing_if = "Option::is_none")]
256 pub children: Option<Vec<Block>>,
257}
258
259#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
260#[serde(tag = "type", rename_all = "snake_case")]
261pub enum SyncedFrom {
262 BlockId { block_id: String },
263}
264
265#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
266pub struct TableValue {
267 pub table_width: u32,
268 pub has_column_header: bool,
269 pub has_row_header: bool,
270 #[serde(skip_serializing_if = "Option::is_none")]
271 pub children: Option<Vec<Block>>,
272}
273
274#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
275pub struct TableRowsValue {
276 pub cells: Vec<Vec<RichText>>,
277}
278
279#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
280pub struct TableOfContentsValue {
281 pub color: TextColor,
282}
283
284#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
285pub struct TemplateValue {
286 pub rich_text: Vec<RichText>,
287 #[serde(skip_serializing_if = "Option::is_none")]
288 pub children: Option<Vec<Block>>,
289}
290
291#[skip_serializing_none]
292#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
293pub struct ToDoValue {
294 pub rich_text: Vec<RichText>,
295 pub checked: Option<bool>,
296 pub color: Option<TextColor>,
297 #[serde(skip_serializing_if = "Option::is_none")]
298 pub children: Option<Vec<Block>>,
299}
300
301#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
302pub struct ToggleValue {
303 pub rich_text: Vec<RichText>,
304 pub color: TextColor,
305 #[serde(skip_serializing_if = "Option::is_none")]
306 pub children: Option<Vec<Block>>,
307}
308
309#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
310pub struct VideoValue {
311 #[serde(flatten)]
312 pub file_type: File,
313}
314
315#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
316pub struct MeetingNotesValue {}
317
318#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
319#[serde(rename_all = "snake_case")]
320pub enum TextColor {
321 Blue,
322 BlueBackground,
323 Brown,
324 BrownBackground,
325 Default,
326 Gray,
327 GrayBackground,
328 Green,
329 GreenBackground,
330 Orange,
331 OrangeBackground,
332 Yellow,
333 YellowBackground,
334 Pink,
335 PinkBackground,
336 Purple,
337 PurpleBackground,
338 Red,
339 RedBackground,
340}
341
342#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
343#[serde(rename_all = "snake_case", untagged)]
344pub enum Icon {
345 File(File),
346 Emoji(Emoji),
347 NativeIcon(NativeIcon),
348}
349
350#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
351#[serde(rename_all = "lowercase")]
352pub enum Language {
353 Abap,
354 Arduino,
355 Bash,
356 Basic,
357 C,
358 Clojure,
359 Coffeescript,
360 #[serde(rename = "c++")]
361 CPlusPlus,
362 #[serde(rename = "c#")]
363 CSharp,
364 Css,
365 Dart,
366 Diff,
367 Docker,
368 Elixir,
369 Elm,
370 Erlang,
371 Flow,
372 Fortran,
373 #[serde(rename = "f#")]
374 FSharp,
375 Gherkin,
376 Glsl,
377 Go,
378 Graphql,
379 Groovy,
380 Haskell,
381 Html,
382 Java,
383 Javascript,
384 Json,
385 Julia,
386 Kotlin,
387 Latex,
388 Less,
389 Lisp,
390 Livescript,
391 Lua,
392 Makefile,
393 Markdown,
394 Markup,
395 Matlab,
396 Mermaid,
397 Nix,
398 #[serde(rename = "objective-c")]
399 ObjectiveC,
400 Ocaml,
401 Pascal,
402 Perl,
403 Php,
404 #[serde(rename = "plain text")]
405 PlainText,
406 Powershell,
407 Prolog,
408 Protobuf,
409 Python,
410 R,
411 Reason,
412 Ruby,
413 Rust,
414 Sass,
415 Scala,
416 Scheme,
417 Scss,
418 Shell,
419 Sql,
420 Swift,
421 Solidity,
422 Typescript,
423 #[serde(rename = "vb.net")]
424 VbNet,
425 Verilog,
426 Vhdl,
427 #[serde(rename = "visual basic")]
428 VisualBasic,
429 Webassembly,
430 Xml,
431 Yaml,
432 #[serde(rename = "java/c/c++/c#")]
433 JavaOrCOrCPlusPlusOrCSharp,
434}
435
436impl BlockType {
437 pub fn plain_text(&self) -> Vec<Option<String>> {
438 match self {
439 BlockType::None => vec![],
440 BlockType::Bookmark { bookmark } => {
441 bookmark.caption.iter().map(|rt| rt.plain_text()).collect()
442 }
443 BlockType::Breadcrumb { breadcrump: _ } => vec![],
444 BlockType::BulletedListItem { bulleted_list_item } => {
445 let mut items = bulleted_list_item
446 .rich_text
447 .iter()
448 .map(|rt| rt.plain_text())
449 .collect();
450 let children = &bulleted_list_item.children;
451 let Some(children) = children else {
452 return items;
453 };
454 items.append(
455 &mut children
456 .iter()
457 .flat_map(|b| b.block_type.plain_text())
458 .collect(),
459 );
460 items
461 }
462 BlockType::Callout { callout } => {
463 callout.rich_text.iter().map(|rt| rt.plain_text()).collect()
464 }
465 BlockType::ChildDatabase { child_database } => vec![Some(child_database.title.clone())],
466 BlockType::ChildPage { child_page } => vec![Some(child_page.title.clone())],
467 BlockType::Code { code } => code.caption.iter().map(|rt| rt.plain_text()).collect(),
468 BlockType::ColumnList { column_list: _ } => vec![],
469 BlockType::Column { column: _ } => vec![],
470 BlockType::Divider { divider: _ } => vec![],
471 BlockType::Embed { embed: _ } => vec![],
472 BlockType::Equation { equation: _ } => vec![],
473 BlockType::File { file } => file.caption.iter().map(|rt| rt.plain_text()).collect(),
474 BlockType::Heading1 { heading_1 } => heading_1
475 .rich_text
476 .iter()
477 .map(|rt| rt.plain_text())
478 .collect(),
479 BlockType::Heading2 { heading_2 } => heading_2
480 .rich_text
481 .iter()
482 .map(|rt| rt.plain_text())
483 .collect(),
484 BlockType::Heading3 { heading_3 } => heading_3
485 .rich_text
486 .iter()
487 .map(|rt| rt.plain_text())
488 .collect(),
489 BlockType::Image { image: _ } => vec![],
490 BlockType::LinkPreview { link_preview: _ } => vec![],
491 BlockType::NumberedListItem { numbered_list_item } => {
492 let mut items = numbered_list_item
493 .rich_text
494 .iter()
495 .map(|rt| rt.plain_text())
496 .collect();
497 let children = &numbered_list_item.children;
498 let Some(children) = children else {
499 return items;
500 };
501 items.append(
502 &mut children
503 .iter()
504 .flat_map(|b| b.block_type.plain_text())
505 .collect(),
506 );
507 items
508 }
509 BlockType::Paragraph { paragraph } => {
510 let mut items = paragraph
511 .rich_text
512 .iter()
513 .map(|rt| rt.plain_text())
514 .collect();
515 let children = ¶graph.children;
516 let Some(children) = children else {
517 return items;
518 };
519 items.append(
520 &mut children
521 .iter()
522 .flat_map(|b| b.block_type.plain_text())
523 .collect(),
524 );
525 items
526 }
527 BlockType::Pdf { pdf } => pdf.caption.iter().map(|rt| rt.plain_text()).collect(),
528 BlockType::Quote { quote } => {
529 let mut items = quote.rich_text.iter().map(|rt| rt.plain_text()).collect();
530 let children = "e.children;
531 let Some(children) = children else {
532 return items;
533 };
534 items.append(
535 &mut children
536 .iter()
537 .flat_map(|b| b.block_type.plain_text())
538 .collect(),
539 );
540 items
541 }
542 BlockType::SyncedBlock { synced_block } => {
543 let Some(children) = &synced_block.children else {
544 return vec![];
545 };
546 children
547 .iter()
548 .flat_map(|b| b.block_type.plain_text())
549 .collect()
550 }
551 BlockType::Table { table } => {
552 let Some(children) = &table.children else {
553 return vec![];
554 };
555 children
556 .iter()
557 .flat_map(|b| b.block_type.plain_text())
558 .collect()
559 }
560 BlockType::TableOfContents {
561 table_of_contents: _,
562 } => vec![],
563 BlockType::TableRow { table_row } => table_row
564 .cells
565 .iter()
566 .flatten()
567 .map(|rt| rt.plain_text())
568 .collect(),
569 BlockType::Template { template } => {
570 let mut items = template
571 .rich_text
572 .iter()
573 .map(|rt| rt.plain_text())
574 .collect();
575 let children = &template.children;
576 let Some(children) = children else {
577 return items;
578 };
579 items.append(
580 &mut children
581 .iter()
582 .flat_map(|b| b.block_type.plain_text())
583 .collect(),
584 );
585 items
586 }
587 BlockType::ToDo { to_do } => {
588 let mut items = to_do.rich_text.iter().map(|rt| rt.plain_text()).collect();
589 let children = &to_do.children;
590 let Some(children) = children else {
591 return items;
592 };
593 items.append(
594 &mut children
595 .iter()
596 .flat_map(|b| b.block_type.plain_text())
597 .collect(),
598 );
599 items
600 }
601 BlockType::Toggle { toggle } => {
602 let mut items = toggle.rich_text.iter().map(|rt| rt.plain_text()).collect();
603 let children = &toggle.children;
604 let Some(children) = children else {
605 return items;
606 };
607 items.append(
608 &mut children
609 .iter()
610 .flat_map(|b| b.block_type.plain_text())
611 .collect(),
612 );
613 items
614 }
615 BlockType::Video { video: _ } => vec![],
616 BlockType::LinkToPage { link_to_page: _ } => vec![],
617 BlockType::MeetingNotes { meeting_notes: _ } => vec![],
618 BlockType::Unsupported => vec![],
619 }
620 }
621}