fix: slash command panel bugs (#3722)

This commit is contained in:
Kilu.He 2023-10-23 10:25:47 +08:00 committed by GitHub
parent bf0cfa798c
commit 7e101b8bf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 130 additions and 119 deletions

View File

@ -853,7 +853,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"async-trait",
@ -872,7 +872,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"async-trait",
@ -902,7 +902,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"proc-macro2",
"quote",
@ -914,7 +914,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"collab",
@ -934,7 +934,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"bytes",
@ -948,7 +948,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"chrono",
@ -990,7 +990,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"async-trait",
"bincode",
@ -1011,7 +1011,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"async-trait",
@ -1038,7 +1038,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"collab",

View File

@ -48,14 +48,14 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "52b
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" }

View File

@ -27,6 +27,7 @@ import { slashCommandActions } from '$app_reducers/document/slice';
import { Keyboard } from '$app/constants/document/keyboard';
import { selectOptionByUpDown } from '$app/utils/document/menu';
import { turnToBlockThunk } from '$app_reducers/document/async-actions';
import {useTranslation} from "react-i18next";
function BlockSlashMenu({
id,
@ -42,6 +43,7 @@ function BlockSlashMenu({
container: HTMLDivElement;
}) {
const dispatch = useAppDispatch();
const { t } = useTranslation()
const ref = useRef<HTMLDivElement | null>(null);
const { docId, controller } = useSubscribeDocument();
const handleInsert = useCallback(
@ -279,6 +281,12 @@ function BlockSlashMenu({
[dispatch, docId]
);
const renderEmptyContent = useCallback(() => {
return <div className={'m-5 text-text-caption flex justify-center items-center'}>
{t('findAndReplace.noResult')}
</div>
}, [t]);
return (
<div
onMouseDown={(e) => {
@ -288,7 +296,7 @@ function BlockSlashMenu({
className={'flex h-[100%] max-h-[40vh] w-[324px] min-w-[180px] max-w-[calc(100vw-32px)] flex-col p-1'}
>
<div ref={ref} className={'min-h-0 flex-1 overflow-y-auto overflow-x-hidden'}>
{Object.entries(optionsByGroup).map(([group, options]) => (
{options.length === 0 ? renderEmptyContent(): Object.entries(optionsByGroup).map(([group, options]) => (
<div key={group}>
<div className={'px-2 py-2 text-sm text-text-caption'}>{group}</div>
<div>

View File

@ -33,12 +33,6 @@ export function useBlockSlash() {
setAnchorPosition(undefined);
}, [blockId, visible]);
useEffect(() => {
if (!slashText) {
dispatch(slashCommandActions.closeSlashCommand(docId));
}
}, [dispatch, docId, slashText]);
const searchText = useMemo(() => {
if (!slashText) return '';
if (slashText[0] !== '/') return slashText;
@ -66,7 +60,7 @@ export function useSubscribeSlash() {
const slashCommandState = useSubscribeSlashState();
const visible = slashCommandState.isSlashCommand;
const blockId = slashCommandState.blockId;
const { searchText } = useSubscribePanelSearchText({ blockId: '', open: visible });
const { searchText } = useSubscribePanelSearchText({ blockId: blockId || '', open: visible });
return {
visible,

View File

@ -4,11 +4,9 @@ import { useAppDispatch } from '$app/stores/store';
import { turnToBlockThunk } from '$app_reducers/document/async-actions';
import { blockConfig } from '$app/constants/document/config';
import Delta, { Op } from 'quill-delta';
import Delta from 'quill-delta';
import { useRangeRef } from '$app/components/document/_shared/SubscribeSelection.hooks';
import { getBlock, getBlockDelta } from '$app/components/document/_shared/SubscribeNode.hooks';
import isHotkey from 'is-hotkey';
import { slashCommandActions } from '$app_reducers/document/slice';
import { getBlockDelta } from '$app/components/document/_shared/SubscribeNode.hooks';
import { getDeltaText } from '$app/utils/document/delta';
import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks';
import { turnIntoConfig } from './shortchut';
@ -212,26 +210,9 @@ export function useTurnIntoBlockEvents(id: string) {
dispatch(turnToBlockThunk({ id, data, type: BlockType.EquationBlock, controller }));
},
},
{
// Here custom slash key event for TextBlock
canHandle: (e: React.KeyboardEvent<HTMLDivElement>) => {
const flag = getFlag();
return isHotkey('/', e) && flag === '';
},
handler: (_: React.KeyboardEvent<HTMLDivElement>) => {
if (!controller) return;
dispatch(
slashCommandActions.openSlashCommand({
blockId: id,
docId,
})
);
},
},
}
];
}, [canHandle, controller, dispatch, docId, getAttrs, getDeltaContent, getFlag, id, spaceTriggerMap]);
}, [canHandle, controller, dispatch, getAttrs, getDeltaContent, id, spaceTriggerMap]);
return turnIntoBlockEvents;
}

View File

@ -13,9 +13,10 @@ function PageInline({ pageId }: { pageId: string }) {
const { t } = useTranslation();
const page = useAppSelector((state) => state.pages.pageMap[pageId]);
const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState<Page | null>(null);
const [currentPage, setCurrentPage] = useState<Page | null>(page);
const loadPage = useCallback(async (id: string) => {
const controller = new PageController(id);
const page = await controller.getPage();
setCurrentPage(page);
}, []);
@ -29,13 +30,15 @@ function PageInline({ pageId }: { pageId: string }) {
);
useEffect(() => {
if (page) {
if (!page) {
loadPage(pageId);
} else {
setCurrentPage(page);
return;
}
void loadPage(pageId);
}, [page, loadPage, pageId]);
return currentPage ? (
<Tooltip arrow title={t('document.mention.page.tooltip')} placement={'top'}>
<span

View File

@ -18,6 +18,14 @@ export function useSubscribeNode(id: string) {
}>((state) => {
const documentState = state[DOCUMENT_NAME][docId];
const node = documentState?.nodes[id];
// if node is root, return page name
if (!node?.parent) {
const delta = state.pages?.pageMap[docId]?.name;
return {
node,
delta: delta ? JSON.stringify(new Delta().insert(delta)) : '',
};
}
const externalId = node?.externalId;
return {

View File

@ -6,7 +6,7 @@ import { getDeltaText } from '$app/utils/document/delta';
export function useSubscribePanelSearchText({ blockId, open }: { blockId: string; open: boolean }) {
const [searchText, setSearchText] = useState<string>('');
const beforeOpenDeltaRef = useRef<Op[]>([]);
const { delta } = useSubscribeNode(blockId);
const { delta: deltaStr } = useSubscribeNode(blockId);
const handleSearch = useCallback((newDelta: Delta) => {
const diff = new Delta(beforeOpenDeltaRef.current).diff(newDelta);
const text = getDeltaText(diff);
@ -15,20 +15,22 @@ export function useSubscribePanelSearchText({ blockId, open }: { blockId: string
}, []);
useEffect(() => {
if (!open || !delta) return;
handleSearch(new Delta(JSON.parse(delta)));
}, [handleSearch, delta, open]);
if (!open || !deltaStr) return;
handleSearch(new Delta(JSON.parse(deltaStr)));
}, [handleSearch, deltaStr, open]);
useEffect(() => {
if (!open) {
beforeOpenDeltaRef.current = [];
return;
}
if (beforeOpenDeltaRef.current.length > 0) return;
beforeOpenDeltaRef.current = new Delta(JSON.parse(delta)).ops;
handleSearch(new Delta(JSON.parse(delta)));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
const delta = new Delta(JSON.parse(deltaStr || "{}"));
beforeOpenDeltaRef.current = delta.ops;
handleSearch(delta);
}, [deltaStr, handleSearch, open]);
return {
searchText,

View File

@ -56,6 +56,7 @@ export class PageController {
getPage = async (id?: string): Promise<Page> => {
const result = await this.backendService.getPage(id || this.id);
if (result.ok) {
return parserViewPBToPage(result.val);
}

View File

@ -34,26 +34,11 @@ export const insertAfterNodeThunk = createAsyncThunk(
const actions = [];
let newNodeId;
const deltaOperator = new BlockDeltaOperator(documentState, controller);
if (defaultDelta) {
newNodeId = generateId();
actions.push(
...deltaOperator.getNewTextLineActions({
blockId: newNodeId,
parentId,
prevId: node.id,
delta: defaultDelta,
type,
})
);
} else {
if (type === BlockType.DividerBlock) {
const newNode = newBlock<any>(type, parentId, data);
actions.push(controller.getInsertAction(newNode, node.id));
newNodeId = newNode.id;
}
if (type === BlockType.DividerBlock) {
const nodeId = generateId();
actions.push(
@ -66,6 +51,24 @@ export const insertAfterNodeThunk = createAsyncThunk(
})
);
newNodeId = nodeId;
} else {
if (defaultDelta) {
newNodeId = generateId();
actions.push(
...deltaOperator.getNewTextLineActions({
blockId: newNodeId,
parentId,
prevId: node.id,
delta: defaultDelta,
type,
})
);
} else {
const newNode = newBlock<any>(type, parentId, data);
actions.push(controller.getInsertAction(newNode, node.id));
newNodeId = newNode.id;
}
}
await controller.applyActions(actions);

View File

@ -8,6 +8,7 @@ import { updatePageName } from '$app_reducers/pages/async_actions';
import { getDeltaText } from '$app/utils/document/delta';
import { BlockDeltaOperator } from '$app/utils/document/block_delta';
import { openMention, closeMention } from '$app_reducers/document/async-actions/mention';
import {slashCommandActions} from "$app_reducers/document/slice";
const updateNodeDeltaAfterThunk = createAsyncThunk(
'document/updateNodeDeltaAfter',
@ -16,7 +17,7 @@ const updateNodeDeltaAfterThunk = createAsyncThunk(
thunkAPI
) => {
const { dispatch } = thunkAPI;
const { docId, ops, oldDelta, newDelta } = payload;
const { docId, ops, oldDelta, newDelta, id } = payload;
const insertOps = ops.filter((op) => op.insert !== undefined);
const deleteOps = ops.filter((op) => op.delete !== undefined);
@ -24,13 +25,32 @@ const updateNodeDeltaAfterThunk = createAsyncThunk(
const newText = getDeltaText(newDelta);
const deleteText = oldText.slice(newText.length);
if (insertOps.length === 1 && insertOps[0].insert === '@') {
dispatch(openMention({ docId }));
if (insertOps.length === 1) {
const char = insertOps[0].insert;
if (char === '@' && (oldText.endsWith(' ') || oldText === '')) {
dispatch(openMention({ docId }));
}
if (char === '/') {
dispatch(
slashCommandActions.openSlashCommand({
blockId: id,
docId,
})
);
}
}
if (deleteOps.length === 1 && deleteText === '@') {
dispatch(closeMention({ docId }));
if (deleteOps.length === 1) {
if (deleteText === '@') {
dispatch(closeMention({ docId }));
}
if (deleteText === '/') {
dispatch(
slashCommandActions.closeSlashCommand(docId)
);
}
}
}
);
@ -44,13 +64,6 @@ export const updateNodeDeltaThunk = createAsyncThunk(
const docState = state[DOCUMENT_NAME][docId];
const node = docState.nodes[id];
const deltaOperator = new BlockDeltaOperator(docState, controller);
const oldDelta = deltaOperator.getDeltaWithBlockId(id);
if (!oldDelta) return;
const diff = oldDelta?.diff(newDelta);
if (ops.length === 0 || diff?.ops.length === 0) return;
// If the node is the root node, update the page name
if (!node.parent) {
await dispatch(
@ -62,7 +75,13 @@ export const updateNodeDeltaThunk = createAsyncThunk(
return;
}
if (!node.externalId) return;
const deltaOperator = new BlockDeltaOperator(docState, controller);
const oldDelta = deltaOperator.getDeltaWithBlockId(id);
if (!oldDelta) return;
const diff = oldDelta?.diff(newDelta);
if (ops.length === 0 || diff?.ops.length === 0 || !node.externalId) return;
await controller.applyTextDelta(node.externalId, JSON.stringify(ops));
await dispatch(updateNodeDeltaAfterThunk({ docId, id, ops, newDelta, oldDelta, controller }));

View File

@ -62,24 +62,16 @@ export const formatMention = createAsyncThunk(
const caret = rangeState.caret;
if (!caret) return;
const index = caret.index - searchTextLength;
const charLength = searchTextLength + 1;
const index = caret.index - charLength;
const deltaOperator = new BlockDeltaOperator(documentState);
const deltaOperator = new BlockDeltaOperator(documentState, controller);
const nodeDelta = deltaOperator.getDeltaWithBlockId(blockId);
if (!nodeDelta) return;
const diffDelta = new Delta()
.retain(index)
.delete(searchTextLength)
.insert(`@`, {
mention: {
type,
[type]: value,
},
});
const diffDelta = new Delta().retain(index).delete(charLength).insert('@',{ mention: { type, [type]: value } });
const applyTextDeltaAction = deltaOperator.getApplyDeltaAction(blockId, diffDelta);
if (!applyTextDeltaAction) return;
await controller.applyActions([applyTextDeltaAction]);
dispatch(

View File

@ -61,7 +61,7 @@ export const turnToBlockThunk = createAsyncThunk(
parentId: parent.id,
prevId: block.id || null,
delta: delta ? delta : new Delta([{ insert: '' }]),
type,
type: BlockType.TextBlock,
data,
});

View File

@ -720,7 +720,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"async-trait",
@ -739,7 +739,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"async-trait",
@ -769,7 +769,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"proc-macro2",
"quote",
@ -781,7 +781,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"collab",
@ -801,7 +801,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"bytes",
@ -815,7 +815,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"chrono",
@ -857,7 +857,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"async-trait",
"bincode",
@ -878,7 +878,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"async-trait",
@ -905,7 +905,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2f5734ae#2f5734ae9e58efd611383b78cd9068d514643e75"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded"
dependencies = [
"anyhow",
"collab",

View File

@ -92,11 +92,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "52b
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2f5734ae" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" }