Merge pull request #1 from cudr/mark_data

feat: mark data
This commit is contained in:
George 2019-10-07 00:15:00 +03:00 committed by GitHub
commit ab3b8c4ee3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 172 additions and 38 deletions

View File

@ -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": [

View File

@ -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"
} }
} }

View File

@ -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"]
} }
} }

View File

@ -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"
]
} }

View File

@ -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
} }

View File

@ -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]

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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) {

View 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

View File

@ -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 }

View File

@ -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",

View File

@ -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"
} }
} }

View File

@ -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)

View File

@ -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,

View File

@ -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()

View File

@ -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
} }
} }