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.