├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── demos ├── collab-editor │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── real-time-simple.png │ ├── server.js │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ └── logo.svg ├── collab-form │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── real-time-form.png │ ├── server.js │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ └── logo.svg └── collab-rich-editor │ ├── README.md │ ├── package.json │ ├── public │ ├── favicon.ico │ └── index.html │ ├── real-time.png │ ├── server.js │ └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── index.css │ ├── index.js │ └── logo.svg ├── index.js ├── package.json ├── scripts └── mocha_runner.js └── src ├── __tests__ └── index.js ├── client ├── CollabEditor.js ├── CollabForm.js ├── CollabRichEditor.js ├── DragAndDropModule.js ├── connection.js ├── fields │ └── CollabStringField.js ├── index.js ├── utils.js └── widgets │ ├── CollabBaseInput.js │ ├── CollabTextWidget.js │ ├── CollabTextareaWidget.js │ └── CollabURLWidget.js ├── index.js └── server ├── CollabCollection.js └── CollabServer.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "react"] 3 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "ecmaFeatures": { 9 | "modules": true 10 | }, 11 | "rules": { 12 | "no-bitwise": 2, 13 | "no-else-return": 2, 14 | "no-eq-null": 2, 15 | "no-extra-parens": 0, 16 | "no-floating-decimal": 2, 17 | "no-inner-declarations": [2, "both"], 18 | "no-lonely-if": 2, 19 | "no-multiple-empty-lines": [2, {"max": 3}], 20 | "no-self-compare": 2, 21 | "no-underscore-dangle": 0, 22 | "no-use-before-define": 0, 23 | "no-unused-expressions": 0, 24 | "no-void": 2, 25 | "brace-style": [2, "1tbs"], 26 | "camelcase": [1, {"properties": "never"}], 27 | "consistent-return": 0, 28 | "comma-style": [2, "last"], 29 | "complexity": [1, 12], 30 | "func-names": 0, 31 | "guard-for-in": 2, 32 | "max-len": [0, 120, 4], 33 | "new-cap": [2, {"newIsCap": true, "capIsNew": false}], 34 | "quotes": [2, "single"], 35 | "keyword-spacing": [2, {"before": true, "after": true}], 36 | "space-before-blocks": [2, "always"], 37 | "array-bracket-spacing": [2, "never"], 38 | "space-in-parens": [2, "never"], 39 | "strict": [0], 40 | "valid-jsdoc": 2, 41 | "wrap-iife": [2, "any"], 42 | "yoda": [1, "never"] 43 | }, 44 | "plugins": [ 45 | "react" 46 | ], 47 | "globals": { 48 | 49 | } 50 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *.iml 4 | .*.haste_cache.* 5 | .DS_Store 6 | .idea 7 | npm-debug.log 8 | node_modules 9 | dist 10 | collab-web-forms-0.0.0.tgz -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | .*.haste_cache.* 4 | .DS_Store 5 | .idea 6 | .babelrc 7 | .eslintrc 8 | npm-debug.log 9 | src 10 | demos -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Julian Ćwirko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # collab-react-components 2 | 3 | Database backend and collaborative React components. 4 | This package is an abstraction layer to the famous collaborative database backend [ShareDB](https://github.com/share/sharedb). 5 | 6 | The current available components are: 7 | - A simple collaborative editor 8 | - A rich collaborative editor based on [React-Quill](https://github.com/zenoamaro/react-quill) 9 | - A collaborative form based on [react-jsonschema-form](https://github.com/mozilla-services/react-jsonschema-form) 10 | 11 | ## Example apps 12 | Multiple example apps are present in the `demos` directory. Feel free look at 13 | them for a real implementation. 14 | 15 | To start the demo applications, just clone the repository, move to the desired demo, 16 | install the npm packages and start the application: 17 | 18 | ``` 19 | cd demos/collab-editor 20 | npm install 21 | npm start 22 | ``` 23 | 24 | ## Table of contents 25 | - [Installation](#installation) 26 | - [Server initialization](#server-initialization) 27 | - [The CollabCollection class](#the-collabcollection-class) 28 | - [Usage](#usage) 29 | - [Server API](#server-api) 30 | - [Simple collaborative Editor](#simple-collaborative-editor) 31 | - [Collaborative Form](#collaborative-form) 32 | - [Rich Collaborative Editor](#rich-collaborative-editor) 33 | - [License](#license) 34 | --- 35 | 36 | ## Installation 37 | - Requires React 15.4.0+. 38 | > Note: The "master" branch of the repository reflects the published stable release. 39 | 40 | ``` 41 | $ npm install --save collab-react-components 42 | ``` 43 | 44 | > Note: It is recommended to use [Bootstrap](http://getbootstrap.com/) 45 | with this package for better rendering of the form elements. 46 | 47 | ## Server initialization 48 | In order to use the collaborative functionalities, you will need to start a new 49 | CollabServer instance. One way to do it is on startup: 50 | 51 | ```JavaScript 52 | const CollabServer = require('collab-react-components').Server; 53 | 54 | // app is you Express app for example. 55 | 56 | CollabServer.start(app); 57 | ``` 58 | You can also stop the server by calling: 59 | 60 | ```JavaScript 61 | CollabServer.stop(); 62 | ``` 63 | > Note: See the demos for an example of implementation with an 64 | [Express](https://expressjs.com/) server. 65 | 66 | ### The CollabCollection class 67 | 68 | The `CollabCollection` class represents a new collaborative collection of documents on the server. 69 | Collaborative Collections collection can be persisted to disk using MongoDB or in memory if no options 70 | are passed to the server. To use MongoDB, set up a database and pass its URL 71 | to the `CollabServer`. For example: 72 | 73 | ```JavaScript 74 | const MongoClient = require('mongodb').MongoClient; 75 | 76 | // Create a MongoDB server 77 | const url = 'mongodb://localhost:27017/my-collaborative-app'; 78 | MongoClient.connect(url) 79 | .catch(function (err) { 80 | if (err) throw err; 81 | }) 82 | ; 83 | 84 | const options = { 85 | db: { 86 | type: 'mongo', 87 | url 88 | } 89 | }; 90 | 91 | // Create a CollabServer instance with MongoDB 92 | CollabServer.start(app, options); 93 | ``` 94 | >Note: For the moment, only MongoDB is supported as a persistence layer. 95 | 96 | Once a `CollabCollection` is created, you will be able to query its content 97 | using the `CollabCollection` methods. 98 | 99 | ## Usage 100 | For a basic implementation, see the demos applications. 101 | 102 | ### Server API 103 | 104 | The `CollabCollection` class methods are the following: 105 | 106 | - `create(id, data = '')`: Creates a new collaborative document for a simple editor with `id` (String) and with initial `data` (String). 107 | - `createForm(id, schema)`: Creates a new collaborative form with `id` (String) and with initial data corresponding to the `schema`, where 108 | `schema` is a `react-jsonschema-form` schema. 109 | - `createRichText(id, data='')`: Creates a new collaborative document for a rich editor with `id` (String) and with initial `data` (String). 110 | - `remove(id)`: Deletes a document with ID `id`. 111 | 112 | ### Simple Collaborative Editor 113 | 114 | To implement a simple collaborative editor (textarea), start by instantiating 115 | a new `CollabCollection` on the server, taking as parameter the name of the collaborative collection: 116 | 117 | ```JavaScript 118 | const CollabCollection = require('collab-react-components').Collection; 119 | 120 | const documents = new CollabCollection("documents"); 121 | documents.create("editor1"); 122 | ``` 123 | 124 | #### Client API 125 | 126 | Call `CollabEditor` from the client 127 | 128 | ```jsx 129 | import React from 'react'; 130 | import { CollabEditor } from 'collab-react-components/dist/client'; 131 | 132 | 136 | ``` 137 | Props of `CollabEditor`: 138 | - `id`: ID of the document to fetch from the database 139 | - `collectionName`: Name of the collection 140 | - `rows`: "rows" attribute of the textarea 141 | - `classNames`: default is `form-control` 142 | - `onChange(text)`: Function called on every local modification. 143 | `text` is a string representing the current value of the editor. 144 | 145 | ### Collaborative Form 146 | >Note: The collaborative form is based on [react-jsonschema-form](https://github.com/mozilla-services/react-jsonschema-form) 147 | 148 | To implement a collaborative form, start by instantiating a new `CollabCollection` on the server, 149 | taking as parameter the name of the collection and a schema: 150 | 151 | ```JavaScript 152 | const CollabCollection = require('collab-react-components').Collection; 153 | 154 | const forms = new CollabCollection("forms"); 155 | const schema = { 156 | title: "My collaborative form", 157 | type: "object", 158 | required: ["input", "textarea"], 159 | properties: { 160 | input: {type: "string", title: "Input"}, 161 | textarea: {type: "string", title: "Textarea", default: 'Default text'}, 162 | } 163 | }; 164 | forms.createForm("form1", schema); 165 | ``` 166 | This will create a new collaborative document on the database containing the 167 | collaborative data of the form. 168 | 169 | #### Client API 170 | 171 | Just call `CollabForm` from the client. 172 | 173 | > Note: You should not use the widget `password` in a collaborative form. 174 | At least not until the data is encrypted on the database. 175 | 176 | ```jsx 177 | import React from 'react'; 178 | import { CollabForm } from 'collab-react-components/dist/client'; 179 | 180 | 184 | ``` 185 | > Note: `CollabForm` can be used exactly like `Form` from `react-jsonschema-form` 186 | with few exceptions (see below). 187 | 188 | Props of `CollabForm` that vary from `react-jsonschema-form`: 189 | - `id`: ID of the form to fetch from the database 190 | - `collectionName`: Name of the collection 191 | - You should not pass a `schema` to the `CollabForm` component. 192 | - You should not pass `formData` to `CollabForm`, the data will be fetched 193 | from the collaboratively shared data in the databas 194 | 195 | The supported collaborative String types are `text`, `textarea` and `uri`. 196 | They can be defined in the `uiSchema` like: 197 | 198 | ```jsx 199 | const uiSchema = { 200 | textarea: {"ui:widget": "textarea"}, 201 | uri: {"ui:widget": "uri"} 202 | } 203 | 204 | 209 | 210 | ``` 211 | > Note: Other types such as `date` or `email` cannot be updated simultaneously in a collaborative manner. 212 | 213 | At the moment, `CollabForm` only supports form schemas where the root 214 | element is a non-nested `object` (does not contain other objects or arrays). 215 | We are currently working on implementing `array` capabilities. 216 | 217 | ### Rich Collaborative Editor 218 | >Note: The collaborative editor is based on [react-quill](https://github.com/zenoamaro/react-quill) 219 | 220 | To implement a rich collaborative editor, start by instantiating a new `CollabCollection` on the server, 221 | taking as parameter the name of the collection: 222 | 223 | ```JavaScript 224 | const CollabCollection = require('collab-react-components').Collection; 225 | 226 | const documents = new CollabCollection("documents"); 227 | documents.createRichText('rich-editor1', 'My initial data'); 228 | ``` 229 | This will create a new collaborative document on the database containing the 230 | collaborative data of the rich text editor. 231 | 232 | #### Client API 233 | 234 | Just call `CollabRichEditor` from the client. 235 | 236 | ```jsx 237 | import React from 'react'; 238 | import { CollabRichEditor } from 'collab-react-components/dist/client'; 239 | 240 | 244 | ``` 245 | 246 | > Note: `CollabRichEditor` can be used exactly like `ReactQuill` from `react-quill` 247 | with few exceptions (see below). 248 | 249 | 250 | Props of `CollabRichEditor` that vary from `react-quill`: 251 | - `id`: ID of the form to fetch from the database 252 | - `collectionName`: Name of the collection 253 | 254 | >Note: Do not pass `value` or `defaultValue` to `CollabRichEditor`, the data will be fetched 255 | from the collaboratively shared data on the server. 256 | 257 | ## License 258 | MIT 259 | -------------------------------------------------------------------------------- /demos/collab-editor/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | #IDE 20 | .idea -------------------------------------------------------------------------------- /demos/collab-editor/README.md: -------------------------------------------------------------------------------- 1 | # Simple Collaborative Editor Example 2 | 3 | A collaborative text editor using [collab-react-components](https://github.com/chili-epfl/collab-react-components) 4 | 5 | This example demonstrates 6 | - Implementation of a backend server using Express and websockets. 7 | - Initialisation of the collaborative server instance. 8 | - Creation of a new document on the server 9 | - Utilisation of the CollabEditor component 10 | 11 | 12 | In this demo, data is not persisted. To persist data, run a Mongo 13 | server and initialize use id in `collab-react-components` as follows (for example): 14 | 15 | ```javascript 16 | const MongoClient = require('mongodb').MongoClient; 17 | 18 | // Create a MongoDB server 19 | const url = 'mongodb://localhost:27017/collab-form'; 20 | MongoClient.connect(url) 21 | .catch(function (err) { 22 | if (err) throw err; 23 | }) 24 | ; 25 | 26 | const options = { 27 | db: { 28 | type: 'mongo', 29 | url 30 | } 31 | }; 32 | 33 | // Create a CollabServer instance 34 | CollabServer.start(app, options); 35 | ``` 36 | ## Install dependencies 37 | Once the project cloned, install the dependencies 38 | ``` 39 | npm install 40 | ``` 41 | 42 | ## Build JavaScript bundle and run server 43 | You will have to run the server instance and the React App. 44 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app) 45 | The implementation is compatible with the create-react-app dev server. 46 | 47 | > Note: that both servers need to be running at once for this 48 | to work, you will need to run both `node server.js` AND `npm start` at the same time, this is don 49 | by running (see package.json): 50 | ``` 51 | npm start 52 | ``` 53 | 54 | ## Run app in browser 55 | Load [http://localhost:3000](http://localhost:8080) 56 | 57 | ## Result 58 | You should be able to see an editor and be able to type in it. 59 | If you open another browser window to the same URL, 60 | you should see the two editors synchronized in real time. 61 | 62 | ![image](https://raw.githubusercontent.com/darioAnongba/collab-react-components/master/demos/collab-editor/real-time-simple.png) 63 | 64 | -------------------------------------------------------------------------------- /demos/collab-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collab-editor", 3 | "version": "0.1.0", 4 | "description": "A simple collaborative text editor based on collab-web-forms", 5 | "private": true, 6 | "dependencies": { 7 | "react": "^15.5.4", 8 | "react-dom": "^15.5.4" 9 | }, 10 | "devDependencies": { 11 | "express": "^4.15.2", 12 | "mongodb": "^2.2.26", 13 | "react-scripts": "0.9.5" 14 | }, 15 | "scripts": { 16 | "start": "node server.js & react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demos/collab-editor/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chili-epfl/collab-react-components/a14c2acbccaa8fbfd1a4cb61770b578fc90d17b2/demos/collab-editor/public/favicon.ico -------------------------------------------------------------------------------- /demos/collab-editor/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Collaborative Web forms : Editor Example 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demos/collab-editor/real-time-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chili-epfl/collab-react-components/a14c2acbccaa8fbfd1a4cb61770b578fc90d17b2/demos/collab-editor/real-time-simple.png -------------------------------------------------------------------------------- /demos/collab-editor/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dario on 11.05.17. 3 | */ 4 | 5 | const Express = require('express'); 6 | const CollabServer = require('../../dist').Server; 7 | const CollabCollection = require('../../dist').Collection; 8 | 9 | /* Create Express application */ 10 | const app = Express(); 11 | // Express only serves static assets in production 12 | if (process.env.NODE_ENV === 'production') { 13 | app.use(Express.static('client/build')); 14 | } 15 | 16 | // Create a CollabServer instance 17 | CollabServer.start(app); 18 | 19 | // Create the collection that will hold the shared data. 20 | const documents = new CollabCollection('documents'); 21 | 22 | // Create the shared form data 23 | documents.create('editor1'); -------------------------------------------------------------------------------- /demos/collab-editor/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-intro { 18 | font-size: large; 19 | } 20 | 21 | @keyframes App-logo-spin { 22 | from { transform: rotate(0deg); } 23 | to { transform: rotate(360deg); } 24 | } 25 | -------------------------------------------------------------------------------- /demos/collab-editor/src/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dario on 11.05.2017. 3 | */ 4 | import React, { Component } from 'react'; 5 | import { CollabEditor } from '../../../dist/client'; 6 | 7 | // App component - represents the whole app 8 | export default class App extends Component { 9 | render() { 10 | return ( 11 |
12 |
13 |

Simple Collaborative Editor

14 |
15 | 16 | 21 |
22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /demos/collab-editor/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /demos/collab-editor/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /demos/collab-editor/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') 9 | ); 10 | -------------------------------------------------------------------------------- /demos/collab-editor/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demos/collab-form/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | #IDE 20 | .idea -------------------------------------------------------------------------------- /demos/collab-form/README.md: -------------------------------------------------------------------------------- 1 | # Collaborative Form Example 2 | 3 | A collaborative form using [collab-react-components](https://github.com/chili-epfl/collab-react-components) 4 | 5 | This example demonstrates 6 | - Implementation of a backend server using Express and websockets. 7 | - Initialisation of the collaborative server instance. 8 | - Creation of a new form document on the server 9 | - Utilisation of the CollabForm component 10 | 11 | 12 | In this demo, data is not persisted. To persist data, run a Mongo 13 | server and initialize use id in `collab-react-components` as follows (for example): 14 | 15 | ```javascript 16 | const MongoClient = require('mongodb').MongoClient; 17 | 18 | // Create a MongoDB server 19 | const url = 'mongodb://localhost:27017/collab-form'; 20 | MongoClient.connect(url) 21 | .catch(function (err) { 22 | if (err) throw err; 23 | }) 24 | ; 25 | 26 | const options = { 27 | db: { 28 | type: 'mongo', 29 | url 30 | } 31 | }; 32 | 33 | // Create a CollabServer instance 34 | CollabServer.start(app, options); 35 | ``` 36 | ## Install dependencies 37 | Once the project cloned, install the dependencies 38 | ``` 39 | npm install 40 | ``` 41 | 42 | ## Build JavaScript bundle and run server 43 | You will have to run the server instance and the React App. 44 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app) 45 | The implementation is compatible with the create-react-app dev server. 46 | 47 | > Note: that both servers need to be running at once for this 48 | to work, you will need to run both `node server.js` AND `npm start` at the same time, this is don 49 | by running (see package.json): 50 | ``` 51 | npm start 52 | ``` 53 | 54 | ## Run app in browser 55 | Load [http://localhost:3000](http://localhost:8080) 56 | 57 | ## Result 58 | You should be able to see a web form and be able to modify it. 59 | If you open another browser window to the same URL, 60 | you should see the two forms synchronized in real time. 61 | 62 | ![image](https://raw.githubusercontent.com/darioAnongba/collab-react-components/master/demos/collab-form/real-time-form.png) 63 | 64 | -------------------------------------------------------------------------------- /demos/collab-form/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collab-form", 3 | "version": "0.1.0", 4 | "description": "A collaborative web form based on react-jsonschema-form and collab-web-forms", 5 | "private": true, 6 | "dependencies": { 7 | "react": "^15.5.4", 8 | "react-dom": "^15.5.4" 9 | }, 10 | "devDependencies": { 11 | "express": "^4.15.2", 12 | "mongodb": "^2.2.26", 13 | "react-scripts": "0.9.5" 14 | }, 15 | "scripts": { 16 | "start": "node server.js & react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demos/collab-form/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chili-epfl/collab-react-components/a14c2acbccaa8fbfd1a4cb61770b578fc90d17b2/demos/collab-form/public/favicon.ico -------------------------------------------------------------------------------- /demos/collab-form/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Collaborative Web forms : Form Example 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demos/collab-form/real-time-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chili-epfl/collab-react-components/a14c2acbccaa8fbfd1a4cb61770b578fc90d17b2/demos/collab-form/real-time-form.png -------------------------------------------------------------------------------- /demos/collab-form/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dario on 11.05.17. 3 | */ 4 | 5 | const Express = require('express'); 6 | const CollabServer = require('../../dist').Server; 7 | const CollabCollection = require('../../dist').Collection; 8 | 9 | /* Create Express application */ 10 | const app = Express(); 11 | // Express only serves static assets in production 12 | if (process.env.NODE_ENV === 'production') { 13 | app.use(Express.static('client/build')); 14 | } 15 | 16 | // Create a CollabServer instance 17 | CollabServer.start(app); 18 | 19 | // Create the collection that will hold the shared data. 20 | const forms = new CollabCollection('forms'); 21 | 22 | // Define the schema of the data 23 | const schema = { 24 | title: 'Bubble sort algorithm', 25 | type: 'object', 26 | properties: { 27 | principle: { 28 | 'type': 'string', 29 | 'title': 'principle', 30 | 'description': 'Explain in one line the principle of bubble sort' 31 | }, 32 | complexity: { 33 | 'type': 'string', 34 | 'title': 'Complexity', 35 | 'description': 'What are the worst case and best case time complexity of bubble sort consequently?', 36 | 'enum': [ 37 | 'O(n), O(n2)', 38 | 'O(n2), O(n3)', 39 | 'O(1), O(n)', 40 | 'None of the above' 41 | ] 42 | }, 43 | comparisons: { 44 | 'type': 'integer', 45 | 'title': 'Comparisons', 46 | 'description': 'What is the maximum number of comparisons if there are 5 elements in array x?', 47 | 'enum': [ 48 | 10, 2, 5, 25 49 | ], 50 | }, 51 | performance: { 52 | 'type': 'string', 53 | 'title': 'Performance', 54 | 'description': 'Explain why the bubble sort algorithm has a worst complexity than other sort algorithms' 55 | }, 56 | } 57 | }; 58 | 59 | const schema2 = { 60 | title: 'Quick sort algorithm', 61 | type: 'object', 62 | properties: { 63 | color: { 64 | 'type': 'string', 65 | 'title': 'Color', 66 | 'description': 'What color is quick sort?', 67 | 'default': '#151ce6' 68 | }, 69 | complexity: { 70 | 'type': 'string', 71 | 'title': 'Complexity', 72 | 'description': 'What are the worst case and best case time complexity of quick sort consequently?', 73 | }, 74 | pivot: { 75 | 'type': 'integer', 76 | 'title': 'pivot', 77 | 'description': 'In this sequence, 11 4 20 45 32 60 98 70, which element seems to be the pivot?', 78 | }, 79 | performance: { 80 | 'type': 'string', 81 | 'title': 'Performance', 82 | 'description': 'Explain why the quick sort algorithm has a good complexity' 83 | }, 84 | } 85 | }; 86 | 87 | // Create the shared form data 88 | forms.createForm('form1', schema, function(err) { 89 | throw err; 90 | }); 91 | forms.createForm('form2', schema2, function(err) { 92 | throw err; 93 | }); -------------------------------------------------------------------------------- /demos/collab-form/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-intro { 18 | font-size: large; 19 | } 20 | 21 | @keyframes App-logo-spin { 22 | from { transform: rotate(0deg); } 23 | to { transform: rotate(360deg); } 24 | } 25 | -------------------------------------------------------------------------------- /demos/collab-form/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import {CollabForm} from '../../../dist/client'; 4 | 5 | class App extends Component { 6 | constructor(props) { 7 | super(props); 8 | 9 | this.uiSchema1 = { 10 | complexity: { 11 | 'ui:placeholder': 'Choose one' 12 | }, 13 | comparisons: { 14 | 'ui:widget': 'radio', 15 | 'ui:options': { 16 | 'inline': true 17 | }, 18 | 'ui:placeholder': 'Choose one' 19 | }, 20 | performance: { 21 | 'ui:widget': 'textarea', 22 | 'ui:options': { 23 | 'rows': 5 24 | } 25 | } 26 | }; 27 | 28 | this.state = { 29 | id: 'form1', 30 | uiSchema: this.uiSchema1, 31 | }; 32 | } 33 | 34 | onChangeForm() { 35 | if (this.state.id === 'form1') { 36 | const uiSchema = { 37 | color: { 38 | 'ui:widget': 'color' 39 | }, 40 | complexity: { 41 | 'ui:placeholder': 'Example: O(n2), O(n3)' 42 | }, 43 | pivot: { 44 | 'ui:widget': 'range', 45 | }, 46 | performance: { 47 | 'ui:widget': 'textarea', 48 | 'ui:options': { 49 | 'rows': 5 50 | } 51 | } 52 | }; 53 | 54 | this.setState({ 55 | id: 'form2', 56 | uiSchema, 57 | }); 58 | } 59 | } 60 | 61 | render() { 62 | return ( 63 |
64 |
65 |
66 |
67 |

Group exercise: Algorithmic quizz

68 |
69 | 70 |
71 | 77 |
78 |
79 |
80 |
81 | ); 82 | } 83 | } 84 | 85 | export default App; 86 | -------------------------------------------------------------------------------- /demos/collab-form/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /demos/collab-form/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /demos/collab-form/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') 9 | ); 10 | -------------------------------------------------------------------------------- /demos/collab-form/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demos/collab-rich-editor/README.md: -------------------------------------------------------------------------------- 1 | # Rich Collaborative Editor Example 2 | 3 | A collaborative rich text editor using [collab-react-components](https://github.com/chili-epfl/collab-react-components) 4 | 5 | This example demonstrates 6 | - Implementation of a backend server using Express and websockets. 7 | - Initialisation of the collaborative server instance. 8 | - Creation of a new rich text document on the server 9 | - Utilisation of the CollabRichEditor component 10 | 11 | 12 | In this demo, data is not persisted. To persist data, run a Mongo 13 | server and initialize use id in `collab-react-components` as follows (for example): 14 | 15 | ```javascript 16 | const MongoClient = require('mongodb').MongoClient; 17 | 18 | // Create a MongoDB server 19 | const url = 'mongodb://localhost:27017/collab-form'; 20 | MongoClient.connect(url) 21 | .catch(function (err) { 22 | if (err) throw err; 23 | }) 24 | ; 25 | 26 | const options = { 27 | db: { 28 | type: 'mongo', 29 | url 30 | } 31 | }; 32 | 33 | // Create a CollabServer instance 34 | CollabServer.start(app, options); 35 | ``` 36 | ## Install dependencies 37 | Once the project cloned, install the dependencies 38 | ``` 39 | npm install 40 | ``` 41 | 42 | ## Build JavaScript bundle and run server 43 | You will have to run the server instance and the React App. 44 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app) 45 | The implementation is compatible with the create-react-app dev server. 46 | 47 | > Note: that both servers need to be running at once for this 48 | to work, you will need to run both `node server.js` AND `npm start` at the same time, this is don 49 | by running (see package.json): 50 | ``` 51 | npm start 52 | ``` 53 | 54 | ## Run app in browser 55 | Load [http://localhost:3000](http://localhost:8080) 56 | 57 | ## Result 58 | You should be able to see an editor and be able to type in it. 59 | If you open another browser window to the same URL, 60 | you should see the two editors synchronized in real time. 61 | 62 | ![image](https://raw.githubusercontent.com/darioAnongba/collab-react-components/master/demos/collab-rich-editor/real-time.png) 63 | 64 | -------------------------------------------------------------------------------- /demos/collab-rich-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collab-rich-editor", 3 | "version": "0.1.0", 4 | "description": "A rich text editor based on Quill and collab-web-forms", 5 | "private": true, 6 | "dependencies": { 7 | "react": "^15.5.4", 8 | "react-dom": "^15.5.4" 9 | }, 10 | "devDependencies": { 11 | "express": "^4.15.2", 12 | "mongodb": "^2.2.26", 13 | "react-scripts": "0.9.5" 14 | }, 15 | "scripts": { 16 | "start": "node server.js & react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demos/collab-rich-editor/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chili-epfl/collab-react-components/a14c2acbccaa8fbfd1a4cb61770b578fc90d17b2/demos/collab-rich-editor/public/favicon.ico -------------------------------------------------------------------------------- /demos/collab-rich-editor/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Collaborative Web forms : Rich Editor Example 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/collab-rich-editor/real-time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chili-epfl/collab-react-components/a14c2acbccaa8fbfd1a4cb61770b578fc90d17b2/demos/collab-rich-editor/real-time.png -------------------------------------------------------------------------------- /demos/collab-rich-editor/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dario on 11.05.17. 3 | */ 4 | 5 | const Express = require('express'); 6 | const CollabServer = require('../../dist').Server; 7 | const CollabCollection = require('../../dist').Collection; 8 | 9 | /* Create Express application */ 10 | const app = Express(); 11 | // Express only serves static assets in production 12 | if (process.env.NODE_ENV === 'production') { 13 | app.use(Express.static('client/build')); 14 | } 15 | 16 | // Create a CollabServer instance 17 | CollabServer.start(app); 18 | 19 | // Create the collection that will hold the shared data. 20 | const documents = new CollabCollection('documents'); 21 | 22 | // Create the shared form data 23 | documents.createRichText('rich-editor1', 'My initial data'); -------------------------------------------------------------------------------- /demos/collab-rich-editor/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-intro { 18 | font-size: large; 19 | } 20 | 21 | @keyframes App-logo-spin { 22 | from { transform: rotate(0deg); } 23 | to { transform: rotate(360deg); } 24 | } 25 | -------------------------------------------------------------------------------- /demos/collab-rich-editor/src/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dario on 11.05.2017. 3 | */ 4 | import React, { Component } from 'react'; 5 | import { CollabRichEditor } from '../../../dist/client'; 6 | 7 | // App component - represents the whole app 8 | export default class App extends Component { 9 | render() { 10 | return ( 11 |
12 |
13 |

Rich Collaborative Editor

14 |
15 | 16 | 20 |
21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /demos/collab-rich-editor/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /demos/collab-rich-editor/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /demos/collab-rich-editor/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') 9 | ); 10 | -------------------------------------------------------------------------------- /demos/collab-rich-editor/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/index'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collab-react-components", 3 | "version": "0.0.7", 4 | "description": "React components for collaborative editing: text, rich text, and complex forms", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/chili-epfl/collab-react-components" 8 | }, 9 | "author": "Dario Anongba Varela (http://dario-anongba.me)", 10 | "license": "MIT", 11 | "bugs": { 12 | "url": "https://github.com/chili-epfl/collab-react-components/issues" 13 | }, 14 | "homepage": "https://github.com/chili-epfl/collab-react-components#readme", 15 | "keywords": [ 16 | "react", 17 | "sharedb", 18 | "collaborative editor", 19 | "collaborative form", 20 | "collaborative rich editor", 21 | "react-jsonschema-form", 22 | "quill", 23 | "react-quill" 24 | ], 25 | "options": { 26 | "mocha": "--require scripts/mocha_runner src/**/__tests__/**/*.js", 27 | "prettier": "--single-quote --jsx-bracket-same-line --trailing-comma es5 --semi" 28 | }, 29 | "scripts": { 30 | "cs-format": "prettier $npm_package_options_prettier ./src/**/*.js --write", 31 | "precommit": "lint-staged", 32 | "prepublish": "rimraf ./dist && babel --plugins transform-es2015-modules-umd src --ignore __tests__ --out-dir ./dist", 33 | "lint": "eslint ./src", 34 | "lintfix": "eslint ./src --fix", 35 | "testonly": "mocha $npm_package_options_mocha", 36 | "test": "npm run lint && npm run testonly", 37 | "test-watch": "npm run testonly -- --watch --watch-extensions js" 38 | }, 39 | "lint-staged": { 40 | "src/**/*.js": [ 41 | "npm run lint", 42 | "npm run cs-format", 43 | "git add" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "babel-cli": "^6.6.4", 48 | "babel-core": "^6.7.4", 49 | "babel-eslint": "^6.0.2", 50 | "babel-plugin-transform-es2015-modules-umd": "^6.6.5", 51 | "babel-polyfill": "^6.7.4", 52 | "babel-preset-es2015": "^6.6.0", 53 | "babel-preset-react": "^6.5.0", 54 | "babel-preset-stage-2": "^6.5.0", 55 | "chai": "^3.5.0", 56 | "enzyme": "^2.2.0", 57 | "eslint": "^2.7.0", 58 | "eslint-plugin-babel": "^3.1.0", 59 | "eslint-plugin-react": "^4.2.3", 60 | "husky": "^0.13.3", 61 | "jsdom": "^8.1.0", 62 | "lint-staged": "^3.4.1", 63 | "mocha": "^2.4.5", 64 | "nodemon": "^1.9.1", 65 | "prettier": "^1.3.1", 66 | "react": "^15.0.0", 67 | "react-addons-test-utils": "^15.0.0", 68 | "react-dom": "^15.0.0", 69 | "rich-text": "^3.1.0", 70 | "rimraf": "^2.6.1", 71 | "sinon": "^1.17.3" 72 | }, 73 | "peerDependencies": { 74 | "react": "~0.14.8 || ^15.0.0", 75 | "react-dom": "~0.14.8 || ^15.0.0" 76 | }, 77 | "dependencies": { 78 | "babel-runtime": "^6.6.1", 79 | "http": "0.0.0", 80 | "prop-types": "^15.5.9", 81 | "react-jsonschema-form": "^0.48.2", 82 | "react-quill": "^1.0.0-rc.2", 83 | "reconnecting-websocket": "^3.0.5", 84 | "rich-text": "^3.1.0", 85 | "sharedb": "^1.0.0-beta.7", 86 | "sharedb-mongo": "^1.0.0-beta.3", 87 | "sharedb-string-binding": "^1.0.0", 88 | "underscore": "^1.8.3", 89 | "websocket-json-stream": "0.0.3", 90 | "ws": "^3.0.0" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /scripts/mocha_runner.js: -------------------------------------------------------------------------------- 1 | var jsdom = require('jsdom').jsdom; 2 | 3 | var exposedProperties = ['window', 'navigator', 'document']; 4 | 5 | global.document = jsdom(''); 6 | global.window = document.defaultView; 7 | Object.keys(document.defaultView).forEach((property) => { 8 | if (typeof global[property] === 'undefined') { 9 | exposedProperties.push(property); 10 | global[property] = document.defaultView[property]; 11 | } 12 | }); 13 | 14 | global.navigator = { 15 | userAgent: 'node.js' 16 | }; 17 | 18 | documentRef = document; 19 | 20 | require('babel-core/register'); 21 | require('babel-polyfill'); -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { shallow, mount, render } from 'enzyme'; 4 | import { expect } from 'chai'; 5 | import sinon from 'sinon'; 6 | 7 | import MyComponent from '../index'; 8 | 9 | // Demo tests 10 | 11 | // Shallow Rendering 12 | // https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md 13 | describe('Shallow Rendering', () => { 14 | it('to have three `.icon-test`s', () => { 15 | const wrapper = shallow(); 16 | expect(wrapper.find('.icon-test')).to.have.length(3); 17 | }); 18 | 19 | it('simulates click events', () => { 20 | const buttonClick = sinon.spy(); 21 | const wrapper = shallow(); 22 | wrapper.find('button').simulate('click'); 23 | expect(buttonClick.calledOnce).to.equal(true); 24 | }); 25 | }); 26 | 27 | // Full DOM Rendering 28 | // https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md 29 | describe('Full DOM Rendering', () => { 30 | it('allows us to set props', () => { 31 | const wrapper = mount(); 32 | expect(wrapper.props().bar).to.equal('baz'); 33 | wrapper.setProps({ bar: 'foo' }); 34 | expect(wrapper.props().bar).to.equal('foo'); 35 | }); 36 | 37 | it('calls componentDidMount', () => { 38 | sinon.spy(MyComponent.prototype, 'componentDidMount'); 39 | const wrapper = mount(); 40 | expect(MyComponent.prototype.componentDidMount.calledOnce).to.be.true; 41 | MyComponent.prototype.componentDidMount.restore(); 42 | }); 43 | }); 44 | 45 | // Static Rendered Markup 46 | // https://github.com/airbnb/enzyme/blob/master/docs/api/render.md 47 | describe('Static Rendered Markup', () => { 48 | it('renders three `.icon-test`s', () => { 49 | const wrapper = render(); 50 | expect(wrapper.find('.icon-test').length).to.equal(3); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/client/CollabEditor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dario on 11.04.17. 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import StringBinding from 'sharedb-string-binding'; 7 | import connection from './connection'; 8 | 9 | /** 10 | * Collaborative Editor. 11 | * 12 | * Creates a new collaborative editor fetching data from the server and displaying it in a