mirror of
https://github.com/cudr/slate-collaborative.git
synced 2024-10-27 20:34:06 +00:00
feat: cursors should works with annotations
This commit is contained in:
parent
242a836ce8
commit
0ceb38bbfd
@ -2,6 +2,7 @@ import io from 'socket.io'
|
||||
import { ValueJSON } from 'slate'
|
||||
import * as Automerge from 'automerge'
|
||||
import throttle from 'lodash/throttle'
|
||||
import merge from 'lodash/merge'
|
||||
|
||||
import { toSync, toJS } from '@slate-collaborative/bridge'
|
||||
|
||||
@ -18,7 +19,7 @@ class Connection {
|
||||
this.io = io(options.port, options.connectOpts)
|
||||
this.docSet = new Automerge.DocSet()
|
||||
this.connections = {}
|
||||
this.options = options
|
||||
this.options = merge(defaultOptions, options)
|
||||
|
||||
this.configure()
|
||||
}
|
||||
@ -32,7 +33,7 @@ class Connection {
|
||||
private appendDoc = (path: string, value: ValueJSON) => {
|
||||
const sync = toSync(value)
|
||||
|
||||
sync.cursors = {}
|
||||
sync.annotations = {}
|
||||
|
||||
const doc = Automerge.from(sync)
|
||||
|
||||
@ -44,11 +45,13 @@ class Connection {
|
||||
if (this.options.onDocumentSave) {
|
||||
const doc = this.docSet.getDoc(pathname)
|
||||
|
||||
const data = toJS(doc)
|
||||
if (doc) {
|
||||
const data = toJS(doc)
|
||||
|
||||
delete data.cursors
|
||||
delete data.annotations
|
||||
|
||||
this.options.onDocumentSave(pathname, data)
|
||||
this.options.onDocumentSave(pathname, data)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
@ -106,6 +109,8 @@ class Connection {
|
||||
socket.on('operation', this.onOperation(id, name))
|
||||
|
||||
socket.on('disconnect', this.onDisconnect(id, socket))
|
||||
|
||||
this.garbageCursors(name)
|
||||
}
|
||||
|
||||
private onOperation = (id, name) => data => {
|
||||
@ -125,6 +130,8 @@ class Connection {
|
||||
socket.leave(id)
|
||||
|
||||
this.garbageCursor(socket.nsp.name, id)
|
||||
this.garbageCursors(socket.nsp.name)
|
||||
|
||||
this.garbageNsp()
|
||||
}
|
||||
|
||||
@ -141,17 +148,32 @@ class Connection {
|
||||
garbageCursor = (nsp, id) => {
|
||||
const doc = this.docSet.getDoc(nsp)
|
||||
|
||||
if (!doc.cursors) return
|
||||
if (!doc.annotations) return
|
||||
|
||||
const change = Automerge.change(
|
||||
doc,
|
||||
`remove cursor ${id}`,
|
||||
(d: any) => delete d.cursors[id]
|
||||
)
|
||||
const change = Automerge.change(doc, `remove cursor ${id}`, (d: any) => {
|
||||
delete d.annotations[id]
|
||||
})
|
||||
|
||||
this.docSet.setDoc(nsp, change)
|
||||
}
|
||||
|
||||
garbageCursors = nsp => {
|
||||
const doc = this.docSet.getDoc(nsp)
|
||||
|
||||
if (!doc.annotations) return
|
||||
|
||||
const namespace = this.io.of(nsp)
|
||||
|
||||
Object.keys(doc.annotations).forEach(key => {
|
||||
if (
|
||||
!namespace.sockets[key] &&
|
||||
doc.annotations[key].type === this.options.cursorAnnotationType
|
||||
) {
|
||||
this.garbageCursor(nsp, key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
removeDoc = async nsp => {
|
||||
const doc = this.docSet.getDoc(nsp)
|
||||
|
||||
|
@ -5,6 +5,7 @@ export interface ConnectionOptions {
|
||||
connectOpts?: SocketIO.ServerOptions
|
||||
defaultValue?: ValueJSON
|
||||
saveTreshold?: number
|
||||
cursorAnnotationType?: string
|
||||
onAuthRequest?: (
|
||||
query: Object,
|
||||
socket?: SocketIO.Socket
|
||||
|
@ -7,7 +7,8 @@ export const getClients = (io, nsp) =>
|
||||
|
||||
export const defaultOptions = {
|
||||
port: 9000,
|
||||
saveTreshold: 2000
|
||||
saveTreshold: 2000,
|
||||
cursorAnnotationType: 'collaborative_selection'
|
||||
}
|
||||
|
||||
export { defaultValue }
|
||||
|
@ -14,7 +14,7 @@ const byAction = {
|
||||
|
||||
const rootKey = '00000000-0000-0000-0000-000000000000'
|
||||
|
||||
const toSlateOp = (ops: Automerge.Diff[], currentTree) => {
|
||||
const toSlateOp = (ops: Automerge.Diff[], doc) => {
|
||||
const iterate = (acc, op) => {
|
||||
const action = byAction[op.action]
|
||||
|
||||
@ -30,9 +30,7 @@ const toSlateOp = (ops: Automerge.Diff[], currentTree) => {
|
||||
[]
|
||||
])
|
||||
|
||||
const res = defer.flatMap(op => op(tempTree, currentTree))
|
||||
|
||||
console.log('toSlate@@@', ops, res)
|
||||
const res = defer.flatMap(op => op(tempTree, doc)).filter(op => op)
|
||||
|
||||
return res
|
||||
}
|
||||
|
@ -41,9 +41,13 @@ const removeNodesOp = ({ index, obj, path }: Automerge.Diff) => (map, doc) => {
|
||||
}
|
||||
|
||||
const removeAnnotationOp = ({ key }: Automerge.Diff) => (map, doc) => {
|
||||
return {
|
||||
type: 'remove_annotation',
|
||||
annotation: toJS(doc.annotations[key])
|
||||
const annotation = toJS(doc.annotations[key])
|
||||
|
||||
if (annotation) {
|
||||
return {
|
||||
type: 'remove_annotation',
|
||||
annotation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,6 @@ const AnnotationSetOp = ({ key, value }: Automerge.Diff) => (map, doc) => {
|
||||
// }
|
||||
// }
|
||||
|
||||
console.log('opSET!!', key, map[value], op)
|
||||
|
||||
return op
|
||||
}
|
||||
|
||||
|
@ -1,75 +1,68 @@
|
||||
import { toJS } from '../utils'
|
||||
|
||||
import { Operation } from 'slate'
|
||||
import { Operation, Selection } from 'slate'
|
||||
import { List } from 'immutable'
|
||||
import merge from 'lodash/merge'
|
||||
|
||||
import { toJS } from '../utils'
|
||||
import { SyncDoc, CursorKey } from '../model'
|
||||
|
||||
export const setCursor = (
|
||||
doc: SyncDoc,
|
||||
key: CursorKey,
|
||||
selection: Selection,
|
||||
type,
|
||||
data
|
||||
) => {
|
||||
if (!doc) return
|
||||
|
||||
export const setCursor = (doc, { id, selection, annotationType }) => {
|
||||
if (!doc.annotations) {
|
||||
doc.annotations = {}
|
||||
}
|
||||
|
||||
if (!doc.annotations[id]) {
|
||||
doc.annotations[id] = {
|
||||
key: id,
|
||||
type: annotationType,
|
||||
if (!doc.annotations[key]) {
|
||||
doc.annotations[key] = {
|
||||
key,
|
||||
type,
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
|
||||
const annotation = toJS(doc.annotations[id])
|
||||
const annotation = toJS(doc.annotations[key])
|
||||
|
||||
// if (selectionOps.size) {
|
||||
// selectionOps.forEach(op => {
|
||||
// const { newProperties } = op.toJSON()
|
||||
annotation.focus = selection.end.toJSON()
|
||||
annotation.anchor = selection.start.toJSON()
|
||||
|
||||
// if (newProperties.focus) annotation.focus = newProperties.focus
|
||||
// if (newProperties.anchor) annotation.anchor = newProperties.anchor
|
||||
// if (newProperties.data) annotation.data = newProperties.data
|
||||
// })
|
||||
// }
|
||||
annotation.data = merge(annotation.data, data, {
|
||||
isBackward: selection.isBackward,
|
||||
targetPath: selection.isBackward
|
||||
? annotation.anchor.path
|
||||
: annotation.focus.path
|
||||
})
|
||||
|
||||
// console.log('cursor!!', cursorStart, cursorEnd)
|
||||
// console.log(
|
||||
// 'selection!!',
|
||||
// selection.toJSON(),
|
||||
// selection.start.offset,
|
||||
// selection.end.offset
|
||||
// )
|
||||
|
||||
annotation.focus = selection.end.toJSON() || {}
|
||||
annotation.anchor = selection.start.toJSON() || {}
|
||||
|
||||
annotation.data.isBackward = selection.isBackward
|
||||
annotation.data.targetPath = selection.isBackward
|
||||
? annotation.anchor.path
|
||||
: annotation.focus.path
|
||||
|
||||
doc.annotations[id] = annotation
|
||||
doc.annotations[key] = annotation
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const removeCursor = (doc, { id }) => {
|
||||
// console.log('!!!removeCursor', doc, id)
|
||||
if (doc.annotations && doc.annotations[id]) {
|
||||
delete doc.annotations[id]
|
||||
export const removeCursor = (doc: SyncDoc, key: CursorKey) => {
|
||||
if (doc.annotations && doc.annotations[key]) {
|
||||
delete doc.annotations[key]
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const cursorOpFilter = (ops: List<Operation>, annotationType) =>
|
||||
export const cursorOpFilter = (ops: List<Operation>, type: string) =>
|
||||
ops.filter(op => {
|
||||
if (op.type === 'set_annotation') {
|
||||
return !(
|
||||
(op.properties && op.properties.type === annotationType) ||
|
||||
(op.newProperties && op.newProperties.type === annotationType)
|
||||
(op.properties && op.properties.type === type) ||
|
||||
(op.newProperties && op.newProperties.type === type)
|
||||
)
|
||||
} else if (
|
||||
op.type === 'add_annotation' ||
|
||||
op.type === 'remove_annotation'
|
||||
) {
|
||||
return op.annotation.type !== annotationType
|
||||
return op.annotation.type !== type
|
||||
}
|
||||
|
||||
return true
|
||||
|
@ -2,3 +2,4 @@ export * from './apply'
|
||||
export * from './convert'
|
||||
export * from './utils'
|
||||
export * from './cursor'
|
||||
export * from './model'
|
||||
|
@ -1,3 +1,3 @@
|
||||
import { Doc } from 'automerge'
|
||||
export type CursorKey = string
|
||||
|
||||
export type SyncDoc = Doc<any>
|
||||
export type SyncDoc = any
|
||||
|
@ -5,7 +5,7 @@ export const toJS = node => {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(node))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error('Convert to js failed!!! Return null')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
0
packages/client/Cursor.tsx
Normal file
0
packages/client/Cursor.tsx
Normal file
@ -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) => (
|
||||
|
@ -16,6 +16,7 @@
|
||||
"concurrently": "^4.1.2",
|
||||
"faker": "^4.1.0",
|
||||
"lodash": "^4.17.15",
|
||||
"randomcolor": "^0.5.4",
|
||||
"react": "^16.9.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-scripts": "3.1.1",
|
||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react'
|
||||
|
||||
import { Value, ValueJSON } from 'slate'
|
||||
import { Editor } from 'slate-react'
|
||||
import randomColor from 'randomcolor'
|
||||
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
@ -23,12 +24,18 @@ class Client extends Component<ClienProps> {
|
||||
|
||||
state = {
|
||||
value: Value.fromJSON(defaultValue as ValueJSON),
|
||||
isOnline: true,
|
||||
isOnline: false,
|
||||
plugins: []
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const plugin = ClientPlugin({
|
||||
const color = randomColor({
|
||||
luminosity: 'dark',
|
||||
format: 'rgba',
|
||||
alpha: 1
|
||||
})
|
||||
|
||||
const options = {
|
||||
url: `http://localhost:9000/${this.props.slug}`,
|
||||
connectOpts: {
|
||||
query: {
|
||||
@ -37,10 +44,25 @@ class Client extends Component<ClienProps> {
|
||||
slug: this.props.slug
|
||||
}
|
||||
},
|
||||
// preloader: () => <div>PRELOADER!!!!!!</div>,
|
||||
annotationDataMixin: {
|
||||
name: this.props.name
|
||||
},
|
||||
cursorStyle: {
|
||||
background: color
|
||||
},
|
||||
caretStyle: {
|
||||
background: color
|
||||
},
|
||||
selectionStyle: {
|
||||
background: color.slice(0, -2) + '0.2)'
|
||||
},
|
||||
renderCursor: data => data.get('name'),
|
||||
// renderPreloader: () => <div>PRELOADER!!!!!!</div>,
|
||||
onConnect: this.onConnect,
|
||||
onDisconnect: this.onDisconnect
|
||||
})
|
||||
}
|
||||
|
||||
const plugin = ClientPlugin(options)
|
||||
|
||||
this.setState({
|
||||
plugins: [plugin]
|
||||
|
Loading…
Reference in New Issue
Block a user