-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathlocal.js
745 lines (594 loc) · 25.9 KB
/
local.js
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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
// Start a WebSocket connection with the server using SocketIO
var socket = io();
// Note that the SocketIO client-side library and this file (local.js)
// were both imported in index.html right before the </body> tag
// SAVING LOCAL STATE -- GLOBAL VARS (ugh)
var currentPlayerId;
var nextPlayerId;
var animationId;
var currentGist;
// Meant to be temporary:
var currentAccessToken;
// Quick fix to check for first turn, for first gist edit
var gistNewlyCreated;
/* -------------------------------------------------
LIST OF IDs, DYNAMIC ELEMENTS:
- loginmodal container for login screen
- loginbutton <button> to log in
- editor <textarea> collab code editor
- timeleft <p> shows minutes:seconds
- currentturn name of current player
- nextturn name of next player
- playerlist <ol> list of player names
- myname <span> user's name
- currentgist <p> displays latest gist info
---------------------------------------------------- */
var loginModalView = document.getElementById('loginmodal');
var loginButtonView = document.getElementById('loginbutton');
var editorInputView = document.getElementById('editor');
var timeLeftView = document.getElementById('timeleft');
var currentTurnView = document.getElementById('currentturn');
var nextTurnView = document.getElementById('nextturn');
var playerListView = document.getElementById('playerlist');
var myNameView = document.getElementById('myname');
var myNameListItemView = document.getElementById('me');
var currentGistView = document.getElementById('currentgist');
/* -------------------------------------------------
GITHUB AUTHENTICATION
---------------------------------------------------- */
// If GitHub access_token is available as a parameter, log in!
// TODO: pass the token as a header instead? can client access it that way?
if (getAllUrlParams().access_token) {
console.log('*********** AUTHENTICATED!!! **********');
console.log('access_token from URL params: ' + getAllUrlParams().access_token);
// TODO: show loading animation while waiting???
// TODO: refactor getAllUrlParams(), don't need it, just need ONE param!
// For now, save the access token as a global variable (I'm sure this is SUPER wrong though!)
currentAccessToken = getAllUrlParams().access_token;
getJSON('https://api.github.com/user?access_token=' + currentAccessToken)
.then(handleUserLogin).catch(handleError);
// Otherwise, if user has not yet started the login process,
} else {
// Get the client ID environment variable from the server
get('/github-client')
.then(function(clientId){
console.log('>>>>>>>>>>>>>> Received response from /github-client route!:');
console.log(clientId);
// Render loginLinkView with the GitHub auth request URL
// TODO: review GitHub SCOPES when I try adding more features
document.getElementById('loginlink').setAttribute('href', 'https://github.com/login/oauth/authorize?client_id=' + clientId + '&scope=gist');
document.getElementById('loginlink').style.display = 'block';
// Hide "...loading..." placeholder
document.getElementById('loginloading').style.display = 'none';
}, handleError).catch(handleError);
}
/* -------------------------------------------------
ACE EDITOR SETUP
https://ace.c9.io
---------------------------------------------------- */
var editor = ace.edit('editor');
var Range = ace.require('ace/range').Range;
editor.setTheme("ace/theme/monokai");
editor.getSession().setMode('ace/mode/javascript');
/* ------------------------------------------------------------
EVENT LISTENERS / SEND DATA TO SERVER
EVENT NAMES: CLIENT FUNCTIONS:
- connection Send: SocketIO built-in event
- disconnect Send: SocketIO built-in event
- userLogin Send: handleUserLogin
- editorTextChange Send: handleLocalEditorTextChange
Receive: handleServerEditorTextChange
- playerListChange Receive: handlePlayerListChange
- updateState Receive: handleUpdateState
- turnChange Receive: handleTurnChange
- editorCursorChange Send: handleLocalEditorCursorChange
Receive: handleServerEditorCursorChange
- editorScrollChange Send: handleLocalEditorScrollChange
Receive: handleServerEditorScrollChange
- createNewGist Receive: handleCreateNewGist
- newGistLink Receive: handleNewGistLink
Send: (sent after creating or forking)
-------------------------------------------------------------- */
editor.getSession().on('change', handleLocalEditorTextChange);
editor.getSession().selection.on('changeCursor', handleLocalEditorCursorChange);
editor.getSession().on('changeScrollLeft', handleLocalEditorScrollChange);
editor.getSession().on('changeScrollTop', handleLocalEditorScrollChange);
// When client connects to server,
socket.on('connect', function(){
// Generate default name to match socket.id
//myNameView.textContent = 'Anonymous-' + socket.id.slice(0,4);
// Update ID of first <li> in playerListView for player name highlighting with togglePlayerHighlight()
myNameListItemView.id = socket.id;
});
// When client disconnects, stop the timer!
socket.on('disconnect', function(){
console.log('* * * * * * * * DISCONNECTED FROM SERVER * * * * * * * *');
window.cancelAnimationFrame(animationId);
timeLeftView.textContent = '....';
});
// Log in with authenticated user's GitHub data
function handleUserLogin (userData) {
console.log('**************** Logged in! GitHub User Data: *********************');
console.log(userData);
// Update views with user's GitHub name and avatar
updateLoggedInView(userData.login, userData.avatar_url);
// Notify server that user logged in
socket.emit('userLogin', {login: userData.login, avatar_url: userData.avatar_url});
}
// Send editorInputView data to server
function handleLocalEditorTextChange (event) {
//console.log('handleLocalEditorTextChange event! value: ');
//console.log(event);
//console.log('%c ' + editor.getValue(), 'color: green; font-weight: bold;');
// If user is the current player, they can broadcast
if (socket.id === currentPlayerId) {
//console.log('Sending data to server!')
// Send data to server
socket.emit( 'editorTextChange', editor.getValue() );
}
}
// Function that prevents user from typing when it's not their turn
function preventUserTyping (event) {
event.preventDefault();
//return false;
};
// Send user's new name to server and update UI
function handleUserNameChange (event) {
//console.log('handleUserNameChange event! value: ');
//console.log('%c ' + myNameView.textContent, 'color: green; font-weight: bold;');
// Update UI if user is the current or next player
if (currentPlayerId === socket.id) {
updateCurrentTurnView(myNameView.textContent);
} else if (nextPlayerId === socket.id) {
updateNextTurnView(myNameView.textContent);
}
// Send user's new name to server
socket.emit('userNameChange', myNameView.textContent);
}
// Send cursor and selection data to server
function handleLocalEditorCursorChange (event) {
//console.log('editorCursorChange fired!');
//console.log('%c ' + event, 'color: green; font-weight: bold;');
// Cursor object:
// {column, row}
// Selection Range object:
// { end: {column, row}, start: {column, row} }
// Send to server:
socket.emit( 'editorCursorChange', { cursor: editor.getSession().selection.getCursor(), range: editor.getSession().selection.getRange() } );
}
// Send scroll data to server
function handleLocalEditorScrollChange (event) {
//console.log('editorScrollChange (left or top) fired!');
//console.log('%c scrollLeft: ' + editor.getSession().getScrollLeft() + ', scrollTop: ' + editor.getSession().getScrollTop(), 'color: green; font-weight: bold;');
// Send to server:
socket.emit('editorScrollChange', { scrollLeft: editor.getSession().getScrollLeft(), scrollTop: editor.getSession().getScrollTop() });
}
// TODO: Test 'input' event some more in different browsers!
// maybe add support for IE < 9 later?
/* -------------------------------------------------
EVENT LISTENERS / RECEIVE DATA FROM SERVER
---------------------------------------------------- */
socket.on('editorTextChange', handleServerEditorTextChange);
socket.on('editorCursorChange', handleServerEditorCursorChange);
socket.on('editorScrollChange', handleServerEditorScrollChange);
socket.on('playerListChange', handlePlayerListChange);
socket.on('updateState', handleUpdateState);
socket.on('turnChange', handleTurnChange);
socket.on('createNewGist', handleCreateNewGist);
socket.on('newGistLink', handleNewGistLink);
// When receiving new editorInputView data from server
function handleServerEditorTextChange (data) {
//console.log('editorTextChange event received!');
//console.log('%c ' + data, 'color: blue; font-weight: bold;');
updateEditorView(data);
}
// When receiving new cursor/selection data from server
function handleServerEditorCursorChange (data) {
//console.log('%c cursorChange event received!', 'color: blue; font-weight: bold;');
//console.dir(data);
// Set Ace editor's cursor and selection range to match
var updatedRange = new Range(data.range.start.row, data.range.start.column, data.range.end.row, data.range.end.column);
editor.getSession().selection.setSelectionRange( updatedRange );
}
// When receiving new scroll data from server
function handleServerEditorScrollChange (data) {
//console.log('%c editorScrollChange event received!', 'color: blue; font-weight: bold;');
//console.dir(data);
// Set Ace editor's scroll position to match
editor.getSession().setScrollLeft(data.scrollLeft);
editor.getSession().setScrollTop(data.scrollTop);
}
// When receiving new player list data from server
function handlePlayerListChange (playerData) {
//console.log('%c playerListChange event received!', 'color: blue; font-weight: bold;');
//console.dir(playerData);
// Transform the data!!
// Transform into an array to more easily reorder it
var playerIdArray = Object.keys(playerData);
var userIndex = playerIdArray.indexOf(socket.id);
var playerListTopSegment = playerIdArray.slice(userIndex+1);
var playerListBottomSegment = playerIdArray.slice(0, userIndex);
// Merge the two arrays, reording so current user is at the top
// (but removed from this list), without modifying the turn order
playerIdArray = playerListTopSegment.concat(playerListBottomSegment);
// Generate an array of ids, user logins, and avatar_urls for updating the UI
var playerArray = playerIdArray.map(function(id){
return {id: id, login: playerData[id].login, avatar_url: playerData[id].avatar_url};
});
//console.log('playerArray:');
//console.log(playerArray);
// Get names of current and next players based on saved local IDs
var currentPlayerName = playerData[currentPlayerId].login;
var nextPlayerName = playerData[nextPlayerId].login;
//console.log('Updating UI with currentPlayerName: ' + currentPlayerName + ', nextPlayerName: ' + nextPlayerName);
// Update the UI
updatePlayerListView(playerArray);
updateCurrentTurnView(currentPlayerName);
updateNextTurnView(nextPlayerName);
}
// When receiving turnChange event from server
function handleTurnChange (turnData) {
console.log('%c turnChange event received! TIME: ' + new Date().toString().substring(16,25), 'color: blue; font-weight: bold;');
//console.dir(turnData);
// Remove highlight from previous current player's name in playerListView
togglePlayerHighlight(false);
// Temporarily save the previous player ID for later comparison
var previousPlayerId = currentPlayerId;
// Update local state
currentPlayerId = turnData.current.id;
nextPlayerId = turnData.next.id;
// If user's turn is ending, fork and/or edit the gist before passing control to next player!
if (socket.id === previousPlayerId) {
console.log("User's turn is about to end.");
// If a gist has been created, AND it's been edited at least once (first game round ended),
// AND if the current player is about to change (and the user is the previous player),
if (turnData.gist != null && !gistNewlyCreated && currentPlayerId !== previousPlayerId) {
//console.log("handleTurnChange: now forking and editing gist " + turnData.gist.id);
// fork and edit the current gist on behalf of previous player and send new ID to server
forkAndEditGist(turnData.gist.id, editor.getValue());
// Otherwise, JUST EDIT the current gist (if one exists) on behalf of previous player and send new ID to server
} else if (turnData.gist != null) {
//console.log("handleTurnChange: now editing gist " + turnData.gist.id);
editGist(turnData.gist.id, editor.getValue());
}
}
// If user is no longer the current player, prevent them from typing/broadcasting!
if (socket.id !== currentPlayerId) {
console.log("User's turn is over.");
editor.setReadOnly(true);
// Otherwise if user's turn is now starting,
} else {
console.log("User's turn is starting!");
// let the user type/broadcast again
editor.setReadOnly(false);
}
// Update UI
togglePlayerHighlight(true);
updateTimeLeftView(turnData.millisRemaining);
updateCurrentTurnView(turnData.current.name);
updateNextTurnView(turnData.next.name);
toggleMyTurnHighlight();
updateCurrentGistView(turnData.gist);
}
// When receiving updateState event from server,
// when new users join the game!
function handleUpdateState (turnData) {
//console.log('%c handleUpdateState event received! TIME: ' + new Date().toString().substring(16,25), 'color: blue; font-weight: bold;');
//console.dir(turnData);
// Remove highlight from previous current player's name in playerListView
togglePlayerHighlight(false);
// Temporarily save the previous player ID for later comparison
var previousPlayerId = currentPlayerId;
// Update local state
currentPlayerId = turnData.current.id;
nextPlayerId = turnData.next.id;
//console.log('Updated local state. Current ID: ' + currentPlayerId + ', next ID: ' + nextPlayerId);
// Add highlight to the new current player's name in playerListView
togglePlayerHighlight(true);
// Update UI
updateTimeLeftView(turnData.millisRemaining);
updateCurrentTurnView(turnData.current.name);
updateNextTurnView(turnData.next.name);
toggleMyTurnHighlight();
updateCurrentGistView(turnData.gist);
}
// When receiving "newGistLink" event from server,
function handleNewGistLink (gistData) {
// Update local state
console.log("called handleNewGist at " + new Date().toString().substring(16,25), 'color: green; font-weight: bold;');
console.log(gistData);
// Update views
updateCurrentGistView(gistData);
}
/* -------------------------------------------------
FUNCTIONS TO UPDATE VIEWS
---------------------------------------------------- */
// Update views for logged in user
function updateLoggedInView (userName, userAvatar) {
// Hide loginModalView
loginModalView.style.opacity = '0';
window.setTimeout(function(){
loginModalView.style.display = 'none';
}, 900);
// Set myNameView to use GitHub username
myNameView.textContent = userName;
// Display user's GitHub avatar image
var userAvatarElem = document.createElement('img');
userAvatarElem.src = userAvatar;
userAvatarElem.classList.add('avatar');
myNameListItemView.insertBefore(userAvatarElem, myNameView);
}
// UI highlights to notify user when it's their turn
function toggleMyTurnHighlight () {
// If user is the next player, highlight text box
if (socket.id === currentPlayerId) {
document.body.classList.add('myturn');
} else {
document.body.classList.remove('myturn');
}
}
// Highlight name of current player in playerListView
function togglePlayerHighlight (toggleOn) {
// First check if element exists, for case where user is the only player
if (document.getElementById(currentPlayerId)) {
// Add highlight
if (toggleOn) {
document.getElementById(currentPlayerId).classList.add('highlight');
// Remove highlight
} else {
document.getElementById(currentPlayerId).classList.remove('highlight');
}
}
}
// Using data from server, update list of players
function updatePlayerListView (playerArray) {
// Delete the contents of playerListView each time
while (playerListView.firstChild) {
playerListView.removeChild(playerListView.firstChild);
}
// Put li#me back into playerListView! (using previously saved reference)
playerListView.appendChild(myNameListItemView);
// Append player names to playerListView
playerArray.forEach(function(player){
// Create an <li> node with player's name and avatar
var playerElement = document.createElement('li');
playerElement.id = player.id;
// Display user's GitHub avatar image
var userAvatarElem = document.createElement('img');
userAvatarElem.src = player.avatar_url;
userAvatarElem.classList.add('avatar');
// If this player is the current player, highlight their name
if (player.id === currentPlayerId) {
playerElement.classList.add('highlight');
}
// Append player <li> to playerListView <ol>
playerListView.appendChild(playerElement);
// Append image and text node to player <li>
playerElement.appendChild(userAvatarElem);
playerElement.appendChild(document.createTextNode(player.login));
});
}
// Using data from server, update text in code editor
function updateEditorView (editorData) {
editor.setValue(editorData);
editor.selection.clearSelection();
}
// Update timeLeftView with the time remaining
function updateTimeLeftView (timerDurationMillis) {
//console.log('updateTimeLeftView CALLED with: ' + timerDurationMillis);
var turnEndTimestamp = Date.now() + timerDurationMillis;
// Animate countdown timer
function step(timestamp) {
var millisRemaining = turnEndTimestamp - Date.now();
//console.log('millisRemaining: ' + millisRemaining);
var secondsRemaining = Math.floor(millisRemaining / 1000);
var minutes = Math.floor(secondsRemaining / 60);
var seconds = secondsRemaining % 60;
// Format mm:ss string, padded with zeroes if needed
timeLeftView.textContent = ((minutes.toString().length > 1) ? minutes.toString() : '0' + minutes.toString()) + ':' + ((seconds.toString().length > 1) ? seconds.toString() : '0' + seconds.toString());
if (millisRemaining > 100) {
animationId = window.requestAnimationFrame(step);
}
}
animationId = window.requestAnimationFrame(step);
}
// Update currentTurnView with current player's name
function updateCurrentTurnView (playerName) {
//console.log('Called updateCurrentTurnView with: ' + playerName);
currentTurnView.textContent = playerName;
// If user is the current player, highlight their name
if (socket.id === currentPlayerId) {
currentTurnView.classList.add('highlightme');
currentTurnView.textContent = "It's your turn!";
} else {
currentTurnView.classList.remove('highlightme');
}
}
// Update nextTurnView with next player's name
function updateNextTurnView (playerName) {
//console.log('Called updateNextTurnView with: ' + playerName);
nextTurnView.textContent = playerName;
// If user is the next player, highlight their name
if (socket.id === nextPlayerId) {
nextTurnView.classList.add('highlightme');
nextTurnView.textContent = "You're up next!";
} else {
nextTurnView.classList.remove('highlightme');
}
}
// Append to a list of gist links for this game
function updateCurrentGistView (gistData) {
currentGistView.innerHTML = '<strong>Latest code:</strong> <a href="' + gistData.url + '">' + gistData.url + '</a>';
}
/* -------------------------------------------------
GITHUB API FUNCTIONS
---------------------------------------------------- */
// Make a POST request via AJAX to create a Gist for the current user
function handleCreateNewGist() {
console.log('called handleCreateNewGist at ' + new Date().toString().substring(16,25), 'color: red; font-weight: bold;');
// use currentAccessToken
// use https://developer.github.com/v3/gists/#create-a-gist
// Quick fix for editing gist on first turn
gistNewlyCreated = true;
var gistObject = {
"description": "A saved mob programming session with Learn Teach Code!",
"public": true,
"files": {
"mob-coding-challenge.js": {
"content": editor.getValue() + '\n'
}
}
};
postWithGitHubToken('https://api.github.com/gists', gistObject).then(function(responseText){
//console.log(responseText);
console.log('handleCreateNewGist: response received at ' + new Date().toString().substring(16,25), 'color: red; font-weight: bold;');
console.dir(gistObject);
var gistObject = JSON.parse(responseText);
// Save new gist ID and URL locally
currentGist = {id: gistObject.id, url: gistObject.html_url};
// Send new gist data to server
socket.emit('newGistLink', {id: gistObject.id, url: gistObject.html_url});
updateCurrentGistView({id: gistObject.id, url: gistObject.html_url});
}, handleError);
}
// Make a POST request via AJAX to update a given Gist with the current code
function editGist(gistId, codeEditorContent) {
console.log('called editGist at ' + new Date().toString().substring(16,25), 'color: orange; font-weight: bold;');
// use https://developer.github.com/v3/gists/#edit-a-gist
var gistObject = {
"description": "A saved mob programming session with Learn Teach Code!",
"public": true,
"files": {
"mob-coding-challenge.js": {
"content": codeEditorContent + '\n'
}
}
};
postWithGitHubToken('https://api.github.com/gists/' + gistId, gistObject).then(function(responseText){
//console.log(responseText);
console.log('editGist: response received at ' + new Date().toString().substring(16,25), 'color: orange; font-weight: bold;');
console.dir(gistObject);
// Quick fix for editing gist on first turn
gistNewlyCreated = false;
}, handleError);
}
// Make a POST request via AJAX to fork a given Gist, then commit to it with editGist()
function forkAndEditGist(gistId, codeEditorContent) {
console.log('called forkAndEditGist at ' + new Date().toString().substring(16,25), 'color: red; font-weight: bold;');
// use https://developer.github.com/v3/gists/#fork-a-gist
// TODO later: see if I can refactor this function, maybe have it return a promise, so I can chain it with editGist better?
var gistObject = {"test": ""};
postWithGitHubToken('https://api.github.com/gists/' + gistId + '/forks', gistObject).then(function(responseText){
//console.log(responseText);
console.log('forkAndEditGist: response received at ' + new Date().toString().substring(16,25), 'color: red; font-weight: bold;');
var gistObject = JSON.parse(responseText);
// Send new gist data to server
socket.emit('newGistLink', {id: gistObject.id, url: gistObject.html_url});
// Then edit the new gist:
editGist(gistObject.id, codeEditorContent);
updateCurrentGistView({id: gistObject.id, url: gistObject.html_url});
}, handleError);
}
/* -------------------------------------------------
HELPER FUNCTIONS
---------------------------------------------------- */
// Returns a promise, as a simple wrapper around XMLHTTPRequest
// via http://eloquentjavascript.net/17_http.html
function get(url) {
return new Promise(function(succeed, fail) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function() {
if (req.status < 400)
succeed(req.responseText);
else
fail(new Error("Request failed: " + req.statusText));
});
req.addEventListener("error", function() {
fail(new Error("Network error"));
});
req.send(null);
});
}
// Returns a promise for a POST request, similar to get() above
function postWithGitHubToken(url, postDataObject) {
return new Promise(function(succeed, fail) {
var req = new XMLHttpRequest();
req.open("POST", url, true);
// Set header for POST, like sending form data
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
// Set header for GitHub auth
req.setRequestHeader('Authorization', 'token ' + currentAccessToken);
req.addEventListener("load", function() {
if (req.status < 400)
succeed(req.responseText);
else
fail(new Error("Request failed: " + req.statusText));
});
req.addEventListener("error", function() {
fail(new Error("Network error"));
});
req.send(JSON.stringify(postDataObject));
});
}
// Return object from parsed JSON data from a given URL
// via http://eloquentjavascript.net/17_http.html
function getJSON(url) {
return get(url).then(JSON.parse, handleError);
}
// Lazy error handling for now
function handleError(error) {
console.log("Error: " + error);
};
// Returns an object containing URL parameters
// via https://www.sitepoint.com/get-url-parameters-with-javascript/
function getAllUrlParams(url) {
// get query string from url (optional) or window
var queryString = url ? url.split('?')[1] : window.location.search.slice(1);
// we'll store the parameters here
var obj = {};
// if query string exists
if (queryString) {
// stuff after # is not part of query string, so get rid of it
queryString = queryString.split('#')[0];
// split our query string into its component parts
var arr = queryString.split('&');
for (var i=0; i<arr.length; i++) {
// separate the keys and the values
var a = arr[i].split('=');
// in case params look like: list[]=thing1&list[]=thing2
var paramNum = undefined;
var paramName = a[0].replace(/\[\d*\]/, function(v) {
paramNum = v.slice(1,-1);
return '';
});
// set parameter value (use 'true' if empty)
var paramValue = typeof(a[1])==='undefined' ? true : a[1];
// (optional) keep case consistent
paramName = paramName.toLowerCase();
paramValue = paramValue.toLowerCase();
// if parameter name already exists
if (obj[paramName]) {
// convert value to array (if still string)
if (typeof obj[paramName] === 'string') {
obj[paramName] = [obj[paramName]];
}
// if no array index number specified...
if (typeof paramNum === 'undefined') {
// put the value on the end of the array
obj[paramName].push(paramValue);
}
// if array index number specified...
else {
// put the value at that index number
obj[paramName][paramNum] = paramValue;
}
}
// if param name doesn't exist yet, set it
else {
obj[paramName] = paramValue;
}
}
}
return obj;
}