AutorÃa | Ultima modificación | Ver Log |
{"version":3,"file":"auto_complete.min.js","sources":["../../src/emoji/auto_complete.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 * Emoji auto complete.\n *\n * @module core/emoji/auto_complete\n * @copyright 2019 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as Emo
jiData from 'core/emoji/data';\nimport {render as renderTemplate} from 'core/templates';\nimport {debounce} from 'core/utils';\nimport LocalStorage from 'core/localstorage';\nimport KeyCodes from 'core/key_codes';\n\nconst INPUT_DEBOUNCE_TIMER = 200;\nconst SUGGESTION_LIMIT = 50;\nconst MAX_RECENT_COUNT = 27;\nconst RECENT_EMOJIS_STORAGE_KEY = 'moodle-recent-emojis';\n\nconst SELECTORS = {\n EMOJI_BUTTON: '[data-region=\"emoji-button\"]',\n ACTIVE_EMOJI_BUTTON: '[data-region=\"emoji-button\"].active',\n};\n\n/**\n * Get the list of recent emojis data from local storage.\n *\n * @return {Array}\n */\nconst getRecentEmojis = () => {\n const storedData = LocalStorage.get(RECENT_EMOJIS_STORAGE_KEY);\n return storedData ? JSON.parse(storedData) : [];\n};\n\n/**\n * Add an emoji data to the set of recent emojis. The new set of recent emojis are\n * saved in local storage.\n *\n * @param {String} unified The char chodes for the emoji\n * @param {String} shortName The emoji short name\n */\nconst addRece
ntEmoji = (unified, shortName) => {\n const newEmoji = {\n unified,\n shortnames: [shortName]\n };\n const recentEmojis = getRecentEmojis();\n // Add the new emoji to the start of the list of recent emojis.\n let newRecentEmojis = [newEmoji, ...recentEmojis.filter(emoji => emoji.unified != newEmoji.unified)];\n // Limit the number of recent emojis.\n newRecentEmojis = newRecentEmojis.slice(0, MAX_RECENT_COUNT);\n\n LocalStorage.set(RECENT_EMOJIS_STORAGE_KEY, JSON.stringify(newRecentEmojis));\n};\n\n/**\n * Get the actual emoji string from the short name.\n *\n * @param {String} shortName Emoji short name\n * @return {String|null}\n */\nconst getEmojiTextFromShortName = (shortName) => {\n const unified = EmojiData.byShortName[shortName];\n\n if (unified) {\n const charCodes = unified.split('-').map(code => `0x${code}`);\n return String.fromCodePoint.apply(null, charCodes);\n } else {\n return null;\n }\n};\n\n/**\n * Render the auto comple
te list for the given short names.\n *\n * @param {Element} root The root container for the emoji auto complete\n * @param {Array} shortNames The list of short names for emoji suggestions to show\n */\nconst render = async(root, shortNames) => {\n const renderContext = {\n emojis: shortNames.map((shortName, index) => {\n return {\n active: index === 0,\n emojitext: getEmojiTextFromShortName(shortName),\n displayshortname: `:${shortName}:`,\n shortname: shortName,\n unified: EmojiData.byShortName[shortName]\n };\n })\n };\n const html = await renderTemplate('core/emoji/auto_complete', renderContext);\n root.innerHTML = html;\n};\n\n/**\n * Get the list of emoji short names that include the given search term. If\n * the search term is an empty string then the list of recently used emojis\n * will be returned.\n *\n * @param {String} searchTerm Text to match on\n * @param {Number} limit Max
imum number of results to return\n * @return {Array}\n */\nconst searchEmojis = (searchTerm, limit) => {\n if (searchTerm === '') {\n return getRecentEmojis().map(data => data.shortnames[0]).slice(0, limit);\n } else {\n searchTerm = searchTerm.toLowerCase();\n return Object.keys(EmojiData.byShortName)\n .filter(shortName => shortName.includes(searchTerm))\n .slice(0, limit);\n }\n};\n\n/**\n * Get the current word at the given position (index) within the text.\n *\n * @param {String} text The text to process\n * @param {Number} position The position (index) within the text to match the word\n * @return {String}\n */\nconst getWordFromPosition = (text, position) => {\n const startMatches = text.slice(0, position).match(/(\\S*)$/);\n const endMatches = text.slice(position).match(/^(\\S*)/);\n let startText = '';\n let endText = '';\n\n if (startMatches) {\n startText = startMatches[startMatches.length - 1];\n }\n\n if (end
Matches) {\n endText = endMatches[endMatches.length - 1];\n }\n\n return `${startText}${endText}`;\n};\n\n/**\n * Check if the given text is a full short name, i.e. has leading and trialing colon\n * characters.\n *\n * @param {String} text The text to process\n * @return {Bool}\n */\nconst isCompleteShortName = text => /^:[^:\\s]+:$/.test(text);\n\n/**\n * Check if the given text is a partial short name, i.e. has a leading colon but no\n * trailing colon.\n *\n * @param {String} text The text to process\n * @return {Bool}\n */\nconst isPartialShortName = text => /^:[^:\\s]*$/.test(text);\n\n/**\n * Remove the colon characters from the given text.\n *\n * @param {String} text The text to process\n * @return {String}\n */\nconst getShortNameFromText = text => text.replace(/:/g, '');\n\n/**\n * Get the currently active emoji button element in the list of suggestions.\n *\n * @param {Element} root The emoji auto complete container element\n * @return {Element|null}\n */\nconst getActiveEmojiSuggest
ion = (root) => {\n return root.querySelector(SELECTORS.ACTIVE_EMOJI_BUTTON);\n};\n\n/**\n * Make the previous sibling of the current active emoji active.\n *\n * @param {Element} root The emoji auto complete container element\n */\nconst selectPreviousEmojiSuggestion = (root) => {\n const activeEmojiSuggestion = getActiveEmojiSuggestion(root);\n const previousSuggestion = activeEmojiSuggestion.previousElementSibling;\n\n if (previousSuggestion) {\n activeEmojiSuggestion.classList.remove('active');\n previousSuggestion.classList.add('active');\n previousSuggestion.scrollIntoView({behaviour: 'smooth', inline: 'center'});\n }\n};\n\n/**\n * Make the next sibling to the current active emoji active.\n *\n * @param {Element} root The emoji auto complete container element\n */\nconst selectNextEmojiSuggestion = (root) => {\n const activeEmojiSuggestion = getActiveEmojiSuggestion(root);\n const nextSuggestion = activeEmojiSuggestion.nextElementSibling;\n\n if (nextSugges
tion) {\n activeEmojiSuggestion.classList.remove('active');\n nextSuggestion.classList.add('active');\n nextSuggestion.scrollIntoView({behaviour: 'smooth', inline: 'center'});\n }\n};\n\n/**\n * Trigger the select callback for the given emoji button element.\n *\n * @param {Element} element The emoji button element\n * @param {Function} selectCallback The callback for when the user selects an emoji\n */\nconst selectEmojiElement = (element, selectCallback) => {\n const shortName = element.getAttribute('data-short-name');\n const unified = element.getAttribute('data-unified');\n addRecentEmoji(unified, shortName);\n selectCallback(element.innerHTML.trim());\n};\n\n/**\n * Initialise the emoji auto complete.\n *\n * @method\n * @param {Element} root The root container element for the auto complete\n * @param {Element} textArea The text area element to monitor for auto complete\n * @param {Function} hasSuggestionCallback Callback for when there are auto-complete suggestions\n
* @param {Function} selectCallback Callback for when the user selects an emoji\n */\nexport default (root, textArea, hasSuggestionCallback, selectCallback) => {\n let hasSuggestions = false;\n let previousSearchText = '';\n\n // Debounce the listener so that each keypress delays the execution of the handler. The\n // handler should only run 200 milliseconds after the last keypress.\n textArea.addEventListener('keyup', debounce(() => {\n // This is a \"keyup\" listener so that it only executes after the text area value\n // has been updated.\n const text = textArea.value;\n const cursorPos = textArea.selectionStart;\n const searchText = getWordFromPosition(text, cursorPos);\n\n if (searchText === previousSearchText) {\n // Nothing has changed so no need to take any action.\n return;\n } else {\n previousSearchText = searchText;\n }\n\n if (isCompleteShortName(searchText)) {\n // If the us
er has entered a full short name (with leading and trialing colons)\n // then see if we can find a match for it and auto complete it.\n const shortName = getShortNameFromText(searchText);\n const emojiText = getEmojiTextFromShortName(shortName);\n hasSuggestions = false;\n if (emojiText) {\n addRecentEmoji(EmojiData.byShortName[shortName], shortName);\n selectCallback(emojiText);\n }\n } else if (isPartialShortName(searchText)) {\n // If the user has entered a partial short name (leading colon but no trailing) then\n // search on the text to see if we can find some suggestions for them.\n const suggestions = searchEmojis(getShortNameFromText(searchText), SUGGESTION_LIMIT);\n\n if (suggestions.length) {\n render(root, suggestions);\n hasSuggestions = true;\n } else {\n hasSuggestions = false;\n }\n
} else {\n hasSuggestions = false;\n }\n\n hasSuggestionCallback(hasSuggestions);\n }, INPUT_DEBOUNCE_TIMER));\n\n textArea.addEventListener('keydown', (e) => {\n if (hasSuggestions) {\n const isModifierPressed = (e.shiftKey || e.metaKey || e.altKey || e.ctrlKey);\n if (!isModifierPressed) {\n switch (e.which) {\n case KeyCodes.escape:\n // Escape key closes the auto complete.\n hasSuggestions = false;\n hasSuggestionCallback(false);\n break;\n case KeyCodes.arrowLeft:\n // Arrow keys navigate through the list of suggetions.\n selectPreviousEmojiSuggestion(root);\n e.preventDefault();\n break;\n case KeyCodes.arrowRight:\n // Arrow keys navigate through the list of sug
getions.\n selectNextEmojiSuggestion(root);\n e.preventDefault();\n break;\n case KeyCodes.enter:\n // Enter key selects the current suggestion.\n selectEmojiElement(getActiveEmojiSuggestion(root), selectCallback);\n e.preventDefault();\n e.stopPropagation();\n break;\n }\n }\n }\n });\n\n root.addEventListener('click', (e) => {\n const target = e.target;\n if (target.matches(SELECTORS.EMOJI_BUTTON)) {\n selectEmojiElement(target, selectCallback);\n }\n });\n};\n"],"names":["SELECTORS","getRecentEmojis","storedData","LocalStorage","get","JSON","parse","addRecentEmoji","unified","shortName","newEmoji","shortnames","recentEmojis","newRecentEmojis","filter","emoji","slice","set","stringify","getEmojiTextFromShortName","EmojiData","byShortN
ame","charCodes","split","map","code","String","fromCodePoint","apply","getShortNameFromText","text","replace","getActiveEmojiSuggestion","root","querySelector","selectEmojiElement","element","selectCallback","getAttribute","innerHTML","trim","textArea","hasSuggestionCallback","hasSuggestions","previousSearchText","addEventListener","searchText","position","startMatches","match","endMatches","startText","endText","length","getWordFromPosition","value","selectionStart","test","isCompleteShortName","emojiText","isPartialShortName","suggestions","searchTerm","limit","data","toLowerCase","Object","keys","includes","async","shortNames","renderContext","emojis","index","active","emojitext","displayshortname","shortname","html","render","e","shiftKey","metaKey","altKey","ctrlKey","which","KeyCodes","escape","arrowLeft","activeEmojiSuggestion","previousSuggestion","previousElementSibling","classList","remove","add","scrollIntoView","behaviour","inline","selectPreviousEmojiSuggestion","preventDefault","arrowRight","n
extSuggestion","nextElementSibling","selectNextEmojiSuggestion","enter","stopPropagation","target","matches"],"mappings":"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"}