Fix tiptap selection sync and toolbar event loop
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
use crate::editor::model::{BlockProps, ContentNode, EditorBlock, EditorBlockType, ReferenceToken};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EditorCommandKind {
|
||||
ReplaceBlock,
|
||||
InsertBlockAfter,
|
||||
DeleteBlock,
|
||||
SplitBlock,
|
||||
MergeWithPrevious,
|
||||
MoveBlock,
|
||||
IndentBlock,
|
||||
OutdentBlock,
|
||||
ToggleHeadingCollapse,
|
||||
AttachReferenceToken,
|
||||
DetachReferenceToken,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorReplaceBlock {
|
||||
pub block_id: String,
|
||||
pub block_type: Option<EditorBlockType>,
|
||||
pub props: Option<BlockProps>,
|
||||
pub content_nodes: Option<Vec<ContentNode>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorInsertBlockAfter {
|
||||
pub after_block_id: String,
|
||||
pub block: EditorBlock,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorDeleteBlock {
|
||||
pub block_id: String,
|
||||
pub preserve_children: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorSplitBlock {
|
||||
pub block_id: String,
|
||||
pub content_node_index: u32,
|
||||
pub text_offset: Option<u32>,
|
||||
pub trailing_block_type: Option<EditorBlockType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorMergeWithPrevious {
|
||||
pub block_id: String,
|
||||
pub previous_block_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorMoveBlock {
|
||||
pub block_id: String,
|
||||
pub parent_block_id: Option<String>,
|
||||
pub after_block_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorIndentBlock {
|
||||
pub block_id: String,
|
||||
pub max_depth: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorOutdentBlock {
|
||||
pub block_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorToggleHeadingCollapse {
|
||||
pub block_id: String,
|
||||
pub collapsed: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorAttachReferenceToken {
|
||||
pub block_id: String,
|
||||
pub content_node_index: u32,
|
||||
pub token: ReferenceToken,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorDetachReferenceToken {
|
||||
pub block_id: String,
|
||||
pub content_node_index: u32,
|
||||
pub target_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum EditorCommand {
|
||||
ReplaceBlock(EditorReplaceBlock),
|
||||
InsertBlockAfter(EditorInsertBlockAfter),
|
||||
DeleteBlock(EditorDeleteBlock),
|
||||
SplitBlock(EditorSplitBlock),
|
||||
MergeWithPrevious(EditorMergeWithPrevious),
|
||||
MoveBlock(EditorMoveBlock),
|
||||
IndentBlock(EditorIndentBlock),
|
||||
OutdentBlock(EditorOutdentBlock),
|
||||
ToggleHeadingCollapse(EditorToggleHeadingCollapse),
|
||||
AttachReferenceToken(EditorAttachReferenceToken),
|
||||
DetachReferenceToken(EditorDetachReferenceToken),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorKernelCommandMapping {
|
||||
pub editor_command: EditorCommandKind,
|
||||
pub kernel_command_name: String,
|
||||
pub lossy: bool,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorCommandDescriptor {
|
||||
pub kind: EditorCommandKind,
|
||||
pub label: String,
|
||||
pub write_command: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorCommandCatalog {
|
||||
#[serde(default)]
|
||||
pub commands: Vec<EditorCommandDescriptor>,
|
||||
#[serde(default)]
|
||||
pub kernel_command_mappings: Vec<EditorKernelCommandMapping>,
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use crate::editor::model::{EditorBlockDocument, ReferenceTokenStrategy};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MarkdownFlavor {
|
||||
Commonmark,
|
||||
Gfm,
|
||||
Mnote,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MarkdownImportMode {
|
||||
ReplaceDocument,
|
||||
ReplaceSelection,
|
||||
MergeIntoDocument,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MarkdownExportMode {
|
||||
FullDocument,
|
||||
Selection,
|
||||
SingleBlock,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MarkdownImportBoundary {
|
||||
pub allow_front_matter: bool,
|
||||
pub allow_html_blocks: bool,
|
||||
pub allow_reference_tokens: bool,
|
||||
pub unsupported_blocks_as_paragraph: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MarkdownExportBoundary {
|
||||
pub preserve_heading_collapse: bool,
|
||||
pub preserve_reference_tokens: bool,
|
||||
pub allow_lossy_block_fallback: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MarkdownImportOptions {
|
||||
pub mode: MarkdownImportMode,
|
||||
pub flavor: MarkdownFlavor,
|
||||
pub reference_token_strategy: ReferenceTokenStrategy,
|
||||
pub boundary: MarkdownImportBoundary,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MarkdownExportOptions {
|
||||
pub mode: MarkdownExportMode,
|
||||
pub flavor: MarkdownFlavor,
|
||||
pub reference_token_strategy: ReferenceTokenStrategy,
|
||||
pub boundary: MarkdownExportBoundary,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MarkdownImportRequest {
|
||||
pub markdown: String,
|
||||
pub options: MarkdownImportOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MarkdownImportResult {
|
||||
pub document: EditorBlockDocument,
|
||||
#[serde(default)]
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MarkdownExportRequest {
|
||||
pub document: EditorBlockDocument,
|
||||
pub options: MarkdownExportOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MarkdownExportResult {
|
||||
pub markdown: String,
|
||||
#[serde(default)]
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
pub mod command;
|
||||
pub mod markdown;
|
||||
pub mod model;
|
||||
|
||||
pub use command::{
|
||||
EditorAttachReferenceToken, EditorCommand, EditorCommandCatalog, EditorCommandDescriptor,
|
||||
EditorCommandKind, EditorDeleteBlock, EditorDetachReferenceToken, EditorIndentBlock,
|
||||
EditorInsertBlockAfter, EditorKernelCommandMapping, EditorMergeWithPrevious, EditorMoveBlock,
|
||||
EditorOutdentBlock, EditorReplaceBlock, EditorSplitBlock, EditorToggleHeadingCollapse,
|
||||
};
|
||||
pub use markdown::{
|
||||
MarkdownExportBoundary, MarkdownExportMode, MarkdownExportOptions, MarkdownExportRequest,
|
||||
MarkdownExportResult, MarkdownFlavor, MarkdownImportBoundary, MarkdownImportMode,
|
||||
MarkdownImportOptions, MarkdownImportRequest, MarkdownImportResult,
|
||||
};
|
||||
pub use model::{
|
||||
BlockProps, ContentNode, ContentNodePayload, EditorBlock, EditorBlockDocument, EditorBlockType,
|
||||
ReferenceToken, ReferenceTokenKind, ReferenceTokenStrategy, TextMark,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn editor_command_kind_uses_phase_one_names() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&EditorCommandKind::InsertBlockAfter).unwrap(),
|
||||
"\"insert_block_after\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&EditorCommandKind::MergeWithPrevious).unwrap(),
|
||||
"\"merge_with_previous\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&EditorCommandKind::ToggleHeadingCollapse).unwrap(),
|
||||
"\"toggle_heading_collapse\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_block_type_uses_expected_snake_case_names() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&EditorBlockType::BulletListItem).unwrap(),
|
||||
"\"bullet_list_item\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&EditorBlockType::PageReference).unwrap(),
|
||||
"\"page_reference\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&EditorBlockType::BlockReference).unwrap(),
|
||||
"\"block_reference\""
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EditorBlockType {
|
||||
Paragraph,
|
||||
Heading,
|
||||
BulletListItem,
|
||||
NumberedListItem,
|
||||
Quote,
|
||||
Todo,
|
||||
CodeBlock,
|
||||
PageReference,
|
||||
BlockReference,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ReferenceTokenKind {
|
||||
PageReference,
|
||||
BlockReference,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ReferenceTokenStrategy {
|
||||
DoubleBracket,
|
||||
DoubleParen,
|
||||
InlineChip,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TextMark {
|
||||
Bold,
|
||||
Italic,
|
||||
Underline,
|
||||
Strike,
|
||||
Code,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReferenceToken {
|
||||
pub kind: ReferenceTokenKind,
|
||||
pub strategy: ReferenceTokenStrategy,
|
||||
pub target_id: String,
|
||||
pub label: Option<String>,
|
||||
pub raw_token: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ContentNodePayload {
|
||||
Text {
|
||||
text: String,
|
||||
#[serde(default)]
|
||||
marks: Vec<TextMark>,
|
||||
},
|
||||
HardBreak,
|
||||
ReferenceToken {
|
||||
token: ReferenceToken,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ContentNode {
|
||||
pub payload: ContentNodePayload,
|
||||
#[serde(default)]
|
||||
pub attrs: BTreeMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockProps {
|
||||
pub indent: Option<u16>,
|
||||
pub heading_level: Option<u8>,
|
||||
pub checked: Option<bool>,
|
||||
pub collapsed: Option<bool>,
|
||||
pub language: Option<String>,
|
||||
pub reference_target_id: Option<String>,
|
||||
pub reference_label: Option<String>,
|
||||
pub reference_token_strategy: Option<ReferenceTokenStrategy>,
|
||||
#[serde(default)]
|
||||
pub extra: BTreeMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorBlock {
|
||||
pub block_id: String,
|
||||
pub block_type: EditorBlockType,
|
||||
#[serde(default)]
|
||||
pub props: BlockProps,
|
||||
#[serde(default)]
|
||||
pub content_nodes: Vec<ContentNode>,
|
||||
#[serde(default)]
|
||||
pub child_block_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditorBlockDocument {
|
||||
pub document_id: String,
|
||||
#[serde(default)]
|
||||
pub root_block_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub blocks: Vec<EditorBlock>,
|
||||
}
|
||||
Reference in New Issue
Block a user