imageuploadpanelview.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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 image/imageupload/ui/imageuploadpanelview
  7. */
  8. import View from '@ckeditor/ckeditor5-ui/src/view';
  9. import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
  10. import SplitButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview';
  11. import ImageUploadFormRowView from './imageuploadformrowview';
  12. import { createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
  13. import Collection from '@ckeditor/ckeditor5-utils/src/collection';
  14. import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection';
  15. import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler';
  16. import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
  17. import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler';
  18. import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
  19. import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg';
  20. import checkIcon from '@ckeditor/ckeditor5-core/theme/icons/check.svg';
  21. import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg';
  22. import '../../../theme/imageupload.css';
  23. /**
  24. * The insert an image via URL view controller class.
  25. *
  26. * See {@link module:image/imageupload/ui/imageuploadpanelview~ImageUploadPanelView}.
  27. *
  28. * @extends module:ui/view~View
  29. */
  30. export default class ImageUploadPanelView extends View {
  31. /**
  32. * Creates a view for the dropdown panel of {@link module:image/imageupload/imageuploadui~ImageUploadUI}.
  33. *
  34. * @param {module:utils/locale~Locale} [locale] The localization services instance.
  35. * @param {Object} [integrations] An integrations object that contains
  36. * components (or tokens for components) to be shown in the panel view.
  37. */
  38. constructor( locale, integrations ) {
  39. super( locale );
  40. const { insertButtonView, cancelButtonView } = this._createActionButtons( locale );
  41. /**
  42. * The "insert/update" button view.
  43. *
  44. * @member {module:ui/button/buttonview~ButtonView}
  45. */
  46. this.insertButtonView = insertButtonView;
  47. /**
  48. * The "cancel" button view.
  49. *
  50. * @member {module:ui/button/buttonview~ButtonView}
  51. */
  52. this.cancelButtonView = cancelButtonView;
  53. /**
  54. * The dropdown view.
  55. *
  56. * @member {module:ui/dropdown/dropdownview~DropdownView}
  57. */
  58. this.dropdownView = this._createDropdownView( locale );
  59. /**
  60. * The value of the URL input.
  61. *
  62. * @member {String} #imageURLInputValue
  63. * @observable
  64. */
  65. this.set( 'imageURLInputValue', '' );
  66. /**
  67. * Tracks information about DOM focus in the form.
  68. *
  69. * @readonly
  70. * @member {module:utils/focustracker~FocusTracker}
  71. */
  72. this.focusTracker = new FocusTracker();
  73. /**
  74. * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
  75. *
  76. * @readonly
  77. * @member {module:utils/keystrokehandler~KeystrokeHandler}
  78. */
  79. this.keystrokes = new KeystrokeHandler();
  80. /**
  81. * A collection of views that can be focused in the form.
  82. *
  83. * @readonly
  84. * @protected
  85. * @member {module:ui/viewcollection~ViewCollection}
  86. */
  87. this._focusables = new ViewCollection();
  88. /**
  89. * Helps cycling over {@link #_focusables} in the form.
  90. *
  91. * @readonly
  92. * @protected
  93. * @member {module:ui/focuscycler~FocusCycler}
  94. */
  95. this._focusCycler = new FocusCycler( {
  96. focusables: this._focusables,
  97. focusTracker: this.focusTracker,
  98. keystrokeHandler: this.keystrokes,
  99. actions: {
  100. // Navigate form fields backwards using the Shift + Tab keystroke.
  101. focusPrevious: 'shift + tab',
  102. // Navigate form fields forwards using the Tab key.
  103. focusNext: 'tab'
  104. }
  105. } );
  106. /**
  107. * A collection of the defined integrations for inserting the images.
  108. *
  109. * @private
  110. * @member {module:utils/collection~Collection}
  111. */
  112. this.set( '_integrations', new Collection() );
  113. if ( integrations ) {
  114. for ( const [ integration, integrationView ] of Object.entries( integrations ) ) {
  115. if ( integration === 'insertImageViaUrl' ) {
  116. integrationView.fieldView.bind( 'value' ).to( this, 'imageURLInputValue', value => value || '' );
  117. integrationView.fieldView.on( 'input', () => {
  118. this.imageURLInputValue = integrationView.fieldView.element.value;
  119. } );
  120. }
  121. integrationView.name = integration;
  122. this._integrations.add( integrationView );
  123. }
  124. }
  125. this.setTemplate( {
  126. tag: 'form',
  127. attributes: {
  128. class: [
  129. 'ck',
  130. 'ck-image-upload-form'
  131. ],
  132. tabindex: '-1'
  133. },
  134. children: [
  135. ...this._integrations,
  136. new ImageUploadFormRowView( locale, {
  137. children: [
  138. this.insertButtonView,
  139. this.cancelButtonView
  140. ],
  141. class: 'ck-image-upload-form__action-row'
  142. } )
  143. ]
  144. } );
  145. }
  146. /**
  147. * @inheritDoc
  148. */
  149. render() {
  150. super.render();
  151. submitHandler( {
  152. view: this
  153. } );
  154. const childViews = [
  155. ...this._integrations,
  156. this.insertButtonView,
  157. this.cancelButtonView
  158. ];
  159. childViews.forEach( v => {
  160. // Register the view as focusable.
  161. this._focusables.add( v );
  162. // Register the view in the focus tracker.
  163. this.focusTracker.add( v.element );
  164. } );
  165. // Start listening for the keystrokes coming from #element.
  166. this.keystrokes.listenTo( this.element );
  167. const stopPropagation = data => data.stopPropagation();
  168. // Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
  169. // keystroke handler would take over the key management in the URL input. We need to prevent
  170. // this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
  171. this.keystrokes.set( 'arrowright', stopPropagation );
  172. this.keystrokes.set( 'arrowleft', stopPropagation );
  173. this.keystrokes.set( 'arrowup', stopPropagation );
  174. this.keystrokes.set( 'arrowdown', stopPropagation );
  175. // Intercept the "selectstart" event, which is blocked by default because of the default behavior
  176. // of the DropdownView#panelView.
  177. // TODO: blocking "selectstart" in the #panelView should be configurable per–drop–down instance.
  178. this.listenTo( childViews[ 0 ].element, 'selectstart', ( evt, domEvt ) => {
  179. domEvt.stopPropagation();
  180. }, { priority: 'high' } );
  181. }
  182. /**
  183. * Returns a view of the integration.
  184. *
  185. * @param {String} name The name of the integration.
  186. * @returns {module:ui/view~View}
  187. */
  188. getIntegration( name ) {
  189. return this._integrations.find( integration => integration.name === name );
  190. }
  191. /**
  192. * Creates the dropdown view.
  193. *
  194. * @param {module:utils/locale~Locale} locale The localization services instance.
  195. *
  196. * @private
  197. * @returns {module:ui/dropdown/dropdownview~DropdownView}
  198. */
  199. _createDropdownView( locale ) {
  200. const t = locale.t;
  201. const dropdownView = createDropdown( locale, SplitButtonView );
  202. const splitButtonView = dropdownView.buttonView;
  203. const panelView = dropdownView.panelView;
  204. splitButtonView.set( {
  205. label: t( 'Insert image' ),
  206. icon: imageIcon,
  207. tooltip: true
  208. } );
  209. panelView.extendTemplate( {
  210. attributes: {
  211. class: 'ck-image-upload__panel'
  212. }
  213. } );
  214. return dropdownView;
  215. }
  216. /**
  217. * Creates the following form controls:
  218. *
  219. * * {@link #insertButtonView},
  220. * * {@link #cancelButtonView}.
  221. *
  222. * @param {module:utils/locale~Locale} locale The localization services instance.
  223. *
  224. * @private
  225. * @returns {Object.<String,module:ui/view~View>}
  226. */
  227. _createActionButtons( locale ) {
  228. const t = locale.t;
  229. const insertButtonView = new ButtonView( locale );
  230. const cancelButtonView = new ButtonView( locale );
  231. insertButtonView.set( {
  232. label: t( 'Insert' ),
  233. icon: checkIcon,
  234. class: 'ck-button-save',
  235. type: 'submit',
  236. withText: true,
  237. isEnabled: this.imageURLInputValue
  238. } );
  239. cancelButtonView.set( {
  240. label: t( 'Cancel' ),
  241. icon: cancelIcon,
  242. class: 'ck-button-cancel',
  243. withText: true
  244. } );
  245. insertButtonView.bind( 'isEnabled' ).to( this, 'imageURLInputValue' );
  246. insertButtonView.delegate( 'execute' ).to( this, 'submit' );
  247. cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
  248. return { insertButtonView, cancelButtonView };
  249. }
  250. /**
  251. * Focuses the first {@link #_focusables focusable} in the form.
  252. */
  253. focus() {
  254. this._focusCycler.focusFirst();
  255. }
  256. }
  257. /**
  258. * Fired when the form view is submitted (when one of the children triggered the submit event),
  259. * e.g. by a click on {@link #insertButtonView}.
  260. *
  261. * @event submit
  262. */
  263. /**
  264. * Fired when the form view is canceled, e.g. by a click on {@link #cancelButtonView}.
  265. *
  266. * @event cancel
  267. */