-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.html
308 lines (254 loc) · 12.2 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="lib/bootstrap.min.css">
<link rel="stylesheet" href="lib/prettify.css">
<style type="text/css">
body {
padding-top: 60px;
}
p {
word-spacing: 1.5px;
font-size: 16px;
line-height: 1.625em;
padding:0 0 0.8125em 0;
}
.wide li {
margin: 0 0 0.8125em 0;
font-size: 16px;
line-height: 1.625em;
}
section {
padding-top: 30px;
}
#pad {
width: 90%;
}
.prettyprint {
background-color: #fefbf3;
padding: 9px;
border: 1px solid rgba(0,0,0,.2);
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1);
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.1);
box-shadow: 0 1px 2px rgba(0,0,0,.1);
}
#trains {
list-style-type: none;
margin: 0;
width: 100%;
}
#trains li {
margin: 0 3px 3px 3px;
padding: 0.4em;
padding-left: 1.5em;
font-size: 1.4em;
height: 18px;
border: 1px solid #CCC;
outline: none;
-webkit-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1);
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.1);
box-shadow: 0 1px 2px rgba(0,0,0,.1);
}
#trains li:hover {
background-color: #fff9bd;
}
.footer {
background-color: #EEE;
min-width: 940px;
padding: 30px 0;
text-shadow: 0 1px 0 white;
border-top: 1px solid #E5E5E5;
-webkit-box-shadow: inset 0 5px 15px rgba(0,0,0,.025);
-moz-box-shadow: inset 0 5px 15px rgba(0,0,0,.025);
}
</style>
<title></title>
</head>
<body onload="prettyPrint()">
<div class="topbar">
<div class="fill">
<div class="container">
<a class="brand" href="#">ShareJS</a>
<ul class="nav">
<!-- <li class="active"><a href="#">Intro</a></li>-->
</ul>
<ul class="nav secondary-nav">
<li><a><span class="label warning" id="status">Loading...</span></a></li>
<li><a href="demos.html">Demos</a></li>
<li><a href="https://github.com/josephg/ShareJS/wiki">Documentation</a></li>
<li><a href="https://github.com/josephg/ShareJS">Github</a></li>
</ul>
</div>
</div>
</div>
<div class="container">
<section id="intro">
<div class="page-header"><h1>ShareJS – Live concurrent editing in your app.</h1></div>
<p>ShareJS is an Operational Transform library for NodeJS & browsers. It lets you easily do live concurrent editing
in your app.</p>
<div class="row">
<div class="row">
<div class="span6 offset1">
<h3>Here’s etherpad in 4 lines.</h3>
<textarea id="pad" rows="8" disabled>Loading...</textarea>
<span class="help-block">Everyone viewing this page can see your edits live. Open this in another browser window for maximum wow.</span>
</div>
<div class="span8">
<h3>Code</h3>
<pre class="prettyprint">
sharejs.open('blogs', 'steve', 'text', function(error, doc) {
var elem = document.getElementById('pad');
doc.attach_textarea(elem);
});
</pre></div>
</div>
</div>
</section>
<section id="body">
<p>
You’re writing a web app. Your app contains data that users edit. Your users should be able to use your app from multiple computers if they need to. Sometimes you want multiple users to view & edit the same data.</p>
<p>How do you make that work, without the data going out of sync and without losing anything?</p>
<p>One option is to have Submit buttons everywhere. <a href="http://stackexchange.com/">Stackexchange</a>, <a href="http://reddit.com/">Reddit</a> and <a href="http://news.ycombinator.com/">Hacker News</a> work like this. You can’t write half a HN comment from your laptop and half from your phone. Most wikis have a ‘save’ button and do locking. It's like the ghostly hand of <a href="http://www.codinghorror.com/blog/2006/08/source-control-anything-but-sourcesafe.html">visual sourcesafe</a> has somehow infected the web with sloppy engineering and a terrible user experience. If your wiki lock expires while you’re doing a big edit, you're in a world of hurt.</p>
<p>The solution is <strong><a href="http://en.wikipedia.org/wiki/Operational_transformation">Operational Transformation</a></strong> (OT). If you haven’t heard of it, OT is a class of algorithms that do <i>multi-site realtime concurrency</i>. OT is like realtime git. It works with any amount of lag (from zero to an extended holiday). It lets users make live, concurrent edits with low bandwidth. OT gives you eventual consistency between multiple users without retries, without errors and without any data being overwritten.</p>
<p>Unfortunately, implementing OT sucks. There's a million algorithms with different tradeoffs, mostly trapped in academic papers. The algorithms are really hard and time consuming to implement correctly. We need some good libraries, so any project can just plug in OT if they need it.</p>
<p>I am an ex <a href="http://wave.google.com">Google Wave</a> engineer. Wave took 2 years to write and if we rewrote it today, it would take almost as long to write a second time. (What??)</p>
<p>Enter <strong><a href="https://github.com/josephg/ShareJS">ShareJS</a></strong>. ShareJS is a simple (~4k LOC) coffeescript server & web client library for OT. With ShareJS, your website can let your users collaboratively edit text documents and arbitrary JSON data in realtime. (Like this one.)</p>
</section>
<section id="worky">
<div class="page-header"><h3>How it works</h3></div>
<p>As you edit the text area at the top of this page, ShareJS generates <i>operations</i>. Operations are like mini commits to the document. (Eg, <code>insert:'hi', position:50</code>.)</p>
<p>Like <a href="http://subversion.tigris.org/">subversion</a>, the server has a version number. If multiple users submit an operation at the same version, one of the edits is applied directly and the other user’s edit is automatically transformed by the server and then applied. Transforming is a bit like a <a href="http://book.git-scm.com/4_rebasing.html">git rebase operation</a>.</p>
<p>In your browser, your edits are visible immediately. Edits from other people get transformed on top of yours. Unlike normal SCM systems, the algorithm is very careful to make sure that everyone ends up with the same document, no matter what order the operations are actually applied in. This allows the whole update & commit stuff to happen completely automatically, in realtime. There are no conflict markers or any of that jazz.</p>
</section>
<section id="examples">
<div class="page-header"><h3>Here’s some more examples</h3></div>
<ul class="wide">
<li><a href="/wiki/Main">A live editable wiki.</a> The wiki renders the page contents live using a <a href="http://daringfireball.net/projects/markdown/">markdown</a> library called <a href="http://softwaremaniacs.org/playground/showdown-highlight/">showdown</a>. You can change the last part of the URL to see different wiki pages. <i>(They get created on the fly when you open them.)</i></li>
<li><a href="/code.html">A collaborative code editor.</a> When you open this URL, you’ll create a new document with a random ID. Share the URL around and you can pair program.</li>
<li><a href="https://gist.github.com/1341527">This nodejs script</a> will watch a ShareJS document and re-save a copy on disk whenever it gets edited. You can use it with the code editor, or with the wiki, or any ShareJS document.</li>
</ul>
<p>Browse through the <a href="/demos.html">demo gallery</a> for more.</p>
<p>As well as plain text, ShareJS has OT functions defined for arbitrary JSON objects. Here’s a list of all the trains in Thomas the Tank Engine. Anyone viewing this webpage can concurrently reorder the list.</p>
<div class="row">
<div class="span5 offset3">
<ul id="trains">
</ul>
<span class="help-block">Drag the trains around, <i>collaboratively</i>.</span>
</div>
</div>
<br />
<p>Jeremy (who implemented the JSON OT code) wrote <a href="/hex.html">this multiplayer game</a> using ShareJS. Share the URL to play with someone at a different computer.</p>
<p>There’s currently nothing stopping you playing as the other team, or cheating and writing a script which moves the other player’s pieces around. You can also use the game board as a low res 3 color display and write rude messages with it. Jeremy’s game is <i>cool</i>.</p>
<p>A simpler demo of the JSON interface is a <b>really trivial</b> <a href="peep-peep/peep-peep.html">Twitter clone</a>. It's based the chat part of Jeremy's Hex game, but strips out all the game logic to make things a bit easier to understand.</p>
<p>ShareJS is <i>mostly</i> working, but it’s still a bit shit. The next version will fix some connection flakiness and tell you who’s editing the document with you. I want to add rich text support as well, though I can’t find any good HTML rich text editors.</p>
<p>If you’re making the next <a href="https://trello.com/">Trello</a> or Google Spreadsheets, check out <a href="https://github.com/josephg/ShareJS">ShareJS</a>. Make your web apps realtime and collaborative. It's the one true way ;)</p>
</section>
</div>
<footer class="footer">
<div class="container">
<p class="pull-right">With love, <a href="http://josephg.com">Joseph Gentle</a>, 6 Nov 2011</p>
</div>
</footer>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.17/jquery-ui.min.js"></script>
<script src="/channel/bcsocket.js"></script>
<script src="/share/share.uncompressed.js"></script>
<script src="/share/textarea.js"></script>
<script src="/lib/prettify.js"></script>
<script src="/share/json.js"></script>
<script>
$(function() {
// *** Editor window
var elem = document.getElementById('pad');
var connection = sharejs.open('users', 'steve', 'text', function(error, doc) {
if (error) {
console.log(error);
} else {
elem.disabled = false;
doc.attach_textarea(elem);
}
});
// *** Connection status display
var status = document.getElementById('status');
var register = function(state, klass, text) {
connection.on(state, function() {
status.className = 'label ' + klass;
status.innerHTML = text;
});
};
register('ok', 'success', 'Online');
register('connecting', 'warning', 'Connecting...');
register('disconnected', 'important', 'Offline');
register('stopped', 'important', 'Error');
// *** Draggable trains example
sharejs.open('trains', 'thomas', 'json', function(error, doc) {
if (error) {
if (console) {
console.error(error);
}
return;
}
if (doc.created) {
doc.set(["Thomas the Tank Engine",
"Edward the Blue Engine",
"Henry the Green Engine",
"Gordon the Big Engine",
"James the Red Engine",
"Percy the Small Engine",
"The Fat Controller"
]);
}
var trains = doc.get();
$.each(trains, function(i, train) {
$('#trains').append($('<li>').text(trains[i])); //.attr('data-id', i)
});
var dragFrom = null;
doc.at().on('move', function(from, to) {
//console.log('move', from, to);
if (dragFrom === from) {
dragFrom = to;
} else {
//console.log('#trains :nth-child(' + (from + 1) + ')');
//console.log($('#trains :nth-child(' + 1 + ')'));
//console.log($('#trains :nth-child(' + 2 + ')'));
if (to === 0) {
$('#trains :nth-child(' + (from + 1) + ')')
.remove()
.insertBefore($('#trains :nth-child(' + (to + 1) + ')'));
} else {
$('#trains :nth-child(' + (from + 1) + ')')
.remove()
.insertAfter($('#trains :nth-child(' + to + ')'));
}
}
});
$("#trains").sortable({
helper: 'clone',
start: function(event, ui) {
dragFrom = ui.item.index();
ui.item.remove();
},
update: function(event, ui) {
//console.log('update', ui.item.index());
dragFrom = null;
},
change: function(event, ui) {
var newPos = ui.placeholder.index();
//console.log('sending move', dragFrom, newPos);
doc.at().move(dragFrom, newPos);
dragFrom = newPos;
},
beforeStop: function(event, ui) {
//console.log(ui.item);
ui.placeholder.before(ui.item);
},
axis: 'y'
});
$("#trains").disableSelection();
});
});
</script>
</body>
</html>