elmethis_notion/
client.rs

1#[derive(Debug)]
2pub struct Client {
3    pub notionrs_client: notionrs::client::Client,
4}
5
6#[derive(Debug)]
7pub struct Image {
8    pub src: String,
9    pub id: String,
10}
11
12impl Client {
13    pub fn new<T>(secret: T) -> Self
14    where
15        T: AsRef<str>,
16    {
17        Client {
18            notionrs_client: notionrs::client::Client::new().secret(secret),
19        }
20    }
21
22    #[async_recursion::async_recursion]
23    pub async fn convert_block(
24        &mut self,
25        page_id: &str,
26    ) -> Result<Vec<crate::block::Block>, crate::error::Error> {
27        let mut blocks: Vec<crate::block::Block> = Vec::new();
28
29        let results = self
30            .notionrs_client
31            .get_block_children_all()
32            .block_id(page_id)
33            .send()
34            .await?;
35
36        for block in results {
37            match block.block {
38                notionrs::object::block::Block::Audio { audio: _ } => {}
39                notionrs::object::block::Block::Bookmark { bookmark } => {
40                    let mut props = crate::block::ElmBookmarkProps {
41                        url: bookmark.url.clone(),
42                        margin: "2rem".to_string(),
43                        ..Default::default()
44                    };
45
46                    let response = reqwest::Client::new()
47                        .get(&bookmark.url)
48                        .header("user-agent", "Rust - reqwest")
49                        .send()
50                        .await?
51                        .text()
52                        .await?;
53
54                    let document = scraper::Html::parse_document(&response);
55
56                    // title
57
58                    let title = document
59                        .select(&scraper::Selector::parse("title")?)
60                        .next()
61                        .map(|element| element.text().collect::<String>());
62
63                    let og_title_selector = scraper::Selector::parse("meta[property='og:title']")?;
64
65                    if let Some(element) = document.select(&og_title_selector).next() {
66                        if let Some(content) = element.value().attr("content") {
67                            props.title = Some(content.to_string());
68                        }
69                    }
70
71                    if let Some(title) = title {
72                        props.title = Some(title);
73                    }
74
75                    // description
76
77                    let description = document
78                        .select(&scraper::Selector::parse("meta[name='description']")?)
79                        .next()
80                        .map(|element| element.value().attr("content").unwrap().to_string());
81
82                    if let Some(description) = description {
83                        props.description = Some(description);
84                    }
85
86                    let og_description_selector =
87                        scraper::Selector::parse("meta[property='og:description']")?;
88
89                    if let Some(element) = document.select(&og_description_selector).next() {
90                        if let Some(content) = element.value().attr("content") {
91                            props.description = Some(content.to_string());
92                        }
93                    }
94
95                    let og_image_selector = scraper::Selector::parse("meta[property='og:image']")?;
96
97                    if let Some(element) = document.select(&og_image_selector).next() {
98                        if let Some(content) = element.value().attr("content") {
99                            props.image = Some(content.to_string());
100                        }
101                    }
102
103                    let block = crate::block::Block::ElmBookmark(crate::block::ElmBookmark {
104                        props,
105                        id: block.id,
106                    });
107
108                    blocks.push(block);
109                }
110                notionrs::object::block::Block::Breadcrumb { breadcrumb: _ } => {}
111                notionrs::object::block::Block::BulletedListItem { bulleted_list_item } => {
112                    let mut list_item_children: Vec<crate::block::Block> = Vec::new();
113
114                    let rich_text_block =
115                        Client::convert_rich_text(bulleted_list_item.rich_text).await?;
116                    list_item_children.extend(rich_text_block);
117
118                    if block.has_children {
119                        let list_item_children_blocks = self.convert_block(&block.id).await?;
120                        list_item_children.extend(list_item_children_blocks);
121                    }
122
123                    let list_item_block =
124                        crate::block::Block::ElmListItem(crate::block::ElmListItem {
125                            children: list_item_children,
126                            id: block.id.clone(),
127                        });
128
129                    let last_item = blocks.last_mut();
130
131                    match last_item {
132                        Some(crate::block::Block::ElmBulletedList(elm_bulleted_list)) => {
133                            elm_bulleted_list.children.push(list_item_block);
134                        }
135                        Some(_) | None => {
136                            let new_ul = vec![list_item_block];
137                            blocks.push(crate::block::Block::ElmBulletedList(
138                                crate::block::ElmBulletedList {
139                                    children: new_ul,
140                                    id: block.id,
141                                },
142                            ));
143                        }
144                    };
145                }
146                notionrs::object::block::Block::Callout { callout } => {
147                    let mut children: Vec<crate::block::Block> = Vec::new();
148                    let text_blocks = Client::convert_rich_text(callout.rich_text).await?;
149                    let children_blocks = self.convert_block(&block.id).await?;
150                    children.extend(text_blocks);
151                    children.extend(children_blocks);
152
153                    let r#type = match callout.color {
154                        notionrs::object::color::Color::Default
155                        | notionrs::object::color::Color::DefaultBackground
156                        | notionrs::object::color::Color::Blue
157                        | notionrs::object::color::Color::BlueBackground
158                        | notionrs::object::color::Color::Gray
159                        | notionrs::object::color::Color::GrayBackground => "note",
160                        notionrs::object::color::Color::Green
161                        | notionrs::object::color::Color::GreenBackground => "tip",
162                        notionrs::object::color::Color::Purple
163                        | notionrs::object::color::Color::PurpleBackground => "important",
164                        notionrs::object::color::Color::Yellow
165                        | notionrs::object::color::Color::YellowBackground
166                        | notionrs::object::color::Color::Orange
167                        | notionrs::object::color::Color::OrangeBackground
168                        | notionrs::object::color::Color::Brown
169                        | notionrs::object::color::Color::BrownBackground => "warning",
170                        notionrs::object::color::Color::Red
171                        | notionrs::object::color::Color::RedBackground
172                        | notionrs::object::color::Color::Pink
173                        | notionrs::object::color::Color::PinkBackground => "caution",
174                    }
175                    .to_string();
176
177                    let props = crate::block::ElmCalloutProps { r#type };
178
179                    let callout_block = crate::block::Block::ElmCallout(crate::block::ElmCallout {
180                        props,
181                        children,
182                        id: block.id,
183                    });
184                    blocks.push(callout_block);
185                }
186                notionrs::object::block::Block::ChildDatabase { child_database: _ } => {}
187                notionrs::object::block::Block::ChildPage { child_page: _ } => {}
188                notionrs::object::block::Block::Code { code } => {
189                    let language = code.language.to_string();
190
191                    let caption = code
192                        .caption
193                        .iter()
194                        .map(|t| t.to_string())
195                        .collect::<String>();
196
197                    let props = crate::block::ElmCodeBlockProps {
198                        code: code
199                            .rich_text
200                            .iter()
201                            .map(|t| t.to_string())
202                            .collect::<String>(),
203                        language: language.to_string(),
204                        caption: if caption.is_empty() {
205                            language.to_string()
206                        } else {
207                            caption
208                        },
209                        margin: "2rem".to_string(),
210                    };
211
212                    blocks.push(crate::block::Block::ElmCodeBlock(
213                        crate::block::ElmCodeBlock {
214                            props,
215                            id: block.id,
216                        },
217                    ));
218                }
219                notionrs::object::block::Block::ColumnList { column_list: _ } => {
220                    let children = self.convert_block(&block.id).await?;
221                    let block = crate::block::Block::ElmColumnList(crate::block::ElmColumnList {
222                        children,
223                        id: block.id,
224                    });
225                    blocks.push(block);
226                }
227                notionrs::object::block::Block::Column { column: _ } => {
228                    let children = self.convert_block(&block.id).await?;
229                    let block = crate::block::Block::ElmColumn(crate::block::ElmColumn {
230                        children,
231                        id: block.id,
232                    });
233                    blocks.push(block);
234                }
235                notionrs::object::block::Block::Divider { divider: _ } => {
236                    blocks.push(crate::block::Block::ElmDivider(crate::block::ElmDivider {
237                        props: crate::block::ElmDividerProps {
238                            margin: "2rem".to_string(),
239                        },
240                        id: block.id,
241                    }));
242                }
243                notionrs::object::block::Block::Embed { embed: _ } => {}
244                notionrs::object::block::Block::Equation { equation } => {
245                    let props = crate::block::ElmKatexProps {
246                        expression: equation.expression,
247                        block: true,
248                    };
249
250                    let block = crate::block::Block::ElmKatex(crate::block::ElmKatex { props });
251
252                    blocks.push(block);
253                }
254                notionrs::object::block::Block::File { file } => {
255                    let (name, src) = match file {
256                        notionrs::object::file::File::External(f) => (f.name, f.external.url),
257                        notionrs::object::file::File::Uploaded(f) => (f.name, f.file.url),
258                    };
259
260                    let props = crate::block::ElmFileProps {
261                        name,
262                        src,
263                        margin: "2rem".to_string(),
264                    };
265
266                    let block = crate::block::Block::ElmFile(crate::block::ElmFile {
267                        props,
268                        id: block.id,
269                    });
270
271                    blocks.push(block);
272                }
273                notionrs::object::block::Block::Heading1 { heading_1 } => {
274                    let heading = heading_1;
275                    if heading.is_toggleable {
276                        let summary = heading
277                            .rich_text
278                            .iter()
279                            .map(|t| t.to_string())
280                            .collect::<String>();
281
282                        let props = crate::block::ElmToggleProps {
283                            summary,
284                            margin: "2rem".to_string(),
285                        };
286
287                        let children = self.convert_block(&block.id).await?;
288
289                        let block = crate::block::Block::ElmToggle(crate::block::ElmToggle {
290                            props,
291                            children,
292                            id: block.id,
293                        });
294
295                        blocks.push(block);
296                    } else {
297                        let props = crate::block::ElmHeadingProps {
298                            text: heading
299                                .rich_text
300                                .iter()
301                                .map(|t| t.to_string())
302                                .collect::<String>(),
303                        };
304
305                        let block = crate::block::Block::ElmHeading1(crate::block::ElmHeading1 {
306                            props,
307                            id: block.id,
308                        });
309
310                        blocks.push(block);
311                    }
312                }
313                notionrs::object::block::Block::Heading2 { heading_2 } => {
314                    let heading = heading_2;
315                    if heading.is_toggleable {
316                        let summary = heading
317                            .rich_text
318                            .iter()
319                            .map(|t| t.to_string())
320                            .collect::<String>();
321
322                        let props = crate::block::ElmToggleProps {
323                            summary,
324                            margin: "2rem".to_string(),
325                        };
326
327                        let children = self.convert_block(&block.id).await?;
328
329                        let block = crate::block::Block::ElmToggle(crate::block::ElmToggle {
330                            props,
331                            children,
332                            id: block.id,
333                        });
334
335                        blocks.push(block);
336                    } else {
337                        let props = crate::block::ElmHeadingProps {
338                            text: heading
339                                .rich_text
340                                .iter()
341                                .map(|t| t.to_string())
342                                .collect::<String>(),
343                        };
344
345                        let block = crate::block::Block::ElmHeading2(crate::block::ElmHeading2 {
346                            props,
347                            id: block.id,
348                        });
349
350                        blocks.push(block);
351                    }
352                }
353                notionrs::object::block::Block::Heading3 { heading_3 } => {
354                    let heading = heading_3;
355                    if heading.is_toggleable {
356                        let summary = heading
357                            .rich_text
358                            .iter()
359                            .map(|t| t.to_string())
360                            .collect::<String>();
361
362                        let props = crate::block::ElmToggleProps {
363                            summary,
364                            margin: "2rem".to_string(),
365                        };
366
367                        let children = self.convert_block(&block.id).await?;
368
369                        let block = crate::block::Block::ElmToggle(crate::block::ElmToggle {
370                            props,
371                            children,
372                            id: block.id,
373                        });
374
375                        blocks.push(block);
376                    } else {
377                        let props = crate::block::ElmHeadingProps {
378                            text: heading
379                                .rich_text
380                                .iter()
381                                .map(|t| t.to_string())
382                                .collect::<String>(),
383                        };
384
385                        let block = crate::block::Block::ElmHeading3(crate::block::ElmHeading3 {
386                            props,
387                            id: block.id,
388                        });
389
390                        blocks.push(block);
391                    }
392                }
393                notionrs::object::block::Block::Image { image } => {
394                    let (src, alt) = match image {
395                        notionrs::object::file::File::External(f) => (
396                            f.external.url,
397                            f.caption.map(|rich_text| {
398                                rich_text.iter().map(|t| t.to_string()).collect::<String>()
399                            }),
400                        ),
401                        notionrs::object::file::File::Uploaded(f) => (
402                            f.file.url,
403                            f.caption.map(|rich_text| {
404                                rich_text.iter().map(|t| t.to_string()).collect::<String>()
405                            }),
406                        ),
407                    };
408
409                    let props = crate::block::ElmBlockImageProps {
410                        src: src.clone(),
411                        alt,
412                        enable_modal: true,
413                        margin: "2rem".to_string(),
414                    };
415
416                    let image_block = crate::block::Block::ElmBlockImage(crate::block::ElmBlockImage {
417                        props,
418                        id: block.id.clone(),
419                    });
420
421                    blocks.push(image_block);
422                }
423                notionrs::object::block::Block::LinkPreview { link_preview: _ } => {}
424                notionrs::object::block::Block::NumberedListItem { numbered_list_item } => {
425                    let mut list_item_children: Vec<crate::block::Block> = Vec::new();
426
427                    let rich_text_block =
428                        Client::convert_rich_text(numbered_list_item.rich_text).await?;
429                    list_item_children.extend(rich_text_block);
430
431                    if block.has_children {
432                        let list_item_children_blocks = self.convert_block(&block.id).await?;
433                        list_item_children.extend(list_item_children_blocks);
434                    }
435
436                    let list_item_block =
437                        crate::block::Block::ElmListItem(crate::block::ElmListItem {
438                            children: list_item_children,
439                            id: block.id.clone(),
440                        });
441
442                    let last_item = blocks.last_mut();
443
444                    match last_item {
445                        Some(crate::block::Block::ElmNumberedList(elm_numbered_list)) => {
446                            elm_numbered_list.children.push(list_item_block);
447                        }
448                        Some(_) | None => {
449                            let new_ol = vec![list_item_block];
450                            blocks.push(crate::block::Block::ElmNumberedList(
451                                crate::block::ElmNumberedList {
452                                    children: new_ol,
453                                    id: block.id,
454                                },
455                            ));
456                        }
457                    };
458                }
459                notionrs::object::block::Block::Paragraph { paragraph } => {
460                    let block = crate::block::Block::ElmParagraph(crate::block::ElmParagraph {
461                        children: Client::convert_rich_text(paragraph.rich_text).await?,
462                        id: block.id,
463                    });
464
465                    blocks.push(block);
466                }
467                notionrs::object::block::Block::Pdf { pdf: _ } => {}
468                notionrs::object::block::Block::Quote { quote } => {
469                    let mut children = Vec::new();
470
471                    let inline_text_block = Client::convert_rich_text(quote.rich_text).await?;
472
473                    let children_block = self.convert_block(&block.id).await?;
474
475                    children.extend(inline_text_block);
476                    children.extend(children_block);
477
478                    let block = crate::block::Block::ElmBlockQuote(crate::block::ElmBlockQuote {
479                        children,
480                        id: block.id,
481                    });
482
483                    blocks.push(block);
484                }
485                notionrs::object::block::Block::SyncedBlock { synced_block: _ } => {
486                    let children = self.convert_block(&block.id).await?;
487                    blocks.extend(children);
488                }
489                notionrs::object::block::Block::TableOfContents {
490                    table_of_contents: _,
491                } => {}
492                notionrs::object::block::Block::Table { table: _ } => {
493                    let rows = self
494                        .notionrs_client
495                        .get_block_children_all()
496                        .block_id(block.id)
497                        .send()
498                        .await?;
499
500                    if let Some((header_row, body_rows)) = rows.split_first() {
501                        let table_header_block =
502                            if let notionrs::object::block::Block::TableRow { table_row } =
503                                &header_row.block
504                            {
505                                let cells_blocks = table_row
506                                    .cells
507                                    .iter()
508                                    .map(|cell| {
509                                        crate::block::Block::ElmTableCell(
510                                            crate::block::ElmTableCell {
511                                                props: crate::block::ElmTableCellProps {
512                                                    has_header: true,
513                                                    text: cell
514                                                        .iter()
515                                                        .map(|t| t.to_string())
516                                                        .collect::<String>(),
517                                                },
518                                            },
519                                        )
520                                    })
521                                    .collect::<Vec<crate::block::Block>>();
522
523                                let table_row_block =
524                                    crate::block::Block::ElmTableRow(crate::block::ElmTableRow {
525                                        children: cells_blocks,
526                                    });
527
528                                crate::block::Block::ElmTableHeader(crate::block::ElmTableHeader {
529                                    children: vec![table_row_block],
530                                })
531                            } else {
532                                crate::block::Block::ElmTableHeader(crate::block::ElmTableHeader {
533                                    children: vec![],
534                                })
535                            };
536
537                        let table_body_row_blocks =
538                            body_rows.iter().filter_map(|row| match &row.block {
539                                notionrs::object::block::Block::TableRow { table_row } => {
540                                    let cells_blocks = table_row
541                                        .cells
542                                        .iter()
543                                        .map(|cell| {
544                                            crate::block::Block::ElmTableCell(
545                                                crate::block::ElmTableCell {
546                                                    props: crate::block::ElmTableCellProps {
547                                                        has_header: false,
548                                                        text: cell
549                                                            .iter()
550                                                            .map(|t| t.to_string())
551                                                            .collect::<String>(),
552                                                    },
553                                                },
554                                            )
555                                        })
556                                        .collect::<Vec<crate::block::Block>>();
557
558                                    Some(crate::block::Block::ElmTableRow(
559                                        crate::block::ElmTableRow {
560                                            children: cells_blocks,
561                                        },
562                                    ))
563                                }
564                                _ => None,
565                            });
566
567                        let table_body_block =
568                            crate::block::Block::ElmTableBody(crate::block::ElmTableBody {
569                                children: table_body_row_blocks.collect(),
570                            });
571
572                        let table_block = crate::block::Block::ElmTable(crate::block::ElmTable {
573                            children: vec![table_header_block, table_body_block],
574                        });
575
576                        blocks.push(table_block);
577                    }
578                }
579                notionrs::object::block::Block::TableRow { table_row: _ } => {}
580                notionrs::object::block::Block::Template { template: _ } => {}
581                notionrs::object::block::Block::ToDo { to_do } => {
582                    let props = crate::block::ElmCheckboxProps {
583                        label: to_do
584                            .rich_text
585                            .iter()
586                            .map(|t| t.to_string())
587                            .collect::<String>(),
588                    };
589
590                    let block = crate::block::Block::ElmCheckbox(crate::block::ElmCheckbox {
591                        props,
592                        id: block.id,
593                    });
594
595                    blocks.push(block);
596                }
597                notionrs::object::block::Block::Toggle { toggle } => {
598                    let summary = toggle
599                        .rich_text
600                        .iter()
601                        .map(|t| t.to_string())
602                        .collect::<String>();
603
604                    let props = crate::block::ElmToggleProps {
605                        summary,
606                        margin: "2rem".to_string(),
607                    };
608
609                    let children = self.convert_block(&block.id).await?;
610
611                    let block = crate::block::Block::ElmToggle(crate::block::ElmToggle {
612                        props,
613                        children,
614                        id: block.id,
615                    });
616
617                    blocks.push(block);
618                }
619                notionrs::object::block::Block::Video { video: _ } => {}
620                notionrs::object::block::Block::Unknown(_) => {}
621
622                #[allow(unreachable_patterns)]
623                _ => {}
624            };
625        }
626
627        Ok(blocks)
628    }
629
630    pub async fn convert_rich_text(
631        rich_text: Vec<notionrs::object::rich_text::RichText>,
632    ) -> Result<Vec<crate::block::Block>, crate::error::Error> {
633        let mut blocks: Vec<crate::block::Block> = Vec::new();
634
635        for r in rich_text {
636            let block: Result<crate::block::Block, crate::error::Error> = match r {
637                notionrs::object::rich_text::RichText::Mention { mention, .. } => match mention {
638                    notionrs::object::rich_text::mention::Mention::LinkMention { link_mention } => {
639                        let href = link_mention.href.as_str();
640
641                        let mut props = crate::block::ElmInlineLinkProps {
642                            href: link_mention.href.to_string(),
643                            text: link_mention.to_string(),
644                            favicon: None,
645                        };
646
647                        let response = reqwest::Client::new()
648                            .get(href)
649                            .header("user-agent", "Rust - reqwest")
650                            .send()
651                            .await?
652                            .text()
653                            .await?;
654
655                        let document = scraper::Html::parse_document(&response);
656
657                        let title = document
658                            .select(&scraper::Selector::parse("title")?)
659                            .next()
660                            .map(|element| element.text().collect::<String>());
661
662                        let og_title_selector =
663                            scraper::Selector::parse("meta[property='og:title']")?;
664
665                        if let Some(element) = document.select(&og_title_selector).next() {
666                            if let Some(content) = element.value().attr("content") {
667                                props.text = content.to_string();
668                            }
669                        }
670
671                        if let Some(title) = title {
672                            props.text = title;
673                        }
674
675                        let favicon_selector = scraper::Selector::parse(r#"link[rel="icon"]"#)?;
676
677                        props.favicon = document
678                            .select(&favicon_selector)
679                            .next()
680                            .and_then(|f| f.value().attr("href").map(String::from))
681                            .and_then(|favicon_href| {
682                                if favicon_href.starts_with("http://")
683                                    || favicon_href.starts_with("https://")
684                                {
685                                    Some(favicon_href)
686                                } else {
687                                    url::Url::parse(&href)
688                                        .and_then(|s| s.join(&favicon_href))
689                                        .map(|s| s.to_string())
690                                        .ok()
691                                }
692                            });
693
694                        Ok(crate::block::Block::ElmInlineLink(
695                            crate::block::ElmInlineLink { props },
696                        ))
697                    }
698                    notionrs::object::rich_text::mention::Mention::User { user: _ } => continue,
699                    notionrs::object::rich_text::mention::Mention::Date { date: _ } => continue,
700                    notionrs::object::rich_text::mention::Mention::LinkPreview {
701                        link_preview: _,
702                    } => {
703                        continue;
704                    }
705                    notionrs::object::rich_text::mention::Mention::TemplateMention {
706                        template_mention: _,
707                    } => continue,
708                    notionrs::object::rich_text::mention::Mention::Page { page: _ } => continue,
709                    notionrs::object::rich_text::mention::Mention::Database { database: _ } => {
710                        continue;
711                    }
712                    notionrs::object::rich_text::mention::Mention::CustomEmoji { custom_emoji } => {
713                        let props = crate::block::ElmInlineIconProps {
714                            src: custom_emoji.url.to_string(),
715                            alt: custom_emoji.name.to_string(),
716                        };
717
718                        let block =
719                            crate::block::Block::ElmInlineIcon(crate::block::ElmInlineIcon {
720                                id: custom_emoji.id.to_string(),
721                                props,
722                            });
723
724                        Ok(block)
725                    }
726                },
727                notionrs::object::rich_text::RichText::Text {
728                    text: _text,
729                    annotations,
730                    plain_text,
731                    href: _href,
732                } => {
733                    let props = crate::block::ElmInlineTextProps {
734                        text: plain_text.to_string(),
735                        bold: annotations.bold,
736                        italic: annotations.italic,
737                        underline: annotations.underline,
738                        strikethrough: annotations.strikethrough,
739                        code: annotations.code,
740                        color: match annotations.color {
741                            notionrs::object::color::Color::Default => None,
742                            notionrs::object::color::Color::Blue => Some(String::from("#6987b8")),
743                            notionrs::object::color::Color::Brown => Some(String::from("#8b4c3f")),
744                            notionrs::object::color::Color::Gray => Some(String::from("#868e9c")),
745                            notionrs::object::color::Color::Green => Some(String::from("#59b57c")),
746                            notionrs::object::color::Color::Orange => Some(String::from("#bf7e71")),
747                            notionrs::object::color::Color::Pink => Some(String::from("#c9699e")),
748                            notionrs::object::color::Color::Purple => Some(String::from("#9771bd")),
749                            notionrs::object::color::Color::Red => Some(String::from("#b36472")),
750                            notionrs::object::color::Color::Yellow => Some(String::from("#b8a36e")),
751                            _ => None,
752                        },
753                    };
754
755                    Ok(crate::block::Block::ElmInlineText(
756                        crate::block::ElmInlineText { props },
757                    ))
758                }
759                notionrs::object::rich_text::RichText::Equation {
760                    equation,
761                    annotations: _annotations,
762                    plain_text: _plain_text,
763                    href: _href,
764                } => {
765                    let props = crate::block::ElmKatexProps {
766                        block: false,
767                        expression: equation.expression,
768                    };
769
770                    let block = crate::block::Block::ElmKatex(crate::block::ElmKatex { props });
771
772                    Ok(block)
773                }
774            };
775
776            blocks.push(block.unwrap());
777        }
778
779        Ok(blocks)
780    }
781}