-
Notifications
You must be signed in to change notification settings - Fork 8
/
converse.html
333 lines (317 loc) · 15.2 KB
/
converse.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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
<!DOCTYPE html>
<html>
<head>
<link rel="canonical" href="https://gun.eco/converse.html" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./dep/jquery.js"></script>
<script src="dep/rsc/analytics.js"></script>
<link rel="stylesheet" type="text/css" href="styles/typography.css">
</head>
<body>
<div class="header">
<div class="title">
<h1 class="oxygen top">Converse tutorial</h1>
<a href="../../index.html"><img src="img/gun_logo_white.svg"></a>
</div>
<div class="panelbg green"></div>
</div>
<div class="wrapper oxygen">
<p class="article green_first">Now that we can jot down our thoughts in a <a href="think.html">To-Do</a> app, let us add something ambitious to it. To-Do: Build a realtime chat app! Which is exactly what we are going to do in this tutorial. It is the next natural step, we can track our thoughts - but now we want to have conversations about them with others to refine our ideas.</p>
<p>So what are the requirements? The ability to write a message, send a message, and receive messages. We will also need a space to keep these messages in, and a web page to access the chat through. Let's start with making a copy of our code from the To-Do app! You can edit the code below, which will update the live preview.</p>
</div>
<div class="edit">... loading editor and preview ...
<textarea style="height: 37em"><html>
<body>
<h1>Thoughts</h1>
<form>
<input><button>Add</button>
</form>
<ul></ul>
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gun/gun.js"></script>
<script>
var gun = Gun().get('thoughts');
$('form').on('submit', function(event){
event.preventDefault();
gun.set($('input').val());
$('input').val("");
});
gun.map().on(function(thought, id){
var li = $('#' + id).get(0) || $('<li>').attr('id', id).appendTo('ul');
if(thought){
$(li).text(thought);
} else {
$(li).hide();
}
});
$('body').on('dblclick', 'li', function(event){
gun.get(this.id).put(null);
});
</script>
</body>
</html></textarea>
</div>
<script>
var code = $('.edit').load('./editor.html', function(){setTimeout(function(){
var editor = $('.CodeMirror')[0].CodeMirror;
editor.live(function(frame){
$('body', frame).on('submit', 'form', function(e){e.preventDefault()});
});
editor.setValue(code);
},1)}).find('textarea').text();
</script>
<style>
.none { display: none }
.step-at { display: block }
textarea { border: none; width: 100%; }
</style>
<div id="step-0" class="wrapper oxygen step">
<p>The first thing we need to do is update the HTML.</p>
<ol>
<li>Change the text in the <u>h1</u> tag to "Chat".</li>
<li>Move the <u>ul</u> tags up to be above the <u>form</u> tag.</li>
<li>Add an opening closing <u>textarea</u> tag inbetween the input and button.</li>
<li>Make the <u>button</u> say "send" instead of "Add".</li>
<li>
Now we want to model what the HTML for our message will look like. Copy the following into the editor to be above the first <u>script</u> tag:
<textarea style="height: 9em;"><div class="model" style="display: none;">
<li class="message">
<i class="when">0</i>
<b class="who"></b>:
<span class="what"></span>
</li>
</div>
</textarea>
</li>
</ol>
<p>What is happening?</p>
<ul>
<li>We do not actually have any messages yet, so we do not want an empty chat message displaying on the screen.</li>
<li>There are a bunch of other HTML tags out there, let us do a quick review of the new ones we added.
<ul>
<li>The <u>textarea</u> tag is like an <u>input</u> tag except longer content.</li>
<li>A <u>div</u> tag is a generic HTML tag to put anything else inside of.</li>
<li>The <i><u>i</u>talicize</i> tag stylizes your text as so.</li>
<li>The <b><u>b</u>old</b> tag does similarly.</li>
<li>And a <u>span</u> tag is a generic HTML tag to put text inside of.</li>
</ul>
</li>
<li>It turns out that HTML allows you to add extra data to your page, let us go over them.
<ul>
<li>One of these attributes is a <u>class</u> which lets you categorize your HTML.</li>
<li>The other one is a <u>style</u> attribute, which lets you style your HTML with <a href="https://www.google.com/search?q=CSS">CSS</a>.</li>
</ul>
<li>To keep our models hidden, we wrap them inside of <u>div</u> container, and then tell the computer not to display it.</li>
<li>When we receive a message, we will make a copy of the <u>"message"</u> model and fill it up with the data using javascript.</li>
</ul>
<button class="none green">All tweaked!</button>
</div>
<div id="step-1" class="wrapper oxygen step">
<p>Before we get started with writing any javascript, we need to clean up some code that no longer applies! Go ahead and remove the following 3 lines of code from the bottom of our script tag:</p>
<textarea style="height: 4em;">
$('body').on('dblclick', 'li', function(event){
gun.get(this.id).put(null);
});</textarea>
<button class="none green">I removed it!</button>
</div>
<div id="step-2" class="wrapper oxygen step">
<p>Wonderful! Now we want to change the name of the data so we do not get our previous app's data. Additionally, if we want this data to be shared and updated in realtime with other people, we have to connect to them directly or a server that does this for us. Modify the line where we initialize gun (hint: it is the very first line) with this:</p>
<textarea style="height: 1em;"> var gun = Gun('https://gunjs.herokuapp.com/gun').get('tutorial/chat/app');</textarea>
<p>What does this do?</p>
<ul>
<li>The URL <u>'https://gunjs.herokuapp.com/gun'</u> happens to be a gun peer.</li>
<li>Anyone can run a gun peer, by using <a href="https://github.com/amark/gun">gun on a server</a>.</li>
<li>This particular peer is a testing server, please do not use it outside these tutorials - it is not very fast or reliable, as we regularly delete data from it.</li>
<li>The <u>'tutorial/chat/app'</u> is the name of our shared data, it acts as a key to help us find it.</li>
</ul>
<button class="none green">Connect!</button>
</div>
<div id="step-3" class="wrapper oxygen step">
<p>Oops, our current code is handling the data incorrectly. We need to fix that, adjust the commented line in the editor to this:</p>
<textarea style="height: 1em;">gun.map().val(function(message, id){</textarea>
<p>Why?</p>
<ul>
<li>Because we do not expect <u>message</u>s to change, therefore we just get the <u>val</u>ue instead of reacting <u>on</u> updates.</li>
<li>We also swap the order, placing <u>map</u> in front because we want to get each message item in the set (or that will be in the set) once rather than every item in the set every time it changes. Kinda confusing, but just reread that a couple of times and think about it.</li>
</ul>
<button class="none green">Got it.</button>
</div>
<div id="step-4" class="wrapper oxygen step">
<p>We still have breaking changes, we need to revamp the inside of the val function with the following:</p>
<textarea style="height: 8em;">
if(!message){ return; }
var $li = $(
$('#' + id).get(0) ||
$('.model').find('.message').clone(true).attr('id', id).appendTo('ul')
);
$li.find('.who').text(message.who);
$li.find('.what').text(message.what);
$li.find('.when').text(message.when);</textarea>
<p>What does this do?</p>
<ul>
<li>Because this is a live app that everybody is coding, if we get bad data we need to cancel by returning.</li>
<li>As before, if the list item already exists, use it.</li>
<li><u>||</u> or find a model message, <u>clone</u> it, set its <u>id</u> so we can reuse it later, and <u>append</u> it to the list.</li>
<li>Then fill in the model with the specific pieces of data from our message.</li>
</ul>
<p>Now we need to make sure that the data we save matches the same format as the messages we receive.</p>
<button class="none green">Okay.</button>
</div>
<div id="step-5" class="wrapper oxygen step">
<p>Replace everything inside of the form on submit function with the following:</p>
<textarea style="height: 8em;">
event.preventDefault();
var message = {};
message.who = $('form').find('input').val();
message.what = $('form').find('textarea').val();
message.when = new Date().getTime();
gun.set(message);
$('form').find('textarea').val("");</textarea>
<p>What is happening?</p>
<ul>
<li>We still want to prevent the default behavior of the browser, which would be to reload the page.</li>
<li>Then we want to create a javascript object using <u>{}</u> curly braces, which allows us to describe complex data.<li>Our data will be composed of three parts, <u>who</u> sent the message, <u>what</u> the message is, and <u>when</u> it was written.</li>
<li>We then add this message to a <u>set</u> of messages in the chat, which will get <u>map</u>ped over.</li>
<li>Finally we want to clear out the <u>textarea</u> we wrote our message in, so that way we can type another.</li>
</ul>
<button class="none green">Try sending a message!</button>
</div>
<div id="step-6" class="wrapper oxygen step">
<p>Wow! Our chat is fully functioning! You can pat yourself on the back and be done now if you want. But there are lots of little things we can do to polish up the experience.</p>
<p>We will not be doing any design, as that exceeds the scope of what this tutorial teaches. So prepare for your app to look pretty ugly, you can try to make things pretty on your own. What then, is left?</p>
<ul>
<li>The message's sent time is not human friendly, we will need to convert it.</li>
<li>Messages currently may be shown out of order, so we need to order them correctly.</li>
<li>Every time a message comes in you have to scroll down to read it, we should automate that.</li>
</ul>
<button class="none green">Let's do it!</button>
</div>
<div id="step-7" class="wrapper oxygen step">
<p>Tutorial Unfinished, Sad Face. We're Working On It Though!</p>
<p>Check out the <a href="https://github.com/amark/gun">github</a> in the meanwhile for more documentation.</p>
</div>
<div id="step-8" class="wrapper oxygen step">
<p>Congratulations! You have built a realtime chat app! That is quite the accomplishment, you should go celebrate by eating a sandwhich.</p>
<p>Now that we can converse with people, we might want to know <i>who</i> they are. In the next tutorial we will create a contact app with some basic social network features and learn a little bit about security.</p>
<a href="#">Coming Later!</a>
<p>Check out the <a href="https://github.com/amark/gun">github</a> in the meanwhile for more documentation.</p>
</div>
<!-- FOOTER -->
<div id="footer">
<div class="container" class="page">
<div class="footer_social">
<div class="grid">
<a href="http://www.github.com/amark/gun" target="_blank"><img src="img/icons/github.svg"></a>
<a href="https://gitter.im/amark/gun" target="_blank"><img src="img/icons/gitter.svg"></a>
<a href="https://twitter.com/databasegun" target="_blank"><img src="img/icons/twitter.svg"></a>
<a href="https://www.linkedin.com/company/gun-inc" target="_blank"><img src="img/icons/linkedin.svg"></a>
</div>
</div>
</div>
<nav>
<a href="https://gun.eco/" target="_blank">Get Started</a>
<a href="docs">Documentation</a>
<a href="https://gun.eco/think.html">Tutorials</a>
<a href="https://github.com/amark/gun">Download</a>
<a href="http://gun.js.org/enterprise/team.html" target="_blank">Our Team</a>
</nav>
</div>
<footer>
<div class="container">
<p>Designed with ♥ by Mark Nadal, the gun team, and many very awesome contributors. Liberally licensed under <a href="https://github.com/amark/gun/wiki/package.json"> Zlib or MIT or Apache 2.0.</a></p>
</div>
</footer>
<!-- END FOOTER -->
<script>
$('.step').addClass('none').first().addClass('step-at');
$('button').on('click', next).removeClass('none');
function next(){
$('.step-at').removeClass('step-at');
$('#step-' + (++next.at)).addClass('step-at');
(next.step[next.at] || function(){})();
}
next.at = 0;
next.comment = /\s*\/\*(.*?)\*\/\s*/i;
next.unwrap = /\s*<\/?(html|body)>\s*\s*/ig;
next.wrap = function(c){ return '<html>\n\t<body>\n\t\t' + c + '\n\t</body>\n</html>' }
next.code = function(){ return $('<div>' + next.mir.getValue().replace(next.wrap, '') + '</div>') }
next.step = [null, function(){
next.mir = $('.CodeMirror')[0].CodeMirror;
var code = next.code();
if(code.find('h1').text().toLowerCase() == 'thoughts'){
code.find('h1').text('Chat');
}
if(code.find('button').text().toLowerCase() == 'add'){
code.find('button').text('send');
}
if(!code.find('textarea').length){
code.find('button').before("<textarea>");
}
if(code.find('form').index() < code.find('ul').index()){
code.find('ul').insertBefore(code.find('form')).after('\n\t\t');
code.contents().filter(function(){
return $(this).text().indexOf('\n') >= 0 && $(this).prev().is('form')
}).remove();
code.find('form').after('\n\n\t\t');
}
if(!code.find('.model').length){
code.find('script').first().before($('#step-0').find('textarea').first().text() + '\n\t\t');
}
next.mir.setValue(next.wrap($.trim(code.html())));
}, function(){
var code = next.code();
var script = code.find('script').last();
script.text(script.text().slice(0, script.text().indexOf(
$('#step-1').find('textarea').first().text().replace(/\s/ig,'').slice(0,7)
)));
next.mir.setValue(next.wrap($.trim(code.html())));
}, function(){
var code = next.code();
var script = code.find('script').last();
if(script.text().indexOf('Gun()')){
script.text(script.text().replace('Gun()', 'Gun(' +
($('#step-2').find('textarea').first().text().match(/Gun\((.*?)\)/i)||[])[1]
+ ')'));
}
if(script.text().indexOf("'thoughts'")){
script.text(script.text().replace("'thoughts'",
($('#step-2').find('textarea').first().text().match(/get\((.*?)\)/i)||[])[1]
));
}
script.text(script.text().replace(/gun\.map\(\)\.on(.*)\n/ig, "gun.map().on(function(thought, id){ // Edit this line!\n"));
next.mir.setValue(next.wrap($.trim(code.html())));
}, function(){
var code = next.code();
var script = code.find('script').last();
if(script.text().indexOf('.map().on')){
script.text(script.text().replace(/gun\.map\(\)\.on(.*)\n/ig,
$('#step-3').find('textarea').first().text() + '\n'
));
}
next.mir.setValue(next.wrap($.trim(code.html())));
}, function(){
var code = next.code();
var script = code.find('script').last();
var jsq = jsQuery(script.text());
if(0 > jsq.find('.function').last().text().indexOf('.clone(')){
jsq.find('.function').last().empty().append(
jsQuery($('#step-4').find('textarea').first().text()).contents()
);
script.text(jsq.text());
}
next.mir.setValue(html_beautify(next.wrap($.trim(code.html()))));
}, function(){
var code = next.code();
var script = code.find('script').last();
var jsq = jsQuery(script.text());
if(0 > jsq.find('.function').first().text().indexOf('.what = ')){
jsq.find('.function').first().empty().append(
jsQuery($('#step-5').find('textarea').first().text()).contents()
);
script.text(jsq.text());
}
next.mir.setValue(html_beautify(next.wrap($.trim(code.html()))));
}]
</script>
</body>
</html>