block-widget.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. /* globals console, window, document */
  6. import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
  7. import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
  8. import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
  9. import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
  10. import Heading from '@ckeditor/ckeditor5-heading/src/heading';
  11. import List from '@ckeditor/ckeditor5-list/src/list';
  12. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  13. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  14. import { toWidget, toWidgetEditable } from '@ckeditor/ckeditor5-widget/src/utils';
  15. import Widget from '@ckeditor/ckeditor5-widget/src/widget';
  16. import Command from '@ckeditor/ckeditor5-core/src/command';
  17. import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
  18. class SimpleBox extends Plugin {
  19. static get requires() {
  20. return [ SimpleBoxEditing, SimpleBoxUI ];
  21. }
  22. }
  23. class SimpleBoxUI extends Plugin {
  24. init() {
  25. console.log( 'SimpleBoxUI#init() got called' );
  26. const editor = this.editor;
  27. const t = editor.t;
  28. // The "simpleBox" button must be registered among the UI components of the editor
  29. // to be displayed in the toolbar.
  30. editor.ui.componentFactory.add( 'simpleBox', locale => {
  31. // The state of the button will be bound to the widget command.
  32. const command = editor.commands.get( 'insertSimpleBox' );
  33. // The button will be an instance of ButtonView.
  34. const buttonView = new ButtonView( locale );
  35. buttonView.set( {
  36. // The t() function helps localize the editor. All strings enclosed in t() can be
  37. // translated and change when the language of the editor changes.
  38. label: t( 'Simple Box' ),
  39. withText: true,
  40. tooltip: true
  41. } );
  42. // Bind the state of the button to the command.
  43. buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
  44. // Execute the command when the button is clicked (executed).
  45. this.listenTo( buttonView, 'execute', () => {
  46. editor.execute( 'insertSimpleBox' );
  47. editor.editing.view.focus();
  48. } );
  49. return buttonView;
  50. } );
  51. }
  52. }
  53. class SimpleBoxEditing extends Plugin {
  54. static get requires() {
  55. return [ Widget ];
  56. }
  57. init() {
  58. console.log( 'SimpleBoxEditing#init() got called' );
  59. this._defineSchema();
  60. this._defineConverters();
  61. this.editor.commands.add( 'insertSimpleBox', new InsertSimpleBoxCommand( this.editor ) );
  62. }
  63. _defineSchema() {
  64. const schema = this.editor.model.schema;
  65. schema.register( 'simpleBox', {
  66. // Behaves like a self-contained object (e.g. an image).
  67. isObject: true,
  68. // Allow in places where other blocks are allowed (e.g. directly in the root).
  69. allowWhere: '$block'
  70. } );
  71. schema.register( 'simpleBoxTitle', {
  72. // Cannot be split or left by the caret.
  73. isLimit: true,
  74. allowIn: 'simpleBox',
  75. // Allow content which is allowed in blocks (i.e. text with attributes).
  76. allowContentOf: '$block'
  77. } );
  78. schema.register( 'simpleBoxDescription', {
  79. // Cannot be split or left by the caret.
  80. isLimit: true,
  81. allowIn: 'simpleBox',
  82. // Allow content which is allowed in the root (e.g. paragraphs).
  83. allowContentOf: '$root'
  84. } );
  85. schema.addChildCheck( ( context, childDefinition ) => {
  86. if ( context.endsWith( 'simpleBoxDescription' ) && childDefinition.name == 'simpleBox' ) {
  87. return false;
  88. }
  89. } );
  90. }
  91. _defineConverters() {
  92. const conversion = this.editor.conversion;
  93. // <simpleBox> converters
  94. conversion.for( 'upcast' ).elementToElement( {
  95. model: 'simpleBox',
  96. view: {
  97. name: 'section',
  98. classes: 'simple-box'
  99. }
  100. } );
  101. conversion.for( 'dataDowncast' ).elementToElement( {
  102. model: 'simpleBox',
  103. view: {
  104. name: 'section',
  105. classes: 'simple-box'
  106. }
  107. } );
  108. conversion.for( 'editingDowncast' ).elementToElement( {
  109. model: 'simpleBox',
  110. view: ( modelElement, viewWriter ) => {
  111. const section = viewWriter.createContainerElement( 'section', { class: 'simple-box' } );
  112. return toWidget( section, viewWriter, { label: 'simple box widget' } );
  113. }
  114. } );
  115. // <simpleBoxTitle> converters
  116. conversion.for( 'upcast' ).elementToElement( {
  117. model: 'simpleBoxTitle',
  118. view: {
  119. name: 'h1',
  120. classes: 'simple-box-title'
  121. }
  122. } );
  123. conversion.for( 'dataDowncast' ).elementToElement( {
  124. model: 'simpleBoxTitle',
  125. view: {
  126. name: 'h1',
  127. classes: 'simple-box-title'
  128. }
  129. } );
  130. conversion.for( 'editingDowncast' ).elementToElement( {
  131. model: 'simpleBoxTitle',
  132. view: ( modelElement, viewWriter ) => {
  133. // Note: You use a more specialized createEditableElement() method here.
  134. const h1 = viewWriter.createEditableElement( 'h1', { class: 'simple-box-title' } );
  135. return toWidgetEditable( h1, viewWriter );
  136. }
  137. } );
  138. // <simpleBoxDescription> converters
  139. conversion.for( 'upcast' ).elementToElement( {
  140. model: 'simpleBoxDescription',
  141. view: {
  142. name: 'div',
  143. classes: 'simple-box-description'
  144. }
  145. } );
  146. conversion.for( 'dataDowncast' ).elementToElement( {
  147. model: 'simpleBoxDescription',
  148. view: {
  149. name: 'div',
  150. classes: 'simple-box-description'
  151. }
  152. } );
  153. conversion.for( 'editingDowncast' ).elementToElement( {
  154. model: 'simpleBoxDescription',
  155. view: ( modelElement, viewWriter ) => {
  156. // Note: You use a more specialized createEditableElement() method here.
  157. const div = viewWriter.createEditableElement( 'div', { class: 'simple-box-description' } );
  158. return toWidgetEditable( div, viewWriter );
  159. }
  160. } );
  161. }
  162. }
  163. class InsertSimpleBoxCommand extends Command {
  164. execute() {
  165. this.editor.model.change( writer => {
  166. // Insert <simpleBox>*</simpleBox> at the current selection position
  167. // in a way that will result in creating a valid model structure.
  168. this.editor.model.insertContent( createSimpleBox( writer ) );
  169. } );
  170. }
  171. refresh() {
  172. const model = this.editor.model;
  173. const selection = model.document.selection;
  174. const allowedIn = model.schema.findAllowedParent( selection.getFirstPosition(), 'simpleBox' );
  175. this.isEnabled = allowedIn !== null;
  176. }
  177. }
  178. function createSimpleBox( writer ) {
  179. const simpleBox = writer.createElement( 'simpleBox' );
  180. const simpleBoxTitle = writer.createElement( 'simpleBoxTitle' );
  181. const simpleBoxDescription = writer.createElement( 'simpleBoxDescription' );
  182. writer.append( simpleBoxTitle, simpleBox );
  183. writer.append( simpleBoxDescription, simpleBox );
  184. // There must be at least one paragraph for the description to be editable.
  185. // See https://github.com/ckeditor/ckeditor5/issues/1464.
  186. writer.appendElement( 'paragraph', simpleBoxDescription );
  187. return simpleBox;
  188. }
  189. ClassicEditor
  190. .create( document.querySelector( '#snippet-block-widget' ), {
  191. plugins: [ Essentials, Bold, Italic, Heading, List, Paragraph, SimpleBox ],
  192. toolbar: {
  193. items: [ 'heading', '|', 'bold', 'italic', 'numberedList', 'bulletedList', 'simpleBox' ],
  194. viewportTopOffset: window.getViewportTopOffsetConfig()
  195. }
  196. } )
  197. .then( editor => {
  198. console.log( 'Editor was initialized', editor );
  199. window.editor = editor;
  200. } )
  201. .catch( err => {
  202. console.error( err.stack );
  203. } );