atproto blogging
1//! EditorAction conversion for JavaScript.
2
3use serde::{Deserialize, Serialize};
4use tsify_next::Tsify;
5use wasm_bindgen::prelude::*;
6use weaver_editor_core::{EditorAction, FormatAction, Range};
7
8/// JavaScript-friendly editor action.
9///
10/// Mirrors EditorAction from core but with JS-compatible types.
11/// Also includes FormatAction variants for extended formatting.
12#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
13#[tsify(into_wasm_abi, from_wasm_abi)]
14#[serde(tag = "type", rename_all = "camelCase")]
15pub enum JsEditorAction {
16 // Text insertion
17 Insert { text: String, start: usize, end: usize },
18 InsertLineBreak { start: usize, end: usize },
19 InsertParagraph { start: usize, end: usize },
20
21 // Deletion
22 DeleteBackward { start: usize, end: usize },
23 DeleteForward { start: usize, end: usize },
24 DeleteWordBackward { start: usize, end: usize },
25 DeleteWordForward { start: usize, end: usize },
26 DeleteToLineStart { start: usize, end: usize },
27 DeleteToLineEnd { start: usize, end: usize },
28 DeleteSoftLineBackward { start: usize, end: usize },
29 DeleteSoftLineForward { start: usize, end: usize },
30
31 // History
32 Undo,
33 Redo,
34
35 // Inline formatting (EditorAction variants)
36 ToggleBold,
37 ToggleItalic,
38 ToggleCode,
39 ToggleStrikethrough,
40 InsertLink,
41
42 // Extended formatting (FormatAction variants)
43 InsertImage,
44 InsertHeading { level: u8 },
45 ToggleBulletList,
46 ToggleNumberedList,
47 ToggleQuote,
48
49 // Clipboard
50 Cut,
51 Copy,
52 Paste { start: usize, end: usize },
53 CopyAsHtml,
54
55 // Selection
56 SelectAll,
57
58 // Cursor
59 MoveCursor { offset: usize },
60 ExtendSelection { offset: usize },
61}
62
63/// Result of converting JsEditorAction.
64pub enum ActionKind {
65 /// Standard EditorAction.
66 Editor(EditorAction),
67 /// FormatAction (needs apply_formatting).
68 Format(FormatAction),
69}
70
71impl JsEditorAction {
72 /// Convert to ActionKind (either EditorAction or FormatAction).
73 pub fn to_action_kind(&self) -> ActionKind {
74 match self {
75 // Text insertion
76 Self::Insert { text, start, end } => ActionKind::Editor(EditorAction::Insert {
77 text: text.clone(),
78 range: Range::new(*start, *end),
79 }),
80 Self::InsertLineBreak { start, end } => ActionKind::Editor(EditorAction::InsertLineBreak {
81 range: Range::new(*start, *end),
82 }),
83 Self::InsertParagraph { start, end } => ActionKind::Editor(EditorAction::InsertParagraph {
84 range: Range::new(*start, *end),
85 }),
86
87 // Deletion
88 Self::DeleteBackward { start, end } => ActionKind::Editor(EditorAction::DeleteBackward {
89 range: Range::new(*start, *end),
90 }),
91 Self::DeleteForward { start, end } => ActionKind::Editor(EditorAction::DeleteForward {
92 range: Range::new(*start, *end),
93 }),
94 Self::DeleteWordBackward { start, end } => ActionKind::Editor(EditorAction::DeleteWordBackward {
95 range: Range::new(*start, *end),
96 }),
97 Self::DeleteWordForward { start, end } => ActionKind::Editor(EditorAction::DeleteWordForward {
98 range: Range::new(*start, *end),
99 }),
100 Self::DeleteToLineStart { start, end } => ActionKind::Editor(EditorAction::DeleteToLineStart {
101 range: Range::new(*start, *end),
102 }),
103 Self::DeleteToLineEnd { start, end } => ActionKind::Editor(EditorAction::DeleteToLineEnd {
104 range: Range::new(*start, *end),
105 }),
106 Self::DeleteSoftLineBackward { start, end } => ActionKind::Editor(EditorAction::DeleteSoftLineBackward {
107 range: Range::new(*start, *end),
108 }),
109 Self::DeleteSoftLineForward { start, end } => ActionKind::Editor(EditorAction::DeleteSoftLineForward {
110 range: Range::new(*start, *end),
111 }),
112
113 // History
114 Self::Undo => ActionKind::Editor(EditorAction::Undo),
115 Self::Redo => ActionKind::Editor(EditorAction::Redo),
116
117 // Inline formatting (EditorAction)
118 Self::ToggleBold => ActionKind::Editor(EditorAction::ToggleBold),
119 Self::ToggleItalic => ActionKind::Editor(EditorAction::ToggleItalic),
120 Self::ToggleCode => ActionKind::Editor(EditorAction::ToggleCode),
121 Self::ToggleStrikethrough => ActionKind::Editor(EditorAction::ToggleStrikethrough),
122 Self::InsertLink => ActionKind::Editor(EditorAction::InsertLink),
123
124 // Extended formatting (FormatAction)
125 Self::InsertImage => ActionKind::Format(FormatAction::Image),
126 Self::InsertHeading { level } => ActionKind::Format(FormatAction::Heading(*level)),
127 Self::ToggleBulletList => ActionKind::Format(FormatAction::BulletList),
128 Self::ToggleNumberedList => ActionKind::Format(FormatAction::NumberedList),
129 Self::ToggleQuote => ActionKind::Format(FormatAction::Quote),
130
131 // Clipboard
132 Self::Cut => ActionKind::Editor(EditorAction::Cut),
133 Self::Copy => ActionKind::Editor(EditorAction::Copy),
134 Self::Paste { start, end } => ActionKind::Editor(EditorAction::Paste {
135 range: Range::new(*start, *end),
136 }),
137 Self::CopyAsHtml => ActionKind::Editor(EditorAction::CopyAsHtml),
138
139 // Selection
140 Self::SelectAll => ActionKind::Editor(EditorAction::SelectAll),
141
142 // Cursor
143 Self::MoveCursor { offset } => ActionKind::Editor(EditorAction::MoveCursor { offset: *offset }),
144 Self::ExtendSelection { offset } => ActionKind::Editor(EditorAction::ExtendSelection { offset: *offset }),
145 }
146 }
147}
148
149/// Parse a JsValue into JsEditorAction.
150pub fn parse_action(value: JsValue) -> Result<JsEditorAction, JsError> {
151 serde_wasm_bindgen::from_value(value)
152 .map_err(|e| JsError::new(&format!("Invalid action: {}", e)))
153}