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:
DarkSky
2022-12-30 21:40:15 +08:00
committed by GitHub
parent cc790dcbc2
commit 6c2c7dcd48
296 changed files with 16139 additions and 2072 deletions
@@ -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;
+127
View File
@@ -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;