Skip to content

Commit

Permalink
feat: support converting documents to JSON, HTML, or TEXT. (AppFlowy-…
Browse files Browse the repository at this point in the history
…IO#3811)

* feat: support converting documents to JSON, HTML, or TEXT

* fix: modify the comment

* fix: modify the comment
  • Loading branch information
qinluhe authored Oct 30, 2023
1 parent e08a1a6 commit dd9b1fb
Show file tree
Hide file tree
Showing 56 changed files with 2,136 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,22 @@ export const turnToBlockThunk = createAsyncThunk(
let caretId,
caretIndex = caret?.index || 0;
const deltaOperator = new BlockDeltaOperator(documentState, controller);
let delta = deltaOperator.getDeltaWithBlockId(node.id);
let delta = deltaOperator.getDeltaWithBlockId(node.id) || new Delta([{ insert: '' }]);
// insert new block after current block
const insertActions = [];

if (node.type === BlockType.EquationBlock) {
delta = new Delta([{ insert: node.data.formula }]);
}

if (delta && type === BlockType.EquationBlock) {
if (type === BlockType.EquationBlock) {
data.formula = deltaOperator.getDeltaText(delta);
const block = newBlock<any>(type, parent.id, data);

insertActions.push(controller.getInsertAction(block, node.id));
caretId = block.id;
caretIndex = 0;
} else if (delta && type === BlockType.DividerBlock) {
} else if (type === BlockType.DividerBlock) {
const block = newBlock<any>(type, parent.id, data);

insertActions.push(controller.getInsertAction(block, node.id));
Expand All @@ -68,7 +68,7 @@ export const turnToBlockThunk = createAsyncThunk(
caretId = nodeId;
caretIndex = 0;
insertActions.push(...actions);
} else if (delta) {
} else {
caretId = generateId();

const actions = deltaOperator.getNewTextLineActions({
Expand Down
16 changes: 16 additions & 0 deletions frontend/rust-lib/event-integration/src/document/document_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use serde_json::Value;

use flowy_document2::entities::*;
use flowy_document2::event_map::DocumentEvent;
use flowy_document2::parser::parser_entities::{
ConvertDocumentPayloadPB, ConvertDocumentResponsePB,
};
use flowy_folder2::entities::{CreateViewPayloadPB, ViewLayoutPB, ViewPB};
use flowy_folder2::event_map::FolderEvent;

Expand Down Expand Up @@ -108,6 +111,19 @@ impl DocumentEventTest {
.await;
}

pub async fn convert_document(
&self,
payload: ConvertDocumentPayloadPB,
) -> ConvertDocumentResponsePB {
let core = &self.inner;
EventBuilder::new(core.clone())
.event(DocumentEvent::ConvertDocument)
.payload(payload)
.async_send()
.await
.parse::<ConvertDocumentResponsePB>()
}

pub async fn create_text(&self, payload: TextDeltaPayloadPB) {
let core = &self.inner;
EventBuilder::new(core.clone())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use collab_document::blocks::json_str_to_hashmap;
use event_integration::document::document_event::DocumentEventTest;
use event_integration::document::utils::*;
use flowy_document2::entities::*;
use flowy_document2::parser::parser_entities::{ConvertDocumentPayloadPB, ExportTypePB};
use serde_json::{json, Value};
use std::collections::HashMap;

Expand Down Expand Up @@ -120,3 +121,35 @@ async fn apply_text_delta_test() {
json!([{ "insert": "Hello! World" }]).to_string()
);
}

macro_rules! generate_convert_document_test_cases {
($($json:ident, $text:ident, $html:ident),*) => {
[
$((ExportTypePB { json: $json, text: $text, html: $html }, ($json, $text, $html))),*
]
};
}

#[tokio::test]
async fn convert_document_test() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;

let test_cases = generate_convert_document_test_cases! {
true, true, true,
false, true, true,
false, false, false
};

for (export_types, (json_assert, text_assert, html_assert)) in test_cases.iter() {
let copy_payload = ConvertDocumentPayloadPB {
document_id: view.id.to_string(),
range: None,
export_types: export_types.clone(),
};
let result = test.convert_document(copy_payload).await;
assert_eq!(result.json.is_some(), *json_assert);
assert_eq!(result.text.is_some(), *text_assert);
assert_eq!(result.html.is_some(), *html_assert);
}
}
2 changes: 1 addition & 1 deletion frontend/rust-lib/flowy-document2/Flowy.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Check out the FlowyConfig (located in flowy_toml.rs) for more details.
proto_input = ["src/event_map.rs", "src/entities.rs", "src/notification.rs"]
proto_input = ["src/event_map.rs", "src/entities.rs", "src/notification.rs", "src/parser/parser_entities.rs"]
event_files = ["src/event_map.rs"]
47 changes: 47 additions & 0 deletions frontend/rust-lib/flowy-document2/src/event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ use flowy_error::{FlowyError, FlowyResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};

use crate::entities::*;
use crate::parser::document_data_parser::DocumentDataParser;
use crate::parser::parser_entities::{
ConvertDocumentParams, ConvertDocumentPayloadPB, ConvertDocumentResponsePB,
};

use crate::{manager::DocumentManager, parser::json::parser::JsonToDocumentParser};

fn upgrade_document(
Expand Down Expand Up @@ -303,3 +308,45 @@ impl From<(&Vec<BlockEvent>, bool)> for DocEventPB {
}
}
}

/**
* Handler for converting a document to a JSON string, HTML string, or plain text string.
* @param data: AFPluginData<[ConvertDocumentPayloadPB]>
* @param manager: AFPluginState<Weak<DocumentManager>>
* @return DataResult<[ConvertDocumentResponsePB], FlowyError>
*/
pub async fn convert_document(
data: AFPluginData<ConvertDocumentPayloadPB>,
manager: AFPluginState<Weak<DocumentManager>>,
) -> DataResult<ConvertDocumentResponsePB, FlowyError> {
let manager = upgrade_document(manager)?;
let params: ConvertDocumentParams = data.into_inner().try_into()?;

let document = manager.get_document(&params.document_id).await?;
let document_data = document.lock().get_document_data()?;
let parser = DocumentDataParser::new(Arc::new(document_data), params.range);

if !params.export_types.any_enabled() {
return data_result_ok(ConvertDocumentResponsePB::default());
}

let root = &parser.to_json();

data_result_ok(ConvertDocumentResponsePB {
json: params
.export_types
.json
.then(|| serde_json::to_string(root).unwrap_or_default()),
html: params
.export_types
.html
.then(|| parser.to_html_with_json(root)),
text: params
.export_types
.text
.then(|| parser.to_text_with_json(root)),
})
}
47 changes: 47 additions & 0 deletions frontend/rust-lib/flowy-document2/src/event_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use strum_macros::Display;
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
use lib_dispatch::prelude::AFPlugin;

use crate::event_handler::convert_document;
use crate::event_handler::get_snapshot_handler;
use crate::{event_handler::*, manager::DocumentManager};

Expand All @@ -27,6 +28,7 @@ pub fn init(document_manager: Weak<DocumentManager>) -> AFPlugin {
.event(DocumentEvent::GetDocumentSnapshots, get_snapshot_handler)
.event(DocumentEvent::CreateText, create_text_handler)
.event(DocumentEvent::ApplyTextDeltaEvent, apply_text_delta_handler)
.event(DocumentEvent::ConvertDocument, convert_document)
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, ProtoBuf_Enum, Flowy_Event)]
Expand Down Expand Up @@ -76,4 +78,49 @@ pub enum DocumentEvent {

#[event(input = "TextDeltaPayloadPB")]
ApplyTextDeltaEvent = 11,

/// Handler for converting a document to a JSON string, HTML string, or plain text string.
///
/// ConvertDocumentPayloadPB is the input of this event.
/// ConvertDocumentResponsePB is the output of this event.
///
/// # Examples
///
/// Basic usage:
///
/// ```txt
/// // document: [{ "block_id": "1", "type": "paragraph", "data": {"delta": [{ "insert": "Hello World!" }] } }, { "block_id": "2", "type": "paragraph", "data": {"delta": [{ "insert": "Hello World!" }] }
/// let test = DocumentEventTest::new().await;
/// let view = test.create_document().await;
/// let payload = ConvertDocumentPayloadPB {
/// document_id: view.id,
/// range: Some(RangePB {
/// start: SelectionPB {
/// block_id: "1".to_string(),
/// index: 0,
/// length: 5,
/// },
/// end: SelectionPB {
/// block_id: "2".to_string(),
/// index: 5,
/// length: 7,
/// }
/// }),
/// export_types: ConvertTypePB {
/// json: true,
/// text: true,
/// html: true,
/// },
/// };
/// let result = test.convert_document(payload).await;
/// assert_eq!(result.json, Some("[{ \"block_id\": \"1\", \"type\": \"paragraph\", \"data\": {\"delta\": [{ \"insert\": \"Hello\" }] } }, { \"block_id\": \"2\", \"type\": \"paragraph\", \"data\": {\"delta\": [{ \"insert\": \" World!\" }] } }".to_string()));
/// assert_eq!(result.text, Some("Hello\n World!".to_string()));
/// assert_eq!(result.html, Some("<p>Hello</p><p> World!</p>".to_string()));
/// ```
/// #
#[event(
input = "ConvertDocumentPayloadPB",
output = "ConvertDocumentResponsePB"
)]
ConvertDocument = 12,
}
37 changes: 37 additions & 0 deletions frontend/rust-lib/flowy-document2/src/parser/constant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
pub const DELTA: &str = "delta";
pub const LEVEL: &str = "level";
pub const NUMBER: &str = "number";
pub const CHECKED: &str = "checked";

pub const COLLAPSED: &str = "collapsed";
pub const LANGUAGE: &str = "language";

pub const ICON: &str = "icon";
pub const WIDTH: &str = "width";
pub const HEIGHT: &str = "height";
pub const URL: &str = "url";
pub const CAPTION: &str = "caption";
pub const ALIGN: &str = "align";

pub const PAGE: &str = "page";
pub const HEADING: &str = "heading";
pub const PARAGRAPH: &str = "paragraph";
pub const NUMBERED_LIST: &str = "numbered_list";
pub const BULLETED_LIST: &str = "bulleted_list";
pub const TODO_LIST: &str = "todo_list";
pub const TOGGLE_LIST: &str = "toggle_list";
pub const QUOTE: &str = "quote";
pub const CALLOUT: &str = "callout";
pub const IMAGE: &str = "image";
pub const DIVIDER: &str = "divider";
pub const MATH_EQUATION: &str = "math_equation";
pub const BOLD: &str = "bold";
pub const ITALIC: &str = "italic";
pub const STRIKETHROUGH: &str = "strikethrough";
pub const CODE: &str = "code";
pub const UNDERLINE: &str = "underline";
pub const FONT_COLOR: &str = "font_color";
pub const BG_COLOR: &str = "bg_color";
pub const HREF: &str = "href";
pub const FORMULA: &str = "formula";
pub const MENTION: &str = "mention";
Loading

0 comments on commit dd9b1fb

Please sign in to comment.