Proyectos de Subversion Moodle

Rev

Rev 1 | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |

{"version":3,"file":"message_drawer.min.js","sources":["../src/message_drawer.js"],"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 <http://www.gnu.org/licenses/>.\n\n/**\n * Controls the message drawer.\n *\n * @module     core_message/message_drawer\n * @copyright  2018 Ryan Wyllie <ryan@moodle.com>\n * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n    'jquery',\n    'core/custom_interaction_events',\n    'core/pubsub',\n    'core_message/message_drawer_view_contact',\n    'core_message/message_drawer_view_contacts',\n    'core_message/message_drawer_view_conversation',\n    'core_message/message_drawer_view_group_info',\n    'core_message/message_drawer_view_overview',\n    'core_message/message_drawer_view_search',\n    'core_message/message_drawer_view_settings',\n    'core_message/message_drawer_router',\n    'core_message/message_drawer_routes',\n    'core_message/message_drawer_events',\n    'core_message/message_drawer_helper',\n    'core/pending',\n    'core/drawer',\n    'core/toast',\n    'core/str',\n    'core/config',\n    'core/ajax',\n    'core/local/aria/focuslock',\n    'core/modal_backdrop',\n    'core/templates',\n    'core/local/aria/selectors',\n],\nfunction(\n    $,\n    CustomEvents,\n    PubSub,\n    ViewContact,\n    ViewContacts,\n    ViewConversation,\n    ViewGroupInfo,\n    ViewOverview,\n    ViewSearch,\n    ViewSettings,\n    Router,\n    Routes,\n    Events,\n    Helper,\n    Pending,\n    Drawer,\n    Toast,\n    Str,\n    Config,\n    Ajax,\n    FocusLock,\n    ModalBackdrop,\n    Templates,\n    AriaSelectors,\n) {\n\n    var SELECTORS = {\n        DRAWER: '[data-region=\"right-hand-drawer\"]',\n        PANEL_BODY_CONTAINER: '[data-region=\"panel-body-container\"]',\n        PANEL_HEADER_CONTAINER: '[data-region=\"panel-header-container\"]',\n        VIEW_CONTACT: '[data-region=\"view-contact\"]',\n        VIEW_CONTACTS: '[data-region=\"view-contacts\"]',\n        VIEW_CONVERSATION: '[data-region=\"view-conversation\"]',\n        VIEW_CONVERSATION_WITH_ID: '[data-region=\"view-conversation\"][data-conversation-id]',\n        VIEW_CONVERSATION_WITH_USER: '[data-region=\"view-conversation\"][data-other-user-id]',\n        VIEW_GROUP_INFO: '[data-region=\"view-group-info\"]',\n        VIEW_OVERVIEW: '[data-region=\"view-overview\"]',\n        VIEW_SEARCH: '[data-region=\"view-search\"]',\n        VIEW_SETTINGS: '[data-region=\"view-settings\"]',\n        ROUTES: '[data-route]',\n        ROUTES_BACK: '[data-route-back]',\n        HEADER_CONTAINER: '[data-region=\"header-container\"]',\n        BODY_CONTAINER: '[data-region=\"body-container\"]',\n        FOOTER_CONTAINER: '[data-region=\"footer-container\"]',\n        CLOSE_BUTTON: '[data-action=\"closedrawer\"]',\n        MESSAGE_INDEX: '[data-region=\"message-index\"]',\n        MESSAGE_TEXT_AREA: '[data-region=\"send-message-txt\"]',\n    };\n\n    /**\n     * Get elements for route.\n     *\n     * @param {String} namespace Unique identifier for the Routes\n     * @param {Object} root The message drawer container.\n     * @param {string} selector The route container.\n     *\n     * @return {array} elements Found route container objects.\n     */\n    var getParametersForRoute = function(namespace, root, selector) {\n\n        var header = root.find(SELECTORS.HEADER_CONTAINER).find(selector);\n        if (!header.length) {\n            header = root.find(SELECTORS.PANEL_HEADER_CONTAINER).find(selector);\n        }\n        var body = root.find(SELECTORS.BODY_CONTAINER).find(selector);\n        if (!body.length) {\n            body = root.find(SELECTORS.PANEL_BODY_CONTAINER).find(selector);\n        }\n        var footer = root.find(SELECTORS.FOOTER_CONTAINER).find(selector);\n\n        return [\n            namespace,\n            header.length ? header : null,\n            body.length ? body : null,\n            footer.length ? footer : null\n        ];\n    };\n\n    var routes = [\n        [Routes.VIEW_CONTACT, SELECTORS.VIEW_CONTACT, ViewContact.show, ViewContact.description],\n        [Routes.VIEW_CONTACTS, SELECTORS.VIEW_CONTACTS, ViewContacts.show, ViewContacts.description],\n        [Routes.VIEW_CONVERSATION, SELECTORS.VIEW_CONVERSATION, ViewConversation.show, ViewConversation.description],\n        [Routes.VIEW_GROUP_INFO, SELECTORS.VIEW_GROUP_INFO, ViewGroupInfo.show, ViewGroupInfo.description],\n        [Routes.VIEW_OVERVIEW, SELECTORS.VIEW_OVERVIEW, ViewOverview.show, ViewOverview.description],\n        [Routes.VIEW_SEARCH, SELECTORS.VIEW_SEARCH, ViewSearch.show, ViewSearch.description],\n        [Routes.VIEW_SETTINGS, SELECTORS.VIEW_SETTINGS, ViewSettings.show, ViewSettings.description]\n    ];\n\n    /**\n     * Create routes.\n     *\n     * @param {String} namespace Unique identifier for the Routes\n     * @param {Object} root The message drawer container.\n     */\n    var createRoutes = function(namespace, root) {\n        routes.forEach(function(route) {\n            Router.add(namespace, route[0], getParametersForRoute(namespace, root, route[1]), route[2], route[3]);\n        });\n    };\n\n    let backdropPromise = null;\n\n    /**\n     * Set the focus on the drawer.\n     *\n     * This method also creates or destroy any necessary backdrop zone and focus trap.\n     *\n     * @param {Object} root The message drawer container.\n     * @param {Boolean} hasFocus Whether the drawer has focus or not.\n     */\n    var setFocus = function(root, hasFocus) {\n        var drawerRoot = Drawer.getDrawerRoot(root);\n        if (!drawerRoot.length) {\n            return;\n        }\n        if (!backdropPromise) {\n            backdropPromise = Templates.render('core/modal_backdrop', {})\n                .then(html => new ModalBackdrop(html));\n        }\n        const backdropWithAdjustments = backdropPromise.then(modalBackdrop => {\n            const messageDrawerZIndex = window.getComputedStyle(drawerRoot[0]).zIndex;\n            if (messageDrawerZIndex) {\n                modalBackdrop.setZIndex(messageDrawerZIndex - 1);\n            }\n            modalBackdrop.getAttachmentPoint().get(0).addEventListener('click', e => {\n                PubSub.publish(Events.HIDE, {});\n                e.preventDefault();\n            });\n            return modalBackdrop;\n        });\n        if (hasFocus) {\n            FocusLock.trapFocus(root[0]);\n            // eslint-disable-next-line promise/catch-or-return\n            backdropWithAdjustments.then(modalBackdrop => {\n                if (modalBackdrop) {\n                    modalBackdrop.show();\n                    const pageWrapper = document.getElementById('page');\n                    pageWrapper.style.overflow = 'hidden';\n                    // Set the focus on the close button so when we press enter, it closes the drawer as it did before\n                    var closeButton = root.find(SELECTORS.CLOSE_BUTTON);\n                    if (closeButton.length) {\n                        closeButton.focus();\n                    }\n                }\n                return modalBackdrop;\n            });\n        } else {\n            // eslint-disable-next-line promise/catch-or-return\n            backdropWithAdjustments.then(modalBackdrop => {\n                if (modalBackdrop) {\n                    FocusLock.untrapFocus();\n                    var button = $(SELECTORS.DRAWER).attr('data-origin');\n                    if (button) {\n                        $('#' + button).focus();\n                    }\n                    modalBackdrop.hide();\n                    const pageWrapper = document.getElementById('page');\n                    pageWrapper.style.overflow = 'visible';\n                }\n                return modalBackdrop;\n            });\n        }\n    };\n    /**\n     * Show the message drawer.\n     *\n     * @param {string} namespace The route namespace.\n     * @param {Object} root The message drawer container.\n     */\n    var show = function(namespace, root) {\n        if (!root.attr('data-shown')) {\n            Router.go(namespace, Routes.VIEW_OVERVIEW);\n            root.attr('data-shown', true);\n        }\n\n        var drawerRoot = Drawer.getDrawerRoot(root);\n        if (drawerRoot.length) {\n            setFocus(root, true);\n            Drawer.show(drawerRoot);\n        }\n    };\n\n    /**\n     * Hide the message drawer.\n     *\n     * @param {Object} root The message drawer container.\n     */\n    var hide = function(root) {\n        var drawerRoot = Drawer.getDrawerRoot(root);\n        if (drawerRoot.length) {\n            setFocus(root, false);\n            Drawer.hide(drawerRoot);\n        }\n    };\n\n    /**\n     * Check if the drawer is visible.\n     *\n     * @param {Object} root The message drawer container.\n     * @return {boolean}\n     */\n    var isVisible = function(root) {\n        var drawerRoot = Drawer.getDrawerRoot(root);\n        if (drawerRoot.length) {\n            return Drawer.isVisible(drawerRoot);\n        }\n        return true;\n    };\n\n    /**\n     * Set Jump from button\n     *\n     * @param {String} buttonid The originating button id\n     */\n    var setJumpFrom = function(buttonid) {\n        $(SELECTORS.DRAWER).attr('data-origin', buttonid);\n    };\n\n    /**\n     * Store an unsent message.\n     *\n     * Don't store this if the user has already seen the unsent message.\n     * This avoids spamming and ensures the user is only reminded once per unsent message.\n     * If the unsent message is sent, this attribute is removed and notification is possible again (see sendMessage).\n     */\n    const storeUnsentMessage = async() => {\n        const messageTextArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n\n        if (messageTextArea.value.trim().length > 0 && !messageTextArea.hasAttribute('data-unsent-message-viewed')) {\n\n            let message = messageTextArea.value;\n            let conversationid = 0;\n            let otheruserid = 0;\n\n            // We don't always have a conversation to link the unsent message to, so let's check for that.\n            const conversationId = document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_ID);\n            if (conversationId) {\n                const conversationWithId = messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_ID);\n                conversationid = conversationWithId.getAttribute('data-conversation-id');\n            }\n            // Store the 'other' user id if it is there. This can be used to create conversations.\n            const conversationUser = document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_USER);\n            if (conversationUser) {\n                const conversationWithUser = messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_USER);\n                otheruserid = conversationWithUser.getAttribute('data-other-user-id');\n            }\n\n            setStoredUnsentMessage(message, conversationid, otheruserid);\n        }\n    };\n\n    /**\n     * Get the stored unsent message from the session via web service.\n     *\n     * @returns {Promise}\n     */\n    const getStoredUnsentMessage = () => Ajax.call([{\n        methodname: 'core_message_get_unsent_message',\n        args: {}\n    }])[0];\n\n    /**\n     * Set the unsent message value in the session via web service.\n     *\n     * SendBeacon is used here because this is called on 'beforeunload'.\n     *\n     * @param {string} message The message string.\n     * @param {number} conversationid The conversation id.\n     * @param {number} otheruserid The other user id.\n     * @returns {Promise}\n     */\n    const setStoredUnsentMessage = (message, conversationid, otheruserid) => {\n        const method = 'core_message_set_unsent_message';\n        const requestUrl = new URL(`${Config.wwwroot}/lib/ajax/service.php`);\n        requestUrl.searchParams.set('sesskey', Config.sesskey);\n        requestUrl.searchParams.set('info', method);\n\n        navigator.sendBeacon(requestUrl, JSON.stringify([{\n            index: 0,\n            methodname: method,\n            args: {\n                message: message,\n                conversationid: conversationid,\n                otheruserid: otheruserid,\n            }\n        }]));\n    };\n\n    /**\n     * Check for an unsent message.\n     *\n     * @param {String} uniqueId Unique identifier for the Routes.\n     * @param {Object} root The message drawer container.\n     */\n    const getUnsentMessage = async(uniqueId, root) => {\n        let type;\n        let messageRoot;\n\n        // We need to check if we are on the message/index page.\n        // This logic is needed to handle the two message widgets here and ensure we are targetting the right one.\n        const messageIndex = document.querySelector(SELECTORS.MESSAGE_INDEX);\n        if (messageIndex !== null) {\n            type = 'index';\n            messageRoot = document.getElementById(`message-index-${uniqueId}`);\n            if (!messageRoot) {\n                // This is not the correct widget.\n                return;\n            }\n\n        } else {\n            type = 'drawer';\n            messageRoot = document.getElementById(`message-drawer-${uniqueId}`);\n        }\n\n        const storedMessage = await getStoredUnsentMessage();\n        const messageTextArea = messageRoot.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n        if (storedMessage.message && messageTextArea !== null) {\n            showUnsentMessage(messageTextArea, storedMessage, type, uniqueId, root);\n        }\n    };\n\n    /**\n     * Show an unsent message.\n     *\n     * There are two message widgets on the message/index page.\n     * Because of that, we need to try and target the correct widget.\n     *\n     * @param {String} textArea The textarea element.\n     * @param {Object} stored The stored message content.\n     * @param {String} type Is this from the drawer or index page?\n     * @param {String} uniqueId Unique identifier for the Routes.\n     * @param {Object} root The message drawer container.\n     */\n    const showUnsentMessage = (textArea, stored, type, uniqueId, root) => {\n        // The user has already been notified.\n        if (textArea.hasAttribute('data-unsent-message-viewed')) {\n            return;\n        }\n\n        // Depending on the type, show the conversation with the data we have available.\n        // A conversation can be continued if there is a conversationid.\n        // If the user was messaging a new non-contact, we won't have a conversationid yet.\n        // In that case, we use the otheruserid value to start a conversation with them.\n        switch (type) {\n            case 'index':\n                // Show the conversation in the main panel on the message/index page.\n                if (stored.conversationid) {\n                    Router.go(uniqueId, Routes.VIEW_CONVERSATION, stored.conversationid, 'frompanel');\n                // There was no conversation id, let's get a conversation going using the user id.\n                } else if (stored.otheruserid) {\n                    Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', stored.otheruserid);\n                }\n                break;\n\n            case 'drawer':\n                // Open the drawer and show the conversation.\n                if (stored.conversationid) {\n                    let args = {\n                        conversationid: stored.conversationid\n                    };\n                    Helper.showConversation(args);\n                // There was no conversation id, let's get a conversation going using the user id.\n                } else if (stored.otheruserid) {\n                    show(uniqueId, root);\n                    Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', stored.otheruserid);\n                }\n                break;\n        }\n\n        // Populate the text area.\n        textArea.value = stored.message;\n        textArea.setAttribute('data-unsent-message-viewed', 1);\n\n        // Notify the user.\n        Toast.add(Str.get_string('unsentmessagenotification', 'core_message'));\n    };\n\n    /**\n     * Listen to and handle events for routing, showing and hiding the message drawer.\n     *\n     * @param {string} namespace The route namespace.\n     * @param {Object} root The message drawer container.\n     * @param {bool} alwaysVisible Is this messaging app always shown?\n     */\n    var registerEventListeners = function(namespace, root, alwaysVisible) {\n        CustomEvents.define(root, [CustomEvents.events.activate, CustomEvents.events.escape]);\n        var paramRegex = /^data-route-param-?(\\d*)$/;\n\n        root.on(CustomEvents.events.activate, SELECTORS.ROUTES, function(e, data) {\n            var element = $(e.target).closest(SELECTORS.ROUTES);\n            var route = element.attr('data-route');\n            var attributes = [];\n\n            for (var i = 0; i < element[0].attributes.length; i++) {\n                attributes.push(element[0].attributes[i]);\n            }\n\n            var paramAttributes = attributes.filter(function(attribute) {\n                var name = attribute.nodeName;\n                var match = paramRegex.test(name);\n                return match;\n            });\n            paramAttributes.sort(function(a, b) {\n                var aParts = paramRegex.exec(a.nodeName);\n                var bParts = paramRegex.exec(b.nodeName);\n                var aIndex = aParts.length > 1 ? aParts[1] : 0;\n                var bIndex = bParts.length > 1 ? bParts[1] : 0;\n\n                if (aIndex < bIndex) {\n                    return -1;\n                } else if (bIndex < aIndex) {\n                    return 1;\n                } else {\n                    return 0;\n                }\n            });\n\n            var params = paramAttributes.map(function(attribute) {\n                return attribute.nodeValue;\n            });\n\n            var routeParams = [namespace, route].concat(params);\n\n            Router.go.apply(null, routeParams);\n\n            data.originalEvent.preventDefault();\n        });\n\n        root.on(CustomEvents.events.activate, SELECTORS.ROUTES_BACK, function(e, data) {\n            Router.back(namespace);\n\n            data.originalEvent.preventDefault();\n        });\n\n        // Close the message drawer if the drawer is visible and the click happened outside the drawer and the toggle button.\n        $(document).on(CustomEvents.events.activate, e => {\n            var drawer = $(e.target).closest(SELECTORS.DRAWER);\n            var toggleButtonId = $(SELECTORS.DRAWER)?.attr('data-origin');\n            var toggleButton = '';\n            if (toggleButtonId !== undefined && toggleButtonId) {\n                toggleButton = $(e.target).closest(\"#\" + toggleButtonId);\n            }\n\n            if (!drawer.length && !toggleButton.length && isVisible(root)) {\n                // Determine if the element that was clicked is focusable.\n                var focusableElement = $(e.target).closest(AriaSelectors.elements.focusable);\n                if (focusableElement.length) {\n                    // We need to move the focus to the clicked element after the drawer is hidden,\n                    // so we need to clear the `data-origin` attribute first.\n                    $(SELECTORS.DRAWER).attr('data-origin', '');\n                }\n                // Hide the drawer.\n                hide(root);\n                // Move the focus to the clicked element if it is focusable.\n                if (focusableElement.length) {\n                    focusableElement.focus();\n                }\n            }\n        });\n\n        // These are theme-specific to help us fix random behat fails.\n        // These events target those events defined in BS3 and BS4 onwards.\n        root[0].querySelectorAll('.collapse').forEach((collapse) => {\n            collapse.addEventListener('hide.bs.collapse', (e) => {\n                var pendingPromise = new Pending();\n                e.target.addEventListener('hidden.bs.collapse', function() {\n                    pendingPromise.resolve();\n                }, {once: true});\n            });\n        });\n\n        root[0].querySelectorAll('.collapse').forEach((collapse) => {\n            collapse.addEventListener('show.bs.collapse', (e) => {\n                var pendingPromise = new Pending();\n                e.target.addEventListener('shown.bs.collapse', function() {\n                    pendingPromise.resolve();\n                }, {once: true});\n            });\n        });\n\n        $(SELECTORS.JUMPTO).focus(function() {\n            var firstInput = root.find(SELECTORS.CLOSE_BUTTON);\n            if (firstInput.length) {\n                firstInput.focus();\n            } else {\n                $(SELECTORS.HEADER_CONTAINER).find(SELECTORS.ROUTES_BACK).focus();\n            }\n        });\n\n        $(SELECTORS.DRAWER).focus(function() {\n            var button = $(this).attr('data-origin');\n            if (button) {\n                $('#' + button).focus();\n            }\n        });\n\n        if (!alwaysVisible) {\n            PubSub.subscribe(Events.SHOW, function() {\n                show(namespace, root);\n            });\n\n            PubSub.subscribe(Events.HIDE, function() {\n                hide(root);\n            });\n\n            PubSub.subscribe(Events.TOGGLE_VISIBILITY, function(buttonid) {\n                const buttonElement = document.getElementById(buttonid);\n                if (isVisible(root)) {\n                    hide(root);\n                    buttonElement?.setAttribute('aria-expanded', false);\n                    $(SELECTORS.JUMPTO).attr('tabindex', -1);\n                } else {\n                    show(namespace, root);\n                    buttonElement?.setAttribute('aria-expanded', true);\n                    setJumpFrom(buttonid);\n                    $(SELECTORS.JUMPTO).attr('tabindex', 0);\n                }\n            });\n            root.on(CustomEvents.events.escape, function() {\n                PubSub.publish(Events.HIDE, {});\n            });\n        }\n\n        PubSub.subscribe(Events.SHOW_CONVERSATION, function(args) {\n            setJumpFrom(args.buttonid);\n            show(namespace, root);\n            Router.go(namespace, Routes.VIEW_CONVERSATION, args.conversationid);\n        });\n\n        var closebutton = root.find(SELECTORS.CLOSE_BUTTON);\n        closebutton.on(CustomEvents.events.activate, function(e, data) {\n            data.originalEvent.preventDefault();\n            setFocus(root, false);\n            var button = $(SELECTORS.DRAWER).attr('data-origin');\n            if (button) {\n                $('#' + button).focus();\n            }\n            PubSub.publish(Events.TOGGLE_VISIBILITY, button);\n        });\n\n        PubSub.subscribe(Events.CREATE_CONVERSATION_WITH_USER, function(args) {\n            setJumpFrom(args.buttonid);\n            show(namespace, root);\n            Router.go(namespace, Routes.VIEW_CONVERSATION, null, 'create', args.userid);\n        });\n\n        PubSub.subscribe(Events.SHOW_SETTINGS, function() {\n            show(namespace, root);\n            Router.go(namespace, Routes.VIEW_SETTINGS);\n        });\n\n        PubSub.subscribe(Events.PREFERENCES_UPDATED, function(preferences) {\n            var filteredPreferences = preferences.filter(function(preference) {\n                return preference.type == 'message_entertosend';\n            });\n            var enterToSendPreference = filteredPreferences.length ? filteredPreferences[0] : null;\n\n            if (enterToSendPreference) {\n                var viewConversationFooter = root.find(SELECTORS.FOOTER_CONTAINER).find(SELECTORS.VIEW_CONVERSATION);\n                viewConversationFooter.attr('data-enter-to-send', enterToSendPreference.value);\n            }\n        });\n\n        // If our textarea is modified, remove the attribute which indicates the user has seen the unsent message notification.\n        // This will allow the user to be notified again.\n        const textArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n        if (textArea) {\n            textArea.addEventListener('keyup', function() {\n                textArea.removeAttribute('data-unsent-message-viewed');\n            });\n        }\n\n        // Catch any unsent messages and store them.\n        window.addEventListener('beforeunload', storeUnsentMessage);\n    };\n\n    /**\n     * Initialise the message drawer.\n     *\n     * @param {Object} root The message drawer container.\n     * @param {String} uniqueId Unique identifier for the Routes\n     * @param {bool} alwaysVisible Should we show the app now, or wait for the user?\n     * @param {Object} route\n     */\n    var init = function(root, uniqueId, alwaysVisible, route) {\n        root = $(root);\n        createRoutes(uniqueId, root);\n        registerEventListeners(uniqueId, root, alwaysVisible);\n\n        if (alwaysVisible) {\n            show(uniqueId, root);\n\n            if (route) {\n                var routeParams = route.params || [];\n                routeParams = [uniqueId, route.path].concat(routeParams);\n                Router.go.apply(null, routeParams);\n            }\n        }\n\n        // Mark the drawer as ready.\n        Helper.markDrawerReady();\n\n        // Get and show any unsent message.\n        getUnsentMessage(uniqueId, root);\n    };\n\n    return {\n        init: init,\n    };\n});\n"],"names":["define","$","CustomEvents","PubSub","ViewContact","ViewContacts","ViewConversation","ViewGroupInfo","ViewOverview","ViewSearch","ViewSettings","Router","Routes","Events","Helper","Pending","Drawer","Toast","Str","Config","Ajax","FocusLock","ModalBackdrop","Templates","AriaSelectors","SELECTORS","DRAWER","PANEL_BODY_CONTAINER","PANEL_HEADER_CONTAINER","VIEW_CONTACT","VIEW_CONTACTS","VIEW_CONVERSATION","VIEW_CONVERSATION_WITH_ID","VIEW_CONVERSATION_WITH_USER","VIEW_GROUP_INFO","VIEW_OVERVIEW","VIEW_SEARCH","VIEW_SETTINGS","ROUTES","ROUTES_BACK","HEADER_CONTAINER","BODY_CONTAINER","FOOTER_CONTAINER","CLOSE_BUTTON","MESSAGE_INDEX","MESSAGE_TEXT_AREA","routes","show","description","createRoutes","namespace","root","forEach","route","add","selector","header","find","length","body","footer","getParametersForRoute","backdropPromise","setFocus","hasFocus","drawerRoot","getDrawerRoot","render","then","html","backdropWithAdjustments","modalBackdrop","messageDrawerZIndex","window","getComputedStyle","zIndex","setZIndex","getAttachmentPoint","get","addEventListener","e","publish","HIDE","preventDefault","trapFocus","document","getElementById","style","overflow","closeButton","focus","untrapFocus","button","attr","hide","go","isVisible","setJumpFrom","buttonid","storeUnsentMessage","async","messageTextArea","querySelector","value","trim","hasAttribute","message","conversationid","otheruserid","closest","getAttribute","setStoredUnsentMessage","method","requestUrl","URL","wwwroot","searchParams","set","sesskey","navigator","sendBeacon","JSON","stringify","index","methodname","args","getUnsentMessage","uniqueId","type","messageRoot","storedMessage","call","showUnsentMessage","textArea","stored","showConversation","setAttribute","get_string","init","alwaysVisible","events","activate","escape","paramRegex","on","data","element","target","attributes","i","push","paramAttributes","filter","attribute","name","nodeName","test","sort","a","b","aParts","exec","bParts","aIndex","bIndex","params","map","nodeValue","routeParams","concat","apply","originalEvent","back","drawer","toggleButtonId","_$","toggleButton","undefined","focusableElement","elements","focusable","querySelectorAll","collapse","pendingPromise","resolve","once","JUMPTO","firstInput","this","subscribe","SHOW","TOGGLE_VISIBILITY","buttonElement","SHOW_CONVERSATION","CREATE_CONVERSATION_WITH_USER","userid","SHOW_SETTINGS","PREFERENCES_UPDATED","preferences","filteredPreferences","preference","enterToSendPreference","removeAttribute","registerEventListeners","path","markDrawerReady"],"mappings":"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"}