diff --git a/message/amd/build/message_drawer_view_conversation_patcher.min.js b/message/amd/build/message_drawer_view_conversation_patcher.min.js index bf663cc9d44..c232d6ad5a0 100644 --- a/message/amd/build/message_drawer_view_conversation_patcher.min.js +++ b/message/amd/build/message_drawer_view_conversation_patcher.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_message/message_drawer_view_conversation_patcher",["jquery","core/user_date","core_message/message_drawer_view_conversation_constants"],function(a,b,c){var d=function(a,c){var d=a.reduce(function(a,d){var e=d.timeCreated?d.timeCreated:c,f=b.getUserMidnightForTimestamp(e,c);if(a.hasOwnProperty(f)){a[f].push(d)}else{a[f]=[d]}return a},{});return Object.keys(d).map(function(a){return{timestamp:a,messages:d[a]}})},e=function(c,a,d){a=a.slice();var e=[],f=[],g=[];c.forEach(function(b){var c=!1,e=0;for(;ee&&1>f){return!0}if(e!=f){return!1}return c.every(function(a,b){return a==d[b]})},h=function(c,a){var b=Object.keys(c),d=Object.keys(a);if(b.length!=d.length){return!1}return b.every(function(b){var d=c[b],e=a[b],f=_typeof(d),i=_typeof(e);f=null===d?"null":f;i=null===d?"null":i;f="object"===f&&Array.isArray(f)?"array":f;i="object"===i&&Array.isArray(i)?"array":i;if(f!==i){return!1}switch(f){case"object":return h(d,e);case"array":return g(d,e);default:return c[b]==a[b];}})},i=function(c,a){return h({id:c.id,state:c.sendState,text:c.text,timeCreated:c.timeCreated},{id:a.id,state:a.sendState,text:a.text,timeCreated:a.timeCreated})},j=function(a,b,c){return{remove:b,add:c.map(function(b){var c=f(a,function(a){return b.timestampd.length){return null}if(1>c.length){return d[d.length-1].id}var e=c[a.messages.length-1],f=d[d.length-1],g=c[0],h=d[0];if(e.id!=f.id){return f.id}else if(g.id!=h.id){return g.id}return null},q=function(a,b){if(!a.loadingMembers&&b.loadingMembers){return!0}else if(a.loadingMembers&&!b.loadingMembers){return!1}else{return null}},r=function(a,b){if(a.hasTriedToLoadMessages===b.hasTriedToLoadMessages){return null}else if(!b.hasTriedToLoadMessages&&b.loadingMessages){return!0}else if(b.hasTriedToLoadMessages&&!b.loadingMessages){return!1}else{return null}},s=function(a,b){if(!a.loadingMessages&&b.loadingMessages){return!0}else if(a.loadingMessages&&!b.loadingMessages){return!1}else{return null}},t=function(a,b){if(!a.showEmojiPicker&&b.showEmojiPicker){return!0}else if(a.showEmojiPicker&&!b.showEmojiPicker){return!1}else{return null}},u=function(a,b){if(!a.showEmojiAutoComplete&&b.showEmojiAutoComplete){return!0}else if(a.showEmojiAutoComplete&&!b.showEmojiAutoComplete){return!1}else{return null}},v=function(a,b){if(b.pendingBlockUserIds.length){var c=b.pendingBlockUserIds[0];return b.members[c]}else if(a.pendingBlockUserIds.length){return!1}return null},w=function(a,b){if(b.pendingUnblockUserIds.length){var c=b.pendingUnblockUserIds[0];return b.members[c]}else if(a.pendingUnblockUserIds.length){return!1}return null},x=function(a,b){if(b.pendingAddContactIds.length){var c=b.pendingAddContactIds[0];return b.members[c]}else if(a.pendingAddContactIds.length){return!1}return null},y=function(a,b){if(b.pendingRemoveContactIds.length){var c=b.pendingRemoveContactIds[0];return b.members[c]}else if(a.pendingRemoveContactIds.length){return!1}return null},z=function(a,b){var c=a.pendingDeleteMessageIds.length,d=b.pendingDeleteMessageIds.length;if(d&&!c){return{show:!0,type:b.type,canDeleteMessagesForAllUsers:b.canDeleteMessagesForAllUsers}}else if(c&&!d){return{show:!1}}return null},A=function(a,b){if(!a.pendingDeleteConversation&&b.pendingDeleteConversation){return b.type}else if(a.pendingDeleteConversation&&!b.pendingDeleteConversation){return!1}return null},B=function(a,b){var c=a.loggedInUserId,d=J(a),e=J(b),f=!d?[]:d.contactrequests.filter(function(a){return a.requesteduserid==c&&a.userid==d.id}),g=!e?[]:e.contactrequests.filter(function(a){return a.requesteduserid==c&&a.userid==e.id}),h=f.length?f[0]:null,i=g.length?g[0]:null;if(!h&&i){return e}else if(h&&!i){return!1}else{return null}},C=function(a,b){var c=J(a),d=J(b);if(!c&&!d){return null}else if(!c&&d){return d.isblocked?!0:null}else if(!d&&c){return c.isblocked?!1:null}else if(c.isblocked&&!d.isblocked){return!1}else if(!c.isblocked&&d.isblocked){return!0}else{return null}},D=function(a,b){var c=a.isFavourite,d=b.isFavourite;if(null===a.id&&null===b.id){return null}else if(null===a.id&&null!==b.id){return"show-add"}else if(null!==a.id&&null===b.id){return"hide"}else if(c==d){return null}else if(!c&&d){return"show-remove"}else if(c&&!d){return"show-add"}else{return null}},E=function(a,b){var c=a.isMuted,d=b.isMuted;if(null===a.id&&null===b.id){return null}else if(null===a.id&&null!==b.id){return"show-mute"}else if(null!==a.id&&null===b.id){return"hide"}else if(c==d){return null}else if(!c&&d){return"show-unmute"}else if(c&&!d){return"show-mute"}else{return null}},F=function(a,b){var c=a.loggedInUserId,d=J(a),e=J(b),f=!d?[]:d.contactrequests.filter(function(a){return a.userid==c&&a.requesteduserid==d.id||a.userid==d.id&&a.requesteduserid==c}),g=!e?[]:e.contactrequests.filter(function(a){return a.userid==c&&a.requesteduserid==e.id||a.userid==e.id&&a.requesteduserid==c}),h=0e&&1>f){return!0}if(e!=f){return!1}return c.every(function(a,b){return a==d[b]})},h=function(c,a){var b=Object.keys(c),d=Object.keys(a);if(b.length!=d.length){return!1}return b.every(function(b){var d=c[b],e=a[b],f=_typeof(d),i=_typeof(e);f=null===d?"null":f;i=null===d?"null":i;f="object"===f&&Array.isArray(f)?"array":f;i="object"===i&&Array.isArray(i)?"array":i;if(f!==i){return!1}switch(f){case"object":return h(d,e);case"array":return g(d,e);default:return c[b]==a[b];}})},i=function(c,a){return h({id:c.id,state:c.sendState,text:c.text,timeCreated:c.timeCreated},{id:a.id,state:a.sendState,text:a.text,timeCreated:a.timeCreated})},j=function(a,b,c){return{remove:b,add:c.map(function(b){var c=f(a,function(a){return b.timestampd.length){return null}if(1>c.length){return d[d.length-1].id}var e=c[a.messages.length-1],f=d[d.length-1],g=c[0],h=d[0];if(e.id!=f.id){return f.id}else if(g.id!=h.id){return g.id}return null},q=function(a,b){if(!a.loadingMembers&&b.loadingMembers){return!0}else if(a.loadingMembers&&!b.loadingMembers){return!1}else{return null}},r=function(a,b){if(a.hasTriedToLoadMessages===b.hasTriedToLoadMessages){return null}else if(!b.hasTriedToLoadMessages&&b.loadingMessages){return!0}else if(b.hasTriedToLoadMessages&&!b.loadingMessages){return!1}else{return null}},s=function(a,b){if(!a.loadingMessages&&b.loadingMessages){return!0}else if(a.loadingMessages&&!b.loadingMessages){return!1}else{return null}},t=function(a,b){if(!a.showEmojiPicker&&b.showEmojiPicker){return!0}else if(a.showEmojiPicker&&!b.showEmojiPicker){return!1}else{return null}},u=function(a,b){if(!a.showEmojiAutoComplete&&b.showEmojiAutoComplete){return!0}else if(a.showEmojiAutoComplete&&!b.showEmojiAutoComplete){return!1}else{return null}},v=function(a,b){if(b.pendingBlockUserIds.length){var c=b.pendingBlockUserIds[0];return b.members[c]}else if(a.pendingBlockUserIds.length){return!1}return null},w=function(a,b){if(b.pendingUnblockUserIds.length){var c=b.pendingUnblockUserIds[0];return b.members[c]}else if(a.pendingUnblockUserIds.length){return!1}return null},x=function(a,b){if(b.pendingAddContactIds.length){var c=b.pendingAddContactIds[0];return b.members[c]}else if(a.pendingAddContactIds.length){return!1}return null},y=function(a,b){if(b.pendingRemoveContactIds.length){var c=b.pendingRemoveContactIds[0];return b.members[c]}else if(a.pendingRemoveContactIds.length){return!1}return null},z=function(a,b){var c=a.pendingDeleteMessageIds.length,d=b.pendingDeleteMessageIds.length;if(d&&!c){return{show:!0,type:b.type,canDeleteMessagesForAllUsers:b.canDeleteMessagesForAllUsers}}else if(c&&!d){return{show:!1}}return null},A=function(a,b){if(!a.pendingDeleteConversation&&b.pendingDeleteConversation){return b.type}else if(a.pendingDeleteConversation&&!b.pendingDeleteConversation){return!1}return null},B=function(a,b){var c=a.loggedInUserId,d=J(a),e=J(b),f=!d?[]:d.contactrequests.filter(function(a){return a.requesteduserid==c&&a.userid==d.id}),g=!e?[]:e.contactrequests.filter(function(a){return a.requesteduserid==c&&a.userid==e.id}),h=f.length?f[0]:null,i=g.length?g[0]:null;if(!h&&i){return e}else if(h&&!i){return!1}else{return null}},C=function(a,b){var c=J(a),d=J(b);if(!c&&!d){return null}else if(!c&&d){return d.isblocked?!0:null}else if(!d&&c){return c.isblocked?!1:null}else if(c.isblocked&&!d.isblocked){return!1}else if(!c.isblocked&&d.isblocked){return!0}else{return null}},D=function(a,b){var c=a.isFavourite,d=b.isFavourite;if(null===a.id&&null===b.id){return null}else if(null===a.id&&null!==b.id){return"show-add"}else if(null!==a.id&&null===b.id){return"hide"}else if(c==d){return null}else if(!c&&d){return"show-remove"}else if(c&&!d){return"show-add"}else{return null}},E=function(a,b){var c=a.isMuted,d=b.isMuted;if(null===a.id&&null===b.id){return null}else if(null===a.id&&null!==b.id){return"show-mute"}else if(null!==a.id&&null===b.id){return"hide"}else if(c==d){return null}else if(!c&&d){return"show-unmute"}else if(c&&!d){return"show-mute"}else{return null}},F=function(a,b){var c=a.loggedInUserId,d=J(a),e=J(b),f=!d?[]:d.contactrequests.filter(function(a){return a.userid==c&&a.requesteduserid==d.id||a.userid==d.id&&a.requesteduserid==c}),g=!e?[]:e.contactrequests.filter(function(a){return a.userid==c&&a.requesteduserid==e.id||a.userid==e.id&&a.requesteduserid==c}),h=0.\n\n/**\n * This module will take 2 view states from the message_drawer_view_conversation\n * module and generate a patch that can be given to the\n * message_drawer_view_conversation_renderer module to update the UI.\n *\n * This module should never modify either state. It's purely a read only\n * module.\n *\n * @module core_message/message_drawer_view_conversation_patcher\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/user_date',\n 'core_message/message_drawer_view_conversation_constants'\n],\nfunction(\n $,\n UserDate,\n Constants\n) {\n /**\n * Sort messages by day.\n *\n * @param {Array} messages The list of messages to sort.\n * @param {Number} midnight User's midnight timestamp.\n * @return {Array} messages sorted by day.\n */\n var sortMessagesByDay = function(messages, midnight) {\n var messagesByDay = messages.reduce(function(carry, message) {\n var timeCreated = message.timeCreated ? message.timeCreated : midnight;\n var dayTimestamp = UserDate.getUserMidnightForTimestamp(timeCreated, midnight);\n\n if (carry.hasOwnProperty(dayTimestamp)) {\n carry[dayTimestamp].push(message);\n } else {\n carry[dayTimestamp] = [message];\n }\n\n return carry;\n }, {});\n\n return Object.keys(messagesByDay).map(function(dayTimestamp) {\n return {\n timestamp: dayTimestamp,\n messages: messagesByDay[dayTimestamp]\n };\n });\n };\n\n /**\n * Diff 2 arrays using a match function\n *\n * @param {Array} a The first array.\n * @param {Array} b The second array.\n * @param {Function} matchFunction Function used for matching array items.\n * @return {Object} Object containing array items missing from a, array items missing from b\n * and matches\n */\n var diffArrays = function(a, b, matchFunction) {\n // Make copy of it.\n b = b.slice();\n var missingFromA = [];\n var missingFromB = [];\n var matches = [];\n\n a.forEach(function(current) {\n var found = false;\n var index = 0;\n\n for (; index < b.length; index++) {\n var next = b[index];\n\n if (matchFunction(current, next)) {\n found = true;\n matches.push({\n a: current,\n b: next\n });\n break;\n }\n }\n\n if (found) {\n // This day has been processed so removed it from the list.\n b.splice(index, 1);\n } else {\n // If we couldn't find it in the next messages then it means\n // it needs to be added.\n missingFromB.push(current);\n }\n });\n\n missingFromA = b;\n\n return {\n missingFromA: missingFromA,\n missingFromB: missingFromB,\n matches: matches\n };\n };\n\n /**\n * Find an element in a array based on a matching function.\n *\n * @param {array} array Array to search.\n * @param {Function} breakFunction Function to run on array item.\n * @return {*} The array item.\n */\n var findPositionInArray = function(array, breakFunction) {\n var before = null;\n\n for (var i = 0; i < array.length; i++) {\n var candidate = array[i];\n\n if (breakFunction(candidate)) {\n return candidate;\n }\n }\n\n return before;\n };\n\n /**\n * Check if 2 arrays are equal.\n *\n * @param {Array} a The first array.\n * @param {Array} b The second array.\n * @return {Boolean} Are arrays equal.\n */\n var isArrayEqual = function(a, b) {\n // Make shallow copies so that we don't mess with the array sorting.\n a = a.slice();\n b = b.slice();\n a.sort();\n b.sort();\n var aLength = a.length;\n var bLength = b.length;\n\n if (aLength < 1 && bLength < 1) {\n return true;\n }\n\n if (aLength != bLength) {\n return false;\n }\n\n return a.every(function(item, index) {\n return item == b[index];\n });\n };\n\n /**\n * Do a shallow check to see if two objects appear to be equal. This should\n * only be used for pretty basic objects.\n *\n * @param {Object} a First object to compare.\n * @param {Object} b Second object to compare\n * @return {Bool}\n */\n var isObjectEqual = function(a, b) {\n var aKeys = Object.keys(a);\n var bKeys = Object.keys(b);\n\n if (aKeys.length != bKeys.length) {\n return false;\n }\n\n return aKeys.every(function(key) {\n var aVal = a[key];\n var bVal = b[key];\n var aType = typeof aVal;\n var bType = typeof bVal;\n aType = (aVal === null) ? 'null' : aType;\n bType = (aVal === null) ? 'null' : bType;\n aType = (aType === 'object' && Array.isArray(aType)) ? 'array' : aType;\n bType = (bType === 'object' && Array.isArray(bType)) ? 'array' : bType;\n\n if (aType !== bType) {\n return false;\n }\n\n switch (aType) {\n case 'object':\n return isObjectEqual(aVal, bVal);\n case 'array':\n return isArrayEqual(aVal, bVal);\n default:\n return a[key] == b[key];\n }\n });\n };\n\n /**\n * Compare two messages to check if they are equal. This function only checks a subset\n * of the message properties which we know will change rather than all properties.\n *\n * @param {Object} a The first message\n * @param {Object} b The second message\n * @return {Bool}\n */\n var isMessageEqual = function(a, b) {\n return isObjectEqual(\n {\n id: a.id,\n state: a.sendState,\n text: a.text,\n timeCreated: a.timeCreated\n },\n {\n id: b.id,\n state: b.sendState,\n text: b.text,\n timeCreated: b.timeCreated\n }\n );\n };\n\n /**\n * Build a patch based on days.\n *\n * @param {Object} current Current list current items.\n * @param {Array} remove List of days to remove.\n * @param {Array} add List of days to add.\n * @return {Object} Patch with elements to add and remove.\n */\n var buildDaysPatch = function(current, remove, add) {\n return {\n remove: remove,\n add: add.map(function(day) {\n // Any days left over in the \"next\" list weren't in the \"current\" list\n // so they will need to be added.\n var before = findPositionInArray(current, function(candidate) {\n return day.timestamp < candidate.timestamp;\n });\n\n return {\n before: before,\n value: day\n };\n })\n };\n };\n\n /**\n * Build the messages patch for each day.\n *\n * @param {Array} matchingDays Array of old and new messages sorted by day.\n * @return {Object} patch.\n */\n var buildMessagesPatch = function(matchingDays) {\n var remove = [];\n var add = [];\n var update = [];\n\n // Iterate over the list of days and determine which messages in those days\n // have been changed.\n matchingDays.forEach(function(days) {\n var dayCurrent = days.a;\n var dayNext = days.b;\n // Find out which messages have changed in this day. This will return a list of messages\n // from the current state that couldn't be found in the next state and a list of messages in\n // the next state which couldn't be count in the current state.\n var messagesDiff = diffArrays(dayCurrent.messages, dayNext.messages, isMessageEqual);\n // Take the two arrays (list of messages changed from dayNext and list of messages changed\n // from dayCurrent) any work out which messages have been added/removed from the list and\n // which messages were just updated.\n var patch = diffArrays(\n // The messages from dayCurrent.message that weren't in dayNext.messages.\n messagesDiff.missingFromB,\n // The messages from dayNext.message that weren't in dayCurrent.messages.\n messagesDiff.missingFromA,\n function(a, b) {\n // This function is going to determine if the messages were\n // added/removed from either list or if they were simply an updated.\n //\n // If the IDs match or it was a state change (i.e. message with a temp\n // ID goes from pending to sent and receives an actual id) then they are\n // the same message which should be an update not an add/remove.\n return a.id == b.id || (a.sendState != b.sendState && a.timeAdded == b.timeAdded);\n }\n );\n\n // Any messages from the current state for this day which aren't in the next state\n // for this day (i.e. the user deleted the message) means we need to remove them from\n // the UI.\n remove = remove.concat(patch.missingFromB);\n\n // Any messages not in the current state for this day which are in the next state\n // for this day (i.e. it's a new message) means we need to add it to the UI so work\n // out where in the list of messages it should appear (it could be a new message the\n // user has sent or older messages loaded as part of the conversation scroll back).\n patch.missingFromA.forEach(function(message) {\n // By default a null value for before will render the message at the bottom of\n // the message UI (i.e. it's the newest message).\n var before = null;\n\n if (message.timeCreated) {\n // If this message has a time created then find where it sits in the list of\n // message to insert it into the correct position.\n before = findPositionInArray(dayCurrent.messages, function(candidate) {\n if (message.timeCreated == candidate.timeCreated) {\n return message.id < candidate.id;\n } else {\n return message.timeCreated < candidate.timeCreated;\n }\n });\n }\n\n add.push({\n before: before,\n value: message,\n day: dayCurrent\n });\n });\n\n // Any message that appears in both the current state for this day and the next state\n // for this day means something in the message was updated.\n update = update.concat(patch.matches.map(function(message) {\n return {\n before: message.a,\n after: message.b\n };\n }));\n });\n\n return {\n add: add,\n remove: remove,\n update: update\n };\n };\n\n /**\n * Build a patch for this conversation.\n *\n * @param {Object} state, The current state of this conversation.\n * @param {Object} newState, The new state of this conversation.\n * @return {Object} Patch with days and messsages for each day.\n */\n var buildConversationPatch = function(state, newState) {\n var diff = diffArrays(state.messages, newState.messages, isMessageEqual);\n\n if (diff.missingFromA.length || diff.missingFromB.length) {\n // Some messages have changed so let's work out which ones by sorting\n // them into their respective days.\n var current = sortMessagesByDay(state.messages, state.midnight);\n var next = sortMessagesByDay(newState.messages, newState.midnight);\n // This diffs the arrays to work out if there are any missing days that need\n // to be added (i.e. we've got some new messages on a new day) or if there\n // are any days that need to be deleted (i.e. the user has deleted some old messages).\n var daysDiff = diffArrays(current, next, function(dayCurrent, dayNext) {\n return dayCurrent.timestamp == dayNext.timestamp;\n });\n\n return {\n // Handle adding or removing whole days.\n days: buildDaysPatch(current, daysDiff.missingFromB, daysDiff.missingFromA),\n // Handle updating messages that don't require adding/removing a whole day.\n messages: buildMessagesPatch(daysDiff.matches)\n };\n } else {\n return null;\n }\n };\n\n /**\n * Build a patch for the header of this conversation. Check if this conversation\n * is a group conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildHeaderPatchTypePrivate = function(state, newState) {\n var requireAddContact = buildRequireAddContact(state, newState);\n var confirmContactRequest = buildConfirmContactRequest(state, newState);\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var requiresAddContact = requireAddContact && requireAddContact.show && !requireAddContact.hasMessages;\n var requiredAddContact = requireAddContact && !requireAddContact.show;\n // Render the header once we've got a user.\n var shouldRenderHeader = !oldOtherUser && newOtherUser;\n // We should also re-render the header if the other user requires\n // being added as a contact or if they did but no longer do.\n shouldRenderHeader = shouldRenderHeader || requiresAddContact || requiredAddContact;\n // Finally, we should re-render if the other user has sent this user\n // a contact request that is waiting for approval or if it's been approved/declined.\n shouldRenderHeader = shouldRenderHeader || confirmContactRequest !== null;\n\n if (shouldRenderHeader) {\n return {\n type: Constants.CONVERSATION_TYPES.PRIVATE,\n // We can show controls if the other user doesn't require add contact\n // and we aren't waiting for this user to respond to a contact request.\n showControls: !requiresAddContact && !confirmContactRequest,\n context: {\n id: newState.id,\n name: newState.name,\n subname: newState.subname,\n totalmembercount: newState.totalMemberCount,\n imageurl: newState.imageUrl,\n isfavourite: newState.isFavourite,\n ismuted: newState.isMuted,\n // Don't show favouriting if we don't have a conversation.\n showfavourite: newState.id !== null,\n userid: newOtherUser.id,\n showonlinestatus: newOtherUser.showonlinestatus,\n isonline: newOtherUser.isonline,\n isblocked: newOtherUser.isblocked,\n iscontact: newOtherUser.iscontact\n }\n };\n }\n\n return null;\n };\n\n /**\n * Build a patch for the header of this conversation. Check if this conversation\n * is a group conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildHeaderPatchTypeSelf = function(state, newState) {\n var shouldRenderHeader = (state.name === null && newState.name !== null);\n\n if (shouldRenderHeader) {\n return {\n type: Constants.CONVERSATION_TYPES.SELF,\n // Don't display the controls for the self-conversations.\n showControls: false,\n context: {\n id: newState.id,\n name: newState.name,\n subname: newState.subname,\n imageurl: newState.imageUrl,\n isfavourite: newState.isFavourite,\n // Don't show favouriting if we don't have a conversation.\n showfavourite: newState.id !== null,\n showonlinestatus: true,\n }\n };\n }\n\n return null;\n };\n\n /**\n * Build a patch for the header of this conversation. Check if this conversation\n * is a group conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildHeaderPatchTypePublic = function(state, newState) {\n var oldMemberCount = state.totalMemberCount;\n var newMemberCount = newState.totalMemberCount;\n\n if (oldMemberCount != newMemberCount) {\n return {\n type: Constants.CONVERSATION_TYPES.PUBLIC,\n showControls: true,\n context: {\n id: newState.id,\n name: newState.name,\n subname: newState.subname,\n totalmembercount: newState.totalMemberCount,\n imageurl: newState.imageUrl,\n isfavourite: newState.isFavourite,\n ismuted: newState.isMuted,\n // Don't show favouriting if we don't have a conversation.\n showfavourite: newState.id !== null\n }\n };\n } else {\n return null;\n }\n };\n\n /**\n * Find the newest or oldest message.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Number} Oldest or newest message id.\n */\n var buildScrollToMessagePatch = function(state, newState) {\n var oldMessages = state.messages;\n var newMessages = newState.messages;\n\n if (newMessages.length < 1) {\n return null;\n }\n\n if (oldMessages.length < 1) {\n return newMessages[newMessages.length - 1].id;\n }\n\n var previousNewest = oldMessages[state.messages.length - 1];\n var currentNewest = newMessages[newMessages.length - 1];\n var previousOldest = oldMessages[0];\n var currentOldest = newMessages[0];\n\n if (previousNewest.id != currentNewest.id) {\n return currentNewest.id;\n } else if (previousOldest.id != currentOldest.id) {\n return previousOldest.id;\n }\n\n return null;\n };\n\n /**\n * Check if members should be loaded.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingMembersPatch = function(state, newState) {\n if (!state.loadingMembers && newState.loadingMembers) {\n return true;\n } else if (state.loadingMembers && !newState.loadingMembers) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if the messages are being loaded for the first time.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingFirstMessages = function(state, newState) {\n if (state.hasTriedToLoadMessages === newState.hasTriedToLoadMessages) {\n return null;\n } else if (!newState.hasTriedToLoadMessages && newState.loadingMessages) {\n return true;\n } else if (newState.hasTriedToLoadMessages && !newState.loadingMessages) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if the messages are still being loaded\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingMessages = function(state, newState) {\n if (!state.loadingMessages && newState.loadingMessages) {\n return true;\n } else if (state.loadingMessages && !newState.loadingMessages) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Determine if we should show the emoji picker.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildShowEmojiPicker = function(state, newState) {\n if (!state.showEmojiPicker && newState.showEmojiPicker) {\n return true;\n } else if (state.showEmojiPicker && !newState.showEmojiPicker) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Determine if we should show the emoji auto complete.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildShowEmojiAutoComplete = function(state, newState) {\n if (!state.showEmojiAutoComplete && newState.showEmojiAutoComplete) {\n return true;\n } else if (state.showEmojiAutoComplete && !newState.showEmojiAutoComplete) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Get the user Object of user to be blocked if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmBlockUser = function(state, newState) {\n if (newState.pendingBlockUserIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingBlockUserIds[0];\n return newState.members[userId];\n } else if (state.pendingBlockUserIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Get the user Object of user to be unblocked if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmUnblockUser = function(state, newState) {\n if (newState.pendingUnblockUserIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingUnblockUserIds[0];\n return newState.members[userId];\n } else if (state.pendingUnblockUserIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Get the user Object of user to be added as contact if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmAddContact = function(state, newState) {\n if (newState.pendingAddContactIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingAddContactIds[0];\n return newState.members[userId];\n } else if (state.pendingAddContactIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Get the user Object of user to be removed as contact if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmRemoveContact = function(state, newState) {\n if (newState.pendingRemoveContactIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingRemoveContactIds[0];\n return newState.members[userId];\n } else if (state.pendingRemoveContactIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Check if there are any messages to be deleted.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Null} The conversation type and if the user can delete the messages for all users.\n */\n var buildConfirmDeleteSelectedMessages = function(state, newState) {\n var oldPendingCount = state.pendingDeleteMessageIds.length;\n var newPendingCount = newState.pendingDeleteMessageIds.length;\n\n if (newPendingCount && !oldPendingCount) {\n return {\n show: true,\n type: newState.type,\n canDeleteMessagesForAllUsers: newState.canDeleteMessagesForAllUsers\n };\n } else if (oldPendingCount && !newPendingCount) {\n return {\n show: false\n };\n }\n\n return null;\n };\n\n /**\n * Check if there is a conversation to be deleted.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {int|Null} The conversation type to be deleted.\n */\n var buildConfirmDeleteConversation = function(state, newState) {\n if (!state.pendingDeleteConversation && newState.pendingDeleteConversation) {\n return newState.type;\n } else if (state.pendingDeleteConversation && !newState.pendingDeleteConversation) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Check if there is a pending contact request to accept or decline.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildConfirmContactRequest = function(state, newState) {\n var loggedInUserId = state.loggedInUserId;\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var oldReceivedRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId && request.userid == oldOtherUser.id;\n });\n var newReceivedRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId && request.userid == newOtherUser.id;\n });\n var oldRequest = oldReceivedRequests.length ? oldReceivedRequests[0] : null;\n var newRequest = newReceivedRequests.length ? newReceivedRequests[0] : null;\n\n if (!oldRequest && newRequest) {\n return newOtherUser;\n } else if (oldRequest && !newRequest) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes in blocked users.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildIsBlocked = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.isblocked ? true : null;\n } else if (!newOtherUser && oldOtherUser) {\n return oldOtherUser.isblocked ? false : null;\n } else if (oldOtherUser.isblocked && !newOtherUser.isblocked) {\n return false;\n } else if (!oldOtherUser.isblocked && newOtherUser.isblocked) {\n return true;\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes the conversation favourite state.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildIsFavourite = function(state, newState) {\n var oldIsFavourite = state.isFavourite;\n var newIsFavourite = newState.isFavourite;\n\n if (state.id === null && newState.id === null) {\n // The conversation isn't yet created so don't change anything.\n return null;\n } else if (state.id === null && newState.id !== null) {\n // The conversation was created so we can show the add favourite button.\n return 'show-add';\n } else if (state.id !== null && newState.id === null) {\n // We're changing from a created conversation to a new conversation so hide\n // the favouriting functionality for now.\n return 'hide';\n } else if (oldIsFavourite == newIsFavourite) {\n // No change.\n return null;\n } else if (!oldIsFavourite && newIsFavourite) {\n return 'show-remove';\n } else if (oldIsFavourite && !newIsFavourite) {\n return 'show-add';\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes the conversation muted state.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {string|null}\n */\n var buildIsMuted = function(state, newState) {\n var oldIsMuted = state.isMuted;\n var newIsMuted = newState.isMuted;\n\n if (state.id === null && newState.id === null) {\n // The conversation isn't yet created so don't change anything.\n return null;\n } else if (state.id === null && newState.id !== null) {\n // The conversation was created so we can show the mute button.\n return 'show-mute';\n } else if (state.id !== null && newState.id === null) {\n // We're changing from a created conversation to a new conversation so hide\n // the muting functionality for now.\n return 'hide';\n } else if (oldIsMuted == newIsMuted) {\n // No change.\n return null;\n } else if (!oldIsMuted && newIsMuted) {\n return 'show-unmute';\n } else if (oldIsMuted && !newIsMuted) {\n return 'show-mute';\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes in the contact status of the current user\n * and other user.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildIsContact = function(state, newState) {\n var loggedInUserId = state.loggedInUserId;\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var oldContactRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) {\n return (request.userid == loggedInUserId && request.requesteduserid == oldOtherUser.id) ||\n (request.userid == oldOtherUser.id && request.requesteduserid == loggedInUserId);\n });\n var newContactRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) {\n return (request.userid == loggedInUserId && request.requesteduserid == newOtherUser.id) ||\n (request.userid == newOtherUser.id && request.requesteduserid == loggedInUserId);\n });\n var oldHasContactRequests = oldContactRequests.length > 0;\n var newHasContactRequests = newContactRequests.length > 0;\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (oldHasContactRequests && newHasContactRequests) {\n return null;\n } else if (!oldHasContactRequests && newHasContactRequests && !newOtherUser.iscontact) {\n return 'pending-contact';\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.iscontact ? 'contact' : null;\n } else if (!newOtherUser && oldOtherUser) {\n return oldOtherUser.iscontact ? 'non-contact' : null;\n } else if (oldOtherUser.iscontact && !newOtherUser.iscontact) {\n return newHasContactRequests ? 'pending-contact' : 'non-contact';\n } else if (!oldOtherUser.iscontact && newOtherUser.iscontact) {\n return 'contact';\n } else {\n return null;\n }\n };\n\n /**\n * Check if a confirm action is active.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingConfirmationAction = function(state, newState) {\n if (!state.loadingConfirmAction && newState.loadingConfirmAction) {\n return true;\n } else if (state.loadingConfirmAction && !newState.loadingConfirmAction) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if a edit mode is active.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildInEditMode = function(state, newState) {\n var oldHasSelectedMessages = state.selectedMessageIds.length > 0;\n var newHasSelectedMessages = newState.selectedMessageIds.length > 0;\n var numberOfMessagesHasChanged = state.messages.length != newState.messages.length;\n\n if (!oldHasSelectedMessages && newHasSelectedMessages) {\n return true;\n } else if (oldHasSelectedMessages && !newHasSelectedMessages) {\n return false;\n } else if (oldHasSelectedMessages && numberOfMessagesHasChanged) {\n return true;\n } else {\n return null;\n }\n };\n\n /**\n * Build a patch for the messages selected.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildSelectedMessages = function(state, newState) {\n var oldSelectedMessages = state.selectedMessageIds;\n var newSelectedMessages = newState.selectedMessageIds;\n\n if (isArrayEqual(oldSelectedMessages, newSelectedMessages)) {\n return null;\n }\n\n var diff = diffArrays(oldSelectedMessages, newSelectedMessages, function(a, b) {\n return a == b;\n });\n\n return {\n count: newSelectedMessages.length,\n add: diff.missingFromA,\n remove: diff.missingFromB\n };\n };\n\n /**\n * Get a list of users from the state that are not the logged in user. Use to find group\n * message members or the other user in a conversation.\n *\n * @param {Object} state State\n * @return {Array} List of users.\n */\n var getOtherUserFromState = function(state) {\n return Object.keys(state.members).reduce(function(carry, userId) {\n if (userId != state.loggedInUserId && !carry) {\n carry = state.members[userId];\n }\n\n return carry;\n }, null);\n };\n\n /**\n * Check if the given user requires a contact request from the logged in user.\n *\n * @param {Integer} loggedInUserId The logged in user id\n * @param {Object} user User record\n * @return {Bool}\n */\n var requiresContactRequest = function(loggedInUserId, user) {\n // If a user can message then no contact request is required.\n if (user.canmessage) {\n return false;\n }\n\n var contactRequests = user.contactrequests.filter(function(request) {\n return request.userid == loggedInUserId || request.requesteduserid;\n });\n var hasSentContactRequest = contactRequests.length > 0;\n return user.requirescontact && !user.iscontact && !hasSentContactRequest;\n };\n\n /**\n * Check if other users are required to be added as contact.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} Object controlling the required to add contact dialog variables.\n */\n var buildRequireAddContact = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var hadMessages = state.messages.length > 0;\n var hasMessages = newState.messages.length > 0;\n var loggedInUserId = newState.loggedInUserId;\n var prevRequiresContactRequest = oldOtherUser && requiresContactRequest(loggedInUserId, oldOtherUser);\n var nextRequiresContactRequest = newOtherUser && requiresContactRequest(loggedInUserId, newOtherUser);\n var confirmAddContact = buildConfirmAddContact(state, newState);\n var finishedAddContact = confirmAddContact === false;\n\n // Still doing first load.\n if (!state.hasTriedToLoadMessages && !newState.hasTriedToLoadMessages) {\n return null;\n }\n\n // No users yet.\n if (!oldOtherUser && !newOtherUser) {\n return null;\n }\n\n // We've loaded a new user and they require a contact request.\n if (!oldOtherUser && nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n\n // The logged in user has completed the confirm contact request dialogue\n // but the other user still requires a contact request which means the logged\n // in user either declined the confirmation or it failed.\n if (finishedAddContact && nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n\n // Everything is loaded.\n if (state.hasTriedToLoadMessages && newState.hasTriedToLoadMessages) {\n if (!prevRequiresContactRequest && nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n\n if (prevRequiresContactRequest && !nextRequiresContactRequest) {\n return {\n show: false,\n hasMessages: hasMessages\n };\n }\n }\n\n // First load just completed.\n if (!state.hasTriedToLoadMessages && newState.hasTriedToLoadMessages) {\n if (nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n }\n\n // Being reset.\n if (state.hasTriedToLoadMessages && !newState.hasTriedToLoadMessages) {\n if (prevRequiresContactRequest) {\n return {\n show: false,\n hasMessages: hadMessages\n };\n }\n }\n\n return null;\n };\n\n /**\n * Check if other users are required to be unblocked.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildRequireUnblock = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (oldOtherUser && !newOtherUser) {\n return oldOtherUser.isblocked ? false : null;\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.isblocked ? true : null;\n } else if (!oldOtherUser.isblocked && newOtherUser.isblocked) {\n return true;\n } else if (oldOtherUser.isblocked && !newOtherUser.isblocked) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Check if other users can be messaged.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildUnableToMessage = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n\n if (newState.type == Constants.CONVERSATION_TYPES.SELF) {\n // Users always can send message themselves on self-conversations.\n return null;\n }\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (oldOtherUser && !newOtherUser) {\n return oldOtherUser.canmessage ? null : true;\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.canmessage ? null : true;\n } else if (!oldOtherUser.canmessage && newOtherUser.canmessage) {\n return false;\n } else if (oldOtherUser.canmessage && !newOtherUser.canmessage) {\n return true;\n }\n\n return null;\n };\n\n /**\n * Build patch for footer information for a private conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} containing footer state type.\n */\n var buildFooterPatchTypePrivate = function(state, newState) {\n var loadingFirstMessages = buildLoadingFirstMessages(state, newState);\n var inEditMode = buildInEditMode(state, newState);\n var requireAddContact = buildRequireAddContact(state, newState);\n var requireUnblock = buildRequireUnblock(state, newState);\n var unableToMessage = buildUnableToMessage(state, newState);\n var showRequireAddContact = requireAddContact !== null ? requireAddContact.show && requireAddContact.hasMessages : null;\n var otherUser = getOtherUserFromState(newState);\n var generateReturnValue = function(checkValue, successReturn) {\n if (checkValue) {\n return successReturn;\n } else if (checkValue !== null && !checkValue) {\n if (!otherUser) {\n return {type: 'content'};\n } else if (otherUser.isblocked) {\n return {type: 'unblock'};\n } else if (newState.messages.length && requiresContactRequest(newState.loggedInUserId, otherUser)) {\n return {\n type: 'add-contact',\n user: otherUser\n };\n } else if (!otherUser.canmessage && (otherUser.requirescontact && !otherUser.iscontact)) {\n return {type: 'unable-to-message'};\n }\n }\n\n return null;\n };\n\n if (\n loadingFirstMessages === null &&\n inEditMode === null &&\n requireAddContact === null &&\n requireUnblock === null\n ) {\n return null;\n }\n\n var checks = [\n [loadingFirstMessages, {type: 'placeholder'}],\n [inEditMode, {type: 'edit-mode'}],\n [unableToMessage, {type: 'unable-to-message'}],\n [requireUnblock, {type: 'unblock'}],\n [showRequireAddContact, {type: 'add-contact', user: otherUser}]\n ];\n\n for (var i = 0; i < checks.length; i++) {\n var checkValue = checks[i][0];\n var successReturn = checks[i][1];\n var result = generateReturnValue(checkValue, successReturn);\n\n if (result !== null) {\n return result;\n }\n }\n\n return {\n type: 'content'\n };\n };\n\n /**\n * Build patch for footer information for a public conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} containing footer state type.\n */\n var buildFooterPatchTypePublic = function(state, newState) {\n var loadingFirstMessages = buildLoadingFirstMessages(state, newState);\n var inEditMode = buildInEditMode(state, newState);\n\n if (loadingFirstMessages === null && inEditMode === null) {\n return null;\n }\n\n if (loadingFirstMessages) {\n return {type: 'placeholder'};\n }\n\n if (inEditMode) {\n return {type: 'edit-mode'};\n }\n\n return {\n type: 'content'\n };\n };\n\n /**\n * Check if we're viewing a different conversation. If so then we need to\n * reset the UI.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {bool|null} If a reset needs to occur\n */\n var buildReset = function(state, newState) {\n var oldType = state.type;\n var newType = newState.type;\n var oldConversationId = state.id;\n var newConversationId = newState.id;\n var oldMemberIds = Object.keys(state.members);\n var newMemberIds = Object.keys(newState.members);\n\n oldMemberIds.sort();\n newMemberIds.sort();\n\n var membersUnchanged = oldMemberIds.every(function(id, index) {\n return id == newMemberIds[index];\n });\n\n if (oldType != newType) {\n // If we've changed conversation type then we need to reset.\n return true;\n } else if (oldConversationId && !newConversationId) {\n // We previously had a conversation id but no longer do. This likely means\n // the user is viewing the conversation with someone they've never spoken to\n // before.\n return true;\n } else if (oldConversationId && newConversationId && oldConversationId != newConversationId) {\n // If we had a conversation id and it's changed then we need to reset.\n return true;\n } else if (!oldConversationId && !newConversationId && !membersUnchanged) {\n // If we never had a conversation id but the members of the conversation have\n // changed then we need to reset. This can happen if the user goes from viewing\n // a user they've never had a conversation with to viewing a different user that\n // they've never had a conversation with.\n return true;\n }\n\n return null;\n };\n\n /**\n * We should show this message always, for all the self-conversations.\n *\n * The message should be hidden when it's not a self-conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {bool}\n */\n var buildSelfConversationMessage = function(state, newState) {\n if (state.type != newState.type) {\n return (newState.type == Constants.CONVERSATION_TYPES.SELF);\n }\n\n return null;\n };\n\n /**\n * We should show the contact request sent message if the user just sent\n * a contact request to the other user and there are no messages in the\n * conversation.\n *\n * The messages should be hidden when there are messages in the conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {string|false|null}\n */\n var buildContactRequestSent = function(state, newState) {\n var loggedInUserId = newState.loggedInUserId;\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var oldSentRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) {\n return request.userid == loggedInUserId;\n });\n var newSentRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) {\n return request.userid == loggedInUserId;\n });\n var oldRequest = oldSentRequests.length > 0;\n var newRequest = newSentRequests.length > 0;\n var hadMessages = state.messages.length > 0;\n var hasMessages = state.messages.length > 0;\n\n if (!oldRequest && newRequest && !newOtherUser.iscontact && !hasMessages) {\n return newOtherUser.fullname;\n } else if (oldOtherUser && !oldOtherUser.iscontact && newRequest && newOtherUser.iscontact) {\n // Contact request accepted.\n return false;\n } else if (oldRequest && !newRequest) {\n return false;\n } else if (!hadMessages && hasMessages) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Build the full patch comparing the current state and the new state. This patch is used by\n * the conversation renderer to render the UI on any update.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} Patch containing all information changed.\n */\n var buildPatch = function(state, newState) {\n var config = {\n all: {\n reset: buildReset,\n conversation: buildConversationPatch,\n scrollToMessage: buildScrollToMessagePatch,\n loadingMembers: buildLoadingMembersPatch,\n loadingFirstMessages: buildLoadingFirstMessages,\n loadingMessages: buildLoadingMessages,\n confirmDeleteSelectedMessages: buildConfirmDeleteSelectedMessages,\n inEditMode: buildInEditMode,\n selectedMessages: buildSelectedMessages,\n isFavourite: buildIsFavourite,\n isMuted: buildIsMuted,\n showEmojiPicker: buildShowEmojiPicker,\n showEmojiAutoComplete: buildShowEmojiAutoComplete\n }\n };\n // These build functions are only applicable to private conversations.\n config[Constants.CONVERSATION_TYPES.PRIVATE] = {\n header: buildHeaderPatchTypePrivate,\n footer: buildFooterPatchTypePrivate,\n confirmBlockUser: buildConfirmBlockUser,\n confirmUnblockUser: buildConfirmUnblockUser,\n confirmAddContact: buildConfirmAddContact,\n confirmRemoveContact: buildConfirmRemoveContact,\n confirmContactRequest: buildConfirmContactRequest,\n confirmDeleteConversation: buildConfirmDeleteConversation,\n isBlocked: buildIsBlocked,\n isContact: buildIsContact,\n loadingConfirmAction: buildLoadingConfirmationAction,\n requireAddContact: buildRequireAddContact,\n contactRequestSent: buildContactRequestSent\n };\n // These build functions are only applicable to public (group) conversations.\n config[Constants.CONVERSATION_TYPES.PUBLIC] = {\n header: buildHeaderPatchTypePublic,\n footer: buildFooterPatchTypePublic,\n };\n // These build functions are only applicable to self-conversations.\n config[Constants.CONVERSATION_TYPES.SELF] = {\n header: buildHeaderPatchTypeSelf,\n footer: buildFooterPatchTypePublic,\n confirmDeleteConversation: buildConfirmDeleteConversation,\n selfConversationMessage: buildSelfConversationMessage\n };\n\n var patchConfig = $.extend({}, config.all);\n if (newState.type && newState.type in config) {\n // Add the type specific builders to the patch config.\n patchConfig = $.extend(patchConfig, config[newState.type]);\n }\n\n return Object.keys(patchConfig).reduce(function(patch, key) {\n var buildFunc = patchConfig[key];\n var value = buildFunc(state, newState);\n\n if (value !== null) {\n patch[key] = value;\n }\n\n return patch;\n }, {});\n };\n\n return {\n buildPatch: buildPatch\n };\n});\n"],"file":"message_drawer_view_conversation_patcher.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/message_drawer_view_conversation_patcher.js"],"names":["define","$","UserDate","Constants","sortMessagesByDay","messages","midnight","messagesByDay","reduce","carry","message","timeCreated","dayTimestamp","getUserMidnightForTimestamp","hasOwnProperty","push","Object","keys","map","timestamp","diffArrays","a","b","matchFunction","slice","missingFromA","missingFromB","matches","forEach","current","found","index","length","next","splice","findPositionInArray","array","breakFunction","before","i","candidate","isArrayEqual","sort","aLength","bLength","every","item","isObjectEqual","aKeys","bKeys","key","aVal","bVal","aType","bType","Array","isArray","isMessageEqual","id","state","sendState","text","buildDaysPatch","remove","add","day","value","buildMessagesPatch","matchingDays","update","days","dayCurrent","dayNext","messagesDiff","patch","timeAdded","concat","after","buildConversationPatch","newState","diff","daysDiff","buildHeaderPatchTypePrivate","requireAddContact","buildRequireAddContact","confirmContactRequest","buildConfirmContactRequest","oldOtherUser","getOtherUserFromState","newOtherUser","requiresAddContact","show","hasMessages","requiredAddContact","shouldRenderHeader","type","CONVERSATION_TYPES","PRIVATE","showControls","context","name","subname","totalmembercount","totalMemberCount","imageurl","imageUrl","isfavourite","isFavourite","ismuted","isMuted","showfavourite","userid","showonlinestatus","isonline","isblocked","iscontact","buildHeaderPatchTypeSelf","SELF","buildHeaderPatchTypePublic","oldMemberCount","newMemberCount","PUBLIC","buildScrollToMessagePatch","oldMessages","newMessages","previousNewest","currentNewest","previousOldest","currentOldest","buildLoadingMembersPatch","loadingMembers","buildLoadingFirstMessages","hasTriedToLoadMessages","loadingMessages","buildLoadingMessages","buildShowEmojiPicker","showEmojiPicker","buildShowEmojiAutoComplete","showEmojiAutoComplete","buildConfirmBlockUser","pendingBlockUserIds","userId","members","buildConfirmUnblockUser","pendingUnblockUserIds","buildConfirmAddContact","pendingAddContactIds","buildConfirmRemoveContact","pendingRemoveContactIds","buildConfirmDeleteSelectedMessages","oldPendingCount","pendingDeleteMessageIds","newPendingCount","canDeleteMessagesForAllUsers","buildConfirmDeleteConversation","pendingDeleteConversation","loggedInUserId","oldReceivedRequests","contactrequests","filter","request","requesteduserid","newReceivedRequests","oldRequest","newRequest","buildIsBlocked","buildIsFavourite","oldIsFavourite","newIsFavourite","buildIsMuted","oldIsMuted","newIsMuted","buildIsContact","oldContactRequests","newContactRequests","oldHasContactRequests","newHasContactRequests","buildLoadingConfirmationAction","loadingConfirmAction","buildInEditMode","oldHasSelectedMessages","selectedMessageIds","newHasSelectedMessages","numberOfMessagesHasChanged","buildSelectedMessages","oldSelectedMessages","newSelectedMessages","count","requiresContactRequest","user","canmessage","contactRequests","hasSentContactRequest","requirescontact","hadMessages","prevRequiresContactRequest","nextRequiresContactRequest","confirmAddContact","buildRequireUnblock","buildUnableToMessage","buildFooterPatchTypePrivate","loadingFirstMessages","inEditMode","requireUnblock","unableToMessage","showRequireAddContact","otherUser","generateReturnValue","checkValue","successReturn","checks","result","buildFooterPatchTypePublic","buildReset","oldType","newType","oldConversationId","newConversationId","oldMemberIds","newMemberIds","membersUnchanged","buildSelfConversationMessage","buildContactRequestSent","oldSentRequests","newSentRequests","fullname","buildPatch","config","all","reset","conversation","scrollToMessage","confirmDeleteSelectedMessages","selectedMessages","header","footer","confirmBlockUser","confirmUnblockUser","confirmRemoveContact","confirmDeleteConversation","isBlocked","isContact","contactRequestSent","selfConversationMessage","patchConfig","extend","buildFunc"],"mappings":"mSA2BAA,OAAM,yDACN,CACI,QADJ,CAEI,gBAFJ,CAGI,yDAHJ,CADM,CAMN,SACIC,CADJ,CAEIC,CAFJ,CAGIC,CAHJ,CAIE,IAQMC,CAAAA,CAAiB,CAAG,SAASC,CAAT,CAAmBC,CAAnB,CAA6B,CACjD,GAAIC,CAAAA,CAAa,CAAGF,CAAQ,CAACG,MAAT,CAAgB,SAASC,CAAT,CAAgBC,CAAhB,CAAyB,IACrDC,CAAAA,CAAW,CAAGD,CAAO,CAACC,WAAR,CAAsBD,CAAO,CAACC,WAA9B,CAA4CL,CADL,CAErDM,CAAY,CAAGV,CAAQ,CAACW,2BAAT,CAAqCF,CAArC,CAAkDL,CAAlD,CAFsC,CAIzD,GAAIG,CAAK,CAACK,cAAN,CAAqBF,CAArB,CAAJ,CAAwC,CACpCH,CAAK,CAACG,CAAD,CAAL,CAAoBG,IAApB,CAAyBL,CAAzB,CACH,CAFD,IAEO,CACHD,CAAK,CAACG,CAAD,CAAL,CAAsB,CAACF,CAAD,CACzB,CAED,MAAOD,CAAAA,CACV,CAXmB,CAWjB,EAXiB,CAApB,CAaA,MAAOO,CAAAA,MAAM,CAACC,IAAP,CAAYV,CAAZ,EAA2BW,GAA3B,CAA+B,SAASN,CAAT,CAAuB,CACzD,MAAO,CACHO,SAAS,CAAEP,CADR,CAEHP,QAAQ,CAAEE,CAAa,CAACK,CAAD,CAFpB,CAIV,CALM,CAMV,CA5BH,CAuCMQ,CAAU,CAAG,SAASC,CAAT,CAAYC,CAAZ,CAAeC,CAAf,CAA8B,CAE3CD,CAAC,CAAGA,CAAC,CAACE,KAAF,EAAJ,CAF2C,GAGvCC,CAAAA,CAAY,CAAG,EAHwB,CAIvCC,CAAY,CAAG,EAJwB,CAKvCC,CAAO,CAAG,EAL6B,CAO3CN,CAAC,CAACO,OAAF,CAAU,SAASC,CAAT,CAAkB,IACpBC,CAAAA,CAAK,GADe,CAEpBC,CAAK,CAAG,CAFY,CAIxB,KAAOA,CAAK,CAAGT,CAAC,CAACU,MAAjB,CAAyBD,CAAK,EAA9B,CAAkC,CAC9B,GAAIE,CAAAA,CAAI,CAAGX,CAAC,CAACS,CAAD,CAAZ,CAEA,GAAIR,CAAa,CAACM,CAAD,CAAUI,CAAV,CAAjB,CAAkC,CAC9BH,CAAK,GAAL,CACAH,CAAO,CAACZ,IAAR,CAAa,CACTM,CAAC,CAAEQ,CADM,CAETP,CAAC,CAAEW,CAFM,CAAb,EAIA,KACH,CACJ,CAED,GAAIH,CAAJ,CAAW,CAEPR,CAAC,CAACY,MAAF,CAASH,CAAT,CAAgB,CAAhB,CACH,CAHD,IAGO,CAGHL,CAAY,CAACX,IAAb,CAAkBc,CAAlB,CACH,CACJ,CAzBD,EA2BAJ,CAAY,CAAGH,CAAf,CAEA,MAAO,CACHG,YAAY,CAAEA,CADX,CAEHC,YAAY,CAAEA,CAFX,CAGHC,OAAO,CAAEA,CAHN,CAKV,CAhFH,CAyFMQ,CAAmB,CAAG,SAASC,CAAT,CAAgBC,CAAhB,CAA+B,CAGrD,OAFIC,CAAAA,CAAM,CAAG,IAEb,CAASC,CAAC,CAAG,CAAb,CACQC,CADR,CAAgBD,CAAC,CAAGH,CAAK,CAACJ,MAA1B,CAAkCO,CAAC,EAAnC,CAAuC,CAC/BC,CAD+B,CACnBJ,CAAK,CAACG,CAAD,CADc,CAGnC,GAAIF,CAAa,CAACG,CAAD,CAAjB,CAA8B,CAC1B,MAAOA,CAAAA,CACV,CACJ,CAED,MAAOF,CAAAA,CACV,CArGH,CA8GMG,CAAY,CAAG,SAASpB,CAAT,CAAYC,CAAZ,CAAe,CAE9BD,CAAC,CAAGA,CAAC,CAACG,KAAF,EAAJ,CACAF,CAAC,CAAGA,CAAC,CAACE,KAAF,EAAJ,CACAH,CAAC,CAACqB,IAAF,GACApB,CAAC,CAACoB,IAAF,GAL8B,GAM1BC,CAAAA,CAAO,CAAGtB,CAAC,CAACW,MANc,CAO1BY,CAAO,CAAGtB,CAAC,CAACU,MAPc,CAS9B,GAAc,CAAV,CAAAW,CAAO,EAAkB,CAAV,CAAAC,CAAnB,CAAgC,CAC5B,QACH,CAED,GAAID,CAAO,EAAIC,CAAf,CAAwB,CACpB,QACH,CAED,MAAOvB,CAAAA,CAAC,CAACwB,KAAF,CAAQ,SAASC,CAAT,CAAef,CAAf,CAAsB,CACjC,MAAOe,CAAAA,CAAI,EAAIxB,CAAC,CAACS,CAAD,CACnB,CAFM,CAGV,CAlIH,CA4IMgB,CAAa,CAAG,SAAS1B,CAAT,CAAYC,CAAZ,CAAe,IAC3B0B,CAAAA,CAAK,CAAGhC,MAAM,CAACC,IAAP,CAAYI,CAAZ,CADmB,CAE3B4B,CAAK,CAAGjC,MAAM,CAACC,IAAP,CAAYK,CAAZ,CAFmB,CAI/B,GAAI0B,CAAK,CAAChB,MAAN,EAAgBiB,CAAK,CAACjB,MAA1B,CAAkC,CAC9B,QACH,CAED,MAAOgB,CAAAA,CAAK,CAACH,KAAN,CAAY,SAASK,CAAT,CAAc,IACzBC,CAAAA,CAAI,CAAG9B,CAAC,CAAC6B,CAAD,CADiB,CAEzBE,CAAI,CAAG9B,CAAC,CAAC4B,CAAD,CAFiB,CAGzBG,CAAK,SAAUF,CAAV,CAHoB,CAIzBG,CAAK,SAAUF,CAAV,CAJoB,CAK7BC,CAAK,CAAa,IAAT,GAAAF,CAAD,CAAkB,MAAlB,CAA2BE,CAAnC,CACAC,CAAK,CAAa,IAAT,GAAAH,CAAD,CAAkB,MAAlB,CAA2BG,CAAnC,CACAD,CAAK,CAAc,QAAV,GAAAA,CAAK,EAAiBE,KAAK,CAACC,OAAN,CAAcH,CAAd,CAAvB,CAA+C,OAA/C,CAAyDA,CAAjE,CACAC,CAAK,CAAc,QAAV,GAAAA,CAAK,EAAiBC,KAAK,CAACC,OAAN,CAAcF,CAAd,CAAvB,CAA+C,OAA/C,CAAyDA,CAAjE,CAEA,GAAID,CAAK,GAAKC,CAAd,CAAqB,CACjB,QACH,CAED,OAAQD,CAAR,EACI,IAAK,QAAL,CACI,MAAON,CAAAA,CAAa,CAACI,CAAD,CAAOC,CAAP,CAApB,CACJ,IAAK,OAAL,CACI,MAAOX,CAAAA,CAAY,CAACU,CAAD,CAAOC,CAAP,CAAnB,CACJ,QACI,MAAO/B,CAAAA,CAAC,CAAC6B,CAAD,CAAD,EAAU5B,CAAC,CAAC4B,CAAD,CAAlB,CANR,CAQH,CAtBM,CAuBV,CA3KH,CAqLMO,CAAc,CAAG,SAASpC,CAAT,CAAYC,CAAZ,CAAe,CAChC,MAAOyB,CAAAA,CAAa,CAChB,CACIW,EAAE,CAAErC,CAAC,CAACqC,EADV,CAEIC,KAAK,CAAEtC,CAAC,CAACuC,SAFb,CAGIC,IAAI,CAAExC,CAAC,CAACwC,IAHZ,CAIIlD,WAAW,CAAEU,CAAC,CAACV,WAJnB,CADgB,CAOhB,CACI+C,EAAE,CAAEpC,CAAC,CAACoC,EADV,CAEIC,KAAK,CAAErC,CAAC,CAACsC,SAFb,CAGIC,IAAI,CAAEvC,CAAC,CAACuC,IAHZ,CAIIlD,WAAW,CAAEW,CAAC,CAACX,WAJnB,CAPgB,CAcvB,CApMH,CA8MMmD,CAAc,CAAG,SAASjC,CAAT,CAAkBkC,CAAlB,CAA0BC,CAA1B,CAA+B,CAChD,MAAO,CACHD,MAAM,CAAEA,CADL,CAEHC,GAAG,CAAEA,CAAG,CAAC9C,GAAJ,CAAQ,SAAS+C,CAAT,CAAc,CAGvB,GAAI3B,CAAAA,CAAM,CAAGH,CAAmB,CAACN,CAAD,CAAU,SAASW,CAAT,CAAoB,CAC1D,MAAOyB,CAAAA,CAAG,CAAC9C,SAAJ,CAAgBqB,CAAS,CAACrB,SACpC,CAF+B,CAAhC,CAIA,MAAO,CACHmB,MAAM,CAAEA,CADL,CAEH4B,KAAK,CAAED,CAFJ,CAIV,CAXI,CAFF,CAeV,CA9NH,CAsOME,CAAkB,CAAG,SAASC,CAAT,CAAuB,IACxCL,CAAAA,CAAM,CAAG,EAD+B,CAExCC,CAAG,CAAG,EAFkC,CAGxCK,CAAM,CAAG,EAH+B,CAO5CD,CAAY,CAACxC,OAAb,CAAqB,SAAS0C,CAAT,CAAe,IAC5BC,CAAAA,CAAU,CAAGD,CAAI,CAACjD,CADU,CAE5BmD,CAAO,CAAGF,CAAI,CAAChD,CAFa,CAM5BmD,CAAY,CAAGrD,CAAU,CAACmD,CAAU,CAAClE,QAAZ,CAAsBmE,CAAO,CAACnE,QAA9B,CAAwCoD,CAAxC,CANG,CAU5BiB,CAAK,CAAGtD,CAAU,CAElBqD,CAAY,CAAC/C,YAFK,CAIlB+C,CAAY,CAAChD,YAJK,CAKlB,SAASJ,CAAT,CAAYC,CAAZ,CAAe,CAOX,MAAOD,CAAAA,CAAC,CAACqC,EAAF,EAAQpC,CAAC,CAACoC,EAAV,EAAiBrC,CAAC,CAACuC,SAAF,EAAetC,CAAC,CAACsC,SAAjB,EAA8BvC,CAAC,CAACsD,SAAF,EAAerD,CAAC,CAACqD,SAC1E,CAbiB,CAVU,CA6BhCZ,CAAM,CAAGA,CAAM,CAACa,MAAP,CAAcF,CAAK,CAAChD,YAApB,CAAT,CAMAgD,CAAK,CAACjD,YAAN,CAAmBG,OAAnB,CAA2B,SAASlB,CAAT,CAAkB,CAGzC,GAAI4B,CAAAA,CAAM,CAAG,IAAb,CAEA,GAAI5B,CAAO,CAACC,WAAZ,CAAyB,CAGrB2B,CAAM,CAAGH,CAAmB,CAACoC,CAAU,CAAClE,QAAZ,CAAsB,SAASmC,CAAT,CAAoB,CAClE,GAAI9B,CAAO,CAACC,WAAR,EAAuB6B,CAAS,CAAC7B,WAArC,CAAkD,CAC9C,MAAOD,CAAAA,CAAO,CAACgD,EAAR,CAAalB,CAAS,CAACkB,EACjC,CAFD,IAEO,CACH,MAAOhD,CAAAA,CAAO,CAACC,WAAR,CAAsB6B,CAAS,CAAC7B,WAC1C,CACJ,CAN2B,CAO/B,CAEDqD,CAAG,CAACjD,IAAJ,CAAS,CACLuB,MAAM,CAAEA,CADH,CAEL4B,KAAK,CAAExD,CAFF,CAGLuD,GAAG,CAAEM,CAHA,CAAT,CAKH,CAtBD,EA0BAF,CAAM,CAAGA,CAAM,CAACO,MAAP,CAAcF,CAAK,CAAC/C,OAAN,CAAcT,GAAd,CAAkB,SAASR,CAAT,CAAkB,CACvD,MAAO,CACH4B,MAAM,CAAE5B,CAAO,CAACW,CADb,CAEHwD,KAAK,CAAEnE,CAAO,CAACY,CAFZ,CAIV,CALsB,CAAd,CAMZ,CAnED,EAqEA,MAAO,CACH0C,GAAG,CAAEA,CADF,CAEHD,MAAM,CAAEA,CAFL,CAGHM,MAAM,CAAEA,CAHL,CAKV,CAvTH,CAgUMS,CAAsB,CAAG,SAASnB,CAAT,CAAgBoB,CAAhB,CAA0B,CACnD,GAAIC,CAAAA,CAAI,CAAG5D,CAAU,CAACuC,CAAK,CAACtD,QAAP,CAAiB0E,CAAQ,CAAC1E,QAA1B,CAAoCoD,CAApC,CAArB,CAEA,GAAIuB,CAAI,CAACvD,YAAL,CAAkBO,MAAlB,EAA4BgD,CAAI,CAACtD,YAAL,CAAkBM,MAAlD,CAA0D,IAGlDH,CAAAA,CAAO,CAAGzB,CAAiB,CAACuD,CAAK,CAACtD,QAAP,CAAiBsD,CAAK,CAACrD,QAAvB,CAHuB,CAIlD2B,CAAI,CAAG7B,CAAiB,CAAC2E,CAAQ,CAAC1E,QAAV,CAAoB0E,CAAQ,CAACzE,QAA7B,CAJ0B,CAQlD2E,CAAQ,CAAG7D,CAAU,CAACS,CAAD,CAAUI,CAAV,CAAgB,SAASsC,CAAT,CAAqBC,CAArB,CAA8B,CACnE,MAAOD,CAAAA,CAAU,CAACpD,SAAX,EAAwBqD,CAAO,CAACrD,SAC1C,CAFwB,CAR6B,CAYtD,MAAO,CAEHmD,IAAI,CAAER,CAAc,CAACjC,CAAD,CAAUoD,CAAQ,CAACvD,YAAnB,CAAiCuD,CAAQ,CAACxD,YAA1C,CAFjB,CAIHpB,QAAQ,CAAE8D,CAAkB,CAACc,CAAQ,CAACtD,OAAV,CAJzB,CAMV,CAlBD,IAkBO,CACH,MAAO,KACV,CACJ,CAxVH,CAkWMuD,CAA2B,CAAG,SAASvB,CAAT,CAAgBoB,CAAhB,CAA0B,IACpDI,CAAAA,CAAiB,CAAGC,CAAsB,CAACzB,CAAD,CAAQoB,CAAR,CADU,CAEpDM,CAAqB,CAAGC,CAA0B,CAAC3B,CAAD,CAAQoB,CAAR,CAFE,CAGpDQ,CAAY,CAAGC,CAAqB,CAAC7B,CAAD,CAHgB,CAIpD8B,CAAY,CAAGD,CAAqB,CAACT,CAAD,CAJgB,CAKpDW,CAAkB,CAAGP,CAAiB,EAAIA,CAAiB,CAACQ,IAAvC,EAA+C,CAACR,CAAiB,CAACS,WALnC,CAMpDC,CAAkB,CAAGV,CAAiB,EAAI,CAACA,CAAiB,CAACQ,IANT,CAQpDG,CAAkB,CAAG,CAACP,CAAD,EAAiBE,CARc,CAWxDK,CAAkB,CAAGA,CAAkB,EAAIJ,CAAtB,EAA4CG,CAAjE,CAGAC,CAAkB,CAAGA,CAAkB,EAA8B,IAA1B,GAAAT,CAA3C,CAEA,GAAIS,CAAJ,CAAwB,CACpB,MAAO,CACHC,IAAI,CAAE5F,CAAS,CAAC6F,kBAAV,CAA6BC,OADhC,CAIHC,YAAY,CAAE,CAACR,CAAD,EAAuB,CAACL,CAJnC,CAKHc,OAAO,CAAE,CACLzC,EAAE,CAAEqB,CAAQ,CAACrB,EADR,CAEL0C,IAAI,CAAErB,CAAQ,CAACqB,IAFV,CAGLC,OAAO,CAAEtB,CAAQ,CAACsB,OAHb,CAILC,gBAAgB,CAAEvB,CAAQ,CAACwB,gBAJtB,CAKLC,QAAQ,CAAEzB,CAAQ,CAAC0B,QALd,CAMLC,WAAW,CAAE3B,CAAQ,CAAC4B,WANjB,CAOLC,OAAO,CAAE7B,CAAQ,CAAC8B,OAPb,CASLC,aAAa,CAAkB,IAAhB,GAAA/B,CAAQ,CAACrB,EATnB,CAULqD,MAAM,CAAEtB,CAAY,CAAC/B,EAVhB,CAWLsD,gBAAgB,CAAEvB,CAAY,CAACuB,gBAX1B,CAYLC,QAAQ,CAAExB,CAAY,CAACwB,QAZlB,CAaLC,SAAS,CAAEzB,CAAY,CAACyB,SAbnB,CAcLC,SAAS,CAAE1B,CAAY,CAAC0B,SAdnB,CALN,CAsBV,CAED,MAAO,KACV,CA5YH,CAsZMC,CAAwB,CAAG,SAASzD,CAAT,CAAgBoB,CAAhB,CAA0B,CACrD,GAAIe,CAAAA,CAAkB,CAAmB,IAAf,GAAAnC,CAAK,CAACyC,IAAN,EAAyC,IAAlB,GAAArB,CAAQ,CAACqB,IAA1D,CAEA,GAAIN,CAAJ,CAAwB,CACpB,MAAO,CACHC,IAAI,CAAE5F,CAAS,CAAC6F,kBAAV,CAA6BqB,IADhC,CAGHnB,YAAY,GAHT,CAIHC,OAAO,CAAE,CACLzC,EAAE,CAAEqB,CAAQ,CAACrB,EADR,CAEL0C,IAAI,CAAErB,CAAQ,CAACqB,IAFV,CAGLC,OAAO,CAAEtB,CAAQ,CAACsB,OAHb,CAILG,QAAQ,CAAEzB,CAAQ,CAAC0B,QAJd,CAKLC,WAAW,CAAE3B,CAAQ,CAAC4B,WALjB,CAOLG,aAAa,CAAkB,IAAhB,GAAA/B,CAAQ,CAACrB,EAPnB,CAQLsD,gBAAgB,GARX,CAJN,CAeV,CAED,MAAO,KACV,CA5aH,CAsbMM,CAA0B,CAAG,SAAS3D,CAAT,CAAgBoB,CAAhB,CAA0B,IACnDwC,CAAAA,CAAc,CAAG5D,CAAK,CAAC4C,gBAD4B,CAEnDiB,CAAc,CAAGzC,CAAQ,CAACwB,gBAFyB,CAIvD,GAAIgB,CAAc,EAAIC,CAAtB,CAAsC,CAClC,MAAO,CACHzB,IAAI,CAAE5F,CAAS,CAAC6F,kBAAV,CAA6ByB,MADhC,CAEHvB,YAAY,GAFT,CAGHC,OAAO,CAAE,CACLzC,EAAE,CAAEqB,CAAQ,CAACrB,EADR,CAEL0C,IAAI,CAAErB,CAAQ,CAACqB,IAFV,CAGLC,OAAO,CAAEtB,CAAQ,CAACsB,OAHb,CAILC,gBAAgB,CAAEvB,CAAQ,CAACwB,gBAJtB,CAKLC,QAAQ,CAAEzB,CAAQ,CAAC0B,QALd,CAMLC,WAAW,CAAE3B,CAAQ,CAAC4B,WANjB,CAOLC,OAAO,CAAE7B,CAAQ,CAAC8B,OAPb,CASLC,aAAa,CAAkB,IAAhB,GAAA/B,CAAQ,CAACrB,EATnB,CAHN,CAeV,CAhBD,IAgBO,CACH,MAAO,KACV,CACJ,CA7cH,CAsdMgE,CAAyB,CAAG,SAAS/D,CAAT,CAAgBoB,CAAhB,CAA0B,IAClD4C,CAAAA,CAAW,CAAGhE,CAAK,CAACtD,QAD8B,CAElDuH,CAAW,CAAG7C,CAAQ,CAAC1E,QAF2B,CAItD,GAAyB,CAArB,CAAAuH,CAAW,CAAC5F,MAAhB,CAA4B,CACxB,MAAO,KACV,CAED,GAAyB,CAArB,CAAA2F,CAAW,CAAC3F,MAAhB,CAA4B,CACxB,MAAO4F,CAAAA,CAAW,CAACA,CAAW,CAAC5F,MAAZ,CAAqB,CAAtB,CAAX,CAAoC0B,EAC9C,CAVqD,GAYlDmE,CAAAA,CAAc,CAAGF,CAAW,CAAChE,CAAK,CAACtD,QAAN,CAAe2B,MAAf,CAAwB,CAAzB,CAZsB,CAalD8F,CAAa,CAAGF,CAAW,CAACA,CAAW,CAAC5F,MAAZ,CAAqB,CAAtB,CAbuB,CAclD+F,CAAc,CAAGJ,CAAW,CAAC,CAAD,CAdsB,CAelDK,CAAa,CAAGJ,CAAW,CAAC,CAAD,CAfuB,CAiBtD,GAAIC,CAAc,CAACnE,EAAf,EAAqBoE,CAAa,CAACpE,EAAvC,CAA2C,CACvC,MAAOoE,CAAAA,CAAa,CAACpE,EACxB,CAFD,IAEO,IAAIqE,CAAc,CAACrE,EAAf,EAAqBsE,CAAa,CAACtE,EAAvC,CAA2C,CAC9C,MAAOqE,CAAAA,CAAc,CAACrE,EACzB,CAED,MAAO,KACV,CA9eH,CAufMuE,CAAwB,CAAG,SAAStE,CAAT,CAAgBoB,CAAhB,CAA0B,CACrD,GAAI,CAACpB,CAAK,CAACuE,cAAP,EAAyBnD,CAAQ,CAACmD,cAAtC,CAAsD,CAClD,QACH,CAFD,IAEO,IAAIvE,CAAK,CAACuE,cAAN,EAAwB,CAACnD,CAAQ,CAACmD,cAAtC,CAAsD,CACzD,QACH,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CA/fH,CAwgBMC,CAAyB,CAAG,SAASxE,CAAT,CAAgBoB,CAAhB,CAA0B,CACtD,GAAIpB,CAAK,CAACyE,sBAAN,GAAiCrD,CAAQ,CAACqD,sBAA9C,CAAsE,CAClE,MAAO,KACV,CAFD,IAEO,IAAI,CAACrD,CAAQ,CAACqD,sBAAV,EAAoCrD,CAAQ,CAACsD,eAAjD,CAAkE,CACrE,QACH,CAFM,IAEA,IAAItD,CAAQ,CAACqD,sBAAT,EAAmC,CAACrD,CAAQ,CAACsD,eAAjD,CAAkE,CACrE,QACH,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CAlhBH,CA2hBMC,CAAoB,CAAG,SAAS3E,CAAT,CAAgBoB,CAAhB,CAA0B,CACjD,GAAI,CAACpB,CAAK,CAAC0E,eAAP,EAA0BtD,CAAQ,CAACsD,eAAvC,CAAwD,CACpD,QACH,CAFD,IAEO,IAAI1E,CAAK,CAAC0E,eAAN,EAAyB,CAACtD,CAAQ,CAACsD,eAAvC,CAAwD,CAC3D,QACH,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CAniBH,CA4iBME,CAAoB,CAAG,SAAS5E,CAAT,CAAgBoB,CAAhB,CAA0B,CACjD,GAAI,CAACpB,CAAK,CAAC6E,eAAP,EAA0BzD,CAAQ,CAACyD,eAAvC,CAAwD,CACpD,QACH,CAFD,IAEO,IAAI7E,CAAK,CAAC6E,eAAN,EAAyB,CAACzD,CAAQ,CAACyD,eAAvC,CAAwD,CAC3D,QACH,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CApjBH,CA6jBMC,CAA0B,CAAG,SAAS9E,CAAT,CAAgBoB,CAAhB,CAA0B,CACvD,GAAI,CAACpB,CAAK,CAAC+E,qBAAP,EAAgC3D,CAAQ,CAAC2D,qBAA7C,CAAoE,CAChE,QACH,CAFD,IAEO,IAAI/E,CAAK,CAAC+E,qBAAN,EAA+B,CAAC3D,CAAQ,CAAC2D,qBAA7C,CAAoE,CACvE,QACH,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CArkBH,CA8kBMC,CAAqB,CAAG,SAAShF,CAAT,CAAgBoB,CAAhB,CAA0B,CAClD,GAAIA,CAAQ,CAAC6D,mBAAT,CAA6B5G,MAAjC,CAAyC,CAErC,GAAI6G,CAAAA,CAAM,CAAG9D,CAAQ,CAAC6D,mBAAT,CAA6B,CAA7B,CAAb,CACA,MAAO7D,CAAAA,CAAQ,CAAC+D,OAAT,CAAiBD,CAAjB,CACV,CAJD,IAIO,IAAIlF,CAAK,CAACiF,mBAAN,CAA0B5G,MAA9B,CAAsC,CACzC,QACH,CAED,MAAO,KACV,CAxlBH,CAimBM+G,CAAuB,CAAG,SAASpF,CAAT,CAAgBoB,CAAhB,CAA0B,CACpD,GAAIA,CAAQ,CAACiE,qBAAT,CAA+BhH,MAAnC,CAA2C,CAEvC,GAAI6G,CAAAA,CAAM,CAAG9D,CAAQ,CAACiE,qBAAT,CAA+B,CAA/B,CAAb,CACA,MAAOjE,CAAAA,CAAQ,CAAC+D,OAAT,CAAiBD,CAAjB,CACV,CAJD,IAIO,IAAIlF,CAAK,CAACqF,qBAAN,CAA4BhH,MAAhC,CAAwC,CAC3C,QACH,CAED,MAAO,KACV,CA3mBH,CAonBMiH,CAAsB,CAAG,SAAStF,CAAT,CAAgBoB,CAAhB,CAA0B,CACnD,GAAIA,CAAQ,CAACmE,oBAAT,CAA8BlH,MAAlC,CAA0C,CAEtC,GAAI6G,CAAAA,CAAM,CAAG9D,CAAQ,CAACmE,oBAAT,CAA8B,CAA9B,CAAb,CACA,MAAOnE,CAAAA,CAAQ,CAAC+D,OAAT,CAAiBD,CAAjB,CACV,CAJD,IAIO,IAAIlF,CAAK,CAACuF,oBAAN,CAA2BlH,MAA/B,CAAuC,CAC1C,QACH,CAED,MAAO,KACV,CA9nBH,CAuoBMmH,CAAyB,CAAG,SAASxF,CAAT,CAAgBoB,CAAhB,CAA0B,CACtD,GAAIA,CAAQ,CAACqE,uBAAT,CAAiCpH,MAArC,CAA6C,CAEzC,GAAI6G,CAAAA,CAAM,CAAG9D,CAAQ,CAACqE,uBAAT,CAAiC,CAAjC,CAAb,CACA,MAAOrE,CAAAA,CAAQ,CAAC+D,OAAT,CAAiBD,CAAjB,CACV,CAJD,IAIO,IAAIlF,CAAK,CAACyF,uBAAN,CAA8BpH,MAAlC,CAA0C,CAC7C,QACH,CAED,MAAO,KACV,CAjpBH,CA0pBMqH,CAAkC,CAAG,SAAS1F,CAAT,CAAgBoB,CAAhB,CAA0B,IAC3DuE,CAAAA,CAAe,CAAG3F,CAAK,CAAC4F,uBAAN,CAA8BvH,MADW,CAE3DwH,CAAe,CAAGzE,CAAQ,CAACwE,uBAAT,CAAiCvH,MAFQ,CAI/D,GAAIwH,CAAe,EAAI,CAACF,CAAxB,CAAyC,CACrC,MAAO,CACH3D,IAAI,GADD,CAEHI,IAAI,CAAEhB,CAAQ,CAACgB,IAFZ,CAGH0D,4BAA4B,CAAE1E,CAAQ,CAAC0E,4BAHpC,CAKV,CAND,IAMO,IAAIH,CAAe,EAAI,CAACE,CAAxB,CAAyC,CAC5C,MAAO,CACH7D,IAAI,GADD,CAGV,CAED,MAAO,KACV,CA3qBH,CAorBM+D,CAA8B,CAAG,SAAS/F,CAAT,CAAgBoB,CAAhB,CAA0B,CAC3D,GAAI,CAACpB,CAAK,CAACgG,yBAAP,EAAoC5E,CAAQ,CAAC4E,yBAAjD,CAA4E,CACxE,MAAO5E,CAAAA,CAAQ,CAACgB,IACnB,CAFD,IAEO,IAAIpC,CAAK,CAACgG,yBAAN,EAAmC,CAAC5E,CAAQ,CAAC4E,yBAAjD,CAA4E,CAC/E,QACH,CAED,MAAO,KACV,CA5rBH,CAqsBMrE,CAA0B,CAAG,SAAS3B,CAAT,CAAgBoB,CAAhB,CAA0B,IACnD6E,CAAAA,CAAc,CAAGjG,CAAK,CAACiG,cAD4B,CAEnDrE,CAAY,CAAGC,CAAqB,CAAC7B,CAAD,CAFe,CAGnD8B,CAAY,CAAGD,CAAqB,CAACT,CAAD,CAHe,CAInD8E,CAAmB,CAAG,CAACtE,CAAD,CAAgB,EAAhB,CAAqBA,CAAY,CAACuE,eAAb,CAA6BC,MAA7B,CAAoC,SAASC,CAAT,CAAkB,CACjG,MAAOA,CAAAA,CAAO,CAACC,eAAR,EAA2BL,CAA3B,EAA6CI,CAAO,CAACjD,MAAR,EAAkBxB,CAAY,CAAC7B,EACtF,CAF8C,CAJQ,CAOnDwG,CAAmB,CAAG,CAACzE,CAAD,CAAgB,EAAhB,CAAqBA,CAAY,CAACqE,eAAb,CAA6BC,MAA7B,CAAoC,SAASC,CAAT,CAAkB,CACjG,MAAOA,CAAAA,CAAO,CAACC,eAAR,EAA2BL,CAA3B,EAA6CI,CAAO,CAACjD,MAAR,EAAkBtB,CAAY,CAAC/B,EACtF,CAF8C,CAPQ,CAUnDyG,CAAU,CAAGN,CAAmB,CAAC7H,MAApB,CAA6B6H,CAAmB,CAAC,CAAD,CAAhD,CAAsD,IAVhB,CAWnDO,CAAU,CAAGF,CAAmB,CAAClI,MAApB,CAA6BkI,CAAmB,CAAC,CAAD,CAAhD,CAAsD,IAXhB,CAavD,GAAI,CAACC,CAAD,EAAeC,CAAnB,CAA+B,CAC3B,MAAO3E,CAAAA,CACV,CAFD,IAEO,IAAI0E,CAAU,EAAI,CAACC,CAAnB,CAA+B,CAClC,QACH,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CAztBH,CAkuBMC,CAAc,CAAG,SAAS1G,CAAT,CAAgBoB,CAAhB,CAA0B,IACvCQ,CAAAA,CAAY,CAAGC,CAAqB,CAAC7B,CAAD,CADG,CAEvC8B,CAAY,CAAGD,CAAqB,CAACT,CAAD,CAFG,CAI3C,GAAI,CAACQ,CAAD,EAAiB,CAACE,CAAtB,CAAoC,CAChC,MAAO,KACV,CAFD,IAEO,IAAI,CAACF,CAAD,EAAiBE,CAArB,CAAmC,CACtC,MAAOA,CAAAA,CAAY,CAACyB,SAAb,IAAgC,IAC1C,CAFM,IAEA,IAAI,CAACzB,CAAD,EAAiBF,CAArB,CAAmC,CACtC,MAAOA,CAAAA,CAAY,CAAC2B,SAAb,IAAiC,IAC3C,CAFM,IAEA,IAAI3B,CAAY,CAAC2B,SAAb,EAA0B,CAACzB,CAAY,CAACyB,SAA5C,CAAuD,CAC1D,QACH,CAFM,IAEA,IAAI,CAAC3B,CAAY,CAAC2B,SAAd,EAA2BzB,CAAY,CAACyB,SAA5C,CAAuD,CAC1D,QACH,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CAnvBH,CA4vBMoD,CAAgB,CAAG,SAAS3G,CAAT,CAAgBoB,CAAhB,CAA0B,IACzCwF,CAAAA,CAAc,CAAG5G,CAAK,CAACgD,WADkB,CAEzC6D,CAAc,CAAGzF,CAAQ,CAAC4B,WAFe,CAI7C,GAAiB,IAAb,GAAAhD,CAAK,CAACD,EAAN,EAAqC,IAAhB,GAAAqB,CAAQ,CAACrB,EAAlC,CAA+C,CAE3C,MAAO,KACV,CAHD,IAGO,IAAiB,IAAb,GAAAC,CAAK,CAACD,EAAN,EAAqC,IAAhB,GAAAqB,CAAQ,CAACrB,EAAlC,CAA+C,CAElD,MAAO,UACV,CAHM,IAGA,IAAiB,IAAb,GAAAC,CAAK,CAACD,EAAN,EAAqC,IAAhB,GAAAqB,CAAQ,CAACrB,EAAlC,CAA+C,CAGlD,MAAO,MACV,CAJM,IAIA,IAAI6G,CAAc,EAAIC,CAAtB,CAAsC,CAEzC,MAAO,KACV,CAHM,IAGA,IAAI,CAACD,CAAD,EAAmBC,CAAvB,CAAuC,CAC1C,MAAO,aACV,CAFM,IAEA,IAAID,CAAc,EAAI,CAACC,CAAvB,CAAuC,CAC1C,MAAO,UACV,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CApxBH,CA6xBMC,CAAY,CAAG,SAAS9G,CAAT,CAAgBoB,CAAhB,CAA0B,IACrC2F,CAAAA,CAAU,CAAG/G,CAAK,CAACkD,OADkB,CAErC8D,CAAU,CAAG5F,CAAQ,CAAC8B,OAFe,CAIzC,GAAiB,IAAb,GAAAlD,CAAK,CAACD,EAAN,EAAqC,IAAhB,GAAAqB,CAAQ,CAACrB,EAAlC,CAA+C,CAE3C,MAAO,KACV,CAHD,IAGO,IAAiB,IAAb,GAAAC,CAAK,CAACD,EAAN,EAAqC,IAAhB,GAAAqB,CAAQ,CAACrB,EAAlC,CAA+C,CAElD,MAAO,WACV,CAHM,IAGA,IAAiB,IAAb,GAAAC,CAAK,CAACD,EAAN,EAAqC,IAAhB,GAAAqB,CAAQ,CAACrB,EAAlC,CAA+C,CAGlD,MAAO,MACV,CAJM,IAIA,IAAIgH,CAAU,EAAIC,CAAlB,CAA8B,CAEjC,MAAO,KACV,CAHM,IAGA,IAAI,CAACD,CAAD,EAAeC,CAAnB,CAA+B,CAClC,MAAO,aACV,CAFM,IAEA,IAAID,CAAU,EAAI,CAACC,CAAnB,CAA+B,CAClC,MAAO,WACV,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CArzBH,CA+zBMC,CAAc,CAAG,SAASjH,CAAT,CAAgBoB,CAAhB,CAA0B,IACvC6E,CAAAA,CAAc,CAAGjG,CAAK,CAACiG,cADgB,CAEvCrE,CAAY,CAAGC,CAAqB,CAAC7B,CAAD,CAFG,CAGvC8B,CAAY,CAAGD,CAAqB,CAACT,CAAD,CAHG,CAIvC8F,CAAkB,CAAG,CAACtF,CAAD,CAAgB,EAAhB,CAAqBA,CAAY,CAACuE,eAAb,CAA6BC,MAA7B,CAAoC,SAASC,CAAT,CAAkB,CAChG,MAAQA,CAAAA,CAAO,CAACjD,MAAR,EAAkB6C,CAAlB,EAAoCI,CAAO,CAACC,eAAR,EAA2B1E,CAAY,CAAC7B,EAA7E,EACFsG,CAAO,CAACjD,MAAR,EAAkBxB,CAAY,CAAC7B,EAA/B,EAAqCsG,CAAO,CAACC,eAAR,EAA2BL,CACxE,CAH6C,CAJH,CAQvCkB,CAAkB,CAAG,CAACrF,CAAD,CAAgB,EAAhB,CAAqBA,CAAY,CAACqE,eAAb,CAA6BC,MAA7B,CAAoC,SAASC,CAAT,CAAkB,CAChG,MAAQA,CAAAA,CAAO,CAACjD,MAAR,EAAkB6C,CAAlB,EAAoCI,CAAO,CAACC,eAAR,EAA2BxE,CAAY,CAAC/B,EAA7E,EACFsG,CAAO,CAACjD,MAAR,EAAkBtB,CAAY,CAAC/B,EAA/B,EAAqCsG,CAAO,CAACC,eAAR,EAA2BL,CACxE,CAH6C,CARH,CAYvCmB,CAAqB,CAA+B,CAA5B,CAAAF,CAAkB,CAAC7I,MAZJ,CAavCgJ,CAAqB,CAA+B,CAA5B,CAAAF,CAAkB,CAAC9I,MAbJ,CAe3C,GAAI,CAACuD,CAAD,EAAiB,CAACE,CAAtB,CAAoC,CAChC,MAAO,KACV,CAFD,IAEO,IAAIsF,CAAqB,EAAIC,CAA7B,CAAoD,CACvD,MAAO,KACV,CAFM,IAEA,IAAI,CAACD,CAAD,EAA0BC,CAA1B,EAAmD,CAACvF,CAAY,CAAC0B,SAArE,CAAgF,CACnF,MAAO,iBACV,CAFM,IAEA,IAAI,CAAC5B,CAAD,EAAiBE,CAArB,CAAmC,CACtC,MAAOA,CAAAA,CAAY,CAAC0B,SAAb,CAAyB,SAAzB,CAAqC,IAC/C,CAFM,IAEA,IAAI,CAAC1B,CAAD,EAAiBF,CAArB,CAAmC,CACtC,MAAOA,CAAAA,CAAY,CAAC4B,SAAb,CAAyB,aAAzB,CAAyC,IACnD,CAFM,IAEA,IAAI5B,CAAY,CAAC4B,SAAb,EAA0B,CAAC1B,CAAY,CAAC0B,SAA5C,CAAuD,CAC1D,MAAO6D,CAAAA,CAAqB,CAAG,iBAAH,CAAuB,aACtD,CAFM,IAEA,IAAI,CAACzF,CAAY,CAAC4B,SAAd,EAA2B1B,CAAY,CAAC0B,SAA5C,CAAuD,CAC1D,MAAO,SACV,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CA/1BH,CAw2BM8D,CAA8B,CAAG,SAAStH,CAAT,CAAgBoB,CAAhB,CAA0B,CAC3D,GAAI,CAACpB,CAAK,CAACuH,oBAAP,EAA+BnG,CAAQ,CAACmG,oBAA5C,CAAkE,CAC9D,QACH,CAFD,IAEO,IAAIvH,CAAK,CAACuH,oBAAN,EAA8B,CAACnG,CAAQ,CAACmG,oBAA5C,CAAkE,CACrE,QACH,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CAh3BH,CAy3BMC,CAAe,CAAG,SAASxH,CAAT,CAAgBoB,CAAhB,CAA0B,IACxCqG,CAAAA,CAAsB,CAAqC,CAAlC,CAAAzH,CAAK,CAAC0H,kBAAN,CAAyBrJ,MADV,CAExCsJ,CAAsB,CAAwC,CAArC,CAAAvG,CAAQ,CAACsG,kBAAT,CAA4BrJ,MAFb,CAGxCuJ,CAA0B,CAAG5H,CAAK,CAACtD,QAAN,CAAe2B,MAAf,EAAyB+C,CAAQ,CAAC1E,QAAT,CAAkB2B,MAHhC,CAK5C,GAAI,CAACoJ,CAAD,EAA2BE,CAA/B,CAAuD,CACnD,QACH,CAFD,IAEO,IAAIF,CAAsB,EAAI,CAACE,CAA/B,CAAuD,CAC1D,QACH,CAFM,IAEA,IAAIF,CAAsB,EAAIG,CAA9B,CAA0D,CAC7D,QACH,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CAv4BH,CAg5BMC,CAAqB,CAAG,SAAS7H,CAAT,CAAgBoB,CAAhB,CAA0B,IAC9C0G,CAAAA,CAAmB,CAAG9H,CAAK,CAAC0H,kBADkB,CAE9CK,CAAmB,CAAG3G,CAAQ,CAACsG,kBAFe,CAIlD,GAAI5I,CAAY,CAACgJ,CAAD,CAAsBC,CAAtB,CAAhB,CAA4D,CACxD,MAAO,KACV,CAED,GAAI1G,CAAAA,CAAI,CAAG5D,CAAU,CAACqK,CAAD,CAAsBC,CAAtB,CAA2C,SAASrK,CAAT,CAAYC,CAAZ,CAAe,CAC3E,MAAOD,CAAAA,CAAC,EAAIC,CACf,CAFoB,CAArB,CAIA,MAAO,CACHqK,KAAK,CAAED,CAAmB,CAAC1J,MADxB,CAEHgC,GAAG,CAAEgB,CAAI,CAACvD,YAFP,CAGHsC,MAAM,CAAEiB,CAAI,CAACtD,YAHV,CAKV,CAj6BH,CA06BM8D,CAAqB,CAAG,SAAS7B,CAAT,CAAgB,CACxC,MAAO3C,CAAAA,MAAM,CAACC,IAAP,CAAY0C,CAAK,CAACmF,OAAlB,EAA2BtI,MAA3B,CAAkC,SAASC,CAAT,CAAgBoI,CAAhB,CAAwB,CAC7D,GAAIA,CAAM,EAAIlF,CAAK,CAACiG,cAAhB,EAAkC,CAACnJ,CAAvC,CAA8C,CAC1CA,CAAK,CAAGkD,CAAK,CAACmF,OAAN,CAAcD,CAAd,CACX,CAED,MAAOpI,CAAAA,CACV,CANM,CAMJ,IANI,CAOV,CAl7BH,CA27BMmL,CAAsB,CAAG,SAAShC,CAAT,CAAyBiC,CAAzB,CAA+B,CAExD,GAAIA,CAAI,CAACC,UAAT,CAAqB,CACjB,QACH,CAJuD,GAMpDC,CAAAA,CAAe,CAAGF,CAAI,CAAC/B,eAAL,CAAqBC,MAArB,CAA4B,SAASC,CAAT,CAAkB,CAChE,MAAOA,CAAAA,CAAO,CAACjD,MAAR,EAAkB6C,CAAlB,EAAoCI,CAAO,CAACC,eACtD,CAFqB,CANkC,CASpD+B,CAAqB,CAA4B,CAAzB,CAAAD,CAAe,CAAC/J,MATY,CAUxD,MAAO6J,CAAAA,CAAI,CAACI,eAAL,EAAwB,CAACJ,CAAI,CAAC1E,SAA9B,EAA2C,CAAC6E,CACtD,CAt8BH,CA+8BM5G,CAAsB,CAAG,SAASzB,CAAT,CAAgBoB,CAAhB,CAA0B,IAC/CQ,CAAAA,CAAY,CAAGC,CAAqB,CAAC7B,CAAD,CADW,CAE/C8B,CAAY,CAAGD,CAAqB,CAACT,CAAD,CAFW,CAG/CmH,CAAW,CAA2B,CAAxB,CAAAvI,CAAK,CAACtD,QAAN,CAAe2B,MAHkB,CAI/C4D,CAAW,CAA8B,CAA3B,CAAAb,CAAQ,CAAC1E,QAAT,CAAkB2B,MAJe,CAK/C4H,CAAc,CAAG7E,CAAQ,CAAC6E,cALqB,CAM/CuC,CAA0B,CAAG5G,CAAY,EAAIqG,CAAsB,CAAChC,CAAD,CAAiBrE,CAAjB,CANpB,CAO/C6G,CAA0B,CAAG3G,CAAY,EAAImG,CAAsB,CAAChC,CAAD,CAAiBnE,CAAjB,CAPpB,CAQ/C4G,CAAiB,CAAGpD,CAAsB,CAACtF,CAAD,CAAQoB,CAAR,CARK,CAYnD,GAAI,CAACpB,CAAK,CAACyE,sBAAP,EAAiC,CAACrD,CAAQ,CAACqD,sBAA/C,CAAuE,CACnE,MAAO,KACV,CAGD,GAAI,CAAC7C,CAAD,EAAiB,CAACE,CAAtB,CAAoC,CAChC,MAAO,KACV,CAGD,GAAI,CAACF,CAAD,EAAiB6G,CAArB,CAAiD,CAC7C,MAAO,CACHzG,IAAI,GADD,CAEHC,WAAW,CAAEA,CAFV,CAGHiG,IAAI,CAAEpG,CAHH,CAKV,CAKD,GAxByB,KAAA4G,CAwBrB,EAAsBD,CAA1B,CAAsD,CAClD,MAAO,CACHzG,IAAI,GADD,CAEHC,WAAW,CAAEA,CAFV,CAGHiG,IAAI,CAAEpG,CAHH,CAKV,CAGD,GAAI9B,CAAK,CAACyE,sBAAN,EAAgCrD,CAAQ,CAACqD,sBAA7C,CAAqE,CACjE,GAAI,CAAC+D,CAAD,EAA+BC,CAAnC,CAA+D,CAC3D,MAAO,CACHzG,IAAI,GADD,CAEHC,WAAW,CAAEA,CAFV,CAGHiG,IAAI,CAAEpG,CAHH,CAKV,CAED,GAAI0G,CAA0B,EAAI,CAACC,CAAnC,CAA+D,CAC3D,MAAO,CACHzG,IAAI,GADD,CAEHC,WAAW,CAAEA,CAFV,CAIV,CACJ,CAGD,GAAI,CAACjC,CAAK,CAACyE,sBAAP,EAAiCrD,CAAQ,CAACqD,sBAA9C,CAAsE,CAClE,GAAIgE,CAAJ,CAAgC,CAC5B,MAAO,CACHzG,IAAI,GADD,CAEHC,WAAW,CAAEA,CAFV,CAGHiG,IAAI,CAAEpG,CAHH,CAKV,CACJ,CAGD,GAAI9B,CAAK,CAACyE,sBAAN,EAAgC,CAACrD,CAAQ,CAACqD,sBAA9C,CAAsE,CAClE,GAAI+D,CAAJ,CAAgC,CAC5B,MAAO,CACHxG,IAAI,GADD,CAEHC,WAAW,CAAEsG,CAFV,CAIV,CACJ,CAED,MAAO,KACV,CAhiCH,CAyiCMI,CAAmB,CAAG,SAAS3I,CAAT,CAAgBoB,CAAhB,CAA0B,IAC5CQ,CAAAA,CAAY,CAAGC,CAAqB,CAAC7B,CAAD,CADQ,CAE5C8B,CAAY,CAAGD,CAAqB,CAACT,CAAD,CAFQ,CAIhD,GAAI,CAACQ,CAAD,EAAiB,CAACE,CAAtB,CAAoC,CAChC,MAAO,KACV,CAFD,IAEO,IAAIF,CAAY,EAAI,CAACE,CAArB,CAAmC,CACtC,MAAOF,CAAAA,CAAY,CAAC2B,SAAb,IAAiC,IAC3C,CAFM,IAEA,IAAI,CAAC3B,CAAD,EAAiBE,CAArB,CAAmC,CACtC,MAAOA,CAAAA,CAAY,CAACyB,SAAb,IAAgC,IAC1C,CAFM,IAEA,IAAI,CAAC3B,CAAY,CAAC2B,SAAd,EAA2BzB,CAAY,CAACyB,SAA5C,CAAuD,CAC1D,QACH,CAFM,IAEA,IAAI3B,CAAY,CAAC2B,SAAb,EAA0B,CAACzB,CAAY,CAACyB,SAA5C,CAAuD,CAC1D,QACH,CAED,MAAO,KACV,CA1jCH,CAmkCMqF,CAAoB,CAAG,SAAS5I,CAAT,CAAgBoB,CAAhB,CAA0B,IAC7CQ,CAAAA,CAAY,CAAGC,CAAqB,CAAC7B,CAAD,CADS,CAE7C8B,CAAY,CAAGD,CAAqB,CAACT,CAAD,CAFS,CAIjD,GAAIA,CAAQ,CAACgB,IAAT,EAAiB5F,CAAS,CAAC6F,kBAAV,CAA6BqB,IAAlD,CAAwD,CAEpD,MAAO,KACV,CAED,GAAI,CAAC9B,CAAD,EAAiB,CAACE,CAAtB,CAAoC,CAChC,MAAO,KACV,CAFD,IAEO,IAAIF,CAAY,EAAI,CAACE,CAArB,CAAmC,CACtC,MAAOF,CAAAA,CAAY,CAACuG,UAAb,CAA0B,IAA1B,GACV,CAFM,IAEA,IAAI,CAACvG,CAAD,EAAiBE,CAArB,CAAmC,CACtC,MAAOA,CAAAA,CAAY,CAACqG,UAAb,CAA0B,IAA1B,GACV,CAFM,IAEA,IAAI,CAACvG,CAAY,CAACuG,UAAd,EAA4BrG,CAAY,CAACqG,UAA7C,CAAyD,CAC5D,QACH,CAFM,IAEA,IAAIvG,CAAY,CAACuG,UAAb,EAA2B,CAACrG,CAAY,CAACqG,UAA7C,CAAyD,CAC5D,QACH,CAED,MAAO,KACV,CAzlCH,CAkmCMU,CAA2B,CAAG,SAAS7I,CAAT,CAAgBoB,CAAhB,CAA0B,IACpD0H,CAAAA,CAAoB,CAAGtE,CAAyB,CAACxE,CAAD,CAAQoB,CAAR,CADI,CAEpD2H,CAAU,CAAGvB,CAAe,CAACxH,CAAD,CAAQoB,CAAR,CAFwB,CAGpDI,CAAiB,CAAGC,CAAsB,CAACzB,CAAD,CAAQoB,CAAR,CAHU,CAIpD4H,CAAc,CAAGL,CAAmB,CAAC3I,CAAD,CAAQoB,CAAR,CAJgB,CAKpD6H,CAAe,CAAGL,CAAoB,CAAC5I,CAAD,CAAQoB,CAAR,CALc,CAMpD8H,CAAqB,CAAyB,IAAtB,GAAA1H,CAAiB,CAAYA,CAAiB,CAACQ,IAAlB,EAA0BR,CAAiB,CAACS,WAAxD,CAAsE,IAN3D,CAOpDkH,CAAS,CAAGtH,CAAqB,CAACT,CAAD,CAPmB,CAQpDgI,CAAmB,CAAG,SAASC,CAAT,CAAqBC,CAArB,CAAoC,CAC1D,GAAID,CAAJ,CAAgB,CACZ,MAAOC,CAAAA,CACV,CAFD,IAEO,IAAmB,IAAf,GAAAD,CAAU,EAAa,CAACA,CAA5B,CAAwC,CAC3C,GAAI,CAACF,CAAL,CAAgB,CACZ,MAAO,CAAC/G,IAAI,CAAE,SAAP,CACV,CAFD,IAEO,IAAI+G,CAAS,CAAC5F,SAAd,CAAyB,CAC5B,MAAO,CAACnB,IAAI,CAAE,SAAP,CACV,CAFM,IAEA,IAAIhB,CAAQ,CAAC1E,QAAT,CAAkB2B,MAAlB,EAA4B4J,CAAsB,CAAC7G,CAAQ,CAAC6E,cAAV,CAA0BkD,CAA1B,CAAtD,CAA4F,CAC/F,MAAO,CACH/G,IAAI,CAAE,aADH,CAEH8F,IAAI,CAAEiB,CAFH,CAIV,CALM,IAKA,IAAI,CAACA,CAAS,CAAChB,UAAX,EAA0BgB,CAAS,CAACb,eAAV,EAA6B,CAACa,CAAS,CAAC3F,SAAtE,CAAkF,CACrF,MAAO,CAACpB,IAAI,CAAE,mBAAP,CACV,CACJ,CAED,MAAO,KACV,CA3BuD,CA6BxD,GAC6B,IAAzB,GAAA0G,CAAoB,EACL,IAAf,GAAAC,CADA,EAEsB,IAAtB,GAAAvH,CAFA,EAGmB,IAAnB,GAAAwH,CAJJ,CAKE,CACE,MAAO,KACV,CAUD,OARIO,CAAAA,CAAM,CAAG,CACT,CAACT,CAAD,CAAuB,CAAC1G,IAAI,CAAE,aAAP,CAAvB,CADS,CAET,CAAC2G,CAAD,CAAa,CAAC3G,IAAI,CAAE,WAAP,CAAb,CAFS,CAGT,CAAC6G,CAAD,CAAkB,CAAC7G,IAAI,CAAE,mBAAP,CAAlB,CAHS,CAIT,CAAC4G,CAAD,CAAiB,CAAC5G,IAAI,CAAE,SAAP,CAAjB,CAJS,CAKT,CAAC8G,CAAD,CAAwB,CAAC9G,IAAI,CAAE,aAAP,CAAsB8F,IAAI,CAAEiB,CAA5B,CAAxB,CALS,CAQb,CAASvK,CAAC,CAAG,CAAb,CAAgBA,CAAC,CAAG2K,CAAM,CAAClL,MAA3B,CAAmCO,CAAC,EAApC,CAAwC,IAChCyK,CAAAA,CAAU,CAAGE,CAAM,CAAC3K,CAAD,CAAN,CAAU,CAAV,CADmB,CAEhC0K,CAAa,CAAGC,CAAM,CAAC3K,CAAD,CAAN,CAAU,CAAV,CAFgB,CAGhC4K,CAAM,CAAGJ,CAAmB,CAACC,CAAD,CAAaC,CAAb,CAHI,CAKpC,GAAe,IAAX,GAAAE,CAAJ,CAAqB,CACjB,MAAOA,CAAAA,CACV,CACJ,CAED,MAAO,CACHpH,IAAI,CAAE,SADH,CAGV,CA7pCH,CAsqCMqH,CAA0B,CAAG,SAASzJ,CAAT,CAAgBoB,CAAhB,CAA0B,IACnD0H,CAAAA,CAAoB,CAAGtE,CAAyB,CAACxE,CAAD,CAAQoB,CAAR,CADG,CAEnD2H,CAAU,CAAGvB,CAAe,CAACxH,CAAD,CAAQoB,CAAR,CAFuB,CAIvD,GAA6B,IAAzB,GAAA0H,CAAoB,EAA4B,IAAf,GAAAC,CAArC,CAA0D,CACtD,MAAO,KACV,CAED,GAAID,CAAJ,CAA0B,CACtB,MAAO,CAAC1G,IAAI,CAAE,aAAP,CACV,CAED,GAAI2G,CAAJ,CAAgB,CACZ,MAAO,CAAC3G,IAAI,CAAE,WAAP,CACV,CAED,MAAO,CACHA,IAAI,CAAE,SADH,CAGV,CAzrCH,CAmsCMsH,CAAU,CAAG,SAAS1J,CAAT,CAAgBoB,CAAhB,CAA0B,IACnCuI,CAAAA,CAAO,CAAG3J,CAAK,CAACoC,IADmB,CAEnCwH,CAAO,CAAGxI,CAAQ,CAACgB,IAFgB,CAGnCyH,CAAiB,CAAG7J,CAAK,CAACD,EAHS,CAInC+J,CAAiB,CAAG1I,CAAQ,CAACrB,EAJM,CAKnCgK,CAAY,CAAG1M,MAAM,CAACC,IAAP,CAAY0C,CAAK,CAACmF,OAAlB,CALoB,CAMnC6E,CAAY,CAAG3M,MAAM,CAACC,IAAP,CAAY8D,CAAQ,CAAC+D,OAArB,CANoB,CAQvC4E,CAAY,CAAChL,IAAb,GACAiL,CAAY,CAACjL,IAAb,GAEA,GAAIkL,CAAAA,CAAgB,CAAGF,CAAY,CAAC7K,KAAb,CAAmB,SAASa,CAAT,CAAa3B,CAAb,CAAoB,CAC1D,MAAO2B,CAAAA,CAAE,EAAIiK,CAAY,CAAC5L,CAAD,CAC5B,CAFsB,CAAvB,CAIA,GAAIuL,CAAO,EAAIC,CAAf,CAAwB,CAEpB,QACH,CAHD,IAGO,IAAIC,CAAiB,EAAI,CAACC,CAA1B,CAA6C,CAIhD,QACH,CALM,IAKA,IAAID,CAAiB,EAAIC,CAArB,EAA0CD,CAAiB,EAAIC,CAAnE,CAAsF,CAEzF,QACH,CAHM,IAGA,IAAI,CAACD,CAAD,EAAsB,CAACC,CAAvB,EAA4C,CAACG,CAAjD,CAAmE,CAKtE,QACH,CAED,MAAO,KACV,CAtuCH,CAivCMC,CAA4B,CAAG,SAASlK,CAAT,CAAgBoB,CAAhB,CAA0B,CACzD,GAAIpB,CAAK,CAACoC,IAAN,EAAchB,CAAQ,CAACgB,IAA3B,CAAiC,CAC7B,MAAQhB,CAAAA,CAAQ,CAACgB,IAAT,EAAiB5F,CAAS,CAAC6F,kBAAV,CAA6BqB,IACzD,CAED,MAAO,KACV,CAvvCH,CAiwCMyG,CAAuB,CAAG,SAASnK,CAAT,CAAgBoB,CAAhB,CAA0B,IAChD6E,CAAAA,CAAc,CAAG7E,CAAQ,CAAC6E,cADsB,CAEhDrE,CAAY,CAAGC,CAAqB,CAAC7B,CAAD,CAFY,CAGhD8B,CAAY,CAAGD,CAAqB,CAACT,CAAD,CAHY,CAIhDgJ,CAAe,CAAG,CAACxI,CAAD,CAAgB,EAAhB,CAAqBA,CAAY,CAACuE,eAAb,CAA6BC,MAA7B,CAAoC,SAASC,CAAT,CAAkB,CAC7F,MAAOA,CAAAA,CAAO,CAACjD,MAAR,EAAkB6C,CAC5B,CAF0C,CAJS,CAOhDoE,CAAe,CAAG,CAACvI,CAAD,CAAgB,EAAhB,CAAqBA,CAAY,CAACqE,eAAb,CAA6BC,MAA7B,CAAoC,SAASC,CAAT,CAAkB,CAC7F,MAAOA,CAAAA,CAAO,CAACjD,MAAR,EAAkB6C,CAC5B,CAF0C,CAPS,CAUhDO,CAAU,CAA4B,CAAzB,CAAA4D,CAAe,CAAC/L,MAVmB,CAWhDoI,CAAU,CAA4B,CAAzB,CAAA4D,CAAe,CAAChM,MAXmB,CAapD,GAAI,CAACmI,CAAD,EAAeC,CAAf,EAA6B,CAAC3E,CAAY,CAAC0B,SAA/C,CAA0D,CACtD,MAAO1B,CAAAA,CAAY,CAACwI,QACvB,CAFD,IAEO,IAAI1I,CAAY,EAAI,CAACA,CAAY,CAAC4B,SAA9B,EAA2CiD,CAA3C,EAAyD3E,CAAY,CAAC0B,SAA1E,CAAqF,CAExF,QACH,CAHM,IAGA,IAAIgD,CAAU,EAAI,CAACC,CAAnB,CAA+B,CAClC,QACH,CAFM,IAEA,CACH,MAAO,KACV,CACJ,CAxxCH,CAkyCM8D,CAAU,CAAG,SAASvK,CAAT,CAAgBoB,CAAhB,CAA0B,CACvC,GAAIoJ,CAAAA,CAAM,CAAG,CACTC,GAAG,CAAE,CACDC,KAAK,CAAEhB,CADN,CAEDiB,YAAY,CAAExJ,CAFb,CAGDyJ,eAAe,CAAE7G,CAHhB,CAIDQ,cAAc,CAAED,CAJf,CAKDwE,oBAAoB,CAAEtE,CALrB,CAMDE,eAAe,CAAEC,CANhB,CAODkG,6BAA6B,CAAEnF,CAP9B,CAQDqD,UAAU,CAAEvB,CARX,CASDsD,gBAAgB,CAAEjD,CATjB,CAUD7E,WAAW,CAAE2D,CAVZ,CAWDzD,OAAO,CAAE4D,CAXR,CAYDjC,eAAe,CAAED,CAZhB,CAaDG,qBAAqB,CAAED,CAbtB,CADI,CAAb,CAkBA0F,CAAM,CAAChO,CAAS,CAAC6F,kBAAV,CAA6BC,OAA9B,CAAN,CAA+C,CAC3CyI,MAAM,CAAExJ,CADmC,CAE3CyJ,MAAM,CAAEnC,CAFmC,CAG3CoC,gBAAgB,CAAEjG,CAHyB,CAI3CkG,kBAAkB,CAAE9F,CAJuB,CAK3CsD,iBAAiB,CAAEpD,CALwB,CAM3C6F,oBAAoB,CAAE3F,CANqB,CAO3C9D,qBAAqB,CAAEC,CAPoB,CAQ3CyJ,yBAAyB,CAAErF,CARgB,CAS3CsF,SAAS,CAAE3E,CATgC,CAU3C4E,SAAS,CAAErE,CAVgC,CAW3CM,oBAAoB,CAAED,CAXqB,CAY3C9F,iBAAiB,CAAEC,CAZwB,CAa3C8J,kBAAkB,CAAEpB,CAbuB,CAA/C,CAgBAK,CAAM,CAAChO,CAAS,CAAC6F,kBAAV,CAA6ByB,MAA9B,CAAN,CAA8C,CAC1CiH,MAAM,CAAEpH,CADkC,CAE1CqH,MAAM,CAAEvB,CAFkC,CAA9C,CAKAe,CAAM,CAAChO,CAAS,CAAC6F,kBAAV,CAA6BqB,IAA9B,CAAN,CAA4C,CACxCqH,MAAM,CAAEtH,CADgC,CAExCuH,MAAM,CAAEvB,CAFgC,CAGxC2B,yBAAyB,CAAErF,CAHa,CAIxCyF,uBAAuB,CAAEtB,CAJe,CAA5C,CAOA,GAAIuB,CAAAA,CAAW,CAAGnP,CAAC,CAACoP,MAAF,CAAS,EAAT,CAAalB,CAAM,CAACC,GAApB,CAAlB,CACA,GAAIrJ,CAAQ,CAACgB,IAAT,EAAiBhB,CAAQ,CAACgB,IAAT,GAAiBoI,CAAAA,CAAtC,CAA8C,CAE1CiB,CAAW,CAAGnP,CAAC,CAACoP,MAAF,CAASD,CAAT,CAAsBjB,CAAM,CAACpJ,CAAQ,CAACgB,IAAV,CAA5B,CACjB,CAED,MAAO/E,CAAAA,MAAM,CAACC,IAAP,CAAYmO,CAAZ,EAAyB5O,MAAzB,CAAgC,SAASkE,CAAT,CAAgBxB,CAAhB,CAAqB,IACpDoM,CAAAA,CAAS,CAAGF,CAAW,CAAClM,CAAD,CAD6B,CAEpDgB,CAAK,CAAGoL,CAAS,CAAC3L,CAAD,CAAQoB,CAAR,CAFmC,CAIxD,GAAc,IAAV,GAAAb,CAAJ,CAAoB,CAChBQ,CAAK,CAACxB,CAAD,CAAL,CAAagB,CAChB,CAED,MAAOQ,CAAAA,CACV,CATM,CASJ,EATI,CAUV,CAj2CH,CAm2CE,MAAO,CACHwJ,UAAU,CAAEA,CADT,CAGV,CAh3CK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module will take 2 view states from the message_drawer_view_conversation\n * module and generate a patch that can be given to the\n * message_drawer_view_conversation_renderer module to update the UI.\n *\n * This module should never modify either state. It's purely a read only\n * module.\n *\n * @module core_message/message_drawer_view_conversation_patcher\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/user_date',\n 'core_message/message_drawer_view_conversation_constants'\n],\nfunction(\n $,\n UserDate,\n Constants\n) {\n /**\n * Sort messages by day.\n *\n * @param {Array} messages The list of messages to sort.\n * @param {Number} midnight User's midnight timestamp.\n * @return {Array} messages sorted by day.\n */\n var sortMessagesByDay = function(messages, midnight) {\n var messagesByDay = messages.reduce(function(carry, message) {\n var timeCreated = message.timeCreated ? message.timeCreated : midnight;\n var dayTimestamp = UserDate.getUserMidnightForTimestamp(timeCreated, midnight);\n\n if (carry.hasOwnProperty(dayTimestamp)) {\n carry[dayTimestamp].push(message);\n } else {\n carry[dayTimestamp] = [message];\n }\n\n return carry;\n }, {});\n\n return Object.keys(messagesByDay).map(function(dayTimestamp) {\n return {\n timestamp: dayTimestamp,\n messages: messagesByDay[dayTimestamp]\n };\n });\n };\n\n /**\n * Diff 2 arrays using a match function\n *\n * @param {Array} a The first array.\n * @param {Array} b The second array.\n * @param {Function} matchFunction Function used for matching array items.\n * @return {Object} Object containing array items missing from a, array items missing from b\n * and matches\n */\n var diffArrays = function(a, b, matchFunction) {\n // Make copy of it.\n b = b.slice();\n var missingFromA = [];\n var missingFromB = [];\n var matches = [];\n\n a.forEach(function(current) {\n var found = false;\n var index = 0;\n\n for (; index < b.length; index++) {\n var next = b[index];\n\n if (matchFunction(current, next)) {\n found = true;\n matches.push({\n a: current,\n b: next\n });\n break;\n }\n }\n\n if (found) {\n // This day has been processed so removed it from the list.\n b.splice(index, 1);\n } else {\n // If we couldn't find it in the next messages then it means\n // it needs to be added.\n missingFromB.push(current);\n }\n });\n\n missingFromA = b;\n\n return {\n missingFromA: missingFromA,\n missingFromB: missingFromB,\n matches: matches\n };\n };\n\n /**\n * Find an element in a array based on a matching function.\n *\n * @param {array} array Array to search.\n * @param {Function} breakFunction Function to run on array item.\n * @return {*} The array item.\n */\n var findPositionInArray = function(array, breakFunction) {\n var before = null;\n\n for (var i = 0; i < array.length; i++) {\n var candidate = array[i];\n\n if (breakFunction(candidate)) {\n return candidate;\n }\n }\n\n return before;\n };\n\n /**\n * Check if 2 arrays are equal.\n *\n * @param {Array} a The first array.\n * @param {Array} b The second array.\n * @return {Boolean} Are arrays equal.\n */\n var isArrayEqual = function(a, b) {\n // Make shallow copies so that we don't mess with the array sorting.\n a = a.slice();\n b = b.slice();\n a.sort();\n b.sort();\n var aLength = a.length;\n var bLength = b.length;\n\n if (aLength < 1 && bLength < 1) {\n return true;\n }\n\n if (aLength != bLength) {\n return false;\n }\n\n return a.every(function(item, index) {\n return item == b[index];\n });\n };\n\n /**\n * Do a shallow check to see if two objects appear to be equal. This should\n * only be used for pretty basic objects.\n *\n * @param {Object} a First object to compare.\n * @param {Object} b Second object to compare\n * @return {Bool}\n */\n var isObjectEqual = function(a, b) {\n var aKeys = Object.keys(a);\n var bKeys = Object.keys(b);\n\n if (aKeys.length != bKeys.length) {\n return false;\n }\n\n return aKeys.every(function(key) {\n var aVal = a[key];\n var bVal = b[key];\n var aType = typeof aVal;\n var bType = typeof bVal;\n aType = (aVal === null) ? 'null' : aType;\n bType = (aVal === null) ? 'null' : bType;\n aType = (aType === 'object' && Array.isArray(aType)) ? 'array' : aType;\n bType = (bType === 'object' && Array.isArray(bType)) ? 'array' : bType;\n\n if (aType !== bType) {\n return false;\n }\n\n switch (aType) {\n case 'object':\n return isObjectEqual(aVal, bVal);\n case 'array':\n return isArrayEqual(aVal, bVal);\n default:\n return a[key] == b[key];\n }\n });\n };\n\n /**\n * Compare two messages to check if they are equal. This function only checks a subset\n * of the message properties which we know will change rather than all properties.\n *\n * @param {Object} a The first message\n * @param {Object} b The second message\n * @return {Bool}\n */\n var isMessageEqual = function(a, b) {\n return isObjectEqual(\n {\n id: a.id,\n state: a.sendState,\n text: a.text,\n timeCreated: a.timeCreated\n },\n {\n id: b.id,\n state: b.sendState,\n text: b.text,\n timeCreated: b.timeCreated\n }\n );\n };\n\n /**\n * Build a patch based on days.\n *\n * @param {Object} current Current list current items.\n * @param {Array} remove List of days to remove.\n * @param {Array} add List of days to add.\n * @return {Object} Patch with elements to add and remove.\n */\n var buildDaysPatch = function(current, remove, add) {\n return {\n remove: remove,\n add: add.map(function(day) {\n // Any days left over in the \"next\" list weren't in the \"current\" list\n // so they will need to be added.\n var before = findPositionInArray(current, function(candidate) {\n return day.timestamp < candidate.timestamp;\n });\n\n return {\n before: before,\n value: day\n };\n })\n };\n };\n\n /**\n * Build the messages patch for each day.\n *\n * @param {Array} matchingDays Array of old and new messages sorted by day.\n * @return {Object} patch.\n */\n var buildMessagesPatch = function(matchingDays) {\n var remove = [];\n var add = [];\n var update = [];\n\n // Iterate over the list of days and determine which messages in those days\n // have been changed.\n matchingDays.forEach(function(days) {\n var dayCurrent = days.a;\n var dayNext = days.b;\n // Find out which messages have changed in this day. This will return a list of messages\n // from the current state that couldn't be found in the next state and a list of messages in\n // the next state which couldn't be count in the current state.\n var messagesDiff = diffArrays(dayCurrent.messages, dayNext.messages, isMessageEqual);\n // Take the two arrays (list of messages changed from dayNext and list of messages changed\n // from dayCurrent) any work out which messages have been added/removed from the list and\n // which messages were just updated.\n var patch = diffArrays(\n // The messages from dayCurrent.message that weren't in dayNext.messages.\n messagesDiff.missingFromB,\n // The messages from dayNext.message that weren't in dayCurrent.messages.\n messagesDiff.missingFromA,\n function(a, b) {\n // This function is going to determine if the messages were\n // added/removed from either list or if they were simply an updated.\n //\n // If the IDs match or it was a state change (i.e. message with a temp\n // ID goes from pending to sent and receives an actual id) then they are\n // the same message which should be an update not an add/remove.\n return a.id == b.id || (a.sendState != b.sendState && a.timeAdded == b.timeAdded);\n }\n );\n\n // Any messages from the current state for this day which aren't in the next state\n // for this day (i.e. the user deleted the message) means we need to remove them from\n // the UI.\n remove = remove.concat(patch.missingFromB);\n\n // Any messages not in the current state for this day which are in the next state\n // for this day (i.e. it's a new message) means we need to add it to the UI so work\n // out where in the list of messages it should appear (it could be a new message the\n // user has sent or older messages loaded as part of the conversation scroll back).\n patch.missingFromA.forEach(function(message) {\n // By default a null value for before will render the message at the bottom of\n // the message UI (i.e. it's the newest message).\n var before = null;\n\n if (message.timeCreated) {\n // If this message has a time created then find where it sits in the list of\n // message to insert it into the correct position.\n before = findPositionInArray(dayCurrent.messages, function(candidate) {\n if (message.timeCreated == candidate.timeCreated) {\n return message.id < candidate.id;\n } else {\n return message.timeCreated < candidate.timeCreated;\n }\n });\n }\n\n add.push({\n before: before,\n value: message,\n day: dayCurrent\n });\n });\n\n // Any message that appears in both the current state for this day and the next state\n // for this day means something in the message was updated.\n update = update.concat(patch.matches.map(function(message) {\n return {\n before: message.a,\n after: message.b\n };\n }));\n });\n\n return {\n add: add,\n remove: remove,\n update: update\n };\n };\n\n /**\n * Build a patch for this conversation.\n *\n * @param {Object} state, The current state of this conversation.\n * @param {Object} newState, The new state of this conversation.\n * @return {Object} Patch with days and messsages for each day.\n */\n var buildConversationPatch = function(state, newState) {\n var diff = diffArrays(state.messages, newState.messages, isMessageEqual);\n\n if (diff.missingFromA.length || diff.missingFromB.length) {\n // Some messages have changed so let's work out which ones by sorting\n // them into their respective days.\n var current = sortMessagesByDay(state.messages, state.midnight);\n var next = sortMessagesByDay(newState.messages, newState.midnight);\n // This diffs the arrays to work out if there are any missing days that need\n // to be added (i.e. we've got some new messages on a new day) or if there\n // are any days that need to be deleted (i.e. the user has deleted some old messages).\n var daysDiff = diffArrays(current, next, function(dayCurrent, dayNext) {\n return dayCurrent.timestamp == dayNext.timestamp;\n });\n\n return {\n // Handle adding or removing whole days.\n days: buildDaysPatch(current, daysDiff.missingFromB, daysDiff.missingFromA),\n // Handle updating messages that don't require adding/removing a whole day.\n messages: buildMessagesPatch(daysDiff.matches)\n };\n } else {\n return null;\n }\n };\n\n /**\n * Build a patch for the header of this conversation. Check if this conversation\n * is a group conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildHeaderPatchTypePrivate = function(state, newState) {\n var requireAddContact = buildRequireAddContact(state, newState);\n var confirmContactRequest = buildConfirmContactRequest(state, newState);\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var requiresAddContact = requireAddContact && requireAddContact.show && !requireAddContact.hasMessages;\n var requiredAddContact = requireAddContact && !requireAddContact.show;\n // Render the header once we've got a user.\n var shouldRenderHeader = !oldOtherUser && newOtherUser;\n // We should also re-render the header if the other user requires\n // being added as a contact or if they did but no longer do.\n shouldRenderHeader = shouldRenderHeader || requiresAddContact || requiredAddContact;\n // Finally, we should re-render if the other user has sent this user\n // a contact request that is waiting for approval or if it's been approved/declined.\n shouldRenderHeader = shouldRenderHeader || confirmContactRequest !== null;\n\n if (shouldRenderHeader) {\n return {\n type: Constants.CONVERSATION_TYPES.PRIVATE,\n // We can show controls if the other user doesn't require add contact\n // and we aren't waiting for this user to respond to a contact request.\n showControls: !requiresAddContact && !confirmContactRequest,\n context: {\n id: newState.id,\n name: newState.name,\n subname: newState.subname,\n totalmembercount: newState.totalMemberCount,\n imageurl: newState.imageUrl,\n isfavourite: newState.isFavourite,\n ismuted: newState.isMuted,\n // Don't show favouriting if we don't have a conversation.\n showfavourite: newState.id !== null,\n userid: newOtherUser.id,\n showonlinestatus: newOtherUser.showonlinestatus,\n isonline: newOtherUser.isonline,\n isblocked: newOtherUser.isblocked,\n iscontact: newOtherUser.iscontact\n }\n };\n }\n\n return null;\n };\n\n /**\n * Build a patch for the header of this conversation. Check if this conversation\n * is a group conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildHeaderPatchTypeSelf = function(state, newState) {\n var shouldRenderHeader = (state.name === null && newState.name !== null);\n\n if (shouldRenderHeader) {\n return {\n type: Constants.CONVERSATION_TYPES.SELF,\n // Don't display the controls for the self-conversations.\n showControls: false,\n context: {\n id: newState.id,\n name: newState.name,\n subname: newState.subname,\n imageurl: newState.imageUrl,\n isfavourite: newState.isFavourite,\n // Don't show favouriting if we don't have a conversation.\n showfavourite: newState.id !== null,\n showonlinestatus: true,\n }\n };\n }\n\n return null;\n };\n\n /**\n * Build a patch for the header of this conversation. Check if this conversation\n * is a group conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildHeaderPatchTypePublic = function(state, newState) {\n var oldMemberCount = state.totalMemberCount;\n var newMemberCount = newState.totalMemberCount;\n\n if (oldMemberCount != newMemberCount) {\n return {\n type: Constants.CONVERSATION_TYPES.PUBLIC,\n showControls: true,\n context: {\n id: newState.id,\n name: newState.name,\n subname: newState.subname,\n totalmembercount: newState.totalMemberCount,\n imageurl: newState.imageUrl,\n isfavourite: newState.isFavourite,\n ismuted: newState.isMuted,\n // Don't show favouriting if we don't have a conversation.\n showfavourite: newState.id !== null\n }\n };\n } else {\n return null;\n }\n };\n\n /**\n * Find the newest or oldest message.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Number} Oldest or newest message id.\n */\n var buildScrollToMessagePatch = function(state, newState) {\n var oldMessages = state.messages;\n var newMessages = newState.messages;\n\n if (newMessages.length < 1) {\n return null;\n }\n\n if (oldMessages.length < 1) {\n return newMessages[newMessages.length - 1].id;\n }\n\n var previousNewest = oldMessages[state.messages.length - 1];\n var currentNewest = newMessages[newMessages.length - 1];\n var previousOldest = oldMessages[0];\n var currentOldest = newMessages[0];\n\n if (previousNewest.id != currentNewest.id) {\n return currentNewest.id;\n } else if (previousOldest.id != currentOldest.id) {\n return previousOldest.id;\n }\n\n return null;\n };\n\n /**\n * Check if members should be loaded.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingMembersPatch = function(state, newState) {\n if (!state.loadingMembers && newState.loadingMembers) {\n return true;\n } else if (state.loadingMembers && !newState.loadingMembers) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if the messages are being loaded for the first time.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingFirstMessages = function(state, newState) {\n if (state.hasTriedToLoadMessages === newState.hasTriedToLoadMessages) {\n return null;\n } else if (!newState.hasTriedToLoadMessages && newState.loadingMessages) {\n return true;\n } else if (newState.hasTriedToLoadMessages && !newState.loadingMessages) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if the messages are still being loaded\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingMessages = function(state, newState) {\n if (!state.loadingMessages && newState.loadingMessages) {\n return true;\n } else if (state.loadingMessages && !newState.loadingMessages) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Determine if we should show the emoji picker.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildShowEmojiPicker = function(state, newState) {\n if (!state.showEmojiPicker && newState.showEmojiPicker) {\n return true;\n } else if (state.showEmojiPicker && !newState.showEmojiPicker) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Determine if we should show the emoji auto complete.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildShowEmojiAutoComplete = function(state, newState) {\n if (!state.showEmojiAutoComplete && newState.showEmojiAutoComplete) {\n return true;\n } else if (state.showEmojiAutoComplete && !newState.showEmojiAutoComplete) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Get the user Object of user to be blocked if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmBlockUser = function(state, newState) {\n if (newState.pendingBlockUserIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingBlockUserIds[0];\n return newState.members[userId];\n } else if (state.pendingBlockUserIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Get the user Object of user to be unblocked if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmUnblockUser = function(state, newState) {\n if (newState.pendingUnblockUserIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingUnblockUserIds[0];\n return newState.members[userId];\n } else if (state.pendingUnblockUserIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Get the user Object of user to be added as contact if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmAddContact = function(state, newState) {\n if (newState.pendingAddContactIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingAddContactIds[0];\n return newState.members[userId];\n } else if (state.pendingAddContactIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Get the user Object of user to be removed as contact if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmRemoveContact = function(state, newState) {\n if (newState.pendingRemoveContactIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingRemoveContactIds[0];\n return newState.members[userId];\n } else if (state.pendingRemoveContactIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Check if there are any messages to be deleted.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Null} The conversation type and if the user can delete the messages for all users.\n */\n var buildConfirmDeleteSelectedMessages = function(state, newState) {\n var oldPendingCount = state.pendingDeleteMessageIds.length;\n var newPendingCount = newState.pendingDeleteMessageIds.length;\n\n if (newPendingCount && !oldPendingCount) {\n return {\n show: true,\n type: newState.type,\n canDeleteMessagesForAllUsers: newState.canDeleteMessagesForAllUsers\n };\n } else if (oldPendingCount && !newPendingCount) {\n return {\n show: false\n };\n }\n\n return null;\n };\n\n /**\n * Check if there is a conversation to be deleted.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {int|Null} The conversation type to be deleted.\n */\n var buildConfirmDeleteConversation = function(state, newState) {\n if (!state.pendingDeleteConversation && newState.pendingDeleteConversation) {\n return newState.type;\n } else if (state.pendingDeleteConversation && !newState.pendingDeleteConversation) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Check if there is a pending contact request to accept or decline.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildConfirmContactRequest = function(state, newState) {\n var loggedInUserId = state.loggedInUserId;\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var oldReceivedRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId && request.userid == oldOtherUser.id;\n });\n var newReceivedRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId && request.userid == newOtherUser.id;\n });\n var oldRequest = oldReceivedRequests.length ? oldReceivedRequests[0] : null;\n var newRequest = newReceivedRequests.length ? newReceivedRequests[0] : null;\n\n if (!oldRequest && newRequest) {\n return newOtherUser;\n } else if (oldRequest && !newRequest) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes in blocked users.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildIsBlocked = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.isblocked ? true : null;\n } else if (!newOtherUser && oldOtherUser) {\n return oldOtherUser.isblocked ? false : null;\n } else if (oldOtherUser.isblocked && !newOtherUser.isblocked) {\n return false;\n } else if (!oldOtherUser.isblocked && newOtherUser.isblocked) {\n return true;\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes the conversation favourite state.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildIsFavourite = function(state, newState) {\n var oldIsFavourite = state.isFavourite;\n var newIsFavourite = newState.isFavourite;\n\n if (state.id === null && newState.id === null) {\n // The conversation isn't yet created so don't change anything.\n return null;\n } else if (state.id === null && newState.id !== null) {\n // The conversation was created so we can show the add favourite button.\n return 'show-add';\n } else if (state.id !== null && newState.id === null) {\n // We're changing from a created conversation to a new conversation so hide\n // the favouriting functionality for now.\n return 'hide';\n } else if (oldIsFavourite == newIsFavourite) {\n // No change.\n return null;\n } else if (!oldIsFavourite && newIsFavourite) {\n return 'show-remove';\n } else if (oldIsFavourite && !newIsFavourite) {\n return 'show-add';\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes the conversation muted state.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {string|null}\n */\n var buildIsMuted = function(state, newState) {\n var oldIsMuted = state.isMuted;\n var newIsMuted = newState.isMuted;\n\n if (state.id === null && newState.id === null) {\n // The conversation isn't yet created so don't change anything.\n return null;\n } else if (state.id === null && newState.id !== null) {\n // The conversation was created so we can show the mute button.\n return 'show-mute';\n } else if (state.id !== null && newState.id === null) {\n // We're changing from a created conversation to a new conversation so hide\n // the muting functionality for now.\n return 'hide';\n } else if (oldIsMuted == newIsMuted) {\n // No change.\n return null;\n } else if (!oldIsMuted && newIsMuted) {\n return 'show-unmute';\n } else if (oldIsMuted && !newIsMuted) {\n return 'show-mute';\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes in the contact status of the current user\n * and other user.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildIsContact = function(state, newState) {\n var loggedInUserId = state.loggedInUserId;\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var oldContactRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) {\n return (request.userid == loggedInUserId && request.requesteduserid == oldOtherUser.id) ||\n (request.userid == oldOtherUser.id && request.requesteduserid == loggedInUserId);\n });\n var newContactRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) {\n return (request.userid == loggedInUserId && request.requesteduserid == newOtherUser.id) ||\n (request.userid == newOtherUser.id && request.requesteduserid == loggedInUserId);\n });\n var oldHasContactRequests = oldContactRequests.length > 0;\n var newHasContactRequests = newContactRequests.length > 0;\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (oldHasContactRequests && newHasContactRequests) {\n return null;\n } else if (!oldHasContactRequests && newHasContactRequests && !newOtherUser.iscontact) {\n return 'pending-contact';\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.iscontact ? 'contact' : null;\n } else if (!newOtherUser && oldOtherUser) {\n return oldOtherUser.iscontact ? 'non-contact' : null;\n } else if (oldOtherUser.iscontact && !newOtherUser.iscontact) {\n return newHasContactRequests ? 'pending-contact' : 'non-contact';\n } else if (!oldOtherUser.iscontact && newOtherUser.iscontact) {\n return 'contact';\n } else {\n return null;\n }\n };\n\n /**\n * Check if a confirm action is active.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingConfirmationAction = function(state, newState) {\n if (!state.loadingConfirmAction && newState.loadingConfirmAction) {\n return true;\n } else if (state.loadingConfirmAction && !newState.loadingConfirmAction) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if a edit mode is active.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildInEditMode = function(state, newState) {\n var oldHasSelectedMessages = state.selectedMessageIds.length > 0;\n var newHasSelectedMessages = newState.selectedMessageIds.length > 0;\n var numberOfMessagesHasChanged = state.messages.length != newState.messages.length;\n\n if (!oldHasSelectedMessages && newHasSelectedMessages) {\n return true;\n } else if (oldHasSelectedMessages && !newHasSelectedMessages) {\n return false;\n } else if (oldHasSelectedMessages && numberOfMessagesHasChanged) {\n return true;\n } else {\n return null;\n }\n };\n\n /**\n * Build a patch for the messages selected.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildSelectedMessages = function(state, newState) {\n var oldSelectedMessages = state.selectedMessageIds;\n var newSelectedMessages = newState.selectedMessageIds;\n\n if (isArrayEqual(oldSelectedMessages, newSelectedMessages)) {\n return null;\n }\n\n var diff = diffArrays(oldSelectedMessages, newSelectedMessages, function(a, b) {\n return a == b;\n });\n\n return {\n count: newSelectedMessages.length,\n add: diff.missingFromA,\n remove: diff.missingFromB\n };\n };\n\n /**\n * Get a list of users from the state that are not the logged in user. Use to find group\n * message members or the other user in a conversation.\n *\n * @param {Object} state State\n * @return {Array} List of users.\n */\n var getOtherUserFromState = function(state) {\n return Object.keys(state.members).reduce(function(carry, userId) {\n if (userId != state.loggedInUserId && !carry) {\n carry = state.members[userId];\n }\n\n return carry;\n }, null);\n };\n\n /**\n * Check if the given user requires a contact request from the logged in user.\n *\n * @param {Integer} loggedInUserId The logged in user id\n * @param {Object} user User record\n * @return {Bool}\n */\n var requiresContactRequest = function(loggedInUserId, user) {\n // If a user can message then no contact request is required.\n if (user.canmessage) {\n return false;\n }\n\n var contactRequests = user.contactrequests.filter(function(request) {\n return request.userid == loggedInUserId || request.requesteduserid;\n });\n var hasSentContactRequest = contactRequests.length > 0;\n return user.requirescontact && !user.iscontact && !hasSentContactRequest;\n };\n\n /**\n * Check if other users are required to be added as contact.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} Object controlling the required to add contact dialog variables.\n */\n var buildRequireAddContact = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var hadMessages = state.messages.length > 0;\n var hasMessages = newState.messages.length > 0;\n var loggedInUserId = newState.loggedInUserId;\n var prevRequiresContactRequest = oldOtherUser && requiresContactRequest(loggedInUserId, oldOtherUser);\n var nextRequiresContactRequest = newOtherUser && requiresContactRequest(loggedInUserId, newOtherUser);\n var confirmAddContact = buildConfirmAddContact(state, newState);\n var finishedAddContact = confirmAddContact === false;\n\n // Still doing first load.\n if (!state.hasTriedToLoadMessages && !newState.hasTriedToLoadMessages) {\n return null;\n }\n\n // No users yet.\n if (!oldOtherUser && !newOtherUser) {\n return null;\n }\n\n // We've loaded a new user and they require a contact request.\n if (!oldOtherUser && nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n\n // The logged in user has completed the confirm contact request dialogue\n // but the other user still requires a contact request which means the logged\n // in user either declined the confirmation or it failed.\n if (finishedAddContact && nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n\n // Everything is loaded.\n if (state.hasTriedToLoadMessages && newState.hasTriedToLoadMessages) {\n if (!prevRequiresContactRequest && nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n\n if (prevRequiresContactRequest && !nextRequiresContactRequest) {\n return {\n show: false,\n hasMessages: hasMessages\n };\n }\n }\n\n // First load just completed.\n if (!state.hasTriedToLoadMessages && newState.hasTriedToLoadMessages) {\n if (nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n }\n\n // Being reset.\n if (state.hasTriedToLoadMessages && !newState.hasTriedToLoadMessages) {\n if (prevRequiresContactRequest) {\n return {\n show: false,\n hasMessages: hadMessages\n };\n }\n }\n\n return null;\n };\n\n /**\n * Check if other users are required to be unblocked.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildRequireUnblock = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (oldOtherUser && !newOtherUser) {\n return oldOtherUser.isblocked ? false : null;\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.isblocked ? true : null;\n } else if (!oldOtherUser.isblocked && newOtherUser.isblocked) {\n return true;\n } else if (oldOtherUser.isblocked && !newOtherUser.isblocked) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Check if other users can be messaged.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildUnableToMessage = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n\n if (newState.type == Constants.CONVERSATION_TYPES.SELF) {\n // Users always can send message themselves on self-conversations.\n return null;\n }\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (oldOtherUser && !newOtherUser) {\n return oldOtherUser.canmessage ? null : true;\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.canmessage ? null : true;\n } else if (!oldOtherUser.canmessage && newOtherUser.canmessage) {\n return false;\n } else if (oldOtherUser.canmessage && !newOtherUser.canmessage) {\n return true;\n }\n\n return null;\n };\n\n /**\n * Build patch for footer information for a private conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} containing footer state type.\n */\n var buildFooterPatchTypePrivate = function(state, newState) {\n var loadingFirstMessages = buildLoadingFirstMessages(state, newState);\n var inEditMode = buildInEditMode(state, newState);\n var requireAddContact = buildRequireAddContact(state, newState);\n var requireUnblock = buildRequireUnblock(state, newState);\n var unableToMessage = buildUnableToMessage(state, newState);\n var showRequireAddContact = requireAddContact !== null ? requireAddContact.show && requireAddContact.hasMessages : null;\n var otherUser = getOtherUserFromState(newState);\n var generateReturnValue = function(checkValue, successReturn) {\n if (checkValue) {\n return successReturn;\n } else if (checkValue !== null && !checkValue) {\n if (!otherUser) {\n return {type: 'content'};\n } else if (otherUser.isblocked) {\n return {type: 'unblock'};\n } else if (newState.messages.length && requiresContactRequest(newState.loggedInUserId, otherUser)) {\n return {\n type: 'add-contact',\n user: otherUser\n };\n } else if (!otherUser.canmessage && (otherUser.requirescontact && !otherUser.iscontact)) {\n return {type: 'unable-to-message'};\n }\n }\n\n return null;\n };\n\n if (\n loadingFirstMessages === null &&\n inEditMode === null &&\n requireAddContact === null &&\n requireUnblock === null\n ) {\n return null;\n }\n\n var checks = [\n [loadingFirstMessages, {type: 'placeholder'}],\n [inEditMode, {type: 'edit-mode'}],\n [unableToMessage, {type: 'unable-to-message'}],\n [requireUnblock, {type: 'unblock'}],\n [showRequireAddContact, {type: 'add-contact', user: otherUser}]\n ];\n\n for (var i = 0; i < checks.length; i++) {\n var checkValue = checks[i][0];\n var successReturn = checks[i][1];\n var result = generateReturnValue(checkValue, successReturn);\n\n if (result !== null) {\n return result;\n }\n }\n\n return {\n type: 'content'\n };\n };\n\n /**\n * Build patch for footer information for a public conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} containing footer state type.\n */\n var buildFooterPatchTypePublic = function(state, newState) {\n var loadingFirstMessages = buildLoadingFirstMessages(state, newState);\n var inEditMode = buildInEditMode(state, newState);\n\n if (loadingFirstMessages === null && inEditMode === null) {\n return null;\n }\n\n if (loadingFirstMessages) {\n return {type: 'placeholder'};\n }\n\n if (inEditMode) {\n return {type: 'edit-mode'};\n }\n\n return {\n type: 'content'\n };\n };\n\n /**\n * Check if we're viewing a different conversation. If so then we need to\n * reset the UI.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {bool|null} If a reset needs to occur\n */\n var buildReset = function(state, newState) {\n var oldType = state.type;\n var newType = newState.type;\n var oldConversationId = state.id;\n var newConversationId = newState.id;\n var oldMemberIds = Object.keys(state.members);\n var newMemberIds = Object.keys(newState.members);\n\n oldMemberIds.sort();\n newMemberIds.sort();\n\n var membersUnchanged = oldMemberIds.every(function(id, index) {\n return id == newMemberIds[index];\n });\n\n if (oldType != newType) {\n // If we've changed conversation type then we need to reset.\n return true;\n } else if (oldConversationId && !newConversationId) {\n // We previously had a conversation id but no longer do. This likely means\n // the user is viewing the conversation with someone they've never spoken to\n // before.\n return true;\n } else if (oldConversationId && newConversationId && oldConversationId != newConversationId) {\n // If we had a conversation id and it's changed then we need to reset.\n return true;\n } else if (!oldConversationId && !newConversationId && !membersUnchanged) {\n // If we never had a conversation id but the members of the conversation have\n // changed then we need to reset. This can happen if the user goes from viewing\n // a user they've never had a conversation with to viewing a different user that\n // they've never had a conversation with.\n return true;\n }\n\n return null;\n };\n\n /**\n * We should show this message always, for all the self-conversations.\n *\n * The message should be hidden when it's not a self-conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {bool}\n */\n var buildSelfConversationMessage = function(state, newState) {\n if (state.type != newState.type) {\n return (newState.type == Constants.CONVERSATION_TYPES.SELF);\n }\n\n return null;\n };\n\n /**\n * We should show the contact request sent message if the user just sent\n * a contact request to the other user\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {string|false|null}\n */\n var buildContactRequestSent = function(state, newState) {\n var loggedInUserId = newState.loggedInUserId;\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var oldSentRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) {\n return request.userid == loggedInUserId;\n });\n var newSentRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) {\n return request.userid == loggedInUserId;\n });\n var oldRequest = oldSentRequests.length > 0;\n var newRequest = newSentRequests.length > 0;\n\n if (!oldRequest && newRequest && !newOtherUser.iscontact) {\n return newOtherUser.fullname;\n } else if (oldOtherUser && !oldOtherUser.iscontact && newRequest && newOtherUser.iscontact) {\n // Contact request accepted.\n return false;\n } else if (oldRequest && !newRequest) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Build the full patch comparing the current state and the new state. This patch is used by\n * the conversation renderer to render the UI on any update.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} Patch containing all information changed.\n */\n var buildPatch = function(state, newState) {\n var config = {\n all: {\n reset: buildReset,\n conversation: buildConversationPatch,\n scrollToMessage: buildScrollToMessagePatch,\n loadingMembers: buildLoadingMembersPatch,\n loadingFirstMessages: buildLoadingFirstMessages,\n loadingMessages: buildLoadingMessages,\n confirmDeleteSelectedMessages: buildConfirmDeleteSelectedMessages,\n inEditMode: buildInEditMode,\n selectedMessages: buildSelectedMessages,\n isFavourite: buildIsFavourite,\n isMuted: buildIsMuted,\n showEmojiPicker: buildShowEmojiPicker,\n showEmojiAutoComplete: buildShowEmojiAutoComplete\n }\n };\n // These build functions are only applicable to private conversations.\n config[Constants.CONVERSATION_TYPES.PRIVATE] = {\n header: buildHeaderPatchTypePrivate,\n footer: buildFooterPatchTypePrivate,\n confirmBlockUser: buildConfirmBlockUser,\n confirmUnblockUser: buildConfirmUnblockUser,\n confirmAddContact: buildConfirmAddContact,\n confirmRemoveContact: buildConfirmRemoveContact,\n confirmContactRequest: buildConfirmContactRequest,\n confirmDeleteConversation: buildConfirmDeleteConversation,\n isBlocked: buildIsBlocked,\n isContact: buildIsContact,\n loadingConfirmAction: buildLoadingConfirmationAction,\n requireAddContact: buildRequireAddContact,\n contactRequestSent: buildContactRequestSent\n };\n // These build functions are only applicable to public (group) conversations.\n config[Constants.CONVERSATION_TYPES.PUBLIC] = {\n header: buildHeaderPatchTypePublic,\n footer: buildFooterPatchTypePublic,\n };\n // These build functions are only applicable to self-conversations.\n config[Constants.CONVERSATION_TYPES.SELF] = {\n header: buildHeaderPatchTypeSelf,\n footer: buildFooterPatchTypePublic,\n confirmDeleteConversation: buildConfirmDeleteConversation,\n selfConversationMessage: buildSelfConversationMessage\n };\n\n var patchConfig = $.extend({}, config.all);\n if (newState.type && newState.type in config) {\n // Add the type specific builders to the patch config.\n patchConfig = $.extend(patchConfig, config[newState.type]);\n }\n\n return Object.keys(patchConfig).reduce(function(patch, key) {\n var buildFunc = patchConfig[key];\n var value = buildFunc(state, newState);\n\n if (value !== null) {\n patch[key] = value;\n }\n\n return patch;\n }, {});\n };\n\n return {\n buildPatch: buildPatch\n };\n});\n"],"file":"message_drawer_view_conversation_patcher.min.js"} \ No newline at end of file diff --git a/message/amd/src/message_drawer_view_conversation_patcher.js b/message/amd/src/message_drawer_view_conversation_patcher.js index c969b685577..406491b3003 100644 --- a/message/amd/src/message_drawer_view_conversation_patcher.js +++ b/message/amd/src/message_drawer_view_conversation_patcher.js @@ -1310,10 +1310,7 @@ function( /** * We should show the contact request sent message if the user just sent - * a contact request to the other user and there are no messages in the - * conversation. - * - * The messages should be hidden when there are messages in the conversation. + * a contact request to the other user * * @param {Object} state The current state. * @param {Object} newState The new state. @@ -1331,18 +1328,14 @@ function( }); var oldRequest = oldSentRequests.length > 0; var newRequest = newSentRequests.length > 0; - var hadMessages = state.messages.length > 0; - var hasMessages = state.messages.length > 0; - if (!oldRequest && newRequest && !newOtherUser.iscontact && !hasMessages) { + if (!oldRequest && newRequest && !newOtherUser.iscontact) { return newOtherUser.fullname; } else if (oldOtherUser && !oldOtherUser.iscontact && newRequest && newOtherUser.iscontact) { // Contact request accepted. return false; } else if (oldRequest && !newRequest) { return false; - } else if (!hadMessages && hasMessages) { - return false; } else { return null; }