3 var sortedBy = 'votes';
5 var mobile = (/iphone|ipad|ipod|android|blackberry|mini|windows\sce|palm/i.test(navigator.userAgent.toLowerCase()));
6 // note that this array applies only to this browser session!
7 // ==> could vote twice on same post during different sessions.
10 var userObj = { userID: null, userName: null, userAffil: null };
12 // the Post object is modelled on EtherPad:
13 // [the schoolid and courseid are implied in the meetingid];
14 function assembleNewPostObj(msgBody) {
15 // the postid is assigned at the server;
17 postObj.userid = userObj.userID;
18 postObj.userName = userObj.userName;
19 postObj.userAffil = userObj.userAffil;
20 postObj.body = msgBody;
24 function renderPosts(fresh, post) {
25 if (fresh) $('#posts .postContainer').remove();
26 //$('#total_posts').text(posts.length);
27 // truncate long array of Posts;
28 var sortedPosts = sortedBy == 'created' ? posts.sort(createdDesc) : posts.sort(votesDesc);
29 var displayPosts = sortedPosts//.slice(0, MAXPOSTS - 1);
30 if (post) $("#postTemplate").tmpl(post).appendTo("#posts");
31 if (fresh) $("#postTemplate").tmpl(displayPosts).appendTo("#posts");
32 else $('#posts').reOrder(displayPosts, 'post-')
33 posts.forEach(function(post) {
34 renderComments(post._id)
35 var $post = $('#post-'+post._id);
37 if (post.reports.length >= 2) {
38 if ($('#reportedContainer').length === 0) {
39 $('#posts').append('<div id="reportedContainer"><h1>Flagged Posts</h1><div class="reportedPosts hidden"></div></div>')
40 $('#reportedContainer h1').click(function() {
41 $('.reportedPosts').toggleClass('hidden')
44 $post.addClass('flagged');
45 $('#reportedContainer .reportedPosts').append($post)
47 if (post.votes.indexOf(userID) == -1) {
48 if (!public) $post.find('.postVoteContainer').addClass('unvoted')
50 $post.find('.vote-tally-rect').die()
51 $post.find('.vote-tally-rect').css('cursor', 'default')
54 if (post.reports.indexOf(userID) !== -1) {
55 $post.find('.voteFlag').css('cursor', 'default')
56 $post.find('.voteFlag').css('background', '#888')
59 if (post.userAffil === 'Instructor') {
60 $post.addClass('instructor');
63 if (commentToggle === '4f1efbb248dc57ba43000075') {
64 $('.commenttoggle').empty();
68 $post.find('.voteFlag').css({
73 $post.find('.vote-tally-rect').css('cursor', 'default');
74 if (!post.public) $post.remove();
76 if (!post.public) $post.find('.privacy').text('Private')
82 function renderComments(id) {
84 $.each(posts, function(i, post) {
86 comments = post.comments;
87 if (comments.length >= 1) {
88 $('#post-'+id+' .commentContainer').empty();
89 $('#post-'+id+' .commentAmt').text(comments.length+' ');
90 $('#commentTemplate').tmpl(comments).appendTo('#post-'+id+' .commentContainer');
95 $( '.commentForm :input' ).removeAttr( 'disabled' );
99 $.fn.reOrder = function(_array, prefix) {
100 var array = $.extend([], _array, true);
101 return this.each(function() {
102 prefix = prefix || "";
105 var reported = $('#reportedContainer');
106 for(var i=0; i < array.length; i++) {
107 var sel = '#' + prefix + array[i]._id;
108 if ($(sel).length === 0)
109 array[i] = $("#postTemplate").tmpl(array[i])
113 $(this).find('.postContainer').remove();
115 for(var i=0; i < array.length; i++) {
116 $(this).append(array[i]);
118 $(this).append(reported)
123 function assembleVoteObj(postid, upOrDown) {
124 return { "parentid": postid, "direction": upOrDown };
126 function votesDesc(a, b) {
127 if (a.reports >= 2) {
129 } else if (b.reports >= 2) {
132 // for descending, reverse usual order;
133 return b.votes.length - a.votes.length;
136 function createdDesc(a, b) {
137 if (a.reports >= 2) {
139 } else if (b.reports >= 2) {
142 // for descending, reverse usual order;
143 return new Date(b.date).valueOf() - new Date(a.date).valueOf();
147 function refreshRO() {
148 if (roID !== false || public === true) {
149 $('#editor div').load(rourl, function() {
150 $('#editor').find('style').remove();
155 $(document).ready(function(){
156 userObj.userName = public ? null : userName;
157 userObj.userAffil = public ? null : userAffil;
158 userObj.userID = public ? null : userID;
159 loggedIn = public ? false : true;
162 $('#editor').css('overflow-y', 'auto')
164 setInterval(function() {
167 } else if (RO === true) {
168 $('#editor').empty().append('<div class="readonly"></div>');
169 $('#editor').css('overflow-y', 'auto')
171 setInterval(function() {
176 $('#userBox').removeClass('hidden');
178 $( '.commentForm :input' ).removeAttr( 'disabled' );
180 // add event handlers;
181 $('#backchatHeader input[type="button"]').click(function() {
182 $('#backchatHeaderInstructions').toggle();
184 $('#enterPostTextarea').keyup(function() {
186 var charsUsed = $(this).val().length;
187 if (charsUsed > 25) {
188 $('#charsLeftMsg').text("characters left: " +
189 (charLimit - charsUsed).toString());
191 $('#charsLeftMsg').text(" ");
195 $('#submitPost').click(function() {
196 var form = $( this );
198 var body = $('#enterPostTextarea').val();
200 var newPost = assembleNewPostObj(body);
202 var anonymous = $('#enterPostForm').find( 'input[name=anonymous]' ).is(':checked') ? true : false;
203 var public = $('#enterPostForm').find( 'input[name=private]' ).is(':checked') ? false : true;
204 newPost.anonymous = anonymous;
205 newPost.public = public;
206 socket.emit('post', {post: newPost, lecture: lectureID});
207 $('#enterPostTextarea').val('');
212 $('.vote-tally-rect').live("click", function() {
214 var postid = $(this).parent().attr('data-postid');
215 posts.forEach(function(post) {
216 if (post._id === postid) {
217 if (post.votes.indexOf(userID) == -1) {
218 var newVoteObj = {parentid: postid, userid: userID};
219 socket.emit('vote', {vote: newVoteObj, lecture: lectureID});
221 $(that).css('cursor', 'default')
222 $(that).parent().removeClass('unvoted');
229 $('#amountPosts').change(function() {
230 MAXPOSTS = $(this).val();
233 $('#sortPosts').change(function() {
234 var sort = $(this).val();
235 sortedBy = sortedBy !== sort ? sort : sortedBy;
238 $('.commentForm').live('submit', function(e) {
240 var body = $(this).find('#commentText').val();
242 var anonymous = $( this ).find( 'input[name=anonymous]' ).is(':checked') ? true : false;
246 userName: userObj.userName,
247 userAffil: userObj.userAffil,
249 anonymous: anonymous,
250 parentid: $(this).find('[name=postid]').val()
252 socket.emit('comment', {comment: comment, lecture: lectureID});
253 $(this).find('#commentText').val('');
257 $('.comments').live('click', function(e) {
259 var id = $(this).attr('id').replace('post-', '');
260 $('#post-'+id+' .commentContainer').toggleClass('hidden');
261 if (!public) $('#post-'+id+' .commentForm').toggleClass('hidden');
265 $('.voteFlag').live('click', function() {
267 var id = $(this).parent().parent().attr('id').replace('post-', '');
268 $.each(posts, function(i, post){
269 if (post._id == id) {
270 if (post.reports.indexOf(userID) == -1) {
271 if(confirm('By flagging a comment, you are identifying it as a violation of the FinalsClub Code of Conduct: Keep it academic.')) {
272 socket.emit('report', {report: {parentid: id, userid: userID}, lecture: lectureID});
274 $(that).css('cursor', 'default')
275 $(that).css('background', '#888')
282 // XXX for demonstration purposes only
283 $('.readonlylink').click(function(e) {
286 $.get('/logout', function() {
287 location.reload(true)
291 //=====================================================================
292 // create socket to server; note that we only permit websocket transport
294 var loc = document.location;
295 var port = loc.port == '' ? (loc.protocol == 'https:' ? 443 : 80) : loc.port;
296 var url = loc.protocol + '//' + loc.hostname + ':' + port;
298 socket = io.connect(url + '/backchannel');
301 // incoming messages are objects with one property whose value is the
303 // { "posts": [ <array of Post objects> ] }
304 // { "recentPosts": [ <array of Post objects> ] }
305 // { "vote": [ "postid": <string>, "direction": <"up"|"down"> ] }
306 // Unresolved: whether to send vote messages for local change of display
307 // or new arrays of posts with updated vote counts. Vote message would not
308 // be adequate if it changed order of posts. For now, send two new
309 // arrays with updated vote counts and refrain from sending vote message.
310 var messagesArrived = 0;
311 socket.on('connect', function(){
312 socket.emit('subscribe', lectureID, function(_packet) {
313 posts = _packet.posts;
315 commentToggle = _packet.toggle;
320 socket.on('post', function(post) {
322 renderPosts(false, post);
325 socket.on('vote', function(vote) {
326 posts = posts.map(function(post) {
327 if(post._id == vote.parentid) {
328 if (!public || (public && post.public)) {
329 post.votes.push(vote.userid);
330 $('#post-'+vote.parentid).find('.vote-tally-rect').text(post.votes.length);
338 socket.on('report', function(report) {
339 posts = posts.map(function(post) {
340 if(post._id == report.parentid) {
341 if (!public || (public && post.public)) {
342 post.reports.push(report.userid);
343 if (post.reports.length >= 2) {
344 $('#post-'+post._id).addClass('flagged');
353 socket.on('comment', function(comment) {
354 posts = posts.map(function(post) {
355 if (post._id == comment.parentid) {
356 if (!public || (public && post.public)) {
357 if (!post.comments) {
360 post.comments.push(comment);
361 post.date = new Date();
362 if (sortedBy == 'created') renderPosts();
363 renderComments(comment.parentid);
370 socket.on('disconnect', function(){
371 // XXX something here
374 $('#enterPostTextarea').val("");