tiptap: fix isEditable

pull/31/head
fantasticit 2022-05-02 16:09:01 +08:00
parent 578753c1f6
commit b5172a8861
8 changed files with 105 additions and 23 deletions

View File

@ -92,21 +92,28 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
const [mentionUsers, setMentionUsers] = useState([]); const [mentionUsers, setMentionUsers] = useState([]);
useEffect(() => { useEffect(() => {
const indexdbProvider = getIndexdbProvider(documentId, provider.document);
indexdbProvider.on('synced', () => {
setStatus('loadCacheSuccess');
});
provider.on('status', async ({ status }) => { provider.on('status', async ({ status }) => {
setStatus(status); setStatus(status);
}); });
return () => { return () => {
destoryProvider(provider, 'EDITOR'); destoryProvider(provider, 'EDITOR');
};
}, [documentId, provider, authority]);
useEffect(() => {
if (!authority || !authority.editable) return;
const indexdbProvider = getIndexdbProvider(documentId, provider.document);
indexdbProvider.on('synced', () => {
setStatus('loadCacheSuccess');
});
return () => {
destoryIndexdbProvider(documentId); destoryIndexdbProvider(documentId);
}; };
}, [documentId, provider]); }, [documentId, provider, authority]);
useEffect(() => { useEffect(() => {
if (!editor) return; if (!editor) return;

View File

@ -0,0 +1,35 @@
import { useState, useEffect, DependencyList } from 'react';
import { EditorOptions } from '@tiptap/core';
import { Editor } from '@tiptap/react';
function useForceUpdate() {
const [, setValue] = useState(0);
return () => setValue((value) => value + 1);
}
export const useEditor = (options: Partial<EditorOptions> = {}, deps: DependencyList = []) => {
const [editor, setEditor] = useState<Editor | null>(null);
const forceUpdate = useForceUpdate();
useEffect(() => {
const instance = new Editor(options);
setEditor(instance);
// instance.on('transaction', () => {
// requestAnimationFrame(() => {
// requestAnimationFrame(() => {
// console.log('update');
// forceUpdate();
// });
// });
// });
return () => {
instance.destroy();
};
}, deps);
return editor;
};

View File

@ -101,7 +101,8 @@ export const createDecorations = (state, awareness, createCursor) => {
export const yCursorPlugin = ( export const yCursorPlugin = (
awareness, awareness,
{ cursorBuilder = defaultCursorBuilder, getSelection = (state) => state.selection } = {}, { cursorBuilder = defaultCursorBuilder, getSelection = (state) => state.selection } = {},
cursorStateField = 'cursor' cursorStateField = 'cursor',
isEditable = false
) => ) =>
new Plugin({ new Plugin({
key: yCursorPluginKey, key: yCursorPluginKey,
@ -131,6 +132,8 @@ export const yCursorPlugin = (
} }
}; };
const updateCursorInfo = () => { const updateCursorInfo = () => {
if (!isEditable) return;
const ystate = ySyncPluginKey.getState(view.state); const ystate = ySyncPluginKey.getState(view.state);
// @note We make implicit checks when checking for the cursor property // @note We make implicit checks when checking for the cursor property
const current = awareness.getLocalState() || {}; const current = awareness.getLocalState() || {};

View File

@ -165,6 +165,7 @@ export const CollaborationCursor = Extension.create<CollaborationCursorOptions,
addProseMirrorPlugins() { addProseMirrorPlugins() {
const extensionThis = this; const extensionThis = this;
const { isEditable } = this.editor;
return [ return [
yCursorPlugin( yCursorPlugin(
@ -184,7 +185,9 @@ export const CollaborationCursor = Extension.create<CollaborationCursorOptions,
// @ts-ignore // @ts-ignore
{ {
cursorBuilder: this.options.render, cursorBuilder: this.options.render,
} },
'cursor',
isEditable
), ),
]; ];
}, },

View File

@ -45,6 +45,7 @@ export const SelectionExtension = Extension.create({
name: 'selection', name: 'selection',
priority: EXTENSION_PRIORITY_HIGHEST, priority: EXTENSION_PRIORITY_HIGHEST,
addProseMirrorPlugins() { addProseMirrorPlugins() {
const { isEditable } = this.editor;
return [ return [
new Plugin({ new Plugin({
key: selectionPluginKey, key: selectionPluginKey,
@ -103,6 +104,30 @@ export const SelectionExtension = Extension.create({
}, },
}, },
}), }),
new Plugin({
key: new PluginKey('preventSelection'),
props: {
// 禁止非可编辑用户选中
handleClick(_, __, event) {
if (!isEditable) {
event.preventDefault();
return true;
}
},
handleKeyPress(_, event) {
if (!isEditable) {
event.preventDefault();
return true;
}
},
handleKeyDown(_, event) {
if (!isEditable) {
event.preventDefault();
return true;
}
},
},
}),
]; ];
}, },
}); });

View File

@ -1,4 +1,5 @@
import React from 'react'; import React, { useMemo } from 'react';
import { Editor } from '@tiptap/core';
import { Space } from '@douyinfe/semi-ui'; import { Space } from '@douyinfe/semi-ui';
import { Divider } from './divider'; import { Divider } from './divider';
@ -43,11 +44,18 @@ import { Iframe } from './menus/iframe';
import { Table } from './menus/table'; import { Table } from './menus/table';
import { Mind } from './menus/mind'; import { Mind } from './menus/mind';
const _MenuBar: React.FC<{ editor: any }> = ({ editor }) => { const _MenuBar: React.FC<{ editor: Editor }> = ({ editor }) => {
const isEditable = useMemo(() => editor.isEditable, [editor]);
if (!editor) return null; if (!editor) return null;
return ( return (
<div> <div
style={{
opacity: isEditable ? 1 : 0.65,
pointerEvents: isEditable ? 'auto' : 'none',
}}
>
<Space spacing={2}> <Space spacing={2}>
<Insert editor={editor} /> <Insert editor={editor} />
@ -109,7 +117,7 @@ export const MenuBar = React.memo(_MenuBar, (prevProps, nextProps) => {
return prevProps.editor === nextProps.editor; return prevProps.editor === nextProps.editor;
}); });
const _CommentMenuBar: React.FC<{ editor: any }> = ({ editor }) => { const _CommentMenuBar: React.FC<{ editor: Editor }> = ({ editor }) => {
return ( return (
<> <>
<Space spacing={2}> <Space spacing={2}>

View File

@ -20,7 +20,6 @@ export const useEditor = (options: Partial<EditorOptions> = {}, deps: Dependency
// instance.on('transaction', () => { // instance.on('transaction', () => {
// requestAnimationFrame(() => { // requestAnimationFrame(() => {
// requestAnimationFrame(() => { // requestAnimationFrame(() => {
// console.log('update');
// forceUpdate(); // forceUpdate();
// }); // });
// }); // });

View File

@ -133,7 +133,7 @@ export class BubbleMenuView {
trigger: 'manual', trigger: 'manual',
placement: 'top', placement: 'top',
hideOnClick: 'toggle', hideOnClick: 'toggle',
...Object.assign({ zIndex: 99 }, this.tippyOptions), ...Object.assign({ zIndex: 999 }, this.tippyOptions),
}); });
// maybe we have to hide tippy on its own blur event as well // maybe we have to hide tippy on its own blur event as well
@ -160,14 +160,16 @@ export class BubbleMenuView {
const from = Math.min(...ranges.map((range) => range.$from.pos)); const from = Math.min(...ranges.map((range) => range.$from.pos));
const to = Math.max(...ranges.map((range) => range.$to.pos)); const to = Math.max(...ranges.map((range) => range.$to.pos));
const shouldShow = this.shouldShow?.({ const shouldShow =
editor: this.editor, this.editor.isEditable &&
view, this.shouldShow?.({
state, editor: this.editor,
oldState, view,
from, state,
to, oldState,
}); from,
to,
});
if (!shouldShow) { if (!shouldShow) {
this.hide(); this.hide();