PageSourceSearch

https://www.coursicle.com/all/js/chat/postView.js?new5

js coursicle.com collected 2026-09-24 08:48:42 UTC 21,778 bytes, 656 lines download raw bytes

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.