context.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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 core/context
  7. */
  8. import Config from '@ckeditor/ckeditor5-utils/src/config';
  9. import Collection from '@ckeditor/ckeditor5-utils/src/collection';
  10. import PluginCollection from './plugincollection';
  11. import Locale from '@ckeditor/ckeditor5-utils/src/locale';
  12. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  13. /**
  14. * Provides a common, higher-level environment for solutions that use multiple {@link module:core/editor/editor~Editor editors}
  15. * or plugins that work outside the editor. Use it instead of {@link module:core/editor/editor~Editor.create `Editor.create()`}
  16. * in advanced application integrations.
  17. *
  18. * All configuration options passed to a context will be used as default options for editor instances initialized in that context.
  19. *
  20. * {@link module:core/contextplugin~ContextPlugin Context plugins} passed to a context instance will be shared among all
  21. * editor instances initialized in this context. These will be the same plugin instances for all the editors.
  22. *
  23. * **Note:** The context can only be initialized with {@link module:core/contextplugin~ContextPlugin context plugins}
  24. * (e.g. [comments](https://ckeditor.com/collaboration/comments/)). Regular {@link module:core/plugin~Plugin plugins} require an
  25. * editor instance to work and cannot be added to a context.
  26. *
  27. * **Note:** You can add a context plugin to an editor instance, though.
  28. *
  29. * If you are using multiple editor instances on one page and use any context plugins, create a context to share the configuration and
  30. * plugins among these editors. Some plugins will use the information about all existing editors to better integrate between them.
  31. *
  32. * If you are using plugins that do not require an editor to work (e.g. [comments](https://ckeditor.com/collaboration/comments/)),
  33. * enable and configure them using the context.
  34. *
  35. * If you are using only a single editor on each page, use {@link module:core/editor/editor~Editor.create `Editor.create()`} instead.
  36. * In such case, a context instance will be created by the editor instance in a transparent way.
  37. *
  38. * See {@link module:core/context~Context.create `Context.create()`} for usage examples.
  39. */
  40. export default class Context {
  41. /**
  42. * Creates a context instance with a given configuration.
  43. *
  44. * Usually not to be used directly. See the static {@link module:core/context~Context.create `create()`} method.
  45. *
  46. * @param {Object} [config={}] The context configuration.
  47. */
  48. constructor( config ) {
  49. /**
  50. * Stores all the configurations specific to this context instance.
  51. *
  52. * @readonly
  53. * @type {module:utils/config~Config}
  54. */
  55. this.config = new Config( config, this.constructor.defaultConfig );
  56. const availablePlugins = this.constructor.builtinPlugins;
  57. this.config.define( 'plugins', availablePlugins );
  58. /**
  59. * The plugins loaded and in use by this context instance.
  60. *
  61. * @readonly
  62. * @type {module:core/plugincollection~PluginCollection}
  63. */
  64. this.plugins = new PluginCollection( this, availablePlugins );
  65. const languageConfig = this.config.get( 'language' ) || {};
  66. /**
  67. * @readonly
  68. * @type {module:utils/locale~Locale}
  69. */
  70. this.locale = new Locale( {
  71. uiLanguage: typeof languageConfig === 'string' ? languageConfig : languageConfig.ui,
  72. contentLanguage: this.config.get( 'language.content' )
  73. } );
  74. /**
  75. * Shorthand for {@link module:utils/locale~Locale#t}.
  76. *
  77. * @see module:utils/locale~Locale#t
  78. * @method #t
  79. */
  80. this.t = this.locale.t;
  81. /**
  82. * A list of editors that this context instance is injected to.
  83. *
  84. * @readonly
  85. * @type {module:utils/collection~Collection}
  86. */
  87. this.editors = new Collection();
  88. /**
  89. * Reference to the editor which created the context.
  90. * Null when the context was created outside of the editor.
  91. *
  92. * It is used to destroy the context when removing the editor that has created the context.
  93. *
  94. * @private
  95. * @type {module:core/editor/editor~Editor|null}
  96. */
  97. this._contextOwner = null;
  98. }
  99. /**
  100. * Loads and initializes plugins specified in the configuration.
  101. *
  102. * @returns {Promise.<module:core/plugin~LoadedPlugins>} A promise which resolves
  103. * once the initialization is completed, providing an array of loaded plugins.
  104. */
  105. initPlugins() {
  106. const plugins = this.config.get( 'plugins' ) || [];
  107. for ( const Plugin of plugins ) {
  108. if ( typeof Plugin != 'function' ) {
  109. /**
  110. * Only a constructor function is allowed as a {@link module:core/contextplugin~ContextPlugin context plugin}.
  111. *
  112. * @error context-initplugins-constructor-only
  113. */
  114. throw new CKEditorError(
  115. 'context-initplugins-constructor-only',
  116. null,
  117. { Plugin }
  118. );
  119. }
  120. if ( Plugin.isContextPlugin !== true ) {
  121. /**
  122. * Only a plugin marked as a {@link module:core/contextplugin~ContextPlugin.isContextPlugin context plugin}
  123. * is allowed to be used with a context.
  124. *
  125. * @error context-initplugins-invalid-plugin
  126. */
  127. throw new CKEditorError(
  128. 'context-initplugins-invalid-plugin',
  129. null,
  130. { Plugin }
  131. );
  132. }
  133. }
  134. return this.plugins.init( plugins );
  135. }
  136. /**
  137. * Destroys the context instance and all editors used with the context,
  138. * releasing all resources used by the context.
  139. *
  140. * @returns {Promise} A promise that resolves once the context instance is fully destroyed.
  141. */
  142. destroy() {
  143. return Promise.all( Array.from( this.editors, editor => editor.destroy() ) )
  144. .then( () => this.plugins.destroy() );
  145. }
  146. /**
  147. * Adds a reference to the editor which is used with this context.
  148. *
  149. * When the given editor has created the context, the reference to this editor will be stored
  150. * as a {@link ~Context#_contextOwner}.
  151. *
  152. * This method should only be used by the editor.
  153. *
  154. * @protected
  155. * @param {module:core/editor/editor~Editor} editor
  156. * @param {Boolean} isContextOwner Stores the given editor as a context owner.
  157. */
  158. _addEditor( editor, isContextOwner ) {
  159. if ( this._contextOwner ) {
  160. /**
  161. * Cannot add multiple editors to the context which is created by the editor.
  162. *
  163. * @error context-addeditor-private-context
  164. */
  165. throw new CKEditorError( 'context-addeditor-private-context' );
  166. }
  167. this.editors.add( editor );
  168. if ( isContextOwner ) {
  169. this._contextOwner = editor;
  170. }
  171. }
  172. /**
  173. * Removes a reference to the editor which was used with this context.
  174. * When the context was created by the given editor, the context will be destroyed.
  175. *
  176. * This method should only be used by the editor.
  177. *
  178. * @protected
  179. * @param {module:core/editor/editor~Editor} editor
  180. * @return {Promise} A promise that resolves once the editor is removed from the context or when the context was destroyed.
  181. */
  182. _removeEditor( editor ) {
  183. if ( this.editors.has( editor ) ) {
  184. this.editors.remove( editor );
  185. }
  186. if ( this._contextOwner === editor ) {
  187. return this.destroy();
  188. }
  189. return Promise.resolve();
  190. }
  191. /**
  192. * Returns the context configuration which will be copied to the editors created using this context.
  193. *
  194. * The configuration returned by this method has the plugins configuration removed &mdash; plugins are shared with all editors
  195. * through another mechanism.
  196. *
  197. * This method should only be used by the editor.
  198. *
  199. * @protected
  200. * @returns {Object} Configuration as a plain object.
  201. */
  202. _getEditorConfig() {
  203. const result = {};
  204. for ( const name of this.config.names() ) {
  205. if ( ![ 'plugins', 'removePlugins', 'extraPlugins' ].includes( name ) ) {
  206. result[ name ] = this.config.get( name );
  207. }
  208. }
  209. return result;
  210. }
  211. /**
  212. * Creates and initializes a new context instance.
  213. *
  214. * const commonConfig = { ... }; // Configuration for all the plugins and editors.
  215. * const editorPlugins = [ ... ]; // Regular plugins here.
  216. *
  217. * Context
  218. * .create( {
  219. * // Only context plugins here.
  220. * plugins: [ ... ],
  221. *
  222. * // Configure the language for all the editors (it cannot be overwritten).
  223. * language: { ... },
  224. *
  225. * // Configuration for context plugins.
  226. * comments: { ... },
  227. * ...
  228. *
  229. * // Default configuration for editor plugins.
  230. * toolbar: { ... },
  231. * image: { ... },
  232. * ...
  233. * } )
  234. * .then( context => {
  235. * const promises = [];
  236. *
  237. * promises.push( ClassicEditor.create(
  238. * document.getElementById( 'editor1' ),
  239. * {
  240. * editorPlugins,
  241. * context
  242. * }
  243. * ) );
  244. *
  245. * promises.push( ClassicEditor.create(
  246. * document.getElementById( 'editor2' ),
  247. * {
  248. * editorPlugins,
  249. * context,
  250. * toolbar: { ... } // You can overwrite the configuration of the context.
  251. * }
  252. * ) );
  253. *
  254. * return Promise.all( promises );
  255. * } );
  256. *
  257. * @param {Object} [config] The context configuration.
  258. * @returns {Promise} A promise resolved once the context is ready. The promise resolves with the created context instance.
  259. */
  260. static create( config ) {
  261. return new Promise( resolve => {
  262. const context = new this( config );
  263. resolve( context.initPlugins().then( () => context ) );
  264. } );
  265. }
  266. }
  267. /**
  268. * An array of plugins built into the `Context` class.
  269. *
  270. * It is used in CKEditor 5 builds featuring `Context` to provide a list of context plugins which are later automatically initialized
  271. * during the context initialization.
  272. *
  273. * They will be automatically initialized by `Context` unless `config.plugins` is passed.
  274. *
  275. * // Build some context plugins into the Context class first.
  276. * Context.builtinPlugins = [ FooPlugin, BarPlugin ];
  277. *
  278. * // Normally, you need to define config.plugins, but since Context.builtinPlugins was
  279. * // defined, now you can call create() without any configuration.
  280. * Context
  281. * .create()
  282. * .then( context => {
  283. * context.plugins.get( FooPlugin ); // -> An instance of the Foo plugin.
  284. * context.plugins.get( BarPlugin ); // -> An instance of the Bar plugin.
  285. * } );
  286. *
  287. * See also {@link module:core/context~Context.defaultConfig `Context.defaultConfig`}
  288. * and {@link module:core/editor/editor~Editor.builtinPlugins `Editor.builtinPlugins`}.
  289. *
  290. * @static
  291. * @member {Array.<Function>} module:core/context~Context.builtinPlugins
  292. */
  293. /**
  294. * The default configuration which is built into the `Context` class.
  295. *
  296. * It is used in CKEditor 5 builds featuring `Context` to provide the default configuration options which are later used during the
  297. * context initialization.
  298. *
  299. * Context.defaultConfig = {
  300. * foo: 1,
  301. * bar: 2
  302. * };
  303. *
  304. * Context
  305. * .create()
  306. * .then( context => {
  307. * context.config.get( 'foo' ); // -> 1
  308. * context.config.get( 'bar' ); // -> 2
  309. * } );
  310. *
  311. * // The default options can be overridden by the configuration passed to create().
  312. * Context
  313. * .create( { bar: 3 } )
  314. * .then( context => {
  315. * context.config.get( 'foo' ); // -> 1
  316. * context.config.get( 'bar' ); // -> 3
  317. * } );
  318. *
  319. * See also {@link module:core/context~Context.builtinPlugins `Context.builtinPlugins`}
  320. * and {@link module:core/editor/editor~Editor.defaultConfig `Editor.defaultConfig`}.
  321. *
  322. * @static
  323. * @member {Object} module:core/context~Context.defaultConfig
  324. */