#![allow(non_snake_case)] use crate::core::{Attributes, Operation, OperationTransform}; use crate::{block_attribute, errors::OTError, ignore_attribute, inline_attribute, list_attribute}; use lazy_static::lazy_static; use std::{ collections::{HashMap, HashSet}, fmt, fmt::Formatter, iter::FromIterator, }; use strum_macros::Display; pub type RichTextOperation = Operation; impl RichTextOperation { pub fn contain_attribute(&self, attribute: &RichTextAttribute) -> bool { self.get_attributes().contains_key(&attribute.key) } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct RichTextAttributes { pub(crate) inner: HashMap, } impl std::default::Default for RichTextAttributes { fn default() -> Self { Self { inner: HashMap::with_capacity(0), } } } impl fmt::Display for RichTextAttributes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!("{:?}", self.inner)) } } #[inline(always)] pub fn plain_attributes() -> RichTextAttributes { RichTextAttributes::default() } impl RichTextAttributes { pub fn new() -> Self { RichTextAttributes { inner: HashMap::new() } } pub fn is_empty(&self) -> bool { self.inner.is_empty() } pub fn add(&mut self, attribute: RichTextAttribute) { let RichTextAttribute { key, value, scope: _ } = attribute; self.inner.insert(key, value); } pub fn insert(&mut self, key: RichTextAttributeKey, value: RichTextAttributeValue) { self.inner.insert(key, value); } pub fn delete(&mut self, key: &RichTextAttributeKey) { self.inner.insert(key.clone(), RichTextAttributeValue(None)); } pub fn mark_all_as_removed_except(&mut self, attribute: Option) { match attribute { None => { self.inner.iter_mut().for_each(|(_k, v)| v.0 = None); } Some(attribute) => { self.inner.iter_mut().for_each(|(k, v)| { if k != &attribute { v.0 = None; } }); } } } pub fn remove(&mut self, key: RichTextAttributeKey) { self.inner.retain(|k, _| k != &key); } // pub fn block_attributes_except_header(attributes: &Attributes) -> Attributes // { let mut new_attributes = Attributes::new(); // attributes.iter().for_each(|(k, v)| { // if k != &AttributeKey::Header { // new_attributes.insert(k.clone(), v.clone()); // } // }); // // new_attributes // } // Update inner by constructing new attributes from the other if it's // not None and replace the key/value with self key/value. pub fn merge(&mut self, other: Option) { if other.is_none() { return; } let mut new_attributes = other.unwrap().inner; self.inner.iter().for_each(|(k, v)| { new_attributes.insert(k.clone(), v.clone()); }); self.inner = new_attributes; } } impl Attributes for RichTextAttributes { fn is_empty(&self) -> bool { self.inner.is_empty() } fn remove_empty(&mut self) { self.inner.retain(|_, v| v.0.is_some()); } fn extend_other(&mut self, other: Self) { self.inner.extend(other.inner); } } impl OperationTransform for RichTextAttributes { fn compose(&self, other: &Self) -> Result where Self: Sized, { let mut attributes = self.clone(); attributes.extend_other(other.clone()); Ok(attributes) } fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> where Self: Sized, { let a = self .iter() .fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| { if !other.contains_key(k) { new_attributes.insert(k.clone(), v.clone()); } new_attributes }); let b = other .iter() .fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| { if !self.contains_key(k) { new_attributes.insert(k.clone(), v.clone()); } new_attributes }); Ok((a, b)) } fn invert(&self, other: &Self) -> Self { let base_inverted = other.iter().fold(RichTextAttributes::new(), |mut attributes, (k, v)| { if other.get(k) != self.get(k) && self.contains_key(k) { attributes.insert(k.clone(), v.clone()); } attributes }); let inverted = self.iter().fold(base_inverted, |mut attributes, (k, _)| { if other.get(k) != self.get(k) && !other.contains_key(k) { attributes.delete(k); } attributes }); inverted } } impl std::ops::Deref for RichTextAttributes { type Target = HashMap; fn deref(&self) -> &Self::Target { &self.inner } } impl std::ops::DerefMut for RichTextAttributes { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } pub fn attributes_except_header(op: &RichTextOperation) -> RichTextAttributes { let mut attributes = op.get_attributes(); attributes.remove(RichTextAttributeKey::Header); attributes } #[derive(Debug, Clone)] pub struct RichTextAttribute { pub key: RichTextAttributeKey, pub value: RichTextAttributeValue, pub scope: AttributeScope, } impl RichTextAttribute { // inline inline_attribute!(Bold, bool); inline_attribute!(Italic, bool); inline_attribute!(Underline, bool); inline_attribute!(StrikeThrough, bool); inline_attribute!(Link, &str); inline_attribute!(Color, String); inline_attribute!(Font, usize); inline_attribute!(Size, usize); inline_attribute!(Background, String); inline_attribute!(InlineCode, bool); // block block_attribute!(Header, usize); block_attribute!(Indent, usize); block_attribute!(Align, String); block_attribute!(List, &str); block_attribute!(CodeBlock, bool); block_attribute!(BlockQuote, bool); // ignore ignore_attribute!(Width, usize); ignore_attribute!(Height, usize); // List extension list_attribute!(Bullet, "bullet"); list_attribute!(Ordered, "ordered"); list_attribute!(Checked, "checked"); list_attribute!(UnChecked, "unchecked"); pub fn to_json(&self) -> String { match serde_json::to_string(self) { Ok(json) => json, Err(e) => { log::error!("Attribute serialize to str failed: {}", e); "".to_owned() } } } } impl fmt::Display for RichTextAttribute { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let s = format!("{:?}:{:?} {:?}", self.key, self.value.0, self.scope); f.write_str(&s) } } impl std::convert::From for RichTextAttributes { fn from(attr: RichTextAttribute) -> Self { let mut attributes = RichTextAttributes::new(); attributes.add(attr); attributes } } #[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] // serde.rs/variant-attrs.html // #[serde(rename_all = "snake_case")] pub enum RichTextAttributeKey { #[serde(rename = "bold")] Bold, #[serde(rename = "italic")] Italic, #[serde(rename = "underline")] Underline, #[serde(rename = "strike")] StrikeThrough, #[serde(rename = "font")] Font, #[serde(rename = "size")] Size, #[serde(rename = "link")] Link, #[serde(rename = "color")] Color, #[serde(rename = "background")] Background, #[serde(rename = "indent")] Indent, #[serde(rename = "align")] Align, #[serde(rename = "code_block")] CodeBlock, #[serde(rename = "code")] InlineCode, #[serde(rename = "list")] List, #[serde(rename = "blockquote")] BlockQuote, #[serde(rename = "width")] Width, #[serde(rename = "height")] Height, #[serde(rename = "header")] Header, } // pub trait AttributeValueData<'a>: Serialize + Deserialize<'a> {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RichTextAttributeValue(pub Option); impl std::convert::From<&usize> for RichTextAttributeValue { fn from(val: &usize) -> Self { RichTextAttributeValue::from(*val) } } impl std::convert::From for RichTextAttributeValue { fn from(val: usize) -> Self { if val > 0_usize { RichTextAttributeValue(Some(format!("{}", val))) } else { RichTextAttributeValue(None) } } } impl std::convert::From<&str> for RichTextAttributeValue { fn from(val: &str) -> Self { val.to_owned().into() } } impl std::convert::From for RichTextAttributeValue { fn from(val: String) -> Self { if val.is_empty() { RichTextAttributeValue(None) } else { RichTextAttributeValue(Some(val)) } } } impl std::convert::From<&bool> for RichTextAttributeValue { fn from(val: &bool) -> Self { RichTextAttributeValue::from(*val) } } impl std::convert::From for RichTextAttributeValue { fn from(val: bool) -> Self { let val = match val { true => Some("true".to_owned()), false => None, }; RichTextAttributeValue(val) } } pub fn is_block_except_header(k: &RichTextAttributeKey) -> bool { if k == &RichTextAttributeKey::Header { return false; } BLOCK_KEYS.contains(k) } lazy_static! { static ref BLOCK_KEYS: HashSet = HashSet::from_iter(vec![ RichTextAttributeKey::Header, RichTextAttributeKey::Indent, RichTextAttributeKey::Align, RichTextAttributeKey::CodeBlock, RichTextAttributeKey::List, RichTextAttributeKey::BlockQuote, ]); static ref INLINE_KEYS: HashSet = HashSet::from_iter(vec![ RichTextAttributeKey::Bold, RichTextAttributeKey::Italic, RichTextAttributeKey::Underline, RichTextAttributeKey::StrikeThrough, RichTextAttributeKey::Link, RichTextAttributeKey::Color, RichTextAttributeKey::Font, RichTextAttributeKey::Size, RichTextAttributeKey::Background, RichTextAttributeKey::InlineCode, ]); static ref INGORE_KEYS: HashSet = HashSet::from_iter(vec![RichTextAttributeKey::Width, RichTextAttributeKey::Height,]); } #[derive(Debug, PartialEq, Eq, Clone)] pub enum AttributeScope { Inline, Block, Embeds, Ignore, }