| 1 | use crate::{display, lists, projection, structure}; |
| 2 | use serde_json::{json, Value}; |
| 3 | use yrs::{Doc, Transact, XmlOut}; |
| 4 | |
| 5 | fn block(doc: &Doc, at: u32) -> Result<String, &'static str> { |
| 6 | let (id, _) = display::locate(doc, at, 0)?; |
| 7 | let root = doc.get_or_insert_xml_fragment("prosemirror"); |
| 8 | let txn = doc.transact(); |
| 9 | match projection::find(&root, &txn, &id) { |
| 10 | Some(XmlOut::Element(_)) => Ok(id), |
| 11 | Some(XmlOut::Text(_)) => structure::parent(&root, &txn, &id) |
| 12 | .map(|(parent, _)| projection::node_id(&XmlOut::Fragment(parent))).ok_or("missing_parent"), |
| 13 | _ => Err("missing_node"), |
| 14 | } |
| 15 | } |
| 16 | pub fn execute(doc: &Doc, request: &Value) -> Result<(), &'static str> { |
| 17 | let at = request["at"].as_u64().and_then(|n| u32::try_from(n).ok()).ok_or("invalid_range")?; |
| 18 | let kind = request["kind"].as_str().ok_or("invalid_command")?; |
| 19 | if !["paragraph", "heading", "blockquote", "bullet_list", "ordered_list"].contains(&kind) { return Err("invalid_command"); } |
| 20 | let id = block(doc, at)?; |
| 21 | match request["op"].as_str() { |
| 22 | Some("display_autoformat") => { |
| 23 | display::execute(doc, &json!({"at":at,"length":request["length"],"text":""}))?; |
| 24 | let command = json!({"op":"block","node":id,"type":kind,"level":request["level"]}); |
| 25 | let target = lists::normalization_target(doc, &command); |
| 26 | structure::execute(doc, &command)?; |
| 27 | if let Some(id) = target.and_then(|t| lists::normalized_identity(doc, t)) { |
| 28 | lists::normalize(doc, &mut std::collections::HashSet::from([id])); |
| 29 | } |
| 30 | }, |
| 31 | Some("display_revert_rule") => { |
| 32 | if lists::contains(doc, &id) { |
| 33 | lists::execute(doc, &json!({"node":id,"delta":-1}))?; |
| 34 | } else if kind == "blockquote" { |
| 35 | structure::execute(doc, &json!({"op":"block","node":id,"type":"blockquote"}))?; |
| 36 | } |
| 37 | let id = block(doc, at)?; |
| 38 | structure::execute(doc, &json!({"op":"block","node":id,"type":request["prior_type"],"level":request["prior_attrs"]["level"].as_str().and_then(|s| s.parse::<u32>().ok()).unwrap_or(1)}))?; |
| 39 | display::execute(doc, &json!({"at":at,"length":0,"text":request["text"]}))?; |
| 40 | }, |
| 41 | Some("display_exit_list") => lists::execute(doc, &json!({"node":id,"delta":-1}))?, |
| 42 | _ => return Err("invalid_command"), |
| 43 | } |
| 44 | Ok(()) |
| 45 | } |
| 46 | |