mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 00:12:56 +02:00
MDL-65896 core: create emoji auto complete
This commit is contained in:
parent
8f80d18766
commit
75962db917
2
lib/amd/build/emoji/auto_complete.min.js
vendored
Normal file
2
lib/amd/build/emoji/auto_complete.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
define ("core/emoji/auto_complete",["exports","core/emoji/data","core/templates","core/utils","core/localstorage","core/key_codes"],function(a,b,c,d,e,f){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}(b);e=g(e);f=g(f);function g(a){return a&&a.__esModule?a:{default:a}}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}function j(a){return m(a)||l(a)||k()}function k(){throw new TypeError("Invalid attempt to spread non-iterable instance")}function l(a){if(Symbol.iterator in Object(a)||"[object Arguments]"===Object.prototype.toString.call(a))return Array.from(a)}function m(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);b<a.length;b++){c[b]=a[b]}return c}}var n="moodle-recent-emojis",o={EMOJI_BUTTON:"[data-region=\"emoji-button\"]",ACTIVE_EMOJI_BUTTON:"[data-region=\"emoji-button\"].active"},p=function(){var a=e.default.get(n);return a?JSON.parse(a):[]},q=function(a,b){var c={unified:a,shortnames:[b]},d=p(),f=[c].concat(j(d.filter(function(a){return a.unified!=c.unified})));f=f.slice(0,27);e.default.set(n,JSON.stringify(f))},r=function(a){var c=b.byShortName[a];if(c){var d=c.split("-").map(function(a){return"0x".concat(a)});return String.fromCodePoint.apply(null,d)}else{return null}},s=function(){var a=i(regeneratorRuntime.mark(function a(d,e){var f,g;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:f={emojis:e.map(function(a,c){return{active:0===c,emojitext:r(a),displayshortname:":".concat(a,":"),shortname:a,unified:b.byShortName[a]}})};a.next=3;return(0,c.render)("core/emoji/auto_complete",f);case 3:g=a.sent;d.innerHTML=g;case 5:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),t=function(a,c){if(""===a){return p().map(function(a){return a.shortnames[0]}).slice(0,c)}else{a=a.toLowerCase();return Object.keys(b.byShortName).filter(function(b){return b.includes(a)}).slice(0,c)}},u=function(a,b){var c=a.slice(0,b).match(/(\S*)$/),d=a.slice(b).match(/^(\S*)/),e="",f="";if(c){e=c[c.length-1]}if(d){f=d[d.length-1]}return"".concat(e).concat(f)},v=function(a){return /^:[^:\s]+:$/.test(a)},w=function(a){return /^:[^:\s]*$/.test(a)},x=function(a){return a.replace(/:/g,"")},y=function(a){return a.querySelector(o.ACTIVE_EMOJI_BUTTON)},z=function(a){var b=y(a),c=b.previousElementSibling;if(c){b.classList.remove("active");c.classList.add("active");c.scrollIntoView({behaviour:"smooth",inline:"center"})}},A=function(a){var b=y(a),c=b.nextElementSibling;if(c){b.classList.remove("active");c.classList.add("active");c.scrollIntoView({behaviour:"smooth",inline:"center"})}},B=function(a,b){var c=a.getAttribute("data-short-name"),d=a.getAttribute("data-unified");q(d,c);b(a.innerHTML.trim())};a.default=function(a,c,g,h){var i=!1,j="";c.addEventListener("keyup",(0,d.debounce)(function(){var d=c.value,e=c.selectionStart,f=u(d,e);if(f===j){return}else{j=f}if(v(f)){var k=x(f),l=r(k);i=!1;if(l){q(b.byShortName[k],k);h(l)}}else if(w(f)){var m=t(x(f),50);if(m.length){s(a,m);i=!0}else{i=!1}}else{i=!1}g(i)},200));c.addEventListener("keydown",function(b){if(i){var c=b.shiftKey||b.metaKey||b.altKey||b.ctrlKey;if(!c){switch(b.which){case f.default.escape:i=!1;g(!1);break;case f.default.arrowLeft:z(a);b.preventDefault();break;case f.default.arrowRight:A(a);b.preventDefault();break;case f.default.enter:B(y(a),h);b.preventDefault();b.stopPropagation();break;}}}});a.addEventListener("click",function(a){var b=a.target;if(b.matches(o.EMOJI_BUTTON)){B(b,h)}})};return a.default});
|
||||
//# sourceMappingURL=auto_complete.min.js.map
|
1
lib/amd/build/emoji/auto_complete.min.js.map
Normal file
1
lib/amd/build/emoji/auto_complete.min.js.map
Normal file
File diff suppressed because one or more lines are too long
326
lib/amd/src/emoji/auto_complete.js
Normal file
326
lib/amd/src/emoji/auto_complete.js
Normal file
@ -0,0 +1,326 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Emoji auto complete.
|
||||
*
|
||||
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
import * as EmojiData from 'core/emoji/data';
|
||||
import {render as renderTemplate} from 'core/templates';
|
||||
import {debounce} from 'core/utils';
|
||||
import LocalStorage from 'core/localstorage';
|
||||
import KeyCodes from 'core/key_codes';
|
||||
|
||||
const INPUT_DEBOUNCE_TIMER = 200;
|
||||
const SUGGESTION_LIMIT = 50;
|
||||
const MAX_RECENT_COUNT = 27;
|
||||
const RECENT_EMOJIS_STORAGE_KEY = 'moodle-recent-emojis';
|
||||
|
||||
const SELECTORS = {
|
||||
EMOJI_BUTTON: '[data-region="emoji-button"]',
|
||||
ACTIVE_EMOJI_BUTTON: '[data-region="emoji-button"].active',
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of recent emojis data from local storage.
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
const getRecentEmojis = () => {
|
||||
const storedData = LocalStorage.get(RECENT_EMOJIS_STORAGE_KEY);
|
||||
return storedData ? JSON.parse(storedData) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an emoji data to the set of recent emojis. The new set of recent emojis are
|
||||
* saved in local storage.
|
||||
*
|
||||
* @param {String} unified The char chodes for the emoji
|
||||
* @param {String} shortName The emoji short name
|
||||
*/
|
||||
const addRecentEmoji = (unified, shortName) => {
|
||||
const newEmoji = {
|
||||
unified,
|
||||
shortnames: [shortName]
|
||||
};
|
||||
const recentEmojis = getRecentEmojis();
|
||||
// Add the new emoji to the start of the list of recent emojis.
|
||||
let newRecentEmojis = [newEmoji, ...recentEmojis.filter(emoji => emoji.unified != newEmoji.unified)];
|
||||
// Limit the number of recent emojis.
|
||||
newRecentEmojis = newRecentEmojis.slice(0, MAX_RECENT_COUNT);
|
||||
|
||||
LocalStorage.set(RECENT_EMOJIS_STORAGE_KEY, JSON.stringify(newRecentEmojis));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the actual emoji string from the short name.
|
||||
*
|
||||
* @param {String} shortName Emoji short name
|
||||
* @return {String|null}
|
||||
*/
|
||||
const getEmojiTextFromShortName = (shortName) => {
|
||||
const unified = EmojiData.byShortName[shortName];
|
||||
|
||||
if (unified) {
|
||||
const charCodes = unified.split('-').map(code => `0x${code}`);
|
||||
return String.fromCodePoint.apply(null, charCodes);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the auto complete list for the given short names.
|
||||
*
|
||||
* @param {Element} root The root container for the emoji auto complete
|
||||
* @param {Array} shortNames The list of short names for emoji suggestions to show
|
||||
*/
|
||||
const render = async (root, shortNames) => {
|
||||
const renderContext = {
|
||||
emojis: shortNames.map((shortName, index) => {
|
||||
return {
|
||||
active: index === 0,
|
||||
emojitext: getEmojiTextFromShortName(shortName),
|
||||
displayshortname: `:${shortName}:`,
|
||||
shortname: shortName,
|
||||
unified: EmojiData.byShortName[shortName]
|
||||
};
|
||||
})
|
||||
};
|
||||
const html = await renderTemplate('core/emoji/auto_complete', renderContext);
|
||||
root.innerHTML = html;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of emoji short names that include the given search term. If
|
||||
* the search term is an empty string then the list of recently used emojis
|
||||
* will be returned.
|
||||
*
|
||||
* @param {String} searchTerm Text to match on
|
||||
* @param {Number} limit Maximum number of results to return
|
||||
* @return {Array}
|
||||
*/
|
||||
const searchEmojis = (searchTerm, limit) => {
|
||||
if (searchTerm === '') {
|
||||
return getRecentEmojis().map(data => data.shortnames[0]).slice(0, limit);
|
||||
} else {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
return Object.keys(EmojiData.byShortName)
|
||||
.filter(shortName => shortName.includes(searchTerm))
|
||||
.slice(0, limit);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current word at the given position (index) within the text.
|
||||
*
|
||||
* @param {String} text The text to process
|
||||
* @param {Number} position The position (index) within the text to match the word
|
||||
* @return {String}
|
||||
*/
|
||||
const getWordFromPosition = (text, position) => {
|
||||
const startMatches = text.slice(0, position).match(/(\S*)$/);
|
||||
const endMatches = text.slice(position).match(/^(\S*)/);
|
||||
let startText = '';
|
||||
let endText = '';
|
||||
|
||||
if (startMatches) {
|
||||
startText = startMatches[startMatches.length - 1];
|
||||
}
|
||||
|
||||
if (endMatches) {
|
||||
endText = endMatches[endMatches.length - 1];
|
||||
}
|
||||
|
||||
return `${startText}${endText}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given text is a full short name, i.e. has leading and trialing colon
|
||||
* characters.
|
||||
*
|
||||
* @param {String} text The text to process
|
||||
* @return {Bool}
|
||||
*/
|
||||
const isCompleteShortName = text => /^:[^:\s]+:$/.test(text);
|
||||
|
||||
/**
|
||||
* Check if the given text is a partial short name, i.e. has a leading colon but no
|
||||
* trailing colon.
|
||||
*
|
||||
* @param {String} text The text to process
|
||||
* @return {Bool}
|
||||
*/
|
||||
const isPartialShortName = text => /^:[^:\s]*$/.test(text);
|
||||
|
||||
/**
|
||||
* Remove the colon characters from the given text.
|
||||
*
|
||||
* @param {String} text The text to process
|
||||
* @return {String}
|
||||
*/
|
||||
const getShortNameFromText = text => text.replace(/:/g, '');
|
||||
|
||||
/**
|
||||
* Get the currently active emoji button element in the list of suggestions.
|
||||
*
|
||||
* @param {Element} root The emoji auto complete container element
|
||||
* @return {Element|null}
|
||||
*/
|
||||
const getActiveEmojiSuggestion = (root) => {
|
||||
return root.querySelector(SELECTORS.ACTIVE_EMOJI_BUTTON);
|
||||
};
|
||||
|
||||
/**
|
||||
* Make the previous sibling of the current active emoji active.
|
||||
*
|
||||
* @param {Element} root The emoji auto complete container element
|
||||
*/
|
||||
const selectPreviousEmojiSuggestion = (root) => {
|
||||
const activeEmojiSuggestion = getActiveEmojiSuggestion(root);
|
||||
const previousSuggestion = activeEmojiSuggestion.previousElementSibling;
|
||||
|
||||
if (previousSuggestion) {
|
||||
activeEmojiSuggestion.classList.remove('active');
|
||||
previousSuggestion.classList.add('active');
|
||||
previousSuggestion.scrollIntoView({behaviour: 'smooth', inline: 'center'});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make the next sibling to the current active emoji active.
|
||||
*
|
||||
* @param {Element} root The emoji auto complete container element
|
||||
*/
|
||||
const selectNextEmojiSuggestion = (root) => {
|
||||
const activeEmojiSuggestion = getActiveEmojiSuggestion(root);
|
||||
const nextSuggestion = activeEmojiSuggestion.nextElementSibling;
|
||||
|
||||
if (nextSuggestion) {
|
||||
activeEmojiSuggestion.classList.remove('active');
|
||||
nextSuggestion.classList.add('active');
|
||||
nextSuggestion.scrollIntoView({behaviour: 'smooth', inline: 'center'});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger the select callback for the given emoji button element.
|
||||
*
|
||||
* @param {Element} element The emoji button element
|
||||
* @param {Function} selectCallback The callback for when the user selects an emoji
|
||||
*/
|
||||
const selectEmojiElement = (element, selectCallback) => {
|
||||
const shortName = element.getAttribute('data-short-name');
|
||||
const unified = element.getAttribute('data-unified');
|
||||
addRecentEmoji(unified, shortName);
|
||||
selectCallback(element.innerHTML.trim());
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise the emoji auto complete.
|
||||
*
|
||||
* @param {Element} root The root container element for the auto complete
|
||||
* @param {Element} textArea The text area element to monitor for auto complete
|
||||
* @param {Function} hasSuggestionCallback Callback for when there are auto-complete suggestions
|
||||
* @param {Function} selectCallback Callback for when the user selects an emoji
|
||||
*/
|
||||
export default (root, textArea, hasSuggestionCallback, selectCallback) => {
|
||||
let hasSuggestions = false;
|
||||
let previousSearchText = '';
|
||||
|
||||
// Debounce the listener so that each keypress delays the execution of the handler. The
|
||||
// handler should only run 200 milliseconds after the last keypress.
|
||||
textArea.addEventListener('keyup', debounce(() => {
|
||||
// This is a "keyup" listener so that it only executes after the text area value
|
||||
// has been updated.
|
||||
const text = textArea.value;
|
||||
const cursorPos = textArea.selectionStart;
|
||||
const searchText = getWordFromPosition(text, cursorPos);
|
||||
|
||||
if (searchText === previousSearchText) {
|
||||
// Nothing has changed so no need to take any action.
|
||||
return;
|
||||
} else {
|
||||
previousSearchText = searchText;
|
||||
}
|
||||
|
||||
if (isCompleteShortName(searchText)) {
|
||||
// If the user has entered a full short name (with leading and trialing colons)
|
||||
// then see if we can find a match for it and auto complete it.
|
||||
const shortName = getShortNameFromText(searchText);
|
||||
const emojiText = getEmojiTextFromShortName(shortName);
|
||||
hasSuggestions = false;
|
||||
if (emojiText) {
|
||||
addRecentEmoji(EmojiData.byShortName[shortName], shortName);
|
||||
selectCallback(emojiText);
|
||||
}
|
||||
} else if (isPartialShortName(searchText)) {
|
||||
// If the user has entered a partial short name (leading colon but no trailing) then
|
||||
// search on the text to see if we can find some suggestions for them.
|
||||
const suggestions = searchEmojis(getShortNameFromText(searchText), SUGGESTION_LIMIT);
|
||||
|
||||
if (suggestions.length) {
|
||||
render(root, suggestions);
|
||||
hasSuggestions = true;
|
||||
} else {
|
||||
hasSuggestions = false;
|
||||
}
|
||||
} else {
|
||||
hasSuggestions = false;
|
||||
}
|
||||
|
||||
hasSuggestionCallback(hasSuggestions);
|
||||
}, INPUT_DEBOUNCE_TIMER));
|
||||
|
||||
textArea.addEventListener('keydown', (e) => {
|
||||
if (hasSuggestions) {
|
||||
const isModifierPressed = (e.shiftKey || e.metaKey || e.altKey || e.ctrlKey);
|
||||
if (!isModifierPressed) {
|
||||
switch (e.which) {
|
||||
case KeyCodes.escape:
|
||||
// Escape key closes the auto complete.
|
||||
hasSuggestions = false;
|
||||
hasSuggestionCallback(false);
|
||||
break;
|
||||
case KeyCodes.arrowLeft:
|
||||
// Arrow keys navigate through the list of suggetions.
|
||||
selectPreviousEmojiSuggestion(root);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case KeyCodes.arrowRight:
|
||||
// Arrow keys navigate through the list of suggetions.
|
||||
selectNextEmojiSuggestion(root);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case KeyCodes.enter:
|
||||
// Enter key selects the current suggestion.
|
||||
selectEmojiElement(getActiveEmojiSuggestion(root), selectCallback);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
root.addEventListener('click', (e) => {
|
||||
const target = e.target;
|
||||
if (target.matches(SELECTORS.EMOJI_BUTTON)) {
|
||||
selectEmojiElement(target, selectCallback);
|
||||
}
|
||||
});
|
||||
};
|
52
lib/templates/emoji/auto_complete.mustache
Normal file
52
lib/templates/emoji/auto_complete.mustache
Normal file
@ -0,0 +1,52 @@
|
||||
{{!
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/emoji/auto_complete
|
||||
|
||||
This template will render the emoji auto complete.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
*
|
||||
|
||||
Context variables required for this template:
|
||||
*
|
||||
|
||||
Example context (json):
|
||||
{}
|
||||
|
||||
}}
|
||||
|
||||
<div
|
||||
data-region="emoji-auto-complete"
|
||||
class="emoji-auto-complete bg-white d-flex align-items-center"
|
||||
>
|
||||
{{#emojis}}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link btn-icon p-0 rounded-lg emoji-button {{#active}}active{{/active}}"
|
||||
title="{{displayshortname}}"
|
||||
data-region="emoji-button"
|
||||
data-unified="{{unified}}"
|
||||
data-short-name="{{shortname}}"
|
||||
>
|
||||
{{emojitext}}
|
||||
</button>
|
||||
{{/emojis}}
|
||||
</div>
|
@ -2355,3 +2355,18 @@ $picker-emojis-per-row: 7 !default;
|
||||
width: $picker-width-xs;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-auto-complete {
|
||||
height: $picker-row-height;
|
||||
|
||||
.btn.btn-link.btn-icon.emoji-button {
|
||||
height: $picker-emoji-button-size;
|
||||
width: $picker-emoji-button-size;
|
||||
line-height: $picker-emoji-button-size;
|
||||
font-size: $picker-emoji-button-font-size;
|
||||
|
||||
&.active {
|
||||
background-color: $gray-200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11520,6 +11520,16 @@ div.editor_atto_toolbar button .icon {
|
||||
.emoji-picker {
|
||||
width: 320px; } }
|
||||
|
||||
.emoji-auto-complete {
|
||||
height: 40px; }
|
||||
.emoji-auto-complete .btn.btn-link.btn-icon.emoji-button, .emoji-auto-complete .btn.btn-icon.emoji-button, .emoji-auto-complete #page-grade-grading-manage .actions .btn-icon.emoji-button.action, #page-grade-grading-manage .actions .emoji-auto-complete .btn-icon.emoji-button.action, .emoji-auto-complete #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-icon.emoji-button, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .emoji-auto-complete input.btn-icon.emoji-button, .emoji-auto-complete #rubric-rubric.gradingform_rubric .btn-icon.emoji-button.addcriterion, #rubric-rubric.gradingform_rubric .emoji-auto-complete .btn-icon.emoji-button.addcriterion {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
line-height: 40px;
|
||||
font-size: 24px; }
|
||||
.emoji-auto-complete .btn.btn-link.btn-icon.emoji-button.active, .emoji-auto-complete .btn.btn-icon.emoji-button.active, .emoji-auto-complete #page-grade-grading-manage .actions .btn-icon.emoji-button.active.action, #page-grade-grading-manage .actions .emoji-auto-complete .btn-icon.emoji-button.active.action, .emoji-auto-complete #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-icon.emoji-button.active, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .emoji-auto-complete input.btn-icon.emoji-button.active, .emoji-auto-complete #rubric-rubric.gradingform_rubric .btn-icon.emoji-button.active.addcriterion, #rubric-rubric.gradingform_rubric .emoji-auto-complete .btn-icon.emoji-button.active.addcriterion {
|
||||
background-color: #e9ecef; }
|
||||
|
||||
.icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
|
@ -11775,6 +11775,16 @@ div.editor_atto_toolbar button .icon {
|
||||
.emoji-picker {
|
||||
width: 320px; } }
|
||||
|
||||
.emoji-auto-complete {
|
||||
height: 40px; }
|
||||
.emoji-auto-complete .btn.btn-link.btn-icon.emoji-button, .emoji-auto-complete .btn.btn-icon.emoji-button, .emoji-auto-complete #page-grade-grading-manage .actions .btn-icon.emoji-button.action, #page-grade-grading-manage .actions .emoji-auto-complete .btn-icon.emoji-button.action, .emoji-auto-complete #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-icon.emoji-button, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .emoji-auto-complete input.btn-icon.emoji-button, .emoji-auto-complete #rubric-rubric.gradingform_rubric .btn-icon.emoji-button.addcriterion, #rubric-rubric.gradingform_rubric .emoji-auto-complete .btn-icon.emoji-button.addcriterion {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
line-height: 40px;
|
||||
font-size: 24px; }
|
||||
.emoji-auto-complete .btn.btn-link.btn-icon.emoji-button.active, .emoji-auto-complete .btn.btn-icon.emoji-button.active, .emoji-auto-complete #page-grade-grading-manage .actions .btn-icon.emoji-button.active.action, #page-grade-grading-manage .actions .emoji-auto-complete .btn-icon.emoji-button.active.action, .emoji-auto-complete #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-icon.emoji-button.active, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .emoji-auto-complete input.btn-icon.emoji-button.active, .emoji-auto-complete #rubric-rubric.gradingform_rubric .btn-icon.emoji-button.active.addcriterion, #rubric-rubric.gradingform_rubric .emoji-auto-complete .btn-icon.emoji-button.active.addcriterion {
|
||||
background-color: #e9ecef; }
|
||||
|
||||
.icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
|
Loading…
x
Reference in New Issue
Block a user