vimwiki_wasm/
elements.rs

1use crate::utils;
2use std::{borrow::Cow, collections::HashMap, convert::TryFrom};
3use vimwiki::{
4    self as v,
5    vendor::{chrono, uriparse},
6    ToHtmlString,
7};
8use wasm_bindgen::prelude::*;
9
10/// Represents a wrapper around a vimwiki page
11#[wasm_bindgen]
12pub struct Page(v::Page<'static>);
13
14#[wasm_bindgen]
15impl Page {
16    /// Returns top-level element at the given index if it exists
17    pub fn element_at(&self, idx: usize) -> Option<BlockElement> {
18        self.0.elements.get(idx).map(|x| {
19            BlockElement(v::Located::new(
20                x.to_borrowed().into_owned(),
21                x.region(),
22            ))
23        })
24    }
25
26    /// Represents total number of top-level elements within the page
27    #[wasm_bindgen(getter)]
28    pub fn element_cnt(&self) -> usize {
29        self.0.elements.len()
30    }
31}
32
33/// Represents a wrapper around a vimwiki element
34#[wasm_bindgen]
35pub struct Element(v::Located<v::Element<'static>>);
36
37#[wasm_bindgen]
38impl Element {
39    /// Returns true if element is block element
40    pub fn is_block(&self) -> bool {
41        matches!(self.0.as_inner(), v::Element::Block(_))
42    }
43
44    /// Casts to block element if it is one
45    pub fn into_block(self) -> Option<BlockElement> {
46        let region = self.0.region();
47        match self.0.into_inner() {
48            v::Element::Block(x) => {
49                Some(BlockElement(v::Located::new(x, region)))
50            }
51            _ => None,
52        }
53    }
54
55    /// Returns true if element is inline element
56    pub fn is_inline(&self) -> bool {
57        matches!(self.0.as_inner(), v::Element::Inline(_))
58    }
59
60    /// Casts to inline element if it is one
61    pub fn into_inline(self) -> Option<InlineElement> {
62        let region = self.0.region();
63        match self.0.into_inner() {
64            v::Element::Inline(x) => {
65                Some(InlineElement(v::Located::new(x, region)))
66            }
67            _ => None,
68        }
69    }
70
71    /// Returns true if element is inline block element
72    pub fn is_inline_block(&self) -> bool {
73        matches!(self.0.as_inner(), v::Element::InlineBlock(_))
74    }
75
76    /// Casts to inline block element if it is one
77    pub fn into_inline_block(self) -> Option<InlineBlockElement> {
78        let region = self.0.region();
79        match self.0.into_inner() {
80            v::Element::InlineBlock(x) => {
81                Some(InlineBlockElement(v::Located::new(x, region)))
82            }
83            _ => None,
84        }
85    }
86}
87
88/// Represents a wrapper around a vimwiki block element
89#[wasm_bindgen]
90pub struct BlockElement(v::Located<v::BlockElement<'static>>);
91
92#[wasm_bindgen]
93impl BlockElement {
94    /// Returns true if element is blockquote
95    pub fn is_blockquote(&self) -> bool {
96        matches!(self.0.as_inner(), v::BlockElement::Blockquote(_))
97    }
98
99    /// Casts to blockquote if it is one
100    pub fn into_blockquote(self) -> Option<Blockquote> {
101        let region = self.0.region();
102        match self.0.into_inner() {
103            v::BlockElement::Blockquote(x) => {
104                Some(Blockquote(v::Located::new(x, region)))
105            }
106            _ => None,
107        }
108    }
109
110    /// Returns true if element is code block
111    pub fn is_code_block(&self) -> bool {
112        matches!(self.0.as_inner(), v::BlockElement::CodeBlock(_))
113    }
114
115    /// Casts to code block if it is one
116    pub fn into_code_block(self) -> Option<CodeBlock> {
117        let region = self.0.region();
118        match self.0.into_inner() {
119            v::BlockElement::CodeBlock(x) => {
120                Some(CodeBlock(v::Located::new(x, region)))
121            }
122            _ => None,
123        }
124    }
125
126    /// Returns true if element is math block
127    pub fn is_math(&self) -> bool {
128        matches!(self.0.as_inner(), v::BlockElement::Math(_))
129    }
130
131    /// Casts to math block if it is one
132    pub fn into_math_block(self) -> Option<MathBlock> {
133        let region = self.0.region();
134        match self.0.into_inner() {
135            v::BlockElement::Math(x) => {
136                Some(MathBlock(v::Located::new(x, region)))
137            }
138            _ => None,
139        }
140    }
141
142    /// Returns true if element is definition list
143    pub fn is_definition_list(&self) -> bool {
144        matches!(self.0.as_inner(), v::BlockElement::DefinitionList(_))
145    }
146
147    /// Casts to definition list if it is one
148    pub fn into_definition_list(self) -> Option<DefinitionList> {
149        let region = self.0.region();
150        match self.0.into_inner() {
151            v::BlockElement::DefinitionList(x) => {
152                Some(DefinitionList(v::Located::new(x, region)))
153            }
154            _ => None,
155        }
156    }
157
158    /// Returns true if element is divider
159    pub fn is_divider(&self) -> bool {
160        matches!(self.0.as_inner(), v::BlockElement::Divider(_))
161    }
162
163    /// Casts to divider if it is one
164    pub fn into_divider(self) -> Option<Divider> {
165        let region = self.0.region();
166        match self.0.into_inner() {
167            v::BlockElement::Divider(x) => {
168                Some(Divider(v::Located::new(x, region)))
169            }
170            _ => None,
171        }
172    }
173
174    /// Returns true if element is header
175    pub fn is_header(&self) -> bool {
176        matches!(self.0.as_inner(), v::BlockElement::Header(_))
177    }
178
179    /// Casts to definition list if it is one
180    pub fn into_header(self) -> Option<Header> {
181        let region = self.0.region();
182        match self.0.into_inner() {
183            v::BlockElement::Header(x) => {
184                Some(Header(v::Located::new(x, region)))
185            }
186            _ => None,
187        }
188    }
189
190    /// Returns true if element is list
191    pub fn is_list(&self) -> bool {
192        matches!(self.0.as_inner(), v::BlockElement::List(_))
193    }
194
195    /// Casts to list if it is one
196    pub fn into_list(self) -> Option<List> {
197        let region = self.0.region();
198        match self.0.into_inner() {
199            v::BlockElement::List(x) => Some(List(v::Located::new(x, region))),
200            _ => None,
201        }
202    }
203
204    /// Returns true if element is paragraph
205    pub fn is_paragraph(&self) -> bool {
206        matches!(self.0.as_inner(), v::BlockElement::Paragraph(_))
207    }
208
209    /// Casts to paragraph if it is one
210    pub fn into_paragraph(self) -> Option<Paragraph> {
211        let region = self.0.region();
212        match self.0.into_inner() {
213            v::BlockElement::Paragraph(x) => {
214                Some(Paragraph(v::Located::new(x, region)))
215            }
216            _ => None,
217        }
218    }
219
220    /// Returns true if element is placeholder
221    pub fn is_placeholder(&self) -> bool {
222        matches!(self.0.as_inner(), v::BlockElement::Placeholder(_))
223    }
224
225    /// Casts to placeholder if it is one
226    pub fn into_placeholder(self) -> Option<Placeholder> {
227        let region = self.0.region();
228        match self.0.into_inner() {
229            v::BlockElement::Placeholder(x) => {
230                Some(Placeholder(v::Located::new(x, region)))
231            }
232            _ => None,
233        }
234    }
235
236    /// Returns true if element is table
237    pub fn is_table(&self) -> bool {
238        matches!(self.0.as_inner(), v::BlockElement::Table(_))
239    }
240
241    /// Casts to table if it is one
242    pub fn into_table(self) -> Option<Table> {
243        let region = self.0.region();
244        match self.0.into_inner() {
245            v::BlockElement::Table(x) => {
246                Some(Table(v::Located::new(x, region)))
247            }
248            _ => None,
249        }
250    }
251}
252
253/// Represents a wrapper around a vimwiki inline block element
254#[wasm_bindgen]
255pub struct InlineBlockElement(v::Located<v::InlineBlockElement<'static>>);
256
257#[wasm_bindgen]
258impl InlineBlockElement {
259    /// Returns true if element is list item
260    pub fn is_list_item(&self) -> bool {
261        matches!(self.0.as_inner(), v::InlineBlockElement::ListItem(_))
262    }
263
264    /// Casts to list item if it is one
265    pub fn into_list_item(self) -> Option<ListItem> {
266        let region = self.0.region();
267        match self.0.into_inner() {
268            v::InlineBlockElement::ListItem(x) => {
269                Some(ListItem(v::Located::new(x, region)))
270            }
271            _ => None,
272        }
273    }
274
275    /// Returns true if element is term
276    pub fn is_term(&self) -> bool {
277        matches!(self.0.as_inner(), v::InlineBlockElement::Term(_))
278    }
279
280    /// Casts to term if it is one
281    pub fn into_term(self) -> Option<InlineElementContainer> {
282        match self.0.into_inner() {
283            v::InlineBlockElement::Term(x) => {
284                Some(InlineElementContainer(x.into()))
285            }
286            _ => None,
287        }
288    }
289
290    /// Returns true if element is definition
291    pub fn is_definition(&self) -> bool {
292        matches!(self.0.as_inner(), v::InlineBlockElement::Definition(_))
293    }
294
295    /// Casts to definition if it is one
296    pub fn into_definition(self) -> Option<InlineElementContainer> {
297        match self.0.into_inner() {
298            v::InlineBlockElement::Definition(x) => {
299                Some(InlineElementContainer(x.into()))
300            }
301            _ => None,
302        }
303    }
304}
305
306/// Represents a wrapper around a vimwiki inline element
307#[wasm_bindgen]
308pub struct InlineElement(v::Located<v::InlineElement<'static>>);
309
310#[wasm_bindgen]
311impl InlineElement {
312    /// Returns true if element is text
313    pub fn is_text(&self) -> bool {
314        matches!(self.0.as_inner(), v::InlineElement::Text(_))
315    }
316
317    /// Casts to text if it is one
318    pub fn into_text(self) -> Option<Text> {
319        let region = self.0.region();
320        match self.0.into_inner() {
321            v::InlineElement::Text(x) => Some(Text(v::Located::new(x, region))),
322            _ => None,
323        }
324    }
325
326    /// Returns true if element is decorated text
327    pub fn is_decorated_text(&self) -> bool {
328        matches!(self.0.as_inner(), v::InlineElement::DecoratedText(_))
329    }
330
331    /// Casts to decorated text if it is one
332    pub fn into_decorated_text(self) -> Option<DecoratedText> {
333        let region = self.0.region();
334        match self.0.into_inner() {
335            v::InlineElement::DecoratedText(x) => {
336                Some(DecoratedText(v::Located::new(x, region)))
337            }
338            _ => None,
339        }
340    }
341
342    /// Returns true if element is keyword
343    pub fn is_keyword(&self) -> bool {
344        matches!(self.0.as_inner(), v::InlineElement::Keyword(_))
345    }
346
347    /// Casts to keyword if it is one
348    pub fn into_keyword(self) -> Option<Keyword> {
349        match self.0.into_inner() {
350            v::InlineElement::Keyword(x) => Some(Keyword::from(x)),
351            _ => None,
352        }
353    }
354
355    /// Returns true if element is link
356    pub fn is_link(&self) -> bool {
357        matches!(self.0.as_inner(), v::InlineElement::Link(_))
358    }
359
360    /// Casts to link if it is one
361    pub fn into_link(self) -> Option<Link> {
362        let region = self.0.region();
363        match self.0.into_inner() {
364            v::InlineElement::Link(x) => Some(Link(v::Located::new(x, region))),
365            _ => None,
366        }
367    }
368
369    /// Returns true if element is tag set
370    pub fn is_tags(&self) -> bool {
371        matches!(self.0.as_inner(), v::InlineElement::Tags(_))
372    }
373
374    /// Casts to tag set if it is one
375    pub fn into_tags(self) -> Option<Tags> {
376        let region = self.0.region();
377        match self.0.into_inner() {
378            v::InlineElement::Tags(x) => Some(Tags(v::Located::new(x, region))),
379            _ => None,
380        }
381    }
382
383    /// Returns true if element is inline code
384    pub fn is_inline_code(&self) -> bool {
385        matches!(self.0.as_inner(), v::InlineElement::Code(_))
386    }
387
388    /// Casts to inline code if it is one
389    pub fn into_inline_code(self) -> Option<CodeInline> {
390        let region = self.0.region();
391        match self.0.into_inner() {
392            v::InlineElement::Code(x) => {
393                Some(CodeInline(v::Located::new(x, region)))
394            }
395            _ => None,
396        }
397    }
398
399    /// Returns true if element is inline math
400    pub fn is_inline_math(&self) -> bool {
401        matches!(self.0.as_inner(), v::InlineElement::Math(_))
402    }
403
404    /// Casts to inline math if it is one
405    pub fn into_inline_math(self) -> Option<MathInline> {
406        let region = self.0.region();
407        match self.0.into_inner() {
408            v::InlineElement::Math(x) => {
409                Some(MathInline(v::Located::new(x, region)))
410            }
411            _ => None,
412        }
413    }
414
415    /// Returns true if element is comment
416    pub fn is_comment(&self) -> bool {
417        matches!(self.0.as_inner(), v::InlineElement::Comment(_))
418    }
419
420    /// Casts to comment if it is one
421    pub fn into_comment(self) -> Option<Comment> {
422        let region = self.0.region();
423        match self.0.into_inner() {
424            v::InlineElement::Comment(x) => {
425                Some(Comment(v::Located::new(x, region)))
426            }
427            _ => None,
428        }
429    }
430}
431
432/// Represents a wrapper around a vimwiki inline element container
433#[wasm_bindgen]
434pub struct InlineElementContainer(v::InlineElementContainer<'static>);
435
436#[wasm_bindgen]
437impl InlineElementContainer {
438    /// Returns element at the given index if it exists
439    pub fn element_at(&self, idx: usize) -> Option<InlineElement> {
440        self.0.get(idx).map(|x| {
441            InlineElement(v::Located::new(
442                x.to_borrowed().into_owned(),
443                x.region(),
444            ))
445        })
446    }
447
448    /// Represents total number of elements within container
449    #[wasm_bindgen(getter)]
450    pub fn element_cnt(&self) -> usize {
451        self.0.len()
452    }
453
454    /// Converts container to a JavaScript string
455    pub fn to_str(&self) -> String {
456        self.0.to_string()
457    }
458}
459
460/// Represents a wrapper around a vimwiki blockquote
461#[wasm_bindgen]
462pub struct Blockquote(v::Located<v::Blockquote<'static>>);
463
464#[wasm_bindgen]
465impl Blockquote {
466    /// Returns line at the given index if it exists
467    pub fn line_at(&self, idx: usize) -> Option<String> {
468        self.0.lines.get(idx).map(ToString::to_string)
469    }
470
471    /// Represents total number of lines within the blockquote
472    #[wasm_bindgen(getter)]
473    pub fn line_cnt(&self) -> usize {
474        self.0.lines.len()
475    }
476}
477
478/// Represents a wrapper around a vimwiki code block
479#[wasm_bindgen]
480pub struct CodeBlock(v::Located<v::CodeBlock<'static>>);
481
482#[wasm_bindgen]
483impl CodeBlock {
484    /// Represents the language associated with the code block
485    #[wasm_bindgen(getter)]
486    pub fn language(&self) -> Option<String> {
487        self.0.language.as_ref().map(ToString::to_string)
488    }
489
490    /// Returns object containing metadata of code block
491    #[wasm_bindgen(getter)]
492    pub fn metadata(&self) -> Option<js_sys::Object> {
493        let arr = js_sys::Array::new();
494
495        for (key, value) in self.0.metadata.iter() {
496            let tuple = js_sys::Array::new();
497            tuple.push(&JsValue::from_str(key));
498            tuple.push(&JsValue::from_str(value));
499
500            arr.push(&tuple);
501        }
502
503        js_sys::Object::from_entries(&arr).ok()
504    }
505
506    /// Returns line at the given index if it exists
507    pub fn line_at(&self, idx: usize) -> Option<String> {
508        self.0.lines.get(idx).map(ToString::to_string)
509    }
510
511    /// Represents total number of lines within the block
512    #[wasm_bindgen(getter)]
513    pub fn line_cnt(&self) -> usize {
514        self.0.lines.len()
515    }
516}
517
518/// Represents a wrapper around a vimwiki definition list
519#[wasm_bindgen]
520pub struct DefinitionList(v::Located<v::DefinitionList<'static>>);
521
522#[wasm_bindgen]
523impl DefinitionList {
524    /// Represents the terms stored in the list
525    #[wasm_bindgen(getter)]
526    pub fn terms(&self) -> Vec<JsValue> {
527        self.0
528            .terms()
529            .map(ToString::to_string)
530            .map(|x| JsValue::from_str(&x))
531            .collect()
532    }
533
534    /// Returns the definition associated with the specified term
535    pub fn get_def(&self, term: &str) -> Option<InlineElementContainer> {
536        self.0.get(term).map(|x| {
537            InlineElementContainer(
538                x.iter()
539                    .map(|x| x.as_inner().as_inner().to_borrowed().into_owned())
540                    .collect::<v::InlineElementContainer>(),
541            )
542        })
543    }
544
545    /// Represents total number of terms stored in the list
546    #[wasm_bindgen(getter)]
547    pub fn term_cnt(&self) -> usize {
548        self.0.terms().count()
549    }
550
551    /// Represents total number of definitions stored in the list
552    #[wasm_bindgen(getter)]
553    pub fn def_cnt(&self) -> usize {
554        self.0.definitions().count()
555    }
556}
557
558/// Represents a wrapper around a vimwiki divider
559#[wasm_bindgen]
560pub struct Divider(v::Located<v::Divider>);
561
562/// Represents a wrapper around a vimwiki header
563#[wasm_bindgen]
564pub struct Header(v::Located<v::Header<'static>>);
565
566#[wasm_bindgen]
567impl Header {
568    /// Represents the level of the header
569    #[wasm_bindgen(getter)]
570    pub fn level(&self) -> usize {
571        self.0.level
572    }
573
574    /// Represents the content of the header
575    #[wasm_bindgen(getter)]
576    pub fn content(&self) -> InlineElementContainer {
577        InlineElementContainer(self.0.content.to_borrowed().into_owned())
578    }
579
580    /// Represents whether or not the header is centered
581    #[wasm_bindgen(getter)]
582    pub fn centered(&self) -> bool {
583        self.0.centered
584    }
585
586    /// Converts paragraph to a JavaScript string
587    pub fn to_str(&self) -> String {
588        self.0.content.to_string()
589    }
590}
591
592/// Represents a wrapper around a vimwiki list
593#[wasm_bindgen]
594pub struct List(v::Located<v::List<'static>>);
595
596#[wasm_bindgen]
597impl List {
598    /// Returns list item at the given index if it exists
599    pub fn item_at(&self, idx: usize) -> Option<ListItem> {
600        self.0.items.get(idx).map(|x| {
601            ListItem(v::Located::new(x.to_borrowed().into_owned(), x.region()))
602        })
603    }
604
605    /// Represents total number of items within list
606    #[wasm_bindgen(getter)]
607    pub fn item_cnt(&self) -> usize {
608        self.0.len()
609    }
610}
611
612/// Represents a wrapper around a vimwiki list item
613#[wasm_bindgen]
614pub struct ListItem(v::Located<v::ListItem<'static>>);
615
616#[wasm_bindgen]
617impl ListItem {
618    /// Represents position of list item within list
619    #[wasm_bindgen(getter)]
620    pub fn pos(&self) -> usize {
621        self.0.pos
622    }
623
624    /// Represents contents contained within list item
625    #[wasm_bindgen(getter)]
626    pub fn contents(&self) -> ListItemContents {
627        ListItemContents(self.0.contents.to_borrowed().into_owned())
628    }
629
630    /// Represents the prefix of list item (e.g. hyphen or roman numeral)
631    #[wasm_bindgen(getter)]
632    pub fn prefix(&self) -> String {
633        self.0.ty.to_prefix(self.0.pos, self.0.suffix)
634    }
635
636    /// Represents suffix of list item (e.g. period or paren)
637    #[wasm_bindgen(getter)]
638    pub fn suffix(&self) -> ListItemSuffix {
639        ListItemSuffix::from(self.0.suffix)
640    }
641
642    /// Represents attributes of list item
643    #[wasm_bindgen(getter)]
644    pub fn attributes(&self) -> ListItemAttributes {
645        ListItemAttributes(self.0.attributes)
646    }
647
648    /// Returns true if list item is ordered type
649    pub fn is_ordered(&self) -> bool {
650        self.0.ty.is_ordered()
651    }
652
653    /// Returns true if list item is unordered type
654    pub fn is_unordered(&self) -> bool {
655        self.0.ty.is_unordered()
656    }
657}
658
659/// Represents contents contained within list item
660#[wasm_bindgen]
661pub struct ListItemContents(v::ListItemContents<'static>);
662
663#[wasm_bindgen]
664impl ListItemContents {
665    /// Returns content at the given index if it exists
666    pub fn content_at(&self, idx: usize) -> Option<ListItemContent> {
667        self.0.get(idx).map(|x| {
668            ListItemContent(v::Located::new(
669                x.to_borrowed().into_owned(),
670                x.region(),
671            ))
672        })
673    }
674
675    /// Represents total number of separate content within the list item contents
676    #[wasm_bindgen(getter)]
677    pub fn content_cnt(&self) -> usize {
678        self.0.len()
679    }
680}
681
682/// Represents a singular piece of list item content
683#[wasm_bindgen]
684pub struct ListItemContent(v::Located<v::ListItemContent<'static>>);
685
686#[wasm_bindgen]
687impl ListItemContent {
688    /// Casts to list if it is one
689    pub fn into_list(self) -> Option<List> {
690        let region = self.0.region();
691        match self.0.into_inner() {
692            v::ListItemContent::List(x) => {
693                Some(List(v::Located::new(x, region)))
694            }
695            _ => None,
696        }
697    }
698
699    /// Casts to inline element container if it is one
700    pub fn into_inline_container(self) -> Option<InlineElementContainer> {
701        match self.0.into_inner() {
702            v::ListItemContent::InlineContent(x) => {
703                Some(InlineElementContainer(x))
704            }
705            _ => None,
706        }
707    }
708
709    /// Returns true if content is a sublist
710    pub fn is_list(&self) -> bool {
711        matches!(self.0.as_inner(), v::ListItemContent::List(_))
712    }
713
714    /// Returns true if content is inline content
715    pub fn is_inline_container(&self) -> bool {
716        matches!(self.0.as_inner(), v::ListItemContent::InlineContent(_))
717    }
718}
719
720/// Represents attributes associated with a list item
721#[wasm_bindgen]
722pub struct ListItemAttributes(v::ListItemAttributes);
723
724#[wasm_bindgen]
725impl ListItemAttributes {
726    /// Represents the todo status of the list item
727    #[wasm_bindgen(getter)]
728    pub fn todo_status(&self) -> Option<ListItemTodoStatus> {
729        self.0
730            .todo_status
731            .as_ref()
732            .copied()
733            .map(ListItemTodoStatus::from)
734    }
735
736    pub fn is_todo_incomplete(&self) -> bool {
737        matches!(self.0.todo_status, Some(v::ListItemTodoStatus::Incomplete))
738    }
739
740    pub fn is_todo_partially_complete_1(&self) -> bool {
741        matches!(
742            self.0.todo_status,
743            Some(v::ListItemTodoStatus::PartiallyComplete1)
744        )
745    }
746
747    pub fn is_todo_partially_complete_2(&self) -> bool {
748        matches!(
749            self.0.todo_status,
750            Some(v::ListItemTodoStatus::PartiallyComplete2)
751        )
752    }
753
754    pub fn is_todo_partially_complete_3(&self) -> bool {
755        matches!(
756            self.0.todo_status,
757            Some(v::ListItemTodoStatus::PartiallyComplete3)
758        )
759    }
760
761    pub fn is_todo_complete(&self) -> bool {
762        matches!(self.0.todo_status, Some(v::ListItemTodoStatus::Complete))
763    }
764
765    pub fn is_todo_rejected(&self) -> bool {
766        matches!(self.0.todo_status, Some(v::ListItemTodoStatus::Rejected))
767    }
768}
769
770/// Represents the todo status for a list item
771#[wasm_bindgen]
772pub enum ListItemTodoStatus {
773    Incomplete = "incomplete",
774    PartiallyComplete1 = "partially_complete_1",
775    PartiallyComplete2 = "partially_complete_2",
776    PartiallyComplete3 = "partially_complete_3",
777    Complete = "complete",
778    Rejected = "rejected",
779}
780
781impl ListItemTodoStatus {
782    pub fn to_vimwiki(&self) -> Option<v::ListItemTodoStatus> {
783        match self {
784            Self::Incomplete => Some(v::ListItemTodoStatus::Incomplete),
785            Self::PartiallyComplete1 => {
786                Some(v::ListItemTodoStatus::PartiallyComplete1)
787            }
788            Self::PartiallyComplete2 => {
789                Some(v::ListItemTodoStatus::PartiallyComplete2)
790            }
791            Self::PartiallyComplete3 => {
792                Some(v::ListItemTodoStatus::PartiallyComplete3)
793            }
794            Self::Complete => Some(v::ListItemTodoStatus::Complete),
795            Self::Rejected => Some(v::ListItemTodoStatus::Rejected),
796            _ => None,
797        }
798    }
799}
800
801impl From<v::ListItemTodoStatus> for ListItemTodoStatus {
802    fn from(x: v::ListItemTodoStatus) -> Self {
803        match x {
804            v::ListItemTodoStatus::Incomplete => Self::Incomplete,
805            v::ListItemTodoStatus::PartiallyComplete1 => {
806                Self::PartiallyComplete1
807            }
808            v::ListItemTodoStatus::PartiallyComplete2 => {
809                Self::PartiallyComplete2
810            }
811            v::ListItemTodoStatus::PartiallyComplete3 => {
812                Self::PartiallyComplete3
813            }
814            v::ListItemTodoStatus::Complete => Self::Complete,
815            v::ListItemTodoStatus::Rejected => Self::Rejected,
816        }
817    }
818}
819
820#[wasm_bindgen]
821pub enum ListItemSuffix {
822    None = "none",
823    Period = "period",
824    Paren = "paren",
825}
826
827impl ListItemSuffix {
828    pub fn to_vimwiki(&self) -> Option<v::ListItemSuffix> {
829        match self {
830            Self::None => Some(v::ListItemSuffix::None),
831            Self::Period => Some(v::ListItemSuffix::Period),
832            Self::Paren => Some(v::ListItemSuffix::Paren),
833            _ => None,
834        }
835    }
836}
837
838impl From<v::ListItemSuffix> for ListItemSuffix {
839    fn from(x: v::ListItemSuffix) -> Self {
840        match x {
841            v::ListItemSuffix::None => Self::None,
842            v::ListItemSuffix::Period => Self::Period,
843            v::ListItemSuffix::Paren => Self::Paren,
844        }
845    }
846}
847
848/// Represents a wrapper around a vimwiki math block
849#[wasm_bindgen]
850pub struct MathBlock(v::Located<v::MathBlock<'static>>);
851
852#[wasm_bindgen]
853impl MathBlock {
854    /// Represents the environment associated with the math block
855    #[wasm_bindgen(getter)]
856    pub fn environment(&self) -> Option<String> {
857        self.0.environment.as_ref().map(ToString::to_string)
858    }
859
860    /// Returns line at the given index if it exists
861    pub fn line_at(&self, idx: usize) -> Option<String> {
862        self.0.lines.get(idx).map(ToString::to_string)
863    }
864
865    /// Represents total number of lines within the block
866    #[wasm_bindgen(getter)]
867    pub fn line_cnt(&self) -> usize {
868        self.0.lines.len()
869    }
870}
871
872/// Represents a wrapper around a vimwiki paragraph
873#[wasm_bindgen]
874pub struct Paragraph(v::Located<v::Paragraph<'static>>);
875
876#[wasm_bindgen]
877impl Paragraph {
878    /// Returns line as inline element container at the given index if it exists
879    pub fn line_at(&self, idx: usize) -> Option<InlineElementContainer> {
880        self.0
881            .lines
882            .get(idx)
883            .map(|x| InlineElementContainer(x.to_borrowed().into_owned()))
884    }
885
886    /// Represents total number of lines within the paragraph
887    #[wasm_bindgen(getter)]
888    pub fn line_cnt(&self) -> usize {
889        self.0.lines.len()
890    }
891
892    /// Converts paragraph to a JavaScript string
893    pub fn to_str(&self) -> String {
894        self.0
895            .lines
896            .iter()
897            .map(ToString::to_string)
898            .collect::<Vec<String>>()
899            .join("\n")
900    }
901}
902
903/// Represents a wrapper around a vimwiki placeholder
904#[wasm_bindgen]
905pub struct Placeholder(v::Located<v::Placeholder<'static>>);
906
907#[wasm_bindgen]
908impl Placeholder {
909    /// Represents the title associated with the placeholder if it has one
910    #[wasm_bindgen(getter)]
911    pub fn title(&self) -> Option<String> {
912        match self.0.as_inner() {
913            v::Placeholder::Title(x) => Some(x.to_string()),
914            _ => None,
915        }
916    }
917
918    /// Represents the template associated with the placeholder if it has one
919    #[wasm_bindgen(getter)]
920    pub fn template(&self) -> Option<String> {
921        match self.0.as_inner() {
922            v::Placeholder::Template(x) => Some(x.to_string()),
923            _ => None,
924        }
925    }
926
927    /// Represents the date associated with the placeholder if it has one
928    #[wasm_bindgen(getter)]
929    pub fn date(&self) -> Option<js_sys::Date> {
930        use chrono::Datelike;
931        match self.0.as_inner() {
932            v::Placeholder::Date(x) => {
933                Some(js_sys::Date::new_with_year_month_day(
934                    x.year() as u32,
935                    x.month() as i32,
936                    x.day() as i32,
937                ))
938            }
939            _ => None,
940        }
941    }
942
943    /// Represents the other placeholder's name if it has one
944    #[wasm_bindgen(getter)]
945    pub fn other_name(&self) -> Option<String> {
946        match self.0.as_inner() {
947            v::Placeholder::Other { name, .. } => Some(name.to_string()),
948            _ => None,
949        }
950    }
951
952    /// Represents the other placeholder's value if it has one
953    #[wasm_bindgen(getter)]
954    pub fn other_value(&self) -> Option<String> {
955        match self.0.as_inner() {
956            v::Placeholder::Other { value, .. } => Some(value.to_string()),
957            _ => None,
958        }
959    }
960
961    /// Returns true if placeholder represents a flag for no html output
962    pub fn is_no_html(&self) -> bool {
963        matches!(self.0.as_inner(), v::Placeholder::NoHtml)
964    }
965}
966
967/// Represents a wrapper around a vimwiki table
968#[wasm_bindgen]
969pub struct Table(v::Located<v::Table<'static>>);
970
971#[wasm_bindgen]
972impl Table {
973    /// Creates a new table from the given cells and centered status
974    ///
975    /// Cells are a 2D matrix
976    #[wasm_bindgen(constructor)]
977    pub fn new(
978        cells: js_sys::Array,
979        centered: bool,
980        region: Option<Region>,
981    ) -> Result<Table, JsValue> {
982        Ok(Self(v::Located::new(
983            v::Table::new(
984                cells
985                    .iter()
986                    .map(js_sys::Array::try_from)
987                    .enumerate()
988                    .filter_map(|(row, res)| {
989                        res.map(|arr| {
990                            arr.iter()
991                            .filter_map(|x| {
992                                utils::cast_value::<Cell>(x, "Cell").ok()
993                            })
994                            .enumerate()
995                            .map(|(col, x)| {
996                                (v::CellPos { row, col }, x.0)
997                            })
998                            .collect::<Vec<(v::CellPos, v::Located<v::Cell>)>>()
999                        })
1000                        .ok()
1001                    })
1002                    .flatten(),
1003                centered,
1004            ),
1005            region.map(|x| x.0).unwrap_or_default(),
1006        )))
1007    }
1008
1009    /// Returns cell at the given row & column if it exists
1010    pub fn cell_at(&self, row: usize, col: usize) -> Option<Cell> {
1011        self.0.get_cell(row, col).map(|x| {
1012            Cell(v::Located::new(x.to_borrowed().into_owned(), x.region()))
1013        })
1014    }
1015
1016    /// Returns total number of rows
1017    #[wasm_bindgen(getter)]
1018    pub fn row_cnt(&self) -> usize {
1019        self.0.row_cnt()
1020    }
1021
1022    /// Returns total number of columns
1023    #[wasm_bindgen(getter)]
1024    pub fn col_cnt(&self) -> usize {
1025        self.0.col_cnt()
1026    }
1027
1028    /// Returns true if centered
1029    #[wasm_bindgen(getter)]
1030    pub fn centered(&self) -> bool {
1031        self.0.centered
1032    }
1033}
1034
1035/// Represents a wrapper around a vimwiki table cell
1036#[wasm_bindgen]
1037pub struct Cell(v::Located<v::Cell<'static>>);
1038
1039#[wasm_bindgen]
1040impl Cell {
1041    /// Creates a new table cell from the given string
1042    #[wasm_bindgen(constructor)]
1043    pub fn new(txt: &str, region: Option<Region>) -> Result<Cell, JsValue> {
1044        Ok(Self(v::Located::new(
1045            v::Cell::Content(v::InlineElementContainer::new(vec![
1046                v::Located::from(v::InlineElement::Text(v::Text::from(txt))),
1047            ]))
1048            .into_owned(),
1049            region.map(|x| x.0).unwrap_or_default(),
1050        )))
1051    }
1052
1053    /// Represents content contained within cell if it has content
1054    #[wasm_bindgen(getter)]
1055    pub fn content(&self) -> Option<InlineElementContainer> {
1056        self.0
1057            .get_content()
1058            .map(|x| InlineElementContainer(x.to_borrowed().into_owned()))
1059    }
1060
1061    /// Returns true if cell is a span type
1062    pub fn is_span(&self) -> bool {
1063        self.0.is_span()
1064    }
1065
1066    /// Returns true if cell is a span from above type
1067    pub fn is_span_from_above(&self) -> bool {
1068        self.0
1069            .get_span()
1070            .map(|x| matches!(x, v::CellSpan::FromAbove))
1071            .unwrap_or_default()
1072    }
1073
1074    /// Returns true if cell is a span from left type
1075    pub fn is_span_from_left(&self) -> bool {
1076        self.0
1077            .get_span()
1078            .map(|x| matches!(x, v::CellSpan::FromLeft))
1079            .unwrap_or_default()
1080    }
1081
1082    /// Returns true if cell is a column alignment type
1083    pub fn is_align(&self) -> bool {
1084        self.0.is_align()
1085    }
1086
1087    /// Returns true if cell is a column alignment left type
1088    pub fn is_align_left(&self) -> bool {
1089        self.0
1090            .get_align()
1091            .map(|x| matches!(x, v::ColumnAlign::Left))
1092            .unwrap_or_default()
1093    }
1094
1095    /// Returns true if cell is a column alignment centered type
1096    pub fn is_align_centered(&self) -> bool {
1097        self.0
1098            .get_align()
1099            .map(|x| matches!(x, v::ColumnAlign::Center))
1100            .unwrap_or_default()
1101    }
1102
1103    /// Returns true if cell is a column alignment right type
1104    pub fn is_align_right(&self) -> bool {
1105        self.0
1106            .get_align()
1107            .map(|x| matches!(x, v::ColumnAlign::Right))
1108            .unwrap_or_default()
1109    }
1110
1111    /// Converts cell to a JavaScript string if it is content
1112    pub fn to_str(&self) -> Option<String> {
1113        self.0.get_content().map(ToString::to_string)
1114    }
1115}
1116
1117/// Represents a wrapper around a vimwiki decorated text
1118#[wasm_bindgen]
1119pub struct DecoratedText(v::Located<v::DecoratedText<'static>>);
1120
1121#[wasm_bindgen]
1122impl DecoratedText {
1123    /// Creates some new bold text
1124    pub fn new_bold_text(txt: &str, region: Option<Region>) -> DecoratedText {
1125        Self(v::Located::new(
1126            v::DecoratedText::Bold(vec![v::Located::from(
1127                v::DecoratedTextContent::Text(v::Text::from(txt)),
1128            )])
1129            .into_owned(),
1130            region.map(|x| x.0).unwrap_or_default(),
1131        ))
1132    }
1133
1134    /// Creates some new italic text
1135    pub fn new_italic_text(txt: &str, region: Option<Region>) -> DecoratedText {
1136        Self(v::Located::new(
1137            v::DecoratedText::Italic(vec![v::Located::from(
1138                v::DecoratedTextContent::Text(v::Text::from(txt)),
1139            )])
1140            .into_owned(),
1141            region.map(|x| x.0).unwrap_or_default(),
1142        ))
1143    }
1144
1145    /// Creates some new strikeout text
1146    pub fn new_strikeout_text(
1147        txt: &str,
1148        region: Option<Region>,
1149    ) -> DecoratedText {
1150        Self(v::Located::new(
1151            v::DecoratedText::Strikeout(vec![v::Located::from(
1152                v::DecoratedTextContent::Text(v::Text::from(txt)),
1153            )])
1154            .into_owned(),
1155            region.map(|x| x.0).unwrap_or_default(),
1156        ))
1157    }
1158
1159    /// Creates some new superscript text
1160    pub fn new_superscript_text(
1161        txt: &str,
1162        region: Option<Region>,
1163    ) -> DecoratedText {
1164        Self(v::Located::new(
1165            v::DecoratedText::Superscript(vec![v::Located::from(
1166                v::DecoratedTextContent::Text(v::Text::from(txt)),
1167            )])
1168            .into_owned(),
1169            region.map(|x| x.0).unwrap_or_default(),
1170        ))
1171    }
1172
1173    /// Creates some new subscript text
1174    pub fn new_subscript_text(
1175        txt: &str,
1176        region: Option<Region>,
1177    ) -> DecoratedText {
1178        Self(v::Located::new(
1179            v::DecoratedText::Subscript(vec![v::Located::from(
1180                v::DecoratedTextContent::Text(v::Text::from(txt)),
1181            )])
1182            .into_owned(),
1183            region.map(|x| x.0).unwrap_or_default(),
1184        ))
1185    }
1186
1187    /// Returns true if bold
1188    pub fn is_bold(&self) -> bool {
1189        matches!(self.0.as_inner(), v::DecoratedText::Bold(_))
1190    }
1191
1192    /// Returns true if italic
1193    pub fn is_italic(&self) -> bool {
1194        matches!(self.0.as_inner(), v::DecoratedText::Italic(_))
1195    }
1196
1197    /// Returns true if strikeout
1198    pub fn is_strikeout(&self) -> bool {
1199        matches!(self.0.as_inner(), v::DecoratedText::Strikeout(_))
1200    }
1201
1202    /// Returns true if superscript
1203    pub fn is_superscript(&self) -> bool {
1204        matches!(self.0.as_inner(), v::DecoratedText::Superscript(_))
1205    }
1206
1207    /// Returns true if subscript
1208    pub fn is_subscript(&self) -> bool {
1209        matches!(self.0.as_inner(), v::DecoratedText::Subscript(_))
1210    }
1211
1212    /// Returns the contents of the decorated text
1213    #[wasm_bindgen(getter)]
1214    pub fn contents(&self) -> DecoratedTextContents {
1215        DecoratedTextContents(
1216            self.0
1217                .iter()
1218                .map(|x| x.as_ref().map(|x| x.to_borrowed().into_owned()))
1219                .collect(),
1220        )
1221    }
1222
1223    /// Converts text to a JavaScript string
1224    pub fn to_str(&self) -> String {
1225        self.0.to_string()
1226    }
1227}
1228
1229/// Represents a collection of decorated text contents
1230#[wasm_bindgen]
1231pub struct DecoratedTextContents(
1232    Vec<v::Located<v::DecoratedTextContent<'static>>>,
1233);
1234
1235#[wasm_bindgen]
1236impl DecoratedTextContents {
1237    /// Returns the content at the specified index
1238    pub fn get(&self, idx: usize) -> Option<DecoratedTextContent> {
1239        self.0.get(idx).map(|x| {
1240            DecoratedTextContent(
1241                x.as_ref().map(|x| x.to_borrowed().into_owned()),
1242            )
1243        })
1244    }
1245
1246    /// Indicates whether or not there is content
1247    pub fn is_empty(&self) -> bool {
1248        self.0.is_empty()
1249    }
1250
1251    /// Represents total number of contents contained within
1252    #[wasm_bindgen(getter)]
1253    pub fn len(&self) -> usize {
1254        self.0.len()
1255    }
1256}
1257
1258/// Represents a singular piece of content within decorated text
1259#[wasm_bindgen]
1260pub struct DecoratedTextContent(v::Located<v::DecoratedTextContent<'static>>);
1261
1262#[wasm_bindgen]
1263impl DecoratedTextContent {
1264    /// Converts text to a JavaScript string
1265    pub fn to_str(&self) -> String {
1266        self.0.to_string()
1267    }
1268}
1269
1270/// Represents a wrapper around a vimwiki keyword
1271#[wasm_bindgen]
1272pub enum Keyword {
1273    Todo = "TODO",
1274    Done = "DONE",
1275    Started = "STARTED",
1276    Fixme = "FIXME",
1277    Fixed = "FIXED",
1278    Xxx = "XXX",
1279}
1280
1281impl Keyword {
1282    /// Converts to vimwiki instance
1283    pub fn to_vimwiki(&self) -> Option<v::Keyword> {
1284        match self {
1285            Keyword::Todo => Some(v::Keyword::Todo),
1286            Keyword::Done => Some(v::Keyword::Done),
1287            Keyword::Started => Some(v::Keyword::Started),
1288            Keyword::Fixme => Some(v::Keyword::Fixme),
1289            Keyword::Fixed => Some(v::Keyword::Fixed),
1290            Keyword::Xxx => Some(v::Keyword::Xxx),
1291            _ => None,
1292        }
1293    }
1294}
1295
1296impl From<v::Keyword> for Keyword {
1297    fn from(x: v::Keyword) -> Self {
1298        match x {
1299            v::Keyword::Todo => Self::Todo,
1300            v::Keyword::Done => Self::Done,
1301            v::Keyword::Started => Self::Started,
1302            v::Keyword::Fixme => Self::Fixme,
1303            v::Keyword::Fixed => Self::Fixed,
1304            v::Keyword::Xxx => Self::Xxx,
1305        }
1306    }
1307}
1308
1309/// Represents a wrapper around a vimwiki link
1310#[wasm_bindgen]
1311pub struct Link(v::Located<v::Link<'static>>);
1312
1313#[wasm_bindgen]
1314impl Link {
1315    /// Creates a new wiki link
1316    pub fn new_wiki_link(
1317        uri: &str,
1318        description: &str,
1319        region: Option<Region>,
1320    ) -> Result<Link, JsValue> {
1321        Ok(Self(v::Located::new(
1322            v::Link::new_wiki_link(
1323                uriparse::URIReference::try_from(uri)
1324                    .map_err(|x| JsValue::from_str(x.to_string().as_str()))?,
1325                v::Description::try_from_uri_ref_str(description)
1326                    .unwrap_or_else(|_| v::Description::from(description)),
1327            )
1328            .into_owned(),
1329            region.map(|x| x.0).unwrap_or_default(),
1330        )))
1331    }
1332
1333    /// Creates a new indexed interwiki link
1334    pub fn new_indexed_interwiki_link(
1335        index: u32,
1336        uri: &str,
1337        description: &str,
1338        region: Option<Region>,
1339    ) -> Result<Link, JsValue> {
1340        Ok(Self(v::Located::new(
1341            v::Link::new_indexed_interwiki_link(
1342                index,
1343                uriparse::URIReference::try_from(uri)
1344                    .map_err(|x| JsValue::from_str(x.to_string().as_str()))?,
1345                v::Description::try_from_uri_ref_str(description)
1346                    .unwrap_or_else(|_| v::Description::from(description)),
1347            )
1348            .into_owned(),
1349            region.map(|x| x.0).unwrap_or_default(),
1350        )))
1351    }
1352
1353    /// Creates a new named interwiki link
1354    pub fn new_named_interwiki_link(
1355        name: &str,
1356        uri: &str,
1357        description: &str,
1358        region: Option<Region>,
1359    ) -> Result<Link, JsValue> {
1360        Ok(Self(v::Located::new(
1361            v::Link::new_named_interwiki_link(
1362                name,
1363                uriparse::URIReference::try_from(uri)
1364                    .map_err(|x| JsValue::from_str(x.to_string().as_str()))?,
1365                v::Description::try_from_uri_ref_str(description)
1366                    .unwrap_or_else(|_| v::Description::from(description)),
1367            )
1368            .into_owned(),
1369            region.map(|x| x.0).unwrap_or_default(),
1370        )))
1371    }
1372
1373    /// Creates a new diary link
1374    pub fn new_diary_link(
1375        date: js_sys::Date,
1376        description: &str,
1377        anchor: &str,
1378        region: Option<Region>,
1379    ) -> Link {
1380        Self(v::Located::new(
1381            v::Link::new_diary_link(
1382                chrono::NaiveDate::from_ymd(
1383                    date.get_utc_full_year() as i32,
1384                    date.get_utc_month(),
1385                    date.get_utc_date(),
1386                ),
1387                v::Description::try_from_uri_ref_str(description)
1388                    .unwrap_or_else(|_| v::Description::from(description)),
1389                v::Anchor::from_uri_fragment(anchor),
1390            )
1391            .into_owned(),
1392            region.map(|x| x.0).unwrap_or_default(),
1393        ))
1394    }
1395
1396    /// Creates a new raw link
1397    pub fn new_raw_link(
1398        uri: &str,
1399        region: Option<Region>,
1400    ) -> Result<Link, JsValue> {
1401        Ok(Self(v::Located::new(
1402            v::Link::new_raw_link(
1403                uriparse::URIReference::try_from(uri)
1404                    .map_err(|x| JsValue::from_str(x.to_string().as_str()))?,
1405            )
1406            .into_owned(),
1407            region.map(|x| x.0).unwrap_or_default(),
1408        )))
1409    }
1410
1411    /// Creates a new transclusion link
1412    pub fn new_transclusion_link(
1413        uri: &str,
1414        description: &str,
1415        properties: &js_sys::Object,
1416        region: Option<Region>,
1417    ) -> Result<Link, JsValue> {
1418        let uri = uriparse::URIReference::try_from(uri)
1419            .map_err(|x| JsValue::from_str(x.to_string().as_str()))?;
1420        let desc = v::Description::try_from_uri_ref_str(description)
1421            .unwrap_or_else(|_| v::Description::from(description));
1422        let props: HashMap<Cow<'_, str>, Cow<'_, str>> =
1423            js_sys::Object::entries(properties)
1424                .iter()
1425                .filter_map(|entry| {
1426                    use wasm_bindgen::JsCast;
1427                    entry.dyn_ref::<js_sys::Array>().and_then(|entry| {
1428                        let key = js_sys::Array::get(entry, 0);
1429                        let value = js_sys::Array::get(entry, 1);
1430
1431                        key.as_string().and_then(|key| {
1432                            value.as_string().map(|value| {
1433                                (Cow::Owned(key), Cow::Owned(value))
1434                            })
1435                        })
1436                    })
1437                })
1438                .collect();
1439
1440        Ok(Self(v::Located::new(
1441            v::Link::new_transclusion_link(uri, desc, props).into_owned(),
1442            region.map(|x| x.0).unwrap_or_default(),
1443        )))
1444    }
1445
1446    /// Returns uri associated with link
1447    #[wasm_bindgen(getter)]
1448    pub fn uri(&self) -> String {
1449        self.0.data().uri_ref.to_string()
1450    }
1451
1452    /// Returns description associated with link (if it exists)
1453    #[wasm_bindgen(getter)]
1454    pub fn description(&self) -> Option<String> {
1455        self.0.description().map(ToString::to_string)
1456    }
1457
1458    /// Returns object containing properties of link
1459    #[wasm_bindgen(getter)]
1460    pub fn properties(&self) -> Option<js_sys::Object> {
1461        self.0.properties().and_then(|props| {
1462            let arr = js_sys::Array::new();
1463
1464            for (key, value) in props.iter() {
1465                let tuple = js_sys::Array::new();
1466                tuple.push(&JsValue::from_str(key.as_ref()));
1467                tuple.push(&JsValue::from_str(value.as_ref()));
1468
1469                arr.push(&tuple);
1470            }
1471
1472            js_sys::Object::from_entries(&arr).ok()
1473        })
1474    }
1475
1476    /// Retrieves the property with the specified name if it exists
1477    pub fn get_property(&self, name: &str) -> Option<String> {
1478        self.0
1479            .properties()
1480            .and_then(|p| p.get(&Cow::from(name)).map(ToString::to_string))
1481    }
1482
1483    /// Returns scheme of link's uri (if it exists)
1484    #[wasm_bindgen(getter)]
1485    pub fn scheme(&self) -> Option<String> {
1486        self.0.scheme().map(ToString::to_string)
1487    }
1488
1489    /// Returns date (YYYY-MM-DD) associated with link (if it exists)
1490    #[wasm_bindgen(getter)]
1491    pub fn date(&self) -> Option<String> {
1492        self.0.date().map(|x| x.format("%Y-%m-%d").to_string())
1493    }
1494
1495    /// Returns index of wiki pointed to by link if it is different
1496    #[wasm_bindgen(getter)]
1497    pub fn wiki_index(&self) -> Option<u32> {
1498        self.0.index()
1499    }
1500
1501    /// Returns name of wiki pointed to by link if it is different
1502    #[wasm_bindgen(getter)]
1503    pub fn wiki_name(&self) -> Option<String> {
1504        self.0.name().map(ToString::to_string)
1505    }
1506}
1507
1508/// Represents a wrapper around a set of vimwiki tags
1509#[wasm_bindgen]
1510pub struct Tags(v::Located<v::Tags<'static>>);
1511
1512#[wasm_bindgen]
1513impl Tags {
1514    /// Creates a new tag set instance using the given list of strings
1515    #[allow(clippy::boxed_local)]
1516    #[wasm_bindgen(constructor)]
1517    pub fn new(
1518        array: Box<[JsValue]>,
1519        region: Option<Region>,
1520    ) -> Result<Tags, JsValue> {
1521        let tags: v::Tags =
1522            array.iter().filter_map(|x| x.as_string()).collect();
1523
1524        Ok(Self(v::Located::new(
1525            tags,
1526            region.map(|x| x.0).unwrap_or_default(),
1527        )))
1528    }
1529
1530    /// Retrieves the tag at the specified index within the tag set
1531    pub fn tag_at(&self, idx: usize) -> Option<Tag> {
1532        self.0.get(idx).map(|x| Tag(x.as_borrowed().into_owned()))
1533    }
1534
1535    /// Represents total tags contained within the set
1536    #[wasm_bindgen(getter)]
1537    pub fn tag_cnt(&self) -> usize {
1538        self.0.len()
1539    }
1540
1541    /// Converts tags to a JavaScript string
1542    pub fn to_str(&self) -> String {
1543        self.0.to_string()
1544    }
1545}
1546
1547/// Represents a wrapper around a singular vimwiki tag
1548#[wasm_bindgen]
1549pub struct Tag(v::Tag<'static>);
1550
1551#[wasm_bindgen]
1552impl Tag {
1553    /// Creates a new tag instance using the given string
1554    pub fn new(txt: &str) -> Self {
1555        Self(v::Tag::from(txt).into_owned())
1556    }
1557
1558    /// Converts tag to a JavaScript string
1559    pub fn to_str(&self) -> String {
1560        self.0.to_string()
1561    }
1562}
1563
1564/// Represents a wrapper around a vimwiki code inline
1565#[wasm_bindgen]
1566pub struct CodeInline(v::Located<v::CodeInline<'static>>);
1567
1568#[wasm_bindgen]
1569impl CodeInline {
1570    /// Creates a new inline code instance using the given string
1571    #[wasm_bindgen(constructor)]
1572    pub fn new(txt: &str, region: Option<Region>) -> Self {
1573        Self(v::Located::new(
1574            v::CodeInline::new(Cow::from(txt)).into_owned(),
1575            region.map(|x| x.0).unwrap_or_default(),
1576        ))
1577    }
1578
1579    /// Converts inline code to a JavaScript string
1580    pub fn to_str(&self) -> String {
1581        self.0.to_string()
1582    }
1583}
1584
1585/// Represents a wrapper around a vimwiki math inline
1586#[wasm_bindgen]
1587pub struct MathInline(v::Located<v::MathInline<'static>>);
1588
1589#[wasm_bindgen]
1590impl MathInline {
1591    /// Creates a new inline math instance using the given string
1592    #[wasm_bindgen(constructor)]
1593    pub fn new(txt: &str, region: Option<Region>) -> Self {
1594        Self(v::Located::new(
1595            v::MathInline::new(Cow::from(txt)).into_owned(),
1596            region.map(|x| x.0).unwrap_or_default(),
1597        ))
1598    }
1599
1600    /// Converts inline math to a JavaScript string
1601    pub fn to_str(&self) -> String {
1602        self.0.to_string()
1603    }
1604}
1605
1606/// Represents a wrapper around a vimwiki comment
1607#[wasm_bindgen]
1608pub struct Comment(v::Located<v::Comment<'static>>);
1609
1610#[wasm_bindgen]
1611impl Comment {
1612    /// Creates a new comment instance, marked as either a line comment
1613    /// or multiline comment based on the flag
1614    #[wasm_bindgen(constructor)]
1615    pub fn new(txt: &str, multiline: bool, region: Option<Region>) -> Self {
1616        if multiline {
1617            Self(v::Located::new(
1618                v::Comment::MultiLine(v::MultiLineComment::new(
1619                    txt.split('\n').map(Cow::from).collect(),
1620                ))
1621                .into_owned(),
1622                region.map(|x| x.0).unwrap_or_default(),
1623            ))
1624        } else {
1625            Self(v::Located::new(
1626                v::Comment::Line(v::LineComment::new(Cow::from(txt)))
1627                    .into_owned(),
1628                region.map(|x| x.0).unwrap_or_default(),
1629            ))
1630        }
1631    }
1632
1633    /// Retrieves the line at the specified index
1634    pub fn line_at(&self, idx: usize) -> Option<String> {
1635        match self.0.as_inner() {
1636            v::Comment::Line(x) if idx == 0 => Some(x.to_string()),
1637            v::Comment::MultiLine(x) => x.get(idx).map(ToString::to_string),
1638            _ => None,
1639        }
1640    }
1641
1642    /// Returns total lines contained within the comment
1643    #[wasm_bindgen(getter)]
1644    pub fn line_cnt(&self) -> usize {
1645        match self.0.as_inner() {
1646            v::Comment::Line(_) => 1,
1647            v::Comment::MultiLine(x) => x.len(),
1648        }
1649    }
1650
1651    /// Converts comment to a JavaScript string
1652    pub fn to_str(&self) -> String {
1653        self.0.to_string()
1654    }
1655}
1656
1657/// Represents a wrapper around a vimwiki text
1658#[wasm_bindgen]
1659pub struct Text(v::Located<v::Text<'static>>);
1660
1661#[wasm_bindgen]
1662impl Text {
1663    /// Creates a new text instance using the given string
1664    #[wasm_bindgen(constructor)]
1665    pub fn new(txt: &str, region: Option<Region>) -> Self {
1666        Self(v::Located::new(
1667            v::Text::new(Cow::from(txt)).into_owned(),
1668            region.map(|x| x.0).unwrap_or_default(),
1669        ))
1670    }
1671
1672    /// Converts text to a JavaScript string
1673    pub fn to_str(&self) -> String {
1674        self.0.to_string()
1675    }
1676}
1677
1678/// Represents a wrapper around a vimwiki region
1679#[wasm_bindgen]
1680pub struct Region(v::Region);
1681
1682#[wasm_bindgen]
1683impl Region {
1684    /// Creates a new region instance
1685    #[wasm_bindgen(constructor)]
1686    pub fn new(offset: usize, len: usize, depth: u16) -> Self {
1687        Self(v::Region::new_at_depth(offset, len, depth))
1688    }
1689
1690    /// Represents the offset (starting from 0) of this region in the text
1691    #[wasm_bindgen(getter)]
1692    pub fn offset(&self) -> usize {
1693        self.0.offset()
1694    }
1695
1696    /// Returns true if the region is actually empty (len == 0)
1697    pub fn is_empty(&self) -> bool {
1698        self.0.is_empty()
1699    }
1700
1701    /// Represents the length of this region in the text
1702    #[wasm_bindgen(getter)]
1703    pub fn len(&self) -> usize {
1704        self.0.len()
1705    }
1706
1707    /// Represents the depth of this region in the text
1708    #[wasm_bindgen(getter)]
1709    pub fn depth(&self) -> u16 {
1710        self.0.depth()
1711    }
1712}
1713
1714/// Provide From impl;
1715///
1716/// use @ to not use lifetimes
1717/// use - to not use Located<..>
1718/// use -@ to apply both
1719macro_rules! impl_from {
1720    (-@$name:ident $($tail:tt)*) => {
1721        impl From<v::$name> for $name {
1722            fn from(x: v::$name) -> Self {
1723                Self(x)
1724            }
1725        }
1726
1727        impl_from!($($tail)*);
1728    };
1729    (-$name:ident $($tail:tt)*) => {
1730        impl From<v::$name<'static>> for $name {
1731            fn from(x: v::$name<'static>) -> Self {
1732                Self(x)
1733            }
1734        }
1735
1736        impl_from!($($tail)*);
1737    };
1738    (@$name:ident $($tail:tt)*) => {
1739        impl From<v::Located<v::$name>> for $name {
1740            fn from(x: v::Located<v::$name>) -> Self {
1741                Self(x)
1742            }
1743        }
1744
1745        impl_from!($($tail)*);
1746    };
1747    ($name:ident $($tail:tt)*) => {
1748        impl From<v::Located<v::$name<'static>>> for $name {
1749            fn from(x: v::Located<v::$name<'static>>) -> Self {
1750                Self(x)
1751            }
1752        }
1753
1754        impl_from!($($tail)*);
1755    };
1756    () => {};
1757}
1758
1759impl_from!(
1760    -Page Element BlockElement InlineBlockElement InlineElement Blockquote
1761    CodeBlock DefinitionList @Divider Header List MathBlock Paragraph Table
1762    DecoratedText Link Tags CodeInline MathInline Comment Text
1763    -InlineElementContainer DecoratedTextContent ListItem ListItemContent
1764    Placeholder -@Region
1765);
1766
1767/// Provide conversion functions; use @ to not include html
1768macro_rules! impl_convert {
1769    (@$name:ident $($tail:tt)*) => {
1770        #[wasm_bindgen]
1771        impl $name {
1772            /// Convert to a JavaScript value
1773            pub fn to_js(&self) -> JsValue {
1774                JsValue::from_serde(&self.0).unwrap()
1775            }
1776
1777            /// Convert to a debug string
1778            pub fn to_debug_str(&self) -> String {
1779                format!("{:?}", self.0)
1780            }
1781        }
1782
1783        impl_convert!($($tail)*);
1784    };
1785    ($name:ident $($tail:tt)*) => {
1786        #[wasm_bindgen]
1787        impl $name {
1788            /// Convert to an HTML string, optionally taking a config object
1789            pub fn to_html_str(&self, config: &JsValue) -> Result<String, JsValue> {
1790                // Attempt to read a config from a JS object, but if not provided
1791                // default to the standard config
1792                let config: v::HtmlConfig = if !config.is_undefined() && !config.is_null() {
1793                    config.into_serde().map_err(|x| JsValue::from(x.to_string()))?
1794                } else {
1795                    Default::default()
1796                };
1797
1798                self.0
1799                    .to_html_string(config)
1800                    .map_err(|x| x.to_string().into())
1801            }
1802        }
1803
1804        impl_convert!(@$name $($tail)*);
1805    };
1806    () => {};
1807}
1808
1809impl_convert!(
1810    Page Element BlockElement InlineBlockElement InlineElement Blockquote
1811    CodeBlock DefinitionList Divider Header List MathBlock Paragraph Table
1812    DecoratedText Link Tags CodeInline MathInline Comment Text
1813    InlineElementContainer DecoratedTextContent ListItem ListItemContent
1814    Placeholder @Region
1815);
1816
1817macro_rules! impl_iter {
1818    ($name:ident $($tail:tt)*) => {
1819        #[wasm_bindgen]
1820        impl $name {
1821            /// Convert to an array containing all immediate children elements
1822            #[wasm_bindgen(getter)]
1823            pub fn children(&self) -> js_sys::Array {
1824                use vimwiki::IntoChildren;
1825                self.0
1826                    .to_borrowed()
1827                    .into_children()
1828                    .into_iter()
1829                    .map(|x| v::Located::new(
1830                        v::Element::from(x.to_borrowed().into_owned()),
1831                        x.region(),
1832                    ))
1833                    .map(Element::from)
1834                    .map(JsValue::from)
1835                    .collect()
1836            }
1837
1838            /// Convert to an array of all elements within current object
1839            #[wasm_bindgen(getter)]
1840            pub fn descendants(&self) -> js_sys::Array {
1841                use vimwiki::IntoChildren;
1842
1843                // Used to keep track of all elements
1844                let mut elements = Vec::new();
1845
1846                // Queue of elements whose children to acquire
1847                let mut queue: Vec<v::Located<v::Element<'_>>> = self.0
1848                    .to_borrowed()
1849                    .into_children()
1850                    .into_iter()
1851                    .map(|x| x.as_ref().map(
1852                        |x| v::Element::from(x.to_borrowed().into_owned())
1853                    ))
1854                    .collect();
1855
1856                while !queue.is_empty() {
1857                    let next = queue.remove(0);
1858                    let children: Vec<v::Located<v::Element<'_>>> = next
1859                        .as_inner()
1860                        .clone()
1861                        .into_children()
1862                        .into_iter()
1863                        .map(|x| x.as_ref().map(
1864                            |x| v::Element::from(x.to_borrowed().into_owned()),
1865                        ))
1866                        .collect();
1867                    elements.push(next);
1868                    queue.extend(children);
1869                }
1870
1871                // Collect elements into final array
1872                elements
1873                    .into_iter()
1874                    .map(Element::from)
1875                    .map(JsValue::from)
1876                    .collect()
1877            }
1878        }
1879
1880        impl_iter!($($tail)*);
1881    };
1882    () => {};
1883}
1884
1885impl_iter!(
1886    Page Element BlockElement InlineBlockElement InlineElement
1887    DefinitionList Header List Paragraph Table
1888    DecoratedText InlineElementContainer DecoratedTextContent ListItem
1889);
1890
1891macro_rules! impl_region {
1892    ($name:ident $($tail:tt)*) => {
1893        #[wasm_bindgen]
1894        impl $name {
1895            /// Represents region of text that this element occupies
1896            #[wasm_bindgen(getter)]
1897            pub fn region(&self) -> Region {
1898                Region(self.0.region())
1899            }
1900        }
1901
1902        impl_region!($($tail)*);
1903    };
1904    () => {};
1905}
1906
1907impl_region!(
1908    Element BlockElement InlineBlockElement InlineElement
1909
1910    Blockquote CodeBlock DefinitionList Divider Header List MathBlock
1911    Paragraph Placeholder Table
1912
1913    DecoratedText Link Tags CodeInline MathInline Comment Text
1914    DecoratedTextContent ListItem ListItemContent
1915);