refactor: crates (#4258)

* chore: rename flowy-folder2 to flowy-folder

* chore: rename flowy-document2 to flowy-document

* chore: fix test

* chore: move lib-infra crate

* chore: remove shared-lib

* chore: fix clippy
This commit is contained in:
Nathan.fooo
2023-12-31 07:29:40 +08:00
committed by GitHub
parent 2f6a4f8c7b
commit 5facb61e23
460 changed files with 498 additions and 11141 deletions

View File

@ -0,0 +1 @@
<meta charset="UTF-8"><ul><li>Highlight<p>You can also</p><ul><li>nest</li></ul></li></ul>

View File

@ -0,0 +1,6 @@
<meta charset="UTF-8"><aside>🥰
Like AppFlowy? Follow us:
<a href="https://github.com/AppFlowy-IO/AppFlowy">GitHub</a>
<a href="https://twitter.com/appflowy">Twitter</a>: @appflowy
<a href="https://blog-appflowy.ghost.io/">Newsletter</a>
</aside>

View File

@ -0,0 +1,5 @@
<meta charset="UTF-8"><pre><code class="language-rust">// This is the main function.
fn main() {
// Print text to the console.
println!("Hello World!");
}</code></pre>

View File

@ -0,0 +1 @@
<meta charset="UTF-8"><hr />

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
<meta charset="UTF-8"><h1>Heading1</h1><h2>Heading2</h2><h3>Heading3</h3>

View File

@ -0,0 +1 @@
<meta charset="UTF-8"><img src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" alt=AppFlowy-Image />

View File

@ -0,0 +1 @@
<meta charset="UTF-8"><p>E = MC^2</p>

View File

@ -0,0 +1,34 @@
<meta charset='utf-8'><h1>The Notion Document</h1>
<h1>Heading-1</h1>
<h2>Heading - 2</h2>
<h3>Heading - 3</h3>
<p>This is a paragraph</p>
<p>paragraphs child</p>
<ul><li>This is a bulleted list - 1<ul><li>This is a bulleted list - 1 - 1</li></ul></li><li>This is a bulleted list - 2</li></ul>
<p>This is a paragraph</p>
<ul><li>[ ] This is a todo - 1<ul><li>[ ] This is a paragraph - 1-1</li></ul></li></ul>
<ol><li>This is a numbered list -1</li></ol>
<p>This is a paragraph</p>
<ul><li><p>This is a toggle list</p><p>This is a toggle child</p></li>
</ul>
<blockquote><p>This is a quote</p><p>This is a quote child</p></blockquote>
<p>This is a paragraph</p>
<hr>
<pre><code class="language-jsx">// This is the main function.
fn main() {
// Print text to the console.
**println**!(&quot;Hello World!&quot;);
}</code></pre>
<p>This is a paragraph</p>
<p>&lt;aside&gt;
💡 callout</p>
<p>&lt;/aside&gt;</p>
<p>This is a paragraph font-color bg-color <strong>bold</strong> <em>italic underline <s>strike-through</s> <code>inline-code</code> $inline-formula$ <a href="https://www.notion.so/The-Notion-Document-d4236da306b84f6199e4091705042d78?pvs=21">link</a></em></p>
<p>$$
|x| = \begin{cases}
x, &amp;\quad x \geq 0 \\
-x, &amp;\quad x &lt; 0
\end{cases}
$$</p>
<p>End</p>
<!-- notionvc: 0b0229d7-b98a-4e36-8a64-f944de21ef0e -->

View File

@ -0,0 +1 @@
<meta charset="UTF-8"><ol><li>Highlight<p>You can also</p><ol><li>nest</li></ol></li></ol>

View File

@ -0,0 +1,6 @@
<meta charset="UTF-8"><p>
Like AppFlowy? Follow us:
<a href="https://github.com/AppFlowy-IO/AppFlowy">GitHub</a>
<a href="https://twitter.com/appflowy">Twitter</a>: @appflowy
<a href="https://blog-appflowy.ghost.io/">Newsletter</a>
</p><p>Click <code>?</code> at the bottom right for help and support.</p><p><span style="background-color: #4dffeb3b;">Highlight </span>any text, and use the editing menu to <span style="font-style: italic;">style</span> <span style="font-weight: bold;">your</span> <span style="text-decoration: underline;">writing</span> <code>however</code><span style="color: #4dffeb3b;"> you </span><span style="text-decoration: line-through;">like.</span><span style="font-family: fantasy;">1+1=2</span></p>

View File

@ -0,0 +1 @@
<meta charset="UTF-8"><blockquote><p>This is a quote</p><p>This is a paragraph</p></blockquote>

View File

@ -0,0 +1 @@
<span style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -webkit-standard; font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none; display: inline !important; float: none;">This is a paragraph</span>

View File

@ -0,0 +1 @@
<meta charset="UTF-8"><ul><li role="checkbox" aria-checked="true">Highlight<p>You can also</p><ul><li role="checkbox" aria-checked="false">nest</li></ul></li></ul>

View File

@ -0,0 +1 @@
<meta charset="UTF-8"><details><summary>Click <code>?</code> at the bottom right for help and support.</summary><p>This is a paragraph</p><details><summary>This is a toggle list</summary></details></details>

View File

@ -0,0 +1,37 @@
{
"type": "page",
"children": [
{
"type": "bulleted_list",
"data": {
"delta": [
{
"insert": "Highlight"
}
]
},
"children": [
{
"type": "paragraph",
"data": {
"delta": [
{
"insert": "You can also"
}
]
}
},
{
"type": "bulleted_list",
"data": {
"delta": [
{
"insert": "nest"
}
]
}
}
]
}
]
}

View File

@ -0,0 +1,32 @@
{
"type": "page",
"data": {},
"children": [
{
"type": "callout",
"data": {
"delta": [
{ "insert": "\nLike AppFlowy? Follow us:\n" },
{
"attributes": {
"href": "https://github.com/AppFlowy-IO/AppFlowy"
},
"insert": "GitHub"
},
{ "insert": "\n" },
{
"attributes": { "href": "https://twitter.com/appflowy" },
"insert": "Twitter"
},
{ "insert": ": @appflowy\n" },
{
"attributes": { "href": "https://blog-appflowy.ghost.io/" },
"insert": "Newsletter"
},
{ "insert": "\n" }
],
"icon": "🥰"
}
}
]
}

View File

@ -0,0 +1,14 @@
{
"type": "page",
"children": [{
"type": "code",
"data": {
"language": "rust",
"delta": [
{
"insert": "// This is the main function.\nfn main() {\n // Print text to the console.\n println!(\"Hello World!\");\n}"
}
]
}
}]
}

View File

@ -0,0 +1,10 @@
{
"type": "page",
"data": {},
"children": [
{
"type": "divider",
"data": {}
}
]
}

View File

@ -0,0 +1,332 @@
{
"children": [
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "The Notion Document"
}
],
"level": 1,
"text_direction": "ltr"
},
"type": "heading"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "Heading-1"
}
],
"level": 1,
"text_direction": "ltr"
},
"type": "heading"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "Heading - 2"
}
],
"level": 2,
"text_direction": "ltr"
},
"type": "heading"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "Heading - 3"
}
],
"level": 3,
"text_direction": "ltr"
},
"type": "heading"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "Heading - 4"
}
],
"level": 3,
"text_direction": "ltr"
},
"type": "heading"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a paragraph"
}
],
"text_direction": "ltr"
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "paragraphs child"
}
],
"text_direction": "ltr"
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a bulleted list - 1"
}
]
},
"type": "bulleted_list"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a bulleted list - 1 - 1"
}
]
},
"type": "bulleted_list"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a bulleted list - 2"
}
]
},
"type": "bulleted_list"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a paragraph"
}
],
"text_direction": "ltr"
},
"type": "paragraph"
},
{
"children": [
{
"children": [],
"data": {
"url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAA1ElEQVR4Ae3bMQ4BURSFYY2xBuwQ7BIkTGxFRj9Oo9RdkXn5TvL3L19u+2ZmZmZmZhVbpH26pFcaJ9IrndMudb/CWadHGiden1bll9MIzqd79SUd0thY20qga4NA50qgoUGgoRJo/NL/V/N+QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEyFeEZyXQpUGgUyXQrkGgTSVQl/qGcG5pnkq3Sn0jOMv0k3Vpm05pmNjfsGPalFyOmZmZmdkbSS9cKbtzhxMAAAAASUVORK5CYII="
},
"type": "image"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a todo - 1"
}
],
"text_direction": "ltr"
},
"type": "paragraph"
}
],
"data": {
"checked": false,
"text_direction": "ltr"
},
"type": "todo_list"
},
{
"children": [
{
"children": [],
"data": {
"url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAA1ElEQVR4Ae3bMQ4BURSFYY2xBuwQ7BIkTGxFRj9Oo9RdkXn5TvL3L19u+2ZmZmZmZhVbpH26pFcaJ9IrndMudb/CWadHGiden1bll9MIzqd79SUd0thY20qga4NA50qgoUGgoRJo/NL/V/N+QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEyFeEZyXQpUGgUyXQrkGgTSVQl/qGcG5pnkq3Sn0jOMv0k3Vpm05pmNjfsGPalFyOmZmZmdkbSS9cKbtzhxMAAAAASUVORK5CYII="
},
"type": "image"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a todo - 1-1"
}
],
"text_direction": "ltr"
},
"type": "paragraph"
}
],
"data": {
"checked": false,
"text_direction": "ltr"
},
"type": "todo_list"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a paragraph"
}
],
"text_direction": "ltr"
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a numbered list -1"
}
]
},
"type": "numbered_list"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a numbered list -2"
}
]
},
"type": "numbered_list"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a numbered list-1-1"
}
]
},
"type": "numbered_list"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a paragraph"
}
],
"text_direction": "ltr"
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": {
"font_color": "#000000"
},
"insert": "This is a paragraph"
}
],
"text_direction": "ltr"
},
"type": "paragraph"
},
{
"children": [],
"data": {},
"type": "divider"
},
{
"children": [],
"data": {},
"type": "paragraph"
}
],
"data": {},
"type": "page"
}

View File

@ -0,0 +1,39 @@
{
"type": "page",
"data": {},
"children": [
{
"type": "heading",
"data": {
"level": 1,
"delta": [
{
"insert": "Heading1"
}
]
}
},
{
"type": "heading",
"data": {
"level": 2,
"delta": [
{
"insert": "Heading2"
}
]
}
},
{
"type": "heading",
"data": {
"level": 3,
"delta": [
{
"insert": "Heading3"
}
]
}
}
]
}

View File

@ -0,0 +1,15 @@
{
"type": "page",
"data": {},
"children": [
{
"type": "image",
"data": {
"url": "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png",
"width": 272,
"height": 92,
"align": "center"
}
}
]
}

View File

@ -0,0 +1,267 @@
{
"type": "page",
"data": {},
"children": [
{
"type": "heading",
"data": { "delta": [{ "insert": "Welcome to AppFlowy!" }], "level": 1 }
},
{
"type": "heading",
"data": { "delta": [{ "insert": "Here are the basics" }], "level": 2 }
},
{
"type": "heading",
"data": { "delta": [{ "insert": "Here is H3" }], "level": 3 }
},
{
"type": "todo_list",
"data": {
"delta": [{ "insert": "Click anywhere and just start typing." }],
"checked": false
},
"children": [
{
"type": "todo_list",
"data": {
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "Enter" },
{ "insert": " to create a new line." }
],
"checked": false
}
}
]
},
{
"type": "todo_list",
"data": {
"checked": false,
"delta": [
{
"attributes": { "bg_color": "0x4dffeb3b" },
"insert": "Highlight "
},
{ "insert": "any text, and use the editing menu to " },
{ "attributes": { "italic": true }, "insert": "style" },
{ "insert": " " },
{ "attributes": { "bold": true }, "insert": "your" },
{ "insert": " " },
{ "attributes": { "underline": true }, "insert": "writing" },
{ "insert": " " },
{ "attributes": { "code": true }, "insert": "however" },
{ "insert": " you " },
{ "attributes": { "strikethrough": true }, "insert": "like." }
]
}
},
{
"type": "todo_list",
"data": {
"checked": false,
"delta": [
{ "insert": "As soon as you type " },
{
"attributes": { "code": true, "font_color": "0xff00b5ff" },
"insert": "/"
},
{ "insert": " a menu will pop up. Select " },
{
"attributes": { "bg_color": "0x4d9c27b0" },
"insert": "different types"
},
{ "insert": " of content blocks you can add." }
]
}
},
{
"type": "todo_list",
"data": {
"delta": [
{ "insert": "Type " },
{ "attributes": { "code": true }, "insert": "/" },
{ "insert": " followed by " },
{ "attributes": { "code": true }, "insert": "/bullet" },
{ "insert": " or " },
{ "attributes": { "code": true }, "insert": "/num" },
{ "attributes": { "code": false }, "insert": " to create a list." }
],
"checked": false
}
},
{
"type": "todo_list",
"data": {
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "+ New Page " },
{
"insert": "button at the bottom of your sidebar to add a new page."
}
],
"checked": true
}
},
{
"type": "todo_list",
"data": {
"checked": false,
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "+" },
{ "insert": " next to any page title in the sidebar to " },
{
"attributes": { "font_color": "0xff8427e0" },
"insert": "quickly"
},
{ "insert": " add a new subpage, " },
{ "attributes": { "code": true }, "insert": "Document" },
{ "attributes": { "code": false }, "insert": ", " },
{ "attributes": { "code": true }, "insert": "Grid" },
{ "attributes": { "code": false }, "insert": ", or " },
{ "attributes": { "code": true }, "insert": "Kanban Board" },
{ "attributes": { "code": false }, "insert": "." }
]
}
},
{ "type": "paragraph", "data": { "delta": [] } },
{ "type": "divider" },
{ "type": "paragraph", "data": { "delta": [] } },
{
"type": "heading",
"data": {
"delta": [{ "insert": "Keyboard shortcuts, markdown, and code block" }],
"level": 2
}
},
{
"type": "numbered_list",
"data": {
"delta": [
{ "insert": "Keyboard shortcuts " },
{
"attributes": {
"href": "https://appflowy.gitbook.io/docs/essential-documentation/shortcuts"
},
"insert": "guide"
}
]
}
},
{
"type": "numbered_list",
"data": {
"delta": [
{ "insert": "Markdown " },
{
"attributes": {
"href": "https://appflowy.gitbook.io/docs/essential-documentation/markdown"
},
"insert": "reference"
}
]
}
},
{
"type": "numbered_list",
"data": {
"delta": [
{ "insert": "Type " },
{ "attributes": { "code": true }, "insert": "/code" },
{
"attributes": { "code": false },
"insert": " to insert a code block"
}
]
}
},
{
"type": "code",
"data": {
"language": "rust",
"delta": [
{
"insert": "// This is the main function.\nfn main() {\n // Print text to the console.\n println!(\"Hello World!\");\n}"
}
]
}
},
{
"type": "paragraph",
"data": { "delta": [] },
"children": [{
"type": "paragraph",
"data": { "delta": [{ "insert": "This is a paragraph" }] },
"children": [{
"type": "paragraph",
"data": { "delta": [{ "insert": "This is a paragraph" }] }
}]
}]
},
{
"type": "heading",
"data": { "level": 2, "delta": [{ "insert": "Have a question❓" }] }
},
{
"type": "toggle_list",
"data": {
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "?" },
{ "insert": " at the bottom right for help and support." }
]
},
"children": [
{
"type": "paragraph",
"data": { "delta": [{ "insert": "This is a paragraph" }] }
},
{
"type": "paragraph",
"data": { "delta": [{ "insert": "This is a paragraph" }] }
}
]
},
{
"type": "quote",
"data": {
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "?" },
{ "insert": " at the bottom right for help and support." }
]
}
},
{ "type": "paragraph", "data": { "delta": [] } },
{
"type": "callout",
"data": {
"delta": [
{ "insert": "\nLike AppFlowy? Follow us:\n" },
{
"attributes": {
"href": "https://github.com/AppFlowy-IO/AppFlowy"
},
"insert": "GitHub"
},
{ "insert": "\n" },
{
"attributes": { "href": "https://twitter.com/appflowy" },
"insert": "Twitter"
},
{ "insert": ": @appflowy\n" },
{
"attributes": { "href": "https://blog-appflowy.ghost.io/" },
"insert": "Newsletter"
},
{ "insert": "\n" }
],
"icon": "🥰"
}
},
{ "type": "paragraph", "data": { "delta": [] } },
{ "type": "paragraph", "data": { "delta": [] } },
{ "type": "paragraph", "data": { "delta": [] } }
]
}

View File

@ -0,0 +1,9 @@
{
"type": "page",
"children": [{
"type": "math_equation",
"data": {
"formula": "E = MC^2"
}
}]
}

View File

@ -0,0 +1,371 @@
{
"type": "page",
"data": {},
"children": [
{
"type": "heading",
"data": {
"delta": [
{
"attributes": null,
"insert": "The Notion Document"
}
],
"level": 1
},
"children": []
},
{
"type": "heading",
"data": {
"level": 1,
"delta": [
{
"attributes": null,
"insert": "Heading-1"
}
]
},
"children": []
},
{
"type": "heading",
"data": {
"level": 2,
"delta": [
{
"attributes": null,
"insert": "Heading - 2"
}
]
},
"children": []
},
{
"type": "heading",
"data": {
"level": 3,
"delta": [
{
"attributes": null,
"insert": "Heading - 3"
}
]
},
"children": []
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph"
}
]
},
"children": []
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "paragraphs child"
}
]
},
"children": []
},
{
"type": "bulleted_list",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a bulleted list - 1"
}
]
},
"children": [
{
"type": "bulleted_list",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a bulleted list - 1 - 1"
}
]
},
"children": []
}
]
},
{
"type": "bulleted_list",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a bulleted list - 2"
}
]
},
"children": []
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph"
}
]
},
"children": []
},
{
"type": "bulleted_list",
"data": {
"delta": [
{
"attributes": null,
"insert": "[ ] This is a todo - 1"
}
]
},
"children": [
{
"type": "bulleted_list",
"data": {
"delta": [
{
"attributes": null,
"insert": "[ ] This is a paragraph - 1-1"
}
]
},
"children": []
}
]
},
{
"type": "numbered_list",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a numbered list -1"
}
]
},
"children": []
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph"
}
]
},
"children": []
},
{
"type": "bulleted_list",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a toggle list"
}
]
},
"children": [
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a toggle child"
}
]
},
"children": []
}
]
},
{
"type": "quote",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a quote"
}
]
},
"children": [
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a quote child"
}
]
},
"children": []
}
]
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph"
}
]
},
"children": []
},
{
"type": "divider",
"data": {},
"children": []
},
{
"type": "code",
"data": {
"delta": [
{
"attributes": null,
"insert": "// This is the main function.\nfn main() {\n // Print text to the console.\n **println**!(\"Hello World!\");\n}"
}
],
"language": "jsx"
},
"children": []
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph"
}
]
},
"children": []
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "<aside>\n 💡 callout"
}
]
},
"children": []
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "</aside>"
}
]
},
"children": []
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph font-color bg-color "
},
{
"attributes": {
"bold": true
},
"insert": "bold"
},
{
"attributes": {
"italic": true
},
"insert": "italic underline "
},
{
"attributes": {
"italic": true,
"strikethrough": true
},
"insert": "strike-through"
},
{
"attributes": {
"code": true,
"italic": true
},
"insert": "inline-code"
},
{
"attributes": {
"italic": true
},
"insert": " $inline-formula$ "
},
{
"attributes": {
"href": "https://www.notion.so/The-Notion-Document-d4236da306b84f6199e4091705042d78?pvs=21",
"italic": true
},
"insert": "link"
}
]
},
"children": []
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "$$\n |x| = \\begin{cases}\n x, &\\quad x \\geq 0 \\\\\n -x, &\\quad x < 0\n \\end{cases}\n $$"
}
]
},
"children": []
},
{
"type": "paragraph",
"data": {
"delta": [
{
"attributes": null,
"insert": "End"
}
]
},
"children": []
}
]
}

View File

@ -0,0 +1,37 @@
{
"type": "page",
"children": [
{
"type": "numbered_list",
"data": {
"delta": [
{
"insert": "Highlight"
}
]
},
"children": [
{
"type": "paragraph",
"data": {
"delta": [
{
"insert": "You can also"
}
]
}
},
{
"type": "numbered_list",
"data": {
"delta": [
{
"insert": "nest"
}
]
}
}
]
}
]
}

View File

@ -0,0 +1,59 @@
{
"type": "page",
"data": {},
"children": [
{
"type": "paragraph",
"data": { "delta": [
{ "insert": "\nLike AppFlowy? Follow us:\n" },
{
"attributes": {
"href": "https://github.com/AppFlowy-IO/AppFlowy"
},
"insert": "GitHub"
},
{ "insert": "\n" },
{
"attributes": { "href": "https://twitter.com/appflowy" },
"insert": "Twitter"
},
{ "insert": ": @appflowy\n" },
{
"attributes": { "href": "https://blog-appflowy.ghost.io/" },
"insert": "Newsletter"
},
{ "insert": "\n" }
]},
"children": [{
"type": "paragraph",
"data": {
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "?" },
{ "insert": " at the bottom right for help and support." }
]
}
}]
},
{
"type": "paragraph",
"data": { "delta": [
{
"attributes": { "bg_color": "0x4dffeb3b" },
"insert": "Highlight "
},
{ "insert": "any text, and use the editing menu to " },
{ "attributes": { "italic": true }, "insert": "style" },
{ "insert": " " },
{ "attributes": { "bold": true }, "insert": "your" },
{ "insert": " " },
{ "attributes": { "underline": true }, "insert": "writing" },
{ "insert": " " },
{ "attributes": { "code": true }, "insert": "however" },
{ "insert": " you ", "attributes": { "font_color": "0x4dffeb3b" } },
{ "attributes": { "strikethrough": true }, "insert": "like." },
{ "attributes": { "formula": true }, "insert": "1+1=2" }
] }
}
]
}

View File

@ -0,0 +1,510 @@
{
"children": [
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "# The Notion Document"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "# Heading-1"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "## Heading - 2"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "### Heading - 3"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "paragraphs child"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "- This is a bulleted list - 1"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": " - This is a bulleted list - 1 - 1"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "- This is a bulleted list - 2"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "- [ ] This is a todo - 1"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": " - [ ] This is a paragraph - 1-1"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "1. This is a numbered list -1"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "- This is a toggle list"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": " This is a toggle child"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "> This is a quote"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": ">"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": ">"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "> This is a quote child"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": ">"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "---"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "```jsx"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "// This is the main function."
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "fn main() {"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": " // Print text to the console."
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": " **println**!(\"Hello World!\");"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "}"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "```"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "<aside>"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "💡 callout"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "</aside>"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "This is a paragraph font-color bg-color **bold** *italic underline ~~strike-through~~ `inline-code` $inline-formula$ [link](https://www.notion.so/The-Notion-Document-d4236da306b84f6199e4091705042d78?pvs=21)*"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "$$"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "|x| = \\begin{cases}             "
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "  x, &\\quad x \\geq 0 \\\\           "
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": " -x, &\\quad x < 0             "
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "\\end{cases}"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "$$"
}
]
},
"type": "paragraph"
},
{
"children": [],
"data": {
"delta": [
{
"attributes": null,
"insert": "End"
}
]
},
"type": "paragraph"
}
],
"data": {},
"type": "page"
}

View File

@ -0,0 +1,25 @@
{
"type": "page",
"children": [
{
"type": "quote",
"data": {
"delta": [
{
"insert": "This is a quote"
}
]
},
"children": [{
"type": "paragraph",
"data": {
"delta": [
{
"insert": "This is a paragraph"
}
]
}
}]
}
]
}

View File

@ -0,0 +1,100 @@
{
"type": "page",
"data": {},
"children": [
{
"type": "heading",
"data": { "delta": [{ "insert": " are the basics" }], "level": 2 }
},
{
"type": "heading",
"data": { "delta": [{ "insert": "Here is H3" }], "level": 3 }
},
{
"type": "todo_list",
"data": {
"delta": [{ "insert": "Click anywhere and just start typing." }],
"checked": false
},
"children": [
{
"type": "todo_list",
"data": {
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "Enter" },
{ "insert": " to create a new line." }
],
"checked": false
}
}
]
},
{
"type": "todo_list",
"data": {
"checked": false,
"delta": [
{
"attributes": { "bg_color": "0x4dffeb3b" },
"insert": "Highlight "
},
{ "insert": "any text, and use the editing menu to " },
{ "attributes": { "italic": true }, "insert": "style" },
{ "insert": " " },
{ "attributes": { "bold": true }, "insert": "your" },
{ "insert": " " },
{ "attributes": { "underline": true }, "insert": "writing" },
{ "insert": " " },
{ "attributes": { "code": true }, "insert": "however" },
{ "insert": " you " },
{ "attributes": { "strikethrough": true }, "insert": "like." }
]
}
},
{
"type": "todo_list",
"data": {
"checked": false,
"delta": [
{ "insert": "As soon as you type " },
{
"attributes": { "code": true, "font_color": "0xff00b5ff" },
"insert": "/"
},
{ "insert": " a menu will pop up. Select " },
{
"attributes": { "bg_color": "0x4d9c27b0" },
"insert": "different types"
},
{ "insert": " of content blocks you can add." }
]
}
},
{
"type": "todo_list",
"data": {
"delta": [
{ "insert": "Type " },
{ "attributes": { "code": true }, "insert": "/" },
{ "insert": " followed by " },
{ "attributes": { "code": true }, "insert": "/bullet" },
{ "insert": " or " },
{ "attributes": { "code": true }, "insert": "/num" },
{ "attributes": { "code": false }, "insert": " to create a list." }
],
"checked": false
}
},
{
"type": "todo_list",
"data": {
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "+ New" }
],
"checked": true
}
}
]
}

View File

@ -0,0 +1,178 @@
{
"type": "page",
"data": {},
"children": [
{
"type": "todo_list",
"data": {
"delta": [
{ "attributes": { "code": true }, "insert": "Enter" },
{ "insert": " to create a new line." }
],
"checked": false
}
},
{
"type": "todo_list",
"data": {
"checked": false,
"delta": [
{
"attributes": { "bg_color": "0x4dffeb3b" },
"insert": "Highlight "
},
{ "insert": "any text, and use the editing menu to " },
{ "attributes": { "italic": true }, "insert": "style" },
{ "insert": " " },
{ "attributes": { "bold": true }, "insert": "your" },
{ "insert": " " },
{ "attributes": { "underline": true }, "insert": "writing" },
{ "insert": " " },
{ "attributes": { "code": true }, "insert": "however" },
{ "insert": " you " },
{ "attributes": { "strikethrough": true }, "insert": "like." }
]
}
},
{
"type": "todo_list",
"data": {
"checked": false,
"delta": [
{ "insert": "As soon as you type " },
{
"attributes": { "code": true, "font_color": "0xff00b5ff" },
"insert": "/"
},
{ "insert": " a menu will pop up. Select " },
{
"attributes": { "bg_color": "0x4d9c27b0" },
"insert": "different types"
},
{ "insert": " of content blocks you can add." }
]
}
},
{
"type": "todo_list",
"data": {
"delta": [
{ "insert": "Type " },
{ "attributes": { "code": true }, "insert": "/" },
{ "insert": " followed by " },
{ "attributes": { "code": true }, "insert": "/bullet" },
{ "insert": " or " },
{ "attributes": { "code": true }, "insert": "/num" },
{ "attributes": { "code": false }, "insert": " to create a list." }
],
"checked": false
}
},
{
"type": "todo_list",
"data": {
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "+ New Page " },
{
"insert": "button at the bottom of your sidebar to add a new page."
}
],
"checked": true
}
},
{
"type": "todo_list",
"data": {
"checked": false,
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "+" },
{ "insert": " next to any page title in the sidebar to " },
{
"attributes": { "font_color": "0xff8427e0" },
"insert": "quickly"
},
{ "insert": " add a new subpage, " },
{ "attributes": { "code": true }, "insert": "Document" },
{ "attributes": { "code": false }, "insert": ", " },
{ "attributes": { "code": true }, "insert": "Grid" },
{ "attributes": { "code": false }, "insert": ", or " },
{ "attributes": { "code": true }, "insert": "Kanban Board" },
{ "attributes": { "code": false }, "insert": "." }
]
}
},
{ "type": "paragraph", "data": { "delta": [] } },
{ "type": "divider" },
{ "type": "paragraph", "data": { "delta": [] } },
{
"type": "heading",
"data": {
"delta": [{ "insert": "Keyboard shortcuts, markdown, and code block" }],
"level": 2
}
},
{
"type": "numbered_list",
"data": {
"delta": [
{ "insert": "Keyboard shortcuts " },
{
"attributes": {
"href": "https://appflowy.gitbook.io/docs/essential-documentation/shortcuts"
},
"insert": "guide"
}
]
}
},
{
"type": "numbered_list",
"data": {
"delta": [
{ "insert": "Markdown " },
{
"attributes": {
"href": "https://appflowy.gitbook.io/docs/essential-documentation/markdown"
},
"insert": "reference"
}
]
}
},
{
"type": "numbered_list",
"data": {
"delta": [
{ "insert": "Type " },
{ "attributes": { "code": true }, "insert": "/code" },
{
"attributes": { "code": false },
"insert": " to insert a code block"
}
]
}
},
{
"type": "code",
"data": {
"language": "rust",
"delta": [
{
"insert": "// This is the main function.\nfn main() {\n // Print text to the console.\n println!(\"Hello World!\");\n}"
}
]
}
},
{
"type": "paragraph",
"data": { "delta": [] },
"children": [{
"type": "paragraph",
"data": { "delta": [{ "insert": "This is a p" }] },
"children": []
}]
}
]
}

View File

@ -0,0 +1,9 @@
{
"type": "page",
"data": {
"delta": [{
"insert": "This is a paragraph"
}]
},
"children": []
}

View File

@ -0,0 +1,39 @@
{
"type": "page",
"children": [
{
"type": "todo_list",
"data": {
"checked": true,
"delta": [
{
"insert": "Highlight"
}
]
},
"children": [
{
"type": "paragraph",
"data": {
"delta": [
{
"insert": "You can also"
}
]
}
},
{
"type": "todo_list",
"checked": false,
"data": {
"delta": [
{
"insert": "nest"
}
]
}
}
]
}
]
}

View File

@ -0,0 +1,25 @@
{
"type": "page",
"children": [
{
"type": "toggle_list",
"data": {
"delta": [
{ "insert": "Click " },
{ "attributes": { "code": true }, "insert": "?" },
{ "insert": " at the bottom right for help and support." }
]
},
"children": [
{
"type": "paragraph",
"data": { "delta": [{ "insert": "This is a paragraph" }] }
},
{
"type": "toggle_list",
"data": { "delta": [{ "insert": "This is a toggle list" }] }
}
]
}
]
}

View File

@ -0,0 +1,3 @@
Highlight
You can also
nest

View File

@ -0,0 +1,6 @@
🥰
Like AppFlowy? Follow us:
GitHub
Twitter: @appflowy
Newsletter

View File

@ -0,0 +1,5 @@
// This is the main function.
fn main() {
// Print text to the console.
println!("Hello World!");
}

View File

@ -0,0 +1,3 @@
Heading1
Heading2
Heading3

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@
E = MC^2

View File

@ -0,0 +1,3 @@
Highlight
You can also
nest

View File

@ -0,0 +1,8 @@
Like AppFlowy? Follow us:
GitHub
Twitter: @appflowy
Newsletter
Click ? at the bottom right for help and support.
Highlight any text, and use the editing menu to style your writing however you like.1+1=2

View File

@ -0,0 +1,64 @@
# The Notion Document
# Heading-1
## Heading - 2
### Heading - 3
This is a paragraph
paragraphs child
- This is a bulleted list - 1
- This is a bulleted list - 1 - 1
- This is a bulleted list - 2
This is a paragraph
- [ ] This is a todo - 1
- [ ] This is a paragraph - 1-1
1. This is a numbered list -1
This is a paragraph
- This is a toggle list
This is a toggle child
> This is a quote
>
>
> This is a quote child
>
This is a paragraph
---
```jsx
// This is the main function.
fn main() {
// Print text to the console.
**println**!("Hello World!");
}
```
This is a paragraph
<aside>
💡 callout
</aside>
This is a paragraph font-color bg-color **bold** *italic underline ~~strike-through~~ `inline-code` $inline-formula$ [link](https://www.notion.so/The-Notion-Document-d4236da306b84f6199e4091705042d78?pvs=21)*
$$
|x| = \begin{cases}             
  x, &\quad x \geq 0 \\           
 -x, &\quad x < 0             
\end{cases}
$$
End

View File

@ -0,0 +1,2 @@
This is a quote
This is a paragraph

View File

@ -0,0 +1,3 @@
Highlight
You can also
nest

View File

@ -0,0 +1,3 @@
Click ? at the bottom right for help and support.
This is a paragraph
This is a toggle list

View File

@ -0,0 +1,39 @@
use std::{collections::HashMap, vec};
use collab_document::blocks::{Block, BlockAction, BlockActionPayload, BlockActionType};
use collab_document::document_data::PARAGRAPH_BLOCK_TYPE;
use crate::document::util;
use crate::document::util::gen_id;
#[tokio::test]
async fn document_apply_insert_block_with_empty_parent_id() {
let (_, document, page_id) = util::create_and_open_empty_document().await;
// create a text block with no parent
let text_block_id = gen_id();
let text_block = Block {
id: text_block_id.clone(),
ty: PARAGRAPH_BLOCK_TYPE.to_string(),
parent: "".to_string(),
children: gen_id(),
external_id: None,
external_type: None,
data: HashMap::new(),
};
let insert_text_action = BlockAction {
action: BlockActionType::Insert,
payload: BlockActionPayload {
block: Some(text_block),
parent_id: Some(page_id.clone()),
prev_id: None,
delta: None,
text_id: None,
},
};
document.lock().apply_action(vec![insert_text_action]);
// read the text block and it's parent id should be the page id
let block = document.lock().get_block(&text_block_id).unwrap();
assert_eq!(block.parent, page_id);
}

View File

@ -0,0 +1,61 @@
use std::collections::HashMap;
use collab_document::blocks::{Block, BlockAction, BlockActionPayload, BlockActionType};
use collab_document::document_data::{default_document_data, PARAGRAPH_BLOCK_TYPE};
use crate::document::util::{gen_document_id, gen_id, DocumentTest};
#[tokio::test]
async fn undo_redo_test() {
let test = DocumentTest::new();
let doc_id: String = gen_document_id();
let data = default_document_data();
// create a document
_ = test
.create_document(test.user.user_id().unwrap(), &doc_id, Some(data.clone()))
.await;
// open a document
let document = test.get_document(&doc_id).await.unwrap();
let document = document.lock();
let page_block = document.get_block(&data.page_id).unwrap();
let page_id = page_block.id;
let text_block_id = gen_id();
// insert a text block
let text_block = Block {
id: text_block_id.clone(),
ty: PARAGRAPH_BLOCK_TYPE.to_string(),
parent: page_id.clone(),
children: gen_id(),
external_id: None,
external_type: None,
data: HashMap::new(),
};
let insert_text_action = BlockAction {
action: BlockActionType::Insert,
payload: BlockActionPayload {
block: Some(text_block),
parent_id: Some(page_id),
prev_id: None,
delta: None,
text_id: None,
},
};
document.apply_action(vec![insert_text_action]);
let can_undo = document.can_undo();
assert!(can_undo);
// undo the insert
let undo = document.undo();
assert!(undo);
assert_eq!(document.get_block(&text_block_id), None);
let can_redo = document.can_redo();
assert!(can_redo);
// redo the insert
let redo = document.redo();
assert!(redo);
}

View File

@ -0,0 +1,217 @@
use std::{collections::HashMap, vec};
use collab_document::blocks::{Block, BlockAction, BlockActionPayload, BlockActionType};
use collab_document::document_data::{default_document_data, PARAGRAPH_BLOCK_TYPE};
use serde_json::{json, to_value, Value};
use crate::document::util::{gen_document_id, gen_id, DocumentTest};
#[tokio::test]
async fn restore_document() {
let test = DocumentTest::new();
// create a document
let doc_id: String = gen_document_id();
let data = default_document_data();
let uid = test.user.user_id().unwrap();
test
.create_document(uid, &doc_id, Some(data.clone()))
.await
.unwrap();
let data_a = test.get_document_data(&doc_id).await.unwrap();
assert_eq!(data_a, data);
let data_b = test
.get_document(&doc_id)
.await
.unwrap()
.lock()
.get_document_data()
.unwrap();
// close a document
_ = test.close_document(&doc_id).await;
assert_eq!(data_b, data);
// restore
_ = test.create_document(uid, &doc_id, Some(data.clone())).await;
// open a document
let data_b = test
.get_document(&doc_id)
.await
.unwrap()
.lock()
.get_document_data()
.unwrap();
// close a document
_ = test.close_document(&doc_id).await;
assert_eq!(data_b, data);
}
#[tokio::test]
async fn document_apply_insert_action() {
let test = DocumentTest::new();
let uid = test.user.user_id().unwrap();
let doc_id: String = gen_document_id();
let data = default_document_data();
// create a document
_ = test.create_document(uid, &doc_id, Some(data.clone())).await;
// open a document
let document = test.get_document(&doc_id).await.unwrap();
let page_block = document.lock().get_block(&data.page_id).unwrap();
// insert a text block
let text_block = Block {
id: gen_id(),
ty: PARAGRAPH_BLOCK_TYPE.to_string(),
parent: page_block.id,
children: gen_id(),
external_id: None,
external_type: None,
data: HashMap::new(),
};
let insert_text_action = BlockAction {
action: BlockActionType::Insert,
payload: BlockActionPayload {
parent_id: None,
prev_id: None,
block: Some(text_block),
delta: None,
text_id: None,
},
};
document.lock().apply_action(vec![insert_text_action]);
let data_a = document.lock().get_document_data().unwrap();
// close the original document
_ = test.close_document(&doc_id).await;
// re-open the document
let data_b = test
.get_document(&doc_id)
.await
.unwrap()
.lock()
.get_document_data()
.unwrap();
// close a document
_ = test.close_document(&doc_id).await;
assert_eq!(data_b, data_a);
}
#[tokio::test]
async fn document_apply_update_page_action() {
let test = DocumentTest::new();
let doc_id: String = gen_document_id();
let uid = test.user.user_id().unwrap();
let data = default_document_data();
// create a document
_ = test.create_document(uid, &doc_id, Some(data.clone())).await;
// open a document
let document = test.get_document(&doc_id).await.unwrap();
let page_block = document.lock().get_block(&data.page_id).unwrap();
let mut page_block_clone = page_block;
page_block_clone.data = HashMap::new();
page_block_clone.data.insert(
"delta".to_string(),
to_value(json!([{"insert": "Hello World!"}])).unwrap(),
);
let action = BlockAction {
action: BlockActionType::Update,
payload: BlockActionPayload {
parent_id: None,
prev_id: None,
block: Some(page_block_clone),
delta: None,
text_id: None,
},
};
let actions = vec![action];
tracing::trace!("{:?}", &actions);
document.lock().apply_action(actions);
let page_block_old = document.lock().get_block(&data.page_id).unwrap();
_ = test.close_document(&doc_id).await;
// re-open the document
let document = test.get_document(&doc_id).await.unwrap();
let page_block_new = document.lock().get_block(&data.page_id).unwrap();
assert_eq!(page_block_old, page_block_new);
assert!(page_block_new.data.contains_key("delta"));
}
#[tokio::test]
async fn document_apply_update_action() {
let test = DocumentTest::new();
let uid = test.user.user_id().unwrap();
let doc_id: String = gen_document_id();
let data = default_document_data();
// create a document
_ = test.create_document(uid, &doc_id, Some(data.clone())).await;
// open a document
let document = test.get_document(&doc_id).await.unwrap();
let page_block = document.lock().get_block(&data.page_id).unwrap();
// insert a text block
let text_block_id = gen_id();
let text_block = Block {
id: text_block_id.clone(),
ty: PARAGRAPH_BLOCK_TYPE.to_string(),
parent: page_block.id,
children: gen_id(),
external_id: None,
external_type: None,
data: HashMap::new(),
};
let insert_text_action = BlockAction {
action: BlockActionType::Insert,
payload: BlockActionPayload {
block: Some(text_block),
parent_id: None,
prev_id: None,
delta: None,
text_id: None,
},
};
document.lock().apply_action(vec![insert_text_action]);
// update the text block
let existing_text_block = document.lock().get_block(&text_block_id).unwrap();
let mut updated_text_block_data = HashMap::new();
updated_text_block_data.insert("delta".to_string(), Value::String("delta".to_string()));
let updated_text_block = Block {
id: existing_text_block.id,
ty: existing_text_block.ty,
parent: existing_text_block.parent,
children: existing_text_block.children,
external_id: None,
external_type: None,
data: updated_text_block_data.clone(),
};
let update_text_action = BlockAction {
action: BlockActionType::Update,
payload: BlockActionPayload {
block: Some(updated_text_block),
parent_id: None,
prev_id: None,
delta: None,
text_id: None,
},
};
document.lock().apply_action(vec![update_text_action]);
// close the original document
_ = test.close_document(&doc_id).await;
// re-open the document
let document = test.get_document(&doc_id).await.unwrap();
let block = document.lock().get_block(&text_block_id).unwrap();
assert_eq!(block.data, updated_text_block_data);
// close a document
_ = test.close_document(&doc_id).await;
}

View File

@ -0,0 +1,32 @@
use flowy_document::{
entities::{ConvertDataPayloadPB, ConvertType},
event_handler::convert_data_to_document_internal,
};
#[test]
fn convert_json_to_document() {
let json_str = r#"
{
"type": "page",
"children": [
{
"type": "paragraph1"
}
]
}"#;
let payload = ConvertDataPayloadPB {
convert_type: ConvertType::Json,
data: json_str.as_bytes().to_vec(),
};
let document_data = convert_data_to_document_internal(payload).unwrap();
let page_id = document_data.page_id;
let blocks = document_data.blocks;
let children_map = document_data.meta.children_map;
let page_block = blocks.get(&page_id).unwrap();
let page_children = children_map.get(&page_block.children_id).unwrap();
assert_eq!(page_children.children.len(), 1);
let paragraph1 = blocks.get(page_children.children.first().unwrap()).unwrap();
assert_eq!(paragraph1.ty, "paragraph1");
assert_eq!(paragraph1.parent_id, page_block.id);
}

View File

@ -0,0 +1,5 @@
mod document_insert_test;
mod document_redo_undo_test;
mod document_test;
mod event_handler_test;
pub mod util;

View File

@ -0,0 +1,192 @@
use std::ops::Deref;
use std::sync::Arc;
use anyhow::Error;
use bytes::Bytes;
use collab::core::collab::CollabDocState;
use collab::preclude::CollabPlugin;
use collab_document::blocks::DocumentData;
use collab_document::document_data::default_document_data;
use nanoid::nanoid;
use parking_lot::Once;
use tempfile::TempDir;
use tracing_subscriber::{fmt::Subscriber, util::SubscriberInitExt, EnvFilter};
use uuid::Uuid;
use collab_integrate::collab_builder::{
AppFlowyCollabBuilder, CollabCloudPluginProvider, CollabPluginProviderContext,
CollabPluginProviderType,
};
use collab_integrate::RocksCollabDB;
use flowy_document::document::MutexDocument;
use flowy_document::manager::{DocumentManager, DocumentUser};
use flowy_document_deps::cloud::*;
use flowy_error::FlowyError;
use flowy_storage::{FileStorageService, StorageObject};
use lib_infra::async_trait::async_trait;
use lib_infra::future::{to_fut, Fut, FutureResult};
pub struct DocumentTest {
inner: DocumentManager,
}
impl DocumentTest {
pub fn new() -> Self {
let user = FakeUser::new();
let cloud_service = Arc::new(LocalTestDocumentCloudServiceImpl());
let file_storage = Arc::new(DocumentTestFileStorageService) as Arc<dyn FileStorageService>;
let manager = DocumentManager::new(
Arc::new(user),
default_collab_builder(),
cloud_service,
Arc::downgrade(&file_storage),
);
Self { inner: manager }
}
}
impl Deref for DocumentTest {
type Target = DocumentManager;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
pub struct FakeUser {
collab_db: Arc<RocksCollabDB>,
}
impl FakeUser {
pub fn new() -> Self {
setup_log();
let tempdir = TempDir::new().unwrap();
let path = tempdir.into_path();
let collab_db = Arc::new(RocksCollabDB::open(path).unwrap());
Self { collab_db }
}
}
impl DocumentUser for FakeUser {
fn user_id(&self) -> Result<i64, FlowyError> {
Ok(1)
}
fn workspace_id(&self) -> Result<String, FlowyError> {
Ok(Uuid::new_v4().to_string())
}
fn token(&self) -> Result<Option<String>, FlowyError> {
Ok(None)
}
fn collab_db(&self, _uid: i64) -> Result<std::sync::Weak<RocksCollabDB>, FlowyError> {
Ok(Arc::downgrade(&self.collab_db))
}
}
pub fn setup_log() {
static START: Once = Once::new();
START.call_once(|| {
std::env::set_var("RUST_LOG", "collab_persistence=trace");
let subscriber = Subscriber::builder()
.with_env_filter(EnvFilter::from_default_env())
.with_ansi(true)
.finish();
subscriber.try_init().unwrap();
});
}
pub fn default_collab_builder() -> Arc<AppFlowyCollabBuilder> {
let builder =
AppFlowyCollabBuilder::new(DefaultCollabStorageProvider(), "fake_device_id".to_string());
builder.initialize(uuid::Uuid::new_v4().to_string());
Arc::new(builder)
}
pub async fn create_and_open_empty_document() -> (DocumentTest, Arc<MutexDocument>, String) {
let test = DocumentTest::new();
let doc_id: String = gen_document_id();
let data = default_document_data();
let uid = test.user.user_id().unwrap();
// create a document
test
.create_document(uid, &doc_id, Some(data.clone()))
.await
.unwrap();
let document = test.get_document(&doc_id).await.unwrap();
(test, document, data.page_id)
}
pub fn gen_document_id() -> String {
let uuid = uuid::Uuid::new_v4();
uuid.to_string()
}
pub fn gen_id() -> String {
nanoid!(10)
}
pub struct LocalTestDocumentCloudServiceImpl();
impl DocumentCloudService for LocalTestDocumentCloudServiceImpl {
fn get_document_doc_state(
&self,
_document_id: &str,
_workspace_id: &str,
) -> FutureResult<CollabDocState, FlowyError> {
FutureResult::new(async move { Ok(vec![]) })
}
fn get_document_snapshots(
&self,
_document_id: &str,
_limit: usize,
_workspace_id: &str,
) -> FutureResult<Vec<DocumentSnapshot>, Error> {
FutureResult::new(async move { Ok(vec![]) })
}
fn get_document_data(
&self,
_document_id: &str,
_workspace_id: &str,
) -> FutureResult<Option<DocumentData>, Error> {
FutureResult::new(async move { Ok(None) })
}
}
pub struct DocumentTestFileStorageService;
impl FileStorageService for DocumentTestFileStorageService {
fn create_object(&self, _object: StorageObject) -> FutureResult<String, FlowyError> {
todo!()
}
fn delete_object_by_url(&self, _object_url: String) -> FutureResult<(), FlowyError> {
todo!()
}
fn get_object_by_url(&self, _object_url: String) -> FutureResult<Bytes, FlowyError> {
todo!()
}
}
struct DefaultCollabStorageProvider();
#[async_trait]
impl CollabCloudPluginProvider for DefaultCollabStorageProvider {
fn provider_type(&self) -> CollabPluginProviderType {
CollabPluginProviderType::Local
}
fn get_plugins(&self, _context: CollabPluginProviderContext) -> Fut<Vec<Arc<dyn CollabPlugin>>> {
to_fut(async move { vec![] })
}
fn is_sync_enabled(&self) -> bool {
false
}
}

View File

@ -0,0 +1,2 @@
mod document;
mod parser;

View File

@ -0,0 +1,105 @@
use collab_document::blocks::DocumentData;
use flowy_document::parser::document_data_parser::DocumentDataParser;
use flowy_document::parser::json::parser::JsonToDocumentParser;
use flowy_document::parser::parser_entities::{NestedBlock, Range, Selection};
use std::sync::Arc;
#[tokio::test]
async fn document_data_parse_json_test() {
let initial_json_str = include_str!("../assets/json/initial_document.json");
let document_data = JsonToDocumentParser::json_str_to_document(initial_json_str)
.unwrap()
.into();
let parser = DocumentDataParser::new(Arc::new(document_data), None);
let read_me_json = serde_json::from_str::<NestedBlock>(initial_json_str).unwrap();
let json = parser.to_json().unwrap();
assert_eq!(read_me_json, json);
}
// range_1 is a range from the 2nd block to the 8th block
#[tokio::test]
async fn document_data_to_json_with_range_1_test() {
let initial_json_str = include_str!("../assets/json/initial_document.json");
let document_data: DocumentData = JsonToDocumentParser::json_str_to_document(initial_json_str)
.unwrap()
.into();
let children_map = &document_data.meta.children_map;
let page_block_id = &document_data.page_id;
let blocks = &document_data.blocks;
let page_block = blocks.get(page_block_id).unwrap();
let children = children_map.get(page_block.children.as_str()).unwrap();
let range = Range {
start: Selection {
block_id: children.get(1).unwrap().to_string(),
index: 4,
length: 15,
},
end: Selection {
block_id: children.get(7).unwrap().to_string(),
index: 0,
length: 11,
},
};
let parser = DocumentDataParser::new(Arc::new(document_data), Some(range));
let json = parser.to_json().unwrap();
let part_1 = include_str!("../assets/json/range_1.json");
let part_1_json = serde_json::from_str::<NestedBlock>(part_1).unwrap();
assert_eq!(part_1_json, json);
}
// range_2 is a range from the 4th block's first child to the 18th block's first child
#[tokio::test]
async fn document_data_to_json_with_range_2_test() {
let initial_json_str = include_str!("../assets/json/initial_document.json");
let document_data: DocumentData = JsonToDocumentParser::json_str_to_document(initial_json_str)
.unwrap()
.into();
let children_map = &document_data.meta.children_map;
let page_block_id = &document_data.page_id;
let blocks = &document_data.blocks;
let page_block = blocks.get(page_block_id).unwrap();
let start_block_parent_id = children_map
.get(page_block.children.as_str())
.unwrap()
.get(3)
.unwrap();
let start_block_parent = blocks.get(start_block_parent_id).unwrap();
let start_block_id = children_map
.get(start_block_parent.children.as_str())
.unwrap()
.first()
.unwrap();
let start = Selection {
block_id: start_block_id.to_string(),
index: 6,
length: 27,
};
let end_block_parent_id = children_map
.get(page_block.children.as_str())
.unwrap()
.get(17)
.unwrap();
let end_block_parent = blocks.get(end_block_parent_id).unwrap();
let end_block_children = children_map
.get(end_block_parent.children.as_str())
.unwrap();
let end_block_id = end_block_children.first().unwrap();
let end = Selection {
block_id: end_block_id.to_string(),
index: 0,
length: 11,
};
let range = Range { start, end };
let parser = DocumentDataParser::new(Arc::new(document_data), Some(range));
let json = parser.to_json().unwrap();
let part_2 = include_str!("../assets/json/range_2.json");
let part_2_json = serde_json::from_str::<NestedBlock>(part_2).unwrap();
assert_eq!(part_2_json, json);
}

View File

@ -0,0 +1 @@
mod parser_test;

View File

@ -0,0 +1,45 @@
use flowy_document::parser::external::parser::ExternalDataToNestedJSONParser;
use flowy_document::parser::parser_entities::{InputType, NestedBlock};
macro_rules! generate_test_cases {
($($ty:ident),*) => {
[
$(
(
include_str!(concat!("../../assets/json/", stringify!($ty), ".json")),
include_str!(concat!("../../assets/html/", stringify!($ty), ".html")),
)
),*
]
};
}
/// test convert data to json
/// - input html: <p>Hello</p><p> World!</p>
#[tokio::test]
async fn html_to_document_test() {
let test_cases = generate_test_cases!(notion, google_docs, simple);
for (json, html) in test_cases.iter() {
let parser = ExternalDataToNestedJSONParser::new(html.to_string(), InputType::Html);
let block = parser.to_nested_block();
assert!(block.is_some());
let block = block.unwrap();
let expect_block = serde_json::from_str::<NestedBlock>(json).unwrap();
assert_eq!(block, expect_block);
}
}
/// test convert data to json
/// - input plain text: Hello World!
#[tokio::test]
async fn plain_text_to_document_test() {
let plain_text = include_str!("../../assets/text/plain_text.txt");
let parser = ExternalDataToNestedJSONParser::new(plain_text.to_string(), InputType::PlainText);
let block = parser.to_nested_block();
assert!(block.is_some());
let block = block.unwrap();
let expect_json = include_str!("../../assets/json/plain_text.json");
let expect_block = serde_json::from_str::<NestedBlock>(expect_json).unwrap();
assert_eq!(block, expect_block);
}

View File

@ -0,0 +1,103 @@
use serde_json::json;
use flowy_document::parser::json::block::SerdeBlock;
#[test]
fn test_empty_data_and_children() {
let json = json!({
"type": "page",
});
let block = serde_json::from_value::<SerdeBlock>(json).unwrap();
assert_eq!(block.ty, "page");
assert!(block.data.is_empty());
assert!(block.children.is_empty());
}
#[test]
fn test_data() {
let json = json!({
"type": "todo_list",
"data": {
"delta": [{ "insert": "Click anywhere and just start typing." }],
"checked": false
}
});
let block = serde_json::from_value::<SerdeBlock>(json).unwrap();
assert_eq!(block.ty, "todo_list");
assert_eq!(block.data.len(), 2);
assert_eq!(block.data.get("checked").unwrap(), false);
assert_eq!(
block.data.get("delta").unwrap().to_owned(),
json!([{ "insert": "Click anywhere and just start typing." }])
);
assert!(block.children.is_empty());
}
#[test]
fn test_children() {
let json = json!({
"type": "page",
"children": [
{
"type": "heading",
"data": {
"delta": [{ "insert": "Welcome to AppFlowy!" }],
"level": 1
}
},
{
"type": "todo_list",
"data": {
"delta": [{ "insert": "Welcome to AppFlowy!" }],
"checked": false
}
}
]});
let block = serde_json::from_value::<SerdeBlock>(json).unwrap();
assert!(block.data.is_empty());
assert_eq!(block.ty, "page");
assert_eq!(block.children.len(), 2);
// heading
let heading = &block.children[0];
assert_eq!(heading.ty, "heading");
assert_eq!(heading.data.len(), 2);
// todo_list
let todo_list = &block.children[1];
assert_eq!(todo_list.ty, "todo_list");
assert_eq!(todo_list.data.len(), 2);
}
#[test]
fn test_nested_children() {
let json = json!({
"type": "page",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "paragraph"
}
]
}
]
}
]
}
]
});
let block = serde_json::from_value::<SerdeBlock>(json).unwrap();
assert!(block.data.is_empty());
assert_eq!(block.ty, "page");
assert_eq!(
block.children[0].children[0].children[0].children[0].ty,
"paragraph"
);
}

View File

@ -0,0 +1,2 @@
mod block_test;
mod parser_test;

View File

@ -0,0 +1,123 @@
use collab_document::blocks::json_str_to_hashmap;
use flowy_document::parser::json::parser::JsonToDocumentParser;
use serde_json::json;
#[test]
fn test_parser_children_in_order() {
let json = json!({
"type": "page",
"children": [
{
"type": "paragraph1",
},
{
"type": "paragraph2",
},
{
"type": "paragraph3",
},
{
"type": "paragraph4",
}
]
});
let document = JsonToDocumentParser::json_str_to_document(json.to_string().as_str()).unwrap();
// root + 4 paragraphs
assert_eq!(document.blocks.len(), 5);
// root + 4 paragraphs
assert_eq!(document.meta.children_map.len(), 5);
let (page_id, page_block) = document
.blocks
.iter()
.find(|(_, block)| block.ty == "page")
.unwrap();
// the children should be in order
let page_children = document
.meta
.children_map
.get(page_block.children_id.as_str())
.unwrap();
assert_eq!(page_children.children.len(), 4);
for (i, child_id) in page_children.children.iter().enumerate() {
let child = document.blocks.get(child_id).unwrap();
assert_eq!(child.ty, format!("paragraph{}", i + 1));
assert_eq!(child.parent_id, page_id.to_owned());
}
}
#[test]
fn test_parser_nested_children() {
let json = json!({
"type": "page",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "paragraph"
}
]
}
]
}
]
}
]
});
let document = JsonToDocumentParser::json_str_to_document(json.to_string().as_str()).unwrap();
// root + 4 paragraphs
assert_eq!(document.blocks.len(), 5);
// root + 4 paragraphs
assert_eq!(document.meta.children_map.len(), 5);
let (page_id, page_block) = document
.blocks
.iter()
.find(|(_, block)| block.ty == "page")
.unwrap();
// first child of root is a paragraph
let page_children = document
.meta
.children_map
.get(page_block.children_id.as_str())
.unwrap();
assert_eq!(page_children.children.len(), 1);
let page_first_child_id = page_children.children.first().unwrap();
let page_first_child = document.blocks.get(page_first_child_id).unwrap();
assert_eq!(page_first_child.ty, "paragraph");
assert_eq!(page_first_child.parent_id, page_id.to_owned());
}
#[tokio::test]
async fn parse_readme_test() {
let json = include_str!("../../../../flowy-core/assets/read_me.json");
let document = JsonToDocumentParser::json_str_to_document(json).unwrap();
document.blocks.iter().for_each(|(_, block)| {
let data = json_str_to_hashmap(&block.data).ok();
assert!(data.is_some());
if let Some(data) = data {
assert!(data.get("delta").is_none());
}
if let Some(external_id) = &block.external_id {
let text = document.meta.text_map.get(external_id);
assert!(text.is_some());
}
});
}

View File

@ -0,0 +1,4 @@
mod document_data_parser_test;
mod html;
mod json;
mod parse_to_html_text;

View File

@ -0,0 +1,2 @@
mod test;
mod utils;

View File

@ -0,0 +1,37 @@
use crate::parser::parse_to_html_text::utils::{assert_document_html_eq, assert_document_text_eq};
macro_rules! generate_test_cases {
($($block_ty:ident),*) => {
[
$(
(
include_str!(concat!("../../assets/json/", stringify!($block_ty), ".json")),
include_str!(concat!("../../assets/html/", stringify!($block_ty), ".html")),
include_str!(concat!("../../assets/text/", stringify!($block_ty), ".txt")),
)
),*
]
};
}
#[tokio::test]
async fn block_tests() {
let test_cases = generate_test_cases!(
heading,
callout,
paragraph,
divider,
image,
math_equation,
code,
bulleted_list,
numbered_list,
todo_list,
toggle_list,
quote
);
for (json_data, expect_html, expect_text) in test_cases.iter() {
assert_document_html_eq(json_data, expect_html);
assert_document_text_eq(json_data, expect_text);
}
}

View File

@ -0,0 +1,21 @@
use flowy_document::parser::document_data_parser::DocumentDataParser;
use flowy_document::parser::json::parser::JsonToDocumentParser;
use std::sync::Arc;
pub fn assert_document_html_eq(source: &str, expect: &str) {
let document_data = JsonToDocumentParser::json_str_to_document(source)
.unwrap()
.into();
let parser = DocumentDataParser::new(Arc::new(document_data), None);
let html = parser.to_html();
assert_eq!(expect, html);
}
pub fn assert_document_text_eq(source: &str, expect: &str) {
let document_data = JsonToDocumentParser::json_str_to_document(source)
.unwrap()
.into();
let parser = DocumentDataParser::new(Arc::new(document_data), None);
let text = parser.to_text();
assert_eq!(expect, text);
}