1use crate::{
2 container::richtext::richtext_state::{unicode_to_utf8_index, utf16_to_utf8_index},
3 delta::{Delta, DeltaItem, Meta},
4 event::{Diff, Index, Path, TextDiff, TextDiffItem, TextMeta},
5 handler::ValueOrHandler,
6 utils::string_slice::StringSlice,
7};
8use generic_btree::rle::HasLength;
9use loro_common::ContainerType;
10pub use loro_common::LoroValue;
11
12pub trait ToJson {
14 fn to_json_value(&self) -> serde_json::Value;
15 fn to_json(&self) -> String {
16 self.to_json_value().to_string()
17 }
18 fn to_json_pretty(&self) -> String {
19 serde_json::to_string_pretty(&self.to_json_value()).unwrap()
20 }
21 fn from_json(s: &str) -> Self;
22}
23
24impl ToJson for LoroValue {
25 fn to_json_value(&self) -> serde_json::Value {
26 serde_json::to_value(self).unwrap()
27 }
28
29 fn to_json(&self) -> String {
30 serde_json::to_string(self).unwrap()
31 }
32
33 fn to_json_pretty(&self) -> String {
34 serde_json::to_string_pretty(self).unwrap()
35 }
36
37 #[allow(unused)]
38 fn from_json(s: &str) -> Self {
39 serde_json::from_str(s).unwrap()
40 }
41}
42
43impl ToJson for DeltaItem<StringSlice, TextMeta> {
44 fn to_json_value(&self) -> serde_json::Value {
45 match self {
46 DeltaItem::Retain {
47 retain: len,
48 attributes: meta,
49 } => {
50 let mut map = serde_json::Map::new();
51 map.insert("retain".into(), serde_json::to_value(len).unwrap());
52 if !meta.is_empty() {
53 map.insert("attributes".into(), meta.to_json_value());
54 }
55 serde_json::Value::Object(map)
56 }
57 DeltaItem::Insert {
58 insert: value,
59 attributes: meta,
60 } => {
61 let mut map = serde_json::Map::new();
62 map.insert("insert".into(), serde_json::to_value(value).unwrap());
63 if !meta.is_empty() {
64 map.insert("attributes".into(), meta.to_json_value());
65 }
66 serde_json::Value::Object(map)
67 }
68 DeltaItem::Delete {
69 delete: len,
70 attributes: _,
71 } => {
72 let mut map = serde_json::Map::new();
73 map.insert("delete".into(), serde_json::to_value(len).unwrap());
74 serde_json::Value::Object(map)
75 }
76 }
77 }
78
79 fn from_json(s: &str) -> Self {
80 let map: serde_json::Map<String, serde_json::Value> = serde_json::from_str(s).unwrap();
81 if map.contains_key("retain") {
82 let len = map["retain"].as_u64().unwrap();
83 let meta = if let Some(meta) = map.get("attributes") {
84 TextMeta::from_json(meta.to_string().as_str())
85 } else {
86 TextMeta::default()
87 };
88 DeltaItem::Retain {
89 retain: len as usize,
90 attributes: meta,
91 }
92 } else if map.contains_key("insert") {
93 let value = map["insert"].as_str().unwrap().to_string().into();
94 let meta = if let Some(meta) = map.get("attributes") {
95 TextMeta::from_json(meta.to_string().as_str())
96 } else {
97 TextMeta::default()
98 };
99 DeltaItem::Insert {
100 insert: value,
101 attributes: meta,
102 }
103 } else if map.contains_key("delete") {
104 let len = map["delete"].as_u64().unwrap();
105 DeltaItem::Delete {
106 delete: len as usize,
107 attributes: Default::default(),
108 }
109 } else {
110 panic!("Invalid delta item: {}", s);
111 }
112 }
113}
114
115fn diff_item_to_json_value(item: &TextDiffItem) -> (serde_json::Value, Option<serde_json::Value>) {
116 match item {
117 loro_delta::DeltaItem::Retain { len, attr } => {
118 let mut map = serde_json::Map::new();
119 map.insert("retain".into(), serde_json::to_value(len).unwrap());
120 if !attr.is_empty() {
121 map.insert("attributes".into(), attr.to_json_value());
122 }
123 (serde_json::Value::Object(map), None)
124 }
125 loro_delta::DeltaItem::Replace {
126 value,
127 attr,
128 delete,
129 } => {
130 let mut a = None;
131 let mut b = None;
132 if value.rle_len() > 0 {
133 let mut map = serde_json::Map::new();
134 map.insert("insert".into(), serde_json::to_value(value).unwrap());
135 if !attr.is_empty() {
136 map.insert("attributes".into(), attr.to_json_value());
137 }
138 a = Some(serde_json::Value::Object(map));
139 }
140 if *delete > 0 {
141 let mut map = serde_json::Map::new();
142 map.insert("delete".into(), serde_json::to_value(delete).unwrap());
143 b = Some(serde_json::Value::Object(map));
144 }
145
146 if a.is_none() {
147 a = std::mem::take(&mut b);
148 if a.is_none() {
149 let mut map = serde_json::Map::new();
150 map.insert("retain".into(), serde_json::to_value(0).unwrap());
151 a = Some(serde_json::Value::Object(map));
152 }
153 }
154 (a.unwrap(), b)
155 }
156 }
157}
158
159fn diff_item_from_json(v: serde_json::Value) -> TextDiffItem {
160 let serde_json::Value::Object(map) = v else {
161 panic!("Invalid delta item: {:?}", v);
162 };
163 if map.contains_key("retain") {
164 let len = map["retain"].as_u64().unwrap();
165 let meta = if let Some(meta) = map.get("attributes") {
166 TextMeta::from_json(meta.to_string().as_str())
167 } else {
168 TextMeta::default()
169 };
170 TextDiffItem::Retain {
171 len: len as usize,
172 attr: meta,
173 }
174 } else if map.contains_key("insert") {
175 let value = map["insert"].as_str().unwrap().to_string().into();
176 let meta = if let Some(meta) = map.get("attributes") {
177 TextMeta::from_json(meta.to_string().as_str())
178 } else {
179 TextMeta::default()
180 };
181 TextDiffItem::Replace {
182 value,
183 attr: meta,
184 delete: 0,
185 }
186 } else if map.contains_key("delete") {
187 let len = map["delete"].as_u64().unwrap();
188 TextDiffItem::new_delete(len as usize)
189 } else {
190 panic!("Invalid delta item: {:?}", map);
191 }
192}
193
194impl ToJson for TextDiff {
195 fn to_json_value(&self) -> serde_json::Value {
196 let mut vec = Vec::new();
197 for item in self.iter() {
198 let (a, b) = diff_item_to_json_value(item);
199 vec.push(a);
200 if let Some(b) = b {
201 vec.push(b);
202 }
203 }
204 serde_json::Value::Array(vec)
205 }
206
207 fn from_json(s: &str) -> Self {
208 let vec: Vec<serde_json::Value> = serde_json::from_str(s).unwrap();
209 let mut ans = TextDiff::new();
210 for item in vec.into_iter() {
211 ans.push(diff_item_from_json(item));
212 }
213 ans
214 }
215}
216
217impl ToJson for Delta<StringSlice, TextMeta> {
218 fn to_json_value(&self) -> serde_json::Value {
219 let mut vec = Vec::new();
220 for item in self.iter() {
221 vec.push(item.to_json_value());
222 }
223 serde_json::Value::Array(vec)
224 }
225
226 fn from_json(s: &str) -> Self {
227 let vec: Vec<serde_json::Value> = serde_json::from_str(s).unwrap();
228 let mut ans = Delta::new();
229 for item in vec.into_iter() {
230 ans.push(DeltaItem::from_json(item.to_string().as_str()));
231 }
232 ans
233 }
234}
235
236#[derive(Debug, PartialEq, Eq)]
237enum TypeHint {
238 Map,
239 Text,
240 List,
241 Tree,
242 #[cfg(feature = "counter")]
243 Counter,
244}
245
246pub trait ApplyDiff {
247 fn apply_diff_shallow(&mut self, diff: &[Diff]);
248 fn apply_diff(&mut self, diff: &[Diff]);
249 fn apply(&mut self, path: &Path, diff: &[Diff]);
250}
251
252impl ApplyDiff for LoroValue {
253 fn apply_diff_shallow(&mut self, diff: &[Diff]) {
254 match self {
255 LoroValue::String(value) => {
256 let mut s = value.to_string();
257 for item in diff.iter() {
258 let delta = item.as_text().unwrap();
259 let mut index = 0;
260 for delta_item in delta.iter() {
261 match delta_item {
262 loro_delta::DeltaItem::Retain { len, attr: _ } => {
263 index += len;
264 }
265 loro_delta::DeltaItem::Replace {
266 value,
267 attr: _,
268 delete,
269 } => {
270 let (start, end) = if cfg!(feature = "wasm") {
271 (
272 utf16_to_utf8_index(&s, index).unwrap(),
273 utf16_to_utf8_index(&s, index + *delete).unwrap(),
274 )
275 } else {
276 (
277 unicode_to_utf8_index(&s, index).unwrap(),
278 unicode_to_utf8_index(&s, index + *delete).unwrap(),
279 )
280 };
281 s.replace_range(start..end, value.as_str());
282 index += value.len_bytes();
283 }
284 }
285 }
286 }
287 *value = s.into()
288 }
289 LoroValue::List(seq) => {
290 let is_tree = matches!(diff.first(), Some(Diff::Tree(_)));
291 if !is_tree {
292 let seq = seq.make_mut();
293 for item in diff.iter() {
294 let delta = item.as_list().unwrap();
295 let mut index = 0;
296 for delta_item in delta.iter() {
297 match delta_item {
298 loro_delta::DeltaItem::Retain { len, attr: _ } => {
299 index += len;
300 }
301 loro_delta::DeltaItem::Replace {
302 value,
303 attr: _,
304 delete,
305 } => {
306 let len = value.len();
307 seq.splice(
308 index..index + delete,
309 value.iter().map(|x| x.to_value()),
310 );
311 index += len;
312 }
313 }
314 }
315 }
316 } else {
317 unimplemented!()
328 }
329 }
330 LoroValue::Map(map) => {
331 for item in diff.iter() {
332 match item {
333 Diff::Map(diff) => {
334 let map = map.make_mut();
335 for (key, value) in diff.updated.iter() {
336 match &value.value {
337 Some(value) => {
338 map.insert(key.to_string(), value.to_value());
339 }
340 None => {
341 map.remove(&key.to_string());
342 }
343 }
344 }
345 }
346 _ => unreachable!(),
347 }
348 }
349 }
350 _ => unreachable!(),
351 }
352 }
353
354 fn apply_diff(&mut self, diff: &[Diff]) {
355 match self {
356 LoroValue::String(value) => {
357 let mut s = value.to_string();
358 for item in diff.iter() {
359 let delta = item.as_text().unwrap();
360 let mut index = 0;
361 for delta_item in delta.iter() {
362 match delta_item {
363 loro_delta::DeltaItem::Retain { len, attr: _ } => {
364 index += len;
365 }
366 loro_delta::DeltaItem::Replace {
367 value,
368 attr: _,
369 delete,
370 } => {
371 s.replace_range(index..index + *delete, value.as_str());
372 index += value.len_bytes();
373 }
374 }
375 }
376 }
377 *value = s.into();
378 }
379 LoroValue::List(seq) => {
380 let is_tree = matches!(diff.first(), Some(Diff::Tree(_)));
381 if !is_tree {
382 let seq = seq.make_mut();
383 for item in diff.iter() {
384 let delta = item.as_list().unwrap();
385 let mut index = 0;
386 for delta_item in delta.iter() {
387 match delta_item {
388 loro_delta::DeltaItem::Retain { len, .. } => {
389 index += len;
390 }
391 loro_delta::DeltaItem::Replace {
392 value,
393 attr: _,
394 delete,
395 } => {
396 let value_iter = value.iter().map(unresolved_to_collection);
397 seq.splice(index..index + *delete, value_iter);
398 index += value.len();
399 }
400 }
401 }
402 }
403 } else {
404 unimplemented!()
415 }
416 }
417 LoroValue::Map(map) => {
418 for item in diff.iter() {
419 match item {
420 Diff::Map(diff) => {
421 let map = map.make_mut();
422 for (key, value) in diff.updated.iter() {
423 match &value.value {
424 Some(value) => {
425 map.insert(
426 key.to_string(),
427 unresolved_to_collection(value),
428 );
429 }
430 None => {
431 map.remove(&key.to_string());
432 }
433 }
434 }
435 }
436 _ => unreachable!(),
437 }
438 }
439 }
440 _ => unreachable!(),
441 }
442 }
443
444 fn apply(&mut self, path: &Path, diff: &[Diff]) {
445 if diff.is_empty() {
446 return;
447 }
448
449 let hint = match diff[0] {
450 Diff::List(_) => TypeHint::List,
451 Diff::Text(_) => TypeHint::Text,
452 Diff::Map(_) => TypeHint::Map,
453 Diff::Tree(_) => TypeHint::Tree,
454 #[cfg(feature = "counter")]
455 Diff::Counter(_) => TypeHint::Counter,
456 Diff::Unknown => unreachable!(),
457 };
458 let value = {
459 let mut hints = Vec::with_capacity(path.len());
460 for item in path.iter().skip(1) {
461 match item {
462 Index::Key(_) => hints.push(TypeHint::Map),
463 Index::Seq(_) => hints.push(TypeHint::List),
464 Index::Node(_) => hints.push(TypeHint::Tree),
465 }
466 }
467
468 hints.push(hint);
469 let mut value: &mut LoroValue = self;
470 for (item, hint) in path.iter().zip(hints.iter()) {
471 match item {
472 Index::Key(key) => {
473 let m = value.as_map_mut().unwrap();
474 let map = m.make_mut();
475 value = map.entry(key.to_string()).or_insert_with(|| match hint {
476 TypeHint::Map => LoroValue::Map(Default::default()),
477 TypeHint::Text => LoroValue::String(Default::default()),
478 TypeHint::List => LoroValue::List(Default::default()),
479 TypeHint::Tree => LoroValue::List(Default::default()),
480 #[cfg(feature = "counter")]
481 TypeHint::Counter => LoroValue::Double(0.),
482 })
483 }
484 Index::Seq(index) => {
485 let l = value.as_list_mut().unwrap();
486 let list = l.make_mut();
487 value = list.get_mut(*index).unwrap();
488 }
489 Index::Node(tree_id) => {
490 let l = value.as_list_mut().unwrap();
491 let list = l.make_mut();
492 let Some(map) = list.iter_mut().find(|x| {
493 let id = x.as_map().unwrap().get("id").unwrap().as_string().unwrap();
494 id.as_ref() == tree_id.to_string()
495 }) else {
496 return;
498 };
499 let map_mut = map.as_map_mut().unwrap().make_mut();
500 let meta = map_mut.get_mut("meta").unwrap();
501 if meta.is_container() {
502 *meta = ContainerType::Map.default_value();
503 }
504 value = meta
505 }
506 }
507 }
508 value
509 };
510 value.apply_diff(diff);
511 }
512}
513
514pub(crate) fn unresolved_to_collection(v: &ValueOrHandler) -> LoroValue {
515 match v {
516 ValueOrHandler::Value(v) => v.clone(),
517 ValueOrHandler::Handler(c) => c.c_type().default_value(),
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use loro_common::IdLp;
524 use loro_delta::{array_vec::ArrayVec, DeltaItem as EventDeltaItem};
525 use rustc_hash::FxHashMap;
526
527 use crate::{
528 delta::{Delta, DeltaItem},
529 event::{ListDeltaMeta, ListDiff, ListDiffInsertItem},
530 handler::{Handler, HandlerTrait},
531 LoroDoc,
532 };
533
534 use super::*;
535
536 fn text_diff(items: Vec<TextDiffItem>) -> TextDiff {
537 let mut diff = TextDiff::new();
538 for item in items {
539 diff.push(item);
540 }
541 diff
542 }
543
544 fn list_values(values: Vec<LoroValue>) -> ListDiffInsertItem {
545 let mut array = ArrayVec::new();
546 for value in values {
547 array.push(ValueOrHandler::Value(value)).unwrap();
548 }
549 array
550 }
551
552 fn list_diff(items: Vec<crate::event::ListDiffItem>) -> ListDiff {
553 let mut diff = ListDiff::new();
554 for item in items {
555 diff.push(item);
556 }
557 diff
558 }
559
560 fn map_value(value: Option<LoroValue>, lamport: u32) -> crate::delta::ResolvedMapValue {
561 crate::delta::ResolvedMapValue {
562 value: value.map(ValueOrHandler::Value),
563 idlp: IdLp::new(1, lamport),
564 }
565 }
566
567 #[test]
568 fn text_delta_json_roundtrip_preserves_attributes_and_operation_order() {
569 let mut attributes = FxHashMap::default();
570 attributes.insert("bold".to_string(), LoroValue::Bool(true));
571 let meta = TextMeta(attributes);
572
573 let item = DeltaItem::Insert {
574 insert: StringSlice::from("hi"),
575 attributes: meta.clone(),
576 };
577 assert_eq!(
578 item.to_json_value(),
579 serde_json::json!({"insert": "hi", "attributes": {"bold": true}})
580 );
581 assert_eq!(
582 DeltaItem::<StringSlice, TextMeta>::from_json(
583 r#"{"insert":"hi","attributes":{"bold":true}}"#
584 ),
585 item
586 );
587
588 let mut delta = Delta::<StringSlice, TextMeta>::new();
589 delta.push(DeltaItem::Retain {
590 retain: 2,
591 attributes: meta.clone(),
592 });
593 delta.push(DeltaItem::Insert {
594 insert: StringSlice::from("!"),
595 attributes: TextMeta::default(),
596 });
597 delta.push(DeltaItem::Delete {
598 delete: 1,
599 attributes: TextMeta::default(),
600 });
601
602 let json = delta.to_json();
603 assert_eq!(
604 serde_json::from_str::<serde_json::Value>(&json).unwrap(),
605 serde_json::json!([
606 {"retain": 2, "attributes": {"bold": true}},
607 {"insert": "!"},
608 {"delete": 1}
609 ])
610 );
611 assert_eq!(Delta::<StringSlice, TextMeta>::from_json(&json), delta);
612 }
613
614 #[test]
615 fn text_diff_json_splits_replace_with_insert_and_delete_entries() {
616 let diff = text_diff(vec![TextDiffItem::Replace {
617 value: StringSlice::from("new"),
618 attr: TextMeta::default(),
619 delete: 2,
620 }]);
621
622 assert_eq!(
623 diff.to_json_value(),
624 serde_json::json!([{"insert": "new"}, {"delete": 2}])
625 );
626 assert_eq!(
627 TextDiff::from_json(r#"[{"retain":1},{"insert":"x"},{"delete":2}]"#).to_json_value(),
628 serde_json::json!([{"retain": 1}, {"insert": "x"}, {"delete": 2}])
629 );
630
631 let (empty_replace, follow_up) = diff_item_to_json_value(&TextDiffItem::Replace {
632 value: StringSlice::from(""),
633 attr: TextMeta::default(),
634 delete: 0,
635 });
636 assert_eq!(empty_replace, serde_json::json!({"retain": 0}));
637 assert_eq!(follow_up, None);
638 }
639
640 #[test]
641 fn apply_diff_updates_string_list_and_map_values() {
642 let mut text = LoroValue::String("aéz".into());
643 text.apply_diff_shallow(&[Diff::Text(text_diff(vec![
644 TextDiffItem::Retain {
645 len: 1,
646 attr: TextMeta::default(),
647 },
648 TextDiffItem::Replace {
649 value: StringSlice::from("bc"),
650 attr: TextMeta::default(),
651 delete: 1,
652 },
653 ]))]);
654 assert_eq!(text, LoroValue::String("abcz".into()));
655
656 let mut list =
657 LoroValue::List(vec![LoroValue::I64(1), LoroValue::I64(2), LoroValue::I64(3)].into());
658 list.apply_diff(&[Diff::List(list_diff(vec![
659 EventDeltaItem::Retain {
660 len: 1,
661 attr: ListDeltaMeta::default(),
662 },
663 EventDeltaItem::Replace {
664 value: list_values(vec![LoroValue::I64(20), LoroValue::I64(21)]),
665 attr: ListDeltaMeta::default(),
666 delete: 1,
667 },
668 ]))]);
669 assert_eq!(
670 list,
671 LoroValue::List(
672 vec![
673 LoroValue::I64(1),
674 LoroValue::I64(20),
675 LoroValue::I64(21),
676 LoroValue::I64(3),
677 ]
678 .into()
679 )
680 );
681
682 let mut map = LoroValue::Map(
683 FxHashMap::from_iter([
684 ("keep".to_string(), LoroValue::I64(1)),
685 ("remove".to_string(), LoroValue::I64(2)),
686 ])
687 .into(),
688 );
689 map.apply_diff(&[Diff::Map(
690 crate::delta::ResolvedMapDelta::new()
691 .with_entry("add".into(), map_value(Some(LoroValue::I64(3)), 1))
692 .with_entry("remove".into(), map_value(None, 2)),
693 )]);
694 let map = map.as_map().unwrap();
695 assert_eq!(map.get("keep"), Some(&LoroValue::I64(1)));
696 assert_eq!(map.get("add"), Some(&LoroValue::I64(3)));
697 assert_eq!(map.get("remove"), None);
698 }
699
700 #[test]
701 fn apply_creates_missing_nested_values_from_path_and_diff_hint() {
702 let mut root = LoroValue::Map(Default::default());
703 let mut title_path = Path::new();
704 title_path.push(Index::Key("doc".into()));
705 title_path.push(Index::Key("title".into()));
706 root.apply(
707 &title_path,
708 &[Diff::Text(text_diff(vec![TextDiffItem::Replace {
709 value: StringSlice::from("Loro"),
710 attr: TextMeta::default(),
711 delete: 0,
712 }]))],
713 );
714
715 assert_eq!(
716 root.to_json_value(),
717 serde_json::json!({"doc": {"title": "Loro"}})
718 );
719
720 let mut tags_path = Path::new();
721 tags_path.push(Index::Key("doc".into()));
722 tags_path.push(Index::Key("tags".into()));
723 root.apply(
724 &tags_path,
725 &[Diff::List(list_diff(vec![EventDeltaItem::Replace {
726 value: list_values(vec![
727 LoroValue::String("crdt".into()),
728 LoroValue::String("rust".into()),
729 ]),
730 attr: ListDeltaMeta::default(),
731 delete: 0,
732 }]))],
733 );
734 assert_eq!(
735 root.to_json_value(),
736 serde_json::json!({"doc": {"title": "Loro", "tags": ["crdt", "rust"]}})
737 );
738 }
739
740 #[test]
741 fn unresolved_container_handlers_apply_as_default_collection_values() {
742 let doc = LoroDoc::new_auto_commit();
743 let map_handler = Handler::new_unattached(ContainerType::Map);
744 let text_handler = Handler::new_unattached(ContainerType::Text);
745 let attached_list = doc.get_list("list").to_handler();
746
747 assert_eq!(
748 unresolved_to_collection(&ValueOrHandler::Handler(map_handler)),
749 LoroValue::Map(Default::default())
750 );
751 assert_eq!(
752 unresolved_to_collection(&ValueOrHandler::Handler(text_handler)),
753 LoroValue::String(Default::default())
754 );
755 assert_eq!(
756 unresolved_to_collection(&ValueOrHandler::Handler(attached_list)),
757 LoroValue::List(Default::default())
758 );
759 assert_eq!(
760 unresolved_to_collection(&ValueOrHandler::Value(LoroValue::Bool(true))),
761 LoroValue::Bool(true)
762 );
763 }
764}
765
766#[cfg(feature = "wasm")]
767pub mod wasm {
768 use crate::{
769 delta::{Delta, DeltaItem, Meta, StyleMeta, TreeDiff, TreeDiffItem, TreeExternalDiff},
770 event::{Index, TextDiff, TextDiffItem, TextMeta},
771 utils::string_slice::StringSlice,
772 TreeParentId,
773 };
774 use fractional_index::FractionalIndex;
775 use generic_btree::rle::HasLength;
776 use js_sys::{Array, Object};
777 use loro_common::{LoroValue, TreeID};
778 use wasm_bindgen::{__rt::IntoJsResult, JsCast, JsValue};
779
780 impl From<Index> for JsValue {
781 fn from(value: Index) -> Self {
782 match value {
783 Index::Key(key) => JsValue::from_str(&key),
784 Index::Seq(num) => JsValue::from_f64(num as f64),
785 Index::Node(node) => node.into(),
786 }
787 }
788 }
789
790 impl From<&TreeDiff> for JsValue {
791 fn from(value: &TreeDiff) -> Self {
792 let array = Array::new();
793 for diff in value.diff.iter() {
794 let obj = Object::new();
795 js_sys::Reflect::set(&obj, &"target".into(), &diff.target.into()).unwrap();
796 match &diff.action {
797 TreeExternalDiff::Create {
798 parent,
799 index,
800 position,
801 } => {
802 js_sys::Reflect::set(&obj, &"action".into(), &"create".into()).unwrap();
803 js_sys::Reflect::set(
804 &obj,
805 &"parent".into(),
806 &JsValue::from(parent.tree_id()),
807 )
808 .unwrap();
809 js_sys::Reflect::set(&obj, &"index".into(), &(*index).into()).unwrap();
810 js_sys::Reflect::set(
811 &obj,
812 &"fractionalIndex".into(),
813 &position.to_string().into(),
814 )
815 .unwrap();
816 }
817 TreeExternalDiff::Delete {
818 old_parent,
819 old_index,
820 } => {
821 js_sys::Reflect::set(&obj, &"action".into(), &"delete".into()).unwrap();
822 js_sys::Reflect::set(
823 &obj,
824 &"oldParent".into(),
825 &JsValue::from(old_parent.tree_id()),
826 )
827 .unwrap();
828 js_sys::Reflect::set(&obj, &"oldIndex".into(), &(*old_index).into())
829 .unwrap();
830 }
831 TreeExternalDiff::Move {
832 parent,
833 index,
834 position,
835 old_parent,
836 old_index,
837 } => {
838 js_sys::Reflect::set(&obj, &"action".into(), &"move".into()).unwrap();
839 js_sys::Reflect::set(
840 &obj,
841 &"parent".into(),
842 &JsValue::from(parent.tree_id()),
843 )
844 .unwrap();
845 js_sys::Reflect::set(&obj, &"index".into(), &(*index).into()).unwrap();
846 js_sys::Reflect::set(
847 &obj,
848 &"fractionalIndex".into(),
849 &position.to_string().into(),
850 )
851 .unwrap();
852 js_sys::Reflect::set(
853 &obj,
854 &"oldParent".into(),
855 &JsValue::from(old_parent.tree_id()),
856 )
857 .unwrap();
858 js_sys::Reflect::set(&obj, &"oldIndex".into(), &(*old_index).into())
859 .unwrap();
860 }
861 }
862 array.push(&obj);
863 }
864 array.into_js_result().unwrap()
865 }
866 }
867
868 impl TryFrom<&JsValue> for TreeDiff {
869 type Error = String;
870
871 fn try_from(value: &JsValue) -> Result<Self, Self::Error> {
872 if !value.is_array() {
873 return Err("Expected an array".to_string());
874 }
875
876 let array = js_sys::Array::from(value);
877 let mut diff = Vec::new();
878
879 for i in 0..array.length() {
880 let item = array.get(i);
881 if !item.is_object() {
882 return Err(format!("Item at index {i} is not an object"));
883 }
884
885 let obj = js_sys::Object::from(item);
886 let target = js_sys::Reflect::get(&obj, &"target".into())
887 .map_err(|e| format!("Failed to get target: {:?}", e))?;
888 let target = TreeID::try_from(target)
889 .map_err(|e| format!("Failed to parse target: {:?}", e))?;
890
891 let action = js_sys::Reflect::get(&obj, &"action".into())
892 .map_err(|e| format!("Failed to get action: {:?}", e))?;
893 let action = action
894 .as_string()
895 .ok_or_else(|| "action is not a string".to_string())?;
896
897 let action = match action.as_str() {
898 "create" => {
899 let parent = js_sys::Reflect::get(&obj, &"parent".into())
900 .map_err(|e| format!("Failed to get parent: {:?}", e))?;
901 let parent_id = if parent.is_null() || parent.is_undefined() {
902 None
903 } else {
904 Some(
905 TreeID::try_from(parent)
906 .map_err(|e| format!("Failed to parse parent: {:?}", e))?,
907 )
908 };
909 let parent = TreeParentId::from(parent_id);
910 let index = js_sys::Reflect::get(&obj, &"index".into())
911 .map_err(|e| format!("Failed to get index: {:?}", e))?;
912 let index = index
913 .as_f64()
914 .ok_or_else(|| "index is not a number".to_string())?
915 as usize;
916
917 let position = js_sys::Reflect::get(&obj, &"fractionalIndex".into())
918 .map_err(|e| format!("Failed to get fractionalIndex: {:?}", e))?;
919 let position = position
920 .as_string()
921 .ok_or_else(|| "fractionalIndex is not a string".to_string())?;
922 let position = FractionalIndex::from_hex_string(position);
923
924 TreeExternalDiff::Create {
925 parent,
926 index,
927 position,
928 }
929 }
930 "move" => {
931 let parent = js_sys::Reflect::get(&obj, &"parent".into())
932 .map_err(|e| format!("Failed to get parent: {:?}", e))?;
933 let parent_id = if parent.is_null() || parent.is_undefined() {
934 None
935 } else {
936 Some(
937 TreeID::try_from(parent)
938 .map_err(|e| format!("Failed to parse parent: {:?}", e))?,
939 )
940 };
941 let parent = TreeParentId::from(parent_id);
942
943 let index = js_sys::Reflect::get(&obj, &"index".into())
944 .map_err(|e| format!("Failed to get index: {:?}", e))?;
945 let index = index
946 .as_f64()
947 .ok_or_else(|| "index is not a number".to_string())?
948 as usize;
949
950 let position = js_sys::Reflect::get(&obj, &"fractionalIndex".into())
951 .map_err(|e| format!("Failed to get fractionalIndex: {:?}", e))?;
952 let position = position
953 .as_string()
954 .ok_or_else(|| "fractionalIndex is not a string".to_string())?;
955 let position = FractionalIndex::from_hex_string(position);
956
957 let old_parent = js_sys::Reflect::get(&obj, &"oldParent".into())
958 .map_err(|e| format!("Failed to get oldParent: {:?}", e))?;
959 let old_parent_id = if old_parent.is_null() || old_parent.is_undefined() {
960 None
961 } else {
962 Some(
963 TreeID::try_from(old_parent)
964 .map_err(|e| format!("Failed to parse oldParent: {:?}", e))?,
965 )
966 };
967 let old_parent = TreeParentId::from(old_parent_id);
968
969 let old_index = js_sys::Reflect::get(&obj, &"oldIndex".into())
970 .map_err(|e| format!("Failed to get oldIndex: {:?}", e))?;
971 let old_index = old_index
972 .as_f64()
973 .ok_or_else(|| "oldIndex is not a number".to_string())?
974 as usize;
975
976 TreeExternalDiff::Move {
977 parent,
978 index,
979 position,
980 old_parent,
981 old_index,
982 }
983 }
984 "delete" => {
985 let old_parent = js_sys::Reflect::get(&obj, &"oldParent".into())
986 .map_err(|e| format!("Failed to get oldParent: {:?}", e))?;
987 let old_parent_id = if old_parent.is_null() || old_parent.is_undefined() {
988 None
989 } else {
990 Some(
991 TreeID::try_from(old_parent)
992 .map_err(|e| format!("Failed to parse oldParent: {:?}", e))?,
993 )
994 };
995 let old_parent = TreeParentId::from(old_parent_id);
996
997 let old_index = js_sys::Reflect::get(&obj, &"oldIndex".into())
998 .map_err(|e| format!("Failed to get oldIndex: {:?}", e))?;
999 let old_index = old_index
1000 .as_f64()
1001 .ok_or_else(|| "oldIndex is not a number".to_string())?
1002 as usize;
1003
1004 TreeExternalDiff::Delete {
1005 old_parent,
1006 old_index,
1007 }
1008 }
1009 action => Err(format!("Unknown tree diff action: {action}"))?,
1010 };
1011
1012 diff.push(TreeDiffItem { target, action });
1013 }
1014
1015 Ok(TreeDiff { diff })
1016 }
1017 }
1018
1019 impl From<&Delta<StringSlice, StyleMeta>> for JsValue {
1020 fn from(value: &Delta<StringSlice, StyleMeta>) -> Self {
1021 let arr = Array::new_with_length(value.len() as u32);
1022 for (i, v) in value.iter().enumerate() {
1023 arr.set(i as u32, JsValue::from(v.clone()));
1024 }
1025
1026 arr.into_js_result().unwrap()
1027 }
1028 }
1029
1030 impl From<DeltaItem<StringSlice, StyleMeta>> for JsValue {
1031 fn from(value: DeltaItem<StringSlice, StyleMeta>) -> Self {
1032 let obj = Object::new();
1033 match value {
1034 DeltaItem::Retain {
1035 retain: len,
1036 attributes: meta,
1037 } => {
1038 js_sys::Reflect::set(
1039 &obj,
1040 &JsValue::from_str("retain"),
1041 &JsValue::from_f64(len as f64),
1042 )
1043 .unwrap();
1044 if !meta.is_empty() {
1045 js_sys::Reflect::set(
1046 &obj,
1047 &JsValue::from_str("attributes"),
1048 &JsValue::from(&meta),
1049 )
1050 .unwrap();
1051 }
1052 }
1053 DeltaItem::Insert {
1054 insert: value,
1055 attributes: meta,
1056 } => {
1057 js_sys::Reflect::set(
1058 &obj,
1059 &JsValue::from_str("insert"),
1060 &JsValue::from_str(value.as_str()),
1061 )
1062 .unwrap();
1063 if !meta.is_empty() {
1064 js_sys::Reflect::set(
1065 &obj,
1066 &JsValue::from_str("attributes"),
1067 &JsValue::from(&meta),
1068 )
1069 .unwrap();
1070 }
1071 }
1072 DeltaItem::Delete {
1073 delete: len,
1074 attributes: _,
1075 } => {
1076 js_sys::Reflect::set(
1077 &obj,
1078 &JsValue::from_str("delete"),
1079 &JsValue::from_f64(len as f64),
1080 )
1081 .unwrap();
1082 }
1083 }
1084
1085 obj.into_js_result().unwrap()
1086 }
1087 }
1088
1089 pub fn text_diff_to_js_value(diff: &TextDiff) -> JsValue {
1090 let arr = Array::new();
1091 let mut i = 0;
1092 for v in diff.iter() {
1093 let (a, b) = text_diff_item_to_js_value(v);
1094 arr.set(i as u32, a);
1095 i += 1;
1096 if let Some(b) = b {
1097 arr.set(i as u32, b);
1098 i += 1;
1099 }
1100 }
1101
1102 arr.into_js_result().unwrap()
1103 }
1104
1105 fn text_diff_item_to_js_value(value: &TextDiffItem) -> (JsValue, Option<JsValue>) {
1106 match value {
1107 loro_delta::DeltaItem::Retain { len, attr } => {
1108 let obj = Object::new();
1109 js_sys::Reflect::set(
1110 &obj,
1111 &JsValue::from_str("retain"),
1112 &JsValue::from_f64(*len as f64),
1113 )
1114 .unwrap();
1115 if !attr.is_empty() {
1116 js_sys::Reflect::set(
1117 &obj,
1118 &JsValue::from_str("attributes"),
1119 &JsValue::from(attr),
1120 )
1121 .unwrap();
1122 }
1123 (obj.into_js_result().unwrap(), None)
1124 }
1125 loro_delta::DeltaItem::Replace {
1126 value,
1127 attr,
1128 delete,
1129 } => {
1130 let mut a = None;
1131 let mut b = None;
1132 if value.rle_len() > 0 {
1133 let obj = Object::new();
1134 js_sys::Reflect::set(
1135 &obj,
1136 &JsValue::from_str("insert"),
1137 &JsValue::from_str(value.as_str()),
1138 )
1139 .unwrap();
1140 if !attr.is_empty() {
1141 js_sys::Reflect::set(
1142 &obj,
1143 &JsValue::from_str("attributes"),
1144 &JsValue::from(attr),
1145 )
1146 .unwrap();
1147 }
1148 a = Some(obj.into_js_result().unwrap());
1149 }
1150
1151 if *delete > 0 {
1152 let obj = Object::new();
1153 js_sys::Reflect::set(
1154 &obj,
1155 &JsValue::from_str("delete"),
1156 &JsValue::from_f64(*delete as f64),
1157 )
1158 .unwrap();
1159 b = Some(obj.into_js_result().unwrap());
1160 }
1161
1162 if a.is_none() {
1163 a = std::mem::take(&mut b);
1164 }
1165
1166 (a.unwrap(), b)
1167 }
1168 }
1169 }
1170
1171 impl From<&StyleMeta> for JsValue {
1172 fn from(value: &StyleMeta) -> Self {
1173 let obj = Object::new();
1175 for (key, style) in value.iter() {
1176 let value = JsValue::from(style.data);
1177 js_sys::Reflect::set(&obj, &JsValue::from_str(&key), &value).unwrap();
1178 }
1179
1180 obj.into_js_result().unwrap()
1181 }
1182 }
1183
1184 impl From<&TextMeta> for JsValue {
1185 fn from(value: &TextMeta) -> Self {
1186 let obj = Object::new();
1187 for (key, value) in value.0.iter() {
1188 js_sys::Reflect::set(&obj, &JsValue::from_str(key), &JsValue::from(value.clone()))
1189 .unwrap();
1190 }
1191
1192 obj.into_js_result().unwrap()
1193 }
1194 }
1195
1196 impl TryFrom<&JsValue> for TextMeta {
1197 type Error = JsValue;
1198
1199 fn try_from(value: &JsValue) -> Result<Self, Self::Error> {
1200 if value.is_null() || value.is_undefined() {
1201 return Ok(TextMeta::default());
1202 }
1203
1204 let obj = value.dyn_ref::<Object>().ok_or("Expected an object")?;
1205 let mut meta = TextMeta::default();
1206
1207 let entries = Object::entries(obj);
1208 for i in 0..entries.length() {
1209 let entry = entries.get(i);
1210 let entry_arr = entry.dyn_ref::<Array>().ok_or("Expected an array")?;
1211 let key = entry_arr
1212 .get(0)
1213 .as_string()
1214 .ok_or("Expected a string key")?;
1215 let value = entry_arr.get(1);
1216 meta.0.insert(key, LoroValue::from(value));
1217 }
1218
1219 Ok(meta)
1220 }
1221 }
1222}