8
0

editor.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import ObservableMixin from './utils/observablemixin.js';
  7. import EditorConfig from './editorconfig.js';
  8. import PluginCollection from './plugincollection.js';
  9. import EditableCollection from './editablecollection.js';
  10. import CKEditorError from './utils/ckeditorerror.js';
  11. import Locale from './utils/locale.js';
  12. import isArray from './utils/lib/lodash/isArray.js';
  13. import nth from './utils/nth.js';
  14. import mix from './utils/mix.js';
  15. /**
  16. * Represents a single editor instance.
  17. *
  18. * @memberOf ckeditor5
  19. * @mixes utils.ObservaleMixin
  20. */
  21. export default class Editor {
  22. /**
  23. * Creates a new instance of the Editor class.
  24. *
  25. * This constructor should be rarely used. When creating new editor instance use instead the
  26. * {@link CKEDITOR#create `CKEDITOR.create()` method}.
  27. *
  28. * @param {Object.<String, HTMLElement>|null} [elements] The DOM elements that will be the source
  29. * for the created editor.
  30. * @param {Object} config The editor config.
  31. */
  32. constructor( elements, config ) {
  33. /**
  34. * The original host page elements upon which the editor is created.
  35. *
  36. * @readonly
  37. * @member {Map.<String, HTMLElement>|null} ckeditor5.Editor#elements
  38. */
  39. if ( elements ) {
  40. this.elements = new Map();
  41. for ( let name in elements ) {
  42. this.elements.set( name, elements[ name ] );
  43. }
  44. } else {
  45. this.elements = null;
  46. }
  47. /**
  48. * Holds all configurations specific to this editor instance.
  49. *
  50. * This instance of the {@link utils.Config} class is customized so its {@link utils.Config#get} method will retrieve
  51. * global configurations available in {@link CKEDITOR.config} if configurations are not found in the
  52. * instance itself.
  53. *
  54. * @readonly
  55. * @member {utils.Config} ckeditor5.Editor#config
  56. */
  57. this.config = config = new EditorConfig( config );
  58. /**
  59. * The plugins loaded and in use by this editor instance.
  60. *
  61. * @readonly
  62. * @member {ckeditor5.PluginCollection} ckeditor5.Editor#plugins
  63. */
  64. this.plugins = new PluginCollection( this );
  65. /**
  66. * The editables of the editor.
  67. *
  68. * @readonly
  69. * @member {ckeditor5.EditableCollection} ckeditor5.Editor#editables
  70. */
  71. this.editables = new EditableCollection();
  72. /**
  73. * Commands registered to the editor.
  74. *
  75. * @readonly
  76. * @member {Map.<ckeditor5.command.Command>} ckeditor5.Editor#commands
  77. */
  78. this.commands = new Map();
  79. /**
  80. * @readonly
  81. * @member {utils.Locale} ckeditor5.Editor#locale
  82. */
  83. this.locale = new Locale( config.lang );
  84. /**
  85. * Shorthand for {@link utils.Locale#t}.
  86. *
  87. * @see utils.Locale#t
  88. * @method ckeditor5.Editor#t
  89. */
  90. this.t = this.locale.t;
  91. /**
  92. * Tree Model document managed by this editor.
  93. *
  94. * This property is set by the {@link ckeditor5.creator.Creator}.
  95. *
  96. * @readonly
  97. * @member {engine.treeModel.Document} ckeditor5.Editor#document
  98. */
  99. /**
  100. * Instance of the {@link engine.treecontroller.EditingController editing controller}.
  101. *
  102. * This property is set by the {@link ckeditor5.creator.Creator}.
  103. *
  104. * @readonly
  105. * @member {engine.treecontroller.EditingController} ckeditor5.Editor#editing
  106. */
  107. /**
  108. * Instance of the {@link engine.treecontroller.DataController data controller}.
  109. *
  110. * This property is set by the {@link ckeditor5.creator.Creator}.
  111. *
  112. * @readonly
  113. * @member {engine.treecontroller.DataController} ckeditor5.Editor#data
  114. */
  115. /**
  116. * The chosen creator.
  117. *
  118. * @protected
  119. * @member {ckeditor5.creator.Creator} ckeditor5.Editor#_creator
  120. */
  121. }
  122. /**
  123. * First element from {@link ckeditor5.Editor#elements}.
  124. *
  125. * @readonly
  126. * @type {HTMLElement|null}
  127. */
  128. get firstElement() {
  129. if ( !this.elements ) {
  130. return null;
  131. }
  132. return nth( 0, this.elements )[ 1 ];
  133. }
  134. /**
  135. * Name of the first element from {@link ckeditor5.Editor#elements}.
  136. *
  137. * @readonly
  138. * @type {String|null}
  139. */
  140. get firstElementName() {
  141. if ( !this.elements ) {
  142. return null;
  143. }
  144. return nth( 0, this.elements )[ 0 ];
  145. }
  146. /**
  147. * Initializes the editor instance object after its creation.
  148. *
  149. * The initialization consists of the following procedures:
  150. *
  151. * * Loading and initializing the configured features and creator.
  152. * * Firing the editor creator.
  153. *
  154. * This method should be rarely used as {@link CKEDITOR#create} calls it one should never use the `Editor`
  155. * constructor directly.
  156. *
  157. * @returns {Promise} A promise which resolves once the initialization is completed.
  158. */
  159. init() {
  160. const that = this;
  161. const config = this.config;
  162. let creatorName = config.creator;
  163. if ( !creatorName ) {
  164. /**
  165. * The `config.creator` option was not defined.
  166. *
  167. * @error editor-undefined-creator
  168. */
  169. return Promise.reject(
  170. new CKEditorError( 'editor-undefined-creator: The config.creator option was not defined.' )
  171. );
  172. }
  173. return loadPlugins()
  174. .then( initPlugins )
  175. .then( fireCreator );
  176. function loadPlugins() {
  177. let plugins = config.features || [];
  178. // Handle features passed as a string.
  179. if ( !isArray( plugins ) ) {
  180. plugins = plugins.split( ',' );
  181. }
  182. plugins.push( creatorName );
  183. return that.plugins.load( plugins );
  184. }
  185. function initPlugins( loadedPlugins ) {
  186. return loadedPlugins.reduce( ( promise, plugin ) => {
  187. return promise.then( plugin.init.bind( plugin ) );
  188. }, Promise.resolve() );
  189. }
  190. function fireCreator() {
  191. // We can always get the creator by its name because config.creator (which is requried) is passed
  192. // to PluginCollection.load().
  193. that._creator = that.plugins.get( creatorName );
  194. // Finally fire the creator. It may be asynchronous, returning a promise.
  195. return that._creator.create();
  196. }
  197. }
  198. /**
  199. * Destroys the editor instance, releasing all resources used by it. If the editor replaced an element, the
  200. * element will be recovered.
  201. *
  202. * @fires ckeditor5.Editor#destroy
  203. * @returns {Promise} A promise that resolves once the editor instance is fully destroyed.
  204. */
  205. destroy() {
  206. this.fire( 'destroy' );
  207. this.stopListening();
  208. // Note: The destruction order should be the reverse of the initialization order.
  209. return Promise.resolve()
  210. .then( () => {
  211. return this._creator && this._creator.destroy();
  212. } )
  213. .then( () => this.editables.destroy() );
  214. }
  215. /**
  216. * Sets the data in the specified editor's editable root.
  217. *
  218. * @param {*} data The data to load.
  219. * @param {String} [editableRootName] The name of the editable root to which the data should be loaded.
  220. * If not specified and if there's only one editable root added to the editor, then the data will be loaded
  221. * to that editable.
  222. */
  223. setData( data, editableRootName ) {
  224. if ( !this.data ) {
  225. /**
  226. * Data controller has not been defined yet, so methds like {@link ckeditor5.Editor#setData} and
  227. * {@link ckeditor5.Editor#getData} cannot be used.
  228. *
  229. * @error editor-no-datacontroller
  230. */
  231. throw new CKEditorError( 'editor-no-datacontroller: Data controller has not been defined yet.' );
  232. }
  233. this.data.set( editableRootName || this._getDefaultRootName(), data );
  234. }
  235. /**
  236. * Gets the data from the specified editor's editable root.
  237. *
  238. * @param {String} [editableRootName] The name of the editable root to get the data from.
  239. * If not specified and if there's only one editable root added to the editor, then the data will be retrieved
  240. * from it.
  241. */
  242. getData( editableRootName ) {
  243. if ( !this.data ) {
  244. throw new CKEditorError( 'editor-no-datacontroller: Data controller has not been defined yet.' );
  245. }
  246. return this.data.get( editableRootName || this._getDefaultRootName() );
  247. }
  248. /**
  249. * Executes specified command with given parameter.
  250. *
  251. * @param {String} commandName Name of command to execute.
  252. * @param {*} [commandParam] If set, command will be executed with this parameter.
  253. */
  254. execute( commandName, commandParam ) {
  255. let command = this.commands.get( commandName );
  256. if ( !command ) {
  257. /**
  258. * Specified command has not been added to the editor.
  259. *
  260. * @error editor-command-not-found
  261. */
  262. throw new CKEditorError( 'editor-command-not-found: Specified command has not been added to the editor.' );
  263. }
  264. command._execute( commandParam );
  265. }
  266. /**
  267. * Returns name of the editable root if there is only one. If there are multiple or no editable roots, throws an error.
  268. *
  269. * Note: The error message makes sense only for methods like {@link ckeditor5.Editor#setData} and
  270. * {@link ckeditor5.Editor#getData}.
  271. *
  272. * @private
  273. * @returns {String}
  274. */
  275. _getDefaultRootName() {
  276. const rootNames = Array.from( this.document.rootNames );
  277. if ( rootNames.length > 1 ) {
  278. /**
  279. * The name of the editable root must be specified. There are multiple editable roots added to the editor,
  280. * so the name of the editable must be specified.
  281. *
  282. * @error editor-editable-root-name-missing
  283. */
  284. throw new CKEditorError( 'editor-editable-root-name-missing: The name of the editable root must be specified.' );
  285. }
  286. if ( rootNames.length === 0 ) {
  287. /**
  288. * The editor does not contain any editable roots, so the data cannot be set or read from it.
  289. *
  290. * @error editor-no-editable-roots
  291. */
  292. throw new CKEditorError( 'editor-no-editable-roots: There are no editable roots defined.' );
  293. }
  294. return rootNames[ 0 ];
  295. }
  296. }
  297. mix( Editor, ObservableMixin );
  298. /**
  299. * Fired when this editor instance is destroyed. The editor at this point is not usable and this event should be used to
  300. * perform the clean-up in any plugin.
  301. *
  302. * @event ckeditor5.Editor#destroy
  303. */