mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-19 02:56:23 +08:00
milestone: publish alpha version (#637)
- document folder - full-text search - blob storage - basic edgeless support Co-authored-by: tzhangchi <terry.zhangchi@outlook.com> Co-authored-by: QiShaoXuan <qishaoxuan777@gmail.com> Co-authored-by: DiamondThree <diamond.shx@gmail.com> Co-authored-by: MingLiang Wang <mingliangwang0o0@gmail.com> Co-authored-by: JimmFly <yangjinfei001@gmail.com> Co-authored-by: Yifeng Wang <doodlewind@toeverything.info> Co-authored-by: Himself65 <himself65@outlook.com> Co-authored-by: lawvs <18554747+lawvs@users.noreply.github.com> Co-authored-by: Qi <474021214@qq.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useAppState, PageMeta } from '@/providers/app-state-provider';
|
||||
|
||||
export type ChangePageMeta = (
|
||||
pageId: string,
|
||||
pageMeta: Partial<PageMeta>
|
||||
) => void;
|
||||
|
||||
export const useChangePageMeta = () => {
|
||||
const { currentWorkspace } = useAppState();
|
||||
|
||||
return useCallback<ChangePageMeta>(
|
||||
(pageId, pageMeta) => {
|
||||
currentWorkspace?.setPageMeta(pageId, {
|
||||
...pageMeta,
|
||||
});
|
||||
},
|
||||
[currentWorkspace]
|
||||
);
|
||||
};
|
||||
|
||||
export default ChangePageMeta;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useAppState, PageMeta } from '@/providers/app-state-provider';
|
||||
|
||||
export const useCurrentPageMeta = (): PageMeta | null => {
|
||||
const { currentPage, currentWorkspace } = useAppState();
|
||||
|
||||
const pageMetaHandler = useCallback((): PageMeta | null => {
|
||||
if (!currentPage || !currentWorkspace) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
(currentWorkspace.meta.pageMetas.find(
|
||||
p => p.id === currentPage.id
|
||||
) as PageMeta) ?? null
|
||||
);
|
||||
}, [currentPage, currentWorkspace]);
|
||||
|
||||
const [currentPageMeta, setCurrentPageMeta] = useState<PageMeta | null>(
|
||||
pageMetaHandler
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPageMeta(pageMetaHandler);
|
||||
|
||||
const dispose = currentWorkspace?.meta.pagesUpdated.on(() => {
|
||||
setCurrentPageMeta(pageMetaHandler);
|
||||
}).dispose;
|
||||
|
||||
return () => {
|
||||
dispose?.();
|
||||
};
|
||||
}, [currentPage, currentWorkspace, pageMetaHandler]);
|
||||
|
||||
return currentPageMeta;
|
||||
};
|
||||
|
||||
export default useCurrentPageMeta;
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
const defaultOutLineWorkspaceId =
|
||||
'local-first-' + '85b4ca0b9081421d903bbc2501ea280f';
|
||||
// It is a fully effective hook
|
||||
// Cause it not just ensure workspace loaded, but also have router change.
|
||||
export const useEnsureWorkspace = () => {
|
||||
const [workspaceLoaded, setWorkspaceLoaded] = useState(false);
|
||||
const { workspacesMeta, loadWorkspace, synced, user } = useAppState();
|
||||
const router = useRouter();
|
||||
|
||||
// const defaultOutLineWorkspaceId = '99ce7eb7';
|
||||
// console.log(defaultOutLineWorkspaceId);
|
||||
useEffect(() => {
|
||||
if (!synced) {
|
||||
setWorkspaceLoaded(false);
|
||||
return;
|
||||
}
|
||||
// If router.query.workspaceId is not in workspace list, jump to 404 page
|
||||
// If workspacesMeta is empty, we need to create a default workspace but not jump to 404
|
||||
if (
|
||||
workspacesMeta.length &&
|
||||
router.query.workspaceId &&
|
||||
workspacesMeta.findIndex(
|
||||
meta => meta.id.toString() === router.query.workspaceId
|
||||
) === -1
|
||||
) {
|
||||
router.push('/404');
|
||||
return;
|
||||
}
|
||||
// If user is not login and input a custom workspaceId, jump to 404 page
|
||||
if (
|
||||
!user &&
|
||||
router.query.workspaceId &&
|
||||
router.query.workspaceId !== defaultOutLineWorkspaceId
|
||||
) {
|
||||
router.push('/404');
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceId = user
|
||||
? (router.query.workspaceId as string) || workspacesMeta?.[0]?.id
|
||||
: defaultOutLineWorkspaceId;
|
||||
|
||||
loadWorkspace(workspaceId).finally(() => {
|
||||
setWorkspaceLoaded(true);
|
||||
});
|
||||
}, [loadWorkspace, router, synced, user, workspacesMeta]);
|
||||
|
||||
return {
|
||||
workspaceLoaded,
|
||||
};
|
||||
};
|
||||
|
||||
export default useEnsureWorkspace;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Page } from '@blocksuite/store';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export type EventCallBack<T> = (callback: (props: T) => void) => void;
|
||||
export type UseHistoryUpdated = (page?: Page) => EventCallBack<Page>;
|
||||
|
||||
export const useHistoryUpdate: UseHistoryUpdated = () => {
|
||||
const { currentPage } = useAppState();
|
||||
|
||||
const callbackQueue = useRef<((page: Page) => void)[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
currentPage.signals.historyUpdated.on(() => {
|
||||
callbackQueue.current.forEach(callback => {
|
||||
callback(currentPage);
|
||||
});
|
||||
});
|
||||
}, 300);
|
||||
|
||||
return () => {
|
||||
callbackQueue.current = [];
|
||||
currentPage.signals.historyUpdated.dispose();
|
||||
};
|
||||
}, [currentPage]);
|
||||
|
||||
return callback => {
|
||||
callbackQueue.current.push(callback);
|
||||
};
|
||||
};
|
||||
|
||||
export default useHistoryUpdate;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export const useInitWorkspace = (disabled?: boolean) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
// Do not set as a constant, or it will trigger a hell of re-rendering
|
||||
const defaultOutLineWorkspaceId = useRef(new Date().getTime().toString());
|
||||
|
||||
const router = useRouter();
|
||||
const {
|
||||
workspacesMeta,
|
||||
loadWorkspace,
|
||||
currentWorkspace,
|
||||
currentWorkspaceId,
|
||||
} = useAppState();
|
||||
const workspaceId =
|
||||
(router.query.workspaceId as string) ||
|
||||
workspacesMeta?.[0]?.id ||
|
||||
defaultOutLineWorkspaceId.current;
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
loadWorkspace(workspaceId).finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [workspaceId, disabled, loadWorkspace]);
|
||||
|
||||
return {
|
||||
workspaceId,
|
||||
workspace: workspaceId === currentWorkspaceId ? currentWorkspace : null,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
|
||||
export type UseLocalStorage = <S>(
|
||||
key: string,
|
||||
initialState: S | (() => S),
|
||||
// If initialState is different from the initial local data, set it
|
||||
initialValue?: S
|
||||
) => [S, Dispatch<SetStateAction<S>>];
|
||||
|
||||
export const useLocalStorage: UseLocalStorage = (
|
||||
key,
|
||||
defaultValue,
|
||||
initialValue
|
||||
) => {
|
||||
const [item, setItem] = useState(defaultValue);
|
||||
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem(key);
|
||||
if (saved) {
|
||||
setItem(JSON.parse(saved));
|
||||
} else if (initialValue) {
|
||||
setItem(initialValue);
|
||||
}
|
||||
}, [initialValue, key, setItem]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(key, JSON.stringify(item));
|
||||
}, [item, key]);
|
||||
|
||||
return [item, setItem];
|
||||
};
|
||||
|
||||
export default useLocalStorage;
|
||||
@@ -0,0 +1,127 @@
|
||||
import { Workspace, uuidv4 } from '@blocksuite/store';
|
||||
import { QueryContent } from '@blocksuite/store/dist/workspace/search';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
import { useChangePageMeta } from '@/hooks/use-change-page-meta';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
export type EditorHandlers = {
|
||||
createPage: (params?: {
|
||||
pageId?: string;
|
||||
title?: string;
|
||||
}) => Promise<string | null>;
|
||||
openPage: (
|
||||
pageId: string,
|
||||
query?: { [key: string]: string },
|
||||
newTab?: boolean
|
||||
) => Promise<boolean>;
|
||||
getPageMeta: (pageId: string) => PageMeta | null;
|
||||
toggleDeletePage: (pageId: string) => Promise<boolean>;
|
||||
toggleFavoritePage: (pageId: string) => Promise<boolean>;
|
||||
permanentlyDeletePage: (pageId: string) => void;
|
||||
search: (query: QueryContent) => Map<string, string | undefined>;
|
||||
// changeEditorMode: (pageId: string) => void;
|
||||
changePageMode: (
|
||||
pageId: string,
|
||||
mode: EditorContainer['mode']
|
||||
) => Promise<EditorContainer['mode']>;
|
||||
};
|
||||
|
||||
const getPageMeta = (workspace: Workspace | null, pageId: string) => {
|
||||
return workspace?.meta.pageMetas.find(p => p.id === pageId);
|
||||
};
|
||||
export const usePageHelper = (): EditorHandlers => {
|
||||
const router = useRouter();
|
||||
const changePageMeta = useChangePageMeta();
|
||||
const { currentWorkspace, editor, currentWorkspaceId } = useAppState();
|
||||
|
||||
return {
|
||||
createPage: ({
|
||||
pageId = uuidv4().replaceAll('-', ''),
|
||||
title = '',
|
||||
} = {}) => {
|
||||
return new Promise(resolve => {
|
||||
if (!currentWorkspace) {
|
||||
return resolve(null);
|
||||
}
|
||||
currentWorkspace.createPage(pageId);
|
||||
currentWorkspace.signals.pageAdded.once(addedPageId => {
|
||||
currentWorkspace.setPageMeta(addedPageId, { title });
|
||||
resolve(addedPageId);
|
||||
});
|
||||
});
|
||||
},
|
||||
toggleFavoritePage: async pageId => {
|
||||
const pageMeta = getPageMeta(currentWorkspace, pageId);
|
||||
if (!pageMeta) {
|
||||
return Promise.reject('No page');
|
||||
}
|
||||
const favorite = !pageMeta.favorite;
|
||||
changePageMeta(pageMeta.id, {
|
||||
favorite,
|
||||
});
|
||||
return favorite;
|
||||
},
|
||||
toggleDeletePage: async pageId => {
|
||||
const pageMeta = getPageMeta(currentWorkspace, pageId);
|
||||
|
||||
if (!pageMeta) {
|
||||
return Promise.reject('No page');
|
||||
}
|
||||
const trash = !pageMeta.trash;
|
||||
|
||||
changePageMeta(pageMeta.id, {
|
||||
trash,
|
||||
trashDate: +new Date(),
|
||||
});
|
||||
return trash;
|
||||
},
|
||||
search: (query: QueryContent) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return currentWorkspace!.search(query);
|
||||
},
|
||||
changePageMode: async (pageId, mode) => {
|
||||
const pageMeta = getPageMeta(currentWorkspace, pageId);
|
||||
if (!pageMeta) {
|
||||
return Promise.reject('No page');
|
||||
}
|
||||
|
||||
editor?.setAttribute('mode', mode as string);
|
||||
|
||||
changePageMeta(pageMeta.id, {
|
||||
mode,
|
||||
});
|
||||
return mode;
|
||||
},
|
||||
permanentlyDeletePage: pageId => {
|
||||
// TODO: workspace.meta.removePage or workspace.removePage?
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
currentWorkspace!.meta.removePage(pageId);
|
||||
},
|
||||
openPage: (pageId, query = {}, newTab = false) => {
|
||||
pageId = pageId.replace('space:', '');
|
||||
|
||||
if (newTab) {
|
||||
window.open(`/workspace/${currentWorkspaceId}/${pageId}`, '_blank');
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return router.push({
|
||||
pathname: `/workspace/${currentWorkspaceId}/${pageId}`,
|
||||
query,
|
||||
});
|
||||
},
|
||||
getPageMeta: pageId => {
|
||||
if (!currentWorkspace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
(currentWorkspace.meta.pageMetas.find(
|
||||
page => page.id === pageId
|
||||
) as PageMeta) || null
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default usePageHelper;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { PageMeta } from '@/providers/app-state-provider';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
|
||||
export const usePageMetaList = () => {
|
||||
const { currentWorkspace } = useAppState();
|
||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentWorkspace) {
|
||||
return;
|
||||
}
|
||||
setPageList(currentWorkspace.meta.pageMetas as PageMeta[]);
|
||||
const dispose = currentWorkspace.meta.pagesUpdated.on(() => {
|
||||
setPageList(currentWorkspace.meta.pageMetas as PageMeta[]);
|
||||
}).dispose;
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}, [currentWorkspace]);
|
||||
|
||||
return pageList;
|
||||
};
|
||||
|
||||
export default usePageMetaList;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
export type EventCallBack<T> = (callback: (props: T) => void) => void;
|
||||
|
||||
export type UsePropsUpdated = (
|
||||
editor?: EditorContainer
|
||||
) => EventCallBack<EditorContainer>;
|
||||
|
||||
export const usePropsUpdated: UsePropsUpdated = () => {
|
||||
const { editor } = useAppState();
|
||||
|
||||
const callbackQueue = useRef<((editor: EditorContainer) => void)[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
editor.model?.propsUpdated.on(() => {
|
||||
callbackQueue.current.forEach(callback => {
|
||||
callback(editor);
|
||||
});
|
||||
});
|
||||
}, 300);
|
||||
|
||||
return () => {
|
||||
callbackQueue.current = [];
|
||||
editor?.model?.propsUpdated.dispose();
|
||||
};
|
||||
}, [editor]);
|
||||
|
||||
return callback => {
|
||||
callbackQueue.current.push(callback);
|
||||
};
|
||||
};
|
||||
|
||||
export default usePropsUpdated;
|
||||
Reference in New Issue
Block a user