imageuploadpanelview.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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] Integrations object that contain
  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. * 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. * 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. this._integrations.add( integrationView );
  122. }
  123. }
  124. this.setTemplate( {
  125. tag: 'form',
  126. attributes: {
  127. class: [
  128. 'ck',
  129. 'ck-image-upload-form'
  130. ],
  131. tabindex: '-1'
  132. },
  133. children: [
  134. ...this._integrations,
  135. new ImageUploadFormRowView( locale, {
  136. children: [
  137. this.insertButtonView,
  138. this.cancelButtonView
  139. ],
  140. class: 'ck-image-upload-form__action-row'
  141. } )
  142. ]
  143. } );
  144. }
  145. /**
  146. * @inheritDoc
  147. */
  148. render() {
  149. super.render();
  150. submitHandler( {
  151. view: this
  152. } );
  153. const childViews = [
  154. ...this._integrations,
  155. this.insertButtonView,
  156. this.cancelButtonView
  157. ];
  158. childViews.forEach( v => {
  159. // Register the view as focusable.
  160. this._focusables.add( v );
  161. // Register the view in the focus tracker.
  162. this.focusTracker.add( v.element );
  163. } );
  164. // Start listening for the keystrokes coming from #element.
  165. this.keystrokes.listenTo( this.element );
  166. const stopPropagation = data => data.stopPropagation();
  167. // Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
  168. // keystroke handler would take over the key management in the URL input. We need to prevent
  169. // this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
  170. this.keystrokes.set( 'arrowright', stopPropagation );
  171. this.keystrokes.set( 'arrowleft', stopPropagation );
  172. this.keystrokes.set( 'arrowup', stopPropagation );
  173. this.keystrokes.set( 'arrowdown', stopPropagation );
  174. // Intercept the "selectstart" event, which is blocked by default because of the default behavior
  175. // of the DropdownView#panelView.
  176. // TODO: blocking "selectstart" in the #panelView should be configurable per–drop–down instance.
  177. this.listenTo( childViews[ 0 ].element, 'selectstart', ( evt, domEvt ) => {
  178. domEvt.stopPropagation();
  179. }, { priority: 'high' } );
  180. }
  181. /**
  182. * Creates dropdown view.
  183. *
  184. * @param {module:utils/locale~Locale} locale The localization services instance.
  185. *
  186. * @private
  187. * @returns {module:ui/dropdown/dropdownview~DropdownView}
  188. */
  189. _createDropdownView( locale ) {
  190. const t = locale.t;
  191. const dropdownView = createDropdown( locale, SplitButtonView );
  192. const splitButtonView = dropdownView.buttonView;
  193. const panelView = dropdownView.panelView;
  194. splitButtonView.set( {
  195. label: t( 'Insert image' ),
  196. icon: imageIcon,
  197. tooltip: true
  198. } );
  199. panelView.extendTemplate( {
  200. attributes: {
  201. class: 'ck-image-upload__panel'
  202. }
  203. } );
  204. return dropdownView;
  205. }
  206. /**
  207. * Creates the following form controls:
  208. *
  209. * * {@link #insertButtonView},
  210. * * {@link #cancelButtonView}.
  211. *
  212. * @param {module:utils/locale~Locale} locale The localization services instance.
  213. *
  214. * @private
  215. * @returns {Object.<String,module:ui/view~View>}
  216. */
  217. _createActionButtons( locale ) {
  218. const t = locale.t;
  219. const insertButtonView = new ButtonView( locale );
  220. const cancelButtonView = new ButtonView( locale );
  221. insertButtonView.set( {
  222. label: t( 'Insert' ),
  223. icon: checkIcon,
  224. class: 'ck-button-save',
  225. type: 'submit',
  226. withText: true,
  227. isEnabled: this.imageURLInputValue
  228. } );
  229. cancelButtonView.set( {
  230. label: t( 'Cancel' ),
  231. icon: cancelIcon,
  232. class: 'ck-button-cancel',
  233. withText: true
  234. } );
  235. insertButtonView.bind( 'isEnabled' ).to( this, 'imageURLInputValue' );
  236. insertButtonView.delegate( 'execute' ).to( this, 'submit' );
  237. cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
  238. return { insertButtonView, cancelButtonView };
  239. }
  240. /**
  241. * Focuses the fist {@link #_focusables} in the form.
  242. */
  243. focus() {
  244. this._focusCycler.focusFirst();
  245. }
  246. }
  247. /**
  248. * Fired when the form view is submitted (when one of the children triggered the submit event),
  249. * e.g. click on {@link #insertButtonView}.
  250. *
  251. * @event submit
  252. */
  253. /**
  254. * Fired when the form view is canceled, e.g. click on {@link #cancelButtonView}.
  255. *
  256. * @event cancel
  257. */