plugin.js 98 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or http://ckeditor.com/license
  4. */
  5. /**
  6. * @ignore
  7. * File overview: Clipboard support.
  8. */
  9. //
  10. // COPY & PASTE EXECUTION FLOWS:
  11. // -- CTRL+C
  12. // * if ( isCustomCopyCutSupported )
  13. // * dataTransfer.setData( 'text/html', getSelectedHtml )
  14. // * else
  15. // * browser's default behavior
  16. // -- CTRL+X
  17. // * listen onKey (onkeydown)
  18. // * fire 'saveSnapshot' on editor
  19. // * if ( isCustomCopyCutSupported )
  20. // * dataTransfer.setData( 'text/html', getSelectedHtml )
  21. // * extractSelectedHtml // remove selected contents
  22. // * else
  23. // * browser's default behavior
  24. // * deferred second 'saveSnapshot' event
  25. // -- CTRL+V
  26. // * listen onKey (onkeydown)
  27. // * simulate 'beforepaste' for non-IEs on editable
  28. // * listen 'onpaste' on editable ('onbeforepaste' for IE)
  29. // * fire 'beforePaste' on editor
  30. // * if ( !canceled && ( htmlInDataTransfer || !external paste) && dataTransfer is not empty ) getClipboardDataByPastebin
  31. // * fire 'paste' on editor
  32. // * !canceled && fire 'afterPaste' on editor
  33. // -- Copy command
  34. // * tryToCutCopy
  35. // * execCommand
  36. // * !success && notification
  37. // -- Cut command
  38. // * fixCut
  39. // * tryToCutCopy
  40. // * execCommand
  41. // * !success && notification
  42. // -- Paste command
  43. // * fire 'paste' on editable ('beforepaste' for IE)
  44. // * !canceled && execCommand 'paste'
  45. // * !success && fire 'pasteDialog' on editor
  46. // -- Paste from native context menu & menubar
  47. // (Fx & Webkits are handled in 'paste' default listner.
  48. // Opera cannot be handled at all because it doesn't fire any events
  49. // Special treatment is needed for IE, for which is this part of doc)
  50. // * listen 'onpaste'
  51. // * cancel native event
  52. // * fire 'beforePaste' on editor
  53. // * if ( !canceled && ( htmlInDataTransfer || !external paste) && dataTransfer is not empty ) getClipboardDataByPastebin
  54. // * execIECommand( 'paste' ) -> this fires another 'paste' event, so cancel it
  55. // * fire 'paste' on editor
  56. // * !canceled && fire 'afterPaste' on editor
  57. //
  58. //
  59. // PASTE EVENT - PREPROCESSING:
  60. // -- Possible dataValue types: auto, text, html.
  61. // -- Possible dataValue contents:
  62. // * text (possible \n\r)
  63. // * htmlified text (text + br,div,p - no presentional markup & attrs - depends on browser)
  64. // * html
  65. // -- Possible flags:
  66. // * htmlified - if true then content is a HTML even if no markup inside. This flag is set
  67. // for content from editable pastebins, because they 'htmlify' pasted content.
  68. //
  69. // -- Type: auto:
  70. // * content: htmlified text -> filter, unify text markup (brs, ps, divs), set type: text
  71. // * content: html -> filter, set type: html
  72. // -- Type: text:
  73. // * content: htmlified text -> filter, unify text markup
  74. // * content: html -> filter, strip presentional markup, unify text markup
  75. // -- Type: html:
  76. // * content: htmlified text -> filter, unify text markup
  77. // * content: html -> filter
  78. //
  79. // -- Phases:
  80. // * if dataValue is empty copy data from dataTransfer to dataValue (priority 1)
  81. // * filtering (priorities 3-5) - e.g. pastefromword filters
  82. // * content type sniffing (priority 6)
  83. // * markup transformations for text (priority 6)
  84. //
  85. // DRAG & DROP EXECUTION FLOWS:
  86. // -- Drag
  87. // * save to the global object:
  88. // * drag timestamp (with 'cke-' prefix),
  89. // * selected html,
  90. // * drag range,
  91. // * editor instance.
  92. // * put drag timestamp into event.dataTransfer.text
  93. // -- Drop
  94. // * if events text == saved timestamp && editor == saved editor
  95. // internal drag & drop occurred
  96. // * getRangeAtDropPosition
  97. // * create bookmarks for drag and drop ranges starting from the end of the document
  98. // * dragRange.deleteContents()
  99. // * fire 'paste' with saved html and drop range
  100. // * if events text == saved timestamp && editor != saved editor
  101. // cross editor drag & drop occurred
  102. // * getRangeAtDropPosition
  103. // * fire 'paste' with saved html
  104. // * dragRange.deleteContents()
  105. // * FF: refreshCursor on afterPaste
  106. // * if events text != saved timestamp
  107. // drop form external source occurred
  108. // * getRangeAtDropPosition
  109. // * if event contains html data then fire 'paste' with html
  110. // * else if event contains text data then fire 'paste' with encoded text
  111. // * FF: refreshCursor on afterPaste
  112. 'use strict';
  113. ( function() {
  114. // Register the plugin.
  115. CKEDITOR.plugins.add( 'clipboard', {
  116. requires: 'dialog',
  117. // jscs:disable maximumLineLength
  118. lang: 'af,ar,bg,bn,bs,ca,cs,cy,da,de,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
  119. // jscs:enable maximumLineLength
  120. icons: 'copy,copy-rtl,cut,cut-rtl,paste,paste-rtl', // %REMOVE_LINE_CORE%
  121. hidpi: true, // %REMOVE_LINE_CORE%
  122. init: function( editor ) {
  123. var filterType,
  124. filtersFactory = filtersFactoryFactory();
  125. if ( editor.config.forcePasteAsPlainText ) {
  126. filterType = 'plain-text';
  127. } else if ( editor.config.pasteFilter ) {
  128. filterType = editor.config.pasteFilter;
  129. }
  130. // On Webkit the pasteFilter defaults 'semantic-content' because pasted data is so terrible
  131. // that it must be always filtered.
  132. else if ( CKEDITOR.env.webkit && !( 'pasteFilter' in editor.config ) ) {
  133. filterType = 'semantic-content';
  134. }
  135. editor.pasteFilter = filtersFactory.get( filterType );
  136. initPasteClipboard( editor );
  137. initDragDrop( editor );
  138. CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) );
  139. editor.on( 'paste', function( evt ) {
  140. // Init `dataTransfer` if `paste` event was fired without it, so it will be always available.
  141. if ( !evt.data.dataTransfer ) {
  142. evt.data.dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer();
  143. }
  144. // If dataValue is already set (manually or by paste bin), so do not override it.
  145. if ( evt.data.dataValue ) {
  146. return;
  147. }
  148. var dataTransfer = evt.data.dataTransfer,
  149. // IE support only text data and throws exception if we try to get html data.
  150. // This html data object may also be empty if we drag content of the textarea.
  151. value = dataTransfer.getData( 'text/html' );
  152. if ( value ) {
  153. evt.data.dataValue = value;
  154. evt.data.type = 'html';
  155. } else {
  156. // Try to get text data otherwise.
  157. value = dataTransfer.getData( 'text/plain' );
  158. if ( value ) {
  159. evt.data.dataValue = editor.editable().transformPlainTextToHtml( value );
  160. evt.data.type = 'text';
  161. }
  162. }
  163. }, null, null, 1 );
  164. editor.on( 'paste', function( evt ) {
  165. var data = evt.data.dataValue,
  166. blockElements = CKEDITOR.dtd.$block;
  167. // Filter webkit garbage.
  168. if ( data.indexOf( 'Apple-' ) > -1 ) {
  169. // Replace special webkit's   with simple space, because webkit
  170. // produces them even for normal spaces.
  171. data = data.replace( /<span class="Apple-converted-space">&nbsp;<\/span>/gi, ' ' );
  172. // Strip <span> around white-spaces when not in forced 'html' content type.
  173. // This spans are created only when pasting plain text into Webkit,
  174. // but for safety reasons remove them always.
  175. if ( evt.data.type != 'html' ) {
  176. data = data.replace( /<span class="Apple-tab-span"[^>]*>([^<]*)<\/span>/gi, function( all, spaces ) {
  177. // Replace tabs with 4 spaces like Fx does.
  178. return spaces.replace( /\t/g, '&nbsp;&nbsp; &nbsp;' );
  179. } );
  180. }
  181. // This br is produced only when copying & pasting HTML content.
  182. if ( data.indexOf( '<br class="Apple-interchange-newline">' ) > -1 ) {
  183. evt.data.startsWithEOL = 1;
  184. evt.data.preSniffing = 'html'; // Mark as not text.
  185. data = data.replace( /<br class="Apple-interchange-newline">/, '' );
  186. }
  187. // Remove all other classes.
  188. data = data.replace( /(<[^>]+) class="Apple-[^"]*"/gi, '$1' );
  189. }
  190. // Strip editable that was copied from inside. (#9534)
  191. if ( data.match( /^<[^<]+cke_(editable|contents)/i ) ) {
  192. var tmp,
  193. editable_wrapper,
  194. wrapper = new CKEDITOR.dom.element( 'div' );
  195. wrapper.setHtml( data );
  196. // Verify for sure and check for nested editor UI parts. (#9675)
  197. while ( wrapper.getChildCount() == 1 &&
  198. ( tmp = wrapper.getFirst() ) &&
  199. tmp.type == CKEDITOR.NODE_ELEMENT && // Make sure first-child is element.
  200. ( tmp.hasClass( 'cke_editable' ) || tmp.hasClass( 'cke_contents' ) ) ) {
  201. wrapper = editable_wrapper = tmp;
  202. }
  203. // If editable wrapper was found strip it and bogus <br> (added on FF).
  204. if ( editable_wrapper )
  205. data = editable_wrapper.getHtml().replace( /<br>$/i, '' );
  206. }
  207. if ( CKEDITOR.env.ie ) {
  208. // &nbsp; <p> -> <p> (br.cke-pasted-remove will be removed later)
  209. data = data.replace( /^&nbsp;(?: |\r\n)?<(\w+)/g, function( match, elementName ) {
  210. if ( elementName.toLowerCase() in blockElements ) {
  211. evt.data.preSniffing = 'html'; // Mark as not a text.
  212. return '<' + elementName;
  213. }
  214. return match;
  215. } );
  216. } else if ( CKEDITOR.env.webkit ) {
  217. // </p><div><br></div> -> </p><br>
  218. // We don't mark br, because this situation can happen for htmlified text too.
  219. data = data.replace( /<\/(\w+)><div><br><\/div>$/, function( match, elementName ) {
  220. if ( elementName in blockElements ) {
  221. evt.data.endsWithEOL = 1;
  222. return '</' + elementName + '>';
  223. }
  224. return match;
  225. } );
  226. } else if ( CKEDITOR.env.gecko ) {
  227. // Firefox adds bogus <br> when user pasted text followed by space(s).
  228. data = data.replace( /(\s)<br>$/, '$1' );
  229. }
  230. evt.data.dataValue = data;
  231. }, null, null, 3 );
  232. editor.on( 'paste', function( evt ) {
  233. var dataObj = evt.data,
  234. type = dataObj.type,
  235. data = dataObj.dataValue,
  236. trueType,
  237. // Default is 'html'.
  238. defaultType = editor.config.clipboard_defaultContentType || 'html',
  239. transferType = dataObj.dataTransfer.getTransferType( editor );
  240. // If forced type is 'html' we don't need to know true data type.
  241. if ( type == 'html' || dataObj.preSniffing == 'html' ) {
  242. trueType = 'html';
  243. } else {
  244. trueType = recogniseContentType( data );
  245. }
  246. // Unify text markup.
  247. if ( trueType == 'htmlifiedtext' ) {
  248. data = htmlifiedTextHtmlification( editor.config, data );
  249. }
  250. // Strip presentional markup & unify text markup.
  251. // Forced plain text (dialog or forcePAPT).
  252. // Note: we do not check dontFilter option in this case, because forcePAPT was implemented
  253. // before pasteFilter and pasteFilter is automatically used on Webkit&Blink since 4.5, so
  254. // forcePAPT should have priority as it had before 4.5.
  255. if ( type == 'text' && trueType == 'html' ) {
  256. data = filterContent( editor, data, filtersFactory.get( 'plain-text' ) );
  257. }
  258. // External paste and pasteFilter exists and filtering isn't disabled.
  259. else if ( transferType == CKEDITOR.DATA_TRANSFER_EXTERNAL && editor.pasteFilter && !dataObj.dontFilter ) {
  260. data = filterContent( editor, data, editor.pasteFilter );
  261. }
  262. if ( dataObj.startsWithEOL ) {
  263. data = '<br data-cke-eol="1">' + data;
  264. }
  265. if ( dataObj.endsWithEOL ) {
  266. data += '<br data-cke-eol="1">';
  267. }
  268. if ( type == 'auto' ) {
  269. type = ( trueType == 'html' || defaultType == 'html' ) ? 'html' : 'text';
  270. }
  271. dataObj.type = type;
  272. dataObj.dataValue = data;
  273. delete dataObj.preSniffing;
  274. delete dataObj.startsWithEOL;
  275. delete dataObj.endsWithEOL;
  276. }, null, null, 6 );
  277. // Inserts processed data into the editor at the end of the
  278. // events chain.
  279. editor.on( 'paste', function( evt ) {
  280. var data = evt.data;
  281. if ( data.dataValue ) {
  282. editor.insertHtml( data.dataValue, data.type, data.range );
  283. // Defer 'afterPaste' so all other listeners for 'paste' will be fired first.
  284. // Fire afterPaste only if paste inserted some HTML.
  285. setTimeout( function() {
  286. editor.fire( 'afterPaste' );
  287. }, 0 );
  288. }
  289. }, null, null, 1000 );
  290. editor.on( 'pasteDialog', function( evt ) {
  291. // TODO it's possible that this setTimeout is not needed any more,
  292. // because of changes introduced in the same commit as this comment.
  293. // Editor.getClipboardData adds listener to the dialog's events which are
  294. // fired after a while (not like 'showDialog').
  295. setTimeout( function() {
  296. // Open default paste dialog.
  297. editor.openDialog( 'paste', evt.data );
  298. }, 0 );
  299. } );
  300. }
  301. } );
  302. function firePasteEvents( editor, data, withBeforePaste ) {
  303. if ( !data.type ) {
  304. data.type = 'auto';
  305. }
  306. if ( withBeforePaste ) {
  307. // Fire 'beforePaste' event so clipboard flavor get customized
  308. // by other plugins.
  309. if ( editor.fire( 'beforePaste', data ) === false )
  310. return false; // Event canceled
  311. }
  312. // Do not fire paste if there is no data (dataValue and dataTranfser are empty).
  313. // This check should be done after firing 'beforePaste' because for native paste
  314. // 'beforePaste' is by default fired even for empty clipboard.
  315. if ( !data.dataValue && data.dataTransfer.isEmpty() ) {
  316. return false;
  317. }
  318. if ( !data.dataValue ) {
  319. data.dataValue = '';
  320. }
  321. // Because of FF bug we need to use this hack, otherwise cursor is hidden
  322. // or it is not possible to move it (#12420).
  323. // Also, check that editor.toolbox exists, because the toolbar plugin might not be loaded (#13305).
  324. if ( CKEDITOR.env.gecko && data.method == 'drop' && editor.toolbox ) {
  325. editor.once( 'afterPaste', function() {
  326. editor.toolbox.focus();
  327. } );
  328. }
  329. return editor.fire( 'paste', data );
  330. }
  331. function initPasteClipboard( editor ) {
  332. var clipboard = CKEDITOR.plugins.clipboard,
  333. preventBeforePasteEvent = 0,
  334. preventPasteEvent = 0,
  335. inReadOnly = 0;
  336. addListeners();
  337. addButtonsCommands();
  338. /**
  339. * Gets clipboard data by directly accessing the clipboard (IE only) or opening the paste dialog window.
  340. *
  341. * editor.getClipboardData( { title: 'Get my data' }, function( data ) {
  342. * if ( data )
  343. * alert( data.type + ' ' + data.dataValue );
  344. * } );
  345. *
  346. * @member CKEDITOR.editor
  347. * @param {Object} options
  348. * @param {String} [options.title] The title of the paste dialog window.
  349. * @param {Function} callback A function that will be executed with `data.type` and `data.dataValue`
  350. * or `null` if none of the capturing methods succeeded.
  351. */
  352. editor.getClipboardData = function( options, callback ) {
  353. var beforePasteNotCanceled = false,
  354. dataType = 'auto',
  355. dialogCommited = false;
  356. // Options are optional - args shift.
  357. if ( !callback ) {
  358. callback = options;
  359. options = null;
  360. }
  361. // Listen with maximum priority to handle content before everyone else.
  362. // This callback will handle paste event that will be fired if direct
  363. // access to the clipboard succeed in IE.
  364. editor.on( 'paste', onPaste, null, null, 0 );
  365. // Listen at the end of listeners chain to see if event wasn't canceled
  366. // and to retrieve modified data.type.
  367. editor.on( 'beforePaste', onBeforePaste, null, null, 1000 );
  368. // getClipboardDataDirectly() will fire 'beforePaste' synchronously, so we can
  369. // check if it was canceled and if any listener modified data.type.
  370. // If command didn't succeed (only IE allows to access clipboard and only if
  371. // user agrees) open and handle paste dialog.
  372. if ( getClipboardDataDirectly() === false ) {
  373. // Direct access to the clipboard wasn't successful so remove listener.
  374. editor.removeListener( 'paste', onPaste );
  375. // If beforePaste was canceled do not open dialog.
  376. // Add listeners only if dialog really opened. 'pasteDialog' can be canceled.
  377. if ( beforePasteNotCanceled && editor.fire( 'pasteDialog', onDialogOpen ) ) {
  378. editor.on( 'pasteDialogCommit', onDialogCommit );
  379. // 'dialogHide' will be fired after 'pasteDialogCommit'.
  380. editor.on( 'dialogHide', function( evt ) {
  381. evt.removeListener();
  382. evt.data.removeListener( 'pasteDialogCommit', onDialogCommit );
  383. // Because Opera has to wait a while in pasteDialog we have to wait here.
  384. setTimeout( function() {
  385. // Notify even if user canceled dialog (clicked 'cancel', ESC, etc).
  386. if ( !dialogCommited )
  387. callback( null );
  388. }, 10 );
  389. } );
  390. } else {
  391. callback( null );
  392. }
  393. }
  394. function onPaste( evt ) {
  395. evt.removeListener();
  396. evt.cancel();
  397. callback( evt.data );
  398. }
  399. function onBeforePaste( evt ) {
  400. evt.removeListener();
  401. beforePasteNotCanceled = true;
  402. dataType = evt.data.type;
  403. }
  404. function onDialogCommit( evt ) {
  405. evt.removeListener();
  406. // Cancel pasteDialogCommit so paste dialog won't automatically fire
  407. // 'paste' evt by itself.
  408. evt.cancel();
  409. dialogCommited = true;
  410. callback( { type: dataType, dataValue: evt.data, method: 'paste' } );
  411. }
  412. function onDialogOpen() {
  413. this.customTitle = ( options && options.title );
  414. }
  415. };
  416. function addButtonsCommands() {
  417. addButtonCommand( 'Cut', 'cut', createCutCopyCmd( 'cut' ), 10, 1 );
  418. addButtonCommand( 'Copy', 'copy', createCutCopyCmd( 'copy' ), 20, 4 );
  419. addButtonCommand( 'Paste', 'paste', createPasteCmd(), 30, 8 );
  420. function addButtonCommand( buttonName, commandName, command, toolbarOrder, ctxMenuOrder ) {
  421. var lang = editor.lang.clipboard[ commandName ];
  422. editor.addCommand( commandName, command );
  423. editor.ui.addButton && editor.ui.addButton( buttonName, {
  424. label: lang,
  425. command: commandName,
  426. toolbar: 'clipboard,' + toolbarOrder
  427. } );
  428. // If the "menu" plugin is loaded, register the menu item.
  429. if ( editor.addMenuItems ) {
  430. editor.addMenuItem( commandName, {
  431. label: lang,
  432. command: commandName,
  433. group: 'clipboard',
  434. order: ctxMenuOrder
  435. } );
  436. }
  437. }
  438. }
  439. function addListeners() {
  440. editor.on( 'key', onKey );
  441. editor.on( 'contentDom', addPasteListenersToEditable );
  442. // For improved performance, we're checking the readOnly state on selectionChange instead of hooking a key event for that.
  443. editor.on( 'selectionChange', function( evt ) {
  444. inReadOnly = evt.data.selection.getRanges()[ 0 ].checkReadOnly();
  445. setToolbarStates();
  446. } );
  447. // If the "contextmenu" plugin is loaded, register the listeners.
  448. if ( editor.contextMenu ) {
  449. editor.contextMenu.addListener( function( element, selection ) {
  450. inReadOnly = selection.getRanges()[ 0 ].checkReadOnly();
  451. return {
  452. cut: stateFromNamedCommand( 'cut' ),
  453. copy: stateFromNamedCommand( 'copy' ),
  454. paste: stateFromNamedCommand( 'paste' )
  455. };
  456. } );
  457. }
  458. }
  459. // Add events listeners to editable.
  460. function addPasteListenersToEditable() {
  461. var editable = editor.editable();
  462. if ( CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) {
  463. var initOnCopyCut = function( evt ) {
  464. clipboard.initPasteDataTransfer( evt, editor );
  465. evt.data.preventDefault();
  466. };
  467. editable.on( 'copy', initOnCopyCut );
  468. editable.on( 'cut', initOnCopyCut );
  469. // Delete content with the low priority so one can overwrite cut data.
  470. editable.on( 'cut', function() {
  471. editor.extractSelectedHtml();
  472. }, null, null, 999 );
  473. }
  474. // We'll be catching all pasted content in one line, regardless of whether
  475. // it's introduced by a document command execution (e.g. toolbar buttons) or
  476. // user paste behaviors (e.g. CTRL+V).
  477. editable.on( clipboard.mainPasteEvent, function( evt ) {
  478. if ( clipboard.mainPasteEvent == 'beforepaste' && preventBeforePasteEvent ) {
  479. return;
  480. }
  481. // If you've just asked yourself why preventPasteEventNow() is not here, but
  482. // in listener for CTRL+V and exec method of 'paste' command
  483. // you've asked the same question we did.
  484. //
  485. // THE ANSWER:
  486. //
  487. // First thing to notice - this answer makes sense only for IE,
  488. // because other browsers don't listen for 'paste' event.
  489. //
  490. // What would happen if we move preventPasteEventNow() here?
  491. // For:
  492. // * CTRL+V - IE fires 'beforepaste', so we prevent 'paste' and pasteDataFromClipboard(). OK.
  493. // * editor.execCommand( 'paste' ) - we fire 'beforepaste', so we prevent
  494. // 'paste' and pasteDataFromClipboard() and doc.execCommand( 'Paste' ). OK.
  495. // * native context menu - IE fires 'beforepaste', so we prevent 'paste', but unfortunately
  496. // on IE we fail with pasteDataFromClipboard() here, because of... we don't know why, but
  497. // we just fail, so... we paste nothing. FAIL.
  498. // * native menu bar - the same as for native context menu.
  499. //
  500. // But don't you know any way to distinguish first two cases from last two?
  501. // Only one - special flag set in CTRL+V handler and exec method of 'paste'
  502. // command. And that's what we did using preventPasteEventNow().
  503. pasteDataFromClipboard( evt );
  504. } );
  505. // It's not possible to clearly handle all four paste methods (ctrl+v, native menu bar
  506. // native context menu, editor's command) in one 'paste/beforepaste' event in IE.
  507. //
  508. // For ctrl+v & editor's command it's easy to handle pasting in 'beforepaste' listener,
  509. // so we do this. For another two methods it's better to use 'paste' event.
  510. //
  511. // 'paste' is always being fired after 'beforepaste' (except of weird one on opening native
  512. // context menu), so for two methods handled in 'beforepaste' we're canceling 'paste'
  513. // using preventPasteEvent state.
  514. //
  515. // 'paste' event in IE is being fired before getClipboardDataByPastebin executes its callback.
  516. //
  517. // QUESTION: Why didn't you handle all 4 paste methods in handler for 'paste'?
  518. // Wouldn't this just be simpler?
  519. // ANSWER: Then we would have to evt.data.preventDefault() only for native
  520. // context menu and menu bar pastes. The same with execIECommand().
  521. // That would force us to mark CTRL+V and editor's paste command with
  522. // special flag, other than preventPasteEvent. But we still would have to
  523. // have preventPasteEvent for the second event fired by execIECommand.
  524. // Code would be longer and not cleaner.
  525. if ( clipboard.mainPasteEvent == 'beforepaste' ) {
  526. editable.on( 'paste', function( evt ) {
  527. if ( preventPasteEvent ) {
  528. return;
  529. }
  530. // Cancel next 'paste' event fired by execIECommand( 'paste' )
  531. // at the end of this callback.
  532. preventPasteEventNow();
  533. // Prevent native paste.
  534. evt.data.preventDefault();
  535. pasteDataFromClipboard( evt );
  536. // Force IE to paste content into pastebin so pasteDataFromClipboard will work.
  537. if ( !execIECommand( 'paste' ) ) {
  538. editor.openDialog( 'paste' );
  539. }
  540. } );
  541. // If mainPasteEvent is 'beforePaste' (IE before Edge),
  542. // dismiss the (wrong) 'beforepaste' event fired on context/toolbar menu open. (#7953)
  543. editable.on( 'contextmenu', preventBeforePasteEventNow, null, null, 0 );
  544. editable.on( 'beforepaste', function( evt ) {
  545. // Do not prevent event on CTRL+V and SHIFT+INS because it blocks paste (#11970).
  546. if ( evt.data && !evt.data.$.ctrlKey && !evt.data.$.shiftKey )
  547. preventBeforePasteEventNow();
  548. }, null, null, 0 );
  549. }
  550. editable.on( 'beforecut', function() {
  551. !preventBeforePasteEvent && fixCut( editor );
  552. } );
  553. var mouseupTimeout;
  554. // Use editor.document instead of editable in non-IEs for observing mouseup
  555. // since editable won't fire the event if selection process started within
  556. // iframe and ended out of the editor (#9851).
  557. editable.attachListener( CKEDITOR.env.ie ? editable : editor.document.getDocumentElement(), 'mouseup', function() {
  558. mouseupTimeout = setTimeout( function() {
  559. setToolbarStates();
  560. }, 0 );
  561. } );
  562. // Make sure that deferred mouseup callback isn't executed after editor instance
  563. // had been destroyed. This may happen when editor.destroy() is called in parallel
  564. // with mouseup event (i.e. a button with onclick callback) (#10219).
  565. editor.on( 'destroy', function() {
  566. clearTimeout( mouseupTimeout );
  567. } );
  568. editable.on( 'keyup', setToolbarStates );
  569. }
  570. // Create object representing Cut or Copy commands.
  571. function createCutCopyCmd( type ) {
  572. return {
  573. type: type,
  574. canUndo: type == 'cut', // We can't undo copy to clipboard.
  575. startDisabled: true,
  576. exec: function() {
  577. // Attempts to execute the Cut and Copy operations.
  578. function tryToCutCopy( type ) {
  579. if ( CKEDITOR.env.ie )
  580. return execIECommand( type );
  581. // non-IEs part
  582. try {
  583. // Other browsers throw an error if the command is disabled.
  584. return editor.document.$.execCommand( type, false, null );
  585. } catch ( e ) {
  586. return false;
  587. }
  588. }
  589. this.type == 'cut' && fixCut();
  590. var success = tryToCutCopy( this.type );
  591. if ( !success ) {
  592. // Show cutError or copyError.
  593. editor.showNotification( editor.lang.clipboard[ this.type + 'Error' ] ); // jshint ignore:line
  594. }
  595. return success;
  596. }
  597. };
  598. }
  599. function createPasteCmd() {
  600. return {
  601. // Snapshots are done manually by editable.insertXXX methods.
  602. canUndo: false,
  603. async: true,
  604. exec: function( editor, data ) {
  605. var fire = function( data, withBeforePaste ) {
  606. data && firePasteEvents( editor, data, !!withBeforePaste );
  607. editor.fire( 'afterCommandExec', {
  608. name: 'paste',
  609. command: cmd,
  610. returnValue: !!data
  611. } );
  612. },
  613. cmd = this;
  614. // Check data precisely - don't open dialog on empty string.
  615. if ( typeof data == 'string' )
  616. fire( {
  617. dataValue: data,
  618. method: 'paste',
  619. dataTransfer: clipboard.initPasteDataTransfer()
  620. }, 1 );
  621. else
  622. editor.getClipboardData( fire );
  623. }
  624. };
  625. }
  626. function preventPasteEventNow() {
  627. preventPasteEvent = 1;
  628. // For safety reason we should wait longer than 0/1ms.
  629. // We don't know how long execution of quite complex getClipboardData will take
  630. // and in for example 'paste' listner execCommand() (which fires 'paste') is called
  631. // after getClipboardData finishes.
  632. // Luckily, it's impossible to immediately fire another 'paste' event we want to handle,
  633. // because we only handle there native context menu and menu bar.
  634. setTimeout( function() {
  635. preventPasteEvent = 0;
  636. }, 100 );
  637. }
  638. function preventBeforePasteEventNow() {
  639. preventBeforePasteEvent = 1;
  640. setTimeout( function() {
  641. preventBeforePasteEvent = 0;
  642. }, 10 );
  643. }
  644. // Tries to execute any of the paste, cut or copy commands in IE. Returns a
  645. // boolean indicating that the operation succeeded.
  646. // @param {String} command *LOWER CASED* name of command ('paste', 'cut', 'copy').
  647. function execIECommand( command ) {
  648. var doc = editor.document,
  649. body = doc.getBody(),
  650. enabled = false,
  651. onExec = function() {
  652. enabled = true;
  653. };
  654. // The following seems to be the only reliable way to detect that
  655. // clipboard commands are enabled in IE. It will fire the
  656. // onpaste/oncut/oncopy events only if the security settings allowed
  657. // the command to execute.
  658. body.on( command, onExec );
  659. // IE7: document.execCommand has problem to paste into positioned element.
  660. if ( CKEDITOR.env.version > 7 ) {
  661. doc.$.execCommand( command );
  662. } else {
  663. doc.$.selection.createRange().execCommand( command );
  664. }
  665. body.removeListener( command, onExec );
  666. return enabled;
  667. }
  668. // Cutting off control type element in IE standards breaks the selection entirely. (#4881)
  669. function fixCut() {
  670. if ( !CKEDITOR.env.ie || CKEDITOR.env.quirks )
  671. return;
  672. var sel = editor.getSelection(),
  673. control, range, dummy;
  674. if ( ( sel.getType() == CKEDITOR.SELECTION_ELEMENT ) && ( control = sel.getSelectedElement() ) ) {
  675. range = sel.getRanges()[ 0 ];
  676. dummy = editor.document.createText( '' );
  677. dummy.insertBefore( control );
  678. range.setStartBefore( dummy );
  679. range.setEndAfter( control );
  680. sel.selectRanges( [ range ] );
  681. // Clear up the fix if the paste wasn't succeeded.
  682. setTimeout( function() {
  683. // Element still online?
  684. if ( control.getParent() ) {
  685. dummy.remove();
  686. sel.selectElement( control );
  687. }
  688. }, 0 );
  689. }
  690. }
  691. // Allow to peek clipboard content by redirecting the
  692. // pasting content into a temporary bin and grab the content of it.
  693. function getClipboardDataByPastebin( evt, callback ) {
  694. var doc = editor.document,
  695. editable = editor.editable(),
  696. cancel = function( evt ) {
  697. evt.cancel();
  698. },
  699. blurListener;
  700. // Avoid recursions on 'paste' event or consequent paste too fast. (#5730)
  701. if ( doc.getById( 'cke_pastebin' ) )
  702. return;
  703. var sel = editor.getSelection();
  704. var bms = sel.createBookmarks();
  705. // #11384. On IE9+ we use native selectionchange (i.e. editor#selectionCheck) to cache the most
  706. // recent selection which we then lock on editable blur. See selection.js for more info.
  707. // selectionchange fired before getClipboardDataByPastebin() cached selection
  708. // before creating bookmark (cached selection will be invalid, because bookmarks modified the DOM),
  709. // so we need to fire selectionchange one more time, to store current seleciton.
  710. // Selection will be locked when we focus pastebin.
  711. if ( CKEDITOR.env.ie )
  712. sel.root.fire( 'selectionchange' );
  713. // Create container to paste into.
  714. // For rich content we prefer to use "body" since it holds
  715. // the least possibility to be splitted by pasted content, while this may
  716. // breaks the text selection on a frame-less editable, "div" would be
  717. // the best one in that case.
  718. // In another case on old IEs moving the selection into a "body" paste bin causes error panic.
  719. // Body can't be also used for Opera which fills it with <br>
  720. // what is indistinguishable from pasted <br> (copying <br> in Opera isn't possible,
  721. // but it can be copied from other browser).
  722. var pastebin = new CKEDITOR.dom.element(
  723. ( CKEDITOR.env.webkit || editable.is( 'body' ) ) && !CKEDITOR.env.ie ? 'body' : 'div', doc );
  724. pastebin.setAttributes( {
  725. id: 'cke_pastebin',
  726. 'data-cke-temp': '1'
  727. } );
  728. var containerOffset = 0,
  729. offsetParent,
  730. win = doc.getWindow();
  731. if ( CKEDITOR.env.webkit ) {
  732. // It's better to paste close to the real paste destination, so inherited styles
  733. // (which Webkits will try to compensate by styling span) differs less from the destination's one.
  734. editable.append( pastebin );
  735. // Style pastebin like .cke_editable, to minimize differences between origin and destination. (#9754)
  736. pastebin.addClass( 'cke_editable' );
  737. // Compensate position of offsetParent.
  738. if ( !editable.is( 'body' ) ) {
  739. // We're not able to get offsetParent from pastebin (body element), so check whether
  740. // its parent (editable) is positioned.
  741. if ( editable.getComputedStyle( 'position' ) != 'static' )
  742. offsetParent = editable;
  743. // And if not - safely get offsetParent from editable.
  744. else
  745. offsetParent = CKEDITOR.dom.element.get( editable.$.offsetParent );
  746. containerOffset = offsetParent.getDocumentPosition().y;
  747. }
  748. } else {
  749. // Opera and IE doesn't allow to append to html element.
  750. editable.getAscendant( CKEDITOR.env.ie ? 'body' : 'html', 1 ).append( pastebin );
  751. }
  752. pastebin.setStyles( {
  753. position: 'absolute',
  754. // Position the bin at the top (+10 for safety) of viewport to avoid any subsequent document scroll.
  755. top: ( win.getScrollPosition().y - containerOffset + 10 ) + 'px',
  756. width: '1px',
  757. // Caret has to fit in that height, otherwise browsers like Chrome & Opera will scroll window to show it.
  758. // Set height equal to viewport's height - 20px (safety gaps), minimum 1px.
  759. height: Math.max( 1, win.getViewPaneSize().height - 20 ) + 'px',
  760. overflow: 'hidden',
  761. // Reset styles that can mess up pastebin position.
  762. margin: 0,
  763. padding: 0
  764. } );
  765. // Paste fails in Safari when the body tag has 'user-select: none'. (#12506)
  766. if ( CKEDITOR.env.safari )
  767. pastebin.setStyles( CKEDITOR.tools.cssVendorPrefix( 'user-select', 'text' ) );
  768. // Check if the paste bin now establishes new editing host.
  769. var isEditingHost = pastebin.getParent().isReadOnly();
  770. if ( isEditingHost ) {
  771. // Hide the paste bin.
  772. pastebin.setOpacity( 0 );
  773. // And make it editable.
  774. pastebin.setAttribute( 'contenteditable', true );
  775. }
  776. // Transparency is not enough since positioned non-editing host always shows
  777. // resize handler, pull it off the screen instead.
  778. else {
  779. pastebin.setStyle( editor.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-1000px' );
  780. }
  781. editor.on( 'selectionChange', cancel, null, null, 0 );
  782. // Webkit fill fire blur on editable when moving selection to
  783. // pastebin (if body is used). Cancel it because it causes incorrect
  784. // selection lock in case of inline editor (#10644).
  785. // The same seems to apply to Firefox (#10787).
  786. if ( CKEDITOR.env.webkit || CKEDITOR.env.gecko )
  787. blurListener = editable.once( 'blur', cancel, null, null, -100 );
  788. // Temporarily move selection to the pastebin.
  789. isEditingHost && pastebin.focus();
  790. var range = new CKEDITOR.dom.range( pastebin );
  791. range.selectNodeContents( pastebin );
  792. var selPastebin = range.select();
  793. // If non-native paste is executed, IE will open security alert and blur editable.
  794. // Editable will then lock selection inside itself and after accepting security alert
  795. // this selection will be restored. We overwrite stored selection, so it's restored
  796. // in pastebin. (#9552)
  797. if ( CKEDITOR.env.ie ) {
  798. blurListener = editable.once( 'blur', function() {
  799. editor.lockSelection( selPastebin );
  800. } );
  801. }
  802. var scrollTop = CKEDITOR.document.getWindow().getScrollPosition().y;
  803. // Wait a while and grab the pasted contents.
  804. setTimeout( function() {
  805. // Restore main window's scroll position which could have been changed
  806. // by browser in cases described in #9771.
  807. if ( CKEDITOR.env.webkit )
  808. CKEDITOR.document.getBody().$.scrollTop = scrollTop;
  809. // Blur will be fired only on non-native paste. In other case manually remove listener.
  810. blurListener && blurListener.removeListener();
  811. // Restore properly the document focus. (#8849)
  812. if ( CKEDITOR.env.ie )
  813. editable.focus();
  814. // IE7: selection must go before removing pastebin. (#8691)
  815. sel.selectBookmarks( bms );
  816. pastebin.remove();
  817. // Grab the HTML contents.
  818. // We need to look for a apple style wrapper on webkit it also adds
  819. // a div wrapper if you copy/paste the body of the editor.
  820. // Remove hidden div and restore selection.
  821. var bogusSpan;
  822. if ( CKEDITOR.env.webkit && ( bogusSpan = pastebin.getFirst() ) && ( bogusSpan.is && bogusSpan.hasClass( 'Apple-style-span' ) ) )
  823. pastebin = bogusSpan;
  824. editor.removeListener( 'selectionChange', cancel );
  825. callback( pastebin.getHtml() );
  826. }, 0 );
  827. }
  828. // Try to get content directly on IE from clipboard, without native event
  829. // being fired before. In other words - synthetically get clipboard data, if it's possible.
  830. // mainPasteEvent will be fired, so if forced native paste:
  831. // * worked, getClipboardDataByPastebin will grab it,
  832. // * didn't work, dataValue and dataTransfer will be empty and editor#paste won't be fired.
  833. // Clipboard data can be accessed directly only on IEs older than Edge.
  834. // On other browsers we should fire beforePaste event and return false.
  835. function getClipboardDataDirectly() {
  836. if ( clipboard.mainPasteEvent == 'paste' ) {
  837. // beforePaste should be fired when dialog open so it can be canceled.
  838. editor.fire( 'beforePaste', { type: 'auto', method: 'paste' } );
  839. return false;
  840. }
  841. // Prevent IE from pasting at the begining of the document.
  842. editor.focus();
  843. // Command will be handled by 'beforepaste', but as
  844. // execIECommand( 'paste' ) will fire also 'paste' event
  845. // we're canceling it.
  846. preventPasteEventNow();
  847. // #9247: Lock focus to prevent IE from hiding toolbar for inline editor.
  848. var focusManager = editor.focusManager;
  849. focusManager.lock();
  850. if ( editor.editable().fire( clipboard.mainPasteEvent ) && !execIECommand( 'paste' ) ) {
  851. focusManager.unlock();
  852. return false;
  853. }
  854. focusManager.unlock();
  855. return true;
  856. }
  857. // Listens for some clipboard related keystrokes, so they get customized.
  858. // Needs to be bind to keydown event.
  859. function onKey( event ) {
  860. if ( editor.mode != 'wysiwyg' )
  861. return;
  862. switch ( event.data.keyCode ) {
  863. // Paste
  864. case CKEDITOR.CTRL + 86: // CTRL+V
  865. case CKEDITOR.SHIFT + 45: // SHIFT+INS
  866. var editable = editor.editable();
  867. // Cancel 'paste' event because ctrl+v is for IE handled
  868. // by 'beforepaste'.
  869. preventPasteEventNow();
  870. // Simulate 'beforepaste' event for all browsers using 'paste' as main event.
  871. if ( clipboard.mainPasteEvent == 'paste' ) {
  872. editable.fire( 'beforepaste' );
  873. }
  874. return;
  875. // Cut
  876. case CKEDITOR.CTRL + 88: // CTRL+X
  877. case CKEDITOR.SHIFT + 46: // SHIFT+DEL
  878. // Save Undo snapshot.
  879. editor.fire( 'saveSnapshot' ); // Save before cut
  880. setTimeout( function() {
  881. editor.fire( 'saveSnapshot' ); // Save after cut
  882. }, 50 ); // OSX is slow (#11416).
  883. }
  884. }
  885. function pasteDataFromClipboard( evt ) {
  886. // Default type is 'auto', but can be changed by beforePaste listeners.
  887. var eventData = {
  888. type: 'auto',
  889. method: 'paste',
  890. dataTransfer: clipboard.initPasteDataTransfer( evt )
  891. };
  892. eventData.dataTransfer.cacheData();
  893. // Fire 'beforePaste' event so clipboard flavor get customized by other plugins.
  894. // If 'beforePaste' is canceled continue executing getClipboardDataByPastebin and then do nothing
  895. // (do not fire 'paste', 'afterPaste' events). This way we can grab all - synthetically
  896. // and natively pasted content and prevent its insertion into editor
  897. // after canceling 'beforePaste' event.
  898. var beforePasteNotCanceled = editor.fire( 'beforePaste', eventData ) !== false;
  899. // Do not use paste bin if the browser let us get HTML or files from dataTranfer.
  900. if ( beforePasteNotCanceled && clipboard.canClipboardApiBeTrusted( eventData.dataTransfer, editor ) ) {
  901. evt.data.preventDefault();
  902. setTimeout( function() {
  903. firePasteEvents( editor, eventData );
  904. }, 0 );
  905. } else {
  906. getClipboardDataByPastebin( evt, function( data ) {
  907. // Clean up.
  908. eventData.dataValue = data.replace( /<span[^>]+data-cke-bookmark[^<]*?<\/span>/ig, '' );
  909. // Fire remaining events (without beforePaste)
  910. beforePasteNotCanceled && firePasteEvents( editor, eventData );
  911. } );
  912. }
  913. }
  914. function setToolbarStates() {
  915. if ( editor.mode != 'wysiwyg' )
  916. return;
  917. var pasteState = stateFromNamedCommand( 'paste' );
  918. editor.getCommand( 'cut' ).setState( stateFromNamedCommand( 'cut' ) );
  919. editor.getCommand( 'copy' ).setState( stateFromNamedCommand( 'copy' ) );
  920. editor.getCommand( 'paste' ).setState( pasteState );
  921. editor.fire( 'pasteState', pasteState );
  922. }
  923. function stateFromNamedCommand( command ) {
  924. if ( inReadOnly && command in { paste: 1, cut: 1 } )
  925. return CKEDITOR.TRISTATE_DISABLED;
  926. if ( command == 'paste' )
  927. return CKEDITOR.TRISTATE_OFF;
  928. // Cut, copy - check if the selection is not empty.
  929. var sel = editor.getSelection(),
  930. ranges = sel.getRanges(),
  931. selectionIsEmpty = sel.getType() == CKEDITOR.SELECTION_NONE || ( ranges.length == 1 && ranges[ 0 ].collapsed );
  932. return selectionIsEmpty ? CKEDITOR.TRISTATE_DISABLED : CKEDITOR.TRISTATE_OFF;
  933. }
  934. }
  935. // Returns:
  936. // * 'htmlifiedtext' if content looks like transformed by browser from plain text.
  937. // See clipboard/paste.html TCs for more info.
  938. // * 'html' if it is not 'htmlifiedtext'.
  939. function recogniseContentType( data ) {
  940. if ( CKEDITOR.env.webkit ) {
  941. // Plain text or ( <div><br></div> and text inside <div> ).
  942. if ( !data.match( /^[^<]*$/g ) && !data.match( /^(<div><br( ?\/)?><\/div>|<div>[^<]*<\/div>)*$/gi ) )
  943. return 'html';
  944. } else if ( CKEDITOR.env.ie ) {
  945. // Text and <br> or ( text and <br> in <p> - paragraphs can be separated by new \r\n ).
  946. if ( !data.match( /^([^<]|<br( ?\/)?>)*$/gi ) && !data.match( /^(<p>([^<]|<br( ?\/)?>)*<\/p>|(\r\n))*$/gi ) )
  947. return 'html';
  948. } else if ( CKEDITOR.env.gecko ) {
  949. // Text or <br>.
  950. if ( !data.match( /^([^<]|<br( ?\/)?>)*$/gi ) )
  951. return 'html';
  952. } else {
  953. return 'html';
  954. }
  955. return 'htmlifiedtext';
  956. }
  957. // This function transforms what browsers produce when
  958. // pasting plain text into editable element (see clipboard/paste.html TCs
  959. // for more info) into correct HTML (similar to that produced by text2Html).
  960. function htmlifiedTextHtmlification( config, data ) {
  961. function repeatParagraphs( repeats ) {
  962. // Repeat blocks floor((n+1)/2) times.
  963. // Even number of repeats - add <br> at the beginning of last <p>.
  964. return CKEDITOR.tools.repeat( '</p><p>', ~~( repeats / 2 ) ) + ( repeats % 2 == 1 ? '<br>' : '' );
  965. }
  966. // Replace adjacent white-spaces (EOLs too - Fx sometimes keeps them) with one space.
  967. data = data.replace( /\s+/g, ' ' )
  968. // Remove spaces from between tags.
  969. .replace( /> +</g, '><' )
  970. // Normalize XHTML syntax and upper cased <br> tags.
  971. .replace( /<br ?\/>/gi, '<br>' );
  972. // IE - lower cased tags.
  973. data = data.replace( /<\/?[A-Z]+>/g, function( match ) {
  974. return match.toLowerCase();
  975. } );
  976. // Don't touch single lines (no <br|p|div>) - nothing to do here.
  977. if ( data.match( /^[^<]$/ ) )
  978. return data;
  979. // Webkit.
  980. if ( CKEDITOR.env.webkit && data.indexOf( '<div>' ) > -1 ) {
  981. // One line break at the beginning - insert <br>
  982. data = data.replace( /^(<div>(<br>|)<\/div>)(?!$|(<div>(<br>|)<\/div>))/g, '<br>' )
  983. // Two or more - reduce number of new lines by one.
  984. .replace( /^(<div>(<br>|)<\/div>){2}(?!$)/g, '<div></div>' );
  985. // Two line breaks create one paragraph in Webkit.
  986. if ( data.match( /<div>(<br>|)<\/div>/ ) ) {
  987. data = '<p>' + data.replace( /(<div>(<br>|)<\/div>)+/g, function( match ) {
  988. return repeatParagraphs( match.split( '</div><div>' ).length + 1 );
  989. } ) + '</p>';
  990. }
  991. // One line break create br.
  992. data = data.replace( /<\/div><div>/g, '<br>' );
  993. // Remove remaining divs.
  994. data = data.replace( /<\/?div>/g, '' );
  995. }
  996. // Opera and Firefox and enterMode != BR.
  997. if ( CKEDITOR.env.gecko && config.enterMode != CKEDITOR.ENTER_BR ) {
  998. // Remove bogus <br> - Fx generates two <brs> for one line break.
  999. // For two line breaks it still produces two <brs>, but it's better to ignore this case than the first one.
  1000. if ( CKEDITOR.env.gecko )
  1001. data = data.replace( /^<br><br>$/, '<br>' );
  1002. // This line satisfy edge case when for Opera we have two line breaks
  1003. //data = data.replace( /)
  1004. if ( data.indexOf( '<br><br>' ) > -1 ) {
  1005. // Two line breaks create one paragraph, three - 2, four - 3, etc.
  1006. data = '<p>' + data.replace( /(<br>){2,}/g, function( match ) {
  1007. return repeatParagraphs( match.length / 4 );
  1008. } ) + '</p>';
  1009. }
  1010. }
  1011. return switchEnterMode( config, data );
  1012. }
  1013. function filtersFactoryFactory() {
  1014. var filters = {};
  1015. function setUpTags() {
  1016. var tags = {};
  1017. for ( var tag in CKEDITOR.dtd ) {
  1018. if ( tag.charAt( 0 ) != '$' && tag != 'div' && tag != 'span' ) {
  1019. tags[ tag ] = 1;
  1020. }
  1021. }
  1022. return tags;
  1023. }
  1024. function createSemanticContentFilter() {
  1025. var filter = new CKEDITOR.filter();
  1026. filter.allow( {
  1027. $1: {
  1028. elements: setUpTags(),
  1029. attributes: true,
  1030. styles: false,
  1031. classes: false
  1032. }
  1033. } );
  1034. return filter;
  1035. }
  1036. return {
  1037. get: function( type ) {
  1038. if ( type == 'plain-text' ) {
  1039. // Does this look confusing to you? Did we forget about enter mode?
  1040. // It is a trick that let's us creating one filter for edidtor, regardless of its
  1041. // activeEnterMode (which as the name indicates can change during runtime).
  1042. //
  1043. // How does it work?
  1044. // The active enter mode is passed to the filter.applyTo method.
  1045. // The filter first marks all elements except <br> as disallowed and then tries to remove
  1046. // them. However, it cannot remove e.g. a <p> element completely, because it's a basic structural element,
  1047. // so it tries to replace it with an element created based on the active enter mode, eventually doing nothing.
  1048. //
  1049. // Now you can sleep well.
  1050. return filters.plainText || ( filters.plainText = new CKEDITOR.filter( 'br' ) );
  1051. } else if ( type == 'semantic-content' ) {
  1052. return filters.semanticContent || ( filters.semanticContent = createSemanticContentFilter() );
  1053. } else if ( type ) {
  1054. // Create filter based on rules (string or object).
  1055. return new CKEDITOR.filter( type );
  1056. }
  1057. return null;
  1058. }
  1059. };
  1060. }
  1061. function filterContent( editor, data, filter ) {
  1062. var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data ),
  1063. writer = new CKEDITOR.htmlParser.basicWriter();
  1064. filter.applyTo( fragment, true, false, editor.activeEnterMode );
  1065. fragment.writeHtml( writer );
  1066. return writer.getHtml();
  1067. }
  1068. function switchEnterMode( config, data ) {
  1069. if ( config.enterMode == CKEDITOR.ENTER_BR ) {
  1070. data = data.replace( /(<\/p><p>)+/g, function( match ) {
  1071. return CKEDITOR.tools.repeat( '<br>', match.length / 7 * 2 );
  1072. } ).replace( /<\/?p>/g, '' );
  1073. } else if ( config.enterMode == CKEDITOR.ENTER_DIV ) {
  1074. data = data.replace( /<(\/)?p>/g, '<$1div>' );
  1075. }
  1076. return data;
  1077. }
  1078. function preventDefaultSetDropEffectToNone( evt ) {
  1079. evt.data.preventDefault();
  1080. evt.data.$.dataTransfer.dropEffect = 'none';
  1081. }
  1082. function initDragDrop( editor ) {
  1083. var clipboard = CKEDITOR.plugins.clipboard;
  1084. editor.on( 'contentDom', function() {
  1085. var editable = editor.editable(),
  1086. dropTarget = CKEDITOR.plugins.clipboard.getDropTarget( editor ),
  1087. top = editor.ui.space( 'top' ),
  1088. bottom = editor.ui.space( 'bottom' );
  1089. // -------------- DRAGOVER TOP & BOTTOM --------------
  1090. // Not allowing dragging on toolbar and bottom (#12613).
  1091. clipboard.preventDefaultDropOnElement( top );
  1092. clipboard.preventDefaultDropOnElement( bottom );
  1093. // -------------- DRAGSTART --------------
  1094. // Listed on dragstart to mark internal and cross-editor drag & drop
  1095. // and save range and selected HTML.
  1096. editable.attachListener( dropTarget, 'dragstart', fireDragEvent );
  1097. // Make sure to reset data transfer (in case dragend was not called or was canceled).
  1098. editable.attachListener( editor, 'dragstart', clipboard.resetDragDataTransfer, clipboard, null, 1 );
  1099. // Create a dataTransfer object and save it globally.
  1100. editable.attachListener( editor, 'dragstart', function( evt ) {
  1101. clipboard.initDragDataTransfer( evt, editor );
  1102. // Save drag range globally for cross editor D&D.
  1103. var dragRange = clipboard.dragRange = editor.getSelection().getRanges()[ 0 ];
  1104. // Store number of children, so we can later tell if any text node was split on drop. (#13011, #13447)
  1105. if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) {
  1106. clipboard.dragStartContainerChildCount = dragRange ? getContainerChildCount( dragRange.startContainer ) : null;
  1107. clipboard.dragEndContainerChildCount = dragRange ? getContainerChildCount( dragRange.endContainer ) : null;
  1108. }
  1109. }, null, null, 2 );
  1110. // -------------- DRAGEND --------------
  1111. // Clean up on dragend.
  1112. editable.attachListener( dropTarget, 'dragend', fireDragEvent );
  1113. // Init data transfer if someone wants to use it in dragend.
  1114. editable.attachListener( editor, 'dragend', clipboard.initDragDataTransfer, clipboard, null, 1 );
  1115. // When drag & drop is done we need to reset dataTransfer so the future
  1116. // external drop will be not recognize as internal.
  1117. editable.attachListener( editor, 'dragend', clipboard.resetDragDataTransfer, clipboard, null, 100 );
  1118. // -------------- DRAGOVER --------------
  1119. // We need to call preventDefault on dragover because otherwise if
  1120. // we drop image it will overwrite document.
  1121. editable.attachListener( dropTarget, 'dragover', function( evt ) {
  1122. var target = evt.data.getTarget();
  1123. // Prevent reloading page when dragging image on empty document (#12619).
  1124. if ( target && target.is && target.is( 'html' ) ) {
  1125. evt.data.preventDefault();
  1126. return;
  1127. }
  1128. // If we do not prevent default dragover on IE the file path
  1129. // will be loaded and we will lose content. On the other hand
  1130. // if we prevent it the cursor will not we shown, so we prevent
  1131. // dragover only on IE, on versions which support file API and only
  1132. // if the event contains files.
  1133. if ( CKEDITOR.env.ie &&
  1134. CKEDITOR.plugins.clipboard.isFileApiSupported &&
  1135. evt.data.$.dataTransfer.types.contains( 'Files' ) ) {
  1136. evt.data.preventDefault();
  1137. }
  1138. } );
  1139. // -------------- DROP --------------
  1140. editable.attachListener( dropTarget, 'drop', function( evt ) {
  1141. // Cancel native drop.
  1142. evt.data.preventDefault();
  1143. var target = evt.data.getTarget(),
  1144. readOnly = target.isReadOnly();
  1145. // Do nothing if drop on non editable element (#13015).
  1146. // The <html> tag isn't editable (body is), but we want to allow drop on it
  1147. // (so it is possible to drop below editor contents).
  1148. if ( readOnly && !( target.type == CKEDITOR.NODE_ELEMENT && target.is( 'html' ) ) ) {
  1149. return;
  1150. }
  1151. // Getting drop position is one of the most complex parts.
  1152. var dropRange = clipboard.getRangeAtDropPosition( evt, editor ),
  1153. dragRange = clipboard.dragRange;
  1154. // Do nothing if it was not possible to get drop range.
  1155. if ( !dropRange ) {
  1156. return;
  1157. }
  1158. // Fire drop.
  1159. fireDragEvent( evt, dragRange, dropRange );
  1160. } );
  1161. // Create dataTransfer or get it, if it was created before.
  1162. editable.attachListener( editor, 'drop', clipboard.initDragDataTransfer, clipboard, null, 1 );
  1163. // Execute drop action, fire paste.
  1164. editable.attachListener( editor, 'drop', function( evt ) {
  1165. var data = evt.data;
  1166. if ( !data ) {
  1167. return;
  1168. }
  1169. // Let user modify drag and drop range.
  1170. var dropRange = data.dropRange,
  1171. dragRange = data.dragRange,
  1172. dataTransfer = data.dataTransfer;
  1173. if ( dataTransfer.getTransferType( editor ) == CKEDITOR.DATA_TRANSFER_INTERNAL ) {
  1174. // Execute drop with a timeout because otherwise selection, after drop,
  1175. // on IE is in the drag position, instead of drop position.
  1176. setTimeout( function() {
  1177. clipboard.internalDrop( dragRange, dropRange, dataTransfer, editor );
  1178. }, 0 );
  1179. } else if ( dataTransfer.getTransferType( editor ) == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) {
  1180. crossEditorDrop( dragRange, dropRange, dataTransfer );
  1181. } else {
  1182. externalDrop( dropRange, dataTransfer );
  1183. }
  1184. }, null, null, 9999 );
  1185. // Cross editor drag and drop (drag in one Editor and drop in the other).
  1186. function crossEditorDrop( dragRange, dropRange, dataTransfer ) {
  1187. // Paste event should be fired before delete contents because otherwise
  1188. // Chrome have a problem with drop range (Chrome split the drop
  1189. // range container so the offset is bigger then container length).
  1190. dropRange.select();
  1191. firePasteEvents( editor, { dataTransfer: dataTransfer, method: 'drop' }, 1 );
  1192. // Remove dragged content and make a snapshot.
  1193. dataTransfer.sourceEditor.fire( 'saveSnapshot' );
  1194. dataTransfer.sourceEditor.editable().extractHtmlFromRange( dragRange );
  1195. // Make some selection before saving snapshot, otherwise error will be thrown, because
  1196. // there will be no valid selection after content is removed.
  1197. dataTransfer.sourceEditor.getSelection().selectRanges( [ dragRange ] );
  1198. dataTransfer.sourceEditor.fire( 'saveSnapshot' );
  1199. }
  1200. // Drop from external source.
  1201. function externalDrop( dropRange, dataTransfer ) {
  1202. // Paste content into the drop position.
  1203. dropRange.select();
  1204. firePasteEvents( editor, { dataTransfer: dataTransfer, method: 'drop' }, 1 );
  1205. // Usually we reset DataTranfer on dragend,
  1206. // but dragend is called on the same element as dragstart
  1207. // so it will not be called on on external drop.
  1208. clipboard.resetDragDataTransfer();
  1209. }
  1210. // Fire drag/drop events (dragstart, dragend, drop).
  1211. function fireDragEvent( evt, dragRange, dropRange ) {
  1212. var eventData = {
  1213. $: evt.data.$,
  1214. target: evt.data.getTarget()
  1215. };
  1216. if ( dragRange ) {
  1217. eventData.dragRange = dragRange;
  1218. }
  1219. if ( dropRange ) {
  1220. eventData.dropRange = dropRange;
  1221. }
  1222. if ( editor.fire( evt.name, eventData ) === false ) {
  1223. evt.data.preventDefault();
  1224. }
  1225. }
  1226. function getContainerChildCount( container ) {
  1227. if ( container.type != CKEDITOR.NODE_ELEMENT ) {
  1228. container = container.getParent();
  1229. }
  1230. return container.getChildCount();
  1231. }
  1232. } );
  1233. }
  1234. /**
  1235. * @singleton
  1236. * @class CKEDITOR.plugins.clipboard
  1237. */
  1238. CKEDITOR.plugins.clipboard = {
  1239. /**
  1240. * True if the environment allows to set data on copy or cut manually. This value is false in IE, because this browser
  1241. * shows the security dialog window when the script tries to set clipboard data and on iOS, because custom data is
  1242. * not saved to clipboard there.
  1243. *
  1244. * @since 4.5
  1245. * @readonly
  1246. * @property {Boolean}
  1247. */
  1248. isCustomCopyCutSupported: !CKEDITOR.env.ie && !CKEDITOR.env.iOS,
  1249. /**
  1250. * True if the environment supports MIME types and custom data types in dataTransfer/cliboardData getData/setData methods.
  1251. *
  1252. * @since 4.5
  1253. * @readonly
  1254. * @property {Boolean}
  1255. */
  1256. isCustomDataTypesSupported: !CKEDITOR.env.ie,
  1257. /**
  1258. * True if the environment supports File API.
  1259. *
  1260. * @since 4.5
  1261. * @readonly
  1262. * @property {Boolean}
  1263. */
  1264. isFileApiSupported: !CKEDITOR.env.ie || CKEDITOR.env.version > 9,
  1265. /**
  1266. * Main native paste event editable should listen to.
  1267. *
  1268. * **Note:** Safari does not like the {@link CKEDITOR.editor#beforePaste} event &mdash; it sometimes does not
  1269. * handle <kbd>Ctrl+C</kbd> properly. This is probably caused by some race condition between events.
  1270. * Chrome, Firefox and Edge work well with both events, so it is better to use {@link CKEDITOR.editor#paste}
  1271. * which will handle pasting from e.g. browsers' menu bars.
  1272. * IE7/8 does not like the {@link CKEDITOR.editor#paste} event for which it is throwing random errors.
  1273. *
  1274. * @since 4.5
  1275. * @readonly
  1276. * @property {String}
  1277. */
  1278. mainPasteEvent: ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) ? 'beforepaste' : 'paste',
  1279. /**
  1280. * Returns `true` if it is expected that a browser provides HTML data through the Clipboard API.
  1281. * If not, this method returns `false` and as a result CKEditor will use the paste bin. Read more in
  1282. * the [Clipboard Integration](http://docs.ckeditor.com/#!/guide/dev_clipboard-section-clipboard-api) guide.
  1283. *
  1284. * @since 4.5.2
  1285. * @returns {Boolean}
  1286. */
  1287. canClipboardApiBeTrusted: function( dataTransfer, editor ) {
  1288. // If it's an internal or cross-editor data transfer, then it means that custom cut/copy/paste support works
  1289. // and that the data were put manually on the data transfer so we can be sure that it's available.
  1290. if ( dataTransfer.getTransferType( editor ) != CKEDITOR.DATA_TRANSFER_EXTERNAL ) {
  1291. return true;
  1292. }
  1293. // In Chrome we can trust Clipboard API, with the exception of Chrome on Android (in both - mobile and desktop modes), where
  1294. // clipboard API is not available so we need to check it (#13187).
  1295. if ( CKEDITOR.env.chrome && !dataTransfer.isEmpty() ) {
  1296. return true;
  1297. }
  1298. // Because of a Firefox bug HTML data are not available in some cases (e.g. paste from Word), in such cases we
  1299. // need to use the pastebin (#13528, https://bugzilla.mozilla.org/show_bug.cgi?id=1183686).
  1300. if ( CKEDITOR.env.gecko && ( dataTransfer.getData( 'text/html' ) || dataTransfer.getFilesCount() ) ) {
  1301. return true;
  1302. }
  1303. // In Safari and IE HTML data is not available though the Clipboard API.
  1304. // In Edge things are a bit messy at the moment -
  1305. // https://connect.microsoft.com/IE/feedback/details/1572456/edge-clipboard-api-text-html-content-messed-up-in-event-clipboarddata
  1306. // It is safer to use the paste bin in unknown cases.
  1307. return false;
  1308. },
  1309. /**
  1310. * Returns the element that should be used as the target for the drop event.
  1311. *
  1312. * @since 4.5
  1313. * @param {CKEDITOR.editor} editor The editor instance.
  1314. * @returns {CKEDITOR.dom.domObject} the element that should be used as the target for the drop event.
  1315. */
  1316. getDropTarget: function( editor ) {
  1317. var editable = editor.editable();
  1318. // #11123 Firefox needs to listen on document, because otherwise event won't be fired.
  1319. // #11086 IE8 cannot listen on document.
  1320. if ( ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ) {
  1321. return editable;
  1322. } else {
  1323. return editor.document;
  1324. }
  1325. },
  1326. /**
  1327. * IE 8 & 9 split text node on drop so the first node contains the
  1328. * text before the drop position and the second contains the rest. If you
  1329. * drag the content from the same node you will be not be able to get
  1330. * it (the range becomes invalid), so you need to join them back.
  1331. *
  1332. * Note that the first node in IE 8 & 9 is the original node object
  1333. * but with shortened content.
  1334. *
  1335. * Before:
  1336. * --- Text Node A ----------------------------------
  1337. * /\
  1338. * Drag position
  1339. *
  1340. * After (IE 8 & 9):
  1341. * --- Text Node A ----- --- Text Node B -----------
  1342. * /\ /\
  1343. * Drop position Drag position
  1344. * (invalid)
  1345. *
  1346. * After (other browsers):
  1347. * --- Text Node A ----------------------------------
  1348. * /\ /\
  1349. * Drop position Drag position
  1350. *
  1351. * **Note:** This function is in the public scope for tests usage only.
  1352. *
  1353. * @since 4.5
  1354. * @private
  1355. * @param {CKEDITOR.dom.range} dragRange The drag range.
  1356. * @param {CKEDITOR.dom.range} dropRange The drop range.
  1357. * @param {Number} preDragStartContainerChildCount The number of children of the drag range start container before the drop.
  1358. * @param {Number} preDragEndContainerChildCount The number of children of the drag range end container before the drop.
  1359. */
  1360. fixSplitNodesAfterDrop: function( dragRange, dropRange, preDragStartContainerChildCount, preDragEndContainerChildCount ) {
  1361. var dropContainer = dropRange.startContainer;
  1362. if (
  1363. typeof preDragEndContainerChildCount != 'number' ||
  1364. typeof preDragStartContainerChildCount != 'number'
  1365. ) {
  1366. return;
  1367. }
  1368. // We are only concerned about ranges anchored in elements.
  1369. if ( dropContainer.type != CKEDITOR.NODE_ELEMENT ) {
  1370. return;
  1371. }
  1372. if ( handleContainer( dragRange.startContainer, dropContainer, preDragStartContainerChildCount ) ) {
  1373. return;
  1374. }
  1375. if ( handleContainer( dragRange.endContainer, dropContainer, preDragEndContainerChildCount ) ) {
  1376. return;
  1377. }
  1378. function handleContainer( dragContainer, dropContainer, preChildCount ) {
  1379. var dragElement = dragContainer;
  1380. if ( dragElement.type == CKEDITOR.NODE_TEXT ) {
  1381. dragElement = dragContainer.getParent();
  1382. }
  1383. if ( dragElement.equals( dropContainer ) && preChildCount != dropContainer.getChildCount() ) {
  1384. applyFix( dropRange );
  1385. return true;
  1386. }
  1387. }
  1388. function applyFix( dropRange ) {
  1389. var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ),
  1390. nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset );
  1391. if (
  1392. nodeBefore && nodeBefore.type == CKEDITOR.NODE_TEXT &&
  1393. nodeAfter && nodeAfter.type == CKEDITOR.NODE_TEXT
  1394. ) {
  1395. var offset = nodeBefore.getLength();
  1396. nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() );
  1397. nodeAfter.remove();
  1398. dropRange.setStart( nodeBefore, offset );
  1399. dropRange.collapse( true );
  1400. }
  1401. }
  1402. },
  1403. /**
  1404. * Checks whether turning the drag range into bookmarks will invalidate the drop range.
  1405. * This usually happens when the drop range shares the container with the drag range and is
  1406. * located after the drag range, but there are countless edge cases.
  1407. *
  1408. * This function is stricly related to {@link #internalDrop} which toggles
  1409. * order in which it creates bookmarks for both ranges based on a value returned
  1410. * by this method. In some cases this method returns a value which is not necessarily
  1411. * true in terms of what it was meant to check, but it is convenient, because
  1412. * we know how it is interpreted in {@link #internalDrop}, so the correct
  1413. * behavior of the entire algorithm is assured.
  1414. *
  1415. * **Note:** This function is in the public scope for tests usage only.
  1416. *
  1417. * @since 4.5
  1418. * @private
  1419. * @param {CKEDITOR.dom.range} dragRange The first range to compare.
  1420. * @param {CKEDITOR.dom.range} dropRange The second range to compare.
  1421. * @returns {Boolean} `true` if the first range is before the second range.
  1422. */
  1423. isDropRangeAffectedByDragRange: function( dragRange, dropRange ) {
  1424. var dropContainer = dropRange.startContainer,
  1425. dropOffset = dropRange.endOffset;
  1426. // Both containers are the same and drop offset is at the same position or later.
  1427. // " A L] A " " M A "
  1428. // ^ ^
  1429. if ( dragRange.endContainer.equals( dropContainer ) && dragRange.endOffset <= dropOffset ) {
  1430. return true;
  1431. }
  1432. // Bookmark for drag start container will mess up with offsets.
  1433. // " O [L A " " M A "
  1434. // ^ ^
  1435. if (
  1436. dragRange.startContainer.getParent().equals( dropContainer ) &&
  1437. dragRange.startContainer.getIndex() < dropOffset
  1438. ) {
  1439. return true;
  1440. }
  1441. // Bookmark for drag end container will mess up with offsets.
  1442. // " O] L A " " M A "
  1443. // ^ ^
  1444. if (
  1445. dragRange.endContainer.getParent().equals( dropContainer ) &&
  1446. dragRange.endContainer.getIndex() < dropOffset
  1447. ) {
  1448. return true;
  1449. }
  1450. return false;
  1451. },
  1452. /**
  1453. * Internal drag and drop (drag and drop in the same editor instance).
  1454. *
  1455. * **Note:** This function is in the public scope for tests usage only.
  1456. *
  1457. * @since 4.5
  1458. * @private
  1459. * @param {CKEDITOR.dom.range} dragRange The first range to compare.
  1460. * @param {CKEDITOR.dom.range} dropRange The second range to compare.
  1461. * @param {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer
  1462. * @param {CKEDITOR.editor} editor
  1463. */
  1464. internalDrop: function( dragRange, dropRange, dataTransfer, editor ) {
  1465. var clipboard = CKEDITOR.plugins.clipboard,
  1466. editable = editor.editable(),
  1467. dragBookmark, dropBookmark, isDropRangeAffected;
  1468. // Save and lock snapshot so there will be only
  1469. // one snapshot for both remove and insert content.
  1470. editor.fire( 'saveSnapshot' );
  1471. editor.fire( 'lockSnapshot', { dontUpdate: 1 } );
  1472. if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) {
  1473. this.fixSplitNodesAfterDrop(
  1474. dragRange,
  1475. dropRange,
  1476. clipboard.dragStartContainerChildCount,
  1477. clipboard.dragEndContainerChildCount
  1478. );
  1479. }
  1480. // Because we manipulate multiple ranges we need to do it carefully,
  1481. // changing one range (event creating a bookmark) may make other invalid.
  1482. // We need to change ranges into bookmarks so we can manipulate them easily in the future.
  1483. // We can change the range which is later in the text before we change the preceding range.
  1484. // We call isDropRangeAffectedByDragRange to test the order of ranges.
  1485. isDropRangeAffected = this.isDropRangeAffectedByDragRange( dragRange, dropRange );
  1486. if ( !isDropRangeAffected ) {
  1487. dragBookmark = dragRange.createBookmark( false );
  1488. }
  1489. dropBookmark = dropRange.clone().createBookmark( false );
  1490. if ( isDropRangeAffected ) {
  1491. dragBookmark = dragRange.createBookmark( false );
  1492. }
  1493. // Check if drop range is inside range.
  1494. // This is an edge case when we drop something on editable's margin/padding.
  1495. // That space is not treated as a part of the range we drag, so it is possible to drop there.
  1496. // When we drop, browser tries to find closest drop position and it finds it inside drag range. (#13453)
  1497. var startNode = dragBookmark.startNode,
  1498. endNode = dragBookmark.endNode,
  1499. dropNode = dropBookmark.startNode,
  1500. dropInsideDragRange =
  1501. // Must check endNode because dragRange could be collapsed in some edge cases (simulated DnD).
  1502. endNode &&
  1503. startNode.getPosition( dropNode ) == CKEDITOR.POSITION_PRECEDING &&
  1504. endNode.getPosition( dropNode ) == CKEDITOR.POSITION_FOLLOWING;
  1505. if ( dropInsideDragRange ) {
  1506. // When we normally drag and drop, the selection is changed to dropRange,
  1507. // so here we simulate the same behavior.
  1508. editor.getSelection().selectRanges( [ dropRange ] );
  1509. // Remove bookmark spans.
  1510. startNode.remove();
  1511. endNode.remove();
  1512. dropNode.remove();
  1513. }
  1514. else {
  1515. // Drop range is outside drag range.
  1516. // No we can safely delete content for the drag range...
  1517. dragRange = editor.createRange();
  1518. dragRange.moveToBookmark( dragBookmark );
  1519. editable.extractHtmlFromRange( dragRange, 1 );
  1520. // ...and paste content into the drop position.
  1521. dropRange = editor.createRange();
  1522. dropRange.moveToBookmark( dropBookmark );
  1523. // We do not select drop range, because of may be in the place we can not set the selection
  1524. // (e.g. between blocks, in case of block widget D&D). We put range to the paste event instead.
  1525. firePasteEvents( editor, { dataTransfer: dataTransfer, method: 'drop', range: dropRange }, 1 );
  1526. }
  1527. editor.fire( 'unlockSnapshot' );
  1528. },
  1529. /**
  1530. * Gets the range from the `drop` event.
  1531. *
  1532. * @since 4.5
  1533. * @param {Object} domEvent A native DOM drop event object.
  1534. * @param {CKEDITOR.editor} editor The source editor instance.
  1535. * @returns {CKEDITOR.dom.range} range at drop position.
  1536. */
  1537. getRangeAtDropPosition: function( dropEvt, editor ) {
  1538. var $evt = dropEvt.data.$,
  1539. x = $evt.clientX,
  1540. y = $evt.clientY,
  1541. $range,
  1542. defaultRange = editor.getSelection( true ).getRanges()[ 0 ],
  1543. range = editor.createRange();
  1544. // Make testing possible.
  1545. if ( dropEvt.data.testRange )
  1546. return dropEvt.data.testRange;
  1547. // Webkits.
  1548. if ( document.caretRangeFromPoint ) {
  1549. $range = editor.document.$.caretRangeFromPoint( x, y );
  1550. range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset );
  1551. range.collapse( true );
  1552. }
  1553. // FF.
  1554. else if ( $evt.rangeParent ) {
  1555. range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset );
  1556. range.collapse( true );
  1557. }
  1558. // IEs 9+.
  1559. // We check if editable is focused to make sure that it's an internal DnD. External DnD must use the second
  1560. // mechanism because of http://dev.ckeditor.com/ticket/13472#comment:6.
  1561. else if ( CKEDITOR.env.ie && CKEDITOR.env.version > 8 && defaultRange && editor.editable().hasFocus ) {
  1562. // On IE 9+ range by default is where we expected it.
  1563. // defaultRange may be undefined if dragover was canceled (file drop).
  1564. return defaultRange;
  1565. }
  1566. // IE 8 and all IEs if !defaultRange or external DnD.
  1567. else if ( document.body.createTextRange ) {
  1568. // To use this method we need a focus (which may be somewhere else in case of external drop).
  1569. editor.focus();
  1570. $range = editor.document.getBody().$.createTextRange();
  1571. try {
  1572. var sucess = false;
  1573. // If user drop between text line IEs moveToPoint throws exception:
  1574. //
  1575. // Lorem ipsum pulvinar purus et euismod
  1576. //
  1577. // dolor sit amet,| consectetur adipiscing
  1578. // *
  1579. // vestibulum tincidunt augue eget tempus.
  1580. //
  1581. // * - drop position
  1582. // | - expected cursor position
  1583. //
  1584. // So we try to call moveToPoint with +-1px up to +-20px above or
  1585. // below original drop position to find nearest good drop position.
  1586. for ( var i = 0; i < 20 && !sucess; i++ ) {
  1587. if ( !sucess ) {
  1588. try {
  1589. $range.moveToPoint( x, y - i );
  1590. sucess = true;
  1591. } catch ( err ) {
  1592. }
  1593. }
  1594. if ( !sucess ) {
  1595. try {
  1596. $range.moveToPoint( x, y + i );
  1597. sucess = true;
  1598. } catch ( err ) {
  1599. }
  1600. }
  1601. }
  1602. if ( sucess ) {
  1603. var id = 'cke-temp-' + ( new Date() ).getTime();
  1604. $range.pasteHTML( '<span id="' + id + '">\u200b</span>' );
  1605. var span = editor.document.getById( id );
  1606. range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START );
  1607. span.remove();
  1608. } else {
  1609. // If the fist method does not succeed we might be next to
  1610. // the short element (like header):
  1611. //
  1612. // Lorem ipsum pulvinar purus et euismod.
  1613. //
  1614. //
  1615. // SOME HEADER| *
  1616. //
  1617. //
  1618. // vestibulum tincidunt augue eget tempus.
  1619. //
  1620. // * - drop position
  1621. // | - expected cursor position
  1622. //
  1623. // In such situation elementFromPoint returns proper element. Using getClientRect
  1624. // it is possible to check if the cursor should be at the beginning or at the end
  1625. // of paragraph.
  1626. var $element = editor.document.$.elementFromPoint( x, y ),
  1627. element = new CKEDITOR.dom.element( $element ),
  1628. rect;
  1629. if ( !element.equals( editor.editable() ) && element.getName() != 'html' ) {
  1630. rect = element.getClientRect();
  1631. if ( x < rect.left ) {
  1632. range.setStartAt( element, CKEDITOR.POSITION_AFTER_START );
  1633. range.collapse( true );
  1634. } else {
  1635. range.setStartAt( element, CKEDITOR.POSITION_BEFORE_END );
  1636. range.collapse( true );
  1637. }
  1638. }
  1639. // If drop happens on no element elementFromPoint returns html or body.
  1640. //
  1641. // * |Lorem ipsum pulvinar purus et euismod.
  1642. //
  1643. // vestibulum tincidunt augue eget tempus.
  1644. //
  1645. // * - drop position
  1646. // | - expected cursor position
  1647. //
  1648. // In such case we can try to use default selection. If startContainer is not
  1649. // 'editable' element it is probably proper selection.
  1650. else if ( defaultRange && defaultRange.startContainer &&
  1651. !defaultRange.startContainer.equals( editor.editable() ) ) {
  1652. return defaultRange;
  1653. // Otherwise we can not find any drop position and we have to return null
  1654. // and cancel drop event.
  1655. } else {
  1656. return null;
  1657. }
  1658. }
  1659. } catch ( err ) {
  1660. return null;
  1661. }
  1662. } else {
  1663. return null;
  1664. }
  1665. return range;
  1666. },
  1667. /**
  1668. * This function tries to link the `evt.data.dataTransfer` property of the {@link CKEDITOR.editor#dragstart},
  1669. * {@link CKEDITOR.editor#dragend} and {@link CKEDITOR.editor#drop} events to a single
  1670. * {@link CKEDITOR.plugins.clipboard.dataTransfer} object.
  1671. *
  1672. * This method is automatically used by the core of the drag and drop functionality and
  1673. * usually does not have to be called manually when using the drag and drop events.
  1674. *
  1675. * This method behaves differently depending on whether the drag and drop events were fired
  1676. * artificially (to represent a non-native drag and drop) or whether they were caused by the native drag and drop.
  1677. *
  1678. * If the native event is not available, then it will create a new {@link CKEDITOR.plugins.clipboard.dataTransfer}
  1679. * instance (if it does not exist already) and will link it to this and all following event objects until
  1680. * the {@link #resetDragDataTransfer} method is called. It means that all three drag and drop events must be fired
  1681. * in order to ensure that the data transfer is bound correctly.
  1682. *
  1683. * If the native event is available, then the {@link CKEDITOR.plugins.clipboard.dataTransfer} is identified
  1684. * by its ID and a new instance is assigned to the `evt.data.dataTransfer` only if the ID changed or
  1685. * the {@link #resetDragDataTransfer} method was called.
  1686. *
  1687. * @since 4.5
  1688. * @param {CKEDITOR.dom.event} [evt] A drop event object.
  1689. * @param {CKEDITOR.editor} [sourceEditor] The source editor instance.
  1690. */
  1691. initDragDataTransfer: function( evt, sourceEditor ) {
  1692. // Create a new dataTransfer object based on the drop event.
  1693. // If this event was used on dragstart to create dataTransfer
  1694. // both dataTransfer objects will have the same id.
  1695. var nativeDataTransfer = evt.data.$ ? evt.data.$.dataTransfer : null,
  1696. dataTransfer = new this.dataTransfer( nativeDataTransfer, sourceEditor );
  1697. if ( !nativeDataTransfer ) {
  1698. // No native event.
  1699. if ( this.dragData ) {
  1700. dataTransfer = this.dragData;
  1701. } else {
  1702. this.dragData = dataTransfer;
  1703. }
  1704. } else {
  1705. // Native event. If there is the same id we will replace dataTransfer with the one
  1706. // created on drag, because it contains drag editor, drag content and so on.
  1707. // Otherwise (in case of drag from external source) we save new object to
  1708. // the global clipboard.dragData.
  1709. if ( this.dragData && dataTransfer.id == this.dragData.id ) {
  1710. dataTransfer = this.dragData;
  1711. } else {
  1712. this.dragData = dataTransfer;
  1713. }
  1714. }
  1715. evt.data.dataTransfer = dataTransfer;
  1716. },
  1717. /**
  1718. * Removes the global {@link #dragData} so the next call to {@link #initDragDataTransfer}
  1719. * always creates a new instance of {@link CKEDITOR.plugins.clipboard.dataTransfer}.
  1720. *
  1721. * @since 4.5
  1722. */
  1723. resetDragDataTransfer: function() {
  1724. this.dragData = null;
  1725. },
  1726. /**
  1727. * Global object storing the data transfer of the current drag and drop operation.
  1728. * Do not use it directly, use {@link #initDragDataTransfer} and {@link #resetDragDataTransfer}.
  1729. *
  1730. * Note: This object is global (meaning that it is not related to a single editor instance)
  1731. * in order to handle drag and drop from one editor into another.
  1732. *
  1733. * @since 4.5
  1734. * @private
  1735. * @property {CKEDITOR.plugins.clipboard.dataTransfer} dragData
  1736. */
  1737. /**
  1738. * Range object to save the drag range and remove its content after the drop.
  1739. *
  1740. * @since 4.5
  1741. * @private
  1742. * @property {CKEDITOR.dom.range} dragRange
  1743. */
  1744. /**
  1745. * Initializes and links data transfer objects based on the paste event. If the data
  1746. * transfer object was already initialized on this event, the function will
  1747. * return that object. In IE it is not possible to link copy/cut and paste events
  1748. * so the method always returns a new object. The same happens if there is no paste event
  1749. * passed to the method.
  1750. *
  1751. * @since 4.5
  1752. * @param {CKEDITOR.dom.event} [evt] A paste event object.
  1753. * @param {CKEDITOR.editor} [sourceEditor] The source editor instance.
  1754. * @returns {CKEDITOR.plugins.clipboard.dataTransfer} The data transfer object.
  1755. */
  1756. initPasteDataTransfer: function( evt, sourceEditor ) {
  1757. if ( !this.isCustomCopyCutSupported ) {
  1758. return new this.dataTransfer( null, sourceEditor );
  1759. } else if ( evt && evt.data && evt.data.$ ) {
  1760. var dataTransfer = new this.dataTransfer( evt.data.$.clipboardData, sourceEditor );
  1761. if ( this.copyCutData && dataTransfer.id == this.copyCutData.id ) {
  1762. dataTransfer = this.copyCutData;
  1763. dataTransfer.$ = evt.data.$.clipboardData;
  1764. } else {
  1765. this.copyCutData = dataTransfer;
  1766. }
  1767. return dataTransfer;
  1768. } else {
  1769. return new this.dataTransfer( null, sourceEditor );
  1770. }
  1771. },
  1772. /**
  1773. * Prevents dropping on the specified element.
  1774. *
  1775. * @since 4.5
  1776. * @param {CKEDITOR.dom.element} element The element on which dropping should be disabled.
  1777. */
  1778. preventDefaultDropOnElement: function( element ) {
  1779. element && element.on( 'dragover', preventDefaultSetDropEffectToNone );
  1780. }
  1781. };
  1782. // Data type used to link drag and drop events.
  1783. //
  1784. // In IE URL data type is buggie and there is no way to mark drag & drop without
  1785. // modifying text data (which would be displayed if user drop content to the textarea)
  1786. // so we just read dragged text.
  1787. //
  1788. // In Chrome and Firefox we can use custom data types.
  1789. var clipboardIdDataType = CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ? 'cke/id' : 'Text';
  1790. /**
  1791. * Facade for the native `dataTransfer`/`clipboadData` object to hide all differences
  1792. * between browsers.
  1793. *
  1794. * @since 4.5
  1795. * @class CKEDITOR.plugins.clipboard.dataTransfer
  1796. * @constructor Creates a class instance.
  1797. * @param {Object} [nativeDataTransfer] A native data transfer object.
  1798. * @param {CKEDITOR.editor} [editor] The source editor instance. If the editor is defined, dataValue will
  1799. * be created based on the editor content and the type will be 'html'.
  1800. */
  1801. CKEDITOR.plugins.clipboard.dataTransfer = function( nativeDataTransfer, editor ) {
  1802. if ( nativeDataTransfer ) {
  1803. this.$ = nativeDataTransfer;
  1804. }
  1805. this._ = {
  1806. metaRegExp: /^<meta.*?>/,
  1807. bodyRegExp: /<body(?:[\s\S]*?)>([\s\S]*)<\/body>/,
  1808. fragmentRegExp: /<!--(?:Start|End)Fragment-->/g,
  1809. data: {},
  1810. files: [],
  1811. normalizeType: function( type ) {
  1812. type = type.toLowerCase();
  1813. if ( type == 'text' || type == 'text/plain' ) {
  1814. return 'Text'; // IE support only Text and URL;
  1815. } else if ( type == 'url' ) {
  1816. return 'URL'; // IE support only Text and URL;
  1817. } else {
  1818. return type;
  1819. }
  1820. }
  1821. };
  1822. // Check if ID is already created.
  1823. this.id = this.getData( clipboardIdDataType );
  1824. // If there is no ID we need to create it. Different browsers needs different ID.
  1825. if ( !this.id ) {
  1826. if ( clipboardIdDataType == 'Text' ) {
  1827. // For IE10+ only Text data type is supported and we have to compare dragged
  1828. // and dropped text. If the ID is not set it means that empty string was dragged
  1829. // (ex. image with no alt). We change null to empty string.
  1830. this.id = '';
  1831. } else {
  1832. // String for custom data type.
  1833. this.id = 'cke-' + CKEDITOR.tools.getUniqueId();
  1834. }
  1835. }
  1836. // In IE10+ we can not use any data type besides text, so we do not call setData.
  1837. if ( clipboardIdDataType != 'Text' ) {
  1838. // Try to set ID so it will be passed from the drag to the drop event.
  1839. // On some browsers with some event it is not possible to setData so we
  1840. // need to catch exceptions.
  1841. try {
  1842. this.$.setData( clipboardIdDataType, this.id );
  1843. } catch ( err ) {}
  1844. }
  1845. if ( editor ) {
  1846. this.sourceEditor = editor;
  1847. this.setData( 'text/html', editor.getSelectedHtml( 1 ) );
  1848. // Without setData( 'text', ... ) on dragstart there is no drop event in Safari.
  1849. // Also 'text' data is empty as drop to the textarea does not work if we do not put there text.
  1850. if ( clipboardIdDataType != 'Text' && !this.getData( 'text/plain' ) ) {
  1851. this.setData( 'text/plain', editor.getSelection().getSelectedText() );
  1852. }
  1853. }
  1854. /**
  1855. * Data transfer ID used to bind all dataTransfer
  1856. * objects based on the same event (e.g. in drag and drop events).
  1857. *
  1858. * @readonly
  1859. * @property {String} id
  1860. */
  1861. /**
  1862. * A native DOM event object.
  1863. *
  1864. * @readonly
  1865. * @property {Object} $
  1866. */
  1867. /**
  1868. * Source editor &mdash; the editor where the drag starts.
  1869. * Might be undefined if the drag starts outside the editor (e.g. when dropping files to the editor).
  1870. *
  1871. * @readonly
  1872. * @property {CKEDITOR.editor} sourceEditor
  1873. */
  1874. /**
  1875. * Private properties and methods.
  1876. *
  1877. * @private
  1878. * @property {Object} _
  1879. */
  1880. };
  1881. /**
  1882. * Data transfer operation (drag and drop or copy and paste) started and ended in the same
  1883. * editor instance.
  1884. *
  1885. * @since 4.5
  1886. * @readonly
  1887. * @property {Number} [=1]
  1888. * @member CKEDITOR
  1889. */
  1890. CKEDITOR.DATA_TRANSFER_INTERNAL = 1;
  1891. /**
  1892. * Data transfer operation (drag and drop or copy and paste) started in one editor
  1893. * instance and ended in another.
  1894. *
  1895. * @since 4.5
  1896. * @readonly
  1897. * @property {Number} [=2]
  1898. * @member CKEDITOR
  1899. */
  1900. CKEDITOR.DATA_TRANSFER_CROSS_EDITORS = 2;
  1901. /**
  1902. * Data transfer operation (drag and drop or copy and paste) started outside of the editor.
  1903. * The source of the data may be a textarea, HTML, another application, etc.
  1904. *
  1905. * @since 4.5
  1906. * @readonly
  1907. * @property {Number} [=3]
  1908. * @member CKEDITOR
  1909. */
  1910. CKEDITOR.DATA_TRANSFER_EXTERNAL = 3;
  1911. CKEDITOR.plugins.clipboard.dataTransfer.prototype = {
  1912. /**
  1913. * Facade for the native `getData` method.
  1914. *
  1915. * @param {String} type The type of data to retrieve.
  1916. * @returns {String} type Stored data for the given type or an empty string if the data for that type does not exist.
  1917. */
  1918. getData: function( type ) {
  1919. function isEmpty( data ) {
  1920. return data === undefined || data === null || data === '';
  1921. }
  1922. type = this._.normalizeType( type );
  1923. var data = this._.data[ type ],
  1924. result;
  1925. if ( isEmpty( data ) ) {
  1926. try {
  1927. data = this.$.getData( type );
  1928. } catch ( e ) {}
  1929. }
  1930. if ( isEmpty( data ) ) {
  1931. data = '';
  1932. }
  1933. // Some browsers add <meta http-equiv="content-type" content="text/html; charset=utf-8"> at the begging of the HTML data
  1934. // or surround it with <html><head>...</head><body>(some content)<!--StartFragment--> and <!--EndFragment-->(some content)</body></html>
  1935. // This code removes meta tags and returns only the contents of the <body> element if found. Note that
  1936. // some significant content may be placed outside Start/EndFragment comments so it's kept.
  1937. //
  1938. // See #13583 for more details.
  1939. if ( type == 'text/html' ) {
  1940. data = data.replace( this._.metaRegExp, '' );
  1941. // Keep only contents of the <body> element
  1942. result = this._.bodyRegExp.exec( data );
  1943. if ( result && result.length ) {
  1944. data = result[ 1 ];
  1945. // Remove also comments.
  1946. data = data.replace( this._.fragmentRegExp, '' );
  1947. }
  1948. }
  1949. // Firefox on Linux put files paths as a text/plain data if there are files
  1950. // in the dataTransfer object. We need to hide it, because files should be
  1951. // handled on paste only if dataValue is empty.
  1952. else if ( type == 'Text' && CKEDITOR.env.gecko && this.getFilesCount() &&
  1953. data.substring( 0, 7 ) == 'file://' ) {
  1954. data = '';
  1955. }
  1956. return data;
  1957. },
  1958. /**
  1959. * Facade for the native `setData` method.
  1960. *
  1961. * @param {String} type The type of data to retrieve.
  1962. * @param {String} value The data to add.
  1963. */
  1964. setData: function( type, value ) {
  1965. type = this._.normalizeType( type );
  1966. this._.data[ type ] = value;
  1967. // There is "Unexpected call to method or property access." error if you try
  1968. // to set data of unsupported type on IE.
  1969. if ( !CKEDITOR.plugins.clipboard.isCustomDataTypesSupported && type != 'URL' && type != 'Text' ) {
  1970. return;
  1971. }
  1972. // If we use the text type to bind the ID, then if someone tries to set the text, we must also
  1973. // update ID accordingly. #13468.
  1974. if ( clipboardIdDataType == 'Text' && type == 'Text' ) {
  1975. this.id = value;
  1976. }
  1977. try {
  1978. this.$.setData( type, value );
  1979. } catch ( e ) {}
  1980. },
  1981. /**
  1982. * Gets the data transfer type.
  1983. *
  1984. * @param {CKEDITOR.editor} targetEditor The drop/paste target editor instance.
  1985. * @returns {Number} Possible values: {@link CKEDITOR#DATA_TRANSFER_INTERNAL},
  1986. * {@link CKEDITOR#DATA_TRANSFER_CROSS_EDITORS}, {@link CKEDITOR#DATA_TRANSFER_EXTERNAL}.
  1987. */
  1988. getTransferType: function( targetEditor ) {
  1989. if ( !this.sourceEditor ) {
  1990. return CKEDITOR.DATA_TRANSFER_EXTERNAL;
  1991. } else if ( this.sourceEditor == targetEditor ) {
  1992. return CKEDITOR.DATA_TRANSFER_INTERNAL;
  1993. } else {
  1994. return CKEDITOR.DATA_TRANSFER_CROSS_EDITORS;
  1995. }
  1996. },
  1997. /**
  1998. * Copies the data from the native data transfer to a private cache.
  1999. * This function is needed because the data from the native data transfer
  2000. * is available only synchronously to the event listener. It is not possible
  2001. * to get the data asynchronously, after a timeout, and the {@link CKEDITOR.editor#paste}
  2002. * event is fired asynchronously &mdash; hence the need for caching the data.
  2003. */
  2004. cacheData: function() {
  2005. if ( !this.$ ) {
  2006. return;
  2007. }
  2008. var that = this,
  2009. i, file;
  2010. function getAndSetData( type ) {
  2011. type = that._.normalizeType( type );
  2012. var data = that.getData( type );
  2013. if ( data ) {
  2014. that._.data[ type ] = data;
  2015. }
  2016. }
  2017. // Copy data.
  2018. if ( CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ) {
  2019. if ( this.$.types ) {
  2020. for ( i = 0; i < this.$.types.length; i++ ) {
  2021. getAndSetData( this.$.types[ i ] );
  2022. }
  2023. }
  2024. } else {
  2025. getAndSetData( 'Text' );
  2026. getAndSetData( 'URL' );
  2027. }
  2028. // Copy files references.
  2029. file = this._getImageFromClipboard();
  2030. if ( ( this.$ && this.$.files ) || file ) {
  2031. this._.files = [];
  2032. for ( i = 0; i < this.$.files.length; i++ ) {
  2033. this._.files.push( this.$.files[ i ] );
  2034. }
  2035. // Don't include $.items if both $.files and $.items contains files, because,
  2036. // according to spec and browsers behavior, they contain the same files.
  2037. if ( this._.files.length === 0 && file ) {
  2038. this._.files.push( file );
  2039. }
  2040. }
  2041. },
  2042. /**
  2043. * Gets the number of files in the dataTransfer object.
  2044. *
  2045. * @returns {Number} The number of files.
  2046. */
  2047. getFilesCount: function() {
  2048. if ( this._.files.length ) {
  2049. return this._.files.length;
  2050. }
  2051. if ( this.$ && this.$.files && this.$.files.length ) {
  2052. return this.$.files.length;
  2053. }
  2054. return this._getImageFromClipboard() ? 1 : 0;
  2055. },
  2056. /**
  2057. * Gets the file at the index given.
  2058. *
  2059. * @param {Number} i Index.
  2060. * @returns {File} File instance.
  2061. */
  2062. getFile: function( i ) {
  2063. if ( this._.files.length ) {
  2064. return this._.files[ i ];
  2065. }
  2066. if ( this.$ && this.$.files && this.$.files.length ) {
  2067. return this.$.files[ i ];
  2068. }
  2069. // File or null if the file was not found.
  2070. return i === 0 ? this._getImageFromClipboard() : undefined;
  2071. },
  2072. /**
  2073. * Checks if the data transfer contains any data.
  2074. *
  2075. * @returns {Boolean} `true` if the object contains no data.
  2076. */
  2077. isEmpty: function() {
  2078. var typesToCheck = {},
  2079. type;
  2080. // If dataTransfer contains files it is not empty.
  2081. if ( this.getFilesCount() ) {
  2082. return false;
  2083. }
  2084. // Add custom types.
  2085. for ( type in this._.data ) {
  2086. typesToCheck[ type ] = 1;
  2087. }
  2088. // Add native types.
  2089. if ( this.$ ) {
  2090. if ( CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ) {
  2091. if ( this.$.types ) {
  2092. for ( var i = 0; i < this.$.types.length; i++ ) {
  2093. typesToCheck[ this.$.types[ i ] ] = 1;
  2094. }
  2095. }
  2096. } else {
  2097. typesToCheck.Text = 1;
  2098. typesToCheck.URL = 1;
  2099. }
  2100. }
  2101. // Remove ID.
  2102. if ( clipboardIdDataType != 'Text' ) {
  2103. typesToCheck[ clipboardIdDataType ] = 0;
  2104. }
  2105. for ( type in typesToCheck ) {
  2106. if ( typesToCheck[ type ] && this.getData( type ) !== '' ) {
  2107. return false;
  2108. }
  2109. }
  2110. return true;
  2111. },
  2112. /**
  2113. * When the content of the clipboard is pasted in Chrome, the clipboard data object has an empty `files` property,
  2114. * but it is possible to get the file as `items[0].getAsFile();` (#12961).
  2115. *
  2116. * @private
  2117. * @returns {File} File instance or `null` if not found.
  2118. */
  2119. _getImageFromClipboard: function() {
  2120. var file;
  2121. if ( this.$ && this.$.items && this.$.items[ 0 ] ) {
  2122. try {
  2123. file = this.$.items[ 0 ].getAsFile();
  2124. // Duck typing
  2125. if ( file && file.type ) {
  2126. return file;
  2127. }
  2128. } catch ( err ) {
  2129. // noop
  2130. }
  2131. }
  2132. return undefined;
  2133. }
  2134. };
  2135. } )();
  2136. /**
  2137. * The default content type that is used when pasted data cannot be clearly recognized as HTML or text.
  2138. *
  2139. * For example: `'foo'` may come from a plain text editor or a website. It is not possible to recognize the content
  2140. * type in this case, so the default type will be used. At the same time it is clear that `'<b>example</b> text'` is
  2141. * HTML and its origin is a web page, email or another rich text editor.
  2142. *
  2143. * **Note:** If content type is text, then styles of the paste context are preserved.
  2144. *
  2145. * CKEDITOR.config.clipboard_defaultContentType = 'text';
  2146. *
  2147. * See also the {@link CKEDITOR.editor#paste} event and read more about the integration with clipboard
  2148. * in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
  2149. *
  2150. * @since 4.0
  2151. * @cfg {'html'/'text'} [clipboard_defaultContentType='html']
  2152. * @member CKEDITOR.config
  2153. */
  2154. /**
  2155. * Fired after the user initiated a paste action, but before the data is inserted into the editor.
  2156. * The listeners to this event are able to process the content before its insertion into the document.
  2157. *
  2158. * Read more about the integration with clipboard in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
  2159. *
  2160. * See also:
  2161. *
  2162. * * the {@link CKEDITOR.config#pasteFilter} option,
  2163. * * the {@link CKEDITOR.editor#drop} event,
  2164. * * the {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
  2165. *
  2166. * @since 3.1
  2167. * @event paste
  2168. * @member CKEDITOR.editor
  2169. * @param {CKEDITOR.editor} editor This editor instance.
  2170. * @param data
  2171. * @param {String} data.type The type of data in `data.dataValue`. Usually `'html'` or `'text'`, but for listeners
  2172. * with a priority smaller than `6` it may also be `'auto'` which means that the content type has not been recognised yet
  2173. * (this will be done by the content type sniffer that listens with priority `6`).
  2174. * @param {String} data.dataValue HTML to be pasted.
  2175. * @param {String} data.method Indicates the data transfer method. It could be drag and drop or copy and paste.
  2176. * Possible values: `'drop'`, `'paste'`. Introduced in CKEditor 4.5.
  2177. * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer Facade for the native dataTransfer object
  2178. * which provides access to various data types and files, and passes some data between linked events
  2179. * (like drag and drop). Introduced in CKEditor 4.5.
  2180. * @param {Boolean} [data.dontFilter=false] Whether the {@link CKEDITOR.editor#pasteFilter paste filter} should not
  2181. * be applied to data. This option has no effect when `data.type` equals `'text'` which means that for instance
  2182. * {@link CKEDITOR.config#forcePasteAsPlainText} has a higher priority. Introduced in CKEditor 4.5.
  2183. */
  2184. /**
  2185. * Fired before the {@link #paste} event. Allows to preset data type.
  2186. *
  2187. * **Note:** This event is deprecated. Add a `0` priority listener for the
  2188. * {@link #paste} event instead.
  2189. *
  2190. * @deprecated
  2191. * @event beforePaste
  2192. * @member CKEDITOR.editor
  2193. */
  2194. /**
  2195. * Fired after the {@link #paste} event if content was modified. Note that if the paste
  2196. * event does not insert any data, the `afterPaste` event will not be fired.
  2197. *
  2198. * @event afterPaste
  2199. * @member CKEDITOR.editor
  2200. */
  2201. /**
  2202. * Internal event to open the Paste dialog window.
  2203. *
  2204. * @private
  2205. * @event pasteDialog
  2206. * @member CKEDITOR.editor
  2207. * @param {CKEDITOR.editor} editor This editor instance.
  2208. * @param {Function} [data] Callback that will be passed to {@link CKEDITOR.editor#openDialog}.
  2209. */
  2210. /**
  2211. * Facade for the native `drop` event. Fired when the native `drop` event occurs.
  2212. *
  2213. * **Note:** To manipulate dropped data, use the {@link CKEDITOR.editor#paste} event.
  2214. * Use the `drop` event only to control drag and drop operations (e.g. to prevent the ability to drop some content).
  2215. *
  2216. * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
  2217. *
  2218. * See also:
  2219. *
  2220. * * The {@link CKEDITOR.editor#paste} event,
  2221. * * The {@link CKEDITOR.editor#dragstart} and {@link CKEDITOR.editor#dragend} events,
  2222. * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
  2223. *
  2224. * @since 4.5
  2225. * @event drop
  2226. * @member CKEDITOR.editor
  2227. * @param {CKEDITOR.editor} editor This editor instance.
  2228. * @param data
  2229. * @param {Object} data.$ Native drop event.
  2230. * @param {CKEDITOR.dom.node} data.target Drop target.
  2231. * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
  2232. * @param {CKEDITOR.dom.range} data.dragRange Drag range, lets you manipulate the drag range.
  2233. * Note that dragged HTML is saved as `text/html` data on `dragstart` so if you change the drag range
  2234. * on drop, dropped HTML will not change. You need to change it manually using
  2235. * {@link CKEDITOR.plugins.clipboard.dataTransfer#setData dataTransfer.setData}.
  2236. * @param {CKEDITOR.dom.range} data.dropRange Drop range, lets you manipulate the drop range.
  2237. */
  2238. /**
  2239. * Facade for the native `dragstart` event. Fired when the native `dragstart` event occurs.
  2240. *
  2241. * This event can be canceled in order to block the drag start operation. It can also be fired to mimic the start of the drag and drop
  2242. * operation. For instance, the `widget` plugin uses this option to integrate its custom block widget drag and drop with
  2243. * the entire system.
  2244. *
  2245. * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
  2246. *
  2247. * See also:
  2248. *
  2249. * * The {@link CKEDITOR.editor#paste} event,
  2250. * * The {@link CKEDITOR.editor#drop} and {@link CKEDITOR.editor#dragend} events,
  2251. * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
  2252. *
  2253. * @since 4.5
  2254. * @event dragstart
  2255. * @member CKEDITOR.editor
  2256. * @param {CKEDITOR.editor} editor This editor instance.
  2257. * @param data
  2258. * @param {Object} data.$ Native dragstart event.
  2259. * @param {CKEDITOR.dom.node} data.target Drag target.
  2260. * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
  2261. */
  2262. /**
  2263. * Facade for the native `dragend` event. Fired when the native `dragend` event occurs.
  2264. *
  2265. * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
  2266. *
  2267. * See also:
  2268. *
  2269. * * The {@link CKEDITOR.editor#paste} event,
  2270. * * The {@link CKEDITOR.editor#drop} and {@link CKEDITOR.editor#dragend} events,
  2271. * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
  2272. *
  2273. * @since 4.5
  2274. * @event dragend
  2275. * @member CKEDITOR.editor
  2276. * @param {CKEDITOR.editor} editor This editor instance.
  2277. * @param data
  2278. * @param {Object} data.$ Native dragend event.
  2279. * @param {CKEDITOR.dom.node} data.target Drag target.
  2280. * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
  2281. */
  2282. /**
  2283. * Defines a filter which is applied to external data pasted or dropped into the editor. Possible values are:
  2284. *
  2285. * * `'plain-text'` &ndash; Content will be pasted as a plain text.
  2286. * * `'semantic-content'` &ndash; Known tags (except `div`, `span`) with all attributes (except
  2287. * `style` and `class`) will be kept.
  2288. * * `'h1 h2 p div'` &ndash; Custom rules compatible with {@link CKEDITOR.filter}.
  2289. * * `null` &ndash; Content will not be filtered by the paste filter (but it still may be filtered
  2290. * by [Advanvced Content Filter](#!/guide/dev_advanced_content_filter)). This value can be used to
  2291. * disable the paste filter in Chrome and Safari, where this option defaults to `'semantic-content'`.
  2292. *
  2293. * Example:
  2294. *
  2295. * config.pasteFilter = 'plain-text';
  2296. *
  2297. * Custom setting:
  2298. *
  2299. * config.pasteFilter = 'h1 h2 p ul ol li; img[!src, alt]; a[!href]';
  2300. *
  2301. * Based on this configuration option, a proper {@link CKEDITOR.filter} instance will be defined and assigned to the editor
  2302. * as a {@link CKEDITOR.editor#pasteFilter}. You can tweak the paste filter settings on the fly on this object
  2303. * as well as delete or replace it.
  2304. *
  2305. * var editor = CKEDITOR.replace( 'editor', {
  2306. * pasteFilter: 'semantic-content'
  2307. * } );
  2308. *
  2309. * editor.on( 'instanceReady', function() {
  2310. * // The result of this will be that all semantic content will be preserved
  2311. * // except tables.
  2312. * editor.pasteFilter.disallow( 'table' );
  2313. * } );
  2314. *
  2315. * Note that the paste filter is applied only to **external** data. There are three data sources:
  2316. *
  2317. * * copied and pasted in the same editor (internal),
  2318. * * copied from one editor and pasted into another (cross-editor),
  2319. * * coming from all other sources like websites, MS Word, etc. (external).
  2320. *
  2321. * If {@link CKEDITOR.config#allowedContent Advanced Content Filter} is not disabled, then
  2322. * it will also be applied to pasted and dropped data. The paste filter job is to "normalize"
  2323. * external data which often needs to be handled differently than content produced by the editor.
  2324. *
  2325. * This setting defaults to `'semantic-content'` in Chrome, Opera and Safari (all Blink and Webkit based browsers)
  2326. * due to messy HTML which these browsers keep in the clipboard. In other browsers it defaults to `null`.
  2327. *
  2328. * @since 4.5
  2329. * @cfg {String} [pasteFilter='semantic-content' in Chrome and Safari and `null` in other browsers]
  2330. * @member CKEDITOR.config
  2331. */
  2332. /**
  2333. * {@link CKEDITOR.filter Content filter} which is used when external data is pasted or dropped into the editor
  2334. * or a forced paste as plain text occurs.
  2335. *
  2336. * This object might be used on the fly to define rules for pasted external content.
  2337. * This object is available and used if the {@link CKEDITOR.plugins.clipboard clipboard} plugin is enabled and
  2338. * {@link CKEDITOR.config#pasteFilter} or {@link CKEDITOR.config#forcePasteAsPlainText} was defined.
  2339. *
  2340. * To enable the filter:
  2341. *
  2342. * var editor = CKEDITOR.replace( 'editor', {
  2343. * pasteFilter: 'plain-text'
  2344. * } );
  2345. *
  2346. * You can also modify the filter on the fly later on:
  2347. *
  2348. * editor.pasteFilter = new CKEDITOR.filter( 'p h1 h2; a[!href]' );
  2349. *
  2350. * Note that the paste filter is only applied to **external** data. There are three data sources:
  2351. *
  2352. * * copied and pasted in the same editor (internal),
  2353. * * copied from one editor and pasted into another (cross-editor),
  2354. * * coming from all other sources like websites, MS Word, etc. (external).
  2355. *
  2356. * If {@link CKEDITOR.config#allowedContent Advanced Content Filter} is not disabled, then
  2357. * it will also be applied to pasted and dropped data. The paste filter job is to "normalize"
  2358. * external data which often needs to be handled differently than content produced by the editor.
  2359. *
  2360. * @since 4.5
  2361. * @readonly
  2362. * @property {CKEDITOR.filter} [pasteFilter]
  2363. * @member CKEDITOR.editor
  2364. */