Skip to main content

tiptap_rusty_parser/
node.rs

1//! Core data model: [`Node`] and [`Mark`].
2//!
3//! Mirrors Tiptap's `JSONContent` interface. Schema-agnostic: any node/mark
4//! `type` is accepted and unknown JSON fields are preserved in `extra` for
5//! faithful roundtrip.
6
7use serde::{Deserialize, Serialize};
8use serde_json::{Map, Value};
9
10/// A single Tiptap/ProseMirror node.
11///
12/// All structural fields are optional to faithfully distinguish "missing" from
13/// "empty" on roundtrip. Unknown top-level keys land in [`Node::extra`].
14#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
15pub struct Node {
16    /// Node type, e.g. `"doc"`, `"paragraph"`, `"text"`.
17    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
18    pub node_type: Option<String>,
19
20    /// Node attributes.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub attrs: Option<Map<String, Value>>,
23
24    /// Child nodes.
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub content: Option<Vec<Node>>,
27
28    /// Marks applied to this node (typically text nodes).
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub marks: Option<Vec<Mark>>,
31
32    /// Text payload (text nodes only).
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub text: Option<String>,
35
36    /// Any unknown/extra top-level fields, preserved verbatim.
37    #[serde(flatten)]
38    pub extra: Map<String, Value>,
39}
40
41/// A mark applied to a node (e.g. `bold`, `italic`, `link`).
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub struct Mark {
44    /// Mark type, e.g. `"bold"`.
45    #[serde(rename = "type")]
46    pub mark_type: String,
47
48    /// Mark attributes (e.g. `href` for a link).
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub attrs: Option<Map<String, Value>>,
51
52    /// Any unknown/extra fields, preserved verbatim.
53    #[serde(flatten)]
54    pub extra: Map<String, Value>,
55}
56
57impl Mark {
58    /// Create a mark of `mark_type` with no attrs.
59    ///
60    /// ```
61    /// use tiptap_rusty_parser::Mark;
62    /// let m = Mark::new("bold");
63    /// assert_eq!(m.mark_type, "bold");
64    /// ```
65    #[inline]
66    pub fn new(mark_type: impl Into<String>) -> Self {
67        Mark {
68            mark_type: mark_type.into(),
69            attrs: None,
70            extra: Map::new(),
71        }
72    }
73
74    /// Builder: set one attr.
75    #[inline]
76    pub fn attr(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
77        self.attrs
78            .get_or_insert_with(Map::new)
79            .insert(key.into(), value.into());
80        self
81    }
82}