diff --git a/admin/tool/langimport/amd/build/search.min.js b/admin/tool/langimport/amd/build/search.min.js new file mode 100644 index 0000000000000..9f2b1aacce2b8 --- /dev/null +++ b/admin/tool/langimport/amd/build/search.min.js @@ -0,0 +1,2 @@ +define ("tool_langimport/search",["exports","core/pending","core/utils"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function d(a,b){return j(a)||h(a,b)||f(a,b)||e()}function e(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function f(a,b){if(!a)return;if("string"==typeof a)return g(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return g(a,b)}function g(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * Add search filtering of available language packs\n *\n * @module tool_langimport/search\n * @package tool_langimport\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\nimport {debounce} from 'core/utils';\n\nconst SELECTORS = {\n AVAILABLE_LANG_SELECT: 'select',\n AVAILABLE_LANG_SEARCH: '[data-action=\"search\"]',\n};\n\nconst DEBOUNCE_TIMER = 250;\n\n/**\n * Initialize module\n *\n * @param {Element} form\n */\nconst init = (form) => {\n const availableLangsElement = form.querySelector(SELECTORS.AVAILABLE_LANG_SELECT);\n\n const availableLangsFilter = (event) => {\n const pendingPromise = new Pending('tool_langimport/search:filter');\n\n // Remove existing options.\n availableLangsElement.querySelectorAll('option').forEach((option) => {\n option.remove();\n });\n\n // Filter for matching languages.\n const searchTerm = event.target.value.toLowerCase();\n const availableLanguages = JSON.parse(availableLangsElement.dataset.availableLanguages);\n const filteredLanguages = Object.keys(availableLanguages).reduce((matches, langcode) => {\n if (availableLanguages[langcode].toLowerCase().includes(searchTerm)) {\n matches[langcode] = availableLanguages[langcode];\n }\n return matches;\n }, []);\n\n // Re-create filtered options.\n Object.entries(filteredLanguages).forEach(([langcode, langname]) => {\n const option = document.createElement('option');\n option.value = langcode;\n option.innerText = langname;\n availableLangsElement.append(option);\n });\n\n pendingPromise.resolve();\n };\n\n // Cache initial available language options.\n const availableLanguages = {};\n availableLangsElement.querySelectorAll('option').forEach((option) => {\n availableLanguages[option.value] = option.text;\n });\n availableLangsElement.dataset.availableLanguages = JSON.stringify(availableLanguages);\n\n // Register event listeners on the search element.\n const availableLangsSearch = form.querySelector(SELECTORS.AVAILABLE_LANG_SEARCH);\n availableLangsSearch.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n }\n });\n\n // Debounce the event listener to allow the user to finish typing.\n availableLangsSearch.addEventListener('keyup', (event) => {\n const pendingPromise = new Pending('tool_langimport/search:keyup');\n\n debounce(availableLangsFilter, DEBOUNCE_TIMER)(event);\n setTimeout(() => {\n pendingPromise.resolve();\n }, DEBOUNCE_TIMER);\n });\n};\n\nexport default {\n init: init,\n};\n"],"file":"search.min.js"} \ No newline at end of file diff --git a/admin/tool/langimport/amd/src/search.js b/admin/tool/langimport/amd/src/search.js new file mode 100644 index 0000000000000..108a8cf3e193c --- /dev/null +++ b/admin/tool/langimport/amd/src/search.js @@ -0,0 +1,100 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Add search filtering of available language packs + * + * @module tool_langimport/search + * @package tool_langimport + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Pending from 'core/pending'; +import {debounce} from 'core/utils'; + +const SELECTORS = { + AVAILABLE_LANG_SELECT: 'select', + AVAILABLE_LANG_SEARCH: '[data-action="search"]', +}; + +const DEBOUNCE_TIMER = 250; + +/** + * Initialize module + * + * @param {Element} form + */ +const init = (form) => { + const availableLangsElement = form.querySelector(SELECTORS.AVAILABLE_LANG_SELECT); + + const availableLangsFilter = (event) => { + const pendingPromise = new Pending('tool_langimport/search:filter'); + + // Remove existing options. + availableLangsElement.querySelectorAll('option').forEach((option) => { + option.remove(); + }); + + // Filter for matching languages. + const searchTerm = event.target.value.toLowerCase(); + const availableLanguages = JSON.parse(availableLangsElement.dataset.availableLanguages); + const filteredLanguages = Object.keys(availableLanguages).reduce((matches, langcode) => { + if (availableLanguages[langcode].toLowerCase().includes(searchTerm)) { + matches[langcode] = availableLanguages[langcode]; + } + return matches; + }, []); + + // Re-create filtered options. + Object.entries(filteredLanguages).forEach(([langcode, langname]) => { + const option = document.createElement('option'); + option.value = langcode; + option.innerText = langname; + availableLangsElement.append(option); + }); + + pendingPromise.resolve(); + }; + + // Cache initial available language options. + const availableLanguages = {}; + availableLangsElement.querySelectorAll('option').forEach((option) => { + availableLanguages[option.value] = option.text; + }); + availableLangsElement.dataset.availableLanguages = JSON.stringify(availableLanguages); + + // Register event listeners on the search element. + const availableLangsSearch = form.querySelector(SELECTORS.AVAILABLE_LANG_SEARCH); + availableLangsSearch.addEventListener('keydown', (event) => { + if (event.key === 'Enter') { + event.preventDefault(); + } + }); + + // Debounce the event listener to allow the user to finish typing. + availableLangsSearch.addEventListener('keyup', (event) => { + const pendingPromise = new Pending('tool_langimport/search:keyup'); + + debounce(availableLangsFilter, DEBOUNCE_TIMER)(event); + setTimeout(() => { + pendingPromise.resolve(); + }, DEBOUNCE_TIMER); + }); +}; + +export default { + init: init, +}; diff --git a/admin/tool/langimport/lang/en/tool_langimport.php b/admin/tool/langimport/lang/en/tool_langimport.php index 27739eda343ba..7fddab115f69f 100644 --- a/admin/tool/langimport/lang/en/tool_langimport.php +++ b/admin/tool/langimport/lang/en/tool_langimport.php @@ -45,6 +45,7 @@ $string['nolangupdateneeded'] = 'All your language packs are up to date, no update is needed'; $string['pluginname'] = 'Language packs'; $string['purgestringcaches'] = 'Purge string caches'; +$string['search'] = 'Search available language packs'; $string['selectlangs'] = 'Select languages to uninstall'; $string['uninstall'] = 'Uninstall selected language pack(s)'; $string['uninstallconfirm'] = 'You are about to completely uninstall these language packs: {$a}. Are you sure?'; diff --git a/admin/tool/langimport/styles.css b/admin/tool/langimport/styles.css index f23c99bc3d01e..e5a827167005b 100644 --- a/admin/tool/langimport/styles.css +++ b/admin/tool/langimport/styles.css @@ -3,3 +3,8 @@ float: none; width: 100%; } + +#page-admin-tool-langimport-index #menuuninstalllang, +#page-admin-tool-langimport-index #menupack { + height: 300px; +} diff --git a/admin/tool/langimport/templates/langimport.mustache b/admin/tool/langimport/templates/langimport.mustache index 71a7515084b87..32f423965760c 100644 --- a/admin/tool/langimport/templates/langimport.mustache +++ b/admin/tool/langimport/templates/langimport.mustache @@ -105,6 +105,13 @@ {{/toinstalloptions}} +
+ {{< core/search_input_auto }} + {{$label}} + {{#str}} search, tool_langimport {{/str}} + {{/label}} + {{/ core/search_input_auto }} +
@@ -115,3 +122,8 @@ {{/caninstall}}
+{{#js}} + require(['tool_langimport/search'], function(search) { + search.init(document.querySelector('#installform')); + }); +{{/js}} diff --git a/admin/tool/langimport/tests/behat/manage_langpacks.feature b/admin/tool/langimport/tests/behat/manage_langpacks.feature index 63fcc5377faf7..6af3c4c8d3248 100644 --- a/admin/tool/langimport/tests/behat/manage_langpacks.feature +++ b/admin/tool/langimport/tests/behat/manage_langpacks.feature @@ -20,6 +20,16 @@ Feature: Manage language packs And I should see "The language pack 'en_ar' was installed." And I log out + @javascript + Scenario: Search for available language pack + Given I log in as "admin" + And I navigate to "Language > Language packs" in site administration + When I set the field "Search available language packs" to "pirate" + Then the "Available language packs" select box should not contain "es" + And I set the field "Available language packs" to "en_ar" + And I press "Install selected language pack(s)" + And I should see "Language pack 'en_ar' was successfully installed" + Scenario: Update language pack Given outdated langpack 'en_ar' is installed And I log in as "admin"