1// init when the page first loads 2window.viewingPost = false 3 4window.renderPostArea = function(){ 5 $('#postViewCommentsAndInputContainer').show(); 6 $('#postViewAndCommentsContainer').show() 7 $('#postViewContainer').show(); 8 updateReviewInputPermission() 9 $('#post-comments-input-container').show() 10 $('#rightColumn').hide() 11} 12 13window.hidePostArea = function(){ 14 window.viewingPost = false 15 $('#postViewCommentsAndInputContainer').hide(); 16 $('#rightColumn').removeAttr('style') 17} 18 19window.renderPost = function (post, type = "post", flaggable = true, shouldEscapeHTML = true) { 20 21 // clear anything that might be in there now 22 $("#post").remove() 23 $("#postLoadingSpinner").hide(); 24 25 // let the web app know that it should display this post until it's hidden by another event 26 window.viewingPost = true 27 renderPostArea() // sometimes it's hidden because of chat race conditions 28 29 var userUpvoted = false; 30 var userDownvoted = false; 31 if(store.get("userData")){ // have to check for userData because bots / first page load deep links don't have getSetUserData at all or yet 32 userUpvoted = post.upvoteList.includes(store.get("userData").coursicleID) 33 userDownvoted = post.downvoteList.includes(store.get("userData").coursicleID) 34 } 35 36 // sometimes we construct posts (from our own data, like professor reviews) that have html in them, so we don't want to escape (it's safe) 37 if(shouldEscapeHTML){ 38 post.userName = escapeHTML(post.userName) 39 post.title = escapeHTML(post.title) 40 post.body = escapeHTML(post.body) 41 } 42 43 //2/26/25 we determined that the coursicle bot post header was unnecessary for reviews. 44 let postHeader = (type == "professorPost" ? '' : `<div id='post-header'> 45 <div id='postUserPhoto'> 46 <img src='/shared/getImage.php?type=userPhoto&file=${post.userPhoto}&size=small' /> 47 </div> 48 49 <div id='post-author-and-labels'> 50 <div id='post-author'>${post.userName}</div> 51 <div class='yearAndMajorContainer'> 52 <div class="userBadge year ${lowercaseFirstLetter(post.userYear)}">${post.userYear}</div> 53 <div class="userBadge major">${post.userMajor}</div> 54 </div> 55 </div> 56 ${post.posted ? ` 57 <div id='post-timestamp'>${prettyDate(post.posted)}</div> 58 ` : ''} 59 </div>`) 60 61 $('#postViewContainer').append($.parseHTML(` 62 <div id='post' data-id='${post.id}' data-coursicleid=${post.coursicleID}> 63 ` + postHeader + ` 64 <div id='post-title'>${post.title}</div> 65 <div id='post-body-container'>${post.body}</div> 66 ${post.imageURL ? ` 67 <div id='post-image'> 68 <img src='/shared/getImage.php?type=postPhoto&file=${post.imageURL}' /> 69 </div> 70 ` : ''} 71 <div id='post-footer'> 72 <div id='post-votes'> 73 <div id='post-upvote-btn' class=${userUpvoted && 'upvoted'}> 74 <i class="fa-light fa-angle-up"></i> 75 </div> 76 <div id='post-vote-count' class= 'post-vote-count ${userUpvoted && 'count-upvote'} ${userDownvoted && 'count-downvote'}'>${post.votes}</div> 77 <div id='post-downvote-btn' class=${userDownvoted && 'downvoted'}> 78 <i class="fa-light fa-angle-down"></i> 79 </div> 80 </div> 81 <div id='post-comments'> 82 <i class='fa-regular fa-comment'></i> 83 <div id='post-comment-count'>${post.commentCount}</div> 84 </div> 85 ${flaggable ? ` 86 <div id='post-flag'class='flag-modal-opener flag-post'> 87 <i class="fa-regular fa-flag"></i> 88 <div id='post-flag-text'>Flag</div> 89 </div> 90 ` : ''} 91 <div id='post-share'> 92 <i class='fa-regular fa-share'></i> 93 Share 94 </div> 95 </div> 96 </div> 97 `)) 98
99 // specify the class of post, so that we know what kind of upvote and flag to send (e.g. post, professor, etc.) 100 $("#post").removeClass("post professorPost"); 101 $("#post").addClass(type); 102 103 $('#post-vote-count').removeClass("false") 104 $('#postViewContainer').removeClass("empty") 105} 106 107window.renderComments = function(comments, type="comment", idToScrollTo = null) { 108 // doesn't look good with the fade, getting rid of this 109 //$("#commentLoadingSpinner").show(); 110 if (!comments.length) { 111 $('#no-comments-notice').show() 112 } else { 113 $('#no-comments-notice').hide() 114 } 115 $('.comment').remove() 116 $('#postCommentsContainer').show(); 117 const commentReplies = [] 118 comments.forEach(comment => { 119 if (comment.replyingToCommentID == '') { 120 $(renderComment(comment, type=type)).hide().appendTo('#postCommentsContainer').fadeIn(200) 121 } else { 122 commentReplies.push(comment) 123 } 124 }) 125 126 let indexToLookAt = 0 127 while (commentReplies.length) { 128 const reply = commentReplies[indexToLookAt] 129 const parentComment = $(`.comment[data-id=${reply.replyingToCommentID}]`) 130 if (parentComment.length) { 131 $(renderComment(reply, type=type)).hide().appendTo(parentComment).fadeIn(200) 132 const indexOfReplyToRemove = commentReplies.indexOf(reply) 133 commentReplies.splice(indexOfReplyToRemove, 1) 134 indexToLookAt = 0 135 } else { 136 indexToLookAt++ 137 } 138 } 139 140 //we don't want to scroll to anything in particular if they're just perusing reviews 141 if(!idToScrollTo){ 142 return 143 } 144 145 // the below is an effort to scroll to the review a user just posted 146 //so they get some visual confirmation. Because of sorting, top level reviews should be at the bottom. 147 //replies, though, are bound to their parent review and so cannot be scrolled to the bottom. 148 let commentToScrollTo = null; 149 $('.comment').each(function(){ 150 let dataID = parseInt($(this).attr('data-id')) 151 if(dataID == idToScrollTo){ 152 commentToScrollTo = $(this) 153 return false 154 } 155 }) 156 157 //the case that comes to mind is deleting a top level comment. 158 if(!commentToScrollTo){ 159 return 160 } 161 162 commentToScrollTo.get(0).scrollIntoView({ block: "center" }) 163 164} 165 166function renderComment(comment, type="comment") { 167 168 var userUpvoted = false; 169 var userDownvoted = false; 170 if(store.get("userData")){ // have to check for userData because bots / first page load deep links don't have getSetUserData at all or yet 171 userUpvoted = comment.upvoteList.includes(store.get("userData").coursicleID) 172 userDownvoted = comment.downvoteList.includes(store.get("userData").coursicleID) 173 } 174 175 return $.parseHTML(` 176 <div class='comment ${type} ${!Number(comment.replyingToCommentID) ? 'topLevel' : 'reply'}' data-id=${comment.id} data-coursicleid=${comment.coursicleID}> 177 <div class='comment-header'> 178 <!-- 179 <div class='comment-author-image comment-dont-collapse'> 180 <img src='/shared/getImage.php?type=userPhoto&file=${comment.userPhoto}&size=small' class="comment-dont-collapse"/> 181 </div> 182 --> 183 <div class='comment-author-timestamp-and-labels'> 184 <div class='comment-author-and-timestamp'> 185 <!--<div class='comment-author'>${escapeHTML(comment.userName)}</div>--> 186 ${comment.additionalHTMLBeforeTimeLabel ? comment.additionalHTMLBeforeTimeLabel : ''} 187 <span class='comment-time-dot'>â¢</span> 188 <div class='comment-timestamp'>${prettyDate(comment.commented, true)}</div> 189 </div> 190 <div class='yearAndMajorContainer'> 191 <div class="userBadge year ${lowercaseFirstLetter(comment.userYear)}">${comment.userYear}</div> 192 <div class="userBadge major">${comment.userMajor}</div> 193 </div> 194 </div> 195 <div class='comment-options-menu-btn comment-dont-collapse'> 196 <i class='fa-regular fa-ellipsis flag-remove-ellipsis comment-dont-collapse'></i> 197 </div> 198 </div> 199 <div class='comment-body'>${escapeHTML(comment.body)}</div> 200 <div class='comment-footer'> 201 <div class='comment-reply-btn comment-dont-collapse' data-id=${comment.id}> 202 <i class='fa-regular fa-comment comment-dont-collapse'></i> 203 Reply 204 </div> 205 <div class='comment-upvote-btn ${userUpvoted && 'upvoted comment-dont-collapse'}'> 206 <i class='fa-light fa-angle-up comment-dont-collapse'></i> 207 </div> 208 <div class='comment-vote-count ${userDownvoted && 'count-downvote comment-dont-collapse'} ${userUpvoted && 'count-upvote'}'>${comment.votes}</div> 209 <div class='comment-downvote-btn ${userDownvoted && 'downvoted'}'> 210 <i class='fa-light fa-angle-down comment-dont-collapse'></i> 211 </div> 212 </div> 213 </div> 214 `) 215} 216 217 218$(document).on('click', '#post-upvote-btn', function () { 219 window.masterUpvotePost($(this).parents().eq(2)) 220}
220) 221 222$(document).on('click', '#post-downvote-btn', function () { 223 window.masterDownvotePost($(this).parents().eq(2)) 224}) 225 226window.masterUpvotePost = function (votedPost) { 227 if (!chatActionMasterCheck("upvote")) { 228 requestUserInformation("upvote") 229 return 230 } 231 var id = votedPost.data("id") 232 var prefix = "" 233 if (votedPost.hasClass("post-preview")) {prefix=".pp"} 234 else if (votedPost.hasClass("search-preview")) {prefix=".search"} 235 else if (votedPost.hasClass("profile-preview")) {prefix=".profile"} 236 else {prefix="#post"} 237 238 const downvoteBtnEl = votedPost.find(prefix+'-downvote-btn') 239 const upvoteBtnEl = votedPost.find(prefix+'-upvote-btn') 240 const voteCountEl = votedPost.find(prefix+'-vote-count') 241 242 var vote = 'up' 243 action = upvoteBtnEl.hasClass('upvoted') ? 'remove' : 'add' 244 if (downvoteBtnEl.hasClass('downvoted')) { 245 vote = 'down' 246 action = 'remove' 247 } 248 var voteCountBefore = Number(voteCountEl.text()) 249 250 if($(`.post-preview[data-id='${id}']`).length != 0) {upvotePostType(id, ".post-preview", action, vote, voteCountBefore)} 251 if($(`#post[data-id='${id}']`).length != 0) {upvotePostType(id, "#post", action, vote, voteCountBefore)} 252 if($(`.profile-preview[data-id='${id}']`).length != 0) {upvotePostType(id, ".profile-preview", action, vote, voteCountBefore)} 253 if($(`.search-preview[data-id='${id}']`).length != 0) {upvotePostType(id, ".search-preview", action, vote, voteCountBefore)} 254 255 // what type of post is it? 256 if(votedPost.hasClass("post")){ 257 voteTarget = "post"; 258 }else if(votedPost.hasClass("professorPost")){ 259 voteTarget = "professor"; 260 id = id.split("_")[1]; 261 } 262 263 $.ajax({ 264 url: '/shared/setVote.php', 265 type: 'POST', 266 data: { 267 uuid: store.get('uuid'), 268 school: retrieveSchoolParameter(), 269 voteTarget: voteTarget, 270 identifier: id, 271 action, 272 vote, 273 }, 274 }) 275} 276 277window.masterDownvotePost = function (votedPost) { 278 if (!chatActionMasterCheck("downvote")) { 279 requestUserInformation("downvote") 280 return 281 } 282 var id = votedPost.data("id") 283 var prefix = "" 284 if (votedPost.hasClass("post-preview")) {prefix=".pp"} 285 else if (votedPost.hasClass("search-preview")) {prefix=".search"} 286 else if (votedPost.hasClass("profile-preview")) {prefix=".profile"} 287 else {prefix="#post"} 288 289 const downvoteBtnEl = votedPost.find(prefix+'-downvote-btn') 290 const upvoteBtnEl = votedPost.find(prefix+'-upvote-btn') 291 const voteCountEl = votedPost.find(prefix+'-vote-count') 292 293 var vote = 'down' 294 action = downvoteBtnEl.hasClass('downvoted') ? 'remove' : 'add' 295 if (upvoteBtnEl.hasClass('upvoted')) { 296 vote = 'up' 297 action = 'remove' 298 } 299 var voteCountBefore = Number(voteCountEl.text()) 300 301 if($(`.post-preview[data-id='${id}']`).length != 0) {downvotePostType(id, ".post-preview", action, vote, voteCountBefore)} 302 if($(`#post[data-id='${id}']`).length != 0) {downvotePostType(id, "#post", action, vote, voteCountBefore)} 303 if($(`.profile-preview[data-id='${id}']`).length != 0) {downvotePostType(id, ".profile-preview", action, vote, voteCountBefore)} 304 if($(`.search-preview[data-id='${id}']`).length != 0) {downvotePostType(id, ".search-preview", action, vote, voteCountBefore)} 305 306 // what type of post is it? 307 if(votedPost.hasClass("post")){ 308 voteTarget = "post"; 309 }else if(votedPost.hasClass("professorPost")){ 310 voteTarget = "professor"; 311 id = id.split("_")[1]; 312 } 313 314 $.ajax({ 315 url: '/shared/setVote.php', 316 type: 'POST', 317 data: { 318 uuid: store.get('uuid'), 319 school: retrieveSchoolParameter(), 320 voteTarget: voteTarget, 321 identifier: id, 322 action, 323 vote, 324 }, 325 }) 326} 327 328function upvotePostType (id, type, action, vote, voteCountBefore) { 329 const post = $(type+`[data-id='${id}']`) 330 331 if(type==".post-preview") {type=".pp"} 332 else if (type==".search-preview" || type==".profile-preview") {type = type.split("-")[0]} 333 334 const voteCountEl = post.find(type+'-vote-count').first() 335 const upvoteButton = post.find(type+'-upvote-btn').first() 336 const downvoteButton = post.find(type+'-downvote-btn').first() 337 338 if (action === 'add' && vote === 'up') { 339 voteCountEl.text(voteCountBefore + 1) 340 voteCountEl.addClass('count-upvote') 341 upvoteButton.addClass('upvoted') 342 } else if (action === 'remove' && vote === 'up') { 343 voteCountEl.text(voteCountBefore - 1) 344 voteCountEl.removeClass('count-upvote') 345 upvoteButton.removeClass('upvoted') 346 } else if (action === 'remove' && vote === 'down') { 347 voteCountEl.text(voteCountBefore + 1) 348 downvoteButton.removeClass('downvoted') 349 voteCountEl.removeClass('count-downvote') 350 } 351 352 if (Number(voteCountEl.text()) < 0) { 353 if (type != "#post" && type != '.comment') voteCountEl.addClass('negative-vote-count') 354 } else { 355 voteCountEl.removeClass('negative-vote-count') 356 } 357} 358 359function downvotePostType (id, type, action, vote, voteCountBefore) { 360 const post = $(type+`[data-id='${id}']`) 361 362 if(type==".post-preview") {type=".pp"} 363 else if (type==".search-preview" || type==".profile-preview") {type = type.split("-")[0]} 364 365 const voteCountEl = post.find(type+'-vote-count').first() 366 const upvoteButton = post.find(type+'-upvote-btn').first() 367 const downvoteButton = post.find(type+'-downvote-btn').first() 368 369 if (action === 'add' && vote === 'down') { 370 voteCountEl.text(voteCountBefore - 1) 371 voteCountEl.addClass('count-downvote') 372 downvoteButton.addClass('downvoted') 373 } else if (action === 'remove' && vote === 'down') { 374 voteCountEl.text(voteCountBefore + 1) 375 voteCountEl.removeClass('count-downvote') 376 downvoteButton.removeClass('downvoted') 377 } else if (action === 'remove' && vote === 'up') { 378 voteCountEl.text(voteCountBefore - 1) 379 voteCountEl.removeClass('count-upvote') 380 upvoteButton.removeClass('upvoted') 381 } 382 383 if (Number(voteCountEl.text()) < 0) { 384 if (type != "#post" && type != '.comment') voteCountEl.addClass('negative-vote-count') 385 } else { 386 voteCountEl.removeClass('negative-vote-count') 387 } 388} 389 390$(document).on("click", "#post-share", function () { 391 var parent = this.parentNode.parentNode.dataset["id"] 392 sharePost(parent) 393}); 394 395function sharePost(parentNode) { 396 // var chatName = $('.channelCard[data-active=true]')[0].textContent 397 // ^ do this if we eventually share posts in chats 398 // channelName = channelName.substring(0, channelName.length - 1) 399 var postName = $(`#post[data-id='${parentNode}']`).find("#post-title")[0].textContent 400 var url = window.location.href.split("#")[0] 401 402 var yourMessage = "Read reviews of " + postName + " on Coursicle. " + url + "?type=reviews&professor=" + postName.replace(" ", "+"); 403 var subject = "Check out reviews of " + postName + " on Coursicle" 404 405 if (navigator.userAgent.indexOf("Mac") != -1) { 406 window.open("sms:?&body=" + encodeURIComponent(yourMessage)) 407 }else { 408 //Linux gets put in here for now since mailto seems to have wider support 409 document.location.href = "mailto: ?subject=" 410 + encodeURIComponent(subject) 411 + "&body=" + encodeURIComponent(yourMessage) 412 } 413} 414 415 416const delta = 6; 417 418$(document).on('mousedown', '.comment', function (e) { 419 e.stopPropagation() 420 window.collapseClick = true 421 if( 422 $(e.target).is('.comment-dont-collapse, .comment-profile-opener, .flag-modal-opener, .comment-option-delete') 423 ) { 424 collapseClick = false 425 } 426 window.startX = e.pageX; 427 window.startY = e.pageY; 428}) 429 430$(document).on('mouseup', '.comment', function (e) { 431 e.stopPropagation() 432 const diffX = Math.abs(e.pageX - startX); 433 const diffY = Math.abs(e.pageY - startY); 434 if (diffX < delta && diffY < delta) { 435 if (!$(this).hasClass('collapsed') && collapseClick) { 436 $(this).addClass('collapsed') 437 } 438 } 439}) 440 441$(document).on('mouseup', '.collapsed.comment', function () { 442 $(this).removeClass('collapsed') 443}) 444 445$(document).on('click', '.comment-reply-btn', function (e) { 446 e.stopPropagation() 447 448 //even if they're not verified we still want to get any id they might be replying to so we can display the correct placeholder text when they're done verifying 449 const commentReplyBtnEl = $(this) 450 window.commentReplyingTo = commentReplyBtnEl.closest('.comment') 451 window.idReplyingTo = commentReplyingTo[0].dataset.id 452 453 if(!chatActionMasterCheck("comment")) {
454 requestUserInformation("comment") 455 return 456 } 457 $('#post-comments-input').attr('placeholder', `Replying to "${generateReplyPlaceholder()}"`) // give a little bit of the comment they're replying to as a placeholder. 458 $('#post-comments-input').focus() 459}) 460 461$(document).on('click', '.reviewVerificationRequired, .comment-reply-button', function(){ 462 if(!chatActionMasterCheck("comment")) { 463 requestUserInformation("comment") 464 return 465 } 466}) 467 468 469$(document).on('click', '.comment-upvote-btn', function (e) { 470 e.stopPropagation() 471 if (!chatActionMasterCheck("upvote")) { 472 requestUserInformation("upvote") 473 return 474 } 475 const commentEl = $(this).closest('.comment') 476 const downvoteBtnEl = commentEl.find('.comment-downvote-btn') 477 const id = commentEl[0].dataset.id 478 let action = $(this).hasClass('upvoted') ? 'remove' : 'add' 479 let vote = 'up' 480 if (downvoteBtnEl.hasClass('downvoted')) { 481 vote = 'down' 482 action = 'remove' 483 } 484 const voteCountEl = commentEl.find('.comment-vote-count').first() 485 const voteCountBefore = Number(voteCountEl.text()) 486 upvotePostType(id, ".comment", action, vote, voteCountBefore) 487 488 // what type of comment is it? 489 var voteTarget = "comment"; 490 if(commentEl.hasClass("review")){ 491 voteTarget = "review"; 492 } 493 494 $.ajax({ 495 url: '/shared/setVote.php', 496 type: 'POST', 497 data: { 498 uuid: store.get('uuid'), 499 school: retrieveSchoolParameter(), 500 voteTarget: voteTarget, 501 identifier: id, 502 action, 503 vote, 504 }, 505 }) 506}) 507 508$(document).on('click', '.comment-downvote-btn', function (e) { 509 e.stopPropagation() 510 if (!chatActionMasterCheck("downvote")) { 511 requestUserInformation("downvote") 512 return 513 } 514 const commentEl = $(this).closest('.comment') 515 const upvoteBtnEl = commentEl.find('.comment-upvote-btn') 516 const id = commentEl[0].dataset.id 517 let action = $(this).hasClass('downvoted') ? 'remove' : 'add' 518 let vote = 'down' 519 if (upvoteBtnEl.hasClass('upvoted')) { 520 vote = 'up' 521 action = 'remove' 522 } 523 const voteCountEl = commentEl.find('.comment-vote-count').first() 524 const voteCountBefore = Number(voteCountEl.text()) 525 downvotePostType(id, ".comment", action, vote, voteCountBefore) 526 527 // what type of comment is it? 528 var voteTarget = "comment"; 529 if(commentEl.hasClass("review")){ 530 voteTarget = "review"; 531 } 532 533 $.ajax({ 534 url: '/shared/setVote.php', 535 type: 'POST', 536 data: { 537 uuid: store.get('uuid'), 538 school: retrieveSchoolParameter(), 539 voteTarget: voteTarget, 540 identifier: id, 541 action, 542 vote, 543 }, 544 }) 545}) 546 547$(document).on('click', '.flag-remove-ellipsis ', function (e) { 548 e.stopPropagation() 549 target = $(e.target) 550 let ellipsis = $(this) 551 if (target.hasClass("flag-remove-ellipsis")) { 552 if (!target[0]._tippy) { 553 window.flagTippy = tippy(this, { 554 content() { 555 const commentCoursicleID = ellipsis.closest('.comment')[0].dataset.coursicleid 556 const coursicleID = store.get('userData').coursicleID 557 return ` 558 <div class='comment-options-container'> 559 ${commentCoursicleID != coursicleID ? 560 `<div class='comment-option comment-option-flag flag-modal-opener flag-comment tippyOption'>Flag</div> 561 ` : ''} 562 ${commentCoursicleID == coursicleID ? ` 563 <div class='comment-option comment-option-delete tippyOption'>Delete</div> 564 ` : ''} 565 </div> 566 ` 567 }, 568 allowHTML: true, 569 theme: 'light-border', 570 trigger: 'click', 571 arrow: false, 572 placement: 'right', 573 interactive: true, 574 hideOnClick: true, 575 }) 576 this._tippy.show() 577 } 578 } 579}) 580 581$(document).on("click", ".flag-modal-opener", function () { 582 583 window.flaggedPostID = null 584 window.flaggedTarget = null 585 window.flagSelectedOption = null 586 587 if (!chatActionMasterCheck("flag")) { 588 requestUserInformation("flag") 589 return 590 } 591 var reason = null 592 if ($(this).hasClass("flag-post")) { 593 reason = "Flag Post" 594 flaggedTarget = "post" 595 idToBeFlagged = $(this).parents().eq(1).data("id") 596 } else { 597 objectToBeFlagged = $(this).parents().eq(6) 598 idToBeFlagged = objectToBeFlagged.data("id") 599 if(objectToBeFlagged.hasClass("review")){ 600 reason = "Flag Review" 601 flaggedTarget = "review" 602 }else{ 603 reason = "Flag Comment" 604 flaggedTarget = "comment" 605 } 606 } 607 608 flagCommentBehavior($(this), flaggedTarget); 609 610 if(!window.isPWA){ 611 $("#flagModal").modal('show'); 612 }else{ 613 renderSlidableModal('#flagModal', true) 614 } 615}); 616 617 618$(document).on('click', '.comment-option-delete', function(){ 619 620 if(!chatActionMasterCheck("deleteComment")){ 621 requestUserInformation("deleteComment") 622 return 623 } 624 625 let commentIDToDelete = $(this).closest('.comment').attr('data-id') 626
627 //for deletion, we want to scroll to the closest parent; if it's a reply, works as expected. If it's already a top level review, this should return $(this). 628 let closestParentID= $(this).closest('.comment.topLevel').attr('data-id') 629 630 let professor = window.isPWA ? $('#postViewCommentsAndInputContainer').attr('data-professor') : $('#postViewWithCommentsModal').attr('data-professor') 631 let school = window.isPWA ? $('#postViewCommentsAndInputContainer').attr('data-school') : $('#postViewWithCommentsModal').attr('data-school') 632 let uuid = store.get('uuid') 633 634 let data = { 635 school: school, 636 uuid: uuid, 637 reviewID: commentIDToDelete, 638 } 639 //delete the review and refresh the view. 640 //if in the future we want to use this as a general comments deletion, we'll need to check for the 'review' class. 641 $.ajax({ 642 url: '/shared/reviews/deleteReview.php', 643 type: 'POST', 644 dataType: 'json', 645 data: data, 646 success: function (response) { 647 window.loadProfessorsReviews({school: school, professor: professor}, closestParentID) 648 } 649 }) 650 651}) 652 653$(document).on('click', '.comment-option', function (e) { 654 e.stopPropagation() 655 window.flagTippy.hide() 656})
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.