|
|
@@ -12,6 +12,8 @@ import detectIndent from 'detect-indent';
|
|
|
import { useTranslation } from 'next-i18next';
|
|
|
import { useRouter } from 'next/router';
|
|
|
import { throttle, debounce } from 'throttle-debounce';
|
|
|
+import { SocketIOProvider } from 'y-socket.io';
|
|
|
+import * as Y from 'yjs';
|
|
|
|
|
|
import { useUpdateStateAfterSave, useSaveOrUpdate } from '~/client/services/page-operation';
|
|
|
import { apiGet, apiPostForm } from '~/client/util/apiv1-client';
|
|
|
@@ -468,16 +470,83 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
|
|
|
}, [mutateCurrentPage, mutateEditingMarkdown, mutateIsConflict, mutateTagsInfo, syncTagsInfoForEditor]);
|
|
|
|
|
|
+ const [ydoc, setYdoc] = useState<Y.Doc | null>(null);
|
|
|
+ const [provider, setProvider] = useState<SocketIOProvider | null>(null);
|
|
|
+ const [cPageId, setCPageId] = useState(pageId);
|
|
|
+
|
|
|
+ // cleanup ydoc and provider
|
|
|
+ useEffect(() => {
|
|
|
+ if (cPageId === pageId) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!provider || !ydoc || socket == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ydoc.destroy();
|
|
|
+ setYdoc(null);
|
|
|
+
|
|
|
+ provider.destroy();
|
|
|
+ provider.disconnect();
|
|
|
+ setProvider(null);
|
|
|
+
|
|
|
+ socket.off('sync:ydoc');
|
|
|
+
|
|
|
+ setCPageId(pageId);
|
|
|
+ }, [cPageId, pageId, provider, socket, ydoc]);
|
|
|
+
|
|
|
+ // setup ydoc
|
|
|
+ useEffect(() => {
|
|
|
+ if (ydoc != null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const _ydoc = new Y.Doc();
|
|
|
+ setYdoc(_ydoc);
|
|
|
+ }, [initialValue, ydoc]);
|
|
|
+
|
|
|
+ // setup socketIOProvider
|
|
|
+ useEffect(() => {
|
|
|
+ if (ydoc == null || provider != null || socket == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const socketIOProvider = new SocketIOProvider(
|
|
|
+ 'ws://localhost:3000',
|
|
|
+ `yjs/${pageId}`,
|
|
|
+ ydoc,
|
|
|
+ { autoConnect: true },
|
|
|
+ );
|
|
|
+ // TODO: apply provider awareness
|
|
|
+ // https://redmine.weseek.co.jp/issues/130770
|
|
|
+ socketIOProvider.awareness.setLocalState({ id: Math.random(), name: 'Perico' });
|
|
|
+ socketIOProvider.on('sync', (isSync: boolean) => {
|
|
|
+ // TODO: check behavior when ydoc diff is retrieved once from server but sync is false exactly 5 seconds later
|
|
|
+ setTimeout(() => {
|
|
|
+ if (!isSync) {
|
|
|
+ ydoc.getText('codemirror').insert(0, initialValue);
|
|
|
+ }
|
|
|
+ }, 5000);
|
|
|
+
|
|
|
+ if (isSync) {
|
|
|
+ socket.emit('sync:ydoc', { pageId, initialValue });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ socketIOProvider.on('status', ({ status: _status }: { status: string }) => {
|
|
|
+ if (_status) console.log(_status);
|
|
|
+ });
|
|
|
+ setProvider(socketIOProvider);
|
|
|
+ }, [initialValue, pageId, provider, socket, ydoc]);
|
|
|
|
|
|
// initialize
|
|
|
useEffect(() => {
|
|
|
- if (initialValue == null) {
|
|
|
+ if (ydoc == null) {
|
|
|
return;
|
|
|
}
|
|
|
- codeMirrorEditor?.initDoc(initialValue);
|
|
|
- setMarkdownToPreview(initialValue);
|
|
|
+ codeMirrorEditor?.initDoc(ydoc.getText('codemirror').toString());
|
|
|
+ setMarkdownToPreview(ydoc.getText('codemirror').toString());
|
|
|
mutateIsEnabledUnsavedWarning(false);
|
|
|
- }, [codeMirrorEditor, initialValue, mutateIsEnabledUnsavedWarning]);
|
|
|
+ }, [codeMirrorEditor, initialValue, mutateIsEnabledUnsavedWarning, ydoc]);
|
|
|
|
|
|
// initial caret line
|
|
|
useEffect(() => {
|
|
|
@@ -536,9 +605,9 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
// when transitioning to a different page, if the initialValue is the same,
|
|
|
// UnControlled CodeMirror value does not reset, so explicitly set the value to initialValue
|
|
|
const onRouterChangeComplete = useCallback(() => {
|
|
|
- codeMirrorEditor?.initDoc(initialValue);
|
|
|
+ codeMirrorEditor?.initDoc(ydoc?.getText('codemirror').toString());
|
|
|
codeMirrorEditor?.setCaretLine();
|
|
|
- }, [codeMirrorEditor, initialValue]);
|
|
|
+ }, [codeMirrorEditor, ydoc]);
|
|
|
|
|
|
useEffect(() => {
|
|
|
router.events.on('routeChangeComplete', onRouterChangeComplete);
|
|
|
@@ -576,6 +645,8 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
|
|
|
onChange={markdownChangedHandler}
|
|
|
onSave={saveWithShortcut}
|
|
|
indentSize={currentIndentSize ?? defaultIndentSize}
|
|
|
+ ydoc={ydoc}
|
|
|
+ provider={provider}
|
|
|
/>
|
|
|
</div>
|
|
|
<div className="page-editor-preview-container flex-expand-vert d-none d-lg-flex">
|