mirror of
https://github.com/cudr/slate-collaborative.git
synced 2026-03-02 03:40:18 +00:00
feat: cursors should works with annotations
This commit is contained in:
@@ -22,7 +22,8 @@ class Connection {
|
||||
socket: SocketIOClient.Socket
|
||||
editor: ExtendedEditor
|
||||
connectOpts: any
|
||||
selection: any
|
||||
annotationDataMixin: any
|
||||
cursorAnnotationType: string
|
||||
onConnect?: () => void
|
||||
onDisconnect?: () => void
|
||||
|
||||
@@ -31,11 +32,16 @@ class Connection {
|
||||
url,
|
||||
connectOpts,
|
||||
onConnect,
|
||||
onDisconnect
|
||||
onDisconnect,
|
||||
cursorAnnotationType,
|
||||
annotationDataMixin
|
||||
}: ConnectionModel) {
|
||||
this.url = url
|
||||
this.editor = editor
|
||||
this.connectOpts = connectOpts
|
||||
this.cursorAnnotationType = cursorAnnotationType
|
||||
this.annotationDataMixin = annotationDataMixin
|
||||
|
||||
this.onConnect = onConnect
|
||||
this.onDisconnect = onDisconnect
|
||||
|
||||
@@ -58,9 +64,6 @@ class Connection {
|
||||
const currentDoc = this.docSet.getDoc(this.docId)
|
||||
const docNew = this.connection.receiveMsg(data)
|
||||
|
||||
console.log('current doc before updates', toJS(currentDoc))
|
||||
console.log('new doc with remote updates!!', toJS(docNew))
|
||||
|
||||
if (!docNew) {
|
||||
return
|
||||
}
|
||||
@@ -82,44 +85,12 @@ class Connection {
|
||||
await Promise.resolve()
|
||||
|
||||
this.editor.remote = false
|
||||
|
||||
this.setCursors(docNew.cursors)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
setCursors = cursors => {
|
||||
if (!cursors) return
|
||||
|
||||
// const {
|
||||
// value: { annotations }
|
||||
// } = this.editor
|
||||
|
||||
// const keyMap = {}
|
||||
|
||||
// console.log('cursors', cursors)
|
||||
|
||||
// this.editor.withoutSaving(() => {
|
||||
// annotations.forEach(anno => {
|
||||
// this.editor.removeAnnotation(anno)
|
||||
// })
|
||||
|
||||
// Object.keys(cursors).forEach(key => {
|
||||
// if (key !== this.socket.id && !keyMap[key]) {
|
||||
// this.editor.addAnnotation(toJS(cursors[key]))
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
|
||||
console.log(
|
||||
'!!!!VAL',
|
||||
this.connectOpts.query.name,
|
||||
this.editor.value.toJSON({ preserveAnnotations: true })
|
||||
)
|
||||
}
|
||||
|
||||
receiveSlateOps = (operations: Immutable.List<Operation>) => {
|
||||
const doc = this.docSet.getDoc(this.docId)
|
||||
const message = `change from ${this.socket.id}`
|
||||
@@ -130,26 +101,18 @@ class Connection {
|
||||
value: { selection }
|
||||
} = this.editor
|
||||
|
||||
const annotationType = 'collaborative_selection'
|
||||
|
||||
const cursorData = {
|
||||
id: this.socket.id,
|
||||
selection,
|
||||
// selectionOps: operations.filter(op => op.type === 'set_selection'),
|
||||
annotationType
|
||||
}
|
||||
|
||||
const withCursor = selection.isFocused ? setCursor : removeCursor
|
||||
|
||||
const changed = Automerge.change(doc, message, (d: any) =>
|
||||
withCursor(
|
||||
applySlateOps(d, cursorOpFilter(operations, annotationType)),
|
||||
cursorData
|
||||
applySlateOps(d, cursorOpFilter(operations, this.cursorAnnotationType)),
|
||||
this.socket.id,
|
||||
selection,
|
||||
this.cursorAnnotationType,
|
||||
this.annotationDataMixin
|
||||
)
|
||||
)
|
||||
|
||||
console.log('doc with annotations!!', toJS(changed))
|
||||
|
||||
this.docSet.setDoc(this.docId, changed)
|
||||
}
|
||||
|
||||
@@ -174,7 +137,7 @@ class Connection {
|
||||
}
|
||||
|
||||
connect = () => {
|
||||
this.socket = io(this.url, this.connectOpts)
|
||||
this.socket = io(this.url, { ...this.connectOpts })
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
this.connection = new Automerge.Connection(this.docSet, this.sendData)
|
||||
@@ -190,6 +153,8 @@ class Connection {
|
||||
disconnect = () => {
|
||||
this.onDisconnect()
|
||||
|
||||
console.log('disconnect', this.socket)
|
||||
|
||||
this.connection && this.connection.close()
|
||||
|
||||
delete this.connection
|
||||
@@ -202,6 +167,7 @@ class Connection {
|
||||
this.onDisconnect()
|
||||
|
||||
this.socket.close()
|
||||
// this.socket.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { KeyUtils } from 'slate'
|
||||
import { hexGen } from '@slate-collaborative/bridge'
|
||||
|
||||
import Connection from './Connection'
|
||||
|
||||
import { ControllerProps } from './model'
|
||||
|
||||
class Controller extends Component<ControllerProps> {
|
||||
@@ -15,7 +14,13 @@ class Controller extends Component<ControllerProps> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { editor, url, connectOpts } = this.props
|
||||
const {
|
||||
editor,
|
||||
url,
|
||||
cursorAnnotationType,
|
||||
annotationDataMixin,
|
||||
connectOpts
|
||||
} = this.props
|
||||
|
||||
KeyUtils.setGenerator(() => hexGen())
|
||||
|
||||
@@ -23,19 +28,11 @@ class Controller extends Component<ControllerProps> {
|
||||
editor,
|
||||
url,
|
||||
connectOpts,
|
||||
cursorAnnotationType,
|
||||
annotationDataMixin,
|
||||
onConnect: this.onConnect,
|
||||
onDisconnect: this.onDisconnect
|
||||
})
|
||||
|
||||
//@ts-ignore
|
||||
if (!window.counter) {
|
||||
//@ts-ignore
|
||||
window.counter = 1
|
||||
}
|
||||
//@ts-ignore
|
||||
window[`Editor_${counter}`] = editor
|
||||
//@ts-ignore
|
||||
window.counter += 1
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -47,10 +44,10 @@ class Controller extends Component<ControllerProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, preloader } = this.props
|
||||
const { children, renderPreloader } = this.props
|
||||
const { preloading } = this.state
|
||||
|
||||
if (preloader && preloading) return preloader()
|
||||
if (renderPreloader && preloading) return renderPreloader()
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import onChange from './onChange'
|
||||
import renderEditor from './renderEditor'
|
||||
import renderAnnotation from './renderAnnotation'
|
||||
|
||||
import Connection from './Connection'
|
||||
import { PluginOptions } from './model'
|
||||
|
||||
export interface PluginOptions {
|
||||
url?: string
|
||||
connectOpts?: SocketIOClient.ConnectOpts
|
||||
preloader?: () => ReactNode
|
||||
onConnect?: (connection: Connection) => void
|
||||
onDisconnect?: (connection: Connection) => void
|
||||
export const defaultOpts = {
|
||||
url: 'http://localhost:9000',
|
||||
cursorAnnotationType: 'collaborative_selection',
|
||||
annotationDataMixin: {
|
||||
name: 'an collaborator'
|
||||
},
|
||||
renderCursor: data => data.name,
|
||||
cursorStyle: {
|
||||
background: 'palevioletred'
|
||||
},
|
||||
caretStyle: {
|
||||
background: 'palevioletred'
|
||||
},
|
||||
selectionStyle: {
|
||||
background: 'rgba(233, 30, 99, 0.2)'
|
||||
}
|
||||
}
|
||||
|
||||
const defaultOpts = {
|
||||
url: 'http://localhost:9000'
|
||||
}
|
||||
|
||||
const plugin = (opts: PluginOptions = {}) => {
|
||||
const plugin = (opts: PluginOptions = defaultOpts) => {
|
||||
const options = { ...defaultOpts, ...opts }
|
||||
|
||||
return {
|
||||
onChange: onChange(options),
|
||||
renderEditor: renderEditor(options),
|
||||
renderAnnotation
|
||||
renderAnnotation: renderAnnotation(options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ReactNode } from 'react'
|
||||
import CSS from 'csstype'
|
||||
import { Editor, Controller, Value } from 'slate'
|
||||
import { PluginOptions } from './index'
|
||||
|
||||
import Connection from './Connection'
|
||||
|
||||
interface FixedController extends Controller {
|
||||
@@ -15,6 +17,7 @@ export interface ExtendedEditor extends Editor {
|
||||
|
||||
export interface ConnectionModel extends PluginOptions {
|
||||
editor: ExtendedEditor
|
||||
cursorAnnotationType: string
|
||||
onConnect: () => void
|
||||
onDisconnect: () => void
|
||||
}
|
||||
@@ -24,3 +27,18 @@ export interface ControllerProps extends PluginOptions {
|
||||
url?: string
|
||||
connectOpts?: SocketIOClient.ConnectOpts
|
||||
}
|
||||
|
||||
export interface PluginOptions {
|
||||
url?: string
|
||||
connectOpts?: SocketIOClient.ConnectOpts
|
||||
cursorAnnotationType?: string
|
||||
caretStyle?: CSS.Properties
|
||||
cursorStyle?: CSS.Properties
|
||||
renderPreloader?: () => ReactNode
|
||||
annotationDataMixin?: {
|
||||
[key: string]: any
|
||||
}
|
||||
renderCursor?: (data: any) => ReactNode | string | any
|
||||
onConnect?: (connection: Connection) => void
|
||||
onDisconnect?: (connection: Connection) => void
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
const wrapStyles = {
|
||||
backgroundColor: 'rgba(233, 30, 99, 0.2)',
|
||||
background: 'rgba(233, 30, 99, 0.2)',
|
||||
position: 'relative'
|
||||
}
|
||||
|
||||
@@ -24,50 +24,53 @@ const caretStyleBase = {
|
||||
userSelect: 'none',
|
||||
height: '100%',
|
||||
width: 2,
|
||||
background: '#bf1b52'
|
||||
}
|
||||
background: 'palevioletred'
|
||||
} as any
|
||||
|
||||
const renderAnnotation = (props, editor, next) => {
|
||||
const renderAnnotation = ({
|
||||
cursorAnnotationType,
|
||||
renderCursor,
|
||||
cursorStyle = {},
|
||||
caretStyle = {},
|
||||
selectionStyle = {}
|
||||
}) => (props, editor, next) => {
|
||||
const { children, annotation, attributes, node } = props
|
||||
|
||||
if (annotation.type !== cursorAnnotationType) return next()
|
||||
|
||||
const isBackward = annotation.data.get('isBackward')
|
||||
const targetPath = annotation.data.get('targetPath')
|
||||
const cursorText = renderCursor(annotation.data)
|
||||
|
||||
console.log(
|
||||
'renderAnnotation',
|
||||
annotation.toJS(),
|
||||
props,
|
||||
isBackward,
|
||||
targetPath
|
||||
)
|
||||
|
||||
const badgeStyles = { ...cursorStyleBase, left: isBackward ? '0%' : '100%' }
|
||||
const caretStyles = { ...caretStyleBase, left: isBackward ? '0%' : '100%' }
|
||||
const cursorStyles = {
|
||||
...cursorStyleBase,
|
||||
...cursorStyle,
|
||||
left: isBackward ? '0%' : '100%'
|
||||
}
|
||||
const caretStyles = {
|
||||
...caretStyleBase,
|
||||
...caretStyle,
|
||||
left: isBackward ? '0%' : '100%'
|
||||
}
|
||||
|
||||
const { document } = editor.value
|
||||
|
||||
const targetNode = document.getNode(targetPath)
|
||||
|
||||
const isShowCursor = targetNode && targetNode.key === node.key
|
||||
|
||||
switch (annotation.type) {
|
||||
case 'collaborative_selection':
|
||||
return (
|
||||
<span {...attributes} style={wrapStyles}>
|
||||
{isShowCursor ? (
|
||||
<Fragment>
|
||||
<span contentEditable={false} style={badgeStyles}>
|
||||
{annotation.key}
|
||||
</span>
|
||||
<span contentEditable={false} style={caretStyles} />
|
||||
</Fragment>
|
||||
) : null}
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
return (
|
||||
<span {...attributes} style={{ ...wrapStyles, ...selectionStyle }}>
|
||||
{isShowCursor ? (
|
||||
<Fragment>
|
||||
<span contentEditable={false} style={cursorStyles}>
|
||||
{cursorText}
|
||||
</span>
|
||||
<span contentEditable={false} style={caretStyles} />
|
||||
</Fragment>
|
||||
) : null}
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default renderAnnotation
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import { PluginOptions } from './index'
|
||||
|
||||
import { PluginOptions } from './model'
|
||||
import Controller from './Controller'
|
||||
|
||||
const renderEditor = (opts: PluginOptions) => (
|
||||
|
||||
Reference in New Issue
Block a user