specialcharacters.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module special-characters/specialcharacters
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import Typing from '@ckeditor/ckeditor5-typing/src/typing';
  10. import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
  11. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  12. import SpecialCharactersNavigationView from './ui/specialcharactersnavigationview';
  13. import CharacterGridView from './ui/charactergridview';
  14. import CharacterInfoView from './ui/characterinfoview';
  15. import specialCharactersIcon from '../theme/icons/specialcharacters.svg';
  16. import '../theme/specialcharacters.css';
  17. const ALL_SPECIAL_CHARACTERS_GROUP = 'All';
  18. /**
  19. * The special characters feature.
  20. *
  21. * Introduces the `'specialCharacters'` dropdown.
  22. *
  23. * @extends module:core/plugin~Plugin
  24. */
  25. export default class SpecialCharacters extends Plugin {
  26. /**
  27. * @inheritDoc
  28. */
  29. static get requires() {
  30. return [ Typing ];
  31. }
  32. /**
  33. * @inheritDoc
  34. */
  35. static get pluginName() {
  36. return 'SpecialCharacters';
  37. }
  38. /**
  39. * @inheritDoc
  40. */
  41. constructor( editor ) {
  42. super( editor );
  43. /**
  44. * Registered characters. A pair of a character name and its symbol.
  45. *
  46. * @private
  47. * @member {Map.<String, String>} #_characters
  48. */
  49. this._characters = new Map();
  50. /**
  51. * Registered groups. Each group contains a collection with symbol names.
  52. *
  53. * @private
  54. * @member {Map.<String, Set.<String>>} #_groups
  55. */
  56. this._groups = new Map();
  57. }
  58. /**
  59. * @inheritDoc
  60. */
  61. init() {
  62. const editor = this.editor;
  63. const t = editor.t;
  64. const inputCommand = editor.commands.get( 'input' );
  65. // Add the `specialCharacters` dropdown button to feature components.
  66. editor.ui.componentFactory.add( 'specialCharacters', locale => {
  67. const dropdownView = createDropdown( locale );
  68. let dropdownPanelContent;
  69. dropdownView.buttonView.set( {
  70. label: t( 'Special characters' ),
  71. icon: specialCharactersIcon,
  72. tooltip: true
  73. } );
  74. dropdownView.bind( 'isEnabled' ).to( inputCommand );
  75. // Insert a special character when a tile was clicked.
  76. dropdownView.on( 'execute', ( evt, data ) => {
  77. editor.execute( 'input', { text: data.character } );
  78. editor.editing.view.focus();
  79. } );
  80. dropdownView.on( 'change:isOpen', () => {
  81. if ( !dropdownPanelContent ) {
  82. dropdownPanelContent = this._createDropdownPanelContent( locale, dropdownView );
  83. dropdownView.panelView.children.add( dropdownPanelContent.navigationView );
  84. dropdownView.panelView.children.add( dropdownPanelContent.gridView );
  85. dropdownView.panelView.children.add( dropdownPanelContent.infoView );
  86. }
  87. dropdownPanelContent.infoView.set( {
  88. character: null,
  89. name: null
  90. } );
  91. } );
  92. return dropdownView;
  93. } );
  94. }
  95. /**
  96. * Adds a collection of special characters to specified group. A title of a special character must be unique.
  97. *
  98. * **Note:** The "All" category name is reserved by the plugin and cannot be used as a new name for special
  99. * characters category.
  100. *
  101. * @param {String} groupName
  102. * @param {Array.<module:special-characters/specialcharacters~SpecialCharacterDefinition>} items
  103. */
  104. addItems( groupName, items ) {
  105. if ( groupName === ALL_SPECIAL_CHARACTERS_GROUP ) {
  106. /**
  107. * The name "All" for special category group cannot be used because it's a special category which displays all available
  108. * special characters.
  109. *
  110. * @error special-character-invalid-group-name
  111. */
  112. throw new CKEditorError(
  113. `special-character-invalid-group-name: The name "${ ALL_SPECIAL_CHARACTERS_GROUP }" is reserved and cannot be used.`
  114. );
  115. }
  116. const group = this._getGroup( groupName );
  117. for ( const item of items ) {
  118. group.add( item.title );
  119. this._characters.set( item.title, item.character );
  120. }
  121. }
  122. /**
  123. * Returns iterator of special characters groups.
  124. *
  125. * @returns {Iterable.<String>}
  126. */
  127. getGroups() {
  128. return this._groups.keys();
  129. }
  130. /**
  131. * Returns a collection of special characters symbol names (titles).
  132. *
  133. * @param {String} groupName
  134. * @returns {Set.<String>|undefined}
  135. */
  136. getCharactersForGroup( groupName ) {
  137. if ( groupName === ALL_SPECIAL_CHARACTERS_GROUP ) {
  138. return new Set( this._characters.keys() );
  139. }
  140. return this._groups.get( groupName );
  141. }
  142. /**
  143. * Returns a symbol of the special character for specified name. If the special character couldn't be found, `undefined` is returned.
  144. *
  145. * @param {String} title A title of the special character.
  146. * @returns {String|undefined}
  147. */
  148. getCharacter( title ) {
  149. return this._characters.get( title );
  150. }
  151. /**
  152. * Returns a group of special characters. If the group with the specified name does not exist, it will be created.
  153. *
  154. * @private
  155. * @param {String} groupName A name of group to create.
  156. */
  157. _getGroup( groupName ) {
  158. if ( !this._groups.has( groupName ) ) {
  159. this._groups.set( groupName, new Set() );
  160. }
  161. return this._groups.get( groupName );
  162. }
  163. /**
  164. * Updates the symbol grid depending on the currently selected character group.
  165. *
  166. * @private
  167. * @param {String} currentGroupName
  168. * @param {module:special-characters/ui/charactergridview~CharacterGridView} gridView
  169. */
  170. _updateGrid( currentGroupName, gridView ) {
  171. // Updating the grid starts with removing all tiles belonging to the old group.
  172. gridView.tiles.clear();
  173. const characterTitles = this.getCharactersForGroup( currentGroupName );
  174. for ( const title of characterTitles ) {
  175. const character = this.getCharacter( title );
  176. gridView.tiles.add( gridView.createTile( character, title ) );
  177. }
  178. }
  179. /**
  180. * Initializes the dropdown, used for lazy loading.
  181. *
  182. * @private
  183. * @param {module:utils/locale~Locale} locale
  184. * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView
  185. * @returns {Object} Returns an object with `navigationView`, `gridView` and `infoView` properties, containing UI parts.
  186. */
  187. _createDropdownPanelContent( locale, dropdownView ) {
  188. const specialCharsGroups = [ ...this.getGroups() ];
  189. // Add a special group that shows all available special characters.
  190. specialCharsGroups.unshift( ALL_SPECIAL_CHARACTERS_GROUP );
  191. const navigationView = new SpecialCharactersNavigationView( locale, specialCharsGroups );
  192. const gridView = new CharacterGridView( locale );
  193. const infoView = new CharacterInfoView( locale );
  194. gridView.delegate( 'execute' ).to( dropdownView );
  195. gridView.on( 'tileHover', ( evt, data ) => {
  196. infoView.set( data );
  197. } );
  198. // Update the grid of special characters when a user changed the character group.
  199. navigationView.on( 'execute', () => {
  200. this._updateGrid( navigationView.currentGroupName, gridView );
  201. } );
  202. // Set the initial content of the special characters grid.
  203. this._updateGrid( navigationView.currentGroupName, gridView );
  204. return { navigationView, gridView, infoView };
  205. }
  206. }
  207. /**
  208. * @typedef {Object} module:special-characters/specialcharacters~SpecialCharacterDefinition
  209. *
  210. * @property {String} title A unique name of the character (e.g. "greek small letter epsilon").
  211. * @property {String} character A human-readable character displayed as label (e.g. "ε").
  212. */