diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/ajax/plugin.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/ajax/plugin.js
new file mode 100644
index 00000000000..cee8b2569f1
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/ajax/plugin.js
@@ -0,0 +1,241 @@
+/* global ActiveXObject */
+/**
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+ * CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+ */
+
+/**
+ * @fileOverview Defines the {@link CKEDITOR.ajax} object, which stores Ajax methods for
+ * data loading.
+ */
+
+( function() {
+ CKEDITOR.plugins.add( 'ajax', {
+ requires: 'xml'
+ } );
+
+ /**
+ * Ajax methods for data loading.
+ *
+ * @class
+ * @singleton
+ */
+ CKEDITOR.ajax = ( function() {
+ function createXMLHttpRequest() {
+ // In IE, using the native XMLHttpRequest for local files may throw
+ // "Access is Denied" errors.
+ if ( !CKEDITOR.env.ie || location.protocol != 'file:' ) {
+ try {
+ return new XMLHttpRequest();
+ } catch ( e ) {
+ }
+ }
+
+ try {
+ return new ActiveXObject( 'Msxml2.XMLHTTP' );
+ } catch ( e ) {}
+ try {
+ return new ActiveXObject( 'Microsoft.XMLHTTP' );
+ } catch ( e ) {}
+
+ return null;
+ }
+
+ function checkStatus( xhr ) {
+ // HTTP Status Codes:
+ // 2xx : Success
+ // 304 : Not Modified
+ // 0 : Returned when running locally (file://)
+ // 1223 : IE may change 204 to 1223 (see http://dev.jquery.com/ticket/1450)
+
+ return ( xhr.readyState == 4 && ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status === 0 || xhr.status == 1223 ) );
+ }
+
+ function getResponse( xhr, type ) {
+ if ( !checkStatus( xhr ) ) {
+ return null;
+ }
+
+ switch ( type ) {
+ case 'text':
+ return xhr.responseText;
+ case 'xml':
+ var xml = xhr.responseXML;
+ return new CKEDITOR.xml( xml && xml.firstChild ? xml : xhr.responseText );
+ case 'arraybuffer':
+ return xhr.response;
+ default:
+ return null;
+ }
+ }
+
+ function load( url, callback, responseType ) {
+ var async = !!callback;
+
+ var xhr = createXMLHttpRequest();
+
+ if ( !xhr )
+ return null;
+
+ if ( async && responseType !== 'text' && responseType !== 'xml' ) {
+ xhr.responseType = responseType;
+ }
+
+ xhr.open( 'GET', url, async );
+
+ if ( async ) {
+ // TODO: perform leak checks on this closure.
+ xhr.onreadystatechange = function() {
+ if ( xhr.readyState == 4 ) {
+ callback( getResponse( xhr, responseType ) );
+ xhr = null;
+ }
+ };
+ }
+
+ xhr.send( null );
+
+ return async ? '' : getResponse( xhr, responseType );
+ }
+
+ function post( url, data, contentType, callback, responseType ) {
+ var xhr = createXMLHttpRequest();
+
+ if ( !xhr )
+ return null;
+
+ xhr.open( 'POST', url, true );
+
+ xhr.onreadystatechange = function() {
+ if ( xhr.readyState == 4 ) {
+ if ( callback ) {
+ callback( getResponse( xhr, responseType ) );
+ }
+ xhr = null;
+ }
+ };
+
+ xhr.setRequestHeader( 'Content-type', contentType || 'application/x-www-form-urlencoded; charset=UTF-8' );
+
+ xhr.send( data );
+ }
+
+ return {
+ /**
+ * Loads data from a given URL.
+ *
+ * // Load data synchronously.
+ * var data = CKEDITOR.ajax.load( 'somedata.txt' );
+ * alert( data );
+ *
+ * // Load data asynchronously.
+ * var data = CKEDITOR.ajax.load( 'somedata.txt', function( data ) {
+ * alert( data );
+ * } );
+ *
+ * @param {String} url The URL from which the data is loaded.
+ * @param {Function} [callback] A callback function to be called on
+ * data load. If not provided, the data will be loaded synchronously.
+ * @param {String} [responseType='text'] Defines type of returned data.
+ * Currently supports: `text`, `xml`, `arraybuffer`. This parameter was introduced in `4.16.0`.
+ * @returns {String/null} The loaded data for synchronous request. For asynchronous requests -
+ * empty string. For invalid requests - `null`.
+ */
+ load: function( url, callback, responseType ) {
+ responseType = responseType || 'text';
+
+ return load( url, callback, responseType );
+ },
+
+ /**
+ * Creates an asynchronous POST `XMLHttpRequest` of the given `url`, `data` and optional `contentType`.
+ * Once the request is done, regardless if it is successful or not, the `callback` is called
+ * with `XMLHttpRequest#responseText` or `null` as an argument.
+ *
+ * CKEDITOR.ajax.post( 'url/post.php', 'foo=bar', null, function( data ) {
+ * console.log( data );
+ * } );
+ *
+ * CKEDITOR.ajax.post( 'url/post.php', JSON.stringify( { foo: 'bar' } ), 'application/json', function( data ) {
+ * console.log( data );
+ * } );
+ *
+ * @since 4.4
+ * @param {String} url The URL of the request.
+ * @param {String/Object/Array} data Data passed to `XMLHttpRequest#send`.
+ * @param {String} [contentType='application/x-www-form-urlencoded; charset=UTF-8'] The value of the `Content-type` header.
+ * @param {Function} [callback] A callback executed asynchronously with `XMLHttpRequest#responseText` or `null` as an argument,
+ * depending on the `status` of the request.
+ */
+ post: function( url, data, contentType, callback ) {
+ return post( url, data, contentType, callback, 'text' );
+ },
+
+ /**
+ * Loads data from a given URL as XML.
+ *
+ * // Load XML synchronously.
+ * var xml = CKEDITOR.ajax.loadXml( 'somedata.xml' );
+ * alert( xml.getInnerXml( '//' ) );
+ *
+ * // Load XML asynchronously.
+ * var data = CKEDITOR.ajax.loadXml( 'somedata.xml', function( xml ) {
+ * alert( xml.getInnerXml( '//' ) );
+ * } );
+ *
+ * @param {String} url The URL from which the data is loaded.
+ * @param {Function} [callback] A callback function to be called on
+ * data load. If not provided, the data will be loaded synchronously.
+ * @returns {CKEDITOR.xml} An XML object storing the loaded data for synchronous
+ * request. For asynchronous requests - empty string. For invalid requests - `null`.
+ */
+ loadXml: function( url, callback ) {
+ return load( url, callback, 'xml' );
+ },
+
+ /**
+ * Loads data from a given URL as text.
+ *
+ * // Load text synchronously.
+ * var text = CKEDITOR.ajax.loadText( 'somedata.txt' );
+ * alert( text );
+ *
+ * // Load text asynchronously.
+ * var data = CKEDITOR.ajax.loadText( 'somedata.txt', function( textData ) {
+ * alert( textData );
+ * } );
+ *
+ * @param {String} url The URL from which the data is loaded.
+ * @param {Function} [callback] A callback function to be called on
+ * data load. If not provided, the data will be loaded synchronously.
+ * @returns {String} String storing the loaded data for synchronous
+ * request. For asynchronous requests - empty string. For invalid requests - `null`.
+ */
+ loadText: function( url, callback ) {
+ return load( url, callback, 'text' );
+ },
+
+ /**
+ * Loads data from a given URL as binary data.
+ *
+ * // Load data synchronously.
+ * var binaryData = CKEDITOR.ajax.loadBinary( 'somedata.png' );
+ * alert( binaryData );
+ *
+ * // Load data asynchronously.
+ * var data = CKEDITOR.ajax.loadBinary( 'somedata.png', function( binaryData ) {
+ * alert( binaryData );
+ * } );
+ *
+ * @param {String} url The URL from which the data is loaded.
+ * @param {Function} [callback] A callback function to be called on
+ * data load. If not provided, the data will be loaded synchronously.
+ * @returns {ArrayBuffer} ArrayBuffer storing the loaded data for synchronous
+ * request. For asynchronous requests - empty string. For invalid requests - `null`.
+ */
+ loadBinary: function( url, callback ) {
+ return load( url, callback, 'arraybuffer' );
+ }
+ };
+ } )();
+} )();
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/autocomplete/plugin.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/autocomplete/plugin.js
new file mode 100644
index 00000000000..99ed0b4bc76
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/autocomplete/plugin.js
@@ -0,0 +1,1694 @@
+/**
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+ * CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+ */
+
+'use strict';
+
+( function() {
+
+ CKEDITOR.plugins.add( 'autocomplete', {
+ requires: 'textwatcher',
+ onLoad: function() {
+ CKEDITOR.document.appendStyleSheet( this.path + 'skins/default.css' );
+ },
+ isSupportedEnvironment: function() {
+ return !CKEDITOR.env.ie || CKEDITOR.env.version > 8;
+ }
+ } );
+
+ /**
+ * The main class implementing a generic [Autocomplete](https://ckeditor.com/cke4/addon/autocomplete) feature in the editor.
+ * It acts as a controller that works with the {@link CKEDITOR.plugins.autocomplete.model model} and
+ * {@link CKEDITOR.plugins.autocomplete.view view} classes.
+ *
+ * It is possible to maintain multiple autocomplete instances for a single editor at a time.
+ * In order to create an autocomplete instance use its {@link #constructor constructor}.
+ *
+ * @class CKEDITOR.plugins.autocomplete
+ * @since 4.10.0
+ * @constructor Creates a new instance of autocomplete and attaches it to the editor.
+ *
+ * In order to initialize the autocomplete feature it is enough to instantiate this class with
+ * two required callbacks:
+ *
+ * * {@link CKEDITOR.plugins.autocomplete.configDefinition#textTestCallback config.textTestCallback} – A function being called by
+ * the {@link CKEDITOR.plugins.textWatcher text watcher} plugin, as new text is being inserted.
+ * Its purpose is to determine whether a given range should be matched or not.
+ * See {@link CKEDITOR.plugins.textWatcher#constructor} for more details.
+ * There is also {@link CKEDITOR.plugins.textMatch#match} which is a handy helper for that purpose.
+ * * {@link CKEDITOR.plugins.autocomplete.configDefinition#dataCallback config.dataCallback} – A function that should return
+ * (through its callback) suggestion data for the current query string.
+ *
+ * # Creating an autocomplete instance
+ *
+ * Depending on your use case, put this code in the {@link CKEDITOR.pluginDefinition#init} callback of your
+ * plugin or, for example, in the {@link CKEDITOR.editor#instanceReady} event listener. Ensure that you loaded the
+ * {@link CKEDITOR.plugins.textMatch Text Match} plugin.
+ *
+ * ```javascript
+ * var itemsArray = [ { id: 1, name: '@Andrew' }, { id: 2, name: '@Kate' } ];
+ *
+ * // Called when the user types in the editor or moves the caret.
+ * // The range represents the caret position.
+ * function textTestCallback( range ) {
+ * // You do not want to autocomplete a non-empty selection.
+ * if ( !range.collapsed ) {
+ * return null;
+ * }
+ *
+ * // Use the text match plugin which does the tricky job of doing
+ * // a text search in the DOM. The matchCallback function should return
+ * // a matching fragment of the text.
+ * return CKEDITOR.plugins.textMatch.match( range, matchCallback );
+ * }
+ *
+ * // Returns the position of the matching text.
+ * // It matches with a word starting from the '@' character
+ * // up to the caret position.
+ * function matchCallback( text, offset ) {
+ * // Get the text before the caret.
+ * var left = text.slice( 0, offset ),
+ * // Will look for an '@' character followed by word characters.
+ * match = left.match( /@\w*$/ );
+ *
+ * if ( !match ) {
+ * return null;
+ * }
+ *
+ * return { start: match.index, end: offset };
+ * }
+ *
+ * // Returns (through its callback) the suggestions for the current query.
+ * function dataCallback( matchInfo, callback ) {
+ * // Simple search.
+ * // Filter the entire items array so only the items that start
+ * // with the query remain.
+ * var suggestions = itemsArray.filter( function( item ) {
+ * return item.name.toLowerCase().indexOf( matchInfo.query.toLowerCase() ) == 0;
+ * } );
+ *
+ * // Note: The callback function can also be executed asynchronously
+ * // so dataCallback can do XHR requests or use any other asynchronous API.
+ * callback( suggestions );
+ * }
+ *
+ * // Finally, instantiate the autocomplete class.
+ * new CKEDITOR.plugins.autocomplete( editor, {
+ * textTestCallback: textTestCallback,
+ * dataCallback: dataCallback
+ * } );
+ * ```
+ *
+ * # Changing the behavior of the autocomplete class by subclassing it
+ *
+ * This plugin will expose a `CKEDITOR.plugins.customAutocomplete` class which uses
+ * a custom view that positions the panel relative to the {@link CKEDITOR.editor#container}.
+ *
+ * ```javascript
+ * CKEDITOR.plugins.add( 'customautocomplete', {
+ * requires: 'autocomplete',
+ *
+ * onLoad: function() {
+ * var View = CKEDITOR.plugins.autocomplete.view,
+ * Autocomplete = CKEDITOR.plugins.autocomplete;
+ *
+ * function CustomView( editor ) {
+ * // Call the parent class constructor.
+ * View.call( this, editor );
+ * }
+ * // Inherit the view methods.
+ * CustomView.prototype = CKEDITOR.tools.prototypedCopy( View.prototype );
+ *
+ * // Change the positioning of the panel, so it is stretched
+ * // to 100% of the editor container width and is positioned
+ * // relative to the editor container.
+ * CustomView.prototype.updatePosition = function( range ) {
+ * var caretRect = this.getViewPosition( range ),
+ * container = this.editor.container;
+ *
+ * this.setPosition( {
+ * // Position the panel relative to the editor container.
+ * left: container.$.offsetLeft,
+ * top: caretRect.top,
+ * bottom: caretRect.bottom
+ * } );
+ * // Stretch the panel to 100% of the editor container width.
+ * this.element.setStyle( 'width', container.getSize( 'width' ) + 'px' );
+ * };
+ *
+ * function CustomAutocomplete( editor, configDefinition ) {
+ * // Call the parent class constructor.
+ * Autocomplete.call( this, editor, configDefinition );
+ * }
+ * // Inherit the autocomplete methods.
+ * CustomAutocomplete.prototype = CKEDITOR.tools.prototypedCopy( Autocomplete.prototype );
+ *
+ * CustomAutocomplete.prototype.getView = function() {
+ * return new CustomView( this.editor );
+ * }
+ *
+ * // Expose the custom autocomplete so it can be used later.
+ * CKEDITOR.plugins.customAutocomplete = CustomAutocomplete;
+ * }
+ * } );
+ * ```
+ * @param {CKEDITOR.editor} editor The editor to watch.
+ * @param {CKEDITOR.plugins.autocomplete.configDefinition} config Configuration object for this autocomplete instance.
+ */
+ function Autocomplete( editor, config ) {
+ var configKeystrokes = editor.config.autocomplete_commitKeystrokes || CKEDITOR.config.autocomplete_commitKeystrokes;
+
+ /**
+ * The editor instance that autocomplete is attached to.
+ *
+ * @readonly
+ * @property {CKEDITOR.editor}
+ */
+ this.editor = editor;
+
+ /**
+ * Indicates throttle threshold expressed in milliseconds, reducing text checks frequency.
+ *
+ * @property {Number} [throttle=20]
+ */
+ this.throttle = config.throttle !== undefined ? config.throttle : 20;
+
+ /**
+ * Indicates if a following space should be added after inserted match into an editor.
+ *
+ * @since 4.20.0
+ * @readonly
+ * @property {Boolean} [followingSpace]
+ */
+ this.followingSpace = config.followingSpace;
+
+ /**
+ * The autocomplete view instance.
+ *
+ * @readonly
+ * @property {CKEDITOR.plugins.autocomplete.view}
+ */
+ this.view = this.getView();
+
+ /**
+ * The autocomplete model instance.
+ *
+ * @readonly
+ * @property {CKEDITOR.plugins.autocomplete.model}
+ */
+ this.model = this.getModel( config.dataCallback );
+ this.model.itemsLimit = config.itemsLimit;
+
+ /**
+ * The autocomplete text watcher instance.
+ *
+ * @readonly
+ * @property {CKEDITOR.plugins.textWatcher}
+ */
+ this.textWatcher = this.getTextWatcher( config.textTestCallback );
+
+ /**
+ * The autocomplete keystrokes used to finish autocompletion with the selected view item.
+ * The property is using the {@link CKEDITOR.config#autocomplete_commitKeystrokes} configuration option as default keystrokes.
+ * You can change this property to set individual keystrokes for the plugin instance.
+ *
+ * @property {Number[]}
+ * @readonly
+ */
+ this.commitKeystrokes = CKEDITOR.tools.array.isArray( configKeystrokes ) ? configKeystrokes.slice() : [ configKeystrokes ];
+
+ /**
+ * Listeners registered by this autocomplete instance.
+ *
+ * @private
+ */
+ this._listeners = [];
+
+ /**
+ * Template of markup to be inserted as the autocomplete item gets committed.
+ *
+ * You can use {@link CKEDITOR.plugins.autocomplete.model.item item} properties to customize the template.
+ *
+ * ```javascript
+ * var outputTemplate = `#{ticket} ({name}) `;
+ * ```
+ *
+ * @readonly
+ * @property {CKEDITOR.template} [outputTemplate=null]
+ */
+ this.outputTemplate = config.outputTemplate !== undefined ? new CKEDITOR.template( config.outputTemplate ) : null;
+
+ if ( config.itemTemplate ) {
+ this.view.itemTemplate = new CKEDITOR.template( config.itemTemplate );
+ }
+
+ // Attach autocomplete when editor instance is ready (#2114).
+ if ( this.editor.status === 'ready' ) {
+ this.attach();
+ } else {
+ this.editor.on( 'instanceReady', function() {
+ this.attach();
+ }, this );
+ }
+
+ editor.on( 'destroy', function() {
+ this.destroy();
+ }, this );
+ }
+
+ Autocomplete.prototype = {
+ /**
+ * Attaches the autocomplete to the {@link #editor}.
+ *
+ * * The view is appended to the DOM and the listeners are attached.
+ * * The {@link #textWatcher text watcher} is attached to the editor.
+ * * The listeners on the {@link #model} and {@link #view} events are added.
+ */
+ attach: function() {
+ var editor = this.editor,
+ win = CKEDITOR.document.getWindow(),
+ editable = editor.editable(),
+ editorScrollableElement = editable.isInline() ? editable : editable.getDocument();
+
+ // iOS classic editor listens on frame parent element for editor `scroll` event (#1910).
+ if ( CKEDITOR.env.iOS && !editable.isInline() ) {
+ editorScrollableElement = iOSViewportElement( editor );
+ }
+
+ this.view.append();
+ this.view.attach();
+ this.textWatcher.attach();
+
+ this._listeners.push( this.textWatcher.on( 'matched', this.onTextMatched, this ) );
+ this._listeners.push( this.textWatcher.on( 'unmatched', this.onTextUnmatched, this ) );
+ this._listeners.push( this.model.on( 'change-data', this.modelChangeListener, this ) );
+ this._listeners.push( this.model.on( 'change-selectedItemId', this.onSelectedItemId, this ) );
+ this._listeners.push( this.view.on( 'change-selectedItemId', this.onSelectedItemId, this ) );
+ this._listeners.push( this.view.on( 'click-item', this.onItemClick, this ) );
+
+ // (#4617)
+ this._listeners.push( this.model.on( 'change-isActive', this.updateAriaAttributesOnEditable, this ) );
+
+ // Update view position on viewport change.
+ // Note: CKEditor's event system has a limitation that one function
+ // cannot be used as listener for the same event more than once. Hence, wrapper functions.
+ this._listeners.push( win.on( 'scroll', function() {
+ this.viewRepositionListener();
+ }, this ) );
+ this._listeners.push( editorScrollableElement.on( 'scroll', function() {
+ this.viewRepositionListener();
+ }, this ) );
+
+ // Don't let browser to focus dropdown element (#2107).
+ this._listeners.push( this.view.element.on( 'mousedown', function( e ) {
+ e.data.preventDefault();
+ }, null, null, 9999 ) );
+
+ // Register keybindings and add ARIA attributes to the editable right away
+ // if editor is already initialized.
+ if ( editable ) {
+ this.registerPanelNavigation();
+ this.addAriaAttributesToEditable();
+ }
+
+ // Note: CKEditor's event system has a limitation that one function
+ // cannot be used as listener for the same event more than once. Hence, wrapper function.
+ // (#4107)
+ editor.on( 'contentDom', function() {
+ this.registerPanelNavigation();
+ this.addAriaAttributesToEditable();
+ }, this );
+ },
+
+ registerPanelNavigation: function() {
+ var editable = this.editor.editable();
+
+ // Priority 5 to get before the enterkey.
+ // Note: CKEditor's event system has a limitation that one function (in this case this.onKeyDown)
+ // cannot be used as listener for the same event more than once. Hence, wrapper function.
+ this._listeners.push( editable.attachListener( editable, 'keydown', function( evt ) {
+ this.onKeyDown( evt );
+ }, this, null, 5 ) );
+ },
+
+ /**
+ * @since 4.16.1
+ */
+ addAriaAttributesToEditable: function() {
+ var editable = this.editor.editable(),
+ autocompleteId = this.view.element.getAttribute( 'id' );
+
+ if ( !editable.isInline() ) {
+ return;
+ }
+
+ editable.setAttribute( 'aria-controls', autocompleteId );
+ editable.setAttribute( 'aria-activedescendant', '' );
+ editable.setAttribute( 'aria-autocomplete', 'list' );
+ editable.setAttribute( 'aria-expanded', 'false' );
+ },
+
+ /**
+ * @since 4.16.1
+ */
+ updateAriaAttributesOnEditable: function( evt ) {
+ var editable = this.editor.editable(),
+ isActive = evt.data;
+
+ if ( !editable || !editable.isInline() ) {
+ return;
+ }
+
+ editable.setAttribute( 'aria-expanded', isActive ? 'true' : 'false' );
+
+ if ( !isActive ) {
+ editable.setAttribute( 'aria-activedescendant', '' );
+ }
+ },
+
+ /**
+ * @since 4.16.1
+ */
+ updateAriaActiveDescendantAttributeOnEditable: function( id ) {
+ var editable = this.editor.editable();
+
+ if ( !editable.isInline() ) {
+ return;
+ }
+
+ editable.setAttribute( 'aria-activedescendant', id );
+ },
+
+ /**
+ * @since 4.16.1
+ */
+ removeAriaAttributesFromEditable: function() {
+ var editable = this.editor.editable();
+
+ if ( !editable || !editable.isInline() ) {
+ return;
+ }
+
+ editable.removeAttributes( [
+ 'aria-controls',
+ 'aria-expanded',
+ 'aria-activedescendant'
+ ] );
+
+ editable.setAttribute( 'aria-autocomplete', 'none' );
+ },
+
+ /**
+ * Closes the view and sets its {@link CKEDITOR.plugins.autocomplete.model#isActive state} to inactive.
+ */
+ close: function() {
+ this.model.setActive( false );
+ this.view.close();
+ },
+
+ /**
+ * Commits the currently chosen or given item. HTML is generated for this item using the
+ * {@link #getHtmlToInsert} method and then it is inserted into the editor. The item is inserted
+ * into the {@link CKEDITOR.plugins.autocomplete.model#range query's range}, so the query text is
+ * replaced by the inserted HTML.
+ *
+ * @param {Number/String} [itemId] If given, then the specified item will be inserted into the editor
+ * instead of the currently chosen one.
+ */
+ commit: function( itemId ) {
+ if ( !this.model.isActive ) {
+ return;
+ }
+
+ this.close();
+
+ if ( itemId == null ) {
+ itemId = this.model.selectedItemId;
+
+ // If non item is selected abort commit.
+ if ( itemId == null ) {
+ return;
+ }
+ }
+
+ var item = this.model.getItemById( itemId ),
+ editor = this.editor,
+ html = this.getHtmlToInsert( item );
+
+ // Insert space after accepting match (#2008).
+ html += this.followingSpace ? ' ' : '';
+
+ editor.fire( 'saveSnapshot' );
+ editor.getSelection().selectRanges( [ this.model.range ] );
+ editor.insertHtml( html, 'text' );
+
+ if ( this.followingSpace ) {
+ removeLeadingSpace( editor );
+ }
+
+ editor.fire( 'saveSnapshot' );
+ },
+
+ /**
+ * Destroys the autocomplete instance.
+ * View element and event listeners will be removed from the DOM.
+ */
+ destroy: function() {
+ CKEDITOR.tools.array.forEach( this._listeners, function( obj ) {
+ obj.removeListener();
+ } );
+
+ this._listeners = [];
+
+ this.view.element && this.view.element.remove();
+ this.removeAriaAttributesFromEditable();
+ },
+
+ /**
+ * Returns HTML that should be inserted into the editor when the item is committed.
+ *
+ * See also the {@link #commit} method.
+ *
+ * @param {CKEDITOR.plugins.autocomplete.model.item} item
+ * @returns {String} The HTML to insert.
+ */
+ getHtmlToInsert: function( item ) {
+ var encodedItem = encodeItem( item );
+ return this.outputTemplate ? this.outputTemplate.output( encodedItem ) : encodedItem.name;
+ },
+
+ /**
+ * Creates and returns the model instance. This method is used when
+ * initializing the autocomplete and can be overwritten in order to
+ * return an instance of a different class than the default model.
+ *
+ * @param {Function} dataCallback See {@link CKEDITOR.plugins.autocomplete.configDefinition#dataCallback configDefinition.dataCallback}.
+ * @returns {CKEDITOR.plugins.autocomplete.model} The model instance.
+ */
+ getModel: function( dataCallback ) {
+ var that = this;
+
+ return new Model( function( matchInfo, callback ) {
+ return dataCallback.call( this, CKEDITOR.tools.extend( {
+ // Make sure autocomplete instance is available in the callback (#2108).
+ autocomplete: that
+ }, matchInfo ), callback );
+ } );
+ },
+
+ /**
+ * Creates and returns the text watcher instance. This method is used while
+ * initializing the autocomplete and can be overwritten in order to
+ * return an instance of a different class than the default text watcher.
+ *
+ * @param {Function} textTestCallback See the {@link CKEDITOR.plugins.autocomplete} arguments.
+ * @returns {CKEDITOR.plugins.textWatcher} The text watcher instance.
+ */
+ getTextWatcher: function( textTestCallback ) {
+ return new CKEDITOR.plugins.textWatcher( this.editor, textTestCallback, this.throttle );
+ },
+
+ /**
+ * Creates and returns the view instance. This method is used while
+ * initializing the autocomplete and can be overwritten in order to
+ * return an instance of a different class than the default view.
+ *
+ * @returns {CKEDITOR.plugins.autocomplete.view} The view instance.
+ */
+ getView: function() {
+ return new View( this.editor );
+ },
+
+ /**
+ * Opens the panel if {@link CKEDITOR.plugins.autocomplete.model#hasData there is any data available}.
+ */
+ open: function() {
+ if ( this.model.hasData() ) {
+ this.model.setActive( true );
+ this.view.open();
+ this.model.selectFirst();
+ this.view.updatePosition( this.model.range );
+ }
+ },
+
+ // LISTENERS ------------------
+
+ /**
+ * The function that should be called when the view has to be repositioned, e.g on scroll.
+ *
+ * @private
+ */
+ viewRepositionListener: function() {
+ if ( this.model.isActive ) {
+ this.view.updatePosition( this.model.range );
+ }
+ },
+
+ /**
+ * The function that should be called once the model data has changed.
+ *
+ * @param {CKEDITOR.eventInfo} evt
+ * @private
+ */
+ modelChangeListener: function( evt ) {
+ if ( this.model.hasData() ) {
+ this.view.updateItems( evt.data );
+ this.open();
+ } else {
+ this.close();
+ }
+ },
+
+ /**
+ * The function that should be called once a view item was clicked.
+ *
+ * @param {CKEDITOR.eventInfo} evt
+ * @private
+ */
+ onItemClick: function( evt ) {
+ this.commit( evt.data );
+ },
+
+ /**
+ * The function that should be called on every `keydown` event occurred within the {@link CKEDITOR.editable editable} element.
+ *
+ * @param {CKEDITOR.dom.event} evt
+ * @private
+ */
+ onKeyDown: function( evt ) {
+ if ( !this.model.isActive ) {
+ return;
+ }
+
+ var keyCode = evt.data.getKey(),
+ handled = false;
+
+ // Esc key.
+ if ( keyCode == 27 ) {
+ this.close();
+ this.textWatcher.unmatch();
+ handled = true;
+ // Down Arrow.
+ } else if ( keyCode == 40 ) {
+ this.model.selectNext();
+ handled = true;
+ // Up Arrow.
+ } else if ( keyCode == 38 ) {
+ this.model.selectPrevious();
+ handled = true;
+ // Completion keys.
+ } else if ( CKEDITOR.tools.indexOf( this.commitKeystrokes, keyCode ) != -1 ) {
+ this.commit();
+ this.textWatcher.unmatch();
+ handled = true;
+ }
+
+ if ( handled ) {
+ evt.cancel();
+ evt.data.preventDefault();
+ this.textWatcher.consumeNext();
+ }
+ },
+
+ /**
+ * The function that should be called once an item was selected.
+ *
+ * @param {CKEDITOR.eventInfo} evt
+ * @private
+ */
+ onSelectedItemId: function( evt ) {
+ var itemId = evt.data,
+ selectedItem = this.view.getItemById( itemId );
+
+ this.model.setItem( itemId );
+ this.view.selectItem( itemId );
+
+ this.updateAriaActiveDescendantAttributeOnEditable( selectedItem.getAttribute( 'id' ) );
+ },
+
+ /**
+ * The function that should be called once a text was matched by the {@link CKEDITOR.plugins.textWatcher text watcher}
+ * component.
+ *
+ * @param {CKEDITOR.eventInfo} evt
+ * @private
+ */
+ onTextMatched: function( evt ) {
+ this.model.setActive( false );
+ this.model.setQuery( evt.data.text, evt.data.range );
+ },
+
+ /**
+ * The function that should be called once a text was unmatched by the {@link CKEDITOR.plugins.textWatcher text watcher}
+ * component.
+ *
+ * @param {CKEDITOR.eventInfo} evt
+ * @private
+ */
+ onTextUnmatched: function() {
+ // Remove query and request ID to avoid opening view for invalid callback (#1984).
+ this.model.query = null;
+ this.model.lastRequestId = null;
+
+ this.close();
+ }
+ };
+
+ /**
+ * Class representing the autocomplete view.
+ *
+ * In order to use a different view, implement a new view class and override
+ * the {@link CKEDITOR.plugins.autocomplete#getView} method.
+ *
+ * ```javascript
+ * myAutocomplete.prototype.getView = function() {
+ * return new myView( this.editor );
+ * };
+ * ```
+ *
+ * You can also modify this autocomplete instance on the fly.
+ *
+ * ```javascript
+ * myAutocomplete.prototype.getView = function() {
+ * // Call the original getView method.
+ * var view = CKEDITOR.plugins.autocomplete.prototype.getView.call( this );
+ *
+ * // Override one property.
+ * view.itemTemplate = new CKEDITOR.template( '
{name} ' );
+ *
+ * return view;
+ * };
+ * ```
+ *
+ * **Note:** This class is marked as private, which means that its API might be subject to change in order to
+ * provide further enhancements.
+ *
+ * @class CKEDITOR.plugins.autocomplete.view
+ * @since 4.10.0
+ * @private
+ * @mixins CKEDITOR.event
+ * @constructor Creates the autocomplete view instance.
+ * @param {CKEDITOR.editor} editor The editor instance.
+ */
+ function View( editor ) {
+ /**
+ * The panel's item template used to render matches in the dropdown.
+ *
+ * You can use {@link CKEDITOR.plugins.autocomplete.model#data data item} properties to customize the template.
+ *
+ * A minimal template must be wrapped with a HTML `li` element containing the `data-id="{id}"` attribute.
+ *
+ * ```javascript
+ * var itemTemplate = ' {name} ';
+ * ```
+ *
+ * @readonly
+ * @property {CKEDITOR.template}
+ */
+ this.itemTemplate = new CKEDITOR.template( '{name} ' );
+
+ /**
+ * The editor instance.
+ *
+ * @readonly
+ * @property {CKEDITOR.editor}
+ */
+ this.editor = editor;
+
+ /**
+ * The ID of the selected item.
+ *
+ * @readonly
+ * @property {Number/String} selectedItemId
+ */
+
+ /**
+ * The document to which the view is attached. It is set by the {@link #append} method.
+ *
+ * @readonly
+ * @property {CKEDITOR.dom.document} document
+ */
+
+ /**
+ * The view's main element. It is set by the {@link #append} method.
+ *
+ * @readonly
+ * @property {CKEDITOR.dom.element} element
+ */
+
+ /**
+ * Event fired when an item in the panel is clicked.
+ *
+ * @event click-item
+ * @param {String} The clicked item {@link CKEDITOR.plugins.autocomplete.model.item#id}. Note: the ID
+ * is stringified due to the way how it is stored in the DOM.
+ */
+
+ /**
+ * Event fired when the {@link #selectedItemId} property changes.
+ *
+ * @event change-selectedItemId
+ * @param {Number/String} data The new value.
+ */
+ }
+
+ View.prototype = {
+ /**
+ * Appends the {@link #element main element} to the DOM.
+ */
+ append: function() {
+ this.document = CKEDITOR.document;
+ this.element = this.createElement();
+
+ this.document.getBody().append( this.element );
+ },
+
+ /**
+ * Removes existing items and appends given items to the {@link #element}.
+ *
+ * @param {CKEDITOR.dom.documentFragment} itemsFragment The document fragment with item elements.
+ */
+ appendItems: function( itemsFragment ) {
+ this.element.setHtml( '' );
+ this.element.append( itemsFragment );
+ },
+
+ /**
+ * Attaches the view's listeners to the DOM elements.
+ */
+ attach: function() {
+ this.element.on( 'click', function( evt ) {
+ var target = evt.data.getTarget(),
+ itemElement = target.getAscendant( this.isItemElement, true );
+
+ if ( itemElement ) {
+ this.fire( 'click-item', itemElement.data( 'id' ) );
+ }
+ }, this );
+
+ this.element.on( 'mouseover', function( evt ) {
+ var target = evt.data.getTarget();
+
+ if ( this.element.contains( target ) ) {
+
+ // Find node containing data-id attribute inside target node tree (#2187).
+ target = target.getAscendant( function( element ) {
+ return element.hasAttribute( 'data-id' );
+ }, true );
+
+ if ( !target ) {
+ return;
+ }
+
+ var itemId = target.data( 'id' );
+
+ this.fire( 'change-selectedItemId', itemId );
+ }
+
+ }, this );
+ },
+
+ /**
+ * Closes the panel.
+ */
+ close: function() {
+ this.element.removeClass( 'cke_autocomplete_opened' );
+ },
+
+ /**
+ * Creates and returns the view's main element.
+ *
+ * @private
+ * @returns {CKEDITOR.dom.element}
+ */
+ createElement: function() {
+ var el = new CKEDITOR.dom.element( 'ul', this.document ),
+ id = CKEDITOR.tools.getNextId();
+
+ // Id is needed to correctly bind autocomplete with the editable (#4617).
+ el.setAttribute( 'id', id );
+ el.addClass( 'cke_autocomplete_panel' );
+ // Below float panels and context menu, but above maximized editor (-5).
+ el.setStyle( 'z-index', this.editor.config.baseFloatZIndex - 3 );
+ // Add also appropriate role (#4617).
+ el.setAttribute( 'role', 'listbox' );
+
+ return el;
+ },
+
+ /**
+ * Creates the item element based on the {@link #itemTemplate}.
+ *
+ * @param {CKEDITOR.plugins.autocomplete.model.item} item The item for which an element will be created.
+ * @returns {CKEDITOR.dom.element}
+ */
+ createItem: function( item ) {
+ var encodedItem = encodeItem( item ),
+ itemElement = CKEDITOR.dom.element.createFromHtml( this.itemTemplate.output( encodedItem ), this.document ),
+ id = CKEDITOR.tools.getNextId();
+
+ // Add attributes needed for a11y support (#4617).
+ itemElement.setAttribute( 'id', id );
+ itemElement.setAttribute( 'role', 'option' );
+
+ return itemElement;
+ },
+
+ /**
+ * Returns the view position based on a given `range`.
+ *
+ * Indicates the start position of the autocomplete dropdown.
+ * The value returned by this function is passed to the {@link #setPosition} method
+ * by the {@link #updatePosition} method.
+ *
+ * @param {CKEDITOR.dom.range} range The range of the text match.
+ * @returns {Object} Represents the position of the caret. The value is relative to the panel's offset parent.
+ * @returns {Number} rect.left
+ * @returns {Number} rect.top
+ * @returns {Number} rect.bottom
+ */
+ getViewPosition: function( range ) {
+ // Use the last rect so the view will be
+ // correctly positioned with a word split into few lines.
+ var rects = range.getClientRects(),
+ viewPositionRect = rects[ rects.length - 1 ],
+ offset,
+ editable = this.editor.editable();
+
+ if ( editable.isInline() ) {
+ offset = CKEDITOR.document.getWindow().getScrollPosition();
+ } else {
+ offset = editable.getParent().getDocumentPosition( CKEDITOR.document );
+ }
+
+ // Consider that offset host might be repositioned on its own.
+ // Similar to #1048. See https://github.com/ckeditor/ckeditor4/pull/1732#discussion_r182790235.
+ var hostElement = CKEDITOR.document.getBody();
+ if ( hostElement.getComputedStyle( 'position' ) === 'static' ) {
+ hostElement = hostElement.getParent();
+ }
+
+ var offsetCorrection = hostElement.getDocumentPosition();
+
+ offset.x -= offsetCorrection.x;
+ offset.y -= offsetCorrection.y;
+
+ return {
+ top: ( viewPositionRect.top + offset.y ),
+ bottom: ( viewPositionRect.top + viewPositionRect.height + offset.y ),
+ left: ( viewPositionRect.left + offset.x )
+ };
+ },
+
+ /**
+ * Gets the item element by the item ID.
+ *
+ * @param {Number/String} itemId
+ * @returns {CKEDITOR.dom.element} The item element.
+ */
+ getItemById: function( itemId ) {
+ return this.element.findOne( 'li[data-id="' + itemId + '"]' );
+ },
+
+ /**
+ * Checks whether a given node is the item element.
+ *
+ * @param {CKEDITOR.dom.node} node
+ * @returns {Boolean}
+ */
+ isItemElement: function( node ) {
+ return node.type == CKEDITOR.NODE_ELEMENT &&
+ Boolean( node.data( 'id' ) );
+ },
+
+ /**
+ * Opens the panel.
+ */
+ open: function() {
+ this.element.addClass( 'cke_autocomplete_opened' );
+ },
+
+ /**
+ * Selects the item in the panel and scrolls the list to show it if needed.
+ * The {@link #selectedItemId currently selected item} is deselected first.
+ *
+ * @param {Number/String} itemId The ID of the item that should be selected.
+ */
+ selectItem: function( itemId ) {
+ if ( this.selectedItemId != null ) {
+ this.getItemById( this.selectedItemId ).removeClass( 'cke_autocomplete_selected' );
+ }
+
+ var itemElement = this.getItemById( itemId );
+ itemElement.addClass( 'cke_autocomplete_selected' );
+ this.selectedItemId = itemId;
+
+ this.scrollElementTo( itemElement );
+ },
+
+ /**
+ * Sets the position of the panel. This method only performs the check
+ * for the available space below and above the specified `rect` and
+ * positions the panel in the best place.
+ *
+ * This method is used by the {@link #updatePosition} method which
+ * controls how the panel should be positioned on the screen, for example
+ * based on the caret position and/or the editor position.
+ *
+ * @param {Object} rect Represents the position of a vertical (e.g. a caret) line relative to which
+ * the panel should be positioned.
+ * @param {Number} rect.left The position relative to the panel's offset parent in pixels.
+ * For example, the position of the caret.
+ * @param {Number} rect.top The position relative to the panel's offset parent in pixels.
+ * For example, the position of the upper end of the caret.
+ * @param {Number} rect.bottom The position relative to the panel's offset parent in pixels.
+ * For example, the position of the bottom end of the caret.
+ */
+ setPosition: function( rect ) {
+ var documentWindow = this.element.getWindow(),
+ windowRect = documentWindow.getViewPaneSize(),
+ top = getVerticalPosition( {
+ editorViewportRect: getEditorViewportRect( this.editor ),
+ caretRect: rect,
+ viewHeight: this.element.getSize( 'height' ),
+ scrollPositionY: documentWindow.getScrollPosition().y,
+ windowHeight: windowRect.height
+ } ),
+ left = getHorizontalPosition( {
+ leftPosition: rect.left,
+ viewWidth: this.element.getSize( 'width' ),
+ windowWidth: windowRect.width
+ } );
+
+ this.element.setStyles( {
+ left: left + 'px',
+ top: top + 'px'
+ } );
+
+ function getVerticalPosition( options ) {
+ var editorViewportRect = options.editorViewportRect,
+ caretRect = options.caretRect,
+ viewHeight = options.viewHeight,
+ scrollPositionY = options.scrollPositionY,
+ windowHeight = options.windowHeight;
+
+ // If the caret position is below the view - keep it at the bottom edge.
+ // +---------------------------------------------+
+ // | editor viewport |
+ // | |
+ // | +--------------+ |
+ // | | | |
+ // | | view | |
+ // | | | |
+ // +-----+==============+------------------------+
+ // | |
+ // | █ - caret position |
+ // | |
+ // +---------------------------------------------+
+ if ( editorViewportRect.bottom < caretRect.bottom ) {
+ return Math.min( caretRect.top, editorViewportRect.bottom ) - viewHeight;
+ }
+
+ // If the view doesn't fit below the caret position and fits above, set it there.
+ // This means that the position below the caret is preferred.
+ // +---------------------------------------------+
+ // | |
+ // | editor viewport |
+ // | +--------------+ |
+ // | | | |
+ // | | view | |
+ // | | | |
+ // | +--------------+ |
+ // | █ - caret position |
+ // | |
+ // | |
+ // +---------------------------------------------+
+ // How much space is there for the view above and below the specified rect.
+ var spaceAbove = caretRect.top - editorViewportRect.top,
+ spaceBelow = editorViewportRect.bottom - caretRect.bottom,
+ viewExceedsTopViewport = ( caretRect.top - viewHeight ) < scrollPositionY;
+
+ if ( viewHeight > spaceBelow && viewHeight < spaceAbove && !viewExceedsTopViewport ) {
+ return caretRect.top - viewHeight;
+ }
+
+ // If the caret position is above the view - keep it at the top edge.
+ // +---------------------------------------------+
+ // | |
+ // | █ - caret position |
+ // | |
+ // +-----+==============+------------------------+
+ // | | | |
+ // | | view | |
+ // | | | |
+ // | +--------------+ |
+ // | |
+ // | editor viewport |
+ // +---------------------------------------------+
+ if ( editorViewportRect.top > caretRect.top ) {
+ return Math.max( caretRect.bottom, editorViewportRect.top );
+ }
+
+ // (#3582)
+ // If the view goes beyond bottom window border - reverse view position, even if it fits editor viewport.
+ // +---------------------------------------------+
+ // | editor viewport |
+ // | |
+ // | |
+ // | +--------------+ |
+ // | | view | |
+ // | +--------------+ |
+ // | caret position - █ |
+ // | |
+ // =============================================== - bottom window border
+ // | |
+ // | |
+ // +---------------------------------------------+
+ var viewExceedsBottomViewport = ( caretRect.bottom + viewHeight ) > ( windowHeight + scrollPositionY );
+
+ if ( !( viewHeight > spaceBelow && viewHeight < spaceAbove ) && viewExceedsBottomViewport ) {
+ return caretRect.top - viewHeight;
+ }
+
+ // As a default, keep the view inside the editor viewport.
+ // +---------------------------------------------+
+ // | editor viewport |
+ // | |
+ // | |
+ // | |
+ // | █ - caret position |
+ // | +--------------+ |
+ // | | view | |
+ // | +--------------+ |
+ // | |
+ // | |
+ // +---------------------------------------------+
+ return Math.min( editorViewportRect.bottom, caretRect.bottom );
+ }
+
+ function getHorizontalPosition( options ) {
+ var caretLeftPosition = options.leftPosition,
+ viewWidth = options.viewWidth,
+ windowWidth = options.windowWidth;
+
+ // (#3582)
+ // If the view goes beyond right window border - stick it to the edge of the available viewport.
+ // +---------------------------------------------+ ||
+ // | editor viewport | ||
+ // | | ||
+ // | | ||
+ // | caret position - █ | || - right window border
+ // | +--------------+||
+ // | | |||
+ // | | view |||
+ // | | |||
+ // | +--------------+||
+ // | | ||
+ // +---------------------------------------------+ ||
+ if ( caretLeftPosition + viewWidth > windowWidth ) {
+ return windowWidth - viewWidth;
+ }
+
+ // Otherwise inherit the horizontal position from caret.
+ return caretLeftPosition;
+ }
+
+ // Bounding rect where the view should fit (visible editor viewport).
+ function getEditorViewportRect( editor ) {
+ var editable = editor.editable();
+
+ // iOS classic editor has different viewport element (#1910).
+ if ( CKEDITOR.env.iOS && !editable.isInline() ) {
+ return iOSViewportElement( editor ).getClientRect( true );
+ } else {
+ return editable.isInline() ? editable.getClientRect( true ) : editor.window.getFrame().getClientRect( true );
+ }
+ }
+ },
+
+ /**
+ * Scrolls the list so the item element is visible in it.
+ *
+ * @param {CKEDITOR.dom.element} itemElement
+ */
+ scrollElementTo: function( itemElement ) {
+ itemElement.scrollIntoParent( this.element );
+ },
+
+ /**
+ * Updates the list of items in the panel.
+ *
+ * @param {CKEDITOR.plugins.autocomplete.model.item[]} items.
+ */
+ updateItems: function( items ) {
+ var i,
+ frag = new CKEDITOR.dom.documentFragment( this.document );
+
+ for ( i = 0; i < items.length; ++i ) {
+ frag.append( this.createItem( items[ i ] ) );
+ }
+
+ this.appendItems( frag );
+ this.selectedItemId = null;
+ },
+
+ /**
+ * Updates the position of the panel.
+ *
+ * By default this method finds the position of the caret and uses
+ * {@link #setPosition} to move the panel to the best position close
+ * to the caret.
+ *
+ * @param {CKEDITOR.dom.range} range The range of the text match.
+ */
+ updatePosition: function( range ) {
+ this.setPosition( this.getViewPosition( range ) );
+ }
+ };
+
+ CKEDITOR.event.implementOn( View.prototype );
+
+ /**
+ * Class representing the autocomplete model.
+ *
+ * In case you want to modify the model behavior, check out the
+ * {@link CKEDITOR.plugins.autocomplete.view} documentation. It contains
+ * examples of how to easily override the default behavior.
+ *
+ * A model instance is created by the {@link CKEDITOR.plugins.autocomplete#getModel} method.
+ *
+ * **Note:** This class is marked as private, which means that its API might be subject to change in order to
+ * provide further enhancements.
+ *
+ * @class CKEDITOR.plugins.autocomplete.model
+ * @since 4.10.0
+ * @private
+ * @mixins CKEDITOR.event
+ * @constructor Creates the autocomplete model instance.
+ * @param {Function} dataCallback See {@link CKEDITOR.plugins.autocomplete} arguments.
+ */
+ function Model( dataCallback ) {
+ /**
+ * The callback executed by the model when requesting data.
+ * See {@link CKEDITOR.plugins.autocomplete} arguments.
+ *
+ * @readonly
+ * @property {Function}
+ */
+ this.dataCallback = dataCallback;
+
+ /**
+ * Whether the autocomplete is active (i.e. can receive user input like click, key press).
+ * Should be modified by the {@link #setActive} method which fires the {@link #change-isActive} event.
+ *
+ * @readonly
+ */
+ this.isActive = false;
+
+ /**
+ * Indicates the limit of items rendered in the dropdown.
+ *
+ * For falsy values like `0` or `null` all items will be rendered.
+ *
+ * @property {Number} [itemsLimit=0]
+ */
+ this.itemsLimit = 0;
+
+ /**
+ * The ID of the last request for data. Used by the {@link #setQuery} method.
+ *
+ * @readonly
+ * @private
+ * @property {Number} lastRequestId
+ */
+
+ /**
+ * The query string set by the {@link #setQuery} method.
+ *
+ * The query string always has a corresponding {@link #range}.
+ *
+ * @readonly
+ * @property {String} query
+ */
+
+ /**
+ * The range in the DOM where the {@link #query} text is.
+ *
+ * The range always has a corresponding {@link #query}. Both can be set by the {@link #setQuery} method.
+ *
+ * @readonly
+ * @property {CKEDITOR.dom.range} range
+ */
+
+ /**
+ * The query results — the items to be displayed in the autocomplete panel.
+ *
+ * @readonly
+ * @property {CKEDITOR.plugins.autocomplete.model.item[]} data
+ */
+
+ /**
+ * The ID of the item currently selected in the panel.
+ *
+ * @readonly
+ * @property {Number/String} selectedItemId
+ */
+
+ /**
+ * Event fired when the {@link #data} array changes.
+ *
+ * @event change-data
+ * @param {CKEDITOR.plugins.autocomplete.model.item[]} data The new value.
+ */
+
+ /**
+ * Event fired when the {@link #selectedItemId} property changes.
+ *
+ * @event change-selectedItemId
+ * @param {Number/String} data The new value.
+ */
+
+ /**
+ * Event fired when the {@link #isActive} property changes.
+ *
+ * @event change-isActive
+ * @param {Boolean} data The new value.
+ */
+ }
+
+ Model.prototype = {
+ /**
+ * Gets an index from the {@link #data} array of the item by its ID.
+ *
+ * @param {Number/String} itemId
+ * @returns {Number}
+ */
+ getIndexById: function( itemId ) {
+ if ( !this.hasData() ) {
+ return -1;
+ }
+
+ for ( var data = this.data, i = 0, l = data.length; i < l; i++ ) {
+ if ( data[ i ].id == itemId ) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ /**
+ * Gets the item from the {@link #data} array by its ID.
+ *
+ * @param {Number/String} itemId
+ * @returns {CKEDITOR.plugins.autocomplete.model.item}
+ */
+ getItemById: function( itemId ) {
+ var index = this.getIndexById( itemId );
+ return ~index && this.data[ index ] || null;
+ },
+
+ /**
+ * Whether the model contains non-empty {@link #data}.
+ *
+ * @returns {Boolean}
+ */
+ hasData: function() {
+ return Boolean( this.data && this.data.length );
+ },
+
+ /**
+ * Sets the {@link #selectedItemId} property.
+ *
+ * @param {Number/String} itemId
+ */
+ setItem: function( itemId ) {
+ if ( this.getIndexById( itemId ) < 0 ) {
+ throw new Error( 'Item with given id does not exist' );
+ }
+
+ this.selectedItemId = itemId;
+ },
+
+ /**
+ * Fires the {@link #change-selectedItemId} event.
+ *
+ * @param {Number/String} itemId
+ */
+ select: function( itemId ) {
+ this.fire( 'change-selectedItemId', itemId );
+ },
+
+ /**
+ * Selects the first item. See also the {@link #select} method.
+ */
+ selectFirst: function() {
+ if ( this.hasData() ) {
+ this.select( this.data[ 0 ].id );
+ }
+ },
+
+ /**
+ * Selects the last item. See also the {@link #select} method.
+ */
+ selectLast: function() {
+ if ( this.hasData() ) {
+ this.select( this.data[ this.data.length - 1 ].id );
+ }
+ },
+
+ /**
+ * Selects the next item in the {@link #data} array. If no item is selected,
+ * it selects the first one. If the last one is selected, it selects the first one.
+ *
+ * See also the {@link #select} method.
+ */
+ selectNext: function() {
+ if ( this.selectedItemId == null ) {
+ this.selectFirst();
+ return;
+ }
+
+ var index = this.getIndexById( this.selectedItemId );
+
+ if ( index < 0 || index + 1 == this.data.length ) {
+ this.selectFirst();
+ } else {
+ this.select( this.data[ index + 1 ].id );
+ }
+ },
+
+ /**
+ * Selects the previous item in the {@link #data} array. If no item is selected,
+ * it selects the last one. If the first one is selected, it selects the last one.
+ *
+ * See also the {@link #select} method.
+ */
+ selectPrevious: function() {
+ if ( this.selectedItemId == null ) {
+ this.selectLast();
+ return;
+ }
+
+ var index = this.getIndexById( this.selectedItemId );
+
+ if ( index <= 0 ) {
+ this.selectLast();
+ } else {
+ this.select( this.data[ index - 1 ].id );
+ }
+ },
+
+ /**
+ * Sets the {@link #isActive} property and fires the {@link #change-isActive} event.
+ *
+ * @param {Boolean} isActive
+ */
+ setActive: function( isActive ) {
+ this.isActive = isActive;
+ this.fire( 'change-isActive', isActive );
+ },
+
+ /**
+ * Sets the {@link #query} and {@link #range} and makes a request for the query results
+ * by executing the {@link #dataCallback} function. When the data is returned (synchronously or
+ * asynchronously, because {@link #dataCallback} exposes a callback function), the {@link #data}
+ * property is set and the {@link #change-data} event is fired.
+ *
+ * This method controls that only the response for the current query is handled.
+ *
+ * @param {String} query
+ * @param {CKEDITOR.dom.range} range
+ */
+ setQuery: function( query, range ) {
+ var that = this,
+ requestId = CKEDITOR.tools.getNextId();
+
+ this.lastRequestId = requestId;
+ this.query = query;
+ this.range = range;
+ this.data = null;
+ this.selectedItemId = null;
+
+ this.dataCallback( {
+ query: query,
+ range: range
+ }, handleData );
+
+ // Note: don't put any executable code here because the callback passed to
+ // this.dataCallback may be executed synchronously or asynchronously
+ // so execution order will differ.
+
+ function handleData( data ) {
+ // Handle only the response for the most recent setQuery call.
+ if ( requestId == that.lastRequestId ) {
+ // Limit number of items (#2030).
+ if ( that.itemsLimit ) {
+ that.data = data.slice( 0, that.itemsLimit );
+ } else {
+ that.data = data;
+ }
+ that.fire( 'change-data', that.data );
+ }
+ }
+ }
+ };
+
+ CKEDITOR.event.implementOn( Model.prototype );
+
+ /**
+ * An abstract class representing one {@link CKEDITOR.plugins.autocomplete.model#data data item}.
+ * A item can be understood as one entry in the autocomplete panel.
+ *
+ * An item must have a unique {@link #id} and may have more properties which can then be used, for example,
+ * in the {@link CKEDITOR.plugins.autocomplete.view#itemTemplate} template or the
+ * {@link CKEDITOR.plugins.autocomplete#getHtmlToInsert} method.
+ *
+ * Example items:
+ *
+ * ```javascript
+ * { id: 345, name: 'CKEditor' }
+ * { id: 'smile1', alt: 'smile', emojiSrc: 'emojis/smile.png' }
+ * ```
+ *
+ * @abstract
+ * @class CKEDITOR.plugins.autocomplete.model.item
+ * @since 4.10.0
+ */
+
+ /**
+ * The unique ID of the item. The ID should not change with time, so two
+ * {@link CKEDITOR.plugins.autocomplete.model#dataCallback}
+ * calls should always result in the same ID for the same logical item.
+ * This can, for example, allow to keep the same item selected when
+ * the data changes.
+ *
+ * **Note:** When using a string as an item, make sure that the string does not
+ * contain any special characters (above all `"[]` characters). This limitation is
+ * due to the simplified way the {@link CKEDITOR.plugins.autocomplete.view}
+ * stores IDs in the DOM.
+ *
+ * @readonly
+ * @property {Number/String} id
+ */
+
+ CKEDITOR.plugins.autocomplete = Autocomplete;
+ Autocomplete.view = View;
+ Autocomplete.model = Model;
+
+ /**
+ * The autocomplete keystrokes used to finish autocompletion with the selected view item.
+ * This setting will set completing keystrokes for each autocomplete plugin respectively.
+ *
+ * To change completing keystrokes individually use the {@link CKEDITOR.plugins.autocomplete#commitKeystrokes} plugin property.
+ *
+ * ```javascript
+ * // Default configuration (9 = Tab, 13 = Enter).
+ * config.autocomplete_commitKeystrokes = [ 9, 13 ];
+ * ```
+ *
+ * Commit keystroke can also be disabled by setting it to an empty array.
+ *
+ * ```javascript
+ * // Disable autocomplete commit keystroke.
+ * config.autocomplete_commitKeystrokes = [];
+ * ```
+ *
+ * @since 4.10.0
+ * @cfg {Number/Number[]} [autocomplete_commitKeystrokes=[9, 13]]
+ * @member CKEDITOR.config
+ */
+ CKEDITOR.config.autocomplete_commitKeystrokes = [ 9, 13 ];
+
+ // Viewport on iOS is moved into iframe parent element because of https://bugs.webkit.org/show_bug.cgi?id=149264 issue.
+ // Once upstream issue is resolved this function should be removed and its concurrences should be refactored to
+ // follow the default code path.
+ function iOSViewportElement( editor ) {
+ return editor.window.getFrame().getParent();
+ }
+
+ function encodeItem( item ) {
+ return CKEDITOR.tools.array.reduce( CKEDITOR.tools.object.keys( item ), function( cur, key ) {
+ cur[ key ] = CKEDITOR.tools.htmlEncode( item[ key ] );
+ return cur;
+ }, {} );
+ }
+
+ function removeLeadingSpace( editor ) {
+ var selection = editor.getSelection(),
+ nextNode = selection.getRanges()[ 0 ].getNextNode( function( node ) {
+ return Boolean( node.type == CKEDITOR.NODE_TEXT && node.getText() );
+ } );
+
+ if ( nextNode && nextNode.getText().match( /^\s+/ ) ) {
+ var range = editor.createRange();
+
+ range.setStart( nextNode, 0 );
+ range.setEnd( nextNode, 1 );
+ range.deleteContents();
+ }
+ }
+
+ /**
+ * Abstract class describing the definition of the [Autocomplete](https://ckeditor.com/cke4/addon/autocomplete) plugin configuration.
+ *
+ * It lists properties used to define and create autocomplete configuration definition.
+ *
+ * Simple usage:
+ *
+ * ```javascript
+ * var definition = {
+ * dataCallback: dataCallback,
+ * textTestCallback: textTestCallback,
+ * throttle: 200
+ * };
+ * ```
+ *
+ * @class CKEDITOR.plugins.autocomplete.configDefinition
+ * @abstract
+ * @since 4.10.0
+ */
+
+ /**
+ * Callback executed to get suggestion data based on the search query. The returned data will be
+ * displayed in the autocomplete view.
+ *
+ * ```javascript
+ * // Returns (through its callback) the suggestions for the current query.
+ * // Note: The itemsArray variable is the example "database".
+ * function dataCallback( matchInfo, callback ) {
+ * // Simple search.
+ * // Filter the entire items array so only the items that start
+ * // with the query remain.
+ * var suggestions = itemsArray.filter( function( item ) {
+ * return item.name.indexOf( matchInfo.query ) === 0;
+ * } );
+ *
+ * // Note: The callback function can also be executed asynchronously
+ * // so dataCallback can do an XHR request or use any other asynchronous API.
+ * callback( suggestions );
+ * }
+ *
+ * ```
+ *
+ * @method dataCallback
+ * @param {CKEDITOR.plugins.autocomplete.matchInfo} matchInfo
+ * @param {Function} callback The callback which should be executed with the matched data.
+ * @param {CKEDITOR.plugins.autocomplete.model.item[]} callback.data The suggestion data that should be
+ * displayed in the autocomplete view for a given query. The data items should implement the
+ * {@link CKEDITOR.plugins.autocomplete.model.item} interface.
+ */
+
+ /**
+ * Callback executed to check if a text next to the selection should open
+ * the autocomplete. See the {@link CKEDITOR.plugins.textWatcher}'s `callback` argument.
+ *
+ * ```javascript
+ * // Called when the user types in the editor or moves the caret.
+ * // The range represents the caret position.
+ * function textTestCallback( range ) {
+ * // You do not want to autocomplete a non-empty selection.
+ * if ( !range.collapsed ) {
+ * return null;
+ * }
+ *
+ * // Use the text match plugin which does the tricky job of doing
+ * // a text search in the DOM. The matchCallback function should return
+ * // a matching fragment of the text.
+ * return CKEDITOR.plugins.textMatch.match( range, matchCallback );
+ * }
+ *
+ * // Returns a position of the matching text.
+ * // It matches with a word starting from the '@' character
+ * // up to the caret position.
+ * function matchCallback( text, offset ) {
+ * // Get the text before the caret.
+ * var left = text.slice( 0, offset ),
+ * // Will look for an '@' character followed by word characters.
+ * match = left.match( /@\w*$/ );
+ *
+ * if ( !match ) {
+ * return null;
+ * }
+ * return { start: match.index, end: offset };
+ * }
+ * ```
+ *
+ * @method textTestCallback
+ * @param {CKEDITOR.dom.range} range Range representing the caret position.
+ */
+
+ /**
+ * @inheritdoc CKEDITOR.plugins.autocomplete#throttle
+ * @property {Number} [throttle]
+ */
+
+ /**
+ * @inheritdoc CKEDITOR.plugins.autocomplete.model#itemsLimit
+ * @property {Number} [itemsLimit]
+ */
+
+ /**
+ * @inheritdoc CKEDITOR.plugins.autocomplete.view#itemTemplate
+ * @property {String} [itemTemplate]
+ */
+
+ /**
+ * @inheritdoc CKEDITOR.plugins.autocomplete#outputTemplate
+ * @property {String} [outputTemplate]
+ */
+
+ /**
+ * @inheritdoc CKEDITOR.plugins.autocomplete#followingSpace
+ * @since 4.20.0
+ * @property {Boolean} [followingSpace]
+ */
+
+ /**
+ * Abstract class describing a set of properties that can be used to produce more adequate suggestion data based on the matched query.
+ *
+ * @class CKEDITOR.plugins.autocomplete.matchInfo
+ * @abstract
+ * @since 4.10.0
+ */
+
+ /**
+ * The query string that was accepted by the
+ * {@link CKEDITOR.plugins.autocomplete.configDefinition#textTestCallback config.textTestCallback}.
+ *
+ * @property {String} query
+ */
+
+ /**
+ * The range in the DOM indicating the position of the {@link #query}.
+ *
+ * @property {CKEDITOR.dom.range} range
+ */
+
+ /**
+ * The {@link CKEDITOR.plugins.autocomplete Autocomplete} instance that matched the query.
+ *
+ * @property {CKEDITOR.plugins.autocomplete} autocomplete
+ */
+} )();
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/autocomplete/skins/default.css b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/autocomplete/skins/default.css
new file mode 100644
index 00000000000..b647916f6e1
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/autocomplete/skins/default.css
@@ -0,0 +1,38 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+
+.cke_autocomplete_panel
+{
+ position: absolute;
+ display: none;
+ box-sizing: border-box;
+ width: 200px;
+ max-height: 300px;
+ overflow: auto;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ background: #FFF;
+ border: 1px solid #b6b6b6;
+ border-bottom-color: #999;
+ border-radius: 3px;
+ font: 12px Arial, Helvetica, Tahoma, Verdana, Sans-Serif;
+}
+.cke_autocomplete_opened
+{
+ display: block;
+}
+.cke_autocomplete_panel > li
+{
+ padding: 5px;
+}
+.cke_autocomplete_panel > li:hover
+{
+ cursor: pointer;
+}
+.cke_autocomplete_selected, .cke_autocomplete_panel > li:hover
+{
+ background-color: #EFF0EF;
+}
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/button/plugin.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/button/plugin.js
new file mode 100644
index 00000000000..779c58ea4e9
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/button/plugin.js
@@ -0,0 +1,446 @@
+/**
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+ * CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+ */
+
+( function() {
+ var template = '' +
+ ' ' +
+ '{label} ' +
+ '{ariaShortcutSpace}{ariaShortcut} ' +
+ '{arrowHtml}' +
+ ' ';
+
+ var templateArrow = '' +
+ // BLACK DOWN-POINTING TRIANGLE
+ ( CKEDITOR.env.hc ? '▼' : '' ) +
+ ' ';
+
+ var btnArrowTpl = CKEDITOR.addTemplate( 'buttonArrow', templateArrow ),
+ btnTpl = CKEDITOR.addTemplate( 'button', template );
+
+ CKEDITOR.plugins.add( 'button', {
+ beforeInit: function( editor ) {
+ editor.ui.addHandler( CKEDITOR.UI_BUTTON, CKEDITOR.ui.button.handler );
+ }
+ } );
+
+ /**
+ * Button UI element.
+ *
+ * @readonly
+ * @property {String} [='button']
+ * @member CKEDITOR
+ */
+ CKEDITOR.UI_BUTTON = 'button';
+
+ /**
+ * Represents a button UI element. This class should not be called directly. To
+ * create new buttons use {@link CKEDITOR.ui#addButton} instead.
+ *
+ * @class
+ * @constructor Creates a button class instance.
+ * @param {Object} definition The button definition.
+ */
+ CKEDITOR.ui.button = function( definition ) {
+ CKEDITOR.tools.extend( this, definition,
+ // Set defaults.
+ {
+ isToggle: definition.isToggle || false,
+ title: definition.label,
+ click: definition.click ||
+ function( editor ) {
+ editor.execCommand( definition.command );
+ }
+ } );
+
+ this._ = {};
+ };
+
+ /**
+ * Represents the button handler object.
+ *
+ * @class
+ * @singleton
+ * @extends CKEDITOR.ui.handlerDefinition
+ */
+ CKEDITOR.ui.button.handler = {
+ /**
+ * Transforms a button definition into a {@link CKEDITOR.ui.button} instance.
+ *
+ * @member CKEDITOR.ui.button.handler
+ * @param {Object} definition
+ * @returns {CKEDITOR.ui.button}
+ */
+ create: function( definition ) {
+ return new CKEDITOR.ui.button( definition );
+ }
+ };
+
+ /** @class CKEDITOR.ui.button */
+ CKEDITOR.ui.button.prototype = {
+ /**
+ * Renders the button.
+ *
+ * @param {CKEDITOR.editor} editor The editor instance which this button is
+ * to be used by.
+ * @param {Array} output The output array to which the HTML code related to
+ * this button should be appended.
+ */
+ render: function( editor, output ) {
+ var modeStates = null;
+
+ function updateState() {
+ // "this" is a CKEDITOR.ui.button instance.
+ var mode = editor.mode;
+
+ if ( mode ) {
+ // Restore saved button state.
+ var state = this.modes[ mode ] ? modeStates[ mode ] !== undefined ? modeStates[ mode ] : CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;
+
+ state = editor.readOnly && !this.readOnly ? CKEDITOR.TRISTATE_DISABLED : state;
+
+ this.setState( state );
+
+ // Let plugin to disable button.
+ if ( this.refresh )
+ this.refresh();
+ }
+ }
+
+ var env = CKEDITOR.env,
+ id = this._.id = CKEDITOR.tools.getNextId(),
+ stateName = '',
+ command = this.command,
+ // Get the command name.
+ clickFn,
+ keystroke,
+ shortcut;
+
+ this._.editor = editor;
+
+ var instance = {
+ id: id,
+ button: this,
+ editor: editor,
+ focus: function() {
+ var element = CKEDITOR.document.getById( id );
+ element.focus();
+ },
+ execute: function() {
+ this.button.click( editor );
+ },
+ attach: function( editor ) {
+ this.button.attach( editor );
+ }
+ };
+
+ var keydownFn = CKEDITOR.tools.addFunction( function( ev ) {
+ if ( instance.onkey ) {
+ ev = new CKEDITOR.dom.event( ev );
+ return ( instance.onkey( instance, ev.getKeystroke() ) !== false );
+ }
+ } );
+
+ var focusFn = CKEDITOR.tools.addFunction( function( ev ) {
+ var retVal;
+
+ if ( instance.onfocus )
+ retVal = ( instance.onfocus( instance, new CKEDITOR.dom.event( ev ) ) !== false );
+
+ return retVal;
+ } );
+
+ var selLocked = 0;
+
+ instance.clickFn = clickFn = CKEDITOR.tools.addFunction( function() {
+
+ // Restore locked selection in Opera.
+ if ( selLocked ) {
+ editor.unlockSelection( 1 );
+ selLocked = 0;
+ }
+ instance.execute();
+
+ // Fixed iOS focus issue when your press disabled button (https://dev.ckeditor.com/ticket/12381).
+ if ( env.iOS ) {
+ editor.focus();
+ }
+ } );
+
+
+ // Indicate a mode sensitive button.
+ if ( this.modes ) {
+ modeStates = {};
+
+ editor.on( 'beforeModeUnload', function() {
+ if ( editor.mode && this._.state != CKEDITOR.TRISTATE_DISABLED )
+ modeStates[ editor.mode ] = this._.state;
+ }, this );
+
+ // Update status when activeFilter, mode or readOnly changes.
+ editor.on( 'activeFilterChange', updateState, this );
+ editor.on( 'mode', updateState, this );
+ // If this button is sensitive to readOnly state, update it accordingly.
+ !this.readOnly && editor.on( 'readOnly', updateState, this );
+
+ } else if ( command ) {
+ // Get the command instance.
+ command = editor.getCommand( command );
+
+ if ( command ) {
+ command.on( 'state', function() {
+ this.setState( command.state );
+ }, this );
+
+ stateName += ( command.state == CKEDITOR.TRISTATE_ON ? 'on' : command.state == CKEDITOR.TRISTATE_DISABLED ? 'disabled' : 'off' );
+ }
+ }
+
+ var iconName;
+
+ // For button that has text-direction awareness on selection path.
+ if ( this.directional ) {
+ editor.on( 'contentDirChanged', function( evt ) {
+ var el = CKEDITOR.document.getById( this._.id ),
+ icon = el.getFirst();
+
+ var pathDir = evt.data;
+
+ // Make a minor direction change to become style-able for the skin icon.
+ if ( pathDir != editor.lang.dir )
+ el.addClass( 'cke_' + pathDir );
+ else
+ el.removeClass( 'cke_ltr' ).removeClass( 'cke_rtl' );
+
+ // Inline style update for the plugin icon.
+ icon.setAttribute( 'style', CKEDITOR.skin.getIconStyle( iconName, pathDir == 'rtl', this.icon, this.iconOffset ) );
+ }, this );
+ }
+
+ if ( !command ) {
+ stateName += 'off';
+ } else {
+ keystroke = editor.getCommandKeystroke( command );
+
+ if ( keystroke ) {
+ shortcut = CKEDITOR.tools.keystrokeToString( editor.lang.common.keyboard, keystroke );
+ }
+ }
+
+ var name = this.name || this.command,
+ iconPath = null,
+ overridePath = this.icon;
+
+ iconName = name;
+
+ // Check if we're pointing to an icon defined by another command. (https://dev.ckeditor.com/ticket/9555)
+ if ( this.icon && !( /\./ ).test( this.icon ) ) {
+ iconName = this.icon;
+ overridePath = null;
+
+ } else {
+ // Register and use custom icon for button (#1530).
+ if ( this.icon ) {
+ iconPath = this.icon;
+ }
+ if ( CKEDITOR.env.hidpi && this.iconHiDpi ) {
+ iconPath = this.iconHiDpi;
+ }
+ }
+
+ if ( iconPath ) {
+ CKEDITOR.skin.addIcon( iconPath, iconPath );
+ overridePath = null;
+ } else {
+ iconPath = iconName;
+ }
+
+ var params = {
+ id: id,
+ name: name,
+ iconName: iconName,
+ label: this.label,
+ // .cke_button_expandable enables additional styling for popup buttons (#2483).
+ cls: ( this.hasArrow ? 'cke_button_expandable ' : '' ) + ( this.className || '' ),
+ state: stateName,
+ ariaDisabled: stateName == 'disabled' ? 'true' : 'false',
+ title: this.title + ( shortcut ? ' (' + shortcut.display + ')' : '' ),
+ ariaShortcutSpace: shortcut ? ' ' : '',
+ ariaShortcut: shortcut ? editor.lang.common.keyboardShortcut + ' ' + shortcut.aria : '',
+ titleJs: env.gecko && !env.hc ? '' : ( this.title || '' ).replace( "'", '' ),
+ hasArrow: typeof this.hasArrow === 'string' && this.hasArrow || ( this.hasArrow ? 'true' : 'false' ),
+ keydownFn: keydownFn,
+ focusFn: focusFn,
+ clickFn: clickFn,
+ style: CKEDITOR.skin.getIconStyle( iconPath, ( editor.lang.dir == 'rtl' ), overridePath, this.iconOffset ),
+ arrowHtml: this.hasArrow ? btnArrowTpl.output() : '',
+ hasArrowAriaHtml: this.hasArrow ? ' aria-expanded="false"' : '',
+ toggleAriaHtml: this.isToggle ? 'aria-pressed="false"' : ''
+ };
+
+ btnTpl.output( params, output );
+
+ if ( this.onRender )
+ this.onRender();
+
+ return instance;
+ },
+
+ /**
+ * Sets the button state.
+ *
+ * @param {Number} state Indicates the button state. One of {@link CKEDITOR#TRISTATE_ON},
+ * {@link CKEDITOR#TRISTATE_OFF}, or {@link CKEDITOR#TRISTATE_DISABLED}.
+ */
+ setState: function( state ) {
+ if ( this._.state == state )
+ return false;
+
+ this._.state = state;
+
+ var element = CKEDITOR.document.getById( this._.id );
+
+ if ( element ) {
+ element.setState( state, 'cke_button' );
+ element.setAttribute( 'aria-disabled', state == CKEDITOR.TRISTATE_DISABLED );
+
+ if ( this.isToggle && !this.hasArrow ) {
+ // Note: aria-pressed attribute should not be added to menuButton instances. (https://dev.ckeditor.com/ticket/11331).
+ // For other buttons, do not remove the attribute, instead set its value (#2444).
+ element.setAttribute( 'aria-pressed', state === CKEDITOR.TRISTATE_ON );
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Gets the button state.
+ *
+ * @returns {Number} The button state. One of {@link CKEDITOR#TRISTATE_ON},
+ * {@link CKEDITOR#TRISTATE_OFF}, or {@link CKEDITOR#TRISTATE_DISABLED}.
+ */
+ getState: function() {
+ return this._.state;
+ },
+
+ /**
+ * Returns this button's {@link CKEDITOR.feature} instance.
+ *
+ * It may be this button instance if it has at least one of
+ * `allowedContent` and `requiredContent` properties. Otherwise,
+ * if a command is bound to this button by the `command` property, then
+ * that command will be returned.
+ *
+ * This method implements the {@link CKEDITOR.feature#toFeature} interface method.
+ *
+ * @since 4.1.0
+ * @param {CKEDITOR.editor} Editor instance.
+ * @returns {CKEDITOR.feature} The feature.
+ */
+ toFeature: function( editor ) {
+ if ( this._.feature )
+ return this._.feature;
+
+ var feature = this;
+
+ // If button isn't a feature, return command if is bound.
+ if ( !this.allowedContent && !this.requiredContent && this.command )
+ feature = editor.getCommand( this.command ) || feature;
+
+ return this._.feature = feature;
+ }
+ };
+
+ /**
+ * Adds a button definition to the UI elements list.
+ *
+ * editorInstance.ui.addButton( 'MyBold', {
+ * label: 'My Bold',
+ * command: 'bold',
+ * toolbar: 'basicstyles,1'
+ * } );
+ *
+ * @member CKEDITOR.ui
+ * @param {String} name The button name.
+ * @param {Object} definition The button definition.
+ * @param {String} definition.label The textual part of the button (if visible) and its tooltip.
+ * @param {String} definition.command The command to be executed once the button is activated.
+ * @param {String} definition.toolbar The {@link CKEDITOR.config#toolbarGroups toolbar group} into which
+ * the button will be added. An optional index value (separated by a comma) determines the button position within the group.
+ * @param {String} definition.icon The path to a custom icon or icon name registered by another plugin. Custom icon paths
+ * are supported since the **4.9.0** version.
+ *
+ * To use icon registered by another plugin, icon parameter should be used like:
+ *
+ * editor.ui.addButton( 'my_button', {
+ * icon: 'Link' // Uses link icon from Link plugin.
+ * } );
+ *
+ * If the plugin provides a HiDPI version of an icon, it will be used for HiDPI displays (so defining `iconHiDpi` is not needed
+ * in this case).
+ *
+ * To use a custom icon, the path to the icon should be provided:
+ *
+ * editor.ui.addButton( 'my_button', {
+ * icon: 'assets/icons/my_button.png'
+ * } )
+ *
+ * This icon will be used for both standard and HiDPI displays unless `iconHiDpi` is explicitly defined.
+ * **Important**: CKEditor will resolve relative paths based on {@link CKEDITOR#basePath}.
+ * @param {String} definition.iconHiDpi The path to the custom HiDPI icon version. Supported since **4.9.0** version.
+ * It will be used only in HiDPI environments. The usage is similar to the `icon` parameter:
+ *
+ * editor.ui.addButton( 'my_button', {
+ * iconHiDpi: 'assets/icons/my_button.hidpi.png'
+ * } )
+ * @param {String/Boolean} definition.hasArrow If Boolean, it indicates whether the button should have a dropdown. If a string, it acts
+ * as a value of the button's `aria-haspopup` attribute. Since **4.11.0** it supports the string as a value.
+ * @param {Boolean} [definition.isToggle=false] Indicates if the button should be treated as a toggle one
+ * (button that can be switched on and off, e.g. the "Bold" button). This option is supported since the **4.19.0** version.
+ */
+ CKEDITOR.ui.prototype.addButton = function( name, definition ) {
+ this.add( name, CKEDITOR.UI_BUTTON, definition );
+ };
+
+} )();
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/assets/iconsall.png b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/assets/iconsall.png
new file mode 100644
index 00000000000..b57109d1413
Binary files /dev/null and b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/assets/iconsall.png differ
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/assets/iconsall.svg b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/assets/iconsall.svg
new file mode 100644
index 00000000000..da6bc8d34e0
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/assets/iconsall.svg
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/emoji.json b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/emoji.json
new file mode 100644
index 00000000000..02be751e7bc
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/emoji.json
@@ -0,0 +1 @@
+[{"id":":grinning_face:","symbol":"😀","group":"people","keywords":["face","grin","grinning face"]},{"id":":beaming_face_with_smiling_eyes:","symbol":"😁","group":"people","keywords":["beaming face with smiling eyes","eye","face","grin","smile"]},{"id":":face_with_tears_of_joy:","symbol":"😂","group":"people","keywords":["face","face with tears of joy","joy","laugh","tear"]},{"id":":rolling_on_the_floor_laughing:","symbol":"🤣","group":"people","keywords":["face","floor","laugh","rolling","rolling on the floor laughing"]},{"id":":grinning_face_with_big_eyes:","symbol":"😃","group":"people","keywords":["face","grinning face with big eyes","mouth","open","smile"]},{"id":":grinning_face_with_smiling_eyes:","symbol":"😄","group":"people","keywords":["eye","face","grinning face with smiling eyes","mouth","open","smile"]},{"id":":grinning_face_with_sweat:","symbol":"😅","group":"people","keywords":["cold","face","grinning face with sweat","open","smile","sweat"]},{"id":":grinning_squinting_face:","symbol":"😆","group":"people","keywords":["face","grinning squinting face","laugh","mouth","satisfied","smile"]},{"id":":winking_face:","symbol":"😉","group":"people","keywords":["face","wink","winking face"]},{"id":":smiling_face_with_smiling_eyes:","symbol":"😊","group":"people","keywords":["blush","eye","face","smile","smiling face with smiling eyes"]},{"id":":face_savoring_food:","symbol":"😋","group":"people","keywords":["delicious","face","face savoring food","savouring","smile","yum"]},{"id":":smiling_face_with_sunglasses:","symbol":"😎","group":"people","keywords":["bright","cool","face","smiling face with sunglasses","sun","sunglasses"]},{"id":":smiling_face_with_heart-eyes:","symbol":"😍","group":"people","keywords":["eye","face","love","smile","smiling face with heart-eyes"]},{"id":":face_blowing_a_kiss:","symbol":"😘","group":"people","keywords":["face","face blowing a kiss","kiss"]},{"id":":kissing_face:","symbol":"😗","group":"people","keywords":["face","kiss","kissing face"]},{"id":":kissing_face_with_smiling_eyes:","symbol":"😙","group":"people","keywords":["eye","face","kiss","kissing face with smiling eyes","smile"]},{"id":":kissing_face_with_closed_eyes:","symbol":"😚","group":"people","keywords":["closed","eye","face","kiss","kissing face with closed eyes"]},{"id":":smiling_face:","symbol":"☺","group":"people","keywords":["face","outlined","relaxed","smile","smiling face"]},{"id":":slightly_smiling_face:","symbol":"🙂","group":"people","keywords":["face","slightly smiling face","smile"]},{"id":":hugging_face:","symbol":"🤗","group":"people","keywords":["face","hug","hugging"]},{"id":":star-struck:","symbol":"🤩","group":"people","keywords":["eyes","face","grinning","star","star-struck",""]},{"id":":thinking_face:","symbol":"🤔","group":"people","keywords":["face","thinking"]},{"id":":face_with_raised_eyebrow:","symbol":"🤨","group":"people","keywords":["distrust","face with raised eyebrow","skeptic",""]},{"id":":neutral_face:","symbol":"😐","group":"people","keywords":["deadpan","face","neutral"]},{"id":":expressionless_face:","symbol":"😑","group":"people","keywords":["expressionless","face","inexpressive","unexpressive"]},{"id":":face_without_mouth:","symbol":"😶","group":"people","keywords":["face","face without mouth","mouth","quiet","silent"]},{"id":":face_with_rolling_eyes:","symbol":"🙄","group":"people","keywords":["eyes","face","face with rolling eyes","rolling"]},{"id":":smirking_face:","symbol":"😏","group":"people","keywords":["face","smirk","smirking face"]},{"id":":persevering_face:","symbol":"😣","group":"people","keywords":["face","persevere","persevering face"]},{"id":":sad_but_relieved_face:","symbol":"😥","group":"people","keywords":["disappointed","face","relieved","sad but relieved face","whew"]},{"id":":face_with_open_mouth:","symbol":"😮","group":"people","keywords":["face","face with open mouth","mouth","open","sympathy"]},{"id":":zipper-mouth_face:","symbol":"🤐","group":"people","keywords":["face","mouth","zipper","zipper-mouth face"]},{"id":":hushed_face:","symbol":"😯","group":"people","keywords":["face","hushed","stunned","surprised"]},{"id":":sleepy_face:","symbol":"😪","group":"people","keywords":["face","sleep","sleepy face"]},{"id":":tired_face:","symbol":"😫","group":"people","keywords":["face","tired"]},{"id":":sleeping_face:","symbol":"😴","group":"people","keywords":["face","sleep","sleeping face","zzz"]},{"id":":relieved_face:","symbol":"😌","group":"people","keywords":["face","relieved"]},{"id":":face_with_tongue:","symbol":"😛","group":"people","keywords":["face","face with tongue","tongue"]},{"id":":winking_face_with_tongue:","symbol":"😜","group":"people","keywords":["eye","face","joke","tongue","wink","winking face with tongue"]},{"id":":squinting_face_with_tongue:","symbol":"😝","group":"people","keywords":["eye","face","horrible","squinting face with tongue","taste","tongue"]},{"id":":drooling_face:","symbol":"🤤","group":"people","keywords":["drooling","face"]},{"id":":unamused_face:","symbol":"😒","group":"people","keywords":["face","unamused","unhappy"]},{"id":":downcast_face_with_sweat:","symbol":"😓","group":"people","keywords":["cold","downcast face with sweat","face","sweat"]},{"id":":pensive_face:","symbol":"😔","group":"people","keywords":["dejected","face","pensive"]},{"id":":confused_face:","symbol":"😕","group":"people","keywords":["confused","face"]},{"id":":upside-down_face:","symbol":"🙃","group":"people","keywords":["face","upside-down"]},{"id":":money-mouth_face:","symbol":"🤑","group":"people","keywords":["face","money","money-mouth face","mouth"]},{"id":":astonished_face:","symbol":"😲","group":"people","keywords":["astonished","face","shocked","totally"]},{"id":":frowning_face:","symbol":"☹","group":"people","keywords":["face","frown","frowning face"]},{"id":":slightly_frowning_face:","symbol":"🙁","group":"people","keywords":["face","frown","slightly frowning face"]},{"id":":confounded_face:","symbol":"😖","group":"people","keywords":["confounded","face"]},{"id":":disappointed_face:","symbol":"😞","group":"people","keywords":["disappointed","face"]},{"id":":worried_face:","symbol":"😟","group":"people","keywords":["face","worried"]},{"id":":face_with_steam_from_nose:","symbol":"😤","group":"people","keywords":["face","face with steam from nose","triumph","won"]},{"id":":crying_face:","symbol":"😢","group":"people","keywords":["cry","crying face","face","sad","tear"]},{"id":":loudly_crying_face:","symbol":"😭","group":"people","keywords":["cry","face","loudly crying face","sad","sob","tear"]},{"id":":frowning_face_with_open_mouth:","symbol":"😦","group":"people","keywords":["face","frown","frowning face with open mouth","mouth","open"]},{"id":":anguished_face:","symbol":"😧","group":"people","keywords":["anguished","face"]},{"id":":fearful_face:","symbol":"😨","group":"people","keywords":["face","fear","fearful","scared"]},{"id":":weary_face:","symbol":"😩","group":"people","keywords":["face","tired","weary"]},{"id":":exploding_head:","symbol":"🤯","group":"people","keywords":["exploding head","shocked"]},{"id":":grimacing_face:","symbol":"😬","group":"people","keywords":["face","grimace","grimacing face"]},{"id":":anxious_face_with_sweat:","symbol":"😰","group":"people","keywords":["anxious face with sweat","blue","cold","face","rushed","sweat"]},{"id":":face_screaming_in_fear:","symbol":"😱","group":"people","keywords":["face","face screaming in fear","fear","munch","scared","scream"]},{"id":":flushed_face:","symbol":"😳","group":"people","keywords":["dazed","face","flushed"]},{"id":":zany_face:","symbol":"🤪","group":"people","keywords":["eye","goofy","large","small","zany face"]},{"id":":dizzy_face:","symbol":"😵","group":"people","keywords":["dizzy","face"]},{"id":":pouting_face:","symbol":"😡","group":"people","keywords":["angry","face","mad","pouting","rage","red"]},{"id":":angry_face:","symbol":"😠","group":"people","keywords":["angry","face","mad"]},{"id":":face_with_symbols_on_mouth:","symbol":"🤬","group":"people","keywords":["face with symbols on mouth","swearing",""]},{"id":":face_with_medical_mask:","symbol":"😷","group":"people","keywords":["cold","doctor","face","face with medical mask","mask","sick"]},{"id":":face_with_thermometer:","symbol":"🤒","group":"people","keywords":["face","face with thermometer","ill","sick","thermometer"]},{"id":":face_with_head-bandage:","symbol":"🤕","group":"people","keywords":["bandage","face","face with head-bandage","hurt","injury"]},{"id":":nauseated_face:","symbol":"🤢","group":"people","keywords":["face","nauseated","vomit"]},{"id":":face_vomiting:","symbol":"🤮","group":"people","keywords":["face vomiting","sick","vomit"]},{"id":":sneezing_face:","symbol":"🤧","group":"people","keywords":["face","gesundheit","sneeze","sneezing face"]},{"id":":smiling_face_with_halo:","symbol":"😇","group":"people","keywords":["angel","face","fantasy","halo","innocent","smiling face with halo"]},{"id":":cowboy_hat_face:","symbol":"🤠","group":"people","keywords":["cowboy","cowgirl","face","hat"]},{"id":":lying_face:","symbol":"🤥","group":"people","keywords":["face","lie","lying face","pinocchio"]},{"id":":shushing_face:","symbol":"🤫","group":"people","keywords":["quiet","shush","shushing face"]},{"id":":face_with_hand_over_mouth:","symbol":"🤭","group":"people","keywords":["face with hand over mouth","whoops",""]},{"id":":face_with_monocle:","symbol":"🧐","group":"people","keywords":["face with monocle","stuffy",""]},{"id":":nerd_face:","symbol":"🤓","group":"people","keywords":["face","geek","nerd"]},{"id":":smiling_face_with_horns:","symbol":"😈","group":"people","keywords":["face","fairy tale","fantasy","horns","smile","smiling face with horns"]},{"id":":angry_face_with_horns:","symbol":"👿","group":"people","keywords":["angry face with horns","demon","devil","face","fantasy","imp"]},{"id":":clown_face:","symbol":"🤡","group":"people","keywords":["clown","face"]},{"id":":ogre:","symbol":"👹","group":"people","keywords":["creature","face","fairy tale","fantasy","monster","ogre",""]},{"id":":goblin:","symbol":"👺","group":"people","keywords":["creature","face","fairy tale","fantasy","goblin","monster"]},{"id":":skull:","symbol":"💀","group":"people","keywords":["death","face","fairy tale","monster","skull"]},{"id":":skull_and_crossbones:","symbol":"☠","group":"people","keywords":["crossbones","death","face","monster","skull","skull and crossbones"]},{"id":":ghost:","symbol":"👻","group":"people","keywords":["creature","face","fairy tale","fantasy","ghost","monster"]},{"id":":alien:","symbol":"👽","group":"people","keywords":["alien","creature","extraterrestrial","face","fantasy","ufo"]},{"id":":alien_monster:","symbol":"👾","group":"people","keywords":["alien","creature","extraterrestrial","face","monster","ufo"]},{"id":":robot_face:","symbol":"🤖","group":"people","keywords":["face","monster","robot"]},{"id":":pile_of_poo:","symbol":"💩","group":"people","keywords":["dung","face","monster","pile of poo","poo","poop"]},{"id":":grinning_cat_face:","symbol":"😺","group":"people","keywords":["cat","face","grinning cat face","mouth","open","smile"]},{"id":":grinning_cat_face_with_smiling_eyes:","symbol":"😸","group":"people","keywords":["cat","eye","face","grin","grinning cat face with smiling eyes","smile"]},{"id":":cat_face_with_tears_of_joy:","symbol":"😹","group":"people","keywords":["cat","cat face with tears of joy","face","joy","tear"]},{"id":":smiling_cat_face_with_heart-eyes:","symbol":"😻","group":"people","keywords":["cat","eye","face","love","smile","smiling cat face with heart-eyes"]},{"id":":cat_face_with_wry_smile:","symbol":"😼","group":"people","keywords":["cat","cat face with wry smile","face","ironic","smile","wry"]},{"id":":kissing_cat_face:","symbol":"😽","group":"people","keywords":["cat","eye","face","kiss","kissing cat face"]},{"id":":weary_cat_face:","symbol":"🙀","group":"people","keywords":["cat","face","oh","surprised","weary"]},{"id":":crying_cat_face:","symbol":"😿","group":"people","keywords":["cat","cry","crying cat face","face","sad","tear"]},{"id":":pouting_cat_face:","symbol":"😾","group":"people","keywords":["cat","face","pouting"]},{"id":":see-no-evil_monkey:","symbol":"🙈","group":"people","keywords":["evil","face","forbidden","monkey","see","see-no-evil monkey"]},{"id":":hear-no-evil_monkey:","symbol":"🙉","group":"people","keywords":["evil","face","forbidden","hear","hear-no-evil monkey","monkey"]},{"id":":speak-no-evil_monkey:","symbol":"🙊","group":"people","keywords":["evil","face","forbidden","monkey","speak","speak-no-evil monkey"]},{"id":":baby:","symbol":"👶","group":"people","keywords":["baby","young"]},{"id":":child:","symbol":"🧒","group":"people","keywords":["child","gender-neutral","unspecified gender","young"]},{"id":":boy:","symbol":"👦","group":"people","keywords":["boy","young"]},{"id":":girl:","symbol":"👧","group":"people","keywords":["girl","Virgo","young","zodiac"]},{"id":":person:","symbol":"🧑","group":"people","keywords":["adult","gender-neutral","person","unspecified gender"]},{"id":":person_blond_hair:","symbol":"👱","group":"people","keywords":["blond","blond-haired person","person: blond hair"]},{"id":":man:","symbol":"👨","group":"people","keywords":["adult","man"]},{"id":":man_blond_hair:","symbol":"👱♂️","group":"people","keywords":["blond","blond-haired man","man","man: blond hair"]},{"id":":man_beard:","symbol":"🧔","group":"people","keywords":["beard","man: beard","person",""]},{"id":":woman:","symbol":"👩","group":"people","keywords":["adult","woman"]},{"id":":woman_blond_hair:","symbol":"👱♀️","group":"people","keywords":["blond-haired woman","blonde","woman","woman: blond hair"]},{"id":":older_person:","symbol":"🧓","group":"people","keywords":["adult","gender-neutral","old","older person","unspecified gender"]},{"id":":old_man:","symbol":"👴","group":"people","keywords":["adult","man","old"]},{"id":":old_woman:","symbol":"👵","group":"people","keywords":["adult","old","woman"]},{"id":":man_health_worker:","symbol":"👨⚕️","group":"people","keywords":["doctor","healthcare","man","man health worker","nurse","therapist"]},{"id":":woman_health_worker:","symbol":"👩⚕️","group":"people","keywords":["doctor","healthcare","nurse","therapist","woman","woman health worker"]},{"id":":man_student:","symbol":"👨🎓","group":"people","keywords":["graduate","man","student"]},{"id":":woman_student:","symbol":"👩🎓","group":"people","keywords":["graduate","student","woman"]},{"id":":man_teacher:","symbol":"👨🏫","group":"people","keywords":["instructor","man","professor","teacher"]},{"id":":woman_teacher:","symbol":"👩🏫","group":"people","keywords":["instructor","professor","teacher","woman"]},{"id":":man_judge:","symbol":"👨⚖️","group":"people","keywords":["justice","man","man judge","scales"]},{"id":":woman_judge:","symbol":"👩⚖️","group":"people","keywords":["judge","scales","woman"]},{"id":":man_farmer:","symbol":"👨🌾","group":"people","keywords":["farmer","gardener","man","rancher"]},{"id":":woman_farmer:","symbol":"👩🌾","group":"people","keywords":["farmer","gardener","rancher","woman"]},{"id":":man_cook:","symbol":"👨🍳","group":"people","keywords":["chef","cook","man"]},{"id":":woman_cook:","symbol":"👩🍳","group":"people","keywords":["chef","cook","woman"]},{"id":":man_mechanic:","symbol":"👨🔧","group":"people","keywords":["electrician","man","mechanic","plumber","tradesperson"]},{"id":":woman_mechanic:","symbol":"👩🔧","group":"people","keywords":["electrician","mechanic","plumber","tradesperson","woman"]},{"id":":man_factory_worker:","symbol":"👨🏭","group":"people","keywords":["assembly","factory","industrial","man","worker"]},{"id":":woman_factory_worker:","symbol":"👩🏭","group":"people","keywords":["assembly","factory","industrial","woman","worker"]},{"id":":man_office_worker:","symbol":"👨💼","group":"people","keywords":["architect","business","man","man office worker","manager","white-collar"]},{"id":":woman_office_worker:","symbol":"👩💼","group":"people","keywords":["architect","business","manager","white-collar","woman","woman office worker"]},{"id":":man_scientist:","symbol":"👨🔬","group":"people","keywords":["biologist","chemist","engineer","man","physicist","scientist"]},{"id":":woman_scientist:","symbol":"👩🔬","group":"people","keywords":["biologist","chemist","engineer","physicist","scientist","woman"]},{"id":":man_technologist:","symbol":"👨💻","group":"people","keywords":["coder","developer","inventor","man","software","technologist"]},{"id":":woman_technologist:","symbol":"👩💻","group":"people","keywords":["coder","developer","inventor","software","technologist","woman"]},{"id":":man_singer:","symbol":"👨🎤","group":"people","keywords":["actor","entertainer","man","rock","singer","star"]},{"id":":woman_singer:","symbol":"👩🎤","group":"people","keywords":["actor","entertainer","rock","singer","star","woman"]},{"id":":man_artist:","symbol":"👨🎨","group":"people","keywords":["artist","man","palette"]},{"id":":woman_artist:","symbol":"👩🎨","group":"people","keywords":["artist","palette","woman"]},{"id":":man_pilot:","symbol":"👨✈️","group":"people","keywords":["man","pilot","plane"]},{"id":":woman_pilot:","symbol":"👩✈️","group":"people","keywords":["pilot","plane","woman"]},{"id":":man_astronaut:","symbol":"👨🚀","group":"people","keywords":["astronaut","man","rocket"]},{"id":":woman_astronaut:","symbol":"👩🚀","group":"people","keywords":["astronaut","rocket","woman"]},{"id":":man_firefighter:","symbol":"👨🚒","group":"people","keywords":["firefighter","firetruck","man"]},{"id":":woman_firefighter:","symbol":"👩🚒","group":"people","keywords":["firefighter","firetruck","woman"]},{"id":":police_officer:","symbol":"👮","group":"people","keywords":["cop","officer","police"]},{"id":":man_police_officer:","symbol":"👮♂️","group":"people","keywords":["cop","man","officer","police"]},{"id":":woman_police_officer:","symbol":"👮♀️","group":"people","keywords":["cop","officer","police","woman"]},{"id":":detective:","symbol":"🕵","group":"people","keywords":["detective","sleuth","spy"]},{"id":":man_detective:","symbol":"🕵️♂️","group":"people","keywords":["detective","man","sleuth","spy"]},{"id":":woman_detective:","symbol":"🕵️♀️","group":"people","keywords":["detective","sleuth","spy","woman"]},{"id":":guard:","symbol":"💂","group":"people","keywords":["guard"]},{"id":":man_guard:","symbol":"💂♂️","group":"people","keywords":["guard","man"]},{"id":":woman_guard:","symbol":"💂♀️","group":"people","keywords":["guard","woman"]},{"id":":construction_worker:","symbol":"👷","group":"people","keywords":["construction","hat","worker"]},{"id":":man_construction_worker:","symbol":"👷♂️","group":"people","keywords":["construction","man","worker"]},{"id":":woman_construction_worker:","symbol":"👷♀️","group":"people","keywords":["construction","woman","worker"]},{"id":":prince:","symbol":"🤴","group":"people","keywords":["prince"]},{"id":":princess:","symbol":"👸","group":"people","keywords":["fairy tale","fantasy","princess"]},{"id":":person_wearing_turban:","symbol":"👳","group":"people","keywords":["person wearing turban","turban"]},{"id":":man_wearing_turban:","symbol":"👳♂️","group":"people","keywords":["man","man wearing turban","turban"]},{"id":":woman_wearing_turban:","symbol":"👳♀️","group":"people","keywords":["turban","woman","woman wearing turban"]},{"id":":man_with_chinese_cap:","symbol":"👲","group":"people","keywords":["gua pi mao","hat","man","man with Chinese cap"]},{"id":":woman_with_headscarf:","symbol":"🧕","group":"people","keywords":["headscarf","hijab","mantilla","tichel","woman with headscarf",""]},{"id":":man_in_tuxedo:","symbol":"🤵","group":"people","keywords":["groom","man","man in tuxedo","tuxedo"]},{"id":":bride_with_veil:","symbol":"👰","group":"people","keywords":["bride","bride with veil","veil","wedding"]},{"id":":pregnant_woman:","symbol":"🤰","group":"people","keywords":["pregnant","woman"]},{"id":":breast-feeding:","symbol":"🤱","group":"people","keywords":["baby","breast","breast-feeding","nursing"]},{"id":":baby_angel:","symbol":"👼","group":"people","keywords":["angel","baby","face","fairy tale","fantasy"]},{"id":":santa_claus:","symbol":"🎅","group":"people","keywords":["celebration","Christmas","claus","father","santa","Santa Claus"]},{"id":":mrs._claus:","symbol":"🤶","group":"people","keywords":["celebration","Christmas","claus","mother","Mrs.","Mrs. Claus"]},{"id":":mage:","symbol":"🧙","group":"people","keywords":["mage","sorcerer","sorceress","witch","wizard"]},{"id":":man_mage:","symbol":"🧙♂️","group":"people","keywords":["man mage","sorcerer","wizard"]},{"id":":woman_mage:","symbol":"🧙♀️","group":"people","keywords":["sorceress","witch","woman mage"]},{"id":":fairy:","symbol":"🧚","group":"people","keywords":["fairy","Oberon","Puck","Titania"]},{"id":":man_fairy:","symbol":"🧚♂️","group":"people","keywords":["man fairy","Oberon","Puck"]},{"id":":woman_fairy:","symbol":"🧚♀️","group":"people","keywords":["Titania","woman fairy"]},{"id":":vampire:","symbol":"🧛","group":"people","keywords":["Dracula","undead","vampire"]},{"id":":man_vampire:","symbol":"🧛♂️","group":"people","keywords":["Dracula","man vampire","undead"]},{"id":":woman_vampire:","symbol":"🧛♀️","group":"people","keywords":["undead","woman vampire"]},{"id":":merperson:","symbol":"🧜","group":"people","keywords":["mermaid","merman","merperson","merwoman"]},{"id":":merman:","symbol":"🧜♂️","group":"people","keywords":["merman","Triton"]},{"id":":mermaid:","symbol":"🧜♀️","group":"people","keywords":["mermaid","merwoman"]},{"id":":elf:","symbol":"🧝","group":"people","keywords":["elf","magical",""]},{"id":":man_elf:","symbol":"🧝♂️","group":"people","keywords":["magical","man elf"]},{"id":":woman_elf:","symbol":"🧝♀️","group":"people","keywords":["magical","woman elf"]},{"id":":genie:","symbol":"🧞","group":"people","keywords":["djinn","genie",""]},{"id":":man_genie:","symbol":"🧞♂️","group":"people","keywords":["djinn","man genie"]},{"id":":woman_genie:","symbol":"🧞♀️","group":"people","keywords":["djinn","woman genie"]},{"id":":zombie:","symbol":"🧟","group":"people","keywords":["undead","walking dead","zombie",""]},{"id":":man_zombie:","symbol":"🧟♂️","group":"people","keywords":["man zombie","undead","walking dead"]},{"id":":woman_zombie:","symbol":"🧟♀️","group":"people","keywords":["undead","walking dead","woman zombie"]},{"id":":person_frowning:","symbol":"🙍","group":"people","keywords":["frown","gesture","person frowning"]},{"id":":man_frowning:","symbol":"🙍♂️","group":"people","keywords":["frowning","gesture","man"]},{"id":":woman_frowning:","symbol":"🙍♀️","group":"people","keywords":["frowning","gesture","woman"]},{"id":":person_pouting:","symbol":"🙎","group":"people","keywords":["gesture","person pouting","pouting"]},{"id":":man_pouting:","symbol":"🙎♂️","group":"people","keywords":["gesture","man","pouting"]},{"id":":woman_pouting:","symbol":"🙎♀️","group":"people","keywords":["gesture","pouting","woman"]},{"id":":person_gesturing_no:","symbol":"🙅","group":"people","keywords":["forbidden","gesture","hand","person gesturing NO","prohibited"]},{"id":":man_gesturing_no:","symbol":"🙅♂️","group":"people","keywords":["forbidden","gesture","hand","man","man gesturing NO","prohibited"]},{"id":":woman_gesturing_no:","symbol":"🙅♀️","group":"people","keywords":["forbidden","gesture","hand","prohibited","woman","woman gesturing NO"]},{"id":":person_gesturing_ok:","symbol":"🙆","group":"people","keywords":["gesture","hand","OK","person gesturing OK"]},{"id":":man_gesturing_ok:","symbol":"🙆♂️","group":"people","keywords":["gesture","hand","man","man gesturing OK","OK"]},{"id":":woman_gesturing_ok:","symbol":"🙆♀️","group":"people","keywords":["gesture","hand","OK","woman","woman gesturing OK"]},{"id":":person_tipping_hand:","symbol":"💁","group":"people","keywords":["hand","help","information","person tipping hand","sassy","tipping"]},{"id":":man_tipping_hand:","symbol":"💁♂️","group":"people","keywords":["man","man tipping hand","sassy","tipping hand"]},{"id":":woman_tipping_hand:","symbol":"💁♀️","group":"people","keywords":["sassy","tipping hand","woman","woman tipping hand"]},{"id":":person_raising_hand:","symbol":"🙋","group":"people","keywords":["gesture","hand","happy","person raising hand","raised"]},{"id":":man_raising_hand:","symbol":"🙋♂️","group":"people","keywords":["gesture","man","man raising hand","raising hand"]},{"id":":woman_raising_hand:","symbol":"🙋♀️","group":"people","keywords":["gesture","raising hand","woman","woman raising hand"]},{"id":":person_bowing:","symbol":"🙇","group":"people","keywords":["apology","bow","gesture","person bowing","sorry"]},{"id":":man_bowing:","symbol":"🙇♂️","group":"people","keywords":["apology","bowing","favor","gesture","man","sorry"]},{"id":":woman_bowing:","symbol":"🙇♀️","group":"people","keywords":["apology","bowing","favor","gesture","sorry","woman"]},{"id":":person_facepalming:","symbol":"🤦","group":"people","keywords":["disbelief","exasperation","face","palm","person facepalming"]},{"id":":man_facepalming:","symbol":"🤦♂️","group":"people","keywords":["disbelief","exasperation","facepalm","man","man facepalming"]},{"id":":woman_facepalming:","symbol":"🤦♀️","group":"people","keywords":["disbelief","exasperation","facepalm","woman","woman facepalming"]},{"id":":person_shrugging:","symbol":"🤷","group":"people","keywords":["doubt","ignorance","indifference","person shrugging","shrug"]},{"id":":man_shrugging:","symbol":"🤷♂️","group":"people","keywords":["doubt","ignorance","indifference","man","man shrugging","shrug"]},{"id":":woman_shrugging:","symbol":"🤷♀️","group":"people","keywords":["doubt","ignorance","indifference","shrug","woman","woman shrugging"]},{"id":":person_getting_massage:","symbol":"💆","group":"people","keywords":["face","massage","person getting massage","salon"]},{"id":":man_getting_massage:","symbol":"💆♂️","group":"people","keywords":["face","man","man getting massage","massage"]},{"id":":woman_getting_massage:","symbol":"💆♀️","group":"people","keywords":["face","massage","woman","woman getting massage"]},{"id":":person_getting_haircut:","symbol":"💇","group":"people","keywords":["barber","beauty","haircut","parlor","person getting haircut"]},{"id":":man_getting_haircut:","symbol":"💇♂️","group":"people","keywords":["haircut","man","man getting haircut"]},{"id":":woman_getting_haircut:","symbol":"💇♀️","group":"people","keywords":["haircut","woman","woman getting haircut"]},{"id":":person_walking:","symbol":"🚶","group":"people","keywords":["hike","person walking","walk","walking"]},{"id":":man_walking:","symbol":"🚶♂️","group":"people","keywords":["hike","man","man walking","walk"]},{"id":":woman_walking:","symbol":"🚶♀️","group":"people","keywords":["hike","walk","woman","woman walking"]},{"id":":person_running:","symbol":"🏃","group":"people","keywords":["marathon","person running","running"]},{"id":":man_running:","symbol":"🏃♂️","group":"people","keywords":["man","marathon","racing","running"]},{"id":":woman_running:","symbol":"🏃♀️","group":"people","keywords":["marathon","racing","running","woman"]},{"id":":woman_dancing:","symbol":"💃","group":"people","keywords":["dancing","woman"]},{"id":":man_dancing:","symbol":"🕺","group":"people","keywords":["dance","man","man dancing"]},{"id":":people_with_bunny_ears:","symbol":"👯","group":"people","keywords":["bunny ear","dancer","partying","people with bunny ears"]},{"id":":men_with_bunny_ears:","symbol":"👯♂️","group":"people","keywords":["bunny ear","dancer","men","men with bunny ears","partying"]},{"id":":women_with_bunny_ears:","symbol":"👯♀️","group":"people","keywords":["bunny ear","dancer","partying","women","women with bunny ears"]},{"id":":person_in_steamy_room:","symbol":"🧖","group":"people","keywords":["person in steamy room","sauna","steam room",""]},{"id":":man_in_steamy_room:","symbol":"🧖♂️","group":"people","keywords":["man in steamy room","sauna","steam room"]},{"id":":woman_in_steamy_room:","symbol":"🧖♀️","group":"people","keywords":["sauna","steam room","woman in steamy room"]},{"id":":person_climbing:","symbol":"🧗","group":"people","keywords":["climber","person climbing"]},{"id":":man_climbing:","symbol":"🧗♂️","group":"people","keywords":["climber","man climbing"]},{"id":":woman_climbing:","symbol":"🧗♀️","group":"people","keywords":["climber","woman climbing"]},{"id":":person_in_lotus_position:","symbol":"🧘","group":"people","keywords":["meditation","person in lotus position","yoga",""]},{"id":":man_in_lotus_position:","symbol":"🧘♂️","group":"people","keywords":["man in lotus position","meditation","yoga"]},{"id":":woman_in_lotus_position:","symbol":"🧘♀️","group":"people","keywords":["meditation","woman in lotus position","yoga"]},{"id":":person_taking_bath:","symbol":"🛀","group":"people","keywords":["bath","bathtub","person taking bath"]},{"id":":person_in_bed:","symbol":"🛌","group":"people","keywords":["hotel","person in bed","sleep"]},{"id":":man_in_suit_levitating:","symbol":"🕴","group":"people","keywords":["business","man","man in suit levitating","suit"]},{"id":":speaking_head:","symbol":"🗣","group":"people","keywords":["face","head","silhouette","speak","speaking"]},{"id":":bust_in_silhouette:","symbol":"👤","group":"people","keywords":["bust","bust in silhouette","silhouette"]},{"id":":busts_in_silhouette:","symbol":"👥","group":"people","keywords":["bust","busts in silhouette","silhouette"]},{"id":":person_fencing:","symbol":"🤺","group":"people","keywords":["fencer","fencing","person fencing","sword"]},{"id":":horse_racing:","symbol":"🏇","group":"people","keywords":["horse","jockey","racehorse","racing"]},{"id":":skier:","symbol":"⛷","group":"people","keywords":["ski","skier","snow"]},{"id":":snowboarder:","symbol":"🏂","group":"people","keywords":["ski","snow","snowboard","snowboarder"]},{"id":":person_golfing:","symbol":"🏌","group":"people","keywords":["ball","golf","person golfing"]},{"id":":man_golfing:","symbol":"🏌️♂️","group":"people","keywords":["golf","man","man golfing"]},{"id":":woman_golfing:","symbol":"🏌️♀️","group":"people","keywords":["golf","woman","woman golfing"]},{"id":":person_surfing:","symbol":"🏄","group":"people","keywords":["person surfing","surfing"]},{"id":":man_surfing:","symbol":"🏄♂️","group":"people","keywords":["man","surfing"]},{"id":":woman_surfing:","symbol":"🏄♀️","group":"people","keywords":["surfing","woman"]},{"id":":person_rowing_boat:","symbol":"🚣","group":"people","keywords":["boat","person rowing boat","rowboat"]},{"id":":man_rowing_boat:","symbol":"🚣♂️","group":"people","keywords":["boat","man","man rowing boat","rowboat"]},{"id":":woman_rowing_boat:","symbol":"🚣♀️","group":"people","keywords":["boat","rowboat","woman","woman rowing boat"]},{"id":":person_swimming:","symbol":"🏊","group":"people","keywords":["person swimming","swim"]},{"id":":man_swimming:","symbol":"🏊♂️","group":"people","keywords":["man","man swimming","swim"]},{"id":":woman_swimming:","symbol":"🏊♀️","group":"people","keywords":["swim","woman","woman swimming"]},{"id":":person_bouncing_ball:","symbol":"⛹","group":"people","keywords":["ball","person bouncing ball"]},{"id":":man_bouncing_ball:","symbol":"⛹️♂️","group":"people","keywords":["ball","man","man bouncing ball"]},{"id":":woman_bouncing_ball:","symbol":"⛹️♀️","group":"people","keywords":["ball","woman","woman bouncing ball"]},{"id":":person_lifting_weights:","symbol":"🏋","group":"people","keywords":["lifter","person lifting weights","weight"]},{"id":":man_lifting_weights:","symbol":"🏋️♂️","group":"people","keywords":["man","man lifting weights","weight lifter"]},{"id":":woman_lifting_weights:","symbol":"🏋️♀️","group":"people","keywords":["weight lifter","woman","woman lifting weights"]},{"id":":person_biking:","symbol":"🚴","group":"people","keywords":["bicycle","biking","cyclist","person biking"]},{"id":":man_biking:","symbol":"🚴♂️","group":"people","keywords":["bicycle","biking","cyclist","man"]},{"id":":woman_biking:","symbol":"🚴♀️","group":"people","keywords":["bicycle","biking","cyclist","woman"]},{"id":":person_mountain_biking:","symbol":"🚵","group":"people","keywords":["bicycle","bicyclist","bike","cyclist","mountain","person mountain biking"]},{"id":":man_mountain_biking:","symbol":"🚵♂️","group":"people","keywords":["bicycle","bike","cyclist","man","man mountain biking","mountain"]},{"id":":woman_mountain_biking:","symbol":"🚵♀️","group":"people","keywords":["bicycle","bike","biking","cyclist","mountain","woman"]},{"id":":racing_car:","symbol":"🏎","group":"people","keywords":["car","racing"]},{"id":":motorcycle:","symbol":"🏍","group":"people","keywords":["motorcycle","racing"]},{"id":":person_cartwheeling:","symbol":"🤸","group":"people","keywords":["cartwheel","gymnastics","person cartwheeling"]},{"id":":man_cartwheeling:","symbol":"🤸♂️","group":"people","keywords":["cartwheel","gymnastics","man","man cartwheeling"]},{"id":":woman_cartwheeling:","symbol":"🤸♀️","group":"people","keywords":["cartwheel","gymnastics","woman","woman cartwheeling"]},{"id":":people_wrestling:","symbol":"🤼","group":"people","keywords":["people wrestling","wrestle","wrestler"]},{"id":":men_wrestling:","symbol":"🤼♂️","group":"people","keywords":["men","men wrestling","wrestle"]},{"id":":women_wrestling:","symbol":"🤼♀️","group":"people","keywords":["women","women wrestling","wrestle"]},{"id":":person_playing_water_polo:","symbol":"🤽","group":"people","keywords":["person playing water polo","polo","water"]},{"id":":man_playing_water_polo:","symbol":"🤽♂️","group":"people","keywords":["man","man playing water polo","water polo"]},{"id":":woman_playing_water_polo:","symbol":"🤽♀️","group":"people","keywords":["water polo","woman","woman playing water polo"]},{"id":":person_playing_handball:","symbol":"🤾","group":"people","keywords":["ball","handball","person playing handball"]},{"id":":man_playing_handball:","symbol":"🤾♂️","group":"people","keywords":["handball","man","man playing handball"]},{"id":":woman_playing_handball:","symbol":"🤾♀️","group":"people","keywords":["handball","woman","woman playing handball"]},{"id":":person_juggling:","symbol":"🤹","group":"people","keywords":["balance","juggle","multitask","person juggling","skill"]},{"id":":man_juggling:","symbol":"🤹♂️","group":"people","keywords":["juggling","man","multitask"]},{"id":":woman_juggling:","symbol":"🤹♀️","group":"people","keywords":["juggling","multitask","woman"]},{"id":":man_and_woman_holding_hands:","symbol":"👫","group":"people","keywords":["couple","hand","hold","man","man and woman holding hands","woman"]},{"id":":two_men_holding_hands:","symbol":"👬","group":"people","keywords":["couple","Gemini","man","twins","two men holding hands","zodiac"]},{"id":":two_women_holding_hands:","symbol":"👭","group":"people","keywords":["couple","hand","two women holding hands","woman"]},{"id":":kiss:","symbol":"💏","group":"people","keywords":["couple","kiss"]},{"id":":kiss_woman_man:","symbol":"👩❤️💋👨","group":"people","keywords":["couple","kiss","man","woman"]},{"id":":kiss_man_man:","symbol":"👨❤️💋👨","group":"people","keywords":["couple","kiss","man"]},{"id":":kiss_woman_woman:","symbol":"👩❤️💋👩","group":"people","keywords":["couple","kiss","woman"]},{"id":":couple_with_heart:","symbol":"💑","group":"people","keywords":["couple","couple with heart","love"]},{"id":":couple_with_heart_woman_man:","symbol":"👩❤️👨","group":"people","keywords":["couple","couple with heart","love","man","woman"]},{"id":":couple_with_heart_man_man:","symbol":"👨❤️👨","group":"people","keywords":["couple","couple with heart","love","man"]},{"id":":couple_with_heart_woman_woman:","symbol":"👩❤️👩","group":"people","keywords":["couple","couple with heart","love","woman"]},{"id":":family:","symbol":"👪","group":"people","keywords":["family"]},{"id":":family_man_woman_boy:","symbol":"👨👩👦","group":"people","keywords":["boy","family","man","woman"]},{"id":":family_man_woman_girl:","symbol":"👨👩👧","group":"people","keywords":["family","girl","man","woman"]},{"id":":family_man_woman_girl_boy:","symbol":"👨👩👧👦","group":"people","keywords":["boy","family","girl","man","woman"]},{"id":":family_man_woman_boy_boy:","symbol":"👨👩👦👦","group":"people","keywords":["boy","family","man","woman"]},{"id":":family_man_woman_girl_girl:","symbol":"👨👩👧👧","group":"people","keywords":["family","girl","man","woman"]},{"id":":family_man_man_boy:","symbol":"👨👨👦","group":"people","keywords":["boy","family","man"]},{"id":":family_man_man_girl:","symbol":"👨👨👧","group":"people","keywords":["family","girl","man"]},{"id":":family_man_man_girl_boy:","symbol":"👨👨👧👦","group":"people","keywords":["boy","family","girl","man"]},{"id":":family_man_man_boy_boy:","symbol":"👨👨👦👦","group":"people","keywords":["boy","family","man"]},{"id":":family_man_man_girl_girl:","symbol":"👨👨👧👧","group":"people","keywords":["family","girl","man"]},{"id":":family_woman_woman_boy:","symbol":"👩👩👦","group":"people","keywords":["boy","family","woman"]},{"id":":family_woman_woman_girl:","symbol":"👩👩👧","group":"people","keywords":["family","girl","woman"]},{"id":":family_woman_woman_girl_boy:","symbol":"👩👩👧👦","group":"people","keywords":["boy","family","girl","woman"]},{"id":":family_woman_woman_boy_boy:","symbol":"👩👩👦👦","group":"people","keywords":["boy","family","woman"]},{"id":":family_woman_woman_girl_girl:","symbol":"👩👩👧👧","group":"people","keywords":["family","girl","woman"]},{"id":":family_man_boy:","symbol":"👨👦","group":"people","keywords":["boy","family","man"]},{"id":":family_man_boy_boy:","symbol":"👨👦👦","group":"people","keywords":["boy","family","man"]},{"id":":family_man_girl:","symbol":"👨👧","group":"people","keywords":["family","girl","man"]},{"id":":family_man_girl_boy:","symbol":"👨👧👦","group":"people","keywords":["boy","family","girl","man"]},{"id":":family_man_girl_girl:","symbol":"👨👧👧","group":"people","keywords":["family","girl","man"]},{"id":":family_woman_boy:","symbol":"👩👦","group":"people","keywords":["boy","family","woman"]},{"id":":family_woman_boy_boy:","symbol":"👩👦👦","group":"people","keywords":["boy","family","woman"]},{"id":":family_woman_girl:","symbol":"👩👧","group":"people","keywords":["family","girl","woman"]},{"id":":family_woman_girl_boy:","symbol":"👩👧👦","group":"people","keywords":["boy","family","girl","woman"]},{"id":":family_woman_girl_girl:","symbol":"👩👧👧","group":"people","keywords":["family","girl","woman"]},{"id":":selfie:","symbol":"🤳","group":"people","keywords":["camera","phone","selfie"]},{"id":":flexed_biceps:","symbol":"💪","group":"people","keywords":["biceps","comic","flex","flexed biceps","muscle"]},{"id":":backhand_index_pointing_left:","symbol":"👈","group":"people","keywords":["backhand","backhand index pointing left","finger","hand","index","point"]},{"id":":backhand_index_pointing_right:","symbol":"👉","group":"people","keywords":["backhand","backhand index pointing right","finger","hand","index","point"]},{"id":":index_pointing_up:","symbol":"☝","group":"people","keywords":["finger","hand","index","index pointing up","point","up"]},{"id":":backhand_index_pointing_up:","symbol":"👆","group":"people","keywords":["backhand","backhand index pointing up","finger","hand","point","up"]},{"id":":middle_finger:","symbol":"🖕","group":"people","keywords":["finger","hand","middle finger"]},{"id":":backhand_index_pointing_down:","symbol":"👇","group":"people","keywords":["backhand","backhand index pointing down","down","finger","hand","point"]},{"id":":victory_hand:","symbol":"✌","group":"people","keywords":["hand","v","victory"]},{"id":":crossed_fingers:","symbol":"🤞","group":"people","keywords":["cross","crossed fingers","finger","hand","luck"]},{"id":":vulcan_salute:","symbol":"🖖","group":"people","keywords":["finger","hand","spock","vulcan","vulcan salute"]},{"id":":sign_of_the_horns:","symbol":"🤘","group":"people","keywords":["finger","hand","horns","rock-on","sign of the horns"]},{"id":":call_me_hand:","symbol":"🤙","group":"people","keywords":["call","call me hand","hand"]},{"id":":hand_with_fingers_splayed:","symbol":"🖐","group":"people","keywords":["finger","hand","hand with fingers splayed","splayed"]},{"id":":raised_hand:","symbol":"✋","group":"people","keywords":["hand","raised hand"]},{"id":":ok_hand:","symbol":"👌","group":"people","keywords":["hand","OK"]},{"id":":thumbs_up:","symbol":"👍","group":"people","keywords":["+1","hand","thumb","thumbs up","up"]},{"id":":thumbs_down:","symbol":"👎","group":"people","keywords":["-1","down","hand","thumb","thumbs down"]},{"id":":raised_fist:","symbol":"✊","group":"people","keywords":["clenched","fist","hand","punch","raised fist"]},{"id":":oncoming_fist:","symbol":"👊","group":"people","keywords":["clenched","fist","hand","oncoming fist","punch"]},{"id":":left-facing_fist:","symbol":"🤛","group":"people","keywords":["fist","left-facing fist","leftwards"]},{"id":":right-facing_fist:","symbol":"🤜","group":"people","keywords":["fist","right-facing fist","rightwards"]},{"id":":raised_back_of_hand:","symbol":"🤚","group":"people","keywords":["backhand","raised","raised back of hand"]},{"id":":waving_hand:","symbol":"👋","group":"people","keywords":["hand","wave","waving"]},{"id":":love-you_gesture:","symbol":"🤟","group":"people","keywords":["hand","ILY","love-you gesture"]},{"id":":writing_hand:","symbol":"✍","group":"people","keywords":["hand","write","writing hand"]},{"id":":clapping_hands:","symbol":"👏","group":"people","keywords":["clap","clapping hands","hand"]},{"id":":open_hands:","symbol":"👐","group":"people","keywords":["hand","open","open hands"]},{"id":":raising_hands:","symbol":"🙌","group":"people","keywords":["celebration","gesture","hand","hooray","raised","raising hands"]},{"id":":palms_up_together:","symbol":"🤲","group":"people","keywords":["palms up together","prayer",""]},{"id":":folded_hands:","symbol":"🙏","group":"people","keywords":["ask","folded hands","hand","please","pray","thanks"]},{"id":":handshake:","symbol":"🤝","group":"people","keywords":["agreement","hand","handshake","meeting","shake"]},{"id":":nail_polish:","symbol":"💅","group":"people","keywords":["care","cosmetics","manicure","nail","polish"]},{"id":":ear:","symbol":"👂","group":"people","keywords":["body","ear"]},{"id":":nose:","symbol":"👃","group":"people","keywords":["body","nose"]},{"id":":footprints:","symbol":"👣","group":"people","keywords":["clothing","footprint","footprints","print"]},{"id":":eyes:","symbol":"👀","group":"people","keywords":["eye","eyes","face"]},{"id":":eye:","symbol":"👁","group":"people","keywords":["body","eye"]},{"id":":eye_in_speech_bubble:","symbol":"👁️🗨️","group":"people","keywords":["eye","eye in speech bubble","speech bubble","witness"]},{"id":":brain:","symbol":"🧠","group":"people","keywords":["brain","intelligent"]},{"id":":tongue:","symbol":"👅","group":"people","keywords":["body","tongue"]},{"id":":mouth:","symbol":"👄","group":"people","keywords":["lips","mouth"]},{"id":":kiss_mark:","symbol":"💋","group":"people","keywords":["kiss","kiss mark","lips"]},{"id":":heart_with_arrow:","symbol":"💘","group":"people","keywords":["arrow","cupid","heart with arrow"]},{"id":":heart_with_ribbon:","symbol":"💝","group":"people","keywords":["heart with ribbon","ribbon","valentine"]},{"id":":sparkling_heart:","symbol":"💖","group":"people","keywords":["excited","sparkle","sparkling heart"]},{"id":":growing_heart:","symbol":"💗","group":"people","keywords":["excited","growing","growing heart","nervous","pulse"]},{"id":":beating_heart:","symbol":"💓","group":"people","keywords":["beating","beating heart","heartbeat","pulsating"]},{"id":":revolving_hearts:","symbol":"💞","group":"people","keywords":["revolving","revolving hearts"]},{"id":":two_hearts:","symbol":"💕","group":"people","keywords":["love","two hearts"]},{"id":":love_letter:","symbol":"💌","group":"people","keywords":["heart","letter","love","mail"]},{"id":":heavy_heart_exclamation:","symbol":"❣","group":"people","keywords":["exclamation","heavy heart exclamation","mark","punctuation"]},{"id":":broken_heart:","symbol":"💔","group":"people","keywords":["break","broken","broken heart"]},{"id":":red_heart:","symbol":"❤","group":"people","keywords":["heart","red heart"]},{"id":":orange_heart:","symbol":"🧡","group":"people","keywords":["orange","orange heart"]},{"id":":yellow_heart:","symbol":"💛","group":"people","keywords":["yellow","yellow heart"]},{"id":":green_heart:","symbol":"💚","group":"people","keywords":["green","green heart"]},{"id":":blue_heart:","symbol":"💙","group":"people","keywords":["blue","blue heart"]},{"id":":purple_heart:","symbol":"💜","group":"people","keywords":["purple","purple heart"]},{"id":":black_heart:","symbol":"🖤","group":"people","keywords":["black","black heart","evil","wicked"]},{"id":":heart_decoration:","symbol":"💟","group":"people","keywords":["heart","heart decoration"]},{"id":":zzz:","symbol":"💤","group":"people","keywords":["comic","sleep","zzz"]},{"id":":anger_symbol:","symbol":"💢","group":"people","keywords":["anger symbol","angry","comic","mad"]},{"id":":bomb:","symbol":"💣","group":"people","keywords":["bomb","comic"]},{"id":":collision:","symbol":"💥","group":"people","keywords":["boom","collision","comic"]},{"id":":sweat_droplets:","symbol":"💦","group":"people","keywords":["comic","splashing","sweat","sweat droplets"]},{"id":":dashing_away:","symbol":"💨","group":"people","keywords":["comic","dash","dashing away","running"]},{"id":":dizzy:","symbol":"💫","group":"people","keywords":["comic","dizzy","star"]},{"id":":speech_balloon:","symbol":"💬","group":"people","keywords":["balloon","bubble","comic","dialog","speech"]},{"id":":left_speech_bubble:","symbol":"🗨","group":"people","keywords":["dialog","left speech bubble","speech"]},{"id":":right_anger_bubble:","symbol":"🗯","group":"people","keywords":["angry","balloon","bubble","mad","right anger bubble"]},{"id":":thought_balloon:","symbol":"💭","group":"people","keywords":["balloon","bubble","comic","thought"]},{"id":":hole:","symbol":"🕳","group":"people","keywords":["hole"]},{"id":":glasses:","symbol":"👓","group":"people","keywords":["clothing","eye","eyeglasses","eyewear","glasses"]},{"id":":sunglasses:","symbol":"🕶","group":"people","keywords":["dark","eye","eyewear","glasses","sunglasses"]},{"id":":necktie:","symbol":"👔","group":"people","keywords":["clothing","necktie","tie"]},{"id":":t-shirt:","symbol":"👕","group":"people","keywords":["clothing","shirt","t-shirt","tshirt"]},{"id":":jeans:","symbol":"👖","group":"people","keywords":["clothing","jeans","pants","trousers"]},{"id":":scarf:","symbol":"🧣","group":"people","keywords":["neck","scarf"]},{"id":":gloves:","symbol":"🧤","group":"people","keywords":["gloves","hand"]},{"id":":coat:","symbol":"🧥","group":"people","keywords":["coat","jacket"]},{"id":":socks:","symbol":"🧦","group":"people","keywords":["socks","stocking"]},{"id":":dress:","symbol":"👗","group":"people","keywords":["clothing","dress"]},{"id":":kimono:","symbol":"👘","group":"people","keywords":["clothing","kimono"]},{"id":":bikini:","symbol":"👙","group":"people","keywords":["bikini","clothing","swim"]},{"id":":woman’s_clothes:","symbol":"👚","group":"people","keywords":["clothing","woman","woman’s clothes"]},{"id":":purse:","symbol":"👛","group":"people","keywords":["clothing","coin","purse"]},{"id":":handbag:","symbol":"👜","group":"people","keywords":["bag","clothing","handbag","purse"]},{"id":":clutch_bag:","symbol":"👝","group":"people","keywords":["bag","clothing","clutch bag","pouch"]},{"id":":shopping_bags:","symbol":"🛍","group":"people","keywords":["bag","hotel","shopping","shopping bags"]},{"id":":backpack:","symbol":"🎒","group":"people","keywords":["backpack","bag","rucksack","satchel","school"]},{"id":":man’s_shoe:","symbol":"👞","group":"people","keywords":["clothing","man","man’s shoe","shoe"]},{"id":":running_shoe:","symbol":"👟","group":"people","keywords":["athletic","clothing","running shoe","shoe","sneaker"]},{"id":":high-heeled_shoe:","symbol":"👠","group":"people","keywords":["clothing","heel","high-heeled shoe","shoe","woman"]},{"id":":woman’s_sandal:","symbol":"👡","group":"people","keywords":["clothing","sandal","shoe","woman","woman’s sandal"]},{"id":":woman’s_boot:","symbol":"👢","group":"people","keywords":["boot","clothing","shoe","woman","woman’s boot"]},{"id":":crown:","symbol":"👑","group":"people","keywords":["clothing","crown","king","queen"]},{"id":":woman’s_hat:","symbol":"👒","group":"people","keywords":["clothing","hat","woman","woman’s hat"]},{"id":":top_hat:","symbol":"🎩","group":"people","keywords":["clothing","hat","top","tophat"]},{"id":":graduation_cap:","symbol":"🎓","group":"people","keywords":["cap","celebration","clothing","graduation","hat"]},{"id":":billed_cap:","symbol":"🧢","group":"people","keywords":["baseball cap","billed cap"]},{"id":":rescue_worker’s_helmet:","symbol":"⛑","group":"people","keywords":["aid","cross","face","hat","helmet","rescue worker’s helmet"]},{"id":":prayer_beads:","symbol":"📿","group":"people","keywords":["beads","clothing","necklace","prayer","religion"]},{"id":":lipstick:","symbol":"💄","group":"people","keywords":["cosmetics","lipstick","makeup"]},{"id":":ring:","symbol":"💍","group":"people","keywords":["diamond","ring"]},{"id":":gem_stone:","symbol":"💎","group":"people","keywords":["diamond","gem","gem stone","jewel"]},{"id":":monkey_face:","symbol":"🐵","group":"nature","keywords":["face","monkey"]},{"id":":monkey:","symbol":"🐒","group":"nature","keywords":["monkey"]},{"id":":gorilla:","symbol":"🦍","group":"nature","keywords":["gorilla"]},{"id":":dog_face:","symbol":"🐶","group":"nature","keywords":["dog","face","pet"]},{"id":":dog:","symbol":"🐕","group":"nature","keywords":["dog","pet"]},{"id":":poodle:","symbol":"🐩","group":"nature","keywords":["dog","poodle"]},{"id":":wolf_face:","symbol":"🐺","group":"nature","keywords":["face","wolf"]},{"id":":fox_face:","symbol":"🦊","group":"nature","keywords":["face","fox"]},{"id":":cat_face:","symbol":"🐱","group":"nature","keywords":["cat","face","pet"]},{"id":":cat:","symbol":"🐈","group":"nature","keywords":["cat","pet"]},{"id":":lion_face:","symbol":"🦁","group":"nature","keywords":["face","Leo","lion","zodiac"]},{"id":":tiger_face:","symbol":"🐯","group":"nature","keywords":["face","tiger"]},{"id":":tiger:","symbol":"🐅","group":"nature","keywords":["tiger"]},{"id":":leopard:","symbol":"🐆","group":"nature","keywords":["leopard"]},{"id":":horse_face:","symbol":"🐴","group":"nature","keywords":["face","horse"]},{"id":":horse:","symbol":"🐎","group":"nature","keywords":["equestrian","horse","racehorse","racing"]},{"id":":unicorn_face:","symbol":"🦄","group":"nature","keywords":["face","unicorn"]},{"id":":zebra:","symbol":"🦓","group":"nature","keywords":["stripe","zebra"]},{"id":":deer:","symbol":"🦌","group":"nature","keywords":["deer"]},{"id":":cow_face:","symbol":"🐮","group":"nature","keywords":["cow","face"]},{"id":":ox:","symbol":"🐂","group":"nature","keywords":["bull","ox","Taurus","zodiac"]},{"id":":water_buffalo:","symbol":"🐃","group":"nature","keywords":["buffalo","water"]},{"id":":cow:","symbol":"🐄","group":"nature","keywords":["cow"]},{"id":":pig_face:","symbol":"🐷","group":"nature","keywords":["face","pig"]},{"id":":pig:","symbol":"🐖","group":"nature","keywords":["pig","sow"]},{"id":":boar:","symbol":"🐗","group":"nature","keywords":["boar","pig"]},{"id":":pig_nose:","symbol":"🐽","group":"nature","keywords":["face","nose","pig"]},{"id":":ram:","symbol":"🐏","group":"nature","keywords":["Aries","male","ram","sheep","zodiac"]},{"id":":ewe:","symbol":"🐑","group":"nature","keywords":["ewe","female","sheep"]},{"id":":goat:","symbol":"🐐","group":"nature","keywords":["Capricorn","goat","zodiac"]},{"id":":camel:","symbol":"🐪","group":"nature","keywords":["camel","dromedary","hump"]},{"id":":two-hump_camel:","symbol":"🐫","group":"nature","keywords":["bactrian","camel","hump","two-hump camel"]},{"id":":giraffe:","symbol":"🦒","group":"nature","keywords":["giraffe","spots"]},{"id":":elephant:","symbol":"🐘","group":"nature","keywords":["elephant"]},{"id":":rhinoceros:","symbol":"🦏","group":"nature","keywords":["rhinoceros"]},{"id":":mouse_face:","symbol":"🐭","group":"nature","keywords":["face","mouse"]},{"id":":mouse:","symbol":"🐁","group":"nature","keywords":["mouse"]},{"id":":rat:","symbol":"🐀","group":"nature","keywords":["rat"]},{"id":":hamster_face:","symbol":"🐹","group":"nature","keywords":["face","hamster","pet"]},{"id":":rabbit_face:","symbol":"🐰","group":"nature","keywords":["bunny","face","pet","rabbit"]},{"id":":rabbit:","symbol":"🐇","group":"nature","keywords":["bunny","pet","rabbit"]},{"id":":chipmunk:","symbol":"🐿","group":"nature","keywords":["chipmunk","squirrel"]},{"id":":hedgehog:","symbol":"🦔","group":"nature","keywords":["hedgehog","spiny"]},{"id":":bat:","symbol":"🦇","group":"nature","keywords":["bat","vampire"]},{"id":":bear_face:","symbol":"🐻","group":"nature","keywords":["bear","face"]},{"id":":koala:","symbol":"🐨","group":"nature","keywords":["bear","koala"]},{"id":":panda_face:","symbol":"🐼","group":"nature","keywords":["face","panda"]},{"id":":paw_prints:","symbol":"🐾","group":"nature","keywords":["feet","paw","paw prints","print"]},{"id":":turkey:","symbol":"🦃","group":"nature","keywords":["bird","turkey"]},{"id":":chicken:","symbol":"🐔","group":"nature","keywords":["bird","chicken"]},{"id":":rooster:","symbol":"🐓","group":"nature","keywords":["bird","rooster"]},{"id":":hatching_chick:","symbol":"🐣","group":"nature","keywords":["baby","bird","chick","hatching"]},{"id":":baby_chick:","symbol":"🐤","group":"nature","keywords":["baby","bird","chick"]},{"id":":front-facing_baby_chick:","symbol":"🐥","group":"nature","keywords":["baby","bird","chick","front-facing baby chick"]},{"id":":bird:","symbol":"🐦","group":"nature","keywords":["bird"]},{"id":":penguin:","symbol":"🐧","group":"nature","keywords":["bird","penguin"]},{"id":":dove:","symbol":"🕊","group":"nature","keywords":["bird","dove","fly","peace"]},{"id":":eagle:","symbol":"🦅","group":"nature","keywords":["bird","eagle"]},{"id":":duck:","symbol":"🦆","group":"nature","keywords":["bird","duck"]},{"id":":owl:","symbol":"🦉","group":"nature","keywords":["bird","owl","wise"]},{"id":":frog_face:","symbol":"🐸","group":"nature","keywords":["face","frog"]},{"id":":crocodile:","symbol":"🐊","group":"nature","keywords":["crocodile"]},{"id":":turtle:","symbol":"🐢","group":"nature","keywords":["terrapin","tortoise","turtle"]},{"id":":lizard:","symbol":"🦎","group":"nature","keywords":["lizard","reptile"]},{"id":":snake:","symbol":"🐍","group":"nature","keywords":["bearer","Ophiuchus","serpent","snake","zodiac"]},{"id":":dragon_face:","symbol":"🐲","group":"nature","keywords":["dragon","face","fairy tale"]},{"id":":dragon:","symbol":"🐉","group":"nature","keywords":["dragon","fairy tale"]},{"id":":sauropod:","symbol":"🦕","group":"nature","keywords":["brachiosaurus","brontosaurus","diplodocus","sauropod"]},{"id":":t-rex:","symbol":"🦖","group":"nature","keywords":["T-Rex","Tyrannosaurus Rex"]},{"id":":spouting_whale:","symbol":"🐳","group":"nature","keywords":["face","spouting","whale"]},{"id":":whale:","symbol":"🐋","group":"nature","keywords":["whale"]},{"id":":dolphin:","symbol":"🐬","group":"nature","keywords":["dolphin","flipper"]},{"id":":fish:","symbol":"🐟","group":"nature","keywords":["fish","Pisces","zodiac"]},{"id":":tropical_fish:","symbol":"🐠","group":"nature","keywords":["fish","tropical"]},{"id":":blowfish:","symbol":"🐡","group":"nature","keywords":["blowfish","fish"]},{"id":":shark:","symbol":"🦈","group":"nature","keywords":["fish","shark"]},{"id":":octopus:","symbol":"🐙","group":"nature","keywords":["octopus"]},{"id":":spiral_shell:","symbol":"🐚","group":"nature","keywords":["shell","spiral"]},{"id":":crab:","symbol":"🦀","group":"nature","keywords":["Cancer","crab","zodiac"]},{"id":":shrimp:","symbol":"🦐","group":"nature","keywords":["food","shellfish","shrimp","small"]},{"id":":squid:","symbol":"🦑","group":"nature","keywords":["food","molusc","squid"]},{"id":":snail:","symbol":"🐌","group":"nature","keywords":["snail"]},{"id":":butterfly:","symbol":"🦋","group":"nature","keywords":["butterfly","insect","pretty"]},{"id":":bug:","symbol":"🐛","group":"nature","keywords":["bug","insect"]},{"id":":ant:","symbol":"🐜","group":"nature","keywords":["ant","insect"]},{"id":":honeybee:","symbol":"🐝","group":"nature","keywords":["bee","honeybee","insect"]},{"id":":lady_beetle:","symbol":"🐞","group":"nature","keywords":["beetle","insect","lady beetle","ladybird","ladybug"]},{"id":":cricket:","symbol":"🦗","group":"nature","keywords":["cricket","grasshopper",""]},{"id":":spider:","symbol":"🕷","group":"nature","keywords":["insect","spider"]},{"id":":spider_web:","symbol":"🕸","group":"nature","keywords":["spider","web"]},{"id":":scorpion:","symbol":"🦂","group":"nature","keywords":["scorpio","Scorpio","scorpion","zodiac"]},{"id":":bouquet:","symbol":"💐","group":"nature","keywords":["bouquet","flower"]},{"id":":cherry_blossom:","symbol":"🌸","group":"nature","keywords":["blossom","cherry","flower"]},{"id":":white_flower:","symbol":"💮","group":"nature","keywords":["flower","white flower"]},{"id":":rosette:","symbol":"🏵","group":"nature","keywords":["plant","rosette"]},{"id":":rose:","symbol":"🌹","group":"nature","keywords":["flower","rose"]},{"id":":wilted_flower:","symbol":"🥀","group":"nature","keywords":["flower","wilted"]},{"id":":hibiscus:","symbol":"🌺","group":"nature","keywords":["flower","hibiscus"]},{"id":":sunflower:","symbol":"🌻","group":"nature","keywords":["flower","sun","sunflower"]},{"id":":blossom:","symbol":"🌼","group":"nature","keywords":["blossom","flower"]},{"id":":tulip:","symbol":"🌷","group":"nature","keywords":["flower","tulip"]},{"id":":seedling:","symbol":"🌱","group":"nature","keywords":["seedling","young"]},{"id":":evergreen_tree:","symbol":"🌲","group":"nature","keywords":["evergreen tree","tree"]},{"id":":deciduous_tree:","symbol":"🌳","group":"nature","keywords":["deciduous","shedding","tree"]},{"id":":palm_tree:","symbol":"🌴","group":"nature","keywords":["palm","tree"]},{"id":":cactus:","symbol":"🌵","group":"nature","keywords":["cactus","plant"]},{"id":":sheaf_of_rice:","symbol":"🌾","group":"nature","keywords":["ear","grain","rice","sheaf of rice"]},{"id":":herb:","symbol":"🌿","group":"nature","keywords":["herb","leaf"]},{"id":":shamrock:","symbol":"☘","group":"nature","keywords":["plant","shamrock"]},{"id":":four_leaf_clover:","symbol":"🍀","group":"nature","keywords":["4","clover","four","four-leaf clover","leaf"]},{"id":":maple_leaf:","symbol":"🍁","group":"nature","keywords":["falling","leaf","maple"]},{"id":":fallen_leaf:","symbol":"🍂","group":"nature","keywords":["fallen leaf","falling","leaf"]},{"id":":leaf_fluttering_in_wind:","symbol":"🍃","group":"nature","keywords":["blow","flutter","leaf","leaf fluttering in wind","wind"]},{"id":":grapes:","symbol":"🍇","group":"food","keywords":["fruit","grape","grapes"]},{"id":":melon:","symbol":"🍈","group":"food","keywords":["fruit","melon"]},{"id":":watermelon:","symbol":"🍉","group":"food","keywords":["fruit","watermelon"]},{"id":":tangerine:","symbol":"🍊","group":"food","keywords":["fruit","orange","tangerine"]},{"id":":lemon:","symbol":"🍋","group":"food","keywords":["citrus","fruit","lemon"]},{"id":":banana:","symbol":"🍌","group":"food","keywords":["banana","fruit"]},{"id":":pineapple:","symbol":"🍍","group":"food","keywords":["fruit","pineapple"]},{"id":":red_apple:","symbol":"🍎","group":"food","keywords":["apple","fruit","red"]},{"id":":green_apple:","symbol":"🍏","group":"food","keywords":["apple","fruit","green"]},{"id":":pear:","symbol":"🍐","group":"food","keywords":["fruit","pear"]},{"id":":peach:","symbol":"🍑","group":"food","keywords":["fruit","peach"]},{"id":":cherries:","symbol":"🍒","group":"food","keywords":["berries","cherries","cherry","fruit","red"]},{"id":":strawberry:","symbol":"🍓","group":"food","keywords":["berry","fruit","strawberry"]},{"id":":kiwi_fruit:","symbol":"🥝","group":"food","keywords":["food","fruit","kiwi"]},{"id":":tomato:","symbol":"🍅","group":"food","keywords":["fruit","tomato","vegetable"]},{"id":":coconut:","symbol":"🥥","group":"food","keywords":["coconut","palm","piña colada"]},{"id":":avocado:","symbol":"🥑","group":"food","keywords":["avocado","food","fruit"]},{"id":":eggplant:","symbol":"🍆","group":"food","keywords":["aubergine","eggplant","vegetable"]},{"id":":potato:","symbol":"🥔","group":"food","keywords":["food","potato","vegetable"]},{"id":":carrot:","symbol":"🥕","group":"food","keywords":["carrot","food","vegetable"]},{"id":":ear_of_corn:","symbol":"🌽","group":"food","keywords":["corn","ear","ear of corn","maize","maze"]},{"id":":hot_pepper:","symbol":"🌶","group":"food","keywords":["hot","pepper"]},{"id":":cucumber:","symbol":"🥒","group":"food","keywords":["cucumber","food","pickle","vegetable"]},{"id":":broccoli:","symbol":"🥦","group":"food","keywords":["broccoli","wild cabbage"]},{"id":":mushroom:","symbol":"🍄","group":"food","keywords":["mushroom","toadstool"]},{"id":":peanuts:","symbol":"🥜","group":"food","keywords":["food","nut","peanut","peanuts","vegetable"]},{"id":":chestnut:","symbol":"🌰","group":"food","keywords":["chestnut","plant"]},{"id":":bread:","symbol":"🍞","group":"food","keywords":["bread","loaf"]},{"id":":croissant:","symbol":"🥐","group":"food","keywords":["bread","crescent roll","croissant","food","french"]},{"id":":baguette_bread:","symbol":"🥖","group":"food","keywords":["baguette","bread","food","french"]},{"id":":pretzel:","symbol":"🥨","group":"food","keywords":["pretzel","twisted",""]},{"id":":pancakes:","symbol":"🥞","group":"food","keywords":["crêpe","food","hotcake","pancake","pancakes"]},{"id":":cheese_wedge:","symbol":"🧀","group":"food","keywords":["cheese","cheese wedge"]},{"id":":meat_on_bone:","symbol":"🍖","group":"food","keywords":["bone","meat","meat on bone"]},{"id":":poultry_leg:","symbol":"🍗","group":"food","keywords":["bone","chicken","drumstick","leg","poultry"]},{"id":":cut_of_meat:","symbol":"🥩","group":"food","keywords":["chop","cut of meat","lambchop","porkchop","steak"]},{"id":":bacon:","symbol":"🥓","group":"food","keywords":["bacon","food","meat"]},{"id":":hamburger:","symbol":"🍔","group":"food","keywords":["burger","hamburger"]},{"id":":french_fries:","symbol":"🍟","group":"food","keywords":["french","fries"]},{"id":":pizza:","symbol":"🍕","group":"food","keywords":["cheese","pizza","slice"]},{"id":":hot_dog:","symbol":"🌭","group":"food","keywords":["frankfurter","hot dog","hotdog","sausage"]},{"id":":sandwich:","symbol":"🥪","group":"food","keywords":["bread","sandwich"]},{"id":":taco:","symbol":"🌮","group":"food","keywords":["mexican","taco"]},{"id":":burrito:","symbol":"🌯","group":"food","keywords":["burrito","mexican","wrap"]},{"id":":stuffed_flatbread:","symbol":"🥙","group":"food","keywords":["falafel","flatbread","food","gyro","kebab","stuffed"]},{"id":":egg:","symbol":"🥚","group":"food","keywords":["egg","food"]},{"id":":cooking:","symbol":"🍳","group":"food","keywords":["cooking","egg","frying","pan"]},{"id":":shallow_pan_of_food:","symbol":"🥘","group":"food","keywords":["casserole","food","paella","pan","shallow","shallow pan of food"]},{"id":":pot_of_food:","symbol":"🍲","group":"food","keywords":["pot","pot of food","stew"]},{"id":":bowl_with_spoon:","symbol":"🥣","group":"food","keywords":["bowl with spoon","breakfast","cereal","congee",""]},{"id":":green_salad:","symbol":"🥗","group":"food","keywords":["food","green","salad"]},{"id":":popcorn:","symbol":"🍿","group":"food","keywords":["popcorn"]},{"id":":canned_food:","symbol":"🥫","group":"food","keywords":["can","canned food"]},{"id":":bento_box:","symbol":"🍱","group":"food","keywords":["bento","box"]},{"id":":rice_cracker:","symbol":"🍘","group":"food","keywords":["cracker","rice"]},{"id":":rice_ball:","symbol":"🍙","group":"food","keywords":["ball","Japanese","rice"]},{"id":":cooked_rice:","symbol":"🍚","group":"food","keywords":["cooked","rice"]},{"id":":curry_rice:","symbol":"🍛","group":"food","keywords":["curry","rice"]},{"id":":steaming_bowl:","symbol":"🍜","group":"food","keywords":["bowl","noodle","ramen","steaming"]},{"id":":spaghetti:","symbol":"🍝","group":"food","keywords":["pasta","spaghetti"]},{"id":":roasted_sweet_potato:","symbol":"🍠","group":"food","keywords":["potato","roasted","sweet"]},{"id":":oden:","symbol":"🍢","group":"food","keywords":["kebab","oden","seafood","skewer","stick"]},{"id":":sushi:","symbol":"🍣","group":"food","keywords":["sushi"]},{"id":":fried_shrimp:","symbol":"🍤","group":"food","keywords":["fried","prawn","shrimp","tempura"]},{"id":":fish_cake_with_swirl:","symbol":"🍥","group":"food","keywords":["cake","fish","fish cake with swirl","pastry","swirl"]},{"id":":dango:","symbol":"🍡","group":"food","keywords":["dango","dessert","Japanese","skewer","stick","sweet"]},{"id":":dumpling:","symbol":"🥟","group":"food","keywords":["dumpling","empanada","gyōza","jiaozi","pierogi","potsticker"]},{"id":":fortune_cookie:","symbol":"🥠","group":"food","keywords":["fortune cookie","prophecy"]},{"id":":takeout_box:","symbol":"🥡","group":"food","keywords":["oyster pail","takeout box"]},{"id":":soft_ice_cream:","symbol":"🍦","group":"food","keywords":["cream","dessert","ice","icecream","soft","sweet"]},{"id":":shaved_ice:","symbol":"🍧","group":"food","keywords":["dessert","ice","shaved","sweet"]},{"id":":ice_cream:","symbol":"🍨","group":"food","keywords":["cream","dessert","ice","sweet"]},{"id":":doughnut:","symbol":"🍩","group":"food","keywords":["dessert","donut","doughnut","sweet"]},{"id":":cookie:","symbol":"🍪","group":"food","keywords":["cookie","dessert","sweet"]},{"id":":birthday_cake:","symbol":"🎂","group":"food","keywords":["birthday","cake","celebration","dessert","pastry","sweet"]},{"id":":shortcake:","symbol":"🍰","group":"food","keywords":["cake","dessert","pastry","shortcake","slice","sweet"]},{"id":":pie:","symbol":"🥧","group":"food","keywords":["filling","pastry","pie",""]},{"id":":chocolate_bar:","symbol":"🍫","group":"food","keywords":["bar","chocolate","dessert","sweet"]},{"id":":candy:","symbol":"🍬","group":"food","keywords":["candy","dessert","sweet"]},{"id":":lollipop:","symbol":"🍭","group":"food","keywords":["candy","dessert","lollipop","sweet"]},{"id":":custard:","symbol":"🍮","group":"food","keywords":["custard","dessert","pudding","sweet"]},{"id":":honey_pot:","symbol":"🍯","group":"food","keywords":["honey","honeypot","pot","sweet"]},{"id":":baby_bottle:","symbol":"🍼","group":"food","keywords":["baby","bottle","drink","milk"]},{"id":":glass_of_milk:","symbol":"🥛","group":"food","keywords":["drink","glass","glass of milk","milk"]},{"id":":hot_beverage:","symbol":"☕","group":"food","keywords":["beverage","coffee","drink","hot","steaming","tea"]},{"id":":teacup_without_handle:","symbol":"🍵","group":"food","keywords":["beverage","cup","drink","tea","teacup","teacup without handle"]},{"id":":sake:","symbol":"🍶","group":"food","keywords":["bar","beverage","bottle","cup","drink","sake"]},{"id":":bottle_with_popping_cork:","symbol":"🍾","group":"food","keywords":["bar","bottle","bottle with popping cork","cork","drink","popping"]},{"id":":wine_glass:","symbol":"🍷","group":"food","keywords":["bar","beverage","drink","glass","wine"]},{"id":":cocktail_glass:","symbol":"🍸","group":"food","keywords":["bar","cocktail","drink","glass"]},{"id":":tropical_drink:","symbol":"🍹","group":"food","keywords":["bar","drink","tropical"]},{"id":":beer_mug:","symbol":"🍺","group":"food","keywords":["bar","beer","drink","mug"]},{"id":":clinking_beer_mugs:","symbol":"🍻","group":"food","keywords":["bar","beer","clink","clinking beer mugs","drink","mug"]},{"id":":clinking_glasses:","symbol":"🥂","group":"food","keywords":["celebrate","clink","clinking glasses","drink","glass"]},{"id":":tumbler_glass:","symbol":"🥃","group":"food","keywords":["glass","liquor","shot","tumbler","whisky"]},{"id":":cup_with_straw:","symbol":"🥤","group":"food","keywords":["cup with straw","juice","soda",""]},{"id":":chopsticks:","symbol":"🥢","group":"food","keywords":["chopsticks","hashi",""]},{"id":":fork_and_knife_with_plate:","symbol":"🍽","group":"food","keywords":["cooking","fork","fork and knife with plate","knife","plate"]},{"id":":fork_and_knife:","symbol":"🍴","group":"food","keywords":["cooking","cutlery","fork","fork and knife","knife"]},{"id":":spoon:","symbol":"🥄","group":"food","keywords":["spoon","tableware"]},{"id":":kitchen_knife:","symbol":"🔪","group":"food","keywords":["cooking","hocho","kitchen knife","knife","tool","weapon"]},{"id":":amphora:","symbol":"🏺","group":"food","keywords":["amphora","Aquarius","cooking","drink","jug","zodiac"]},{"id":":globe_showing_europe-africa:","symbol":"🌍","group":"travel","keywords":["Africa","earth","Europe","globe","globe showing Europe-Africa","world"]},{"id":":globe_showing_americas:","symbol":"🌎","group":"travel","keywords":["Americas","earth","globe","globe showing Americas","world"]},{"id":":globe_showing_asia-australia:","symbol":"🌏","group":"travel","keywords":["Asia","Australia","earth","globe","globe showing Asia-Australia","world"]},{"id":":globe_with_meridians:","symbol":"🌐","group":"travel","keywords":["earth","globe","globe with meridians","meridians","world"]},{"id":":world_map:","symbol":"🗺","group":"travel","keywords":["map","world"]},{"id":":map_of_japan:","symbol":"🗾","group":"travel","keywords":["Japan","map","map of Japan"]},{"id":":snow-capped_mountain:","symbol":"🏔","group":"travel","keywords":["cold","mountain","snow","snow-capped mountain"]},{"id":":mountain:","symbol":"⛰","group":"travel","keywords":["mountain"]},{"id":":volcano:","symbol":"🌋","group":"travel","keywords":["eruption","mountain","volcano"]},{"id":":mount_fuji:","symbol":"🗻","group":"travel","keywords":["fuji","mount fuji","mountain"]},{"id":":camping:","symbol":"🏕","group":"travel","keywords":["camping"]},{"id":":beach_with_umbrella:","symbol":"🏖","group":"travel","keywords":["beach","beach with umbrella","umbrella"]},{"id":":desert:","symbol":"🏜","group":"travel","keywords":["desert"]},{"id":":desert_island:","symbol":"🏝","group":"travel","keywords":["desert","island"]},{"id":":national_park:","symbol":"🏞","group":"travel","keywords":["national park","park"]},{"id":":stadium:","symbol":"🏟","group":"travel","keywords":["stadium"]},{"id":":classical_building:","symbol":"🏛","group":"travel","keywords":["classical","classical building"]},{"id":":building_construction:","symbol":"🏗","group":"travel","keywords":["building construction","construction"]},{"id":":houses:","symbol":"🏘","group":"travel","keywords":["houses"]},{"id":":derelict_house:","symbol":"🏚","group":"travel","keywords":["derelict","house"]},{"id":":house:","symbol":"🏠","group":"travel","keywords":["home","house"]},{"id":":house_with_garden:","symbol":"🏡","group":"travel","keywords":["garden","home","house","house with garden"]},{"id":":office_building:","symbol":"🏢","group":"travel","keywords":["building","office building"]},{"id":":japanese_post_office:","symbol":"🏣","group":"travel","keywords":["Japanese","Japanese post office","post"]},{"id":":post_office:","symbol":"🏤","group":"travel","keywords":["European","post","post office"]},{"id":":hospital:","symbol":"🏥","group":"travel","keywords":["doctor","hospital","medicine"]},{"id":":bank:","symbol":"🏦","group":"travel","keywords":["bank","building"]},{"id":":hotel:","symbol":"🏨","group":"travel","keywords":["building","hotel"]},{"id":":love_hotel:","symbol":"🏩","group":"travel","keywords":["hotel","love"]},{"id":":convenience_store:","symbol":"🏪","group":"travel","keywords":["convenience","store"]},{"id":":school:","symbol":"🏫","group":"travel","keywords":["building","school"]},{"id":":department_store:","symbol":"🏬","group":"travel","keywords":["department","store"]},{"id":":factory:","symbol":"🏭","group":"travel","keywords":["building","factory"]},{"id":":japanese_castle:","symbol":"🏯","group":"travel","keywords":["castle","Japanese"]},{"id":":castle:","symbol":"🏰","group":"travel","keywords":["castle","European"]},{"id":":wedding:","symbol":"💒","group":"travel","keywords":["chapel","romance","wedding"]},{"id":":tokyo_tower:","symbol":"🗼","group":"travel","keywords":["Tokyo","tower"]},{"id":":statue_of_liberty:","symbol":"🗽","group":"travel","keywords":["liberty","statue","Statue of Liberty"]},{"id":":church:","symbol":"⛪","group":"travel","keywords":["Christian","church","cross","religion"]},{"id":":mosque:","symbol":"🕌","group":"travel","keywords":["islam","mosque","Muslim","religion"]},{"id":":synagogue:","symbol":"🕍","group":"travel","keywords":["Jew","Jewish","religion","synagogue","temple"]},{"id":":shinto_shrine:","symbol":"⛩","group":"travel","keywords":["religion","shinto","shrine"]},{"id":":kaaba:","symbol":"🕋","group":"travel","keywords":["islam","kaaba","Muslim","religion"]},{"id":":fountain:","symbol":"⛲","group":"travel","keywords":["fountain"]},{"id":":tent:","symbol":"⛺","group":"travel","keywords":["camping","tent"]},{"id":":foggy:","symbol":"🌁","group":"travel","keywords":["fog","foggy"]},{"id":":night_with_stars:","symbol":"🌃","group":"travel","keywords":["night","night with stars","star"]},{"id":":cityscape:","symbol":"🏙","group":"travel","keywords":["city","cityscape"]},{"id":":sunrise_over_mountains:","symbol":"🌄","group":"travel","keywords":["morning","mountain","sun","sunrise","sunrise over mountains"]},{"id":":sunrise:","symbol":"🌅","group":"travel","keywords":["morning","sun","sunrise"]},{"id":":cityscape_at_dusk:","symbol":"🌆","group":"travel","keywords":["city","cityscape at dusk","dusk","evening","landscape","sunset"]},{"id":":sunset:","symbol":"🌇","group":"travel","keywords":["dusk","sun","sunset"]},{"id":":bridge_at_night:","symbol":"🌉","group":"travel","keywords":["bridge","bridge at night","night"]},{"id":":hot_springs:","symbol":"♨","group":"travel","keywords":["hot","hotsprings","springs","steaming"]},{"id":":milky_way:","symbol":"🌌","group":"travel","keywords":["milky way","space"]},{"id":":carousel_horse:","symbol":"🎠","group":"travel","keywords":["carousel","horse"]},{"id":":ferris_wheel:","symbol":"🎡","group":"travel","keywords":["amusement park","ferris","wheel"]},{"id":":roller_coaster:","symbol":"🎢","group":"travel","keywords":["amusement park","coaster","roller"]},{"id":":barber_pole:","symbol":"💈","group":"travel","keywords":["barber","haircut","pole"]},{"id":":circus_tent:","symbol":"🎪","group":"travel","keywords":["circus","tent"]},{"id":":locomotive:","symbol":"🚂","group":"travel","keywords":["engine","locomotive","railway","steam","train"]},{"id":":railway_car:","symbol":"🚃","group":"travel","keywords":["car","electric","railway","train","tram","trolleybus"]},{"id":":high-speed_train:","symbol":"🚄","group":"travel","keywords":["high-speed train","railway","shinkansen","speed","train"]},{"id":":bullet_train:","symbol":"🚅","group":"travel","keywords":["bullet","railway","shinkansen","speed","train"]},{"id":":train:","symbol":"🚆","group":"travel","keywords":["railway","train"]},{"id":":metro:","symbol":"🚇","group":"travel","keywords":["metro","subway"]},{"id":":light_rail:","symbol":"🚈","group":"travel","keywords":["light rail","railway"]},{"id":":station:","symbol":"🚉","group":"travel","keywords":["railway","station","train"]},{"id":":tram:","symbol":"🚊","group":"travel","keywords":["tram","trolleybus"]},{"id":":monorail:","symbol":"🚝","group":"travel","keywords":["monorail","vehicle"]},{"id":":mountain_railway:","symbol":"🚞","group":"travel","keywords":["car","mountain","railway"]},{"id":":tram_car:","symbol":"🚋","group":"travel","keywords":["car","tram","trolleybus"]},{"id":":bus:","symbol":"🚌","group":"travel","keywords":["bus","vehicle"]},{"id":":oncoming_bus:","symbol":"🚍","group":"travel","keywords":["bus","oncoming"]},{"id":":trolleybus:","symbol":"🚎","group":"travel","keywords":["bus","tram","trolley","trolleybus"]},{"id":":minibus:","symbol":"🚐","group":"travel","keywords":["bus","minibus"]},{"id":":ambulance:","symbol":"🚑","group":"travel","keywords":["ambulance","vehicle"]},{"id":":fire_engine:","symbol":"🚒","group":"travel","keywords":["engine","fire","truck"]},{"id":":police_car:","symbol":"🚓","group":"travel","keywords":["car","patrol","police"]},{"id":":oncoming_police_car:","symbol":"🚔","group":"travel","keywords":["car","oncoming","police"]},{"id":":taxi:","symbol":"🚕","group":"travel","keywords":["taxi","vehicle"]},{"id":":oncoming_taxi:","symbol":"🚖","group":"travel","keywords":["oncoming","taxi"]},{"id":":automobile:","symbol":"🚗","group":"travel","keywords":["automobile","car"]},{"id":":oncoming_automobile:","symbol":"🚘","group":"travel","keywords":["automobile","car","oncoming"]},{"id":":sport_utility_vehicle:","symbol":"🚙","group":"travel","keywords":["recreational","sport utility","sport utility vehicle"]},{"id":":delivery_truck:","symbol":"🚚","group":"travel","keywords":["delivery","truck"]},{"id":":articulated_lorry:","symbol":"🚛","group":"travel","keywords":["articulated lorry","lorry","semi","truck"]},{"id":":tractor:","symbol":"🚜","group":"travel","keywords":["tractor","vehicle"]},{"id":":bicycle:","symbol":"🚲","group":"travel","keywords":["bicycle","bike"]},{"id":":kick_scooter:","symbol":"🛴","group":"travel","keywords":["kick","scooter"]},{"id":":motor_scooter:","symbol":"🛵","group":"travel","keywords":["motor","scooter"]},{"id":":bus_stop:","symbol":"🚏","group":"travel","keywords":["bus","busstop","stop"]},{"id":":motorway:","symbol":"🛣","group":"travel","keywords":["highway","motorway","road"]},{"id":":railway_track:","symbol":"🛤","group":"travel","keywords":["railway","railway track","train"]},{"id":":oil_drum:","symbol":"🛢","group":"travel","keywords":["drum","oil"]},{"id":":fuel_pump:","symbol":"⛽","group":"travel","keywords":["diesel","fuel","fuelpump","gas","pump","station"]},{"id":":police_car_light:","symbol":"🚨","group":"travel","keywords":["beacon","car","light","police","revolving"]},{"id":":horizontal_traffic_light:","symbol":"🚥","group":"travel","keywords":["horizontal traffic light","light","signal","traffic"]},{"id":":vertical_traffic_light:","symbol":"🚦","group":"travel","keywords":["light","signal","traffic","vertical traffic light"]},{"id":":stop_sign:","symbol":"🛑","group":"travel","keywords":["octagonal","sign","stop"]},{"id":":construction:","symbol":"🚧","group":"travel","keywords":["barrier","construction"]},{"id":":anchor:","symbol":"⚓","group":"travel","keywords":["anchor","ship","tool"]},{"id":":sailboat:","symbol":"⛵","group":"travel","keywords":["boat","resort","sailboat","sea","yacht"]},{"id":":canoe:","symbol":"🛶","group":"travel","keywords":["boat","canoe"]},{"id":":speedboat:","symbol":"🚤","group":"travel","keywords":["boat","speedboat"]},{"id":":passenger_ship:","symbol":"🛳","group":"travel","keywords":["passenger","ship"]},{"id":":ferry:","symbol":"⛴","group":"travel","keywords":["boat","ferry","passenger"]},{"id":":motor_boat:","symbol":"🛥","group":"travel","keywords":["boat","motor boat","motorboat"]},{"id":":ship:","symbol":"🚢","group":"travel","keywords":["boat","passenger","ship"]},{"id":":airplane:","symbol":"✈","group":"travel","keywords":["aeroplane","airplane"]},{"id":":small_airplane:","symbol":"🛩","group":"travel","keywords":["aeroplane","airplane","small airplane"]},{"id":":airplane_departure:","symbol":"🛫","group":"travel","keywords":["aeroplane","airplane","check-in","departure","departures"]},{"id":":airplane_arrival:","symbol":"🛬","group":"travel","keywords":["aeroplane","airplane","airplane arrival","arrivals","arriving","landing"]},{"id":":seat:","symbol":"💺","group":"travel","keywords":["chair","seat"]},{"id":":helicopter:","symbol":"🚁","group":"travel","keywords":["helicopter","vehicle"]},{"id":":suspension_railway:","symbol":"🚟","group":"travel","keywords":["railway","suspension"]},{"id":":mountain_cableway:","symbol":"🚠","group":"travel","keywords":["cable","gondola","mountain","mountain cableway"]},{"id":":aerial_tramway:","symbol":"🚡","group":"travel","keywords":["aerial","cable","car","gondola","tramway"]},{"id":":satellite:","symbol":"🛰","group":"travel","keywords":["satellite","space"]},{"id":":rocket:","symbol":"🚀","group":"travel","keywords":["rocket","space"]},{"id":":flying_saucer:","symbol":"🛸","group":"travel","keywords":["flying saucer","UFO"]},{"id":":bellhop_bell:","symbol":"🛎","group":"travel","keywords":["bell","bellhop","hotel"]},{"id":":hourglass_done:","symbol":"⌛","group":"travel","keywords":["hourglass done","sand","timer"]},{"id":":hourglass_not_done:","symbol":"⏳","group":"travel","keywords":["hourglass","hourglass not done","sand","timer"]},{"id":":watch:","symbol":"⌚","group":"travel","keywords":["clock","watch"]},{"id":":alarm_clock:","symbol":"⏰","group":"travel","keywords":["alarm","clock"]},{"id":":stopwatch:","symbol":"⏱","group":"travel","keywords":["clock","stopwatch"]},{"id":":timer_clock:","symbol":"⏲","group":"travel","keywords":["clock","timer"]},{"id":":mantelpiece_clock:","symbol":"🕰","group":"travel","keywords":["clock","mantelpiece clock"]},{"id":":twelve_o’clock:","symbol":"🕛","group":"travel","keywords":["00","12","12:00","clock","o’clock","twelve"]},{"id":":twelve-thirty:","symbol":"🕧","group":"travel","keywords":["12","12:30","clock","thirty","twelve","twelve-thirty"]},{"id":":one_o’clock:","symbol":"🕐","group":"travel","keywords":["00","1","1:00","clock","o’clock","one"]},{"id":":one-thirty:","symbol":"🕜","group":"travel","keywords":["1","1:30","clock","one","one-thirty","thirty"]},{"id":":two_o’clock:","symbol":"🕑","group":"travel","keywords":["00","2","2:00","clock","o’clock","two"]},{"id":":two-thirty:","symbol":"🕝","group":"travel","keywords":["2","2:30","clock","thirty","two","two-thirty"]},{"id":":three_o’clock:","symbol":"🕒","group":"travel","keywords":["00","3","3:00","clock","o’clock","three"]},{"id":":three-thirty:","symbol":"🕞","group":"travel","keywords":["3","3:30","clock","thirty","three","three-thirty"]},{"id":":four_o’clock:","symbol":"🕓","group":"travel","keywords":["00","4","4:00","clock","four","o’clock"]},{"id":":four-thirty:","symbol":"🕟","group":"travel","keywords":["4","4:30","clock","four","four-thirty","thirty"]},{"id":":five_o’clock:","symbol":"🕔","group":"travel","keywords":["00","5","5:00","clock","five","o’clock"]},{"id":":five-thirty:","symbol":"🕠","group":"travel","keywords":["5","5:30","clock","five","five-thirty","thirty"]},{"id":":six_o’clock:","symbol":"🕕","group":"travel","keywords":["00","6","6:00","clock","o’clock","six"]},{"id":":six-thirty:","symbol":"🕡","group":"travel","keywords":["6","6:30","clock","six","six-thirty","thirty"]},{"id":":seven_o’clock:","symbol":"🕖","group":"travel","keywords":["00","7","7:00","clock","o’clock","seven"]},{"id":":seven-thirty:","symbol":"🕢","group":"travel","keywords":["7","7:30","clock","seven","seven-thirty","thirty"]},{"id":":eight_o’clock:","symbol":"🕗","group":"travel","keywords":["00","8","8:00","clock","eight","o’clock"]},{"id":":eight-thirty:","symbol":"🕣","group":"travel","keywords":["8","8:30","clock","eight","eight-thirty","thirty"]},{"id":":nine_o’clock:","symbol":"🕘","group":"travel","keywords":["00","9","9:00","clock","nine","o’clock"]},{"id":":nine-thirty:","symbol":"🕤","group":"travel","keywords":["9","9:30","clock","nine","nine-thirty","thirty"]},{"id":":ten_o’clock:","symbol":"🕙","group":"travel","keywords":["00","10","10:00","clock","o’clock","ten"]},{"id":":ten-thirty:","symbol":"🕥","group":"travel","keywords":["10","10:30","clock","ten","ten-thirty","thirty"]},{"id":":eleven_o’clock:","symbol":"🕚","group":"travel","keywords":["00","11","11:00","clock","eleven","o’clock"]},{"id":":eleven-thirty:","symbol":"🕦","group":"travel","keywords":["11","11:30","clock","eleven","eleven-thirty","thirty"]},{"id":":new_moon:","symbol":"🌑","group":"travel","keywords":["dark","moon","new moon"]},{"id":":waxing_crescent_moon:","symbol":"🌒","group":"travel","keywords":["crescent","moon","waxing"]},{"id":":first_quarter_moon:","symbol":"🌓","group":"travel","keywords":["first quarter moon","moon","quarter"]},{"id":":waxing_gibbous_moon:","symbol":"🌔","group":"travel","keywords":["gibbous","moon","waxing"]},{"id":":full_moon:","symbol":"🌕","group":"travel","keywords":["full","moon"]},{"id":":waning_gibbous_moon:","symbol":"🌖","group":"travel","keywords":["gibbous","moon","waning"]},{"id":":last_quarter_moon:","symbol":"🌗","group":"travel","keywords":["last quarter moon","moon","quarter"]},{"id":":waning_crescent_moon:","symbol":"🌘","group":"travel","keywords":["crescent","moon","waning"]},{"id":":crescent_moon:","symbol":"🌙","group":"travel","keywords":["crescent","moon"]},{"id":":new_moon_face:","symbol":"🌚","group":"travel","keywords":["face","moon","new moon face"]},{"id":":first_quarter_moon_face:","symbol":"🌛","group":"travel","keywords":["face","first quarter moon face","moon","quarter"]},{"id":":last_quarter_moon_face:","symbol":"🌜","group":"travel","keywords":["face","last quarter moon face","moon","quarter"]},{"id":":thermometer:","symbol":"🌡","group":"travel","keywords":["thermometer","weather"]},{"id":":sun:","symbol":"☀","group":"travel","keywords":["bright","rays","sun","sunny"]},{"id":":full_moon_face:","symbol":"🌝","group":"travel","keywords":["bright","face","full","moon"]},{"id":":sun_with_face:","symbol":"🌞","group":"travel","keywords":["bright","face","sun","sun with face"]},{"id":":star:","symbol":"⭐","group":"travel","keywords":["star"]},{"id":":glowing_star:","symbol":"🌟","group":"travel","keywords":["glittery","glow","glowing star","shining","sparkle","star"]},{"id":":shooting_star:","symbol":"🌠","group":"travel","keywords":["falling","shooting","star"]},{"id":":cloud:","symbol":"☁","group":"travel","keywords":["cloud","weather"]},{"id":":sun_behind_cloud:","symbol":"⛅","group":"travel","keywords":["cloud","sun","sun behind cloud"]},{"id":":cloud_with_lightning_and_rain:","symbol":"⛈","group":"travel","keywords":["cloud","cloud with lightning and rain","rain","thunder"]},{"id":":sun_behind_small_cloud:","symbol":"🌤","group":"travel","keywords":["cloud","sun","sun behind small cloud"]},{"id":":sun_behind_large_cloud:","symbol":"🌥","group":"travel","keywords":["cloud","sun","sun behind large cloud"]},{"id":":sun_behind_rain_cloud:","symbol":"🌦","group":"travel","keywords":["cloud","rain","sun","sun behind rain cloud"]},{"id":":cloud_with_rain:","symbol":"🌧","group":"travel","keywords":["cloud","cloud with rain","rain"]},{"id":":cloud_with_snow:","symbol":"🌨","group":"travel","keywords":["cloud","cloud with snow","cold","snow"]},{"id":":cloud_with_lightning:","symbol":"🌩","group":"travel","keywords":["cloud","cloud with lightning","lightning"]},{"id":":tornado:","symbol":"🌪","group":"travel","keywords":["cloud","tornado","whirlwind"]},{"id":":fog:","symbol":"🌫","group":"travel","keywords":["cloud","fog"]},{"id":":wind_face:","symbol":"🌬","group":"travel","keywords":["blow","cloud","face","wind"]},{"id":":cyclone:","symbol":"🌀","group":"travel","keywords":["cyclone","dizzy","hurricane","twister","typhoon"]},{"id":":rainbow:","symbol":"🌈","group":"travel","keywords":["rain","rainbow"]},{"id":":closed_umbrella:","symbol":"🌂","group":"travel","keywords":["closed umbrella","clothing","rain","umbrella"]},{"id":":umbrella:","symbol":"☂","group":"travel","keywords":["clothing","rain","umbrella"]},{"id":":umbrella_with_rain_drops:","symbol":"☔","group":"travel","keywords":["clothing","drop","rain","umbrella","umbrella with rain drops"]},{"id":":umbrella_on_ground:","symbol":"⛱","group":"travel","keywords":["rain","sun","umbrella","umbrella on ground"]},{"id":":high_voltage:","symbol":"⚡","group":"travel","keywords":["danger","electric","high voltage","lightning","voltage","zap"]},{"id":":snowflake:","symbol":"❄","group":"travel","keywords":["cold","snow","snowflake"]},{"id":":snowman:","symbol":"☃","group":"travel","keywords":["cold","snow","snowman"]},{"id":":snowman_without_snow:","symbol":"⛄","group":"travel","keywords":["cold","snow","snowman","snowman without snow"]},{"id":":comet:","symbol":"☄","group":"travel","keywords":["comet","space"]},{"id":":fire:","symbol":"🔥","group":"travel","keywords":["fire","flame","tool"]},{"id":":droplet:","symbol":"💧","group":"travel","keywords":["cold","comic","drop","droplet","sweat"]},{"id":":water_wave:","symbol":"🌊","group":"travel","keywords":["ocean","water","wave"]},{"id":":jack-o-lantern:","symbol":"🎃","group":"activities","keywords":["celebration","halloween","jack","jack-o-lantern","lantern"]},{"id":":christmas_tree:","symbol":"🎄","group":"activities","keywords":["celebration","Christmas","tree"]},{"id":":fireworks:","symbol":"🎆","group":"activities","keywords":["celebration","fireworks"]},{"id":":sparkler:","symbol":"🎇","group":"activities","keywords":["celebration","fireworks","sparkle","sparkler"]},{"id":":sparkles:","symbol":"✨","group":"activities","keywords":["sparkle","sparkles","star"]},{"id":":balloon:","symbol":"🎈","group":"activities","keywords":["balloon","celebration"]},{"id":":party_popper:","symbol":"🎉","group":"activities","keywords":["celebration","party","popper","tada"]},{"id":":confetti_ball:","symbol":"🎊","group":"activities","keywords":["ball","celebration","confetti"]},{"id":":tanabata_tree:","symbol":"🎋","group":"activities","keywords":["banner","celebration","Japanese","tanabata tree","tree"]},{"id":":pine_decoration:","symbol":"🎍","group":"activities","keywords":["bamboo","celebration","Japanese","pine","pine decoration"]},{"id":":japanese_dolls:","symbol":"🎎","group":"activities","keywords":["celebration","doll","festival","Japanese","Japanese dolls"]},{"id":":carp_streamer:","symbol":"🎏","group":"activities","keywords":["carp","celebration","streamer"]},{"id":":wind_chime:","symbol":"🎐","group":"activities","keywords":["bell","celebration","chime","wind"]},{"id":":moon_viewing_ceremony:","symbol":"🎑","group":"activities","keywords":["celebration","ceremony","moon","moon viewing ceremony"]},{"id":":ribbon:","symbol":"🎀","group":"activities","keywords":["celebration","ribbon"]},{"id":":wrapped_gift:","symbol":"🎁","group":"activities","keywords":["box","celebration","gift","present","wrapped"]},{"id":":reminder_ribbon:","symbol":"🎗","group":"activities","keywords":["celebration","reminder","ribbon"]},{"id":":admission_tickets:","symbol":"🎟","group":"activities","keywords":["admission","admission tickets","ticket"]},{"id":":ticket:","symbol":"🎫","group":"activities","keywords":["admission","ticket"]},{"id":":military_medal:","symbol":"🎖","group":"activities","keywords":["celebration","medal","military"]},{"id":":trophy:","symbol":"🏆","group":"activities","keywords":["prize","trophy"]},{"id":":sports_medal:","symbol":"🏅","group":"activities","keywords":["medal","sports medal"]},{"id":":1st_place_medal:","symbol":"🥇","group":"activities","keywords":["1st place medal","first","gold","medal"]},{"id":":2nd_place_medal:","symbol":"🥈","group":"activities","keywords":["2nd place medal","medal","second","silver"]},{"id":":3rd_place_medal:","symbol":"🥉","group":"activities","keywords":["3rd place medal","bronze","medal","third"]},{"id":":soccer_ball:","symbol":"⚽","group":"activities","keywords":["ball","football","soccer"]},{"id":":baseball:","symbol":"⚾","group":"activities","keywords":["ball","baseball"]},{"id":":basketball:","symbol":"🏀","group":"activities","keywords":["ball","basketball","hoop"]},{"id":":volleyball:","symbol":"🏐","group":"activities","keywords":["ball","game","volleyball"]},{"id":":american_football:","symbol":"🏈","group":"activities","keywords":["american","ball","football"]},{"id":":rugby_football:","symbol":"🏉","group":"activities","keywords":["ball","football","rugby"]},{"id":":tennis:","symbol":"🎾","group":"activities","keywords":["ball","racquet","tennis"]},{"id":":bowling:","symbol":"🎳","group":"activities","keywords":["ball","bowling","game"]},{"id":":cricket_game:","symbol":"🏏","group":"activities","keywords":["ball","bat","cricket game","game"]},{"id":":field_hockey:","symbol":"🏑","group":"activities","keywords":["ball","field","game","hockey","stick"]},{"id":":ice_hockey:","symbol":"🏒","group":"activities","keywords":["game","hockey","ice","puck","stick"]},{"id":":ping_pong:","symbol":"🏓","group":"activities","keywords":["ball","bat","game","paddle","ping pong","table tennis"]},{"id":":badminton:","symbol":"🏸","group":"activities","keywords":["badminton","birdie","game","racquet","shuttlecock"]},{"id":":boxing_glove:","symbol":"🥊","group":"activities","keywords":["boxing","glove"]},{"id":":martial_arts_uniform:","symbol":"🥋","group":"activities","keywords":["judo","karate","martial arts","martial arts uniform","taekwondo","uniform"]},{"id":":goal_net:","symbol":"🥅","group":"activities","keywords":["goal","net"]},{"id":":flag_in_hole:","symbol":"⛳","group":"activities","keywords":["flag in hole","golf","hole"]},{"id":":ice_skate:","symbol":"⛸","group":"activities","keywords":["ice","skate"]},{"id":":fishing_pole:","symbol":"🎣","group":"activities","keywords":["fish","fishing pole","pole"]},{"id":":running_shirt:","symbol":"🎽","group":"activities","keywords":["athletics","running","sash","shirt"]},{"id":":skis:","symbol":"🎿","group":"activities","keywords":["ski","skis","snow"]},{"id":":sled:","symbol":"🛷","group":"activities","keywords":["sled","sledge","sleigh",""]},{"id":":curling_stone:","symbol":"🥌","group":"activities","keywords":["curling stone","game","rock"]},{"id":":direct_hit:","symbol":"🎯","group":"activities","keywords":["bullseye","dart","direct hit","game","hit","target"]},{"id":":pool_8_ball:","symbol":"🎱","group":"activities","keywords":["8","ball","billiard","eight","game","pool 8 ball"]},{"id":":crystal_ball:","symbol":"🔮","group":"activities","keywords":["ball","crystal","fairy tale","fantasy","fortune","tool"]},{"id":":video_game:","symbol":"🎮","group":"activities","keywords":["controller","game","video game"]},{"id":":joystick:","symbol":"🕹","group":"activities","keywords":["game","joystick","video game"]},{"id":":slot_machine:","symbol":"🎰","group":"activities","keywords":["game","slot","slot machine"]},{"id":":game_die:","symbol":"🎲","group":"activities","keywords":["dice","die","game"]},{"id":":spade_suit:","symbol":"♠","group":"activities","keywords":["card","game","spade suit"]},{"id":":heart_suit:","symbol":"♥","group":"activities","keywords":["card","game","heart suit"]},{"id":":diamond_suit:","symbol":"♦","group":"activities","keywords":["card","diamond suit","game"]},{"id":":club_suit:","symbol":"♣","group":"activities","keywords":["card","club suit","game"]},{"id":":joker:","symbol":"🃏","group":"activities","keywords":["card","game","joker","wildcard"]},{"id":":mahjong_red_dragon:","symbol":"🀄","group":"activities","keywords":["game","mahjong","mahjong red dragon","red"]},{"id":":flower_playing_cards:","symbol":"🎴","group":"activities","keywords":["card","flower","flower playing cards","game","Japanese","playing"]},{"id":":performing_arts:","symbol":"🎭","group":"activities","keywords":["art","mask","performing","performing arts","theater","theatre"]},{"id":":framed_picture:","symbol":"🖼","group":"activities","keywords":["art","frame","framed picture","museum","painting","picture"]},{"id":":artist_palette:","symbol":"🎨","group":"activities","keywords":["art","artist palette","museum","painting","palette"]},{"id":":muted_speaker:","symbol":"🔇","group":"objects","keywords":["mute","muted speaker","quiet","silent","speaker"]},{"id":":speaker_low_volume:","symbol":"🔈","group":"objects","keywords":["soft","speaker low volume"]},{"id":":speaker_medium_volume:","symbol":"🔉","group":"objects","keywords":["medium","speaker medium volume"]},{"id":":speaker_high_volume:","symbol":"🔊","group":"objects","keywords":["loud","speaker high volume"]},{"id":":loudspeaker:","symbol":"📢","group":"objects","keywords":["loud","loudspeaker","public address"]},{"id":":megaphone:","symbol":"📣","group":"objects","keywords":["cheering","megaphone"]},{"id":":postal_horn:","symbol":"📯","group":"objects","keywords":["horn","post","postal"]},{"id":":bell:","symbol":"🔔","group":"objects","keywords":["bell"]},{"id":":bell_with_slash:","symbol":"🔕","group":"objects","keywords":["bell","bell with slash","forbidden","mute","quiet","silent"]},{"id":":musical_score:","symbol":"🎼","group":"objects","keywords":["music","musical score","score"]},{"id":":musical_note:","symbol":"🎵","group":"objects","keywords":["music","musical note","note"]},{"id":":musical_notes:","symbol":"🎶","group":"objects","keywords":["music","musical notes","note","notes"]},{"id":":studio_microphone:","symbol":"🎙","group":"objects","keywords":["mic","microphone","music","studio"]},{"id":":level_slider:","symbol":"🎚","group":"objects","keywords":["level","music","slider"]},{"id":":control_knobs:","symbol":"🎛","group":"objects","keywords":["control","knobs","music"]},{"id":":microphone:","symbol":"🎤","group":"objects","keywords":["karaoke","mic","microphone"]},{"id":":headphone:","symbol":"🎧","group":"objects","keywords":["earbud","headphone"]},{"id":":radio:","symbol":"📻","group":"objects","keywords":["radio","video"]},{"id":":saxophone:","symbol":"🎷","group":"objects","keywords":["instrument","music","sax","saxophone"]},{"id":":guitar:","symbol":"🎸","group":"objects","keywords":["guitar","instrument","music"]},{"id":":musical_keyboard:","symbol":"🎹","group":"objects","keywords":["instrument","keyboard","music","musical keyboard","piano"]},{"id":":trumpet:","symbol":"🎺","group":"objects","keywords":["instrument","music","trumpet"]},{"id":":violin:","symbol":"🎻","group":"objects","keywords":["instrument","music","violin"]},{"id":":drum:","symbol":"🥁","group":"objects","keywords":["drum","drumsticks","music"]},{"id":":mobile_phone:","symbol":"📱","group":"objects","keywords":["cell","mobile","phone","telephone"]},{"id":":mobile_phone_with_arrow:","symbol":"📲","group":"objects","keywords":["arrow","cell","mobile","mobile phone with arrow","phone","receive"]},{"id":":telephone:","symbol":"☎","group":"objects","keywords":["phone","telephone"]},{"id":":telephone_receiver:","symbol":"📞","group":"objects","keywords":["phone","receiver","telephone"]},{"id":":pager:","symbol":"📟","group":"objects","keywords":["pager"]},{"id":":fax_machine:","symbol":"📠","group":"objects","keywords":["fax","fax machine"]},{"id":":battery:","symbol":"🔋","group":"objects","keywords":["battery"]},{"id":":electric_plug:","symbol":"🔌","group":"objects","keywords":["electric","electricity","plug"]},{"id":":laptop_computer:","symbol":"💻","group":"objects","keywords":["computer","laptop computer","pc","personal"]},{"id":":desktop_computer:","symbol":"🖥","group":"objects","keywords":["computer","desktop"]},{"id":":printer:","symbol":"🖨","group":"objects","keywords":["computer","printer"]},{"id":":keyboard:","symbol":"⌨","group":"objects","keywords":["computer","keyboard"]},{"id":":computer_mouse:","symbol":"🖱","group":"objects","keywords":["computer","computer mouse"]},{"id":":trackball:","symbol":"🖲","group":"objects","keywords":["computer","trackball"]},{"id":":computer_disk:","symbol":"💽","group":"objects","keywords":["computer","disk","minidisk","optical"]},{"id":":floppy_disk:","symbol":"💾","group":"objects","keywords":["computer","disk","floppy"]},{"id":":optical_disk:","symbol":"💿","group":"objects","keywords":["cd","computer","disk","optical"]},{"id":":dvd:","symbol":"📀","group":"objects","keywords":["blu-ray","computer","disk","dvd","optical"]},{"id":":movie_camera:","symbol":"🎥","group":"objects","keywords":["camera","cinema","movie"]},{"id":":film_frames:","symbol":"🎞","group":"objects","keywords":["cinema","film","frames","movie"]},{"id":":film_projector:","symbol":"📽","group":"objects","keywords":["cinema","film","movie","projector","video"]},{"id":":clapper_board:","symbol":"🎬","group":"objects","keywords":["clapper","clapper board","movie"]},{"id":":television:","symbol":"📺","group":"objects","keywords":["television","tv","video"]},{"id":":camera:","symbol":"📷","group":"objects","keywords":["camera","video"]},{"id":":camera_with_flash:","symbol":"📸","group":"objects","keywords":["camera","camera with flash","flash","video"]},{"id":":video_camera:","symbol":"📹","group":"objects","keywords":["camera","video"]},{"id":":videocassette:","symbol":"📼","group":"objects","keywords":["tape","vhs","video","videocassette"]},{"id":":magnifying_glass_tilted_left:","symbol":"🔍","group":"objects","keywords":["glass","magnifying","magnifying glass tilted left","search","tool"]},{"id":":magnifying_glass_tilted_right:","symbol":"🔎","group":"objects","keywords":["glass","magnifying","magnifying glass tilted right","search","tool"]},{"id":":candle:","symbol":"🕯","group":"objects","keywords":["candle","light"]},{"id":":light_bulb:","symbol":"💡","group":"objects","keywords":["bulb","comic","electric","idea","light"]},{"id":":flashlight:","symbol":"🔦","group":"objects","keywords":["electric","flashlight","light","tool","torch"]},{"id":":red_paper_lantern:","symbol":"🏮","group":"objects","keywords":["bar","lantern","light","red","red paper lantern"]},{"id":":notebook_with_decorative_cover:","symbol":"📔","group":"objects","keywords":["book","cover","decorated","notebook","notebook with decorative cover"]},{"id":":closed_book:","symbol":"📕","group":"objects","keywords":["book","closed"]},{"id":":open_book:","symbol":"📖","group":"objects","keywords":["book","open"]},{"id":":green_book:","symbol":"📗","group":"objects","keywords":["book","green"]},{"id":":blue_book:","symbol":"📘","group":"objects","keywords":["blue","book"]},{"id":":orange_book:","symbol":"📙","group":"objects","keywords":["book","orange"]},{"id":":books:","symbol":"📚","group":"objects","keywords":["book","books"]},{"id":":notebook:","symbol":"📓","group":"objects","keywords":["notebook"]},{"id":":ledger:","symbol":"📒","group":"objects","keywords":["ledger","notebook"]},{"id":":page_with_curl:","symbol":"📃","group":"objects","keywords":["curl","document","page","page with curl"]},{"id":":scroll:","symbol":"📜","group":"objects","keywords":["paper","scroll"]},{"id":":page_facing_up:","symbol":"📄","group":"objects","keywords":["document","page","page facing up"]},{"id":":newspaper:","symbol":"📰","group":"objects","keywords":["news","newspaper","paper"]},{"id":":rolled-up_newspaper:","symbol":"🗞","group":"objects","keywords":["news","newspaper","paper","rolled","rolled-up newspaper"]},{"id":":bookmark_tabs:","symbol":"📑","group":"objects","keywords":["bookmark","mark","marker","tabs"]},{"id":":bookmark:","symbol":"🔖","group":"objects","keywords":["bookmark","mark"]},{"id":":label:","symbol":"🏷","group":"objects","keywords":["label"]},{"id":":money_bag:","symbol":"💰","group":"objects","keywords":["bag","dollar","money","moneybag"]},{"id":":yen_banknote:","symbol":"💴","group":"objects","keywords":["banknote","bill","currency","money","note","yen"]},{"id":":dollar_banknote:","symbol":"💵","group":"objects","keywords":["banknote","bill","currency","dollar","money","note"]},{"id":":euro_banknote:","symbol":"💶","group":"objects","keywords":["banknote","bill","currency","euro","money","note"]},{"id":":pound_banknote:","symbol":"💷","group":"objects","keywords":["banknote","bill","currency","money","note","pound"]},{"id":":money_with_wings:","symbol":"💸","group":"objects","keywords":["banknote","bill","fly","money","money with wings","wings"]},{"id":":credit_card:","symbol":"💳","group":"objects","keywords":["card","credit","money"]},{"id":":chart_increasing_with_yen:","symbol":"💹","group":"objects","keywords":["chart","chart increasing with yen","graph","growth","money","yen"]},{"id":":currency_exchange:","symbol":"💱","group":"objects","keywords":["bank","currency","exchange","money"]},{"id":":heavy_dollar_sign:","symbol":"💲","group":"objects","keywords":["currency","dollar","heavy dollar sign","money"]},{"id":":envelope:","symbol":"✉","group":"objects","keywords":["email","envelope","letter"]},{"id":":e-mail:","symbol":"📧","group":"objects","keywords":["e-mail","email","letter","mail"]},{"id":":incoming_envelope:","symbol":"📨","group":"objects","keywords":["e-mail","email","envelope","incoming","letter","receive"]},{"id":":envelope_with_arrow:","symbol":"📩","group":"objects","keywords":["arrow","e-mail","email","envelope","envelope with arrow","outgoing"]},{"id":":outbox_tray:","symbol":"📤","group":"objects","keywords":["box","letter","mail","outbox","sent","tray"]},{"id":":inbox_tray:","symbol":"📥","group":"objects","keywords":["box","inbox","letter","mail","receive","tray"]},{"id":":package:","symbol":"📦","group":"objects","keywords":["box","package","parcel"]},{"id":":closed_mailbox_with_raised_flag:","symbol":"📫","group":"objects","keywords":["closed","closed mailbox with raised flag","mail","mailbox","postbox"]},{"id":":closed_mailbox_with_lowered_flag:","symbol":"📪","group":"objects","keywords":["closed","closed mailbox with lowered flag","lowered","mail","mailbox","postbox"]},{"id":":open_mailbox_with_raised_flag:","symbol":"📬","group":"objects","keywords":["mail","mailbox","open","open mailbox with raised flag","postbox"]},{"id":":open_mailbox_with_lowered_flag:","symbol":"📭","group":"objects","keywords":["lowered","mail","mailbox","open","open mailbox with lowered flag","postbox"]},{"id":":postbox:","symbol":"📮","group":"objects","keywords":["mail","mailbox","postbox"]},{"id":":ballot_box_with_ballot:","symbol":"🗳","group":"objects","keywords":["ballot","ballot box with ballot","box"]},{"id":":pencil:","symbol":"✏","group":"objects","keywords":["pencil"]},{"id":":black_nib:","symbol":"✒","group":"objects","keywords":["black nib","nib","pen"]},{"id":":fountain_pen:","symbol":"🖋","group":"objects","keywords":["fountain","pen"]},{"id":":pen:","symbol":"🖊","group":"objects","keywords":["ballpoint","pen"]},{"id":":paintbrush:","symbol":"🖌","group":"objects","keywords":["paintbrush","painting"]},{"id":":crayon:","symbol":"🖍","group":"objects","keywords":["crayon"]},{"id":":memo:","symbol":"📝","group":"objects","keywords":["memo","pencil"]},{"id":":briefcase:","symbol":"💼","group":"objects","keywords":["briefcase"]},{"id":":file_folder:","symbol":"📁","group":"objects","keywords":["file","folder"]},{"id":":open_file_folder:","symbol":"📂","group":"objects","keywords":["file","folder","open"]},{"id":":card_index_dividers:","symbol":"🗂","group":"objects","keywords":["card","dividers","index"]},{"id":":calendar:","symbol":"📅","group":"objects","keywords":["calendar","date"]},{"id":":tear-off_calendar:","symbol":"📆","group":"objects","keywords":["calendar","tear-off calendar"]},{"id":":spiral_notepad:","symbol":"🗒","group":"objects","keywords":["note","pad","spiral","spiral notepad"]},{"id":":spiral_calendar:","symbol":"🗓","group":"objects","keywords":["calendar","pad","spiral"]},{"id":":card_index:","symbol":"📇","group":"objects","keywords":["card","index","rolodex"]},{"id":":chart_increasing:","symbol":"📈","group":"objects","keywords":["chart","chart increasing","graph","growth","trend","upward"]},{"id":":chart_decreasing:","symbol":"📉","group":"objects","keywords":["chart","chart decreasing","down","graph","trend"]},{"id":":bar_chart:","symbol":"📊","group":"objects","keywords":["bar","chart","graph"]},{"id":":clipboard:","symbol":"📋","group":"objects","keywords":["clipboard"]},{"id":":pushpin:","symbol":"📌","group":"objects","keywords":["pin","pushpin"]},{"id":":round_pushpin:","symbol":"📍","group":"objects","keywords":["pin","pushpin","round pushpin"]},{"id":":paperclip:","symbol":"📎","group":"objects","keywords":["paperclip"]},{"id":":linked_paperclips:","symbol":"🖇","group":"objects","keywords":["link","linked paperclips","paperclip"]},{"id":":straight_ruler:","symbol":"📏","group":"objects","keywords":["ruler","straight edge","straight ruler"]},{"id":":triangular_ruler:","symbol":"📐","group":"objects","keywords":["ruler","set","triangle","triangular ruler"]},{"id":":scissors:","symbol":"✂","group":"objects","keywords":["cutting","scissors","tool"]},{"id":":card_file_box:","symbol":"🗃","group":"objects","keywords":["box","card","file"]},{"id":":file_cabinet:","symbol":"🗄","group":"objects","keywords":["cabinet","file","filing"]},{"id":":wastebasket:","symbol":"🗑","group":"objects","keywords":["wastebasket"]},{"id":":locked:","symbol":"🔒","group":"objects","keywords":["closed","locked"]},{"id":":unlocked:","symbol":"🔓","group":"objects","keywords":["lock","open","unlock","unlocked"]},{"id":":locked_with_pen:","symbol":"🔏","group":"objects","keywords":["ink","lock","locked with pen","nib","pen","privacy"]},{"id":":locked_with_key:","symbol":"🔐","group":"objects","keywords":["closed","key","lock","locked with key","secure"]},{"id":":key:","symbol":"🔑","group":"objects","keywords":["key","lock","password"]},{"id":":old_key:","symbol":"🗝","group":"objects","keywords":["clue","key","lock","old"]},{"id":":hammer:","symbol":"🔨","group":"objects","keywords":["hammer","tool"]},{"id":":pick:","symbol":"⛏","group":"objects","keywords":["mining","pick","tool"]},{"id":":hammer_and_pick:","symbol":"⚒","group":"objects","keywords":["hammer","hammer and pick","pick","tool"]},{"id":":hammer_and_wrench:","symbol":"🛠","group":"objects","keywords":["hammer","hammer and wrench","spanner","tool","wrench"]},{"id":":dagger:","symbol":"🗡","group":"objects","keywords":["dagger","knife","weapon"]},{"id":":crossed_swords:","symbol":"⚔","group":"objects","keywords":["crossed","swords","weapon"]},{"id":":pistol:","symbol":"🔫","group":"objects","keywords":["gun","handgun","pistol","revolver","tool","weapon"]},{"id":":bow_and_arrow:","symbol":"🏹","group":"objects","keywords":["archer","arrow","bow","bow and arrow","Sagittarius","zodiac"]},{"id":":shield:","symbol":"🛡","group":"objects","keywords":["shield","weapon"]},{"id":":wrench:","symbol":"🔧","group":"objects","keywords":["spanner","tool","wrench"]},{"id":":nut_and_bolt:","symbol":"🔩","group":"objects","keywords":["bolt","nut","nut and bolt","tool"]},{"id":":gear:","symbol":"⚙","group":"objects","keywords":["cog","cogwheel","gear","tool"]},{"id":":clamp:","symbol":"🗜","group":"objects","keywords":["clamp","compress","tool","vice"]},{"id":":balance_scale:","symbol":"⚖","group":"objects","keywords":["balance","justice","Libra","scale","zodiac"]},{"id":":link:","symbol":"🔗","group":"objects","keywords":["link"]},{"id":":chains:","symbol":"⛓","group":"objects","keywords":["chain","chains"]},{"id":":alembic:","symbol":"⚗","group":"objects","keywords":["alembic","chemistry","tool"]},{"id":":microscope:","symbol":"🔬","group":"objects","keywords":["microscope","science","tool"]},{"id":":telescope:","symbol":"🔭","group":"objects","keywords":["science","telescope","tool"]},{"id":":satellite_antenna:","symbol":"📡","group":"objects","keywords":["antenna","dish","satellite"]},{"id":":syringe:","symbol":"💉","group":"objects","keywords":["medicine","needle","shot","sick","syringe"]},{"id":":pill:","symbol":"💊","group":"objects","keywords":["doctor","medicine","pill","sick"]},{"id":":door:","symbol":"🚪","group":"objects","keywords":["door"]},{"id":":bed:","symbol":"🛏","group":"objects","keywords":["bed","hotel","sleep"]},{"id":":couch_and_lamp:","symbol":"🛋","group":"objects","keywords":["couch","couch and lamp","hotel","lamp"]},{"id":":toilet:","symbol":"🚽","group":"objects","keywords":["toilet"]},{"id":":shower:","symbol":"🚿","group":"objects","keywords":["shower","water"]},{"id":":bathtub:","symbol":"🛁","group":"objects","keywords":["bath","bathtub"]},{"id":":shopping_cart:","symbol":"🛒","group":"objects","keywords":["cart","shopping","trolley"]},{"id":":cigarette:","symbol":"🚬","group":"objects","keywords":["cigarette","smoking"]},{"id":":coffin:","symbol":"⚰","group":"objects","keywords":["coffin","death"]},{"id":":funeral_urn:","symbol":"⚱","group":"objects","keywords":["ashes","death","funeral","urn"]},{"id":":moai:","symbol":"🗿","group":"objects","keywords":["face","moai","moyai","statue"]},{"id":":atm_sign:","symbol":"🏧","group":"symbols","keywords":["atm","ATM sign","automated","bank","teller"]},{"id":":litter_in_bin_sign:","symbol":"🚮","group":"symbols","keywords":["litter","litter bin","litter in bin sign"]},{"id":":potable_water:","symbol":"🚰","group":"symbols","keywords":["drinking","potable","water"]},{"id":":wheelchair_symbol:","symbol":"♿","group":"symbols","keywords":["access","wheelchair symbol"]},{"id":":men’s_room:","symbol":"🚹","group":"symbols","keywords":["lavatory","man","men’s room","restroom","wc"]},{"id":":women’s_room:","symbol":"🚺","group":"symbols","keywords":["lavatory","restroom","wc","woman","women’s room"]},{"id":":restroom:","symbol":"🚻","group":"symbols","keywords":["lavatory","restroom","WC"]},{"id":":baby_symbol:","symbol":"🚼","group":"symbols","keywords":["baby","baby symbol","changing"]},{"id":":water_closet:","symbol":"🚾","group":"symbols","keywords":["closet","lavatory","restroom","water","wc"]},{"id":":passport_control:","symbol":"🛂","group":"symbols","keywords":["control","passport"]},{"id":":customs:","symbol":"🛃","group":"symbols","keywords":["customs"]},{"id":":baggage_claim:","symbol":"🛄","group":"symbols","keywords":["baggage","claim"]},{"id":":left_luggage:","symbol":"🛅","group":"symbols","keywords":["baggage","left luggage","locker","luggage"]},{"id":":warning:","symbol":"⚠","group":"symbols","keywords":["warning"]},{"id":":children_crossing:","symbol":"🚸","group":"symbols","keywords":["child","children crossing","crossing","pedestrian","traffic"]},{"id":":no_entry:","symbol":"⛔","group":"symbols","keywords":["entry","forbidden","no","not","prohibited","traffic"]},{"id":":prohibited:","symbol":"🚫","group":"symbols","keywords":["entry","forbidden","no","not","prohibited"]},{"id":":no_bicycles:","symbol":"🚳","group":"symbols","keywords":["bicycle","bike","forbidden","no","no bicycles","prohibited"]},{"id":":no_smoking:","symbol":"🚭","group":"symbols","keywords":["forbidden","no","not","prohibited","smoking"]},{"id":":no_littering:","symbol":"🚯","group":"symbols","keywords":["forbidden","litter","no","no littering","not","prohibited"]},{"id":":non-potable_water:","symbol":"🚱","group":"symbols","keywords":["non-drinking","non-potable","water"]},{"id":":no_pedestrians:","symbol":"🚷","group":"symbols","keywords":["forbidden","no","no pedestrians","not","pedestrian","prohibited"]},{"id":":no_mobile_phones:","symbol":"📵","group":"symbols","keywords":["cell","forbidden","mobile","no","no mobile phones","phone"]},{"id":":no_one_under_eighteen:","symbol":"🔞","group":"symbols","keywords":["18","age restriction","eighteen","no one under eighteen","prohibited","underage"]},{"id":":radioactive:","symbol":"☢","group":"symbols","keywords":["radioactive","sign"]},{"id":":biohazard:","symbol":"☣","group":"symbols","keywords":["biohazard","sign"]},{"id":":up_arrow:","symbol":"⬆","group":"symbols","keywords":["arrow","cardinal","direction","north","up arrow"]},{"id":":up-right_arrow:","symbol":"↗","group":"symbols","keywords":["arrow","direction","intercardinal","northeast","up-right arrow"]},{"id":":right_arrow:","symbol":"➡","group":"symbols","keywords":["arrow","cardinal","direction","east","right arrow"]},{"id":":down-right_arrow:","symbol":"↘","group":"symbols","keywords":["arrow","direction","down-right arrow","intercardinal","southeast"]},{"id":":down_arrow:","symbol":"⬇","group":"symbols","keywords":["arrow","cardinal","direction","down","south"]},{"id":":down-left_arrow:","symbol":"↙","group":"symbols","keywords":["arrow","direction","down-left arrow","intercardinal","southwest"]},{"id":":left_arrow:","symbol":"⬅","group":"symbols","keywords":["arrow","cardinal","direction","left arrow","west"]},{"id":":up-left_arrow:","symbol":"↖","group":"symbols","keywords":["arrow","direction","intercardinal","northwest","up-left arrow"]},{"id":":up-down_arrow:","symbol":"↕","group":"symbols","keywords":["arrow","up-down arrow"]},{"id":":left-right_arrow:","symbol":"↔","group":"symbols","keywords":["arrow","left-right arrow"]},{"id":":right_arrow_curving_left:","symbol":"↩","group":"symbols","keywords":["arrow","right arrow curving left"]},{"id":":left_arrow_curving_right:","symbol":"↪","group":"symbols","keywords":["arrow","left arrow curving right"]},{"id":":right_arrow_curving_up:","symbol":"⤴","group":"symbols","keywords":["arrow","right arrow curving up"]},{"id":":right_arrow_curving_down:","symbol":"⤵","group":"symbols","keywords":["arrow","down","right arrow curving down"]},{"id":":clockwise_vertical_arrows:","symbol":"🔃","group":"symbols","keywords":["arrow","clockwise","clockwise vertical arrows","reload"]},{"id":":counterclockwise_arrows_button:","symbol":"🔄","group":"symbols","keywords":["anticlockwise","arrow","counterclockwise","counterclockwise arrows button","withershins"]},{"id":":back_arrow:","symbol":"🔙","group":"symbols","keywords":["arrow","back","BACK arrow"]},{"id":":end_arrow:","symbol":"🔚","group":"symbols","keywords":["arrow","end","END arrow"]},{"id":":on!_arrow:","symbol":"🔛","group":"symbols","keywords":["arrow","mark","on","ON! arrow"]},{"id":":soon_arrow:","symbol":"🔜","group":"symbols","keywords":["arrow","soon","SOON arrow"]},{"id":":top_arrow:","symbol":"🔝","group":"symbols","keywords":["arrow","top","TOP arrow","up"]},{"id":":place_of_worship:","symbol":"🛐","group":"symbols","keywords":["place of worship","religion","worship"]},{"id":":atom_symbol:","symbol":"⚛","group":"symbols","keywords":["atheist","atom","atom symbol"]},{"id":":om:","symbol":"🕉","group":"symbols","keywords":["Hindu","om","religion"]},{"id":":star_of_david:","symbol":"✡","group":"symbols","keywords":["David","Jew","Jewish","religion","star","star of David"]},{"id":":wheel_of_dharma:","symbol":"☸","group":"symbols","keywords":["Buddhist","dharma","religion","wheel","wheel of dharma"]},{"id":":yin_yang:","symbol":"☯","group":"symbols","keywords":["religion","tao","taoist","yang","yin"]},{"id":":latin_cross:","symbol":"✝","group":"symbols","keywords":["Christian","cross","latin cross","religion"]},{"id":":orthodox_cross:","symbol":"☦","group":"symbols","keywords":["Christian","cross","orthodox cross","religion"]},{"id":":star_and_crescent:","symbol":"☪","group":"symbols","keywords":["islam","Muslim","religion","star and crescent"]},{"id":":peace_symbol:","symbol":"☮","group":"symbols","keywords":["peace","peace symbol"]},{"id":":menorah:","symbol":"🕎","group":"symbols","keywords":["candelabrum","candlestick","menorah","religion"]},{"id":":dotted_six-pointed_star:","symbol":"🔯","group":"symbols","keywords":["dotted six-pointed star","fortune","star"]},{"id":":aries:","symbol":"♈","group":"symbols","keywords":["Aries","ram","zodiac"]},{"id":":taurus:","symbol":"♉","group":"symbols","keywords":["bull","ox","Taurus","zodiac"]},{"id":":gemini:","symbol":"♊","group":"symbols","keywords":["Gemini","twins","zodiac"]},{"id":":cancer:","symbol":"♋","group":"symbols","keywords":["Cancer","crab","zodiac"]},{"id":":leo:","symbol":"♌","group":"symbols","keywords":["Leo","lion","zodiac"]},{"id":":virgo:","symbol":"♍","group":"symbols","keywords":["Virgo","zodiac"]},{"id":":libra:","symbol":"♎","group":"symbols","keywords":["balance","justice","Libra","scales","zodiac"]},{"id":":scorpio:","symbol":"♏","group":"symbols","keywords":["Scorpio","scorpion","scorpius","zodiac"]},{"id":":sagittarius:","symbol":"♐","group":"symbols","keywords":["archer","Sagittarius","zodiac"]},{"id":":capricorn:","symbol":"♑","group":"symbols","keywords":["Capricorn","goat","zodiac"]},{"id":":aquarius:","symbol":"♒","group":"symbols","keywords":["Aquarius","bearer","water","zodiac"]},{"id":":pisces:","symbol":"♓","group":"symbols","keywords":["fish","Pisces","zodiac"]},{"id":":ophiuchus:","symbol":"⛎","group":"symbols","keywords":["bearer","Ophiuchus","serpent","snake","zodiac"]},{"id":":shuffle_tracks_button:","symbol":"🔀","group":"symbols","keywords":["arrow","crossed","shuffle tracks button"]},{"id":":repeat_button:","symbol":"🔁","group":"symbols","keywords":["arrow","clockwise","repeat","repeat button"]},{"id":":repeat_single_button:","symbol":"🔂","group":"symbols","keywords":["arrow","clockwise","once","repeat single button"]},{"id":":play_button:","symbol":"▶","group":"symbols","keywords":["arrow","play","play button","right","triangle"]},{"id":":fast-forward_button:","symbol":"⏩","group":"symbols","keywords":["arrow","double","fast","fast-forward button","forward"]},{"id":":next_track_button:","symbol":"⏭","group":"symbols","keywords":["arrow","next scene","next track","next track button","triangle"]},{"id":":play_or_pause_button:","symbol":"⏯","group":"symbols","keywords":["arrow","pause","play","play or pause button","right","triangle"]},{"id":":reverse_button:","symbol":"◀","group":"symbols","keywords":["arrow","left","reverse","reverse button","triangle"]},{"id":":fast_reverse_button:","symbol":"⏪","group":"symbols","keywords":["arrow","double","fast reverse button","rewind"]},{"id":":last_track_button:","symbol":"⏮","group":"symbols","keywords":["arrow","last track button","previous scene","previous track","triangle"]},{"id":":upwards_button:","symbol":"🔼","group":"symbols","keywords":["arrow","button","red","upwards button"]},{"id":":fast_up_button:","symbol":"⏫","group":"symbols","keywords":["arrow","double","fast up button"]},{"id":":downwards_button:","symbol":"🔽","group":"symbols","keywords":["arrow","button","down","downwards button","red"]},{"id":":fast_down_button:","symbol":"⏬","group":"symbols","keywords":["arrow","double","down","fast down button"]},{"id":":pause_button:","symbol":"⏸","group":"symbols","keywords":["bar","double","pause","pause button","vertical"]},{"id":":stop_button:","symbol":"⏹","group":"symbols","keywords":["square","stop","stop button"]},{"id":":record_button:","symbol":"⏺","group":"symbols","keywords":["circle","record","record button"]},{"id":":eject_button:","symbol":"⏏","group":"symbols","keywords":["eject","eject button"]},{"id":":cinema:","symbol":"🎦","group":"symbols","keywords":["camera","cinema","film","movie"]},{"id":":dim_button:","symbol":"🔅","group":"symbols","keywords":["brightness","dim","dim button","low"]},{"id":":bright_button:","symbol":"🔆","group":"symbols","keywords":["bright","bright button","brightness"]},{"id":":antenna_bars:","symbol":"📶","group":"symbols","keywords":["antenna","antenna bars","bar","cell","mobile","phone"]},{"id":":vibration_mode:","symbol":"📳","group":"symbols","keywords":["cell","mobile","mode","phone","telephone","vibration"]},{"id":":mobile_phone_off:","symbol":"📴","group":"symbols","keywords":["cell","mobile","off","phone","telephone"]},{"id":":female_sign:","symbol":"♀","group":"symbols","keywords":["female sign","woman"]},{"id":":male_sign:","symbol":"♂","group":"symbols","keywords":["male sign","man"]},{"id":":medical_symbol:","symbol":"⚕","group":"symbols","keywords":["aesculapius","medical symbol","medicine","staff"]},{"id":":recycling_symbol:","symbol":"♻","group":"symbols","keywords":["recycle","recycling symbol"]},{"id":":fleur-de-lis:","symbol":"⚜","group":"symbols","keywords":["fleur-de-lis"]},{"id":":trident_emblem:","symbol":"🔱","group":"symbols","keywords":["anchor","emblem","ship","tool","trident"]},{"id":":name_badge:","symbol":"📛","group":"symbols","keywords":["badge","name"]},{"id":":japanese_symbol_for_beginner:","symbol":"🔰","group":"symbols","keywords":["beginner","chevron","Japanese","Japanese symbol for beginner","leaf"]},{"id":":heavy_large_circle:","symbol":"⭕","group":"symbols","keywords":["circle","heavy large circle","o"]},{"id":":white_heavy_check_mark:","symbol":"✅","group":"symbols","keywords":["check","mark","white heavy check mark"]},{"id":":ballot_box_with_check:","symbol":"☑","group":"symbols","keywords":["ballot","ballot box with check","box","check"]},{"id":":heavy_check_mark:","symbol":"✔","group":"symbols","keywords":["check","heavy check mark","mark"]},{"id":":heavy_multiplication_x:","symbol":"✖","group":"symbols","keywords":["cancel","heavy multiplication x","multiplication","multiply","x"]},{"id":":cross_mark:","symbol":"❌","group":"symbols","keywords":["cancel","cross mark","mark","multiplication","multiply","x"]},{"id":":cross_mark_button:","symbol":"❎","group":"symbols","keywords":["cross mark button","mark","square"]},{"id":":heavy_plus_sign:","symbol":"➕","group":"symbols","keywords":["heavy plus sign","math","plus"]},{"id":":heavy_minus_sign:","symbol":"➖","group":"symbols","keywords":["heavy minus sign","math","minus"]},{"id":":heavy_division_sign:","symbol":"➗","group":"symbols","keywords":["division","heavy division sign","math"]},{"id":":curly_loop:","symbol":"➰","group":"symbols","keywords":["curl","curly loop","loop"]},{"id":":double_curly_loop:","symbol":"➿","group":"symbols","keywords":["curl","double","double curly loop","loop"]},{"id":":part_alternation_mark:","symbol":"〽","group":"symbols","keywords":["mark","part","part alternation mark"]},{"id":":eight-spoked_asterisk:","symbol":"✳","group":"symbols","keywords":["asterisk","eight-spoked asterisk"]},{"id":":eight-pointed_star:","symbol":"✴","group":"symbols","keywords":["eight-pointed star","star"]},{"id":":sparkle:","symbol":"❇","group":"symbols","keywords":["sparkle"]},{"id":":double_exclamation_mark:","symbol":"‼","group":"symbols","keywords":["bangbang","double exclamation mark","exclamation","mark","punctuation"]},{"id":":exclamation_question_mark:","symbol":"⁉","group":"symbols","keywords":["exclamation","interrobang","mark","punctuation","question"]},{"id":":question_mark:","symbol":"❓","group":"symbols","keywords":["mark","punctuation","question"]},{"id":":white_question_mark:","symbol":"❔","group":"symbols","keywords":["mark","outlined","punctuation","question","white question mark"]},{"id":":white_exclamation_mark:","symbol":"❕","group":"symbols","keywords":["exclamation","mark","outlined","punctuation","white exclamation mark"]},{"id":":exclamation_mark:","symbol":"❗","group":"symbols","keywords":["exclamation","mark","punctuation"]},{"id":":wavy_dash:","symbol":"〰","group":"symbols","keywords":["dash","punctuation","wavy"]},{"id":":copyright:","symbol":"©","group":"symbols","keywords":["copyright"]},{"id":":registered:","symbol":"®","group":"symbols","keywords":["registered"]},{"id":":trade_mark:","symbol":"™","group":"symbols","keywords":["mark","tm","trade mark","trademark"]},{"id":":keycap_#:","symbol":"#️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_*:","symbol":"*️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_0:","symbol":"0️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_1:","symbol":"1️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_2:","symbol":"2️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_3:","symbol":"3️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_4:","symbol":"4️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_5:","symbol":"5️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_6:","symbol":"6️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_7:","symbol":"7️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_8:","symbol":"8️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_9:","symbol":"9️⃣","group":"symbols","keywords":["keycap"]},{"id":":keycap_10:","symbol":"🔟","group":"symbols","keywords":["keycap"]},{"id":":hundred_points:","symbol":"💯","group":"symbols","keywords":["100","full","hundred","hundred points","score"]},{"id":":input_latin_uppercase:","symbol":"🔠","group":"symbols","keywords":["ABCD","input","latin","letters","uppercase"]},{"id":":input_latin_lowercase:","symbol":"🔡","group":"symbols","keywords":["abcd","input","latin","letters","lowercase"]},{"id":":input_numbers:","symbol":"🔢","group":"symbols","keywords":["1234","input","numbers"]},{"id":":input_symbols:","symbol":"🔣","group":"symbols","keywords":["〒♪&%","input","input symbols"]},{"id":":input_latin_letters:","symbol":"🔤","group":"symbols","keywords":["abc","alphabet","input","latin","letters"]},{"id":":a_button_(blood_type):","symbol":"🅰","group":"symbols","keywords":["a","A button (blood type)","blood type"]},{"id":":ab_button_(blood_type):","symbol":"🆎","group":"symbols","keywords":["ab","AB button (blood type)","blood type"]},{"id":":b_button_(blood_type):","symbol":"🅱","group":"symbols","keywords":["b","B button (blood type)","blood type"]},{"id":":cl_button:","symbol":"🆑","group":"symbols","keywords":["cl","CL button"]},{"id":":cool_button:","symbol":"🆒","group":"symbols","keywords":["cool","COOL button"]},{"id":":free_button:","symbol":"🆓","group":"symbols","keywords":["free","FREE button"]},{"id":":information:","symbol":"ℹ","group":"symbols","keywords":["i","information"]},{"id":":id_button:","symbol":"🆔","group":"symbols","keywords":["id","ID button","identity"]},{"id":":circled_m:","symbol":"Ⓜ","group":"symbols","keywords":["circle","circled M","m"]},{"id":":new_button:","symbol":"🆕","group":"symbols","keywords":["new","NEW button"]},{"id":":ng_button:","symbol":"🆖","group":"symbols","keywords":["ng","NG button"]},{"id":":o_button_(blood_type):","symbol":"🅾","group":"symbols","keywords":["blood type","o","O button (blood type)"]},{"id":":ok_button:","symbol":"🆗","group":"symbols","keywords":["OK","OK button"]},{"id":":p_button:","symbol":"🅿","group":"symbols","keywords":["P button","parking"]},{"id":":sos_button:","symbol":"🆘","group":"symbols","keywords":["help","sos","SOS button"]},{"id":":up!_button:","symbol":"🆙","group":"symbols","keywords":["mark","up","UP! button"]},{"id":":vs_button:","symbol":"🆚","group":"symbols","keywords":["versus","vs","VS button"]},{"id":":japanese_“here”_button:","symbol":"🈁","group":"symbols","keywords":["“here”","Japanese","Japanese “here” button","katakana","ココ"]},{"id":":japanese_“service_charge”_button:","symbol":"🈂","group":"symbols","keywords":["“service charge”","Japanese","Japanese “service charge” button","katakana","サ"]},{"id":":japanese_“monthly_amount”_button:","symbol":"🈷","group":"symbols","keywords":["“monthly amount”","ideograph","Japanese","Japanese “monthly amount” button","月"]},{"id":":japanese_“not_free_of_charge”_button:","symbol":"🈶","group":"symbols","keywords":["“not free of charge”","ideograph","Japanese","Japanese “not free of charge” button","有"]},{"id":":japanese_“reserved”_button:","symbol":"🈯","group":"symbols","keywords":["“reserved”","ideograph","Japanese","Japanese “reserved” button","指"]},{"id":":japanese_“bargain”_button:","symbol":"🉐","group":"symbols","keywords":["“bargain”","ideograph","Japanese","Japanese “bargain” button","得"]},{"id":":japanese_“discount”_button:","symbol":"🈹","group":"symbols","keywords":["“discount”","ideograph","Japanese","Japanese “discount” button","割"]},{"id":":japanese_“free_of_charge”_button:","symbol":"🈚","group":"symbols","keywords":["“free of charge”","ideograph","Japanese","Japanese “free of charge” button","無"]},{"id":":japanese_“prohibited”_button:","symbol":"🈲","group":"symbols","keywords":["“prohibited”","ideograph","Japanese","Japanese “prohibited” button","禁"]},{"id":":japanese_“acceptable”_button:","symbol":"🉑","group":"symbols","keywords":["“acceptable”","ideograph","Japanese","Japanese “acceptable” button","可"]},{"id":":japanese_“application”_button:","symbol":"🈸","group":"symbols","keywords":["“application”","ideograph","Japanese","Japanese “application” button","申"]},{"id":":japanese_“passing_grade”_button:","symbol":"🈴","group":"symbols","keywords":["“passing grade”","ideograph","Japanese","Japanese “passing grade” button","合"]},{"id":":japanese_“vacancy”_button:","symbol":"🈳","group":"symbols","keywords":["“vacancy”","ideograph","Japanese","Japanese “vacancy” button","空"]},{"id":":japanese_“congratulations”_button:","symbol":"㊗","group":"symbols","keywords":["“congratulations”","ideograph","Japanese","Japanese “congratulations” button","祝"]},{"id":":japanese_“secret”_button:","symbol":"㊙","group":"symbols","keywords":["“secret”","ideograph","Japanese","Japanese “secret” button","秘"]},{"id":":japanese_“open_for_business”_button:","symbol":"🈺","group":"symbols","keywords":["“open for business”","ideograph","Japanese","Japanese “open for business” button","営"]},{"id":":japanese_“no_vacancy”_button:","symbol":"🈵","group":"symbols","keywords":["“no vacancy”","ideograph","Japanese","Japanese “no vacancy” button","満"]},{"id":":red_circle:","symbol":"🔴","group":"symbols","keywords":["circle","geometric","red"]},{"id":":blue_circle:","symbol":"🔵","group":"symbols","keywords":["blue","circle","geometric"]},{"id":":white_circle:","symbol":"⚪","group":"symbols","keywords":["circle","geometric","white circle"]},{"id":":black_circle:","symbol":"⚫","group":"symbols","keywords":["black circle","circle","geometric"]},{"id":":white_large_square:","symbol":"⬜","group":"symbols","keywords":["geometric","square","white large square"]},{"id":":black_large_square:","symbol":"⬛","group":"symbols","keywords":["black large square","geometric","square"]},{"id":":black_medium_square:","symbol":"◼","group":"symbols","keywords":["black medium square","geometric","square"]},{"id":":white_medium_square:","symbol":"◻","group":"symbols","keywords":["geometric","square","white medium square"]},{"id":":white_medium-small_square:","symbol":"◽","group":"symbols","keywords":["geometric","square","white medium-small square"]},{"id":":black_medium-small_square:","symbol":"◾","group":"symbols","keywords":["black medium-small square","geometric","square"]},{"id":":white_small_square:","symbol":"▫","group":"symbols","keywords":["geometric","square","white small square"]},{"id":":black_small_square:","symbol":"▪","group":"symbols","keywords":["black small square","geometric","square"]},{"id":":large_orange_diamond:","symbol":"🔶","group":"symbols","keywords":["diamond","geometric","large orange diamond","orange"]},{"id":":large_blue_diamond:","symbol":"🔷","group":"symbols","keywords":["blue","diamond","geometric","large blue diamond"]},{"id":":small_orange_diamond:","symbol":"🔸","group":"symbols","keywords":["diamond","geometric","orange","small orange diamond"]},{"id":":small_blue_diamond:","symbol":"🔹","group":"symbols","keywords":["blue","diamond","geometric","small blue diamond"]},{"id":":red_triangle_pointed_up:","symbol":"🔺","group":"symbols","keywords":["geometric","red","red triangle pointed up"]},{"id":":red_triangle_pointed_down:","symbol":"🔻","group":"symbols","keywords":["down","geometric","red","red triangle pointed down"]},{"id":":diamond_with_a_dot:","symbol":"💠","group":"symbols","keywords":["comic","diamond","diamond with a dot","geometric","inside"]},{"id":":radio_button:","symbol":"🔘","group":"symbols","keywords":["button","geometric","radio"]},{"id":":black_square_button:","symbol":"🔲","group":"symbols","keywords":["black square button","button","geometric","square"]},{"id":":white_square_button:","symbol":"🔳","group":"symbols","keywords":["button","geometric","outlined","square","white square button"]},{"id":":chequered_flag:","symbol":"🏁","group":"flags","keywords":["checkered","chequered","chequered flag","racing"]},{"id":":triangular_flag:","symbol":"🚩","group":"flags","keywords":["post","triangular flag"]},{"id":":crossed_flags:","symbol":"🎌","group":"flags","keywords":["celebration","cross","crossed","crossed flags","Japanese"]},{"id":":black_flag:","symbol":"🏴","group":"flags","keywords":["black flag","waving"]},{"id":":white_flag:","symbol":"🏳","group":"flags","keywords":["waving","white flag"]},{"id":":rainbow_flag:","symbol":"🏳️🌈","group":"flags","keywords":["rainbow","rainbow flag"]},{"id":":flag_ascension_island:","symbol":"🇦🇨","group":"flags","keywords":["flag"]},{"id":":flag_andorra:","symbol":"🇦🇩","group":"flags","keywords":["flag"]},{"id":":flag_united_arab_emirates:","symbol":"🇦🇪","group":"flags","keywords":["flag"]},{"id":":flag_afghanistan:","symbol":"🇦🇫","group":"flags","keywords":["flag"]},{"id":":flag_antigua_&_barbuda:","symbol":"🇦🇬","group":"flags","keywords":["flag"]},{"id":":flag_anguilla:","symbol":"🇦🇮","group":"flags","keywords":["flag"]},{"id":":flag_albania:","symbol":"🇦🇱","group":"flags","keywords":["flag"]},{"id":":flag_armenia:","symbol":"🇦🇲","group":"flags","keywords":["flag"]},{"id":":flag_angola:","symbol":"🇦🇴","group":"flags","keywords":["flag"]},{"id":":flag_antarctica:","symbol":"🇦🇶","group":"flags","keywords":["flag"]},{"id":":flag_argentina:","symbol":"🇦🇷","group":"flags","keywords":["flag"]},{"id":":flag_american_samoa:","symbol":"🇦🇸","group":"flags","keywords":["flag"]},{"id":":flag_austria:","symbol":"🇦🇹","group":"flags","keywords":["flag"]},{"id":":flag_australia:","symbol":"🇦🇺","group":"flags","keywords":["flag"]},{"id":":flag_aruba:","symbol":"🇦🇼","group":"flags","keywords":["flag"]},{"id":":flag_åland_islands:","symbol":"🇦🇽","group":"flags","keywords":["flag"]},{"id":":flag_azerbaijan:","symbol":"🇦🇿","group":"flags","keywords":["flag"]},{"id":":flag_bosnia_&_herzegovina:","symbol":"🇧🇦","group":"flags","keywords":["flag"]},{"id":":flag_barbados:","symbol":"🇧🇧","group":"flags","keywords":["flag"]},{"id":":flag_bangladesh:","symbol":"🇧🇩","group":"flags","keywords":["flag"]},{"id":":flag_belgium:","symbol":"🇧🇪","group":"flags","keywords":["flag"]},{"id":":flag_burkina_faso:","symbol":"🇧🇫","group":"flags","keywords":["flag"]},{"id":":flag_bulgaria:","symbol":"🇧🇬","group":"flags","keywords":["flag"]},{"id":":flag_bahrain:","symbol":"🇧🇭","group":"flags","keywords":["flag"]},{"id":":flag_burundi:","symbol":"🇧🇮","group":"flags","keywords":["flag"]},{"id":":flag_benin:","symbol":"🇧🇯","group":"flags","keywords":["flag"]},{"id":":flag_st._barthélemy:","symbol":"🇧🇱","group":"flags","keywords":["flag"]},{"id":":flag_bermuda:","symbol":"🇧🇲","group":"flags","keywords":["flag"]},{"id":":flag_brunei:","symbol":"🇧🇳","group":"flags","keywords":["flag"]},{"id":":flag_bolivia:","symbol":"🇧🇴","group":"flags","keywords":["flag"]},{"id":":flag_caribbean_netherlands:","symbol":"🇧🇶","group":"flags","keywords":["flag"]},{"id":":flag_brazil:","symbol":"🇧🇷","group":"flags","keywords":["flag"]},{"id":":flag_bahamas:","symbol":"🇧🇸","group":"flags","keywords":["flag"]},{"id":":flag_bhutan:","symbol":"🇧🇹","group":"flags","keywords":["flag"]},{"id":":flag_bouvet_island:","symbol":"🇧🇻","group":"flags","keywords":["flag"]},{"id":":flag_botswana:","symbol":"🇧🇼","group":"flags","keywords":["flag"]},{"id":":flag_belarus:","symbol":"🇧🇾","group":"flags","keywords":["flag"]},{"id":":flag_belize:","symbol":"🇧🇿","group":"flags","keywords":["flag"]},{"id":":flag_canada:","symbol":"🇨🇦","group":"flags","keywords":["flag"]},{"id":":flag_cocos_(keeling)_islands:","symbol":"🇨🇨","group":"flags","keywords":["flag"]},{"id":":flag_congo_-_kinshasa:","symbol":"🇨🇩","group":"flags","keywords":["flag"]},{"id":":flag_central_african_republic:","symbol":"🇨🇫","group":"flags","keywords":["flag"]},{"id":":flag_congo_-_brazzaville:","symbol":"🇨🇬","group":"flags","keywords":["flag"]},{"id":":flag_switzerland:","symbol":"🇨🇭","group":"flags","keywords":["flag"]},{"id":":flag_côte_d’ivoire:","symbol":"🇨🇮","group":"flags","keywords":["flag"]},{"id":":flag_cook_islands:","symbol":"🇨🇰","group":"flags","keywords":["flag"]},{"id":":flag_chile:","symbol":"🇨🇱","group":"flags","keywords":["flag"]},{"id":":flag_cameroon:","symbol":"🇨🇲","group":"flags","keywords":["flag"]},{"id":":flag_china:","symbol":"🇨🇳","group":"flags","keywords":["flag"]},{"id":":flag_colombia:","symbol":"🇨🇴","group":"flags","keywords":["flag"]},{"id":":flag_clipperton_island:","symbol":"🇨🇵","group":"flags","keywords":["flag"]},{"id":":flag_costa_rica:","symbol":"🇨🇷","group":"flags","keywords":["flag"]},{"id":":flag_cuba:","symbol":"🇨🇺","group":"flags","keywords":["flag"]},{"id":":flag_cape_verde:","symbol":"🇨🇻","group":"flags","keywords":["flag"]},{"id":":flag_curaçao:","symbol":"🇨🇼","group":"flags","keywords":["flag"]},{"id":":flag_christmas_island:","symbol":"🇨🇽","group":"flags","keywords":["flag"]},{"id":":flag_cyprus:","symbol":"🇨🇾","group":"flags","keywords":["flag"]},{"id":":flag_czechia:","symbol":"🇨🇿","group":"flags","keywords":["flag"]},{"id":":flag_germany:","symbol":"🇩🇪","group":"flags","keywords":["flag"]},{"id":":flag_diego_garcia:","symbol":"🇩🇬","group":"flags","keywords":["flag"]},{"id":":flag_djibouti:","symbol":"🇩🇯","group":"flags","keywords":["flag"]},{"id":":flag_denmark:","symbol":"🇩🇰","group":"flags","keywords":["flag"]},{"id":":flag_dominica:","symbol":"🇩🇲","group":"flags","keywords":["flag"]},{"id":":flag_dominican_republic:","symbol":"🇩🇴","group":"flags","keywords":["flag"]},{"id":":flag_algeria:","symbol":"🇩🇿","group":"flags","keywords":["flag"]},{"id":":flag_ceuta_&_melilla:","symbol":"🇪🇦","group":"flags","keywords":["flag"]},{"id":":flag_ecuador:","symbol":"🇪🇨","group":"flags","keywords":["flag"]},{"id":":flag_estonia:","symbol":"🇪🇪","group":"flags","keywords":["flag"]},{"id":":flag_egypt:","symbol":"🇪🇬","group":"flags","keywords":["flag"]},{"id":":flag_western_sahara:","symbol":"🇪🇭","group":"flags","keywords":["flag"]},{"id":":flag_eritrea:","symbol":"🇪🇷","group":"flags","keywords":["flag"]},{"id":":flag_spain:","symbol":"🇪🇸","group":"flags","keywords":["flag"]},{"id":":flag_ethiopia:","symbol":"🇪🇹","group":"flags","keywords":["flag"]},{"id":":flag_european_union:","symbol":"🇪🇺","group":"flags","keywords":["flag"]},{"id":":flag_finland:","symbol":"🇫🇮","group":"flags","keywords":["flag"]},{"id":":flag_fiji:","symbol":"🇫🇯","group":"flags","keywords":["flag"]},{"id":":flag_falkland_islands:","symbol":"🇫🇰","group":"flags","keywords":["flag"]},{"id":":flag_micronesia:","symbol":"🇫🇲","group":"flags","keywords":["flag"]},{"id":":flag_faroe_islands:","symbol":"🇫🇴","group":"flags","keywords":["flag"]},{"id":":flag_france:","symbol":"🇫🇷","group":"flags","keywords":["flag"]},{"id":":flag_gabon:","symbol":"🇬🇦","group":"flags","keywords":["flag"]},{"id":":flag_united_kingdom:","symbol":"🇬🇧","group":"flags","keywords":["flag"]},{"id":":flag_grenada:","symbol":"🇬🇩","group":"flags","keywords":["flag"]},{"id":":flag_georgia:","symbol":"🇬🇪","group":"flags","keywords":["flag"]},{"id":":flag_french_guiana:","symbol":"🇬🇫","group":"flags","keywords":["flag"]},{"id":":flag_guernsey:","symbol":"🇬🇬","group":"flags","keywords":["flag"]},{"id":":flag_ghana:","symbol":"🇬🇭","group":"flags","keywords":["flag"]},{"id":":flag_gibraltar:","symbol":"🇬🇮","group":"flags","keywords":["flag"]},{"id":":flag_greenland:","symbol":"🇬🇱","group":"flags","keywords":["flag"]},{"id":":flag_gambia:","symbol":"🇬🇲","group":"flags","keywords":["flag"]},{"id":":flag_guinea:","symbol":"🇬🇳","group":"flags","keywords":["flag"]},{"id":":flag_guadeloupe:","symbol":"🇬🇵","group":"flags","keywords":["flag"]},{"id":":flag_equatorial_guinea:","symbol":"🇬🇶","group":"flags","keywords":["flag"]},{"id":":flag_greece:","symbol":"🇬🇷","group":"flags","keywords":["flag"]},{"id":":flag_south_georgia_&_south_sandwich_islands:","symbol":"🇬🇸","group":"flags","keywords":["flag"]},{"id":":flag_guatemala:","symbol":"🇬🇹","group":"flags","keywords":["flag"]},{"id":":flag_guam:","symbol":"🇬🇺","group":"flags","keywords":["flag"]},{"id":":flag_guinea-bissau:","symbol":"🇬🇼","group":"flags","keywords":["flag"]},{"id":":flag_guyana:","symbol":"🇬🇾","group":"flags","keywords":["flag"]},{"id":":flag_hong_kong_sar_china:","symbol":"🇭🇰","group":"flags","keywords":["flag"]},{"id":":flag_heard_&_mcdonald_islands:","symbol":"🇭🇲","group":"flags","keywords":["flag"]},{"id":":flag_honduras:","symbol":"🇭🇳","group":"flags","keywords":["flag"]},{"id":":flag_croatia:","symbol":"🇭🇷","group":"flags","keywords":["flag"]},{"id":":flag_haiti:","symbol":"🇭🇹","group":"flags","keywords":["flag"]},{"id":":flag_hungary:","symbol":"🇭🇺","group":"flags","keywords":["flag"]},{"id":":flag_canary_islands:","symbol":"🇮🇨","group":"flags","keywords":["flag"]},{"id":":flag_indonesia:","symbol":"🇮🇩","group":"flags","keywords":["flag"]},{"id":":flag_ireland:","symbol":"🇮🇪","group":"flags","keywords":["flag"]},{"id":":flag_israel:","symbol":"🇮🇱","group":"flags","keywords":["flag"]},{"id":":flag_isle_of_man:","symbol":"🇮🇲","group":"flags","keywords":["flag"]},{"id":":flag_india:","symbol":"🇮🇳","group":"flags","keywords":["flag"]},{"id":":flag_british_indian_ocean_territory:","symbol":"🇮🇴","group":"flags","keywords":["flag"]},{"id":":flag_iraq:","symbol":"🇮🇶","group":"flags","keywords":["flag"]},{"id":":flag_iran:","symbol":"🇮🇷","group":"flags","keywords":["flag"]},{"id":":flag_iceland:","symbol":"🇮🇸","group":"flags","keywords":["flag"]},{"id":":flag_italy:","symbol":"🇮🇹","group":"flags","keywords":["flag"]},{"id":":flag_jersey:","symbol":"🇯🇪","group":"flags","keywords":["flag"]},{"id":":flag_jamaica:","symbol":"🇯🇲","group":"flags","keywords":["flag"]},{"id":":flag_jordan:","symbol":"🇯🇴","group":"flags","keywords":["flag"]},{"id":":flag_japan:","symbol":"🇯🇵","group":"flags","keywords":["flag"]},{"id":":flag_kenya:","symbol":"🇰🇪","group":"flags","keywords":["flag"]},{"id":":flag_kyrgyzstan:","symbol":"🇰🇬","group":"flags","keywords":["flag"]},{"id":":flag_cambodia:","symbol":"🇰🇭","group":"flags","keywords":["flag"]},{"id":":flag_kiribati:","symbol":"🇰🇮","group":"flags","keywords":["flag"]},{"id":":flag_comoros:","symbol":"🇰🇲","group":"flags","keywords":["flag"]},{"id":":flag_st._kitts_&_nevis:","symbol":"🇰🇳","group":"flags","keywords":["flag"]},{"id":":flag_north_korea:","symbol":"🇰🇵","group":"flags","keywords":["flag"]},{"id":":flag_south_korea:","symbol":"🇰🇷","group":"flags","keywords":["flag"]},{"id":":flag_kuwait:","symbol":"🇰🇼","group":"flags","keywords":["flag"]},{"id":":flag_cayman_islands:","symbol":"🇰🇾","group":"flags","keywords":["flag"]},{"id":":flag_kazakhstan:","symbol":"🇰🇿","group":"flags","keywords":["flag"]},{"id":":flag_laos:","symbol":"🇱🇦","group":"flags","keywords":["flag"]},{"id":":flag_lebanon:","symbol":"🇱🇧","group":"flags","keywords":["flag"]},{"id":":flag_st._lucia:","symbol":"🇱🇨","group":"flags","keywords":["flag"]},{"id":":flag_liechtenstein:","symbol":"🇱🇮","group":"flags","keywords":["flag"]},{"id":":flag_sri_lanka:","symbol":"🇱🇰","group":"flags","keywords":["flag"]},{"id":":flag_liberia:","symbol":"🇱🇷","group":"flags","keywords":["flag"]},{"id":":flag_lesotho:","symbol":"🇱🇸","group":"flags","keywords":["flag"]},{"id":":flag_lithuania:","symbol":"🇱🇹","group":"flags","keywords":["flag"]},{"id":":flag_luxembourg:","symbol":"🇱🇺","group":"flags","keywords":["flag"]},{"id":":flag_latvia:","symbol":"🇱🇻","group":"flags","keywords":["flag"]},{"id":":flag_libya:","symbol":"🇱🇾","group":"flags","keywords":["flag"]},{"id":":flag_morocco:","symbol":"🇲🇦","group":"flags","keywords":["flag"]},{"id":":flag_monaco:","symbol":"🇲🇨","group":"flags","keywords":["flag"]},{"id":":flag_moldova:","symbol":"🇲🇩","group":"flags","keywords":["flag"]},{"id":":flag_montenegro:","symbol":"🇲🇪","group":"flags","keywords":["flag"]},{"id":":flag_st._martin:","symbol":"🇲🇫","group":"flags","keywords":["flag"]},{"id":":flag_madagascar:","symbol":"🇲🇬","group":"flags","keywords":["flag"]},{"id":":flag_marshall_islands:","symbol":"🇲🇭","group":"flags","keywords":["flag"]},{"id":":flag_macedonia:","symbol":"🇲🇰","group":"flags","keywords":["flag"]},{"id":":flag_mali:","symbol":"🇲🇱","group":"flags","keywords":["flag"]},{"id":":flag_myanmar_(burma):","symbol":"🇲🇲","group":"flags","keywords":["flag"]},{"id":":flag_mongolia:","symbol":"🇲🇳","group":"flags","keywords":["flag"]},{"id":":flag_macau_sar_china:","symbol":"🇲🇴","group":"flags","keywords":["flag"]},{"id":":flag_northern_mariana_islands:","symbol":"🇲🇵","group":"flags","keywords":["flag"]},{"id":":flag_martinique:","symbol":"🇲🇶","group":"flags","keywords":["flag"]},{"id":":flag_mauritania:","symbol":"🇲🇷","group":"flags","keywords":["flag"]},{"id":":flag_montserrat:","symbol":"🇲🇸","group":"flags","keywords":["flag"]},{"id":":flag_malta:","symbol":"🇲🇹","group":"flags","keywords":["flag"]},{"id":":flag_mauritius:","symbol":"🇲🇺","group":"flags","keywords":["flag"]},{"id":":flag_maldives:","symbol":"🇲🇻","group":"flags","keywords":["flag"]},{"id":":flag_malawi:","symbol":"🇲🇼","group":"flags","keywords":["flag"]},{"id":":flag_mexico:","symbol":"🇲🇽","group":"flags","keywords":["flag"]},{"id":":flag_malaysia:","symbol":"🇲🇾","group":"flags","keywords":["flag"]},{"id":":flag_mozambique:","symbol":"🇲🇿","group":"flags","keywords":["flag"]},{"id":":flag_namibia:","symbol":"🇳🇦","group":"flags","keywords":["flag"]},{"id":":flag_new_caledonia:","symbol":"🇳🇨","group":"flags","keywords":["flag"]},{"id":":flag_niger:","symbol":"🇳🇪","group":"flags","keywords":["flag"]},{"id":":flag_norfolk_island:","symbol":"🇳🇫","group":"flags","keywords":["flag"]},{"id":":flag_nigeria:","symbol":"🇳🇬","group":"flags","keywords":["flag"]},{"id":":flag_nicaragua:","symbol":"🇳🇮","group":"flags","keywords":["flag"]},{"id":":flag_netherlands:","symbol":"🇳🇱","group":"flags","keywords":["flag"]},{"id":":flag_norway:","symbol":"🇳🇴","group":"flags","keywords":["flag"]},{"id":":flag_nepal:","symbol":"🇳🇵","group":"flags","keywords":["flag"]},{"id":":flag_nauru:","symbol":"🇳🇷","group":"flags","keywords":["flag"]},{"id":":flag_niue:","symbol":"🇳🇺","group":"flags","keywords":["flag"]},{"id":":flag_new_zealand:","symbol":"🇳🇿","group":"flags","keywords":["flag"]},{"id":":flag_oman:","symbol":"🇴🇲","group":"flags","keywords":["flag"]},{"id":":flag_panama:","symbol":"🇵🇦","group":"flags","keywords":["flag"]},{"id":":flag_peru:","symbol":"🇵🇪","group":"flags","keywords":["flag"]},{"id":":flag_french_polynesia:","symbol":"🇵🇫","group":"flags","keywords":["flag"]},{"id":":flag_papua_new_guinea:","symbol":"🇵🇬","group":"flags","keywords":["flag"]},{"id":":flag_philippines:","symbol":"🇵🇭","group":"flags","keywords":["flag"]},{"id":":flag_pakistan:","symbol":"🇵🇰","group":"flags","keywords":["flag"]},{"id":":flag_poland:","symbol":"🇵🇱","group":"flags","keywords":["flag"]},{"id":":flag_st._pierre_&_miquelon:","symbol":"🇵🇲","group":"flags","keywords":["flag"]},{"id":":flag_pitcairn_islands:","symbol":"🇵🇳","group":"flags","keywords":["flag"]},{"id":":flag_puerto_rico:","symbol":"🇵🇷","group":"flags","keywords":["flag"]},{"id":":flag_palestinian_territories:","symbol":"🇵🇸","group":"flags","keywords":["flag"]},{"id":":flag_portugal:","symbol":"🇵🇹","group":"flags","keywords":["flag"]},{"id":":flag_palau:","symbol":"🇵🇼","group":"flags","keywords":["flag"]},{"id":":flag_paraguay:","symbol":"🇵🇾","group":"flags","keywords":["flag"]},{"id":":flag_qatar:","symbol":"🇶🇦","group":"flags","keywords":["flag"]},{"id":":flag_réunion:","symbol":"🇷🇪","group":"flags","keywords":["flag"]},{"id":":flag_romania:","symbol":"🇷🇴","group":"flags","keywords":["flag"]},{"id":":flag_serbia:","symbol":"🇷🇸","group":"flags","keywords":["flag"]},{"id":":flag_russia:","symbol":"🇷🇺","group":"flags","keywords":["flag"]},{"id":":flag_rwanda:","symbol":"🇷🇼","group":"flags","keywords":["flag"]},{"id":":flag_saudi_arabia:","symbol":"🇸🇦","group":"flags","keywords":["flag"]},{"id":":flag_solomon_islands:","symbol":"🇸🇧","group":"flags","keywords":["flag"]},{"id":":flag_seychelles:","symbol":"🇸🇨","group":"flags","keywords":["flag"]},{"id":":flag_sudan:","symbol":"🇸🇩","group":"flags","keywords":["flag"]},{"id":":flag_sweden:","symbol":"🇸🇪","group":"flags","keywords":["flag"]},{"id":":flag_singapore:","symbol":"🇸🇬","group":"flags","keywords":["flag"]},{"id":":flag_st._helena:","symbol":"🇸🇭","group":"flags","keywords":["flag"]},{"id":":flag_slovenia:","symbol":"🇸🇮","group":"flags","keywords":["flag"]},{"id":":flag_svalbard_&_jan_mayen:","symbol":"🇸🇯","group":"flags","keywords":["flag"]},{"id":":flag_slovakia:","symbol":"🇸🇰","group":"flags","keywords":["flag"]},{"id":":flag_sierra_leone:","symbol":"🇸🇱","group":"flags","keywords":["flag"]},{"id":":flag_san_marino:","symbol":"🇸🇲","group":"flags","keywords":["flag"]},{"id":":flag_senegal:","symbol":"🇸🇳","group":"flags","keywords":["flag"]},{"id":":flag_somalia:","symbol":"🇸🇴","group":"flags","keywords":["flag"]},{"id":":flag_suriname:","symbol":"🇸🇷","group":"flags","keywords":["flag"]},{"id":":flag_south_sudan:","symbol":"🇸🇸","group":"flags","keywords":["flag"]},{"id":":flag_são_tomé_&_príncipe:","symbol":"🇸🇹","group":"flags","keywords":["flag"]},{"id":":flag_el_salvador:","symbol":"🇸🇻","group":"flags","keywords":["flag"]},{"id":":flag_sint_maarten:","symbol":"🇸🇽","group":"flags","keywords":["flag"]},{"id":":flag_syria:","symbol":"🇸🇾","group":"flags","keywords":["flag"]},{"id":":flag_swaziland:","symbol":"🇸🇿","group":"flags","keywords":["flag"]},{"id":":flag_tristan_da_cunha:","symbol":"🇹🇦","group":"flags","keywords":["flag"]},{"id":":flag_turks_&_caicos_islands:","symbol":"🇹🇨","group":"flags","keywords":["flag"]},{"id":":flag_chad:","symbol":"🇹🇩","group":"flags","keywords":["flag"]},{"id":":flag_french_southern_territories:","symbol":"🇹🇫","group":"flags","keywords":["flag"]},{"id":":flag_togo:","symbol":"🇹🇬","group":"flags","keywords":["flag"]},{"id":":flag_thailand:","symbol":"🇹🇭","group":"flags","keywords":["flag"]},{"id":":flag_tajikistan:","symbol":"🇹🇯","group":"flags","keywords":["flag"]},{"id":":flag_tokelau:","symbol":"🇹🇰","group":"flags","keywords":["flag"]},{"id":":flag_timor-leste:","symbol":"🇹🇱","group":"flags","keywords":["flag"]},{"id":":flag_turkmenistan:","symbol":"🇹🇲","group":"flags","keywords":["flag"]},{"id":":flag_tunisia:","symbol":"🇹🇳","group":"flags","keywords":["flag"]},{"id":":flag_tonga:","symbol":"🇹🇴","group":"flags","keywords":["flag"]},{"id":":flag_turkey:","symbol":"🇹🇷","group":"flags","keywords":["flag"]},{"id":":flag_trinidad_&_tobago:","symbol":"🇹🇹","group":"flags","keywords":["flag"]},{"id":":flag_tuvalu:","symbol":"🇹🇻","group":"flags","keywords":["flag"]},{"id":":flag_taiwan:","symbol":"🇹🇼","group":"flags","keywords":["flag"]},{"id":":flag_tanzania:","symbol":"🇹🇿","group":"flags","keywords":["flag"]},{"id":":flag_ukraine:","symbol":"🇺🇦","group":"flags","keywords":["flag"]},{"id":":flag_uganda:","symbol":"🇺🇬","group":"flags","keywords":["flag"]},{"id":":flag_u.s._outlying_islands:","symbol":"🇺🇲","group":"flags","keywords":["flag"]},{"id":":flag_united_nations:","symbol":"🇺🇳","group":"flags","keywords":["flag"]},{"id":":flag_united_states:","symbol":"🇺🇸","group":"flags","keywords":["flag"]},{"id":":flag_uruguay:","symbol":"🇺🇾","group":"flags","keywords":["flag"]},{"id":":flag_uzbekistan:","symbol":"🇺🇿","group":"flags","keywords":["flag"]},{"id":":flag_vatican_city:","symbol":"🇻🇦","group":"flags","keywords":["flag"]},{"id":":flag_st._vincent_&_grenadines:","symbol":"🇻🇨","group":"flags","keywords":["flag"]},{"id":":flag_venezuela:","symbol":"🇻🇪","group":"flags","keywords":["flag"]},{"id":":flag_british_virgin_islands:","symbol":"🇻🇬","group":"flags","keywords":["flag"]},{"id":":flag_u.s._virgin_islands:","symbol":"🇻🇮","group":"flags","keywords":["flag"]},{"id":":flag_vietnam:","symbol":"🇻🇳","group":"flags","keywords":["flag"]},{"id":":flag_vanuatu:","symbol":"🇻🇺","group":"flags","keywords":["flag"]},{"id":":flag_wallis_&_futuna:","symbol":"🇼🇫","group":"flags","keywords":["flag"]},{"id":":flag_samoa:","symbol":"🇼🇸","group":"flags","keywords":["flag"]},{"id":":flag_kosovo:","symbol":"🇽🇰","group":"flags","keywords":["flag"]},{"id":":flag_yemen:","symbol":"🇾🇪","group":"flags","keywords":["flag"]},{"id":":flag_mayotte:","symbol":"🇾🇹","group":"flags","keywords":["flag"]},{"id":":flag_south_africa:","symbol":"🇿🇦","group":"flags","keywords":["flag"]},{"id":":flag_zambia:","symbol":"🇿🇲","group":"flags","keywords":["flag"]},{"id":":flag_zimbabwe:","symbol":"🇿🇼","group":"flags","keywords":["flag"]},{"id":":flag_england:","symbol":"🏴","group":"flags","keywords":["flag"]},{"id":":flag_scotland:","symbol":"🏴","group":"flags","keywords":["flag"]},{"id":":flag_wales:","symbol":"🏴","group":"flags","keywords":["flag"]}]
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/icons/emojipanel.png b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/icons/emojipanel.png
new file mode 100644
index 00000000000..b363553d942
Binary files /dev/null and b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/icons/emojipanel.png differ
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/icons/hidpi/emojipanel.png b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/icons/hidpi/emojipanel.png
new file mode 100644
index 00000000000..c8aefd019cf
Binary files /dev/null and b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/icons/hidpi/emojipanel.png differ
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/cs.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/cs.js
new file mode 100644
index 00000000000..eb005c053bd
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/cs.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'cs', {
+ searchPlaceholder: 'Vyhledat emodži...',
+ searchLabel: 'Vstupní pole používané pro vyhledávání a filtrování emodži v rámci panelu.',
+ navigationLabel: 'Procházení podle skupin emodži.',
+ title: 'Seznam emodži',
+ groups: {
+ people: 'Lidé',
+ nature: 'Příroda a zvířata',
+ food: 'Jídlo a pití',
+ travel: 'Cestování a místa',
+ activities: 'Činnosti',
+ objects: 'Objekty',
+ symbols: 'Symboly',
+ flags: 'Vlajky'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/da.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/da.js
new file mode 100644
index 00000000000..fd57b4d38af
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/da.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'da', {
+ searchPlaceholder: 'Søg emojier...',
+ searchLabel: 'Indtastningsfelt ansvarligt for udsøgning og filtrering af emojier i panelet.',
+ navigationLabel: 'Grupperer navigation for emojisektioner.',
+ title: 'Emoji-liste',
+ groups: {
+ people: 'Mennesker',
+ nature: 'Natur og dyr',
+ food: 'Mad og drikkelse',
+ travel: 'Rejser og steder',
+ activities: 'Aktiviteter',
+ objects: 'Objekter',
+ symbols: 'Symboler',
+ flags: 'Flag'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/de-ch.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/de-ch.js
new file mode 100644
index 00000000000..d249f1af788
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/de-ch.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'de-ch', {
+ searchPlaceholder: 'Emoji suchen…',
+ searchLabel: 'Das Eingabefeld ermöglicht die Suche nach Emojis.',
+ navigationLabel: 'Navigieren Sie zwischen den Emoji-Kategorien.',
+ title: 'Emoji-Liste',
+ groups: {
+ people: 'Personen',
+ nature: 'Natur und Tiere',
+ food: 'Essen und Getränke',
+ travel: 'Reisen und Orte',
+ activities: 'Aktivitäten',
+ objects: 'Objekte',
+ symbols: 'Symbole',
+ flags: 'Flaggen'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/de.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/de.js
new file mode 100644
index 00000000000..515e56d60a1
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/de.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'de', {
+ searchPlaceholder: 'Emoji suchen…',
+ searchLabel: 'Input field responsible for searching and filtering emoji inside panel.', // MISSING
+ navigationLabel: 'Groups navigation for emoji sections.', // MISSING
+ title: 'Emoji-Liste',
+ groups: {
+ people: 'Personen',
+ nature: 'Natur und Tiere',
+ food: 'Essen und Getränke',
+ travel: 'Reisen und Orte',
+ activities: 'Aktivitäten',
+ objects: 'Objekte',
+ symbols: 'Symbole',
+ flags: 'Flaggen'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/el.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/el.js
new file mode 100644
index 00000000000..6f8b1e741cb
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/el.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'el', {
+ searchPlaceholder: 'Αναζήτηση emoji…',
+ searchLabel: 'Το πεδίο εισαγωγής που ευθύνεται για την αναζήτηση και το φιλτράρισμα των emoji μέσα στον πίνακα.',
+ navigationLabel: 'Πλοήγηση ομάδων για τους τομείς των emoji.',
+ title: 'Λίστα emoji',
+ groups: {
+ people: 'Άνθρωποι',
+ nature: 'Φύση και ζώα',
+ food: 'Τρόφιμα και ποτά',
+ travel: 'Ταξίδια και τοποθεσίες',
+ activities: 'Δραστηριότητες',
+ objects: 'Αντικείμενα',
+ symbols: 'Σύμβολα',
+ flags: 'Σημαίες'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/en-au.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/en-au.js
new file mode 100644
index 00000000000..db3b886e3f3
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/en-au.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'en-au', {
+ searchPlaceholder: 'Search emoji…',
+ searchLabel: 'Input field responsible for searching and filtering emoji inside panel.',
+ navigationLabel: 'Groups navigation for emoji sections.',
+ title: 'Emoji List',
+ groups: {
+ people: 'People',
+ nature: 'Nature and animals',
+ food: 'Food and drinks',
+ travel: 'Travel and places',
+ activities: 'Activities',
+ objects: 'Objects',
+ symbols: 'Symbols',
+ flags: 'Flags'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/en.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/en.js
new file mode 100644
index 00000000000..18901faf0a3
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/en.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'en', {
+ searchPlaceholder: 'Search emoji…',
+ searchLabel: 'Input field responsible for searching and filtering emoji inside panel.',
+ navigationLabel: 'Groups navigation for emoji sections.',
+ title: 'Emoji List',
+ groups: {
+ people: 'People',
+ nature: 'Nature and animals',
+ food: 'Food and drinks',
+ travel: 'Travel and places',
+ activities: 'Activities',
+ objects: 'Objects',
+ symbols: 'Symbols',
+ flags: 'Flags'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/et.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/et.js
new file mode 100644
index 00000000000..94aa119dcb2
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/et.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'et', {
+ searchPlaceholder: 'Otsi emotikoni...',
+ searchLabel: 'Lahter, mille abil paneelil otsida ja filtreerida emotikone.',
+ navigationLabel: 'Emotikonide gruppide vahel liikumine.',
+ title: 'Emotikonide loend',
+ groups: {
+ people: 'Inimesed',
+ nature: 'Loodus ja loomad',
+ food: 'Toit ja joogid',
+ travel: 'Reisimine ja kohad',
+ activities: 'Tegevused',
+ objects: 'Asjad',
+ symbols: 'Sümbolid',
+ flags: 'Lipud'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/fa.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/fa.js
new file mode 100644
index 00000000000..f56fa463465
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/fa.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'fa', {
+ searchPlaceholder: 'جست و جوی ایموجی',
+ searchLabel: 'Input field responsible for searching and filtering emoji inside panel.', // MISSING
+ navigationLabel: 'Groups navigation for emoji sections.', // MISSING
+ title: 'لیست ایموجی',
+ groups: {
+ people: 'افراد',
+ nature: 'طبیعت و حیوانات',
+ food: 'غذا و نوشیدنی',
+ travel: 'سفر و اماکن',
+ activities: 'فعالیت ها',
+ objects: 'اشیا',
+ symbols: 'نمادها',
+ flags: 'پرچمها'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/fr.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/fr.js
new file mode 100644
index 00000000000..03d1847dc1d
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/fr.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'fr', {
+ searchPlaceholder: 'Chercher un émoticône',
+ searchLabel: 'Champ de saisie chargé de rechercher et de filtrer les émoticônes à l\'intérieur du panneau',
+ navigationLabel: 'Catégorisation des sections d\'émoticône',
+ title: 'Liste des émoticônes',
+ groups: {
+ people: 'Personnes',
+ nature: 'Nature et animal',
+ food: 'Nourriture et boisson',
+ travel: 'Lieu et voyage',
+ activities: 'Activités',
+ objects: 'Objets',
+ symbols: 'Symboles',
+ flags: 'Drapeaux'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/gl.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/gl.js
new file mode 100644
index 00000000000..df93530809d
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/gl.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'gl', {
+ searchPlaceholder: 'Buscar emoji…',
+ searchLabel: 'Campo de entrada encargado de buscar e filtrar os emojis no panel.',
+ navigationLabel: 'Navegación de grupos para seccións de emojis.',
+ title: 'Lista de emojis',
+ groups: {
+ people: 'Xente',
+ nature: 'Natureza e aimais',
+ food: 'Comida e bebida',
+ travel: 'Viaxes e lugares',
+ activities: 'Actividades',
+ objects: 'Obxectos',
+ symbols: 'Símbolos',
+ flags: 'Bandeiras'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/hr.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/hr.js
new file mode 100644
index 00000000000..291958a736a
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/hr.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'hr', {
+ searchPlaceholder: 'Traži emoji',
+ searchLabel: 'Polje za unos odgovorno za traženje i filtriranje emoji-a.',
+ navigationLabel: 'Navigacijska grupa za emoji sekcije',
+ title: 'Lista emoji-a',
+ groups: {
+ people: 'Ljudi',
+ nature: 'Priroda i životinje',
+ food: 'Hrana i pića',
+ travel: 'Putovanja i mjesta',
+ activities: 'Aktivnosti',
+ objects: 'Objekti',
+ symbols: 'Simboli',
+ flags: 'Zastave'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/hu.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/hu.js
new file mode 100644
index 00000000000..600a601bbd4
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/hu.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'hu', {
+ searchPlaceholder: 'Emoji keresése...',
+ searchLabel: 'A beviteli mezőben kereshetőek és szűrhetőek az emojik a panelban.',
+ navigationLabel: 'Csoport navigáció az emoji szekciókhoz.',
+ title: 'Emoji lista',
+ groups: {
+ people: 'Emberek',
+ nature: 'Természet és állatok',
+ food: 'Ételek és italok',
+ travel: 'Utazás és helyek',
+ activities: 'Tevékenységek',
+ objects: 'Tárgyak',
+ symbols: 'Szimbólumok',
+ flags: 'Zászlók'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/it.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/it.js
new file mode 100644
index 00000000000..12efdb48a8a
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/it.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'it', {
+ searchPlaceholder: 'Cerca emoji…',
+ searchLabel: 'Campo di input responsabile di ricerca e filtro delle emoji all\'interno del pannello.',
+ navigationLabel: 'Navigazione tra gruppi per le sezioni di emoji.',
+ title: 'Elenco emoji',
+ groups: {
+ people: 'Persone',
+ nature: 'Natura e animali',
+ food: 'Cibo e bevande',
+ travel: 'Viaggio e luoghi',
+ activities: 'Attività',
+ objects: 'Oggetti',
+ symbols: 'Simboli',
+ flags: 'Bandiere'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/nl.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/nl.js
new file mode 100644
index 00000000000..7c96170230a
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/nl.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'nl', {
+ searchPlaceholder: 'Zoek emoji…',
+ searchLabel: 'Invoerveld verantwoordelijk voor het zoeken en filteren van emoji\'s in het paneel.',
+ navigationLabel: 'Groepsnavigatie voor emoji-secties.',
+ title: 'Emoji Lijst',
+ groups: {
+ people: 'Mensen',
+ nature: 'Natuur en dieren',
+ food: 'Eten en drinken',
+ travel: 'Reizen en plaatsen',
+ activities: 'Activiteiten',
+ objects: 'Objecten',
+ symbols: 'Symbolen',
+ flags: 'Vlaggen'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/pl.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/pl.js
new file mode 100644
index 00000000000..ddd81633907
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/pl.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'pl', {
+ searchPlaceholder: 'Wyszukaj emoji...',
+ searchLabel: 'Pole odpowiedzialne za wyszukiwanie i filtrowanie emoji wewnątrz panelu.',
+ navigationLabel: 'Groups navigation for emoji sections.', // MISSING
+ title: 'Lista emoji',
+ groups: {
+ people: 'Ludzie',
+ nature: 'Natura i zwierzęta',
+ food: 'Jedzenie i picie',
+ travel: 'Podróże i miejsca',
+ activities: 'Aktywności',
+ objects: 'Obiekty',
+ symbols: 'Symbole',
+ flags: 'Flagi'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/pt-br.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/pt-br.js
new file mode 100644
index 00000000000..ab9480ab92a
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/pt-br.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'pt-br', {
+ searchPlaceholder: 'Buscar emoji...',
+ searchLabel: 'Campo de texto responsável por buscar e filtrar emojis dentro do painel.',
+ navigationLabel: 'Agrupa a navegação das categorias de emojis.',
+ title: 'Lista de Emojis',
+ groups: {
+ people: 'Pessoas',
+ nature: 'Natureza e animais',
+ food: 'Comidas e bebidas',
+ travel: 'Viagem e Lugares',
+ activities: 'Atividades',
+ objects: 'Objetos',
+ symbols: 'Símbolos',
+ flags: 'Bandeiras'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sk.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sk.js
new file mode 100644
index 00000000000..33e9dfe483d
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sk.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'sk', {
+ searchPlaceholder: 'Nájsť emodži...',
+ searchLabel: 'Vstupné pole na hľadanie a filtrovanie emodži v paneli.',
+ navigationLabel: 'Emodži podľa skupín.',
+ title: 'Zoznam emodži',
+ groups: {
+ people: 'Ľudia',
+ nature: 'Príroda a zvieratá',
+ food: 'Jedlá a nápoje',
+ travel: 'Cestovanie a miesta',
+ activities: 'Činnosti',
+ objects: 'Objekty',
+ symbols: 'Symboly',
+ flags: 'Vlajky'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sr-latn.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sr-latn.js
new file mode 100644
index 00000000000..99be247b1d5
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sr-latn.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'sr-latn', {
+ searchPlaceholder: 'Traži emoji...',
+ searchLabel: 'Polje za pretraživanje i filtriranje emojia u panelu.',
+ navigationLabel: 'Grupna navigacija za emoji sekcije',
+ title: 'Lista emojia',
+ groups: {
+ people: 'Lljudi',
+ nature: 'Priroda i životinje',
+ food: 'Hrana i piće',
+ travel: 'Putovanja i mesta',
+ activities: 'Aktivnosti',
+ objects: 'Predmeti',
+ symbols: 'Simboli',
+ flags: 'Zastave'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sr.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sr.js
new file mode 100644
index 00000000000..33a9c873db7
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sr.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'sr', {
+ searchPlaceholder: 'Tражи емоји..',
+ searchLabel: 'Поље за претраживање и филтрирање емојиа у панелу.',
+ navigationLabel: 'Групна навигација за емоји секције',
+ title: 'Листа емојиа',
+ groups: {
+ people: 'Људи',
+ nature: 'Природа и животиње',
+ food: 'Храна и пиће',
+ travel: 'Путовања и места',
+ activities: 'Активности',
+ objects: 'Предмети',
+ symbols: 'Симболи',
+ flags: 'Заставе'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sv.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sv.js
new file mode 100644
index 00000000000..507c35b6c62
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/sv.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'sv', {
+ searchPlaceholder: 'Sök emoji...',
+ searchLabel: 'Input field responsible for searching and filtering emoji inside panel.', // MISSING
+ navigationLabel: 'Groups navigation for emoji sections.', // MISSING
+ title: 'Emojilista',
+ groups: {
+ people: 'Människor',
+ nature: 'Natur och djur',
+ food: 'Mat och dryck',
+ travel: 'Resor och platser',
+ activities: 'Aktiviteter',
+ objects: 'Objekt',
+ symbols: 'Symboler',
+ flags: 'Flaggor'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/tr.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/tr.js
new file mode 100644
index 00000000000..78da1d8f305
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/tr.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'tr', {
+ searchPlaceholder: 'Emoji ara...',
+ searchLabel: 'Panel içindeki emojiyi aramaya ve filtrelemeye ait giriş alanı.',
+ navigationLabel: 'Emoji bölümleri için gezinti grupları',
+ title: 'Emoji Listesi',
+ groups: {
+ people: 'İnsanlar',
+ nature: 'Doğa ve hayvanlar',
+ food: 'Yiyecek ve içecekler',
+ travel: 'Seyahat ve mekanlar',
+ activities: 'Faaliyetler',
+ objects: 'Nesneler',
+ symbols: 'Semboller',
+ flags: 'Bayraklar'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/uk.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/uk.js
new file mode 100644
index 00000000000..d09b2d63981
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/uk.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'uk', {
+ searchPlaceholder: 'Пошук емодзі…',
+ searchLabel: 'Поле введення, відповідальне за пошук та фільтрацію емодзі всередині панелі.',
+ navigationLabel: 'Групова навігація по розділах емодзі.',
+ title: 'Список емодзі',
+ groups: {
+ people: 'Люди',
+ nature: 'Природа та тварини',
+ food: 'Їжа та напої',
+ travel: 'Подорожі та місця',
+ activities: 'Діяльність',
+ objects: 'Об\'єкти',
+ symbols: 'Символи',
+ flags: 'Прапори'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/zh-cn.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/zh-cn.js
new file mode 100644
index 00000000000..28311fb16af
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/zh-cn.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'zh-cn', {
+ searchPlaceholder: '搜索表情...',
+ searchLabel: 'Input field responsible for searching and filtering emoji inside panel.', // MISSING
+ navigationLabel: 'Groups navigation for emoji sections.', // MISSING
+ title: '表情清单',
+ groups: {
+ people: '笑脸和人物',
+ nature: '自然和动物',
+ food: '食物和饮料',
+ travel: '旅游和地方',
+ activities: '活动和行为',
+ objects: '物品和对象',
+ symbols: '符号和标志',
+ flags: '国旗和旗帜'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/zh.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/zh.js
new file mode 100644
index 00000000000..82bff9b6986
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/lang/zh.js
@@ -0,0 +1,20 @@
+/*
+Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+*/
+CKEDITOR.plugins.setLang( 'emoji', 'zh', {
+ searchPlaceholder: '搜尋顏文字……',
+ searchLabel: '輸入欄位負責在面板中搜尋並過濾顏文字。',
+ navigationLabel: '表情符號部份的群組導航。',
+ title: '顏文字清單',
+ groups: {
+ people: '人們',
+ nature: '自然與動物',
+ food: '飲食',
+ travel: '旅行與地點',
+ activities: '活動',
+ objects: '物件',
+ symbols: '符號',
+ flags: '旗幟'
+ }
+} );
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/plugin.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/plugin.js
new file mode 100644
index 00000000000..28271ffd527
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/plugin.js
@@ -0,0 +1,750 @@
+/**
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+ * CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+ */
+
+( function() {
+ 'use strict';
+
+ var stylesLoaded = false,
+ arrTools = CKEDITOR.tools.array,
+ htmlEncode = CKEDITOR.tools.htmlEncodeAttr,
+ EmojiDropdown = CKEDITOR.tools.createClass( {
+ $: function( editor, plugin ) {
+ var lang = this.lang = editor.lang.emoji,
+ self = this,
+ ICON_SIZE = 21;
+
+ this.listeners = [];
+ this.plugin = plugin;
+ this.editor = editor;
+ this.groups = [
+ {
+ name: 'people',
+ sectionName: lang.groups.people,
+ svgId: 'cke4-icon-emoji-2',
+ position: {
+ x: -1 * ICON_SIZE,
+ y: 0
+ },
+ items: []
+ },
+ {
+ name: 'nature',
+ sectionName: lang.groups.nature,
+ svgId: 'cke4-icon-emoji-3',
+ position: {
+ x: -2 * ICON_SIZE,
+ y: 0
+ },
+ items: []
+ },
+ {
+ name: 'food',
+ sectionName: lang.groups.food,
+ svgId: 'cke4-icon-emoji-4',
+ position: {
+ x: -3 * ICON_SIZE,
+ y: 0
+ },
+ items: []
+ },
+ {
+ name: 'travel',
+ sectionName: lang.groups.travel,
+ svgId: 'cke4-icon-emoji-6',
+ position: {
+ x: -2 * ICON_SIZE,
+ y: -1 * ICON_SIZE
+ },
+ items: []
+ },
+ {
+ name: 'activities',
+ sectionName: lang.groups.activities,
+ svgId: 'cke4-icon-emoji-5',
+ position: {
+ x: -4 * ICON_SIZE,
+ y: 0
+ },
+ items: []
+ },
+ {
+ name: 'objects',
+ sectionName: lang.groups.objects,
+ svgId: 'cke4-icon-emoji-7',
+ position: {
+ x: 0,
+ y: -1 * ICON_SIZE
+ },
+ items: []
+ },
+ {
+ name: 'symbols',
+ sectionName: lang.groups.symbols,
+ svgId: 'cke4-icon-emoji-8',
+ position: {
+ x: -1 * ICON_SIZE,
+ y: -1 * ICON_SIZE
+ },
+ items: []
+ },
+ {
+ name: 'flags',
+ sectionName: lang.groups.flags,
+ svgId: 'cke4-icon-emoji-9',
+ position: {
+ x: -3 * ICON_SIZE,
+ y: -1 * ICON_SIZE
+ },
+ items: []
+ }
+ ];
+
+ // Keeps html elements references to not find them again.
+ this.elements = {};
+
+ // Below line might be removable
+ editor.ui.addToolbarGroup( 'emoji', 'insert' );
+ // Name is responsible for icon name also.
+ editor.ui.add( 'EmojiPanel', CKEDITOR.UI_PANELBUTTON, {
+ label: 'emoji',
+ title: lang.title,
+ modes: { wysiwyg: 1 },
+ editorFocus: 0,
+ toolbar: 'insert',
+ panel: {
+ css: [
+ CKEDITOR.skin.getPath( 'editor' ),
+ plugin.path + 'skins/default.css'
+ ],
+ attributes: {
+ role: 'listbox',
+ 'aria-label': lang.title
+ },
+ markFirst: false
+ },
+
+ onBlock: function( panel, block ) {
+ var keys = block.keys,
+ rtl = editor.lang.dir === 'rtl';
+
+ keys[ rtl ? 37 : 39 ] = 'next'; // ARROW-RIGHT
+ keys[ 40 ] = 'next'; // ARROW-DOWN
+ keys[ 9 ] = 'next'; // TAB
+ keys[ rtl ? 39 : 37 ] = 'prev'; // ARROW-LEFT
+ keys[ 38 ] = 'prev'; // ARROW-UP
+ keys[ CKEDITOR.SHIFT + 9 ] = 'prev'; // SHIFT + TAB
+ keys[ 32 ] = 'click'; // SPACE
+
+ self.blockElement = block.element;
+ self.emojiList = self.editor._.emoji.list;
+
+ self.addEmojiToGroups();
+
+ block.element.getAscendant( 'html' ).addClass( 'cke_emoji' );
+ block.element.getDocument().appendStyleSheet( CKEDITOR.getUrl( CKEDITOR.basePath + 'contents.css' ) );
+ block.element.addClass( 'cke_emoji-panel_block' );
+ block.element.setHtml( self.createEmojiBlock() );
+ block.element.removeAttribute( 'title' );
+ panel.element.addClass( 'cke_emoji-panel' );
+
+ self.items = block._.getItems();
+
+ self.blockObject = block;
+ self.elements.emojiItems = block.element.find( '.cke_emoji-outer_emoji_block li > a' );
+ self.elements.sectionHeaders = block.element.find( '.cke_emoji-outer_emoji_block h2' );
+ self.elements.input = block.element.findOne( 'input' );
+ self.inputIndex = self.getItemIndex( self.items, self.elements.input );
+ self.elements.emojiBlock = block.element.findOne( '.cke_emoji-outer_emoji_block' );
+ self.elements.navigationItems = block.element.find( 'nav li' );
+ self.elements.statusIcon = block.element.findOne( '.cke_emoji-status_icon' );
+ self.elements.statusDescription = block.element.findOne( 'p.cke_emoji-status_description' );
+ self.elements.statusName = block.element.findOne( 'p.cke_emoji-status_full_name' );
+ self.elements.sections = block.element.find( 'section' );
+ self.registerListeners();
+
+ },
+
+ onOpen: self.openReset()
+ } );
+ },
+ proto: {
+ registerListeners: function() {
+ arrTools.forEach( this.listeners, function( item ) {
+ var root = this.blockElement,
+ selector = item.selector,
+ listener = item.listener,
+ event = item.event,
+ ctx = item.ctx || this;
+
+ arrTools.forEach( root.find( selector ).toArray(), function( node ) {
+ node.on( event, listener, ctx );
+ } );
+ }, this );
+ },
+ createEmojiBlock: function() {
+ var output = [];
+
+ // (#2607)
+ this.loadSVGNavigationIcons();
+
+ output.push( this.createGroupsNavigation() );
+ output.push( this.createSearchSection() );
+ output.push( this.createEmojiListBlock() );
+ output.push( this.createStatusBar() );
+
+ return '' + output.join( '' ) + '
';
+ },
+ createGroupsNavigation: function() {
+ var itemTemplate,
+ items,
+ imgUrl,
+ useAttr;
+
+ if ( !this.editor.plugins.emoji.isSVGSupported() ) {
+ imgUrl = CKEDITOR.getUrl( this.plugin.path + 'assets/iconsall.png' );
+
+ itemTemplate = new CKEDITOR.template(
+ '' +
+ '' +
+ ' ' +
+ ' '
+ );
+
+ items = arrTools.reduce( this.groups, function( acc, item ) {
+ if ( !item.items.length ) {
+ return acc;
+ } else {
+ return acc + itemTemplate.output( {
+ group: htmlEncode( item.name ),
+ name: htmlEncode( item.sectionName ),
+ positionX: item.position.x,
+ positionY: item.position.y
+ } );
+ }
+ }, '' );
+ } else {
+ // iOS has problem with reading `href` attribute, that's why,
+ // its necessary to use `xlink:href` even its deprecated: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
+ useAttr = CKEDITOR.env.safari ? 'xlink:href="#{svgId}"' : 'href="#{svgId}"';
+
+ itemTemplate = new CKEDITOR.template(
+ '' +
+ '' +
+ '{name} '
+ );
+
+ items = arrTools.reduce( this.groups, function( acc, item ) {
+ if ( !item.items.length ) {
+ return acc;
+ } else {
+ return acc + itemTemplate.output( {
+ group: htmlEncode( item.name ),
+ name: htmlEncode( item.sectionName ),
+ svgId: htmlEncode( item.svgId ),
+ translateX: item.translate && item.translate.x ? htmlEncode( item.translate.x ) : 0,
+ translateY: item.translate && item.translate.y ? htmlEncode( item.translate.y ) : 0
+ } );
+ }
+ }, '' );
+ }
+
+ this.listeners.push( {
+ selector: 'nav',
+ event: 'click',
+ listener: function( event ) {
+ var activeElement = event.data.getTarget().getAscendant( 'li', true );
+ if ( !activeElement ) {
+ return;
+ }
+ arrTools.forEach( this.elements.navigationItems.toArray(), function( node ) {
+ if ( node.equals( activeElement ) ) {
+ node.addClass( 'active' );
+ } else {
+ node.removeClass( 'active' );
+ }
+ } );
+
+ this.clearSearchAndMoveFocus( activeElement );
+
+ event.data.preventDefault();
+ }
+ } );
+
+ return ' ';
+ },
+ createSearchSection: function() {
+ var self = this;
+
+ this.listeners.push( {
+ selector: 'input',
+ event: 'input',
+ listener: ( function() {
+ var buffer = CKEDITOR.tools.throttle( 200, self.filter, self );
+ return buffer.input;
+ } )()
+ } );
+ this.listeners.push( {
+ selector: 'input',
+ event: 'click',
+ listener: function() {
+ this.blockObject._.markItem( this.inputIndex );
+ }
+ } );
+ return '' + this.getLoupeIcon() +
+ ' ';
+ },
+ createEmojiListBlock: function() {
+ var self = this;
+ this.listeners.push( {
+ selector: '.cke_emoji-outer_emoji_block',
+ event: 'scroll',
+ listener: ( function() {
+ var buffer = CKEDITOR.tools.throttle( 150, self.refreshNavigationStatus, self );
+ return buffer.input;
+ } )()
+ } );
+
+ this.listeners.push( {
+ selector: '.cke_emoji-outer_emoji_block',
+ event: 'click',
+ listener: function( event ) {
+ if ( event.data.getTarget().data( 'cke-emoji-name' ) ) {
+ this.editor.execCommand( 'insertEmoji', { emojiText: event.data.getTarget().data( 'cke-emoji-symbol' ) } );
+ }
+ }
+ } );
+
+ this.listeners.push( {
+ selector: '.cke_emoji-outer_emoji_block',
+ event: 'mouseover',
+ listener: function( event ) {
+ this.updateStatusbar( event.data.getTarget() );
+ }
+ } );
+
+ this.listeners.push( {
+ selector: '.cke_emoji-outer_emoji_block',
+ event: 'keyup',
+ listener: function() {
+ this.updateStatusbar( this.items.getItem( this.blockObject._.focusIndex ) );
+ }
+ } );
+
+ return '' + this.getEmojiSections() + '
';
+ },
+ createStatusBar: function() {
+ return '';
+ },
+ getLoupeIcon: function() {
+ var loupePngUrl = CKEDITOR.getUrl( this.plugin.path + 'assets/iconsall.png' ),
+ useAttr;
+
+ if ( !this.editor.plugins.emoji.isSVGSupported() ) {
+ return ' ';
+ } else {
+ useAttr = CKEDITOR.env.safari ? 'xlink:href="#cke4-icon-emoji-10"' : 'href="#cke4-icon-emoji-10"';
+ return ' ';
+ }
+ },
+ getEmojiSections: function() {
+ return arrTools.reduce( this.groups, function( acc, item ) {
+ // If group is empty skip it.
+ if ( !item.items.length ) {
+ return acc;
+ } else {
+ return acc + this.getEmojiSection( item );
+ }
+ }, '', this );
+ },
+ getEmojiSection: function( item ) {
+ var groupName = htmlEncode( item.name ),
+ sectionName = htmlEncode( item.sectionName ),
+ group = this.getEmojiListGroup( item.items );
+
+ return '';
+ },
+ getEmojiListGroup: function( items ) {
+ var emojiTpl = new CKEDITOR.template( '' +
+ '{symbol} ' +
+ ' ' );
+
+ return arrTools.reduce(
+ items,
+ function( acc, item ) {
+ addEncodedName( item );
+ return acc + emojiTpl.output( {
+ symbol: htmlEncode( item.symbol ),
+ id: htmlEncode( item.id ),
+ name: item.name,
+ group: htmlEncode( item.group ),
+ keywords: htmlEncode( ( item.keywords || [] ).join( ',' ) )
+ } );
+ },
+ '',
+ this
+ );
+ },
+ filter: function( evt ) {
+ // Apply filters to emoji items in dropdown.
+ // Hiding not searched one.
+ // Can accept input event or string
+ var groups = {},
+ query = typeof evt === 'string' ? evt : evt.sender.getValue();
+
+ arrTools.forEach( this.elements.emojiItems.toArray(), function( element ) {
+ if ( isNameOrKeywords( query, element.data( 'cke-emoji-name' ), element.data( 'cke-emoji-keywords' ) ) || query === '' ) {
+ element.removeClass( 'hidden' );
+ element.getParent().removeClass( 'hidden' );
+ groups[ element.data( 'cke-emoji-group' ) ] = true;
+ } else {
+ element.addClass( 'hidden' );
+ element.getParent().addClass( 'hidden' );
+ }
+
+ function isNameOrKeywords( query, name, keywordsString ) {
+ var keywords,
+ i;
+ if ( name.indexOf( query ) !== -1 ) {
+ return true;
+ }
+ if ( keywordsString ) {
+ keywords = keywordsString.split( ',' );
+ for ( i = 0; i < keywords.length; i++ ) {
+ if ( keywords[ i ].indexOf( query ) !== -1 ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ } );
+
+ arrTools.forEach( this.elements.sectionHeaders.toArray(), function( element ) {
+ if ( groups[ element.getId() ] ) {
+ element.getParent().removeClass( 'hidden' );
+ element.removeClass( 'hidden' );
+ } else {
+ element.addClass( 'hidden' );
+ element.getParent().addClass( 'hidden' );
+ }
+ } );
+
+ this.refreshNavigationStatus();
+ },
+ clearSearchInput: function() {
+ this.elements.input.setValue( '' );
+ this.filter( '' );
+ },
+ openReset: function() {
+ // Resets state of emoji dropdown.
+ // Clear filters, reset focus, etc.
+ var self = this,
+ firstCall;
+
+ return function() {
+
+ if ( !firstCall ) {
+ self.filter( '' );
+ firstCall = true;
+ }
+
+ self.elements.emojiBlock.$.scrollTop = 0;
+ self.refreshNavigationStatus();
+
+ // Clear search results:
+ self.clearSearchInput();
+
+ // Reset focus:
+ CKEDITOR.tools.setTimeout( function() {
+ self.elements.input.focus( true );
+ self.blockObject._.markItem( self.inputIndex );
+ }, 0, self );
+
+ // Remove statusbar icons:
+ self.clearStatusbar();
+ };
+ },
+ refreshNavigationStatus: function() {
+ var containerOffset = this.elements.emojiBlock.getClientRect().top,
+ section,
+ groupName;
+
+ section = arrTools.filter( this.elements.sections.toArray(), function( element ) {
+ var rect = element.getClientRect();
+ if ( !rect.height || element.findOne( 'h2' ).hasClass( 'hidden' ) ) {
+ return false;
+ }
+ return rect.height + rect.top > containerOffset;
+ } );
+ groupName = section.length ? section[ 0 ].data( 'cke-emoji-group' ) : false;
+
+ arrTools.forEach( this.elements.navigationItems.toArray(), function( node ) {
+ if ( node.data( 'cke-emoji-group' ) === groupName ) {
+ node.addClass( 'active' );
+ } else {
+ node.removeClass( 'active' );
+ }
+ } );
+ },
+ updateStatusbar: function( element ) {
+ if ( element.getName() !== 'a' || !element.hasAttribute( 'data-cke-emoji-name' ) ) {
+ return;
+ }
+
+ this.elements.statusIcon.setText( htmlEncode( element.getText() ) );
+ this.elements.statusDescription.setText( htmlEncode( element.data( 'cke-emoji-name' ) ) );
+ this.elements.statusName.setText( htmlEncode( element.data( 'cke-emoji-full-name' ) ) );
+ },
+ clearStatusbar: function() {
+ this.elements.statusIcon.setText( '' );
+ this.elements.statusDescription.setText( '' );
+ this.elements.statusName.setText( '' );
+ },
+ clearSearchAndMoveFocus: function( activeElement ) {
+ this.clearSearchInput();
+ this.moveFocus( activeElement.data( 'cke-emoji-group' ) );
+ },
+ moveFocus: function( groupName ) {
+ var firstSectionItem = this.blockElement.findOne( 'a[data-cke-emoji-group="' + htmlEncode( groupName ) + '"]' ),
+ itemIndex;
+
+ if ( !firstSectionItem ) {
+ return;
+ }
+
+ itemIndex = this.getItemIndex( this.items, firstSectionItem );
+ firstSectionItem.focus( true );
+ firstSectionItem.getAscendant( 'section' ).getFirst().scrollIntoView( true );
+ this.blockObject._.markItem( itemIndex );
+ },
+ getItemIndex: function( nodeList, item ) {
+ return arrTools.indexOf( nodeList.toArray(), function( element ) {
+ return element.equals( item );
+ } );
+ },
+
+ // To avoid CORS issues due to XML-based SVG icons, they should be loaded into the panel document.
+ // This method ensures that the icons are loaded locally.
+ loadSVGNavigationIcons: function() {
+ if ( !this.editor.plugins.emoji.isSVGSupported() ) {
+ return;
+ }
+
+ var doc = this.blockElement.getDocument();
+
+ CKEDITOR.ajax.load( CKEDITOR.getUrl( this.plugin.path + 'assets/iconsall.svg' ), function( html ) {
+ var container = new CKEDITOR.dom.element( 'div' );
+
+ container.addClass( 'cke_emoji-navigation_icons' );
+ container.setHtml( html );
+
+ doc.getBody().append( container );
+ } );
+ },
+
+ addEmojiToGroups: function() {
+ var groupObj = {};
+ arrTools.forEach( this.groups, function( group ) {
+ groupObj[ group.name ] = group.items;
+ }, this );
+
+ arrTools.forEach( this.emojiList, function( emojiObj ) {
+ groupObj[ emojiObj.group ].push( emojiObj );
+ }, this );
+ }
+ }
+ } );
+
+
+ CKEDITOR.plugins.add( 'emoji', {
+ requires: 'autocomplete,textmatch,ajax,panelbutton,floatpanel',
+ lang: 'cs,da,de,de-ch,el,en,en-au,et,fa,fr,gl,hr,hu,it,nl,pl,pt-br,sk,sr,sr-latn,sv,tr,uk,zh,zh-cn', // %REMOVE_LINE_CORE%
+ icons: 'emojipanel',
+ hidpi: true,
+
+ isSupportedEnvironment: function() {
+ return !CKEDITOR.env.ie || CKEDITOR.env.version >= 11;
+ },
+
+ beforeInit: function() {
+ if ( !this.isSupportedEnvironment() ) {
+ return;
+ }
+ if ( !stylesLoaded ) {
+ CKEDITOR.document.appendStyleSheet( this.path + 'skins/default.css' );
+ stylesLoaded = true;
+ }
+ },
+
+ init: function( editor ) {
+ if ( !this.isSupportedEnvironment() ) {
+ return;
+ }
+
+ var emojiListUrl = editor.config.emoji_emojiListUrl || 'plugins/emoji/emoji.json',
+ arrTools = CKEDITOR.tools.array;
+
+
+ CKEDITOR.ajax.load( CKEDITOR.getUrl( emojiListUrl ), function( data ) {
+ if ( data === null ) {
+ return;
+ }
+ if ( editor._.emoji === undefined ) {
+ editor._.emoji = {};
+ }
+
+ if ( editor._.emoji.list === undefined ) {
+ editor._.emoji.list = JSON.parse( data );
+ }
+
+ var emojiList = editor._.emoji.list,
+ charactersToStart = editor.config.emoji_minChars === undefined ? 2 : editor.config.emoji_minChars;
+
+ if ( editor.status !== 'ready' ) {
+ editor.once( 'instanceReady', initPlugin );
+ } else {
+ initPlugin();
+ }
+
+ // HELPER FUNCTIONS:
+
+ function initPlugin() {
+ editor._.emoji.autocomplete = new CKEDITOR.plugins.autocomplete( editor, {
+ textTestCallback: getTextTestCallback(),
+ dataCallback: dataCallback,
+ itemTemplate: '{symbol} {name} ',
+ outputTemplate: '{symbol}',
+ followingSpace: editor.config.emoji_followingSpace
+ } );
+ }
+
+ function getTextTestCallback() {
+ return function( range ) {
+ if ( !range.collapsed ) {
+ return null;
+ }
+ return CKEDITOR.plugins.textMatch.match( range, matchCallback );
+ };
+ }
+
+ function matchCallback( text, offset ) {
+ var left = text.slice( 0, offset ),
+ // Emoji should be started with space or newline, but space shouldn't leak to output, hence it is in non captured group (#2195).
+ match = left.match( new RegExp( '(?:\\s\|^)(:\\S{' + charactersToStart + '}\\S*)$' ) );
+
+ if ( !match ) {
+ return null;
+ }
+
+ // In case of space preceding colon we need to return the last index (#2394) of capturing group.
+ return { start: left.lastIndexOf( match[ 1 ] ), end: offset };
+ }
+
+ function dataCallback( matchInfo, callback ) {
+ var emojiName = matchInfo.query.substr( 1 ).toLowerCase(),
+ data = arrTools.filter( emojiList, function( item ) {
+ // Comparing lowercase strings, because emoji should be case insensitive (#2167).
+ return item.id.toLowerCase().indexOf( emojiName ) !== -1;
+ } ).sort( function( a, b ) {
+ var aStartsWithEmojiName = !a.id.substr( 1 ).indexOf( emojiName ),
+ bStartsWithEmojiName = !b.id.substr( 1 ).indexOf( emojiName );
+
+ if ( aStartsWithEmojiName != bStartsWithEmojiName ) {
+ return aStartsWithEmojiName ? -1 : 1;
+ } else {
+ return a.id > b.id ? 1 : -1;
+ }
+ } );
+ data = arrTools.map( data, addEncodedName );
+ callback( data );
+ }
+ } );
+
+ editor.addCommand( 'insertEmoji', {
+ exec: function( editor, data ) {
+ editor.insertHtml( data.emojiText );
+ }
+ } );
+
+ if ( editor.plugins.toolbar ) {
+ new EmojiDropdown( editor, this );
+ }
+
+ },
+
+ isSVGSupported: function() {
+ return !CKEDITOR.env.ie || CKEDITOR.env.edge;
+ }
+ } );
+
+ function addEncodedName( item ) {
+ if ( !item.name ) {
+ item.name = htmlEncode( item.id.replace( /::.*$/, ':' ).replace( /^:|:$/g, '' ) );
+ }
+ return item;
+ }
+} )();
+
+/**
+ * A number that defines how many characters are required to start displaying emoji's autocomplete suggestion box.
+ * Delimiter `:`, which activates the emoji suggestion box, is not included in this value.
+ *
+ * ```js
+ * editor.emoji_minChars = 0; // Emoji suggestion box appears after typing ':'.
+ * ```
+ *
+ * @since 4.10.0
+ * @cfg {Number} [emoji_minChars=2]
+ * @member CKEDITOR.config
+ */
+
+/**
+ * Address of the JSON file containing the emoji list. The file is downloaded through the {@link CKEDITOR.ajax#load} method
+ * and the URL address is processed by {@link CKEDITOR#getUrl}.
+ * Emoji list has to be an array of objects with the `id` and `symbol` properties. These keys represent the text to match and the
+ * UTF symbol for its replacement.
+ * An emoji has to start with the `:` (colon) symbol.
+ *
+ * ```json
+ * [
+ * {
+ * "id": ":grinning_face:",
+ * "symbol":"😀"
+ * },
+ * {
+ * "id": ":bug:",
+ * "symbol":"🐛"
+ * },
+ * {
+ * "id": ":star:",
+ * "symbol":"⭐"
+ * }
+ * ]
+ * ```
+ *
+ * ```js
+ * editor.emoji_emojiListUrl = 'https://my.custom.domain/ckeditor/emoji.json';
+ * ```
+ *
+ * @since 4.10.0
+ * @cfg {String} [emoji_emojiListUrl='plugins/emoji/emoji.json']
+ * @member CKEDITOR.config
+ */
+
+/**
+ * Indicates if a following space should be added after inserted match into an editor.
+ *
+ * @since 4.20.0
+ * @cfg {Boolean} [emoji_followingSpace=false]
+ * @member CKEDITOR.config
+ */
+
+CKEDITOR.config.emoji_followingSpace = false;
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/samples/emoji.html b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/samples/emoji.html
new file mode 100644
index 00000000000..4f3cded5183
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/samples/emoji.html
@@ -0,0 +1,122 @@
+
+
+
+
+
+ Emoji plugin — CKEditor Sample
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Emoji plugin with dropdown menu
+
This sample shows the progress of work on Emoji. Type “ : ” and 2 letters to start inserting emoji.
+
Some emoji to type in editor:
+
+
+
+
+
+
+
+
+
This is emoji sample.
+
Type : and 2 letters to show suggestion box with emoji.
+
You can also select emoji icon in toolbar and select interesting emoji from drop down menu.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/skins/default.css b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/skins/default.css
new file mode 100644
index 00000000000..60a44393861
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/emoji/skins/default.css
@@ -0,0 +1,237 @@
+.cke_emoji {
+ overflow-y: hidden;
+ height: 100%;
+}
+
+.cke_emoji-suggestion_item {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-family: sans-serif, Arial, Verdana, "Trebuchet MS", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+}
+
+.cke_emoji-suggestion_item span {
+ font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+}
+
+.cke_emoji-panel {
+ width: 310px;
+ height: 300px;
+ overflow: hidden;
+}
+
+.cke_emoji-inner_panel {
+ width: 100%;
+}
+
+.cke_emoji-panel_block a {
+ display: inline-block;
+ width: 100%;
+ padding-top: 2px;
+}
+
+.cke_emoji-inner_panel > h2 {
+ font-size: 2em;
+}
+
+/* TOP NAVIGATION */
+.cke_emoji-navigation_icons {
+ display: none;
+}
+.cke_emoji-inner_panel > nav {
+ width: 100%;
+ height: 24px;
+ margin-top: 10px;
+ margin-bottom: 6px;
+ padding-bottom: 4px;
+ border-bottom: 1px solid #d1d1d1;
+}
+.cke_emoji-inner_panel > nav > ul {
+ margin-left: 10px;
+ margin-right: 10px;
+ margin-top: 8px;
+ padding: 0;
+ list-style-type: none;
+ height: 24px;
+}
+
+.cke_emoji-inner_panel > nav li {
+ display: inline-block;
+ width: 24px;
+ height: auto;
+ margin: 0 6px;
+ text-align: center;
+}
+
+.cke_browser_ie .cke_emoji-inner_panel > nav li {
+ height: 22px;
+}
+
+.cke_emoji-inner_panel li svg {
+ opacity: 0.4;
+ width: 80%;
+}
+
+.cke_emoji-inner_panel li span {
+ opacity: 0.4;
+}
+
+.cke_emoji-inner_panel li:hover svg, .cke_emoji-inner_panel li:hover span{
+ opacity: 1;
+}
+
+.cke_emoji-inner_panel .active {
+ border-bottom: 5px solid rgba(44, 195, 255, 1);
+}
+
+.cke_emoji-navigation_item span {
+ width: 21px;
+ height: 21px;
+ display: inline-block;
+}
+
+/* SEARCHBOX */
+.cke_emoji-search {
+ position: relative;
+ height: 25px;
+ display: block;
+ border: 1px solid #d1d1d1;
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+.cke_emoji-search .cke_emoji-search_loupe {
+ position: absolute;
+ top: 6px;
+ left: 6px;
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ opacity: 0.4;
+}
+
+.cke_rtl .cke_emoji-search .cke_emoji-search_loupe {
+ left: auto;
+ right: 6px;
+}
+
+.cke_emoji-search span {
+ background-repeat: no-repeat;
+ background-position: -60px -15px;
+ background-size: 75px 30px;
+}
+
+.cke_emoji-search input {
+ -webkit-appearance: none;
+ border: none;
+ width: 100%;
+ height: 100%;
+ padding-left: 25px;
+ padding-right: 10px;
+ margin-left: 0
+}
+
+.cke_rtl .cke_emoji-search input {
+ padding-left: 10px;
+ padding-right: 25px;
+ margin-right: 0;
+}
+
+/* EMOJI */
+.cke_emoji-outer_emoji_block {
+ height: 180px;
+ overflow-x: hidden;
+ overflow-y: auto;
+ margin-top: 5px;
+ margin-left: 10px;
+ margin-right: 10px;
+ padding-left: 2px;
+ padding-right: 2px;
+}
+
+.cke_emoji-outer_emoji_block h2 {
+ font-size: 1.3em;
+ font-weight: 600;
+ margin: 5px 0 3px 0;
+}
+
+.cke_emoji-outer_emoji_block ul {
+ margin: 0 0 15px 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+.cke_emoji-item {
+ font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ list-style-type: none;
+ display: inline-table;
+ width: 36px;
+ height: 36px;
+ font-size: 1.8em;
+ text-align: center;
+}
+
+.cke_emoji-item:hover {
+ border-radius: 10%;
+ background-color: rgba(44, 195, 255, 0.2);
+}
+
+.cke_emoji-item > a {
+ text-decoration: none;
+ display: table-cell;
+ vertical-align: middle;
+}
+
+.cke_emoji-outer_emoji_block .hidden {
+ display: none
+}
+
+/* STATUS BAR */
+.cke_emoji-status_bar {
+ height: 34px;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 3px;
+ margin-top: 3px;
+ border-top: 1px solid #d1d1d1;
+ line-height: 1;
+}
+
+.cke_emoji-status_bar p {
+ margin-top: 3px;
+}
+
+.cke_emoji-status_bar > div {
+ display: inline-block;
+ margin-top: 3px;
+}
+
+.cke_emoji-status_icon {
+ font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ font-size: 2.2em;
+ float: left;
+ margin-right: 10px;
+}
+
+.cke_rtl .cke_emoji-status_icon {
+ float: right;
+ margin-right: 0px;
+ margin-left: 10px;
+}
+
+.cke_emoji-panel_block p {
+ margin-bottom: 0;
+}
+
+p.cke_emoji-status_description {
+ font-weight: 600;
+}
+
+p.cke_emoji-status_full_name {
+ font-size: 0.8em;
+ color: #d1d1d1;
+}
+
+.cke_emoji-inner_panel a:focus, .cke_emoji-inner_panel input:focus {
+ outline: 2px solid #139FF7;
+}
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/panel/plugin.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/panel/plugin.js
new file mode 100644
index 00000000000..54bfc27ff95
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/panel/plugin.js
@@ -0,0 +1,469 @@
+/**
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+ * CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+ */
+
+( function() {
+ CKEDITOR.plugins.add( 'panel', {
+ beforeInit: function( editor ) {
+ editor.ui.addHandler( CKEDITOR.UI_PANEL, CKEDITOR.ui.panel.handler );
+ }
+ } );
+
+ /**
+ * Panel UI element.
+ *
+ * @readonly
+ * @property {String} [='panel']
+ * @member CKEDITOR
+ */
+ CKEDITOR.UI_PANEL = 'panel';
+
+ /**
+ * @class
+ * @constructor Creates a panel class instance.
+ * @param {CKEDITOR.dom.document} document
+ * @param {Object} definition
+ */
+ CKEDITOR.ui.panel = function( document, definition ) {
+ // Copy all definition properties to this object.
+ if ( definition )
+ CKEDITOR.tools.extend( this, definition );
+
+ // Set defaults.
+ CKEDITOR.tools.extend( this, {
+ className: '',
+ css: []
+ } );
+
+ this.id = CKEDITOR.tools.getNextId();
+ this.document = document;
+ this.isFramed = this.forceIFrame || this.css.length;
+
+ this._ = {
+ blocks: {}
+ };
+ };
+
+ /**
+ * Represents panel handler object.
+ *
+ * @class
+ * @singleton
+ * @extends CKEDITOR.ui.handlerDefinition
+ */
+ CKEDITOR.ui.panel.handler = {
+ /**
+ * Transforms a panel definition in a {@link CKEDITOR.ui.panel} instance.
+ *
+ * @param {Object} definition
+ * @returns {CKEDITOR.ui.panel}
+ */
+ create: function( definition ) {
+ return new CKEDITOR.ui.panel( definition );
+ }
+ };
+
+ var panelTpl = CKEDITOR.addTemplate( 'panel', '' +
+ '{frame}' +
+ '
' );
+
+ var frameTpl = CKEDITOR.addTemplate( 'panel-frame', '' );
+
+ var frameDocTpl = CKEDITOR.addTemplate( 'panel-frame-inner', '' +
+ '' +
+ '{css}' +
+ '' +
+ '<\/html>' );
+
+ /** @class CKEDITOR.ui.panel */
+ CKEDITOR.ui.panel.prototype = {
+ /**
+ * Renders the combo.
+ *
+ * @param {CKEDITOR.editor} editor The editor instance which this button is
+ * to be used by.
+ * @param {Array} [output] The output array to which append the HTML relative
+ * to this button.
+ */
+ render: function( editor, output ) {
+ var data = {
+ editorId: editor.id,
+ id: this.id,
+ langCode: editor.langCode,
+ dir: editor.lang.dir,
+ cls: this.className,
+ frame: '',
+ env: CKEDITOR.env.cssClass,
+ 'z-index': editor.config.baseFloatZIndex + 1
+ };
+
+ this.getHolderElement = function() {
+ var holder = this._.holder;
+
+ if ( !holder ) {
+ if ( this.isFramed ) {
+ var iframe = this.document.getById( this.id + '_frame' ),
+ parentDiv = iframe.getParent(),
+ doc = iframe.getFrameDocument();
+
+ // Make it scrollable on iOS. (https://dev.ckeditor.com/ticket/8308)
+ CKEDITOR.env.iOS && parentDiv.setStyles( {
+ 'overflow': 'scroll',
+ '-webkit-overflow-scrolling': 'touch'
+ } );
+
+ var onLoad = CKEDITOR.tools.addFunction( CKEDITOR.tools.bind( function() {
+ this.isLoaded = true;
+ if ( this.onLoad )
+ this.onLoad();
+ }, this ) );
+
+ doc.write( frameDocTpl.output( CKEDITOR.tools.extend( {
+ css: CKEDITOR.tools.buildStyleHtml( this.css ),
+ onload: 'window.parent.CKEDITOR.tools.callFunction(' + onLoad + ');'
+ }, data ) ) );
+
+ var win = doc.getWindow();
+
+ // Register the CKEDITOR global.
+ win.$.CKEDITOR = CKEDITOR;
+
+ // Arrow keys for scrolling is only preventable with 'keypress' event in Opera (https://dev.ckeditor.com/ticket/4534).
+ doc.on( 'keydown', function( evt ) {
+ var keystroke = evt.data.getKeystroke(),
+ dir = this.document.getById( this.id ).getAttribute( 'dir' );
+
+ // Arrow left and right should use native behaviour inside input element
+ if ( evt.data.getTarget().getName() === 'input' && ( keystroke === 37 || keystroke === 39 ) ) {
+ return;
+ }
+ // Delegate key processing to block.
+ if ( this._.onKeyDown && this._.onKeyDown( keystroke ) === false ) {
+ if ( !( evt.data.getTarget().getName() === 'input' && keystroke === 32 ) ) {
+ // Don't prevent space when is pressed on a input filed.
+ evt.data.preventDefault();
+ }
+ return;
+ }
+
+ // ESC/ARROW-LEFT(ltr) OR ARROW-RIGHT(rtl)
+ if ( keystroke == 27 || keystroke == ( dir == 'rtl' ? 39 : 37 ) ) {
+ if ( this.onEscape && this.onEscape( keystroke ) === false )
+ evt.data.preventDefault();
+ }
+ }, this );
+
+ holder = doc.getBody();
+ holder.unselectable();
+ CKEDITOR.env.air && CKEDITOR.tools.callFunction( onLoad );
+ } else {
+ holder = this.document.getById( this.id );
+ }
+
+ this._.holder = holder;
+ }
+
+ return holder;
+ };
+
+ if ( this.isFramed ) {
+ // With IE, the custom domain has to be taken care at first,
+ // for other browers, the 'src' attribute should be left empty to
+ // trigger iframe's 'load' event.
+ var src =
+ CKEDITOR.env.air ? 'javascript:void(0)' : // jshint ignore:line
+ ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) ? 'javascript:void(function(){' + encodeURIComponent( // jshint ignore:line
+ 'document.open();' +
+ // In IE, the document domain must be set any time we call document.open().
+ '(' + CKEDITOR.tools.fixDomain + ')();' +
+ 'document.close();'
+ ) + '}())' :
+ '';
+
+ data.frame = frameTpl.output( {
+ id: this.id + '_frame',
+ src: src
+ } );
+ }
+
+ var html = panelTpl.output( data );
+
+ if ( output )
+ output.push( html );
+
+ return html;
+ },
+
+ /**
+ * @todo
+ */
+ addBlock: function( name, block ) {
+ block = this._.blocks[ name ] = block instanceof CKEDITOR.ui.panel.block ? block : new CKEDITOR.ui.panel.block( this.getHolderElement(), block );
+
+ if ( !this._.currentBlock )
+ this.showBlock( name );
+
+ return block;
+ },
+
+ /**
+ * @todo
+ */
+ getBlock: function( name ) {
+ return this._.blocks[ name ];
+ },
+
+ /**
+ * @todo
+ */
+ showBlock: function( name ) {
+ var blocks = this._.blocks,
+ block = blocks[ name ],
+ current = this._.currentBlock;
+
+ // ARIA role works better in IE on the body element, while on the iframe
+ // for FF. (https://dev.ckeditor.com/ticket/8864)
+ var holder = !this.forceIFrame || CKEDITOR.env.ie ? this._.holder : this.document.getById( this.id + '_frame' );
+
+ if ( current )
+ current.hide();
+
+ this._.currentBlock = block;
+
+ CKEDITOR.fire( 'ariaWidget', holder );
+
+ // Reset the focus index, so it will always go into the first one.
+ block._.focusIndex = -1;
+
+ this._.onKeyDown = block.onKeyDown && CKEDITOR.tools.bind( block.onKeyDown, block );
+
+ block.show();
+
+ return block;
+ },
+
+ /**
+ * @todo
+ */
+ destroy: function() {
+ this.element && this.element.remove();
+ }
+ };
+
+ /**
+ * @class
+ *
+ * @todo class and all methods
+ */
+ CKEDITOR.ui.panel.block = CKEDITOR.tools.createClass( {
+ /**
+ * Creates a block class instances.
+ *
+ * @constructor
+ * @todo
+ */
+ $: function( blockHolder, blockDefinition ) {
+ this.element = blockHolder.append( blockHolder.getDocument().createElement( 'div', {
+ attributes: {
+ 'tabindex': -1,
+ 'class': 'cke_panel_block'
+ },
+ styles: {
+ display: 'none'
+ }
+ } ) );
+
+ // Copy all definition properties to this object.
+ if ( blockDefinition )
+ CKEDITOR.tools.extend( this, blockDefinition );
+
+ // Set the a11y attributes of this element ...
+ this.element.setAttributes( {
+ 'role': this.attributes.role || 'presentation',
+ 'aria-label': this.attributes[ 'aria-label' ],
+ 'title': this.attributes.title || this.attributes[ 'aria-label' ]
+ } );
+
+ this.keys = {};
+
+ this._.focusIndex = -1;
+
+ // Disable context menu for panels.
+ this.element.disableContextMenu();
+ },
+
+ _: {
+
+ /**
+ * Mark the item specified by the index as current activated.
+ */
+ markItem: function( index ) {
+ if ( index == -1 )
+ return;
+ var focusables = this._.getItems();
+ var item = focusables.getItem( this._.focusIndex = index );
+
+ // Safari need focus on the iframe window first(https://dev.ckeditor.com/ticket/3389), but we need
+ // lock the blur to avoid hiding the panel.
+ if ( CKEDITOR.env.webkit )
+ item.getDocument().getWindow().focus();
+ item.focus();
+
+ this.onMark && this.onMark( item );
+ },
+
+ /**
+ * Marks the first visible item or the one whose `aria-selected` attribute is set to `true`.
+ * The latter has priority over the former.
+ *
+ * @private
+ * @param beforeMark function to be executed just before marking.
+ * Used in cases when any preparatory cleanup (like unmarking all items) would simultaneously
+ * destroy the information that is needed to determine the focused item.
+ */
+ markFirstDisplayed: function( beforeMark ) {
+ var notDisplayed = function( element ) {
+ return element.type == CKEDITOR.NODE_ELEMENT && element.getStyle( 'display' ) == 'none';
+ },
+ focusables = this._.getItems(),
+ item, focused;
+
+ for ( var i = focusables.count() - 1; i >= 0; i-- ) {
+ item = focusables.getItem( i );
+
+ if ( !item.getAscendant( notDisplayed ) ) {
+ focused = item;
+ this._.focusIndex = i;
+ }
+
+ if ( item.getAttribute( 'aria-selected' ) == 'true' ) {
+ focused = item;
+ this._.focusIndex = i;
+ break;
+ }
+ }
+
+ if ( !focused ) {
+ return;
+ }
+
+ if ( beforeMark ) {
+ beforeMark();
+ }
+
+ if ( CKEDITOR.env.webkit )
+ focused.getDocument().getWindow().focus();
+ focused.focus();
+
+ this.onMark && this.onMark( focused );
+ },
+
+ /**
+ * Returns a `CKEDITOR.dom.nodeList` of block items.
+ *
+ * @returns {CKEDITOR.dom.nodeList}
+ */
+ getItems: function() {
+ return this.element.find( 'a,input' );
+ }
+ },
+
+ proto: {
+ show: function() {
+ this.element.setStyle( 'display', '' );
+ },
+
+ hide: function() {
+ if ( !this.onHide || this.onHide.call( this ) !== true )
+ this.element.setStyle( 'display', 'none' );
+ },
+
+ onKeyDown: function( keystroke, noCycle ) {
+ var keyAction = this.keys[ keystroke ];
+ switch ( keyAction ) {
+ // Move forward.
+ case 'next':
+ var index = this._.focusIndex,
+ focusables = this._.getItems(),
+ focusable;
+
+ while ( ( focusable = focusables.getItem( ++index ) ) ) {
+ // Move the focus only if the element is marked with
+ // the _cke_focus and it it's visible (check if it has
+ // width).
+ if ( focusable.getAttribute( '_cke_focus' ) && focusable.$.offsetWidth ) {
+ this._.focusIndex = index;
+ focusable.focus( true );
+ break;
+ }
+ }
+
+ // If no focusable was found, cycle and restart from the top. (https://dev.ckeditor.com/ticket/11125)
+ if ( !focusable && !noCycle ) {
+ this._.focusIndex = -1;
+ return this.onKeyDown( keystroke, 1 );
+ }
+
+ return false;
+
+ // Move backward.
+ case 'prev':
+ index = this._.focusIndex;
+ focusables = this._.getItems();
+
+ while ( index > 0 && ( focusable = focusables.getItem( --index ) ) ) {
+ // Move the focus only if the element is marked with
+ // the _cke_focus and it it's visible (check if it has
+ // width).
+ if ( focusable.getAttribute( '_cke_focus' ) && focusable.$.offsetWidth ) {
+ this._.focusIndex = index;
+ focusable.focus( true );
+ break;
+ }
+
+ // Make sure focusable is null when the loop ends and nothing was
+ // found (https://dev.ckeditor.com/ticket/11125).
+ focusable = null;
+ }
+
+ // If no focusable was found, cycle and restart from the bottom. (https://dev.ckeditor.com/ticket/11125)
+ if ( !focusable && !noCycle ) {
+ this._.focusIndex = focusables.count();
+ return this.onKeyDown( keystroke, 1 );
+ }
+
+ return false;
+
+ case 'click':
+ case 'mouseup':
+ index = this._.focusIndex;
+ focusable = index >= 0 && this._.getItems().getItem( index );
+
+ if ( focusable ) {
+ // We must pass info about clicked button (#2857).
+ focusable.fireEventHandler( keyAction, {
+ button: CKEDITOR.tools.normalizeMouseButton( CKEDITOR.MOUSE_BUTTON_LEFT, true )
+ } );
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+ } );
+
+} )();
+
+/**
+ * Fired when a panel is added to the document.
+ *
+ * @event ariaWidget
+ * @member CKEDITOR
+ * @param {Object} data The element wrapping the panel.
+ */
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/panelbutton/plugin.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/panelbutton/plugin.js
new file mode 100644
index 00000000000..f8a6ed1c9aa
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/panelbutton/plugin.js
@@ -0,0 +1,168 @@
+/**
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+ * CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+ */
+
+CKEDITOR.plugins.add( 'panelbutton', {
+ requires: 'button',
+ onLoad: function() {
+ function clickFn( editor ) {
+ var _ = this._;
+
+ if ( _.state == CKEDITOR.TRISTATE_DISABLED )
+ return;
+
+ this.createPanel( editor );
+
+ if ( _.on ) {
+ _.panel.hide();
+ return;
+ }
+
+ _.panel.showBlock( this._.id, this.document.getById( this._.id ), 4 );
+ }
+
+ /**
+ * @class
+ * @extends CKEDITOR.ui.button
+ * @todo class and methods
+ */
+ CKEDITOR.ui.panelButton = CKEDITOR.tools.createClass( {
+ base: CKEDITOR.ui.button,
+
+ /**
+ * Creates a panelButton class instance.
+ *
+ * @constructor
+ */
+ $: function( definition ) {
+ // We don't want the panel definition in this object.
+ var panelDefinition = definition.panel || {};
+
+ delete definition.panel;
+
+ this.base( definition );
+
+ this.document = ( panelDefinition.parent && panelDefinition.parent.getDocument() ) || CKEDITOR.document;
+
+ panelDefinition.block = {
+ attributes: panelDefinition.attributes
+ };
+ panelDefinition.toolbarRelated = true;
+
+ this.hasArrow = 'listbox';
+
+ this.click = clickFn;
+
+ this._ = {
+ panelDefinition: panelDefinition
+ };
+ },
+
+ statics: {
+ handler: {
+ create: function( definition ) {
+ return new CKEDITOR.ui.panelButton( definition );
+ }
+ }
+ },
+
+ proto: {
+ createPanel: function( editor ) {
+ var _ = this._;
+
+ if ( _.panel ) {
+ return;
+ }
+
+ var panelDefinition = this._.panelDefinition,
+ panelBlockDefinition = this._.panelDefinition.block,
+ panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(),
+ buttonElement = CKEDITOR.document.getById( this._.id ),
+ panel = this._.panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ),
+ block = panel.addBlock( _.id, panelBlockDefinition ),
+ me = this,
+ command = editor.getCommand( this.command );
+
+ panel.onShow = function() {
+ if ( me.className ) {
+ this.element.addClass( me.className + '_panel' );
+ }
+
+ me.setState( CKEDITOR.TRISTATE_ON );
+
+ // Indicates that panel button is open (#421, #5144).
+ buttonElement.setAttribute( 'aria-expanded', 'true' );
+
+ _.on = 1;
+
+ me.editorFocus && editor.focus();
+
+ if ( me.onOpen ) {
+ me.onOpen();
+ }
+ };
+
+ panel.onHide = function( preventOnClose ) {
+ if ( me.className ) {
+ this.element.getFirst().removeClass( me.className + '_panel' );
+ }
+
+ // Defined `modes` has priority over the command for a backward compatibility (#3727).
+ if ( !me.modes && command ) {
+ me.setStateFromCommand( command );
+ } else {
+ me.setState( me.modes && me.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
+ }
+
+ _.on = 0;
+
+ // Indicates that panel button is closed (#421, #5144).
+ buttonElement.setAttribute( 'aria-expanded', 'false' );
+
+ if ( !preventOnClose && me.onClose ) {
+ me.onClose();
+ }
+ };
+
+ panel.onEscape = function() {
+ panel.hide( 1 );
+ me.document.getById( _.id ).focus();
+ };
+
+ if ( this.onBlock ) {
+ this.onBlock( panel, block );
+ }
+
+ block.onHide = function() {
+ _.on = 0;
+
+ // Defined `modes` has priority over the command for a backward compatibility (#3727).
+ if ( !me.modes && me.command ) {
+ me.setStateFromCommand( command );
+ } else {
+ me.setState( CKEDITOR.TRISTATE_OFF );
+ }
+ };
+ },
+
+ setStateFromCommand: function( command ) {
+ this.setState( command.state );
+ }
+ }
+ } );
+
+ },
+ beforeInit: function( editor ) {
+ editor.ui.addHandler( CKEDITOR.UI_PANELBUTTON, CKEDITOR.ui.panelButton.handler );
+ }
+} );
+
+/**
+ * Button UI element.
+ *
+ * @readonly
+ * @property {String} [='panelbutton']
+ * @member CKEDITOR
+ */
+CKEDITOR.UI_PANELBUTTON = 'panelbutton';
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/textwatcher/plugin.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/textwatcher/plugin.js
new file mode 100644
index 00000000000..c89b5b9ff87
--- /dev/null
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/plugins/textwatcher/plugin.js
@@ -0,0 +1,295 @@
+/**
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
+ * CKEditor 4 LTS ("Long Term Support") is available under the terms of the Extended Support Model.
+ */
+
+'use strict';
+
+( function() {
+
+ CKEDITOR.plugins.add( 'textwatcher', {} );
+
+ /**
+ * API exposed by the [Text Watcher](https://ckeditor.com/cke4/addon/textwatcher) plugin.
+ *
+ * Class implementing the text watcher — a base for features like
+ * autocomplete. It fires the {@link #matched} and {@link #unmatched} events
+ * based on changes in the text and the position of the caret in the editor.
+ *
+ * To check whether the text matches some criteria, the text watcher uses
+ * a callback function which should return the matching text and a {@link CKEDITOR.dom.range}
+ * for that text.
+ *
+ * Since the text watcher works on the DOM where searching for text
+ * is pretty complicated, it is usually recommended to use the {@link CKEDITOR.plugins.textMatch#match}
+ * function.
+ *
+ * Example:
+ *
+ * ```javascript
+ * function textTestCallback( range ) {
+ * // You do not want to autocomplete a non-empty selection.
+ * if ( !range.collapsed ) {
+ * return null;
+ * }
+ *
+ * // Use the text match plugin which does the tricky job of doing
+ * // a text search in the DOM. The matchCallback function should return
+ * // a matching fragment of the text.
+ * return CKEDITOR.plugins.textMatch.match( range, matchCallback );
+ * }
+ *
+ * function matchCallback( text, offset ) {
+ * // Get the text before the caret.
+ * var left = text.slice( 0, offset ),
+ * // Will look for an '@' character followed by word characters.
+ * match = left.match( /@\w*$/ );
+ *
+ * if ( !match ) {
+ * return null;
+ * }
+ * return { start: match.index, end: offset };
+ * }
+ *
+ * // Initialize the text watcher.
+ * var textWatcher = new CKEDITOR.plugins.textWatcher( editor, textTestCallback );
+ * // Start listening.
+ * textWatcher.attach();
+ *
+ * // Handle text matching.
+ * textWatcher.on( 'matched', function( evt ) {
+ * autocomplete.setQuery( evt.data.text );
+ * } );
+ * ```
+ *
+ * @class CKEDITOR.plugins.textWatcher
+ * @since 4.10.0
+ * @mixins CKEDITOR.event
+ * @constructor Creates the text watcher instance.
+ * @param {CKEDITOR.editor} editor The editor instance to watch in.
+ * @param {Function} callback Callback executed when the text watcher
+ * thinks that something might have changed.
+ * @param {Number} [throttle=0] Throttle interval, see {@link #throttle}.
+ * @param {CKEDITOR.dom.range} callback.range The range representing the caret position.
+ * @param {Object} [callback.return=null] Matching text data (`null` if nothing matches).
+ * @param {String} callback.return.text The matching text.
+ * @param {CKEDITOR.dom.range} callback.return.range A range in the DOM for the text that matches.
+ */
+ function TextWatcher( editor, callback, throttle ) {
+ /**
+ * The editor instance which the text watcher watches.
+ *
+ * @readonly
+ * @property {CKEDITOR.editor}
+ */
+ this.editor = editor;
+
+ /**
+ * The last matched text.
+ *
+ * @readonly
+ * @property {String}
+ */
+ this.lastMatched = null;
+
+ /**
+ * Whether the next check should be ignored. See the {@link #consumeNext} method.
+ *
+ * @readonly
+ */
+ this.ignoreNext = false;
+
+ /**
+ * The callback passed to the {@link CKEDITOR.plugins.textWatcher} constructor.
+ *
+ * @readonly
+ * @property {Function}
+ */
+ this.callback = callback;
+
+ /**
+ * Keys that should be ignored by the {@link #check} method.
+ *
+ * @readonly
+ * @property {Number[]}
+ */
+ this.ignoredKeys = [
+ 16, // Shift
+ 17, // Ctrl
+ 18, // Alt
+ 91, // Cmd
+ 35, // End
+ 36, // Home
+ 37, // Left
+ 38, // Up
+ 39, // Right
+ 40, // Down
+ 33, // PageUp
+ 34 // PageUp
+ ];
+
+ /**
+ * Listeners registered by this text watcher.
+ *
+ * @private
+ */
+ this._listeners = [];
+
+ /**
+ * Indicates throttle threshold mitigating text checks.
+ *
+ * Higher levels of the throttle threshold will create a delay for text watcher checks
+ * but also improve its performance.
+ *
+ * See the {@link CKEDITOR.tools#throttle throttle} feature for more information.
+ *
+ * @readonly
+ * @property {Number} [throttle=0]
+ */
+ this.throttle = throttle || 0;
+
+ /**
+ * The {@link CKEDITOR.tools#throttle throttle buffer} used to mitigate text checks.
+ *
+ * @private
+ */
+ this._buffer = CKEDITOR.tools.throttle( this.throttle, testTextMatch, this );
+
+ /**
+ * Event fired when the text is no longer matching.
+ *
+ * @event matched
+ * @param {Object} data The value returned by the {@link #callback}.
+ * @param {String} data.text
+ * @param {CKEDITOR.dom.range} data.range
+ */
+
+ /**
+ * Event fired when the text stops matching.
+ *
+ * @event unmatched
+ */
+
+ function testTextMatch( selectionRange ) {
+ var matched = this.callback( selectionRange );
+
+ if ( matched ) {
+ if ( matched.text == this.lastMatched ) {
+ return;
+ }
+
+ this.lastMatched = matched.text;
+ this.fire( 'matched', matched );
+ } else if ( this.lastMatched ) {
+ this.unmatch();
+ }
+ }
+ }
+
+ TextWatcher.prototype = {
+ /**
+ * Attaches the text watcher to the {@link #editor}.
+ *
+ * @chainable
+ */
+ attach: function() {
+ var editor = this.editor;
+
+ this._listeners.push( editor.on( 'contentDom', onContentDom, this ) );
+ this._listeners.push( editor.on( 'blur', unmatch, this ) );
+ this._listeners.push( editor.on( 'beforeModeUnload', unmatch, this ) );
+ this._listeners.push( editor.on( 'setData', unmatch, this ) );
+ this._listeners.push( editor.on( 'afterCommandExec', unmatch, this ) );
+
+ // Attach if editor is already initialized.
+ if ( editor.editable() ) {
+ onContentDom.call( this );
+ }
+
+ return this;
+
+ function onContentDom() {
+ var editable = editor.editable();
+
+ this._listeners.push( editable.attachListener( editable, 'keyup', check, this ) );
+ }
+
+ // CKEditor's event system has a limitation that one function (in this case this.check)
+ // cannot be used as listener for the same event more than once. Hence, wrapper function.
+ function check( evt ) {
+ this.check( evt );
+ }
+
+ function unmatch() {
+ this.unmatch();
+ }
+ },
+
+ /**
+ * Triggers a text check. Fires the {@link #matched} and {@link #unmatched} events.
+ * The {@link #matched} event will not be fired twice in a row for the same text
+ * unless the text watcher is {@link #unmatch reset}.
+ *
+ * @param {CKEDITOR.dom.event/CKEDITOR.eventInfo} [evt]
+ */
+ check: function( evt ) {
+ if ( this.ignoreNext ) {
+ this.ignoreNext = false;
+ return;
+ }
+
+ // Ignore control keys, so they don't trigger the check.
+ if ( evt && evt.name == 'keyup' && ( CKEDITOR.tools.array.indexOf( this.ignoredKeys, evt.data.getKey() ) != -1 ) ) {
+ return;
+ }
+
+ var sel = this.editor.getSelection();
+ if ( !sel ) {
+ return;
+ }
+
+ var selectionRange = sel.getRanges()[ 0 ];
+ if ( !selectionRange ) {
+ return;
+ }
+
+ this._buffer.input( selectionRange );
+ },
+
+ /**
+ * Ignores the next {@link #check}.
+ *
+ * @chainable
+ */
+ consumeNext: function() {
+ this.ignoreNext = true;
+ return this;
+ },
+
+ /**
+ * Resets the state and fires the {@link #unmatched} event.
+ *
+ * @chainable
+ */
+ unmatch: function() {
+ this.lastMatched = null;
+ this.fire( 'unmatched' );
+ return this;
+ },
+
+ /**
+ * Destroys the text watcher instance. The DOM event listeners will be cleaned up.
+ */
+ destroy: function() {
+ CKEDITOR.tools.array.forEach( this._listeners, function( obj ) {
+ obj.removeListener();
+ } );
+ this._listeners = [];
+ }
+ };
+
+ CKEDITOR.event.implementOn( TextWatcher.prototype );
+
+ CKEDITOR.plugins.textWatcher = TextWatcher;
+
+} )();
diff --git a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/silverconfig.js b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/silverconfig.js
index 3333c85bb22..5c90e0658c9 100644
--- a/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/silverconfig.js
+++ b/core-war/src/main/webapp/wysiwyg/jsp/ckeditor/silverconfig.js
@@ -11,7 +11,7 @@ CKEDITOR.editorConfig = function( config )
config.filebrowserFlashBrowseUrl = config.baseHref+'uploadFile.jsp';
config.filebrowserBrowseUrl = config.baseHref+'uploadFile.jsp';
config.imageUploadUrl = 'activated';
- config.extraPlugins = 'userzoom,identitycard,autolink,video,html5audio,imageresizerowandcolumn,variables,listblock,floatpanel,richcombo,mediaofcontribution,imagebank,filebank';
+ config.extraPlugins = 'userzoom,identitycard,autolink,video,html5audio,imageresizerowandcolumn,variables,listblock,floatpanel,richcombo,mediaofcontribution,imagebank,filebank,emoji';
config.allowedContent = true;
config.toolbarCanCollapse = true;
config.disableNativeSpellChecker = false;
@@ -32,7 +32,7 @@ CKEDITOR.editorConfig = function( config )
{ name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] },
{ name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','CreateDiv','-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock' ] },
{ name: 'links', items : [ 'Link','Unlink','Anchor' ] },
- { name: 'insert', items : [ 'Image','mediaofcontribution','imagebank','Video','Html5audio','Iframe','filebank','Table','HorizontalRule','Smiley','SpecialChar','PageBreak', 'identitycard', 'userzoom', 'variables' ] },
+ { name: 'insert', items : [ 'Image','mediaofcontribution','imagebank','Video','Html5audio','Iframe','filebank','Table','HorizontalRule','EmojiPanel','SpecialChar','PageBreak', 'identitycard', 'userzoom', 'variables' ] },
'/',
{ name: 'styles', items : [ 'Styles','Format','Font','FontSize' ] },
{ name: 'colors', items : [ 'TextColor','BGColor' ] },
@@ -42,7 +42,7 @@ CKEDITOR.editorConfig = function( config )
config.toolbar_Light = [
{ name: 'clipboard', items : [ 'Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo' ] },
{ name: 'links', items : [ 'Link','Unlink' ] },
- { name: 'insert', items : [ 'Table','HorizontalRule','Smiley','SpecialChar', 'identitycard', 'userzoom', 'variables' ] },
+ { name: 'insert', items : [ 'Table','HorizontalRule','EmojiPanel','SpecialChar', 'identitycard', 'userzoom', 'variables' ] },
'/',
{ name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] },
{ name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','CreateDiv','-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock' ] },
@@ -66,4 +66,4 @@ CKEDITOR.editorConfig = function( config )
config.toolbar_suggestionBox = config.toolbar_Light;
config.toolbar_userNotification = config.toolbar_Basic;
-};
\ No newline at end of file
+};