imagestyleengine.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module image/imagestyle/imagestyleengine
  7. */
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import ImageStyleCommand from './imagestylecommand';
  10. import ImageEngine from '../image/imageengine';
  11. import { viewToModelStyleAttribute, modelToViewStyleAttribute } from './converters';
  12. import log from '@ckeditor/ckeditor5-utils/src/log';
  13. import fullWidthIcon from '@ckeditor/ckeditor5-core/theme/icons/object-full-width.svg';
  14. import leftIcon from '@ckeditor/ckeditor5-core/theme/icons/object-left.svg';
  15. import centerIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg';
  16. import rightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.svg';
  17. /**
  18. * The image style engine plugin. It sets the default configuration, creates converters and registers
  19. * {@link module:image/imagestyle/imagestylecommand~ImageStyleCommand ImageStyleCommand}.
  20. *
  21. * @extends {module:core/plugin~Plugin}
  22. */
  23. export default class ImageStyleEngine extends Plugin {
  24. /**
  25. * @inheritDoc
  26. */
  27. static get requires() {
  28. return [ ImageEngine ];
  29. }
  30. /**
  31. * @inheritDoc
  32. */
  33. static get pluginName() {
  34. return 'ImageStyleEngine';
  35. }
  36. /**
  37. * @inheritDoc
  38. */
  39. init() {
  40. const editor = this.editor;
  41. const schema = editor.model.schema;
  42. const data = editor.data;
  43. const editing = editor.editing;
  44. // Define default configuration.
  45. editor.config.define( 'image.styles', [ 'imageStyleFull', 'imageStyleSide' ] );
  46. // Get configuration.
  47. const styles = this.imageStyles;
  48. // Allow imageStyle attribute in image.
  49. // We could call it 'style' but https://github.com/ckeditor/ckeditor5-engine/issues/559.
  50. schema.extend( 'image', { allowAttributes: 'imageStyle' } );
  51. // Converters for imageStyle attribute from model to view.
  52. const modelToViewConverter = modelToViewStyleAttribute( styles );
  53. editing.downcastDispatcher.on( 'attribute:imageStyle:image', modelToViewConverter );
  54. data.downcastDispatcher.on( 'attribute:imageStyle:image', modelToViewConverter );
  55. // Converter for figure element from view to model.
  56. data.upcastDispatcher.on( 'element:figure', viewToModelStyleAttribute( styles ), { priority: 'low' } );
  57. // Register separate command for each style.
  58. for ( const style of styles ) {
  59. editor.commands.add( style.name, new ImageStyleCommand( editor, style ) );
  60. }
  61. }
  62. /**
  63. * Returns {@link module:image/image~ImageConfig#styles} array with items normalized in the
  64. * {@link module:image/imagestyle/imagestyleengine~ImageStyleFormat} format, translated
  65. * `title` and a complete `icon` markup for each style.
  66. *
  67. * @readonly
  68. * @type {Array.<module:image/imagestyle/imagestyleengine~ImageStyleFormat>}
  69. */
  70. get imageStyles() {
  71. // Return cached value if there is one to improve the performance.
  72. if ( this._cachedImageStyles ) {
  73. return this._cachedImageStyles;
  74. }
  75. const styles = [];
  76. const editor = this.editor;
  77. const titles = this.localizedDefaultStylesTitles;
  78. const configuredStyles = editor.config.get( 'image.styles' );
  79. for ( let style of configuredStyles ) {
  80. style = normalizeStyle( style );
  81. // Localize the titles of the styles, if a title corresponds with
  82. // a localized default provided by the plugin.
  83. if ( titles[ style.title ] ) {
  84. style.title = titles[ style.title ];
  85. }
  86. // Don't override the user-defined styles array, clone it instead.
  87. styles.push( style );
  88. }
  89. return ( this._cachedImageStyles = styles );
  90. }
  91. /**
  92. * Returns the default localized style titles provided by the plugin e.g. ready to
  93. * use in the {@link #imageStyles}.
  94. *
  95. * The following localized titles corresponding with
  96. * {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine.defaultStyles} are available:
  97. *
  98. * * `'Full size image'`,
  99. * * `'Side image'`,
  100. * * `'Left aligned image'`,
  101. * * `'Centered image'`,
  102. * * `'Right aligned image'`
  103. *
  104. * @readonly
  105. * @type {Object.<String,String>}
  106. */
  107. get localizedDefaultStylesTitles() {
  108. const t = this.editor.t;
  109. return {
  110. 'Full size image': t( 'Full size image' ),
  111. 'Side image': t( 'Side image' ),
  112. 'Left aligned image': t( 'Left aligned image' ),
  113. 'Centered image': t( 'Centered image' ),
  114. 'Right aligned image': t( 'Right aligned image' ),
  115. };
  116. }
  117. }
  118. /**
  119. * Default image styles provided by the plugin, which can be referred in the
  120. * {@link module:image/image~ImageConfig#styles} config.
  121. *
  122. * Among them, 2 default semantic content styles are available:
  123. *
  124. * * `imageStyleFull` is a full–width image without any CSS class,
  125. * * `imageStyleSide` is a side image styled with the `image-style-side` CSS class
  126. *
  127. * There are also 3 styles focused on formatting:
  128. *
  129. * * `imageStyleAlignLeft` aligns the image to the left using the `image-style-align-left` class,
  130. * * `imageStyleAlignCenter` centers the image to the left using the `image-style-align-center` class,
  131. * * `imageStyleAlignRight` aligns the image to the right using the `image-style-align-right` class,
  132. *
  133. * @member {Object.<String,Object>}
  134. */
  135. ImageStyleEngine.defaultStyles = {
  136. // This option is equal to situation when no style is applied.
  137. imageStyleFull: {
  138. name: 'imageStyleFull',
  139. title: 'Full size image',
  140. icon: fullWidthIcon,
  141. isDefault: true
  142. },
  143. // This represents side image.
  144. imageStyleSide: {
  145. name: 'imageStyleSide',
  146. title: 'Side image',
  147. icon: rightIcon,
  148. className: 'image-style-side'
  149. },
  150. // This style represents an imaged aligned to the left.
  151. imageStyleAlignLeft: {
  152. name: 'imageStyleAlignLeft',
  153. title: 'Left aligned image',
  154. icon: leftIcon,
  155. className: 'image-style-align-left'
  156. },
  157. // This style represents a centered imaged.
  158. imageStyleAlignCenter: {
  159. name: 'imageStyleAlignCenter',
  160. title: 'Centered image',
  161. icon: centerIcon,
  162. className: 'image-style-align-center'
  163. },
  164. // This style represents an imaged aligned to the right.
  165. imageStyleAlignRight: {
  166. name: 'imageStyleAlignRight',
  167. title: 'Right aligned image',
  168. icon: rightIcon,
  169. className: 'image-style-align-right'
  170. }
  171. };
  172. /**
  173. * Default image style icons provided by the plugin, which can be referred in the
  174. * {@link module:image/image~ImageConfig#styles} config.
  175. *
  176. * There are 4 icons available: `'full'`, `'left'`, `'center'` and `'right'`.
  177. *
  178. * @member {Object.<String, String>}
  179. */
  180. ImageStyleEngine.defaultIcons = {
  181. full: fullWidthIcon,
  182. left: leftIcon,
  183. right: rightIcon,
  184. center: centerIcon,
  185. };
  186. // Normalizes an image style provided in the {@link module:image/image~ImageConfig#styles}
  187. // and returns it in a {@link module:image/imagestyle/imagestyleengine~ImageStyleFormat}.
  188. //
  189. // @private
  190. // @param {Object} style
  191. // @returns {@link module:image/imagestyle/imagestyleengine~ImageStyleFormat}
  192. function normalizeStyle( style ) {
  193. const defaultStyles = ImageStyleEngine.defaultStyles;
  194. const defaultIcons = ImageStyleEngine.defaultIcons;
  195. // Just the name of the style has been passed.
  196. if ( typeof style == 'string' ) {
  197. // If it's one of the defaults, just use it.
  198. // Clone the style to avoid overriding defaults.
  199. if ( defaultStyles[ style ] ) {
  200. style = Object.assign( {}, defaultStyles[ style ] );
  201. }
  202. // If it's just a name but none of the defaults, warn because probably it's a mistake.
  203. else {
  204. log.warn(
  205. 'image-style-not-found: There is no such image style of given name.',
  206. { name: style }
  207. );
  208. // Normalize the style anyway to prevent errors.
  209. style = {
  210. name: style
  211. };
  212. }
  213. }
  214. // If an object style has been passed and if the name matches one of the defaults,
  215. // extend it with defaults – the user wants to customize a default style.
  216. // Note: Don't override the user–defined style object, clone it instead.
  217. else if ( defaultStyles[ style.name ] ) {
  218. const defaultStyle = defaultStyles[ style.name ];
  219. const extendedStyle = Object.assign( {}, style );
  220. for ( const prop in defaultStyle ) {
  221. if ( !style.hasOwnProperty( prop ) ) {
  222. extendedStyle[ prop ] = defaultStyle[ prop ];
  223. }
  224. }
  225. style = extendedStyle;
  226. }
  227. // If an icon is defined as a string and correspond with a name
  228. // in default icons, use the default icon provided by the plugin.
  229. if ( typeof style.icon == 'string' && defaultIcons[ style.icon ] ) {
  230. style.icon = defaultIcons[ style.icon ];
  231. }
  232. return style;
  233. }
  234. /**
  235. * Image style format descriptor.
  236. *
  237. * import fullWidthIcon from 'path/to/icon.svg`;
  238. *
  239. * const imageStyleFormat = {
  240. * name: 'fullSizeImage',
  241. * icon: fullWidthIcon,
  242. * title: 'Full size image',
  243. * className: 'image-full-size'
  244. * }
  245. *
  246. * @typedef {Object} module:image/imagestyle/imagestyleengine~ImageStyleFormat
  247. * @property {String} name The unique name of the style. It will be used to:
  248. * * register the {@link module:core/command~Command command} which will apply this style,
  249. * * store the style's button in the editor {@link module:ui/componentfactory~ComponentFactory},
  250. * * store the style in the `imageStyle` model attribute.
  251. * @property {Boolean} [isDefault] When set, the style will be used as the default one.
  252. * A default style does not apply any CSS class to the view element.
  253. * @property {String} icon One of the following to be used when creating the style's button:
  254. * * An SVG icon source (as an XML string),
  255. * * One of {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine.defaultIcons} to use a default icon provided by the plugin.
  256. * @property {String} title The style's title.
  257. * @property {String} className The CSS class used to represent the style in view.
  258. */