Kilu.He 23c67bcdba
feat: support publish document (#5576)
* feat: support a event for getting encoded collab of document

* feat: support publish view and unpublish views

* feat: publish page to the web

* chore: refacotor share bloc

* feat: call the publish event

* feat: support publish view and unpublish views

* feat: integrate publish api

* feat: integrate unpublish api

* feat: fetch the publish info to show the publish status

* feat: support publish interfaces

* fix: lint error

* fix: modified web server

* fix: some style

* fix: some style

* fix: some style

* fix: some style

* fix: some style

* fix: some style

* fix: some style

* fix: some style

* fix: some style

* fix: update codes

* fix: update codes

* fix: update codes

* fix: update codes

* fix: update codes

* chore: refactor publish bloc

* fix: some style

* fix: some style

* fix: some style

* fix: some style

* fix: some style

* fix: some style

* fix: the name is too long to publish

* chore: change color

* fix: some style

* fix: some style

* feat: refacotor share menu UI

* fix: some style

* fix: lint

* fix: some style

* feat: refacotor export-as

* fix: some style

* chore: refactor share menu colors

* fix: rust ci

* fix: some style

* fix: some style

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: bugs

* fix: rerelease

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: deploy

* fix: og image

* fix: support copy button

* fix: support copy button

* fix: support copy button

* chore: add a params

* feat: use default publish name

* chore: update copy

* feat: show a confirm deletion dialog if the deleted page contains published page

* feat: add copy toast in publish tab

* fix: to 404

fix: to 404

fix: to 404

fix: the error to 404

* feat: unpublish the page auto when moving it to another space

* feat: improve confirm deletion dialog

* feat: show unpublish error

* chore: use beta.appflowy.com

* feat: disable publish in non-apppflowy-cloud user mode

* fix: modified bullted icon style

* fix: the dark mode color

* fix: save the dark mode in local storage

* fix: text color

* chore: make bash script more portable (#5679)

* fix: title longer

* chore: move the files and modified the en

* chore: update deploy.sh

* chore: modified Dockerfile

* chore: modified server.cjs to server.js

* chore: modifed server.js to server.ts

* chore: replace publish url

* chore: remove todo list hover

* chore: show confirm dialog before deleting page

* fix: unpublish the pages before deleting

* fix: table cell bg color

* fix: callout icon

* fix: list number

* fix: emoji

* fix: number icon

* fix: callout icon position

* fix: add margin bottom

* fix: code block

* fix: support scroll for breadcrumbs

* fix: the breadcrumb doesn't update after moving page

* fix: 0705 issues

* fix: update publish status afer deleting page

* chore: add hover effect for visit site button

* fix: remove puiblish url text field enable border color

* chore: update delete page copy

* chore: enable debug category

* fix: only render sidebar if the spaces are ready

* fix: the breadcrumb doesn't update after moving page

* fix: auto code

* fix: add emoji

* fix: add emoji

* fix: favicon

* fix: cypress test

* fix: remove deploy ci

* fix: default url

* chore: revert launch.json

* fix: docker ci

* fix: change favicon

* fix: flutter integration test

* feat: add hover effect to share menu

* chore: add a checkmark if the page has been published

* chore: revert space deletion

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
Co-authored-by: Zack <speed2exe@live.com.sg>
2024-07-08 13:45:57 +08:00

233 lines
7.6 KiB
Rust

use crate::document::generate_random_string;
use collab_document::blocks::json_str_to_hashmap;
use event_integration_test::document::document_event::DocumentEventTest;
use event_integration_test::document::utils::*;
use flowy_document::entities::*;
use flowy_document::parser::parser_entities::{
ConvertDataToJsonPayloadPB, ConvertDocumentPayloadPB, InputType, NestedBlock, ParseTypePB,
};
use serde_json::{json, Value};
use std::collections::HashMap;
#[tokio::test]
async fn get_document_event_test() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;
let document = test.open_document(view.id).await;
let document_data = document.data;
assert!(!document_data.page_id.is_empty());
assert!(document_data.blocks.len() > 1);
}
#[tokio::test]
async fn get_encoded_collab_event_test() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;
let doc_id = view.id.clone();
let encoded_v1 = test.get_encoded_collab(&doc_id).await;
assert!(!encoded_v1.doc_state.is_empty());
assert!(!encoded_v1.state_vector.is_empty());
}
#[tokio::test]
async fn apply_document_event_test() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;
let doc_id = view.id.clone();
let document = test.open_document(doc_id.clone()).await;
let block_count = document.data.blocks.len();
let insert_action = gen_insert_block_action(document);
let payload = ApplyActionPayloadPB {
document_id: doc_id.clone(),
actions: vec![insert_action],
};
test.apply_actions(payload).await;
let document = test.open_document(doc_id).await;
let document_data = document.data;
let block_count_after = document_data.blocks.len();
assert_eq!(block_count_after, block_count + 1);
}
#[tokio::test]
async fn undo_redo_event_test() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;
let doc_id = view.id.clone();
let document = test.open_document(doc_id.clone()).await;
let insert_action = gen_insert_block_action(document);
let payload = ApplyActionPayloadPB {
document_id: doc_id.clone(),
actions: vec![insert_action],
};
test.apply_actions(payload).await;
let block_count_after_insert = test.open_document(doc_id.clone()).await.data.blocks.len();
// undo insert action
let can_undo = test.can_undo_redo(doc_id.clone()).await.can_undo;
assert!(can_undo);
test.undo(doc_id.clone()).await;
let block_count_after_undo = test.open_document(doc_id.clone()).await.data.blocks.len();
assert_eq!(block_count_after_undo, block_count_after_insert - 1);
// redo insert action
let can_redo = test.can_undo_redo(doc_id.clone()).await.can_redo;
assert!(can_redo);
test.redo(doc_id.clone()).await;
let block_count_after_redo = test.open_document(doc_id.clone()).await.data.blocks.len();
assert_eq!(block_count_after_redo, block_count_after_insert);
}
#[tokio::test]
async fn insert_text_block_test() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;
let text = "Hello World";
let block_id = test.insert_index(&view.id, text, 1, None).await;
let text_id = test.get_text_id(&view.id, &block_id).await.unwrap();
let delta = test.get_delta(&view.id, &text_id).await;
assert_eq!(delta.unwrap(), json!([{ "insert": text }]).to_string());
}
#[tokio::test]
async fn document_size_test() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;
let max_size = 1024 * 1024; // 1mb
let total_string_size = 500 * 1024; // 500kb
let string_size = 1000;
let iter_len = total_string_size / string_size;
for _ in 0..iter_len {
let s = generate_random_string(string_size);
test.insert_index(&view.id, &s, 1, None).await;
}
let encoded_v1 = test.get_encoded_v1(&view.id).await;
if encoded_v1.doc_state.len() > max_size {
panic!(
"The document size is too large. {}",
encoded_v1.doc_state.len()
);
}
println!("The document size is {}", encoded_v1.doc_state.len());
}
#[tokio::test]
async fn update_block_test() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;
let block_id = test.insert_index(&view.id, "Hello World", 1, None).await;
let data: HashMap<String, Value> = HashMap::from([
(
"bg_color".to_string(),
serde_json::to_value("#000000").unwrap(),
),
(
"text_color".to_string(),
serde_json::to_value("#ffffff").unwrap(),
),
]);
test.update_data(&view.id, &block_id, data.clone()).await;
let block = test.get_block(&view.id, &block_id).await;
assert!(block.is_some());
let block = block.unwrap();
let block_data = json_str_to_hashmap(&block.data).ok().unwrap();
assert_eq!(block_data, data);
}
#[tokio::test]
async fn apply_text_delta_test() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;
let text = "Hello World";
let block_id = test.insert_index(&view.id, text, 1, None).await;
let text_id = test.get_text_id(&view.id, &block_id).await.unwrap();
let delta = test.get_delta(&view.id, &text_id).await;
assert_eq!(
delta.unwrap(),
json!([{ "insert": "Hello World" }]).to_string()
);
}
macro_rules! generate_convert_document_test_cases {
($($json:ident, $text:ident, $html:ident),*) => {
[
$((ParseTypePB { 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,
parse_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);
}
}
/// test convert data to json
/// - input html: <p>Hello</p><p> World!</p>
/// - input plain text: Hello World!
/// - output json: { "type": "page", "data": {}, "children": [{ "type": "paragraph", "children": [], "data": { "delta": [{ "insert": "Hello" }] } }, { "type": "paragraph", "children": [], "data": { "delta": [{ "insert": " World!" }] } }] }
#[tokio::test]
async fn convert_data_to_json_test() {
let test = DocumentEventTest::new().await;
let _ = test.create_document().await;
let html = r#"<p>Hello</p><p>World!</p>"#;
let payload = ConvertDataToJsonPayloadPB {
data: html.to_string(),
input_type: InputType::Html,
};
let result = test.convert_data_to_json(payload).await;
let expect_json = json!({
"type": "page",
"data": {},
"children": [{
"type": "paragraph",
"children": [],
"data": {
"delta": [{ "insert": "Hello" }]
}
}, {
"type": "paragraph",
"children": [],
"data": {
"delta": [{ "insert": "World!" }]
}
}]
});
let expect_json = serde_json::from_value::<NestedBlock>(expect_json).unwrap();
assert!(serde_json::from_str::<NestedBlock>(&result.json)
.unwrap()
.eq(&expect_json));
let plain_text = "Hello\nWorld!";
let payload = ConvertDataToJsonPayloadPB {
data: plain_text.to_string(),
input_type: InputType::PlainText,
};
let result = test.convert_data_to_json(payload).await;
assert!(serde_json::from_str::<NestedBlock>(&result.json)
.unwrap()
.eq(&expect_json));
}