Skip to main content

tablo_core/rich_text/
tool.rs

1//! [`Tool`]: one button of a rich text toolbar.
2
3/// One button of a rich text field's toolbar. A tool is also a grant: the field keeps only the
4/// elements and attributes its tools produce, so a tool left off the toolbar cannot be typed,
5/// is dropped from a paste, and is removed from a forged post.
6///
7/// ```rust
8/// # #[derive(Debug, Clone, toasty::Model)]
9/// # struct Post { #[key] #[auto] id: uuid::Uuid, body: String }
10/// use tablo_core::{Field, Tool};
11///
12/// Field::rich_text(Post::fields().body()).toolbar(&[
13///     &[Tool::Bold, Tool::Italic, Tool::Link],
14///     &[Tool::BulletList, Tool::OrderedList],
15/// ]);
16/// ```
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[non_exhaustive]
19pub enum Tool {
20    /// `<strong>`.
21    Bold,
22    /// `<em>`.
23    Italic,
24    /// `<u>`.
25    Underline,
26    /// `<s>`.
27    Strike,
28    /// Inline `<code>`.
29    Code,
30    /// `<a href>` to an `http`, `https` or `mailto` URL, or a relative one.
31    Link,
32    /// `<h1>`.
33    Heading1,
34    /// `<h2>`.
35    Heading2,
36    /// `<h3>`.
37    Heading3,
38    /// `<ul>` of `<li>`.
39    BulletList,
40    /// `<ol>` of `<li>`, which may set `start` and `type`.
41    OrderedList,
42    /// `<blockquote>`.
43    Blockquote,
44    /// `<pre><code>`. The editor records a block's language as a class, which cleaning drops,
45    /// and the allowlist names elements, not where they nest, so a forged post keeps inline
46    /// `<code>` too.
47    CodeBlock,
48    /// `<hr>`.
49    HorizontalRule,
50    /// Undoes the last change; produces nothing.
51    Undo,
52    /// Redoes the last undone change; produces nothing.
53    Redo,
54}
55
56impl Tool {
57    /// The toolbar a rich text field shows unless it declares its own.
58    pub const DEFAULT: &[&[Tool]] = &[
59        &[
60            Tool::Bold,
61            Tool::Italic,
62            Tool::Underline,
63            Tool::Strike,
64            Tool::Link,
65        ],
66        &[Tool::Heading2, Tool::Heading3],
67        &[
68            Tool::BulletList,
69            Tool::OrderedList,
70            Tool::Blockquote,
71            Tool::CodeBlock,
72        ],
73        &[Tool::Undo, Tool::Redo],
74    ];
75
76    /// Every tool, whose union a column renders with.
77    pub(crate) const ALL: &[Tool] = &[
78        Tool::Bold,
79        Tool::Italic,
80        Tool::Underline,
81        Tool::Strike,
82        Tool::Code,
83        Tool::Link,
84        Tool::Heading1,
85        Tool::Heading2,
86        Tool::Heading3,
87        Tool::BulletList,
88        Tool::OrderedList,
89        Tool::Blockquote,
90        Tool::CodeBlock,
91        Tool::HorizontalRule,
92        Tool::Undo,
93        Tool::Redo,
94    ];
95
96    /// The name `assets/rich_text.js` knows the tool by.
97    pub(crate) fn key(self) -> &'static str {
98        match self {
99            Tool::Bold => "bold",
100            Tool::Italic => "italic",
101            Tool::Underline => "underline",
102            Tool::Strike => "strike",
103            Tool::Code => "code",
104            Tool::Link => "link",
105            Tool::Heading1 => "heading-1",
106            Tool::Heading2 => "heading-2",
107            Tool::Heading3 => "heading-3",
108            Tool::BulletList => "bullet-list",
109            Tool::OrderedList => "ordered-list",
110            Tool::Blockquote => "blockquote",
111            Tool::CodeBlock => "code-block",
112            Tool::HorizontalRule => "horizontal-rule",
113            Tool::Undo => "undo",
114            Tool::Redo => "redo",
115        }
116    }
117
118    /// The message naming the button.
119    pub(crate) fn label(self) -> &'static str {
120        match self {
121            Tool::Bold => "tablo-rich-bold",
122            Tool::Italic => "tablo-rich-italic",
123            Tool::Underline => "tablo-rich-underline",
124            Tool::Strike => "tablo-rich-strike",
125            Tool::Code => "tablo-rich-code",
126            Tool::Link => "tablo-rich-link",
127            Tool::Heading1 => "tablo-rich-heading-1",
128            Tool::Heading2 => "tablo-rich-heading-2",
129            Tool::Heading3 => "tablo-rich-heading-3",
130            Tool::BulletList => "tablo-rich-bullet-list",
131            Tool::OrderedList => "tablo-rich-ordered-list",
132            Tool::Blockquote => "tablo-rich-blockquote",
133            Tool::CodeBlock => "tablo-rich-code-block",
134            Tool::HorizontalRule => "tablo-rich-horizontal-rule",
135            Tool::Undo => "tablo-rich-undo",
136            Tool::Redo => "tablo-rich-redo",
137        }
138    }
139
140    /// The elements the tool produces, each with the attributes it may carry.
141    pub(super) fn elements(self) -> &'static [(&'static str, &'static [&'static str])] {
142        match self {
143            Tool::Bold => &[("strong", &[])],
144            Tool::Italic => &[("em", &[])],
145            Tool::Underline => &[("u", &[])],
146            Tool::Strike => &[("s", &[])],
147            Tool::Code => &[("code", &[])],
148            Tool::Link => &[("a", &["href"])],
149            Tool::Heading1 => &[("h1", &[])],
150            Tool::Heading2 => &[("h2", &[])],
151            Tool::Heading3 => &[("h3", &[])],
152            Tool::BulletList => &[("ul", &[]), ("li", &[])],
153            Tool::OrderedList => &[("ol", &["start", "type"]), ("li", &[])],
154            Tool::Blockquote => &[("blockquote", &[])],
155            Tool::CodeBlock => &[("pre", &[]), ("code", &[])],
156            Tool::HorizontalRule => &[("hr", &[])],
157            Tool::Undo | Tool::Redo => &[],
158        }
159    }
160}