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