Files
mnote/rust/crates/mnote-editor-core/src/model.rs
T

232 lines
6.4 KiB
Rust

use serde::{Deserialize, Serialize};
use std::ops::RangeInclusive;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BlockType {
Paragraph,
Heading,
BulletListItem,
NumberedListItem,
Todo,
Quote,
Divider,
CodeBlock,
PageReference,
BlockReference,
MediaPlaceholder,
ProgressPlaceholder,
}
impl BlockType {
pub fn from_editor_label(label: &str) -> Option<Self> {
match label {
"paragraph" => Some(Self::Paragraph),
"heading" => Some(Self::Heading),
"bullet_list_item" => Some(Self::BulletListItem),
"numbered_list_item" => Some(Self::NumberedListItem),
"todo" => Some(Self::Todo),
"quote" => Some(Self::Quote),
"divider" => Some(Self::Divider),
"code_block" => Some(Self::CodeBlock),
"page_reference" => Some(Self::PageReference),
"block_reference" => Some(Self::BlockReference),
"media_placeholder" => Some(Self::MediaPlaceholder),
"progress_placeholder" => Some(Self::ProgressPlaceholder),
_ => None,
}
}
pub fn as_editor_label(&self) -> &'static str {
match self {
Self::Paragraph => "paragraph",
Self::Heading => "heading",
Self::BulletListItem => "bullet_list_item",
Self::NumberedListItem => "numbered_list_item",
Self::Todo => "todo",
Self::Quote => "quote",
Self::Divider => "divider",
Self::CodeBlock => "code_block",
Self::PageReference => "page_reference",
Self::BlockReference => "block_reference",
Self::MediaPlaceholder => "media_placeholder",
Self::ProgressPlaceholder => "progress_placeholder",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReferenceKind {
Page,
Block,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockReference {
pub kind: ReferenceKind,
pub target_id: String,
pub label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct BlockContent {
pub text: String,
pub language: Option<String>,
pub reference: Option<BlockReference>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DocumentBlock {
pub id: String,
pub block_type: BlockType,
pub parent_id: Option<String>,
pub indent: u16,
pub collapsed: bool,
pub heading_level: Option<u8>,
pub checked: Option<bool>,
pub content: BlockContent,
}
impl DocumentBlock {
pub fn new(id: impl Into<String>, block_type: BlockType) -> Self {
Self {
id: id.into(),
block_type,
parent_id: None,
indent: 0,
collapsed: false,
heading_level: None,
checked: None,
content: BlockContent::default(),
}
}
pub fn with_parent(mut self, parent_id: impl Into<String>, indent: u16) -> Self {
self.parent_id = Some(parent_id.into());
self.indent = indent;
self
}
pub fn with_heading_level(mut self, level: u8) -> Self {
self.heading_level = Some(level);
self
}
pub fn with_text(mut self, text: impl Into<String>) -> Self {
self.content.text = text.into();
self
}
pub fn with_checked(mut self, checked: bool) -> Self {
self.checked = Some(checked);
self
}
pub fn with_language(mut self, language: impl Into<String>) -> Self {
self.content.language = Some(language.into());
self
}
pub fn with_reference(
mut self,
kind: ReferenceKind,
target_id: impl Into<String>,
label: Option<String>,
) -> Self {
self.content.reference = Some(BlockReference {
kind,
target_id: target_id.into(),
label,
});
self
}
pub fn with_collapsed(mut self, collapsed: bool) -> Self {
self.collapsed = collapsed;
self
}
pub fn set_block_type(mut self, block_type: BlockType) -> Self {
self.block_type = block_type;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct DocumentModel {
blocks: Vec<DocumentBlock>,
}
impl DocumentModel {
pub fn new(blocks: Vec<DocumentBlock>) -> Self {
Self { blocks }
}
pub fn empty() -> Self {
Self::default()
}
pub fn blocks(&self) -> &[DocumentBlock] {
&self.blocks
}
pub(crate) fn blocks_mut(&mut self) -> &mut Vec<DocumentBlock> {
&mut self.blocks
}
pub fn push(&mut self, block: DocumentBlock) {
self.blocks.push(block);
}
pub fn block(&self, id: &str) -> Option<&DocumentBlock> {
self.blocks.iter().find(|block| block.id == id)
}
pub(crate) fn block_mut(&mut self, id: &str) -> Option<&mut DocumentBlock> {
self.blocks.iter_mut().find(|block| block.id == id)
}
pub fn contains_id(&self, id: &str) -> bool {
self.block(id).is_some()
}
pub fn index_of(&self, id: &str) -> Option<usize> {
self.blocks.iter().position(|block| block.id == id)
}
pub fn children_of<'a>(&'a self, parent_id: Option<&str>) -> Vec<&'a DocumentBlock> {
self.blocks
.iter()
.filter(|block| block.parent_id.as_deref() == parent_id)
.collect()
}
pub fn is_descendant_of(&self, block_id: &str, ancestor_id: &str) -> bool {
let mut current_parent = self
.block(block_id)
.and_then(|block| block.parent_id.as_deref());
while let Some(parent_id) = current_parent {
if parent_id == ancestor_id {
return true;
}
current_parent = self
.block(parent_id)
.and_then(|parent| parent.parent_id.as_deref());
}
false
}
pub fn subtree_range(&self, block_id: &str) -> Option<RangeInclusive<usize>> {
let start = self.index_of(block_id)?;
let mut end = start;
for next_index in (start + 1)..self.blocks.len() {
if self.is_descendant_of(&self.blocks[next_index].id, block_id) {
end = next_index;
} else {
break;
}
}
Some(start..=end)
}
}