|
@@ -1,11 +1,179 @@
|
|
|
#![allow(non_snake_case)]
|
|
|
-
|
|
|
-use crate::{block_attribute, core::RichTextAttributes, ignore_attribute, inline_attribute, list_attribute};
|
|
|
+use crate::{
|
|
|
+ block_attribute,
|
|
|
+ core::{Attributes, OperationTransformable, RichTextOperation},
|
|
|
+ errors::OTError,
|
|
|
+ ignore_attribute,
|
|
|
+ inline_attribute,
|
|
|
+ list_attribute,
|
|
|
+};
|
|
|
use lazy_static::lazy_static;
|
|
|
-
|
|
|
-use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator};
|
|
|
+use std::{
|
|
|
+ collections::{HashMap, HashSet},
|
|
|
+ fmt,
|
|
|
+ fmt::Formatter,
|
|
|
+ iter::FromIterator,
|
|
|
+};
|
|
|
use strum_macros::Display;
|
|
|
|
|
|
+#[derive(Debug, Clone, Eq, PartialEq)]
|
|
|
+pub struct RichTextAttributes {
|
|
|
+ pub(crate) inner: HashMap<RichTextAttributeKey, RichTextAttributeValue>,
|
|
|
+}
|
|
|
+
|
|
|
+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)) }
|
|
|
+}
|
|
|
+
|
|
|
+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 add_kv(&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<RichTextAttributeKey>) {
|
|
|
+ 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<RichTextAttributes>) {
|
|
|
+ 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 OperationTransformable for RichTextAttributes {
|
|
|
+ fn compose(&self, other: &Self) -> Result<Self, OTError>
|
|
|
+ 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<RichTextAttributeKey, RichTextAttributeValue>;
|
|
|
+
|
|
|
+ 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,
|