plugin.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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/plugin
  7. */
  8. import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
  9. import mix from '@ckeditor/ckeditor5-utils/src/mix';
  10. /**
  11. * The base class for CKEditor plugin classes.
  12. *
  13. * @implements module:core/plugin~PluginInterface
  14. * @mixes module:utils/observablemixin~ObservableMixin
  15. */
  16. export default class Plugin {
  17. /**
  18. * @inheritDoc
  19. */
  20. constructor( editor ) {
  21. /**
  22. * The editor instance.
  23. *
  24. * Note that most editors implement the {@link module:core/editor/editorwithui~EditorWithUI} interface in addition
  25. * to the base {@link module:core/editor/editor~Editor} interface. However, editors with an external UI
  26. * (i.e. Bootstrap-based) or a headless editor may not implement the {@link module:core/editor/editorwithui~EditorWithUI}
  27. * interface.
  28. *
  29. * Because of above, to make plugins more universal, it is recommended to split features into:
  30. * - The "editing" part that only uses the {@link module:core/editor/editor~Editor} interface.
  31. * - The "UI" part that uses both the {@link module:core/editor/editor~Editor} interface and
  32. * the {@link module:core/editor/editorwithui~EditorWithUI} interface.
  33. *
  34. * @readonly
  35. * @member {module:core/editor/editor~Editor} #editor
  36. */
  37. this.editor = editor;
  38. /**
  39. * Flag indicating whether a plugin is enabled or disabled.
  40. * A disabled plugin will not transform text.
  41. *
  42. * Plugin can be simply disabled like that:
  43. *
  44. * // Disable the plugin so that no toolbars are visible.
  45. * editor.plugins.get( 'TextTransformation' ).isEnabled = false;
  46. *
  47. * You can also use {@link #forceDisabled} method.
  48. *
  49. * @observable
  50. * @readonly
  51. * @member {Boolean} #isEnabled
  52. */
  53. this.set( 'isEnabled', true );
  54. /**
  55. * Holds identifiers for {@link #forceDisabled} mechanism.
  56. *
  57. * @type {Set.<String>}
  58. * @private
  59. */
  60. this._disableStack = new Set();
  61. }
  62. /**
  63. * Disables the plugin.
  64. *
  65. * Plugin may be disabled by multiple features or algorithms (at once). When disabling a plugin, unique id should be passed
  66. * (e.g. feature name). The same identifier should be used when {@link #clearForceDisabled enabling back} the plugin.
  67. * The plugin becomes enabled only after all features {@link #clearForceDisabled enabled it back}.
  68. *
  69. * Disabling and enabling a plugin:
  70. *
  71. * plugin.isEnabled; // -> true
  72. * plugin.forceDisabled( 'MyFeature' );
  73. * plugin.isEnabled; // -> false
  74. * plugin.clearForceDisabled( 'MyFeature' );
  75. * plugin.isEnabled; // -> true
  76. *
  77. * Plugin disabled by multiple features:
  78. *
  79. * plugin.forceDisabled( 'MyFeature' );
  80. * plugin.forceDisabled( 'OtherFeature' );
  81. * plugin.clearForceDisabled( 'MyFeature' );
  82. * plugin.isEnabled; // -> false
  83. * plugin.clearForceDisabled( 'OtherFeature' );
  84. * plugin.isEnabled; // -> true
  85. *
  86. * Multiple disabling with the same identifier is redundant:
  87. *
  88. * plugin.forceDisabled( 'MyFeature' );
  89. * plugin.forceDisabled( 'MyFeature' );
  90. * plugin.clearForceDisabled( 'MyFeature' );
  91. * plugin.isEnabled; // -> true
  92. *
  93. * **Note:** some plugins or algorithms may have more complex logic when it comes to enabling or disabling certain plugins,
  94. * so the plugin might be still disabled after {@link #clearForceDisabled} was used.
  95. *
  96. * @param {String} id Unique identifier for disabling. Use the same id when {@link #clearForceDisabled enabling back} the plugin.
  97. */
  98. forceDisabled( id ) {
  99. this._disableStack.add( id );
  100. if ( this._disableStack.size == 1 ) {
  101. this.on( 'set:isEnabled', forceDisable, { priority: 'highest' } );
  102. this.isEnabled = false;
  103. }
  104. }
  105. /**
  106. * Clears forced disable previously set through {@link #forceDisabled}. See {@link #forceDisabled}.
  107. *
  108. * @param {String} id Unique identifier, equal to the one passed in {@link #forceDisabled} call.
  109. */
  110. clearForceDisabled( id ) {
  111. this._disableStack.delete( id );
  112. if ( this._disableStack.size == 0 ) {
  113. this.off( 'set:isEnabled', forceDisable );
  114. this.isEnabled = true;
  115. }
  116. }
  117. /**
  118. * @inheritDoc
  119. */
  120. destroy() {
  121. this.stopListening();
  122. }
  123. /**
  124. * @inheritDoc
  125. */
  126. static get isContextPlugin() {
  127. return false;
  128. }
  129. }
  130. mix( Plugin, ObservableMixin );
  131. /**
  132. * The base interface for CKEditor plugins.
  133. *
  134. * In its minimal form a plugin can be a simple function that accepts {@link module:core/editor/editor~Editor the editor}
  135. * as a parameter:
  136. *
  137. * // A simple plugin that enables a data processor.
  138. * function MyPlugin( editor ) {
  139. * editor.data.processor = new MyDataProcessor();
  140. * }
  141. *
  142. * In most cases however, you will want to inherit from the {@link module:core/plugin~Plugin} class which implements the
  143. * {@link module:utils/observablemixin~ObservableMixin} and is, therefore, more convenient:
  144. *
  145. * class MyPlugin extends Plugin {
  146. * init() {
  147. * // `listenTo()` and `editor` are available thanks to `Plugin`.
  148. * // By using `listenTo()` you will ensure that the listener is removed when
  149. * // the plugin is destroyed.
  150. * this.listenTo( this.editor.data, 'ready', () => {
  151. * // Do something when the data is ready.
  152. * } );
  153. * }
  154. * }
  155. *
  156. * The plugin can also implement methods (e.g. {@link module:core/plugin~PluginInterface#init `init()`} or
  157. * {@link module:core/plugin~PluginInterface#destroy `destroy()`}) which, when present, will be used to properly
  158. * initialize and destroy the plugin.
  159. *
  160. * **Note:** When defined as a plain function, the plugin acts as a constructor and will be
  161. * called in parallel with other plugins' {@link module:core/plugin~PluginInterface#constructor constructors}.
  162. * This means the code of that plugin will be executed **before** {@link module:core/plugin~PluginInterface#init `init()`} and
  163. * {@link module:core/plugin~PluginInterface#afterInit `afterInit()`} methods of other plugins and, for instance,
  164. * you cannot use it to extend other plugins' {@glink framework/guides/architecture/editing-engine#schema schema}
  165. * rules as they are defined later on during the `init()` stage.
  166. *
  167. * @interface PluginInterface
  168. */
  169. /**
  170. * Creates a new plugin instance. This is the first step of the plugin initialization.
  171. * See also {@link #init} and {@link #afterInit}.
  172. *
  173. * A plugin is always instantiated after its {@link module:core/plugin~PluginInterface.requires dependencies} and the
  174. * {@link #init} and {@link #afterInit} methods are called in the same order.
  175. *
  176. * Usually, you will want to put your plugin's initialization code in the {@link #init} method.
  177. * The constructor can be understood as "before init" and used in special cases, just like
  178. * {@link #afterInit} serves the special "after init" scenarios (e.g.the code which depends on other
  179. * plugins, but which does not {@link module:core/plugin~PluginInterface.requires explicitly require} them).
  180. *
  181. * @method #constructor
  182. * @param {module:core/editor/editor~Editor} editor
  183. */
  184. /**
  185. * An array of plugins required by this plugin.
  186. *
  187. * To keep the plugin class definition tight it is recommended to define this property as a static getter:
  188. *
  189. * import Image from './image.js';
  190. *
  191. * export default class ImageCaption {
  192. * static get requires() {
  193. * return [ Image ];
  194. * }
  195. * }
  196. *
  197. * @static
  198. * @readonly
  199. * @member {Array.<Function>|undefined} module:core/plugin~PluginInterface.requires
  200. */
  201. /**
  202. * An optional name of the plugin. If set, the plugin will be available in
  203. * {@link module:core/plugincollection~PluginCollection#get} by its
  204. * name and its constructor. If not, then only by its constructor.
  205. *
  206. * The name should reflect the constructor name.
  207. *
  208. * To keep the plugin class definition tight, it is recommended to define this property as a static getter:
  209. *
  210. * export default class ImageCaption {
  211. * static get pluginName() {
  212. * return 'ImageCaption';
  213. * }
  214. * }
  215. *
  216. * Note: The native `Function.name` property could not be used to keep the plugin name because
  217. * it will be mangled during code minification.
  218. *
  219. * Naming a plugin is necessary to enable removing it through the
  220. * {@link module:core/editor/editorconfig~EditorConfig#removePlugins `config.removePlugins`} option.
  221. *
  222. * @static
  223. * @readonly
  224. * @member {String|undefined} module:core/plugin~PluginInterface.pluginName
  225. */
  226. /**
  227. * The second stage (after plugin {@link #constructor}) of the plugin initialization.
  228. * Unlike the plugin constructor this method can be asynchronous.
  229. *
  230. * A plugin's `init()` method is called after its {@link module:core/plugin~PluginInterface.requires dependencies} are initialized,
  231. * so in the same order as the constructors of these plugins.
  232. *
  233. * **Note:** This method is optional. A plugin instance does not need to have it defined.
  234. *
  235. * @method #init
  236. * @returns {null|Promise}
  237. */
  238. /**
  239. * The third (and last) stage of the plugin initialization. See also {@link #constructor} and {@link #init}.
  240. *
  241. * **Note:** This method is optional. A plugin instance does not need to have it defined.
  242. *
  243. * @method #afterInit
  244. * @returns {null|Promise}
  245. */
  246. /**
  247. * Destroys the plugin.
  248. *
  249. * **Note:** This method is optional. A plugin instance does not need to have it defined.
  250. *
  251. * @method #destroy
  252. * @returns {null|Promise}
  253. */
  254. /**
  255. * A flag which defines if a plugin is allowed or not allowed to be used directly by a {@link module:core/context~Context}.
  256. *
  257. * @static
  258. * @readonly
  259. * @member {Boolean} module:core/plugin~PluginInterface.isContextPlugin
  260. */
  261. /**
  262. * An array of loaded plugins.
  263. *
  264. * @typedef {Array.<module:core/plugin~PluginInterface>} module:core/plugin~LoadedPlugins
  265. */
  266. // Helper function that forces plugin to be disabled.
  267. function forceDisable( evt ) {
  268. evt.return = false;
  269. evt.stop();
  270. }