PageSourceSearch

https://www.coursicle.com/all/js/chat/chatDetailView.js?new10

js coursicle.com collected 2026-09-24 08:48:44 UTC 13,078 bytes, 387 lines download raw bytes

1/******************************************************************************/
2/******************************************************************************/
3/*********************************** RENDER ***********************************/
4/******************************************************************************/
5/******************************************************************************/
6
7function renderUserListItem({ username, year, major, profilePictureUrl, sharedClassesCount, coursicleId }) {
8	if (!year) year = 'First-year'
9	return /*html*/`
10		<li class='userListItem' data-coursicleid=${coursicleId}>
11			<img class='profilePicture userListProfilePicture' src='/shared/getImage.php?type=userPhoto&file=${profilePictureUrl}&size=small' />
12			<div class='usernameYearAndMajorContainer'>
13				<div class='username'>${username == "Me" ? "Coursicle User" : escapeHTML(username)}</div>
14				<div class='yearAndMajorContainer'>
15					<div class='userBadge year ${lowercaseFirstLetter(year)}'>${capitalizeFirstLetter(year)}</div>
16					<div class='userBadge major'>${major || 'Undecided'}</div>
17				</div>
18			</div>
19			${sharedClassesCount ? `<div class='sharedClasses'>${sharedClassesCount} shared ${sharedClassesCount == 1 ? 'class' : 'classes'}</div>` : ''}
20		</li>
21	`
22}
23
24function renderChatDetails(chatID) {
25	let chatData
26	usersChats().forEach(chat => {
27		if (chat.id == chatID) {
28			chatData = chat
29		}
30	})
31
32	// to prevent an error, but this generally shouldn't happen except in cases
33	// of sample active chat
34	if(chatData == null){
35		console.warn("renderChatDetails called with a chat that doesn't exist.")
36		return;
37	}
38
39	$('#profilePictureGroupRight').empty()
40	let size = !isSingleView() ? "small" : "medium"
41	// anonymous chat is diferent
42	if(chatData.userPhotos.includes("anonymous")){
43		if(!["direct", "group"].includes(chatData.type)){
44			$('#profilePictureGroupRight').html(generateAnonymousPhotoForChat(chatData));
45		}else{
46			$('#profilePictureGroupRight').html(`
47				<img 
48					class='profilePicture anonymousDefaultProfile'
49					src='/shared/getImage.php?type=userPhoto&file=ANONYMOUS.jpg&size=${size}'
50				>
51			`);
52		}
53	}else{
54		chatData.userPhotos.forEach((photo, i) => {
55			$('#profilePictureGroupRight').append(`
56				<img 
57					class='profilePicture rightTriple rightTriple${i + 1}'
58					src='/shared/getImage.php?type=userPhoto&file=${photo}&size=${size}'
59				>
60			`)
61		})
62	}
63	$('#activeChatLongTitle').text(chatData.name)
64	var descrip = getPlacementHolderTextForChat(chatData, "long")
65	$('#sectionsNotice').text(descrip)
66	// add data to button
67	$('#chatNotificationsBtn').data("chatName", chatData.name)
68	
69	if(isChatMuted(chatData)){
70		$('#chatNotificationsBtn').empty()
71		$('#chatNotificationsBtn').append(`<i class='fa fa-bell-slash'></i>`)
72	}else{
73		$('#chatNotificationsBtn').empty()
74		$('#chatNotificationsBtn').append(`<i class='fa-solid fa-bell'></i>`)
75	}
76	if(isSingleView() == true){
77		$('#rightColumn').append(/*html*/`
78			<div id='scrollToCenterColumnBtn'>
79				<i class="fa fa-angle-left"></i>
80			</div>
81		`)
82	} else {
83		$('#scrollToCenterColumnBtn').hide()
84	}
85}
86
87function renderUserList(users) {
88	//if user is current user
89	//append at max offset OR append to end and hide and add b4 it
90	const userListFragment = $(document.createDocumentFragment())
91	users.forEach(user => {
92		const userData = JSON.parse(user.userData)
93		if(user.coursicleID == getCoursicleID()){
94			// console.log(userData.username)
95		}else{
96		$(userListFragment).append(renderUserListItem({
97			username: userData.username,
98			year: userData.year,
99			major: userData.major,
100			profilePictureUrl: userData.userPhotoUrl,
101			sharedClassesCount: user.sharedClassesCount,
102			coursicleId: user.coursicleID
103		}))}
104	})
105	if(window.userListLoadingState.allLoaded || users.length < 12){
106		const userData = store.get("userData")
107		// only do this if it's not anonymous
108		if(!window.chatsAreAnonymous){
109			$(userListFragment).append(renderUserListItem({
110				username: userData.username,
111				year: userData.year,
112				major: userData.major,
113				profilePictureUrl: userData.userPhotoUrl,
114				sharedClassesCount: null,
115				coursicleId: userData.coursicleID
116			}))
117		}
118	}else{
119		// console.log("upping offset")
120		// window.userListOffset += 12
121	}
122	$('#activeChatUserList').append(userListFragment)
123}
124
125/******************************************************************************/
126/******************************************************************************/
127/*********************************** EVENTS ***********************************/
128/******************************************************************************/
129/******************************************************************************/
130
131function openChatAtTopOfList(){
132	selectChatAtTopOfList()
133	renderChat(activeChatID())
134	renderChatDetails(activeChatID())
135	getChatsUsers(loadingMore=false, clearExisting=true)
136	emitStartedViewing(activeChatID())
137}
138
139
140$('#leaveChatBtn').click(function () {
141	
142
143	let currentChat = usersChats().filter(chat=> Number(chat.id) == activeChatID())[0]
144	let isOnlyChat = (usersChats().length == 1);
145	let isSampleActiveChat = (currentChat.sampleActiveChat);
146
147	if(isOnlyChat || isSampleActiveChat){
148
149		if(isSampleActiveChat){
150			var text = "This is an example of an active chat at your school. Adding another chat will replace this one. You won't receive notifications for this chat.";
151		}else if(isOnlyChat){
152			var text = `You must be in at least one chat. Add another chat before removing this one. ${window.isPWA ? "Tap" : "Click"} "Notifications" to mute this chat.`
153		}
154
155		//initializing the leave chat error popover
156		$('#leaveChatBtn').popover({content:`<span id="leaveChatError" style='font-size:13px'>${text}</span>`, placement:"left", html:true});
157		$('#leaveChatBtn').popover("show")
158		
159		let leaveChatBtnX = $('#leaveChatBtn')[0].offsetLeft
160		let leaveSymbol = $('#leaveChatBtn').children('.fa-solid')
161
162		// no idea why Joe put this here, it just messed things up
163		/*if (isSingleView()) //popover message left setting doesn't work as expected; position absolute now.
164		{
165			$('#leaveChatError').parent().parent().css({"position": "absolute", "left"
165:  leaveChatBtnX - (leaveSymbol[0].offsetLeft - leaveSymbol.parent()[0].offsetLeft)+ "px"})
166		}*/
167
168		setTimeout(function() {$("#leaveChatBtn").popover("destroy")},4000);
169
170		// prevent leaving
171		return
172	}
173
174
175	// hide the element immediately in the html
176	$(".chatListItem").each(function() {
177		if($(this).data("id") == activeChatID()){
178			$(this).slideUp(150);
179		}
180	})
181	// remove the chat from their local set of chats to 
182	// make sure on page reload we don't have it stored locally
183	usersChatsReduced = usersChats().filter(function(chat) {
184		return chat.id != activeChatID();
185	});
186	store.set("usersChats",usersChatsReduced)
187
188	// if it was a local chat only (new message chat), no need to tell the server about it
189	//additional note by Nathan: openChatAtTopOfList emits started viewing without a counterpart. This could cause confusion later on.
190	if(activeChatID() == 0){
191		(isSingleView() == true) ? scrolltoLeftColumn() : openChatAtTopOfList()
192		return;
193	}
194	
195	$.ajax({
196		url: '/shared/chat/setUsersChats.php',
197		type: 'POST',
198		dataType: 'json',
199		data: {
200			uuid: store.get('uuid'),
201			action: 'leave',
202			chatID: activeChatID(),
203		},
204		success: function (response) {
205			
206			//if pwa or narrow view, we don't want to see the chat detail view.
207			if (isSingleView() == true)  {scrolltoLeftColumn()}
208			else {emitStoppedViewing(activeChatID()); openChatAtTopOfList()} 
209		}
210	})
211})
212
213$('#activeChatUserListContainer').scroll(async function () {
214
215	// height of two list items (make -3 for 3, etc.)
216	let buffer = 0
217	$('.userListItem').slice(-2).each(function () {
218		buffer += $(this).outerHeight()
219	})
220
221	const heightOfVisibleUserListItems = $('#activeChatUserListContainer').scrollTop() + $('#activeChatUserListContainer').height()
222	const heightOfLoadedUserListItems = $('#activeChatUserListContainer').prop('scrollHeight')
223	// console.log(heightOfLoadedUserListItems)
224	// console.log(heightOfVisibleUserListItems)
225	// console.log(buffer)
226	if (
227		heightOfVisibleUserListItems < heightOfLoadedUserListItems - buffer
228		|| window.userListLoadingState.isLoading
229		|| window.userListLoadingState.allLoaded
230	) {
231		return
232	}
233	window.userListLoadingState.isLoading = true
234	$('#activeChatUserList').append(`
235		<li id='userLoading'>
236			<div class="ispinner ispinner-small" >
237				<div class="ispinner-blade"></div>
238				<div class="ispinner-blade"></div>
239				<div class="ispinner-blade"></div>
240				<div class="ispinner-blade"></div>
241				<div class="ispinner-blade"></div>
242				<div class="ispinner-blade"></div>
243				<div class="ispinner-blade"></div>
244				<div class="ispinner-blade"></div>
245			</div>
246		</li>
247	`)
248	$('#userLoading').show()
249
250	await setTimeout(function () {
251		getChatsUsers(loadingMore=true, clearExisting=false);
252	}, 300)
253
254})
255
256$('#chatNotificationsBtn').click(function(){
257	if(["unset", "disabled"].includes(getSystemLevelNotificationSetting())){
258        requestNotificationPermissions("tappedNotificationSetting");
259        return;
260    }
261    $('#notificationsModal').addClass("chatSpecific").removeClass("global");
262
263    let activeChat = usersChats().filter(chat=> chat.id == activeChatID())[0]
264    if(activeChat.type == "direct"){
265    	openMuteModal();
266    	return;
267    }
268
269	paintChatSpecificNotificationSettings();
270	$('#notificationsModal').modal('show');
271})
272
273function paintChatSpecificNotificationSettings () {
274	$('#classStatusNotifToggle').attr('checked', getNotificationPreferences().statusChanges)
275	$('#classUpdatesNotifToggle').attr('checked', getNotificationPreferences().classUpdates)
276	$('.notifModSection, #chatNotificationOptions').show()
277	$('.notifModSection.withSlider, .notifModSectionTitle').hide()
278	$(`.chatNotificationOptionContainer`).removeClass('selected')
279	$('#muteModalTitle').text("Mute " + $('#chatNotificationsBtn').data("chatName") + " for")
280	$(`label[for=chatNotifOption${getChatNotificationPreference(activeChatID())}]`).addClass('selected')
281}
282
283$('#invitationBtn').click(function () {
284	let activeChat = usersChats().filter(chat=> chat.id == activeChatID())[0]
285	$('#reviewGroupBtn').text("Review")
286	$('#reviewGroupBtn').removeClass("addingToExistingGroup")
287
288	
289	window.newGroupChatList = []
290	window.chatSearchTitle = "Groups"
291	if (activeChat.type == "group")
292		{invitationBtnForGroupBehavior($(this), activeChat)}
293	else if (activeChat.type != "direct")
294		{
295			$('#invitationModal').modal('show')		
296		}
297	else 
298		{
299			window.userProfileViewContextName = activeChat.name
300			specificUserInvitationModalSetup()
301		}
302
303
304})
305
306$('#sendInviteLinkButton').click(function () { // hard coded until we do our own share link generation here
307
308	if (navigator.userAgent.indexOf("Mac") != -1) {
309		window.open("sms:?&body=Tap this link to join the " + $('#activeChatLongTitle').text() + " chat on Coursicle: \n" + "https://www.coursicle.com/app/?chat-invite")
310	}
311
312	//Linux gets put in here for now since mailto seems to have wider support
313
314	else {
315
316		//customize to the name of the relevant chat.
317		window.open("mailto:?subject=Invitation to join " +  $('#activeChatLongTitle').text()+" on Coursicle&body=Tap this link to join the " + $('#activeChatLongTitle').text() + " chat on Coursicle: \n" + "https://www.coursicle.com/app/?chat-invite")
318	}
319})
320
321function scrollToCenterColumn(){
322	$('#chatMainContent').animate({
323		scrollLeft: window.isPWA ? $(window).width() : $('#leftRail').outerWidth()
324	}, 250)
325}
326
327$(document).on('click', '#scrollToCenterColumnBtn', function(e) {
328	e.stopPropagation()
329	// $('#centerColumn').get(0).scrollIntoView({ behavior: 'smooth' })
330	scrollToCenterColumn()
331})
332
333$('#rightColumn').on('touchstart', handleTouchStart);        
334$('#rightColumn').on('touchmove', handleTouchMove);
335var xDown = null;                                                        
336var yDown = null;
337
338function getTouches(evt) {
339	return evt.touches ||             // browser API
340		   evt.originalEvent.touches; // jQuery
341}      
342
343function handleTouchStart(evt) {
344	const firstTouch = getTouches(evt)[0];                                      
345	xDown = firstTouch.clientX;                                      
346	yDown = firstTouch.clientY; 
347}
348
349function handleTouchMove(evt) {
350
351	if ( ! xDown || ! yDown ) {
352		return;
353	}
354	const touch = getTouches(evt)[0];                                      
355
356	var xUp = touch.clientX;                                    
357	var yUp = touch.clientY;
358
359	var xDiff = xDown - xUp;
360	var yDiff = yDown - yUp;
361																		 
362	if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {/*most significant*/
363		// if it's horizontal
364		if(xDiff < 0){
365			scrollToCenterColumn()
366		}
367	} 
368	
369	/* reset values */
370	xDown = null;
371	yDown = null;               
372
373}			
374
375
376//these three listeners are all retrieved and re-added from an earlier commit. At some point they were removed entirely.
377$('#rightColumnCloseBtn').click(function () {
378	$('#rightColumn').removeClass('open')
379})
380
381$('#leftColumn').click(function () {
382	$('#rightColumn').removeClass('open')
383})
384
385$('#centerColumn').click(function () {
386	$('#rightColumn').removeClass('open')
387})

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.