context.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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: Only a constructor function is allowed as a context plugin.',
  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: Only a plugin marked as a context plugin is allowed to be used with a context.',
  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(
  166. 'context-addEditor-private-context: Cannot add multiple editors to the context which is created by the editor.'
  167. );
  168. }
  169. this.editors.add( editor );
  170. if ( isContextOwner ) {
  171. this._contextOwner = editor;
  172. }
  173. }
  174. /**
  175. * Removes a reference to the editor which was used with this context.
  176. * When the context was created by the given editor, the context will be destroyed.
  177. *
  178. * This method should only be used by the editor.
  179. *
  180. * @protected
  181. * @param {module:core/editor/editor~Editor} editor
  182. * @return {Promise} A promise that resolves once the editor is removed from the context or when the context was destroyed.
  183. */
  184. _removeEditor( editor ) {
  185. if ( this.editors.has( editor ) ) {
  186. this.editors.remove( editor );
  187. }
  188. if ( this._contextOwner === editor ) {
  189. return this.destroy();
  190. }
  191. return Promise.resolve();
  192. }
  193. /**
  194. * Returns the context configuration which will be copied to the editors created using this context.
  195. *
  196. * The configuration returned by this method has the plugins configuration removed &mdash; plugins are shared with all editors
  197. * through another mechanism.
  198. *
  199. * This method should only be used by the editor.
  200. *
  201. * @protected
  202. * @returns {Object} Configuration as a plain object.
  203. */
  204. _getEditorConfig() {
  205. const result = {};
  206. for ( const name of this.config.names() ) {
  207. if ( ![ 'plugins', 'removePlugins', 'extraPlugins' ].includes( name ) ) {
  208. result[ name ] = this.config.get( name );
  209. }
  210. }
  211. return result;
  212. }
  213. /**
  214. * Creates and initializes a new context instance.
  215. *
  216. * const commonConfig = { ... }; // Configuration for all the plugins and editors.
  217. * const editorPlugins = [ ... ]; // Regular plugins here.
  218. *
  219. * Context
  220. * .create( {
  221. * // Only context plugins here.
  222. * plugins: [ ... ],
  223. *
  224. * // Configure the language for all the editors (it cannot be overwritten).
  225. * language: { ... },
  226. *
  227. * // Configuration for context plugins.
  228. * comments: { ... },
  229. * ...
  230. *
  231. * // Default configuration for editor plugins.
  232. * toolbar: { ... },
  233. * image: { ... },
  234. * ...
  235. * } )
  236. * .then( context => {
  237. * const promises = [];
  238. *
  239. * promises.push( ClassicEditor.create(
  240. * document.getElementById( 'editor1' ),
  241. * {
  242. * editorPlugins,
  243. * context
  244. * }
  245. * ) );
  246. *
  247. * promises.push( ClassicEditor.create(
  248. * document.getElementById( 'editor2' ),
  249. * {
  250. * editorPlugins,
  251. * context,
  252. * toolbar: { ... } // You can overwrite the configuration of the context.
  253. * }
  254. * ) );
  255. *
  256. * return Promise.all( promises );
  257. * } );
  258. *
  259. * @param {Object} [config] The context configuration.
  260. * @returns {Promise} A promise resolved once the context is ready. The promise resolves with the created context instance.
  261. */
  262. static create( config ) {
  263. return new Promise( resolve => {
  264. const context = new this( config );
  265. resolve( context.initPlugins().then( () => context ) );
  266. } );
  267. }
  268. }
  269. /**
  270. * An array of plugins built into the `Context` class.
  271. *
  272. * It is used in CKEditor 5 builds featuring `Context` to provide a list of context plugins which are later automatically initialized
  273. * during the context initialization.
  274. *
  275. * They will be automatically initialized by `Context` unless `config.plugins` is passed.
  276. *
  277. * // Build some context plugins into the Context class first.
  278. * Context.builtinPlugins = [ FooPlugin, BarPlugin ];
  279. *
  280. * // Normally, you need to define config.plugins, but since Context.builtinPlugins was
  281. * // defined, now you can call create() without any configuration.
  282. * Context
  283. * .create()
  284. * .then( context => {
  285. * context.plugins.get( FooPlugin ); // -> An instance of the Foo plugin.
  286. * context.plugins.get( BarPlugin ); // -> An instance of the Bar plugin.
  287. * } );
  288. *
  289. * See also {@link module:core/context~Context.defaultConfig `Context.defaultConfig`}
  290. * and {@link module:core/editor/editor~Editor.builtinPlugins `Editor.builtinPlugins`}.
  291. *
  292. * @static
  293. * @member {Array.<Function>} module:core/context~Context.builtinPlugins
  294. */
  295. /**
  296. * The default configuration which is built into the `Context` class.
  297. *
  298. * It is used in CKEditor 5 builds featuring `Context` to provide the default configuration options which are later used during the
  299. * context initialization.
  300. *
  301. * Context.defaultConfig = {
  302. * foo: 1,
  303. * bar: 2
  304. * };
  305. *
  306. * Context
  307. * .create()
  308. * .then( context => {
  309. * context.config.get( 'foo' ); // -> 1
  310. * context.config.get( 'bar' ); // -> 2
  311. * } );
  312. *
  313. * // The default options can be overridden by the configuration passed to create().
  314. * Context
  315. * .create( { bar: 3 } )
  316. * .then( context => {
  317. * context.config.get( 'foo' ); // -> 1
  318. * context.config.get( 'bar' ); // -> 3
  319. * } );
  320. *
  321. * See also {@link module:core/context~Context.builtinPlugins `Context.builtinPlugins`}
  322. * and {@link module:core/editor/editor~Editor.defaultConfig `Editor.defaultConfig`}.
  323. *
  324. * @static
  325. * @member {Object} module:core/context~Context.defaultConfig
  326. */