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([]);
useEffect(() => {
const indexdbProvider = getIndexdbProvider(documentId, provider.document);
indexdbProvider.on('synced', () => {
setStatus('loadCacheSuccess');
});
provider.on('status', async ({ status }) => {
setStatus(status);
});
return () => {
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);
};
}, [documentId, provider]);
}, [documentId, provider, authority]);
useEffect(() => {
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 = (
awareness,
{ cursorBuilder = defaultCursorBuilder, getSelection = (state) => state.selection } = {},
cursorStateField = 'cursor'
cursorStateField = 'cursor',
isEditable = false
) =>
new Plugin({
key: yCursorPluginKey,
@ -131,6 +132,8 @@ export const yCursorPlugin = (
}
};
const updateCursorInfo = () => {
if (!isEditable) return;
const ystate = ySyncPluginKey.getState(view.state);
// @note We make implicit checks when checking for the cursor property
const current = awareness.getLocalState() || {};

View File

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

View File

@ -45,6 +45,7 @@ export const SelectionExtension = Extension.create({
name: 'selection',
priority: EXTENSION_PRIORITY_HIGHEST,
addProseMirrorPlugins() {
const { isEditable } = this.editor;
return [
new Plugin({
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 { Divider } from './divider';
@ -43,11 +44,18 @@ import { Iframe } from './menus/iframe';
import { Table } from './menus/table';
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;
return (
<div>
<div
style={{
opacity: isEditable ? 1 : 0.65,
pointerEvents: isEditable ? 'auto' : 'none',
}}
>
<Space spacing={2}>
<Insert editor={editor} />
@ -109,7 +117,7 @@ export const MenuBar = React.memo(_MenuBar, (prevProps, nextProps) => {
return prevProps.editor === nextProps.editor;
});
const _CommentMenuBar: React.FC<{ editor: any }> = ({ editor }) => {
const _CommentMenuBar: React.FC<{ editor: Editor }> = ({ editor }) => {
return (
<>
<Space spacing={2}>

View File

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

View File

@ -133,7 +133,7 @@ export class BubbleMenuView {
trigger: 'manual',
placement: 'top',
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
@ -160,7 +160,9 @@ export class BubbleMenuView {
const from = Math.min(...ranges.map((range) => range.$from.pos));
const to = Math.max(...ranges.map((range) => range.$to.pos));
const shouldShow = this.shouldShow?.({
const shouldShow =
this.editor.isEditable &&
this.shouldShow?.({
editor: this.editor,
view,
state,