initial commit

This commit is contained in:
cudr
2019-10-05 11:44:49 +03:00
commit a817eb1ceb
63 changed files with 1769 additions and 0 deletions

23
packages/example/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -0,0 +1,51 @@
{
"name": "@slate-collaborative/example",
"version": "0.0.1",
"private": true,
"dependencies": {
"@emotion/core": "^10.0.17",
"@emotion/styled": "^10.0.17",
"@slate-collaborative/backend": "0.0.1",
"@slate-collaborative/client": "0.0.1",
"@types/faker": "^4.1.5",
"@types/jest": "24.0.18",
"@types/node": "12.7.5",
"@types/react": "16.9.2",
"@types/react-dom": "16.9.0",
"@types/slate-react": "^0.22.5",
"concurrently": "^4.1.2",
"faker": "^4.1.0",
"lodash": "^4.17.15",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-scripts": "3.1.1",
"slate": "^0.47.8",
"slate-react": "^0.22.8",
"typescript": "3.6.3"
},
"scripts": {
"start": "react-scripts start",
"dev": "concurrently \"yarn start\" \"yarn serve\"",
"serve": "nodemon --watch ../backend/lib --inspect server.js",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"nodemon": "^1.19.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Slate collaborative</title>
</head>
<body>
<noscript></noscript>
<div id="root"></div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "Slate collaborative",
"name": "collaborative plugin & microservice",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

View File

@@ -0,0 +1,21 @@
const Connection = require('@slate-collaborative/backend')
const defaultValue = require('./src/defaultValue')
const config = {
port: 9000,
defaultValue,
saveTreshold: 2000,
onAuthRequest: async (query, socket) => {
// some query validation
return true
},
onDocumentLoad: async pathname => {
// return initial document ValueJSON by pathnme
return defaultValue
},
onDocumentSave: async (pathname, document) => {
// save document
}
}
const connection = new Connection(config)

View File

@@ -0,0 +1,64 @@
import React, { Component } from 'react'
import faker from 'faker'
import styled from '@emotion/styled'
import Room from './Room'
class App extends Component<{}, { rooms: string[] }> {
state = {
rooms: []
}
componentDidMount() {
this.addRoom()
}
render() {
const { rooms } = this.state
return (
<Container>
<AddButton type="button" onClick={this.addRoom}>
Add Room
</AddButton>
{rooms.map(room => (
<Room key={room} slug={room} removeRoom={this.removeRoom(room)} />
))}
</Container>
)
}
addRoom = () => {
const room = faker.lorem.slug(4)
this.setState({ rooms: [...this.state.rooms, room] })
}
removeRoom = (room: string) => () => {
this.setState({
rooms: this.state.rooms.filter(r => r !== room)
})
}
}
export default App
const Container = styled.div``
const Button = styled.button`
padding: 6px 14px;
display: block;
outline: none;
font-size: 14px;
max-width: 200px;
text-align: center;
color: palevioletred;
border: 2px solid palevioletred;
`
const AddButton = styled(Button)`
margin-left: 30px;
color: violet;
border: 2px solid violet;
`

View File

@@ -0,0 +1,101 @@
import React, { Component } from 'react'
import { Value, ValueJSON } from 'slate'
import { Editor } from 'slate-react'
import styled from '@emotion/styled'
import ClientPlugin from '@slate-collaborative/client'
import defaultValue from './defaultValue'
import { Instance, ClientFrame, Title, H4, Button } from './elements'
interface ClienProps {
name: string
id: string
slug: string
removeUser: (id: any) => void
}
class Client extends Component<ClienProps> {
editor: any
state = {
value: Value.fromJSON(defaultValue as ValueJSON),
isOnline: true,
plugins: []
}
componentDidMount() {
const plugin = ClientPlugin({
url: `http://localhost:9000/${this.props.slug}`,
connectOpts: {
query: {
name: this.props.name,
token: this.props.id,
slug: this.props.slug
}
},
// preloader: () => <div>PRELOADER!!!!!!</div>,
onConnect: this.onConnect,
onDisconnect: this.onDisconnect
})
this.setState({
plugins: [plugin]
})
}
render() {
const { plugins, isOnline, value } = this.state
const { id, name } = this.props
return (
<Instance online={isOnline}>
<Title>
<Head>Editor: {name}</Head>
<Button type="button" onClick={this.toggleOnline}>
Go {isOnline ? 'offline' : 'online'}
</Button>
<Button type="button" onClick={() => this.props.removeUser(id)}>
Remove
</Button>
</Title>
<ClientFrame>
<Editor
value={value}
ref={this.ref}
plugins={plugins}
onChange={this.onChange}
/>
</ClientFrame>
</Instance>
)
}
onChange = ({ value }: any) => {
this.setState({ value })
}
onConnect = () => this.setState({ isOnline: true })
onDisconnect = () => this.setState({ isOnline: false })
ref = node => {
this.editor = node
}
toggleOnline = () => {
const { isOnline } = this.state
const { connect, disconnect } = this.editor.connection
isOnline ? disconnect() : connect()
}
}
export default Client
const Head = styled(H4)`
margin-right: auto;
`

View File

@@ -0,0 +1,92 @@
import React, { Component, ChangeEvent } from 'react'
import faker from 'faker'
import debounce from 'lodash/debounce'
import { RoomWrapper, H4, Title, Button, Grid, Input } from './elements'
import Client from './Client'
interface User {
id: string
name: string
}
interface RoomProps {
slug: string
removeRoom: () => void
}
interface RoomState {
users: User[]
slug: string
rebuild: boolean
}
class Room extends Component<RoomProps, RoomState> {
state = {
users: [],
slug: this.props.slug,
rebuild: false
}
componentDidMount() {
this.addUser()
}
render() {
const { users, slug, rebuild } = this.state
return (
<RoomWrapper>
<Title>
<H4>Document slug:</H4>
<Input type="text" value={slug} onChange={this.changeSlug} />
<Button type="button" onClick={this.addUser}>
Add random user
</Button>
<Button type="button" onClick={this.props.removeRoom}>
Remove Room
</Button>
</Title>
<Grid>
{users.map(
(user: User) =>
!rebuild && (
<Client
{...user}
slug={slug}
key={user.id}
removeUser={this.removeUser}
/>
)
)}
</Grid>
</RoomWrapper>
)
}
addUser = () => {
const user = {
id: faker.random.uuid(),
name: `${faker.name.firstName()} ${faker.name.lastName()}`
}
this.setState({ users: [...this.state.users, user] })
}
removeUser = (userId: string) => {
this.setState({
users: this.state.users.filter((u: User) => u.id !== userId)
})
}
changeSlug = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ slug: e.target.value }, this.rebuildClient)
}
rebuildClient = debounce(() => {
this.setState({ rebuild: true }, () => this.setState({ rebuild: false }))
}, 300)
}
export default Room

View File

@@ -0,0 +1,17 @@
module.exports = {
document: {
nodes: [
{
object: 'block',
type: 'paragraph',
nodes: [
{
object: 'text',
marks: [],
text: 'Hello collaborator!'
}
]
}
]
}
}

View File

@@ -0,0 +1,64 @@
import styled from '@emotion/styled'
export const RoomWrapper = styled.div`
padding: 30px;
border-bottom: 2px solid #e8e8e8;
`
export const H4 = styled.h4`
margin: 0;
padding-right: 10px;
`
export const Input = styled.input`
padding: 6px 14px;
font-size: 14px;
margin-right: 10px;
min-width: 240px;
outline: none;
border: 2px solid palevioletred;
& + button {
margin-left: auto;
}
`
export const Button = styled.button`
padding: 6px 14px;
display: block;
outline: none;
font-size: 14px;
text-align: center;
color: palevioletred;
white-space: nowrap;
border: 2px solid palevioletred;
& + button {
margin-left: 10px;
}
`
export const Grid = styled.div`
display: grid;
grid-gap: 2vw;
grid-template-columns: 1fr 1fr;
`
export const Title = styled.div`
display: flex;
align-items: center;
margin-bottom: 20px;
`
export const Instance = styled.div<{ online: boolean }>`
background: ${props =>
props.online ? 'rgba(128, 128, 128, 0.1)' : 'rgba(247, 0, 0, 0.2)'};
padding: 20px 30px 40px;
`
export const ClientFrame = styled.div`
box-shadow: 2px 2px 4px rgba(128, 128, 128, 0.2);
padding: 10px;
min-height: 70px;
margin-left: -10px;
margin-right: -10px;
background: white;
`

View File

@@ -0,0 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@@ -0,0 +1,22 @@
{
"include": ["src/**/*"],
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"baseUrl": "src",
"jsx": "react",
"allowJs": true,
"declaration": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"noEmit": true,
"isolatedModules": true,
"skipLibCheck": true
}
}