mirror of https://github.com/fantasticit/think.git
feat: add support for offline edit
This commit is contained in:
parent
ca7f18e640
commit
2ad6518a51
|
@ -78,6 +78,7 @@
|
|||
"scroll-into-view-if-needed": "^2.2.29",
|
||||
"swr": "^1.2.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"y-indexeddb": "^9.0.7",
|
||||
"y-prosemirror": "^1.0.14",
|
||||
"yjs": "^13.5.24"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import { Banner as SemiBanner } from '@douyinfe/semi-ui';
|
||||
import { BannerProps } from '@douyinfe/semi-ui/banner';
|
||||
import { useToggle } from 'hooks/useToggle';
|
||||
|
||||
interface IProps extends BannerProps {
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export const Banner: React.FC<IProps> = ({ type, description, duration }) => {
|
||||
const timer = useRef<ReturnType<typeof setTimeout>>();
|
||||
const [visible, toggleVisible] = useToggle(true);
|
||||
|
||||
useEffect(() => {
|
||||
clearTimeout(timer.current);
|
||||
if (duration <= 0) return;
|
||||
|
||||
timer.current = setTimeout(() => {
|
||||
toggleVisible(false);
|
||||
}, duration);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer.current);
|
||||
};
|
||||
}, [duration]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return <SemiBanner type="success" description="以为您恢复上一次离线时编辑数据。 " />;
|
||||
};
|
|
@ -1,20 +1,24 @@
|
|||
import React, { useMemo, useEffect } from 'react';
|
||||
import React, { useMemo, useEffect, useState } from 'react';
|
||||
import cls from 'classnames';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import { BackTop } from '@douyinfe/semi-ui';
|
||||
import { ILoginUser, IAuthority } from '@think/domains';
|
||||
import { useToggle } from 'hooks/useToggle';
|
||||
import {
|
||||
MenuBar,
|
||||
DEFAULT_EXTENSION,
|
||||
DocumentWithTitle,
|
||||
getCollaborationExtension,
|
||||
getCollaborationCursorExtension,
|
||||
getProvider,
|
||||
destoryProvider,
|
||||
MenuBar,
|
||||
ProviderStatus,
|
||||
getIndexdbProvider,
|
||||
destoryIndexdbProvider,
|
||||
} from 'components/tiptap';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { joinUser } from 'components/document/collaboration';
|
||||
import { Banner } from 'components/banner';
|
||||
import { debounce } from 'helpers/debounce';
|
||||
import { changeTitle } from './index';
|
||||
import styles from './index.module.scss';
|
||||
|
@ -29,7 +33,7 @@ interface IProps {
|
|||
|
||||
export const Editor: React.FC<IProps> = ({ user, documentId, authority, className, style }) => {
|
||||
if (!user) return null;
|
||||
|
||||
const [status, setStatus] = useState<ProviderStatus>('connecting');
|
||||
const provider = useMemo(() => {
|
||||
return getProvider({
|
||||
targetId: documentId,
|
||||
|
@ -63,16 +67,23 @@ export const Editor: React.FC<IProps> = ({ user, documentId, authority, classNam
|
|||
const [loading, toggleLoading] = useToggle(true);
|
||||
|
||||
useEffect(() => {
|
||||
const indexdbProvider = getIndexdbProvider(documentId, provider.document);
|
||||
|
||||
indexdbProvider.on('synced', () => {
|
||||
setStatus('loadCacheSuccess');
|
||||
});
|
||||
|
||||
provider.on('synced', () => {
|
||||
toggleLoading(false);
|
||||
});
|
||||
|
||||
provider.on('status', async ({ status }) => {
|
||||
console.log('status', status);
|
||||
setStatus(status);
|
||||
});
|
||||
|
||||
return () => {
|
||||
destoryProvider(provider, 'EDITOR');
|
||||
destoryIndexdbProvider(documentId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@ -83,6 +94,13 @@ export const Editor: React.FC<IProps> = ({ user, documentId, authority, classNam
|
|||
normalContent={() => {
|
||||
return (
|
||||
<div className={styles.editorWrap}>
|
||||
{status === 'disconnected' && (
|
||||
<Banner
|
||||
type="warning"
|
||||
description="我们已与您断开连接,您可以继续编辑文档。一旦重新连接,我们会自动重新提交数据。
|
||||
"
|
||||
/>
|
||||
)}
|
||||
<header className={className}>
|
||||
<div>
|
||||
<MenuBar editor={editor} />
|
||||
|
|
|
@ -9,6 +9,7 @@ import { BaseKit } from './basekit';
|
|||
export { getSchema } from '@tiptap/core';
|
||||
export * from './menubar';
|
||||
export * from './provider';
|
||||
export * from './indexdb';
|
||||
export * from './skeleton';
|
||||
|
||||
export const DocumentWithTitle = Document.extend({
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// const provider = new IndexeddbPersistence(docName, ydoc);
|
||||
|
||||
// provider.on('synced', () => {
|
||||
// console.log('content from the database is loaded');
|
||||
// });
|
||||
|
||||
import { IndexeddbPersistence } from 'y-indexeddb';
|
||||
|
||||
const POOL = new Map();
|
||||
|
||||
export const getIndexdbProvider = (name, doc) => {
|
||||
if (!POOL.has(name)) {
|
||||
POOL.set(name, new IndexeddbPersistence(name, doc));
|
||||
}
|
||||
|
||||
return POOL.get(name);
|
||||
};
|
||||
|
||||
export const destoryIndexdbProvider = (name) => {
|
||||
const provider = POOL.get(name);
|
||||
|
||||
if (!provider) return;
|
||||
|
||||
provider.destroy();
|
||||
POOL.delete(name);
|
||||
};
|
|
@ -4,6 +4,8 @@ import { IUser } from '@think/domains';
|
|||
const PROVIDER_POOL_READER = new Map();
|
||||
const PROVIDER_POOL_EDITOR = new Map();
|
||||
|
||||
export type ProviderStatus = 'connecting' | 'connected' | 'disconnected' | 'loadCacheSuccess';
|
||||
|
||||
export const getProvider = ({
|
||||
targetId,
|
||||
token,
|
||||
|
|
|
@ -116,6 +116,7 @@ importers:
|
|||
tippy.js: ^6.3.7
|
||||
tsconfig-paths-webpack-plugin: ^3.5.2
|
||||
typescript: 4.5.5
|
||||
y-indexeddb: ^9.0.7
|
||||
y-prosemirror: ^1.0.14
|
||||
yjs: ^13.5.24
|
||||
dependencies:
|
||||
|
@ -188,6 +189,7 @@ importers:
|
|||
scroll-into-view-if-needed: 2.2.29
|
||||
swr: 1.2.0_react@17.0.2
|
||||
tippy.js: 6.3.7
|
||||
y-indexeddb: 9.0.7_yjs@13.5.24
|
||||
y-prosemirror: 1.0.14_0fedec857d2fb730ad5b02a71124bf2a
|
||||
yjs: 13.5.24
|
||||
devDependencies:
|
||||
|
@ -8500,6 +8502,15 @@ packages:
|
|||
engines: {node: '>=0.4'}
|
||||
dev: false
|
||||
|
||||
/y-indexeddb/9.0.7_yjs@13.5.24:
|
||||
resolution: {integrity: sha512-58rDlwtRgXucgR9Kxc49AepAR6uGNGfcvPPAMrmMfSvRBQ/tPnx6NoHNyrRkhbALEAjV9tPEAIP0a/KkVqAIyA==}
|
||||
peerDependencies:
|
||||
yjs: ^13.0.0
|
||||
dependencies:
|
||||
lib0: 0.2.47
|
||||
yjs: 13.5.24
|
||||
dev: false
|
||||
|
||||
/y-prosemirror/1.0.14_0fedec857d2fb730ad5b02a71124bf2a:
|
||||
resolution: {integrity: sha512-fJQn/XT+z/gks9sd64eB+Mf1UQP0d5SHQ8RwbrzIwYFW+FgU/nx8/hJm+nrBAoO9azHOY5aDeoffeLmOFXLD9w==}
|
||||
peerDependencies:
|
||||
|
|
Loading…
Reference in New Issue