',
180 | '
Index: ' + connection.range.index + '
',
181 | '
Length: ' + connection.range.length + '
',
182 | '
'
183 | ].join('');
184 | } else
185 | userDataEl.innerHTML = '(Not focusing on editor.)';
186 |
187 |
188 | userItemEl.appendChild(userNameEl);
189 | userItemEl.appendChild(userDataEl);
190 |
191 | userItemEl.style.backgroundColor = connection.color;
192 | usersListEl.appendChild(userItemEl);
193 | });
194 | }
195 |
196 | usernameInputEl.value = chance.name();
197 | usernameInputEl.focus();
198 | usernameInputEl.select();
199 |
200 | document.getElementById('username-form').addEventListener('submit', function(event) {
201 | cursors.localConnection.name = usernameInputEl.value;
202 | cursors.update();
203 | quill.enable();
204 | document.getElementById('connect-panel').style.display = 'none';
205 | document.getElementById('users-panel').style.display = 'block';
206 | event.preventDefault();
207 | return false;
208 | });
209 |
210 | // DEBUG
211 |
212 | var sharedbSocketStateEl = document.getElementById('sharedb-socket-state');
213 | var sharedbSocketIndicatorEl = document.getElementById('sharedb-socket-indicator');
214 |
215 | shareDBConnection.on('state', function(state, reason) {
216 | var indicatorColor;
217 |
218 | console.log('[sharedb] New connection state: ' + state + ' Reason: ' + reason);
219 |
220 | sharedbSocketStateEl.innerHTML = state.toString();
221 |
222 | switch (state.toString()) {
223 | case 'connecting':
224 | indicatorColor = 'silver';
225 | break;
226 | case 'connected':
227 | indicatorColor = 'lime';
228 | break;
229 | case 'disconnected':
230 | case 'closed':
231 | case 'stopped':
232 | indicatorColor = 'red';
233 | break;
234 | }
235 |
236 | sharedbSocketIndicatorEl.style.backgroundColor = indicatorColor;
237 | });
238 |
--------------------------------------------------------------------------------
/public/javascripts/utils.js:
--------------------------------------------------------------------------------
1 | // Returns a function, that, as long as it continues to be invoked, will not
2 | // be triggered. The function will be called after it stops being called for
3 | // N milliseconds. If `immediate` is passed, trigger the function on the
4 | // leading edge, instead of the trailing.
5 | exports.debounce = function(func, wait, immediate) {
6 | var timeout;
7 | return function() {
8 | var context = this,
9 | args = arguments;
10 | var later = function() {
11 | timeout = null;
12 | if (!immediate) func.apply(context, args);
13 | };
14 | var callNow = immediate && !timeout;
15 | clearTimeout(timeout);
16 | timeout = setTimeout(later, wait);
17 | if (callNow) func.apply(context, args);
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 50px;
3 | font: 13px "Lucida Grande", Helvetica, Arial, sans-serif; }
4 |
5 | a {
6 | text-decoration: none; }
7 |
8 | a:hover {
9 | text-decoration: underline; }
10 |
11 | input#username-input {
12 | box-sizing: border-box;
13 | width: 100%;
14 | margin-top: 10px;
15 | margin-bottom: 10px; }
16 |
17 | button#connect-btn {
18 | width: 100%; }
19 |
20 | .title {
21 | margin-top: 0; }
22 |
23 | .layout {
24 | display: flex; }
25 |
26 | .info-container {
27 | width: 20%;
28 | min-width: 150px;
29 | margin-right: 50px; }
30 |
31 | .editor-container {
32 | width: 80%; }
33 |
34 | #users-panel {
35 | display: none; }
36 |
37 | #users-panel ul {
38 | list-style: none;
39 | padding: 0; }
40 |
41 | #users-panel ul > li {
42 | padding: 5px;
43 | border-radius: 5px;
44 | color: white;
45 | margin-bottom: 10px; }
46 |
47 | #users-panel .user-name {
48 | margin-bottom: 5px; }
49 |
50 | #users-panel .user-data {
51 | display: flex;
52 | flex-wrap: wrap; }
53 |
54 | #users-panel .user-data > div {
55 | flex-grow: 1; }
56 |
57 | .socket-indicator {
58 | height: 10px;
59 | width: 10px;
60 | display: inline-block;
61 | margin-right: 5px;
62 | border-radius: 5px;
63 | vertical-align: baseline; }
64 |
65 | .socket-state {
66 | text-transform: capitalize; }
67 |
68 | /*# sourceMappingURL=style.css.map */
--------------------------------------------------------------------------------
/public/stylesheets/style.css.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "file": "style.css",
4 | "sources": [
5 | "style.scss"
6 | ],
7 | "names": [],
8 | "mappings": "AAAA,AAAA,IAAI,CAAC;EACH,OAAO,EAAE,IAAI;EACb,IAAI,EAAE,kDAAkD,GACzD;;AAED,AAAA,CAAC,CAAC;EACA,eAAe,EAAE,IAAI,GACtB;;AAED,AAAA,CAAC,AAAA,MAAM,CAAC;EACN,eAAe,EAAE,SAAS,GAC3B;;AAED,AAAA,KAAK,AAAA,eAAe,CAAC;EACnB,UAAU,EAAE,UAAU;EACtB,KAAK,EAAE,IAAI;EACX,UAAU,EAAE,IAAI;EAChB,aAAa,EAAE,IAAI,GACpB;;AAED,AAAA,MAAM,AAAA,YAAY,CAAC;EACjB,KAAK,EAAE,IAAI,GACZ;;AAED,AAAA,MAAM,CAAC;EACL,UAAU,EAAE,CAAC,GACd;;AAED,AAAA,OAAO,CAAC;EACN,OAAO,EAAE,IAAI,GACd;;AAED,AAAA,eAAe,CAAC;EACd,KAAK,EAAE,GAAG;EACV,SAAS,EAAE,KAAK;EAChB,YAAY,EAAE,IAAI,GACnB;;AAED,AAAA,iBAAiB,CAAC;EAChB,KAAK,EAAE,GAAG,GACX;;AAED,AAAA,YAAY,CAAC;EACX,OAAO,EAAE,IAAI,GACd;;AAED,AAAa,YAAD,CAAC,EAAE,CAAC;EACd,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,CAAC,GACX;;AAED,AAAkB,YAAN,CAAC,EAAE,GAAG,EAAE,CAAC;EACnB,OAAO,EAAE,GAAG;EACZ,aAAa,EAAE,GAAG;EAClB,KAAK,EAAE,KAAK;EACZ,aAAa,EAAE,IAAI,GACpB;;AAED,AAAa,YAAD,CAAC,UAAU,CAAC;EACtB,aAAa,EAAE,GAAG,GACnB;;AAED,AAAa,YAAD,CAAC,UAAU,CAAC;EACtB,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI,GAChB;;AAED,AAA0B,YAAd,CAAC,UAAU,GAAG,GAAG,CAAC;EAC5B,SAAS,EAAE,CAAC,GACb;;AAED,AAAA,iBAAiB,CAAC;EAChB,MAAM,EAAE,IAAI;EACZ,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,YAAY;EACrB,YAAY,EAAE,GAAG;EACjB,aAAa,EAAE,GAAG;EAClB,cAAc,EAAE,QAAQ,GACzB;;AAED,AAAA,aAAa,CAAC;EACZ,cAAc,EAAE,UAAU,GAC3B"
9 | }
--------------------------------------------------------------------------------
/public/stylesheets/style.scss:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 50px;
3 | font: 13px "Lucida Grande", Helvetica, Arial, sans-serif;
4 | }
5 |
6 | a {
7 | text-decoration: none;
8 | }
9 |
10 | a:hover {
11 | text-decoration: underline;
12 | }
13 |
14 | input#username-input {
15 | box-sizing: border-box;
16 | width: 100%;
17 | margin-top: 10px;
18 | margin-bottom: 10px;
19 | }
20 |
21 | button#connect-btn {
22 | width: 100%;
23 | }
24 |
25 | .title {
26 | margin-top: 0;
27 | }
28 |
29 | .layout {
30 | display: flex;
31 | }
32 |
33 | .info-container {
34 | width: 20%;
35 | min-width: 150px;
36 | margin-right: 50px;
37 | }
38 |
39 | .editor-container {
40 | width: 80%;
41 | }
42 |
43 | #users-panel {
44 | display: none;
45 | }
46 |
47 | #users-panel ul {
48 | list-style: none;
49 | padding: 0;
50 | }
51 |
52 | #users-panel ul > li {
53 | padding: 5px;
54 | border-radius: 5px;
55 | color: white;
56 | margin-bottom: 10px;
57 | }
58 |
59 | #users-panel .user-name {
60 | margin-bottom: 5px;
61 | }
62 |
63 | #users-panel .user-data {
64 | display: flex;
65 | flex-wrap: wrap;
66 | }
67 |
68 | #users-panel .user-data > div {
69 | flex-grow: 1;
70 | }
71 |
72 | .socket-indicator {
73 | height: 10px;
74 | width: 10px;
75 | display: inline-block;
76 | margin-right: 5px;
77 | border-radius: 5px;
78 | vertical-align: baseline;
79 | }
80 |
81 | .socket-state {
82 | text-transform: capitalize;
83 | }
84 |
--------------------------------------------------------------------------------
/views/error.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= message
5 | h2= error.status
6 | pre #{error.stack}
7 |
--------------------------------------------------------------------------------
/views/index.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | .layout
5 | .info-container
6 | h1.title quill-sharedb-cursors
7 | p
8 | | An attempt at cursor sync in a collaborative editing scenario using
9 | a(href="https://quilljs.com") Quill
10 | | and
11 | a(href="https://github.com/share/sharedb") ShareDB
12 | | .
13 |
14 | p
15 | | Built by
16 | a(href="https://github.com/pedrosanta") pedrosanta
17 | | at
18 | a(href="https://reedsy.com") Reedsy
19 | | .
20 |
21 | p
22 | | Docs and discussion
23 | a(href="https://github.com/pedrosanta/quill-sharedb-cursors")
24 | | at
25 | i.fa.fa-lg.fa-github-square
26 | | GitHub
27 | | .
28 |
29 | h3 Sockets
30 | p
31 | strong ShareDB:
32 | |
33 | span#sharedb-socket-indicator.socket-indicator
34 | span#sharedb-socket-state.socket-state
35 | p
36 | strong Cursors:
37 | |
38 | span#cursors-socket-indicator.socket-indicator
39 | span#cursors-socket-state.socket-state
40 |
41 | #connect-panel
42 | h3 Username
43 | form#username-form
44 | | Set your username and connect to edit:
45 | br
46 | input#username-input(type='text')
47 | br
48 | button#connect-btn Connect
49 | #users-panel
50 | h3 Users editing
51 | ul#users-list
52 | .editor-container
53 | #editor
54 |
--------------------------------------------------------------------------------
/views/layout.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | title= title
5 | link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css')
6 | link(rel='stylesheet', href='/stylesheets/style.css')
7 | link(rel='stylesheet', href='/quill.snow.css')
8 | link(rel='stylesheet', href='/quill-cursors.css')
9 | body
10 | block content
11 | script(src='//cdnjs.cloudflare.com/ajax/libs/chance/1.0.6/chance.min.js')
12 | script(src='/dist/bundle.js')
13 | block scripts
14 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | entry: './public/javascripts/main.js',
5 |
6 | output: {
7 | filename: 'bundle.js',
8 | path: path.resolve(__dirname, 'public/dist')
9 | }
10 | };
11 |
--------------------------------------------------------------------------------