mirror of
https://github.com/cudr/slate-collaborative.git
synced 2024-10-27 20:34:06 +00:00
commit
ab3b8c4ee3
@ -4,9 +4,11 @@
|
|||||||
"description": "Slate collaborative plugin & microservice",
|
"description": "Slate collaborative plugin & microservice",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"dev": "concurrently \"lerna run --parallel watch\" \"lerna run dev --stream\"",
|
"release": "yarn prebuild && yarn build && lerna publish from-package",
|
||||||
|
"dev": "concurrently \"yarn watch\" \"lerna run dev --stream\"",
|
||||||
"build": "lerna run build --stream",
|
"build": "lerna run build --stream",
|
||||||
"prebuild": "lerna clean --yes && rm -rf ./packages/**/lib/",
|
"watch": "lerna run --parallel watch",
|
||||||
|
"prebuild": "rm -rf ./packages/**/lib/",
|
||||||
"format": "prettier --write"
|
"format": "prettier --write"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
@ -7,7 +7,13 @@
|
|||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
"description": "slate-collaborative bridge",
|
"description": "slate-collaborative bridge",
|
||||||
"repository": "https://github.com/cudr/slate-collaborative",
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/cudr/slate-collaborative.git"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
"author": "cudr",
|
"author": "cudr",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -34,5 +40,8 @@
|
|||||||
"@babel/preset-env": "^7.6.0",
|
"@babel/preset-env": "^7.6.0",
|
||||||
"@babel/preset-typescript": "^7.6.0",
|
"@babel/preset-typescript": "^7.6.0",
|
||||||
"@types/socket.io": "^2.1.2"
|
"@types/socket.io": "^2.1.2"
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true,
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,13 @@
|
|||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
"description": "slate-collaborative bridge",
|
"description": "slate-collaborative bridge",
|
||||||
"repository": "https://github.com/cudr/slate-collaborative",
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/cudr/slate-collaborative.git"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
"author": "cudr",
|
"author": "cudr",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -27,5 +33,13 @@
|
|||||||
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
|
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
|
||||||
"@babel/preset-env": "^7.6.0",
|
"@babel/preset-env": "^7.6.0",
|
||||||
"@babel/preset-typescript": "^7.6.0"
|
"@babel/preset-typescript": "^7.6.0"
|
||||||
}
|
},
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"slate",
|
||||||
|
"automerge",
|
||||||
|
"bridge"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
export const addAnnotation = (doc: any, op: any) => {
|
import { Operation, SyncDoc } from '../model'
|
||||||
|
|
||||||
|
export const addAnnotation = (doc: SyncDoc, op: Operation): SyncDoc => {
|
||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeAnnotation = (doc: any, op: any) => {
|
export const removeAnnotation = (doc: SyncDoc, op: Operation): SyncDoc => {
|
||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setAnnotation = (doc: any, op: any) => {
|
export const setAnnotation = (doc: SyncDoc, op: Operation): SyncDoc => {
|
||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import * as Automerge from 'automerge'
|
||||||
|
|
||||||
const createByType = type => (type === 'map' ? {} : type === 'list' ? [] : '')
|
const createByType = type => (type === 'map' ? {} : type === 'list' ? [] : '')
|
||||||
|
|
||||||
const opCreate = ({ obj, type }, [map, ops]) => {
|
const opCreate = ({ obj, type }: Automerge.Diff, [map, ops]) => {
|
||||||
map[obj] = createByType(type)
|
map[obj] = createByType(type)
|
||||||
|
|
||||||
return [map, ops]
|
return [map, ops]
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import * as Automerge from 'automerge'
|
||||||
|
|
||||||
import opInsert from './insert'
|
import opInsert from './insert'
|
||||||
import opRemove from './remove'
|
import opRemove from './remove'
|
||||||
import opSet from './set'
|
import opSet from './set'
|
||||||
@ -12,7 +14,7 @@ const byAction = {
|
|||||||
|
|
||||||
const rootKey = '00000000-0000-0000-0000-000000000000'
|
const rootKey = '00000000-0000-0000-0000-000000000000'
|
||||||
|
|
||||||
const toSlateOp = ops => {
|
const toSlateOp = (ops: Automerge.Diff[], currentTree) => {
|
||||||
const iterate = (acc, op) => {
|
const iterate = (acc, op) => {
|
||||||
const action = byAction[op.action]
|
const action = byAction[op.action]
|
||||||
|
|
||||||
@ -21,14 +23,14 @@ const toSlateOp = ops => {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const [tree, defer] = ops.reduce(iterate, [
|
const [tempTree, defer] = ops.reduce(iterate, [
|
||||||
{
|
{
|
||||||
[rootKey]: {}
|
[rootKey]: {}
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
])
|
])
|
||||||
|
|
||||||
return defer.map(op => op(tree))
|
return defer.flatMap(op => op(tempTree, currentTree))
|
||||||
}
|
}
|
||||||
|
|
||||||
export { toSlateOp }
|
export { toSlateOp }
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import * as Automerge from 'automerge'
|
||||||
import { toSlatePath, toJS } from '../utils/index'
|
import { toSlatePath, toJS } from '../utils/index'
|
||||||
|
|
||||||
const insertTextOp = ({ index, path, value }) => () => ({
|
const insertTextOp = ({ index, path, value }: Automerge.Diff) => () => ({
|
||||||
type: 'insert_text',
|
type: 'insert_text',
|
||||||
path: toSlatePath(path),
|
path: toSlatePath(path),
|
||||||
offset: index,
|
offset: index,
|
||||||
@ -8,18 +9,42 @@ const insertTextOp = ({ index, path, value }) => () => ({
|
|||||||
marks: []
|
marks: []
|
||||||
})
|
})
|
||||||
|
|
||||||
const insertNodeOp = ({ value, index, path }) => map => ({
|
const insertNodeOp = ({ value, obj, index, path }: Automerge.Diff) => map => {
|
||||||
type: 'insert_node',
|
const ops = []
|
||||||
path: [...toSlatePath(path), index],
|
|
||||||
node: map[value]
|
const inserate = ({ nodes, ...json }: any, path) => {
|
||||||
})
|
const node = nodes ? { ...json, nodes: [] } : json
|
||||||
|
|
||||||
|
if (node.object === 'mark') {
|
||||||
|
ops.push({
|
||||||
|
type: 'add_mark',
|
||||||
|
path: path.slice(0, -1),
|
||||||
|
mark: node
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ops.push({
|
||||||
|
type: 'insert_node',
|
||||||
|
path,
|
||||||
|
node
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes && nodes.forEach((n, i) => inserate(n, [...path, i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = map[value] || (map[obj] && toJS(map[obj]))
|
||||||
|
|
||||||
|
source && inserate(source, [...toSlatePath(path), index])
|
||||||
|
|
||||||
|
return ops
|
||||||
|
}
|
||||||
|
|
||||||
const insertByType = {
|
const insertByType = {
|
||||||
text: insertTextOp,
|
text: insertTextOp,
|
||||||
list: insertNodeOp
|
list: insertNodeOp
|
||||||
}
|
}
|
||||||
|
|
||||||
const opInsert = (op, [map, ops]) => {
|
const opInsert = (op: Automerge.Diff, [map, ops]) => {
|
||||||
try {
|
try {
|
||||||
const { link, obj, path, index, type, value } = op
|
const { link, obj, path, index, type, value } = op
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import * as Automerge from 'automerge'
|
||||||
import { toSlatePath, toJS } from '../utils/index'
|
import { toSlatePath, toJS } from '../utils/index'
|
||||||
|
import { getTarget } from '../path'
|
||||||
|
|
||||||
const removeTextOp = ({ index, path }) => () => ({
|
const removeTextOp = ({ index, path }: Automerge.Diff) => () => ({
|
||||||
type: 'remove_text',
|
type: 'remove_text',
|
||||||
path: toSlatePath(path).slice(0, path.length),
|
path: toSlatePath(path).slice(0, path.length),
|
||||||
offset: index,
|
offset: index,
|
||||||
@ -8,11 +10,30 @@ const removeTextOp = ({ index, path }) => () => ({
|
|||||||
marks: []
|
marks: []
|
||||||
})
|
})
|
||||||
|
|
||||||
const removeNodesOp = ({ index, path }) => () => {
|
const removeMarkOp = ({ path, index }: Automerge.Diff) => (map, doc) => {
|
||||||
const nPath = toSlatePath(path)
|
const slatePath = toSlatePath(path)
|
||||||
|
const target = getTarget(doc, slatePath)
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'remove_mark',
|
||||||
|
path: slatePath,
|
||||||
|
mark: {
|
||||||
|
type: target.marks[index].type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeNodesOp = ({ index, obj, path }: Automerge.Diff) => (map, doc) => {
|
||||||
|
const slatePath = toSlatePath(path)
|
||||||
|
if (!map.hasOwnProperty(obj)) {
|
||||||
|
const target = getTarget(doc, [...slatePath, index] as any)
|
||||||
|
|
||||||
|
map[obj] = target
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'remove_node',
|
type: 'remove_node',
|
||||||
path: nPath.length ? nPath.concat(index) : [index],
|
path: slatePath.length ? slatePath.concat(index) : [index],
|
||||||
node: {
|
node: {
|
||||||
object: 'text'
|
object: 'text'
|
||||||
}
|
}
|
||||||
@ -21,10 +42,11 @@ const removeNodesOp = ({ index, path }) => () => {
|
|||||||
|
|
||||||
const removeByType = {
|
const removeByType = {
|
||||||
text: removeTextOp,
|
text: removeTextOp,
|
||||||
nodes: removeNodesOp
|
nodes: removeNodesOp,
|
||||||
|
marks: removeMarkOp
|
||||||
}
|
}
|
||||||
|
|
||||||
const opRemove = (op, [map, ops]) => {
|
const opRemove = (op: Automerge.Diff, [map, ops]) => {
|
||||||
try {
|
try {
|
||||||
const { index, path, obj } = op
|
const { index, path, obj } = op
|
||||||
|
|
||||||
|
@ -1,9 +1,29 @@
|
|||||||
import { toJS } from '../utils/index'
|
import * as Automerge from 'automerge'
|
||||||
|
import { toSlatePath, toJS } from '../utils/index'
|
||||||
|
|
||||||
const opSet = (op, [map, ops]) => {
|
const setDataOp = ({ path, value }: Automerge.Diff) => map => ({
|
||||||
const { link, value, obj, key } = op
|
type: 'set_node',
|
||||||
|
path: toSlatePath(path),
|
||||||
|
properties: {},
|
||||||
|
newProperties: {
|
||||||
|
data: map[value]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const setByType = {
|
||||||
|
data: setDataOp
|
||||||
|
}
|
||||||
|
|
||||||
|
const opSet = (op: Automerge.Diff, [map, ops]) => {
|
||||||
|
const { link, value, path, obj, key } = op
|
||||||
try {
|
try {
|
||||||
map[obj][key] = link ? map[value] : value
|
const set = setByType[key]
|
||||||
|
|
||||||
|
if (set && path) {
|
||||||
|
ops.push(set(op))
|
||||||
|
} else {
|
||||||
|
map[obj][key] = link ? map[value] : value
|
||||||
|
}
|
||||||
|
|
||||||
return [map, ops]
|
return [map, ops]
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
15
packages/bridge/src/utils/hexGen.ts
Normal file
15
packages/bridge/src/utils/hexGen.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export type Hex = string
|
||||||
|
|
||||||
|
const hexGen = (len: number = 12): Hex => {
|
||||||
|
const maxlen = 8
|
||||||
|
const min = Math.pow(16, Math.min(len, maxlen) - 1)
|
||||||
|
const max = Math.pow(16, Math.min(len, maxlen)) - 1
|
||||||
|
const n = Math.floor(Math.random() * (max - min + 1)) + min
|
||||||
|
let r = n.toString(16)
|
||||||
|
while (r.length < len) {
|
||||||
|
r = r + hexGen(len - maxlen)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hexGen
|
@ -1,4 +1,5 @@
|
|||||||
import toSync from './toSync'
|
import toSync from './toSync'
|
||||||
|
import hexGen from './hexGen'
|
||||||
|
|
||||||
export const toJS = node => JSON.parse(JSON.stringify(node))
|
export const toJS = node => JSON.parse(JSON.stringify(node))
|
||||||
|
|
||||||
@ -6,4 +7,4 @@ export const cloneNode = node => toSync(toJS(node))
|
|||||||
|
|
||||||
const toSlatePath = path => (path ? path.filter(d => Number.isInteger(d)) : [])
|
const toSlatePath = path => (path ? path.filter(d => Number.isInteger(d)) : [])
|
||||||
|
|
||||||
export { toSync, toSlatePath }
|
export { toSync, toSlatePath, hexGen }
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
|
@ -7,7 +7,13 @@
|
|||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
"description": "slate-collaborative bridge",
|
"description": "slate-collaborative bridge",
|
||||||
"repository": "https://github.com/cudr/slate-collaborative",
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/cudr/slate-collaborative.git"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
"author": "cudr",
|
"author": "cudr",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -37,5 +43,8 @@
|
|||||||
"@types/react": "^16.9.2",
|
"@types/react": "^16.9.2",
|
||||||
"@types/slate": "^0.47.1",
|
"@types/slate": "^0.47.1",
|
||||||
"@types/socket.io-client": "^1.4.32"
|
"@types/socket.io-client": "^1.4.32"
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,12 +58,14 @@ class Connection {
|
|||||||
const operations = Automerge.diff(currentDoc, docNew)
|
const operations = Automerge.diff(currentDoc, docNew)
|
||||||
|
|
||||||
if (operations.length !== 0) {
|
if (operations.length !== 0) {
|
||||||
const slateOps = toSlateOp(operations, this.connectOpts.query.name)
|
const slateOps = toSlateOp(operations, currentDoc)
|
||||||
|
|
||||||
this.editor.remote = true
|
this.editor.remote = true
|
||||||
|
|
||||||
this.editor.withoutSaving(() => {
|
this.editor.withoutSaving(() => {
|
||||||
slateOps.forEach(o => this.editor.applyOperation(o))
|
slateOps.forEach(o => {
|
||||||
|
this.editor.applyOperation(o)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(() => (this.editor.remote = false), 5)
|
setTimeout(() => (this.editor.remote = false), 5)
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import React, { PureComponent, ReactNode } from 'react'
|
import React, { Component } from 'react'
|
||||||
|
import { KeyUtils } from 'slate'
|
||||||
|
|
||||||
|
import { hexGen } from '@slate-collaborative/bridge'
|
||||||
|
|
||||||
import Connection from './Connection'
|
import Connection from './Connection'
|
||||||
|
|
||||||
import { ControllerProps } from './model'
|
import { ControllerProps } from './model'
|
||||||
|
|
||||||
class Controller extends PureComponent<ControllerProps> {
|
class Controller extends Component<ControllerProps> {
|
||||||
connection?: Connection
|
connection?: Connection
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -14,6 +17,8 @@ class Controller extends PureComponent<ControllerProps> {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { editor, url, connectOpts } = this.props
|
const { editor, url, connectOpts } = this.props
|
||||||
|
|
||||||
|
KeyUtils.setGenerator(() => hexGen())
|
||||||
|
|
||||||
editor.connection = new Connection({
|
editor.connection = new Connection({
|
||||||
editor,
|
editor,
|
||||||
url,
|
url,
|
||||||
|
@ -2,7 +2,9 @@ import { ExtendedEditor } from './model'
|
|||||||
|
|
||||||
const onChange = opts => (editor: ExtendedEditor, next: () => void) => {
|
const onChange = opts => (editor: ExtendedEditor, next: () => void) => {
|
||||||
if (!editor.remote) {
|
if (!editor.remote) {
|
||||||
editor.connection.receiveSlateOps(editor.operations)
|
const operations: any = editor.operations
|
||||||
|
|
||||||
|
editor.connection.receiveSlateOps(operations)
|
||||||
}
|
}
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"lib": ["es6", "dom"],
|
"lib": ["dom", "dom.iterable", "es6"],
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user