8
0

mention-custom-view.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* global console, window */
  6. import global from '@ckeditor/ckeditor5-utils/src/dom/global';
  7. import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
  8. import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
  9. import Enter from '@ckeditor/ckeditor5-enter/src/enter';
  10. import Heading from '@ckeditor/ckeditor5-heading/src/heading';
  11. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  12. import Typing from '@ckeditor/ckeditor5-typing/src/typing';
  13. import Undo from '@ckeditor/ckeditor5-undo/src/undo';
  14. import Widget from '@ckeditor/ckeditor5-widget/src/widget';
  15. import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
  16. import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
  17. import Table from '@ckeditor/ckeditor5-table/src/table';
  18. import Link from '@ckeditor/ckeditor5-link/src/link';
  19. import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
  20. import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
  21. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  22. import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
  23. import ModelSelection from '@ckeditor/ckeditor5-engine/src/model/selection';
  24. import DocumentSelection from '@ckeditor/ckeditor5-engine/src/model/documentselection';
  25. import MentionUI from '../../src/mentionui';
  26. // eslint-disable-next-line no-unused-vars
  27. import MentionEditing from '../../src/mentionediting';
  28. // eslint-disable-next-line no-unused-vars
  29. import MentionElementEditing from '../../src/mentioneditingelement';
  30. // eslint-disable-next-line no-unused-vars
  31. import MentionMarkerEditing, { createMentionMarkerId } from '../../src/mentioneditingmarker';
  32. const HIGHER_THEN_HIGHEST = priorities.highest + 50;
  33. // eslint-disable-next-line no-unused-vars
  34. class CustomMentionAttributeView extends Plugin {
  35. init() {
  36. const editor = this.editor;
  37. editor.conversion.for( 'upcast' ).elementToAttribute( {
  38. view: {
  39. name: 'a',
  40. key: 'data-mention',
  41. classes: 'mention',
  42. attributes: {
  43. href: true
  44. }
  45. },
  46. model: {
  47. key: 'mention',
  48. value: viewItem => {
  49. const mentionValue = {
  50. label: viewItem.getAttribute( 'data-mention' ),
  51. link: viewItem.getAttribute( 'href' )
  52. };
  53. return mentionValue;
  54. }
  55. },
  56. converterPriority: HIGHER_THEN_HIGHEST
  57. } );
  58. editor.conversion.for( 'downcast' ).attributeToElement( {
  59. model: 'mention',
  60. view: ( modelAttributeValue, viewWriter ) => {
  61. if ( !modelAttributeValue ) {
  62. return;
  63. }
  64. return viewWriter.createAttributeElement( 'a', {
  65. class: 'mention',
  66. 'data-mention': modelAttributeValue.label,
  67. 'href': modelAttributeValue.link
  68. } );
  69. },
  70. converterPriority: HIGHER_THEN_HIGHEST
  71. } );
  72. }
  73. }
  74. // eslint-disable-next-line no-unused-vars
  75. class CustomMentionElementView extends Plugin {
  76. init() {
  77. const editor = this.editor;
  78. editor.conversion.for( 'upcast' ).elementToElement( {
  79. view: {
  80. name: 'a',
  81. classes: 'mention',
  82. attributes: {
  83. href: true
  84. }
  85. },
  86. model: ( viewItem, modelWriter ) => {
  87. const item = {
  88. label: viewItem.getAttribute( 'data-mention' ),
  89. link: viewItem.getAttribute( 'href' )
  90. };
  91. const frag = modelWriter.createDocumentFragment();
  92. const mention = modelWriter.createElement( 'mention', { item } );
  93. modelWriter.insert( mention, frag, 0 );
  94. modelWriter.insertText( item.label, mention, 0 );
  95. return mention;
  96. },
  97. converterPriority: HIGHER_THEN_HIGHEST
  98. } );
  99. editor.conversion.for( 'downcast' ).elementToElement( {
  100. model: 'mentionElement',
  101. view: ( modelElement, viewWriter ) => {
  102. const item = modelElement.getAttribute( 'item' );
  103. const link = viewWriter.createContainerElement( 'a', {
  104. class: 'mention',
  105. 'data-mention': item.label,
  106. 'href': item.link
  107. } );
  108. return link;
  109. },
  110. converterPriority: HIGHER_THEN_HIGHEST
  111. } );
  112. }
  113. }
  114. // eslint-disable-next-line no-unused-vars
  115. class CustomMentionMarkerView extends Plugin {
  116. init() {
  117. const editor = this.editor;
  118. // Convert marker v->m.
  119. editor.conversion.for( 'upcast' ).elementToMarker( {
  120. view: {
  121. name: 'a',
  122. attribute: {
  123. 'data-mention': /^\w/
  124. }
  125. },
  126. model: viewElement => createMentionMarkerId( viewElement.getAttribute( 'data-mention' ) )
  127. } );
  128. editor.conversion.for( 'downcast' ).add( dispatcher => {
  129. dispatcher.on( 'addMarker:mention', ( evt, data, conversionApi ) => {
  130. const { id } = data.markerName.split( ':' )[ 1 ];
  131. if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
  132. return;
  133. }
  134. const viewWriter = conversionApi.writer;
  135. // OMG I just wanted to change `<span>` to `<a>`...
  136. const viewElement = viewWriter.createAttributeElement( 'a', {
  137. class: [ 'mention' ],
  138. 'data-mention': id
  139. } );
  140. const viewSelection = viewWriter.document.selection;
  141. if ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection ) {
  142. viewWriter.wrap( viewSelection.getFirstRange(), viewElement, viewSelection );
  143. } else {
  144. const viewRange = conversionApi.mapper.toViewRange( data.range );
  145. const rangeAfterWrap = viewWriter.wrap( viewRange, viewElement );
  146. for ( const element of rangeAfterWrap.getItems() ) {
  147. if ( element.is( 'attributeElement' ) && element.isSimilar( viewElement ) ) {
  148. conversionApi.mapper.bindElementToMarker( element, data.markerName );
  149. // One attribute element is enough, because all of them are bound together by the view writer.
  150. // Mapper uses this binding to get all the elements no matter how many of them are registered in the mapper.
  151. break;
  152. }
  153. }
  154. }
  155. }, { priority: HIGHER_THEN_HIGHEST } );
  156. } );
  157. }
  158. }
  159. ClassicEditor
  160. .create( global.document.querySelector( '#editor' ), {
  161. plugins: [ Enter, Typing, Paragraph, Link, Heading, Bold, Italic, Underline, Undo, Clipboard, Widget, ShiftEnter, Table,
  162. // 1. Using attributes
  163. // MentionEditing,
  164. // CustomMentionAttributeView,
  165. // 2. Using Element - buggy
  166. // MentionElementEditing,
  167. // CustomMentionElementView,
  168. // 3. Using marker - promising
  169. MentionMarkerEditing,
  170. CustomMentionMarkerView,
  171. // Common: ui
  172. MentionUI
  173. ],
  174. toolbar: [ 'heading', '|', 'bold', 'italic', 'underline', 'link', '|', 'insertTable', '|', 'undo', 'redo' ],
  175. mention: [
  176. {
  177. feed: getFeed
  178. }
  179. ]
  180. } )
  181. .then( editor => {
  182. window.editor = editor;
  183. } )
  184. .catch( err => {
  185. console.error( err.stack );
  186. } );
  187. function getFeed( feedText ) {
  188. return Promise.resolve( [
  189. { id: '1', label: 'Barney Stinson', link: 'https://www.imdb.com/title/tt0460649/characters/nm0000439' },
  190. { id: '2', label: 'Lily Aldrin', link: 'https://www.imdb.com/title/tt0460649/characters/nm0004989' },
  191. { id: '3', label: 'Marshall Eriksen', link: 'https://www.imdb.com/title/tt0460649/characters/nm0781981' },
  192. { id: '4', label: 'Robin Scherbatsky', link: 'https://www.imdb.com/title/tt0460649/characters/nm1130627' },
  193. { id: '5', label: 'Ted Mosby', link: 'https://www.imdb.com/title/tt0460649/characters/nm1102140' }
  194. ].filter( item => {
  195. const searchString = feedText.toLowerCase();
  196. return item.label.toLowerCase().includes( searchString );
  197. } ) );
  198. }