Geen omschrijving

plugin.js 33KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208
  1. /* global getUserSetting, setUserSetting */
  2. ( function( tinymce ) {
  3. // Set the minimum value for the modals z-index higher than #wpadminbar (100000).
  4. if ( ! tinymce.ui.FloatPanel.zIndex || tinymce.ui.FloatPanel.zIndex < 100100 ) {
  5. tinymce.ui.FloatPanel.zIndex = 100100;
  6. }
  7. tinymce.PluginManager.add( 'wordpress', function( editor ) {
  8. var wpAdvButton, style,
  9. DOM = tinymce.DOM,
  10. each = tinymce.each,
  11. __ = editor.editorManager.i18n.translate,
  12. $ = window.jQuery,
  13. wp = window.wp,
  14. hasWpautop = ( wp && wp.editor && wp.editor.autop && editor.getParam( 'wpautop', true ) ),
  15. wpTooltips = false;
  16. if ( $ ) {
  17. // Runs as soon as TinyMCE has started initializing, while plugins are loading.
  18. // Handlers attached after the `tinymce.init()` call may not get triggered for this instance.
  19. $( document ).triggerHandler( 'tinymce-editor-setup', [ editor ] );
  20. }
  21. function toggleToolbars( state ) {
  22. var initial, toolbars, iframeHeight,
  23. pixels = 0,
  24. classicBlockToolbar = tinymce.$( '.block-library-classic__toolbar' );
  25. if ( state === 'hide' ) {
  26. initial = true;
  27. } else if ( classicBlockToolbar.length && ! classicBlockToolbar.hasClass( 'has-advanced-toolbar' ) ) {
  28. // Show the second, third, etc. toolbar rows in the Classic block instance.
  29. classicBlockToolbar.addClass( 'has-advanced-toolbar' );
  30. state = 'show';
  31. }
  32. if ( editor.theme.panel ) {
  33. toolbars = editor.theme.panel.find('.toolbar:not(.menubar)');
  34. }
  35. if ( toolbars && toolbars.length > 1 ) {
  36. if ( ! state && toolbars[1].visible() ) {
  37. state = 'hide';
  38. }
  39. each( toolbars, function( toolbar, i ) {
  40. if ( i > 0 ) {
  41. if ( state === 'hide' ) {
  42. toolbar.hide();
  43. pixels += 34;
  44. } else {
  45. toolbar.show();
  46. pixels -= 34;
  47. }
  48. }
  49. });
  50. }
  51. // Resize editor iframe, not needed for iOS and inline instances.
  52. // Don't resize if the editor is in a hidden container.
  53. if ( pixels && ! tinymce.Env.iOS && editor.iframeElement && editor.iframeElement.clientHeight ) {
  54. iframeHeight = editor.iframeElement.clientHeight + pixels;
  55. // Keep min-height.
  56. if ( iframeHeight > 50 ) {
  57. DOM.setStyle( editor.iframeElement, 'height', iframeHeight );
  58. }
  59. }
  60. if ( ! initial ) {
  61. if ( state === 'hide' ) {
  62. setUserSetting( 'hidetb', '0' );
  63. wpAdvButton && wpAdvButton.active( false );
  64. } else {
  65. setUserSetting( 'hidetb', '1' );
  66. wpAdvButton && wpAdvButton.active( true );
  67. }
  68. }
  69. editor.fire( 'wp-toolbar-toggle' );
  70. }
  71. // Add the kitchen sink button :)
  72. editor.addButton( 'wp_adv', {
  73. tooltip: 'Toolbar Toggle',
  74. cmd: 'WP_Adv',
  75. onPostRender: function() {
  76. wpAdvButton = this;
  77. wpAdvButton.active( getUserSetting( 'hidetb' ) === '1' );
  78. }
  79. });
  80. // Hide the toolbars after loading.
  81. editor.on( 'PostRender', function() {
  82. if ( editor.getParam( 'wordpress_adv_hidden', true ) && getUserSetting( 'hidetb', '0' ) === '0' ) {
  83. toggleToolbars( 'hide' );
  84. } else {
  85. tinymce.$( '.block-library-classic__toolbar' ).addClass( 'has-advanced-toolbar' );
  86. }
  87. });
  88. editor.addCommand( 'WP_Adv', function() {
  89. toggleToolbars();
  90. });
  91. editor.on( 'focus', function() {
  92. window.wpActiveEditor = editor.id;
  93. });
  94. editor.on( 'BeforeSetContent', function( event ) {
  95. var title;
  96. if ( event.content ) {
  97. if ( event.content.indexOf( '<!--more' ) !== -1 ) {
  98. title = __( 'Read more...' );
  99. event.content = event.content.replace( /<!--more(.*?)-->/g, function( match, moretext ) {
  100. return '<img src="' + tinymce.Env.transparentSrc + '" data-wp-more="more" data-wp-more-text="' + moretext + '" ' +
  101. 'class="wp-more-tag mce-wp-more" alt="" title="' + title + '" data-mce-resize="false" data-mce-placeholder="1" />';
  102. });
  103. }
  104. if ( event.content.indexOf( '<!--nextpage-->' ) !== -1 ) {
  105. title = __( 'Page break' );
  106. event.content = event.content.replace( /<!--nextpage-->/g,
  107. '<img src="' + tinymce.Env.transparentSrc + '" data-wp-more="nextpage" class="wp-more-tag mce-wp-nextpage" ' +
  108. 'alt="" title="' + title + '" data-mce-resize="false" data-mce-placeholder="1" />' );
  109. }
  110. if ( event.load && event.format !== 'raw' ) {
  111. if ( hasWpautop ) {
  112. event.content = wp.editor.autop( event.content );
  113. } else {
  114. // Prevent creation of paragraphs out of multiple HTML comments.
  115. event.content = event.content.replace( /-->\s+<!--/g, '--><!--' );
  116. }
  117. }
  118. if ( event.content.indexOf( '<script' ) !== -1 || event.content.indexOf( '<style' ) !== -1 ) {
  119. event.content = event.content.replace( /<(script|style)[^>]*>[\s\S]*?<\/\1>/g, function( match, tag ) {
  120. return '<img ' +
  121. 'src="' + tinymce.Env.transparentSrc + '" ' +
  122. 'data-wp-preserve="' + encodeURIComponent( match ) + '" ' +
  123. 'data-mce-resize="false" ' +
  124. 'data-mce-placeholder="1" '+
  125. 'class="mce-object" ' +
  126. 'width="20" height="20" '+
  127. 'alt="&lt;' + tag + '&gt;" ' +
  128. 'title="&lt;' + tag + '&gt;" ' +
  129. '/>';
  130. } );
  131. }
  132. }
  133. });
  134. editor.on( 'setcontent', function() {
  135. // Remove spaces from empty paragraphs.
  136. editor.$( 'p' ).each( function( i, node ) {
  137. if ( node.innerHTML && node.innerHTML.length < 10 ) {
  138. var html = tinymce.trim( node.innerHTML );
  139. if ( ! html || html === '&nbsp;' ) {
  140. node.innerHTML = ( tinymce.Env.ie && tinymce.Env.ie < 11 ) ? '' : '<br data-mce-bogus="1">';
  141. }
  142. }
  143. } );
  144. });
  145. editor.on( 'PostProcess', function( event ) {
  146. if ( event.get ) {
  147. event.content = event.content.replace(/<img[^>]+>/g, function( image ) {
  148. var match,
  149. string,
  150. moretext = '';
  151. if ( image.indexOf( 'data-wp-more="more"' ) !== -1 ) {
  152. if ( match = image.match( /data-wp-more-text="([^"]+)"/ ) ) {
  153. moretext = match[1];
  154. }
  155. string = '<!--more' + moretext + '-->';
  156. } else if ( image.indexOf( 'data-wp-more="nextpage"' ) !== -1 ) {
  157. string = '<!--nextpage-->';
  158. } else if ( image.indexOf( 'data-wp-preserve' ) !== -1 ) {
  159. if ( match = image.match( / data-wp-preserve="([^"]+)"/ ) ) {
  160. string = decodeURIComponent( match[1] );
  161. }
  162. }
  163. return string || image;
  164. });
  165. }
  166. });
  167. // Display the tag name instead of img in element path.
  168. editor.on( 'ResolveName', function( event ) {
  169. var attr;
  170. if ( event.target.nodeName === 'IMG' && ( attr = editor.dom.getAttrib( event.target, 'data-wp-more' ) ) ) {
  171. event.name = attr;
  172. }
  173. });
  174. // Register commands.
  175. editor.addCommand( 'WP_More', function( tag ) {
  176. var parent, html, title,
  177. classname = 'wp-more-tag',
  178. dom = editor.dom,
  179. node = editor.selection.getNode(),
  180. rootNode = editor.getBody();
  181. tag = tag || 'more';
  182. classname += ' mce-wp-' + tag;
  183. title = tag === 'more' ? 'Read more...' : 'Next page';
  184. title = __( title );
  185. html = '<img src="' + tinymce.Env.transparentSrc + '" alt="" title="' + title + '" class="' + classname + '" ' +
  186. 'data-wp-more="' + tag + '" data-mce-resize="false" data-mce-placeholder="1" />';
  187. // Most common case.
  188. if ( node === rootNode || ( node.nodeName === 'P' && node.parentNode === rootNode ) ) {
  189. editor.insertContent( html );
  190. return;
  191. }
  192. // Get the top level parent node.
  193. parent = dom.getParent( node, function( found ) {
  194. if ( found.parentNode && found.parentNode === rootNode ) {
  195. return true;
  196. }
  197. return false;
  198. }, editor.getBody() );
  199. if ( parent ) {
  200. if ( parent.nodeName === 'P' ) {
  201. parent.appendChild( dom.create( 'p', null, html ).firstChild );
  202. } else {
  203. dom.insertAfter( dom.create( 'p', null, html ), parent );
  204. }
  205. editor.nodeChanged();
  206. }
  207. });
  208. editor.addCommand( 'WP_Code', function() {
  209. editor.formatter.toggle('code');
  210. });
  211. editor.addCommand( 'WP_Page', function() {
  212. editor.execCommand( 'WP_More', 'nextpage' );
  213. });
  214. editor.addCommand( 'WP_Help', function() {
  215. var access = tinymce.Env.mac ? __( 'Ctrl + Alt + letter:' ) : __( 'Shift + Alt + letter:' ),
  216. meta = tinymce.Env.mac ? __( 'Cmd + letter:' ) : __( 'Ctrl + letter:' ),
  217. table1 = [],
  218. table2 = [],
  219. row1 = {},
  220. row2 = {},
  221. i1 = 0,
  222. i2 = 0,
  223. labels = editor.settings.wp_shortcut_labels,
  224. header, html, dialog, $wrap;
  225. if ( ! labels ) {
  226. return;
  227. }
  228. function tr( row, columns ) {
  229. var out = '<tr>';
  230. var i = 0;
  231. columns = columns || 1;
  232. each( row, function( text, key ) {
  233. out += '<td><kbd>' + key + '</kbd></td><td>' + __( text ) + '</td>';
  234. i++;
  235. });
  236. while ( i < columns ) {
  237. out += '<td></td><td></td>';
  238. i++;
  239. }
  240. return out + '</tr>';
  241. }
  242. each ( labels, function( label, name ) {
  243. var letter;
  244. if ( label.indexOf( 'meta' ) !== -1 ) {
  245. i1++;
  246. letter = label.replace( 'meta', '' ).toLowerCase();
  247. if ( letter ) {
  248. row1[ letter ] = name;
  249. if ( i1 % 2 === 0 ) {
  250. table1.push( tr( row1, 2 ) );
  251. row1 = {};
  252. }
  253. }
  254. } else if ( label.indexOf( 'access' ) !== -1 ) {
  255. i2++;
  256. letter = label.replace( 'access', '' ).toLowerCase();
  257. if ( letter ) {
  258. row2[ letter ] = name;
  259. if ( i2 % 2 === 0 ) {
  260. table2.push( tr( row2, 2 ) );
  261. row2 = {};
  262. }
  263. }
  264. }
  265. } );
  266. // Add remaining single entries.
  267. if ( i1 % 2 > 0 ) {
  268. table1.push( tr( row1, 2 ) );
  269. }
  270. if ( i2 % 2 > 0 ) {
  271. table2.push( tr( row2, 2 ) );
  272. }
  273. header = [ __( 'Letter' ), __( 'Action' ), __( 'Letter' ), __( 'Action' ) ];
  274. header = '<tr><th>' + header.join( '</th><th>' ) + '</th></tr>';
  275. html = '<div class="wp-editor-help">';
  276. // Main section, default and additional shortcuts.
  277. html = html +
  278. '<h2>' + __( 'Default shortcuts,' ) + ' ' + meta + '</h2>' +
  279. '<table class="wp-help-th-center fixed">' +
  280. header +
  281. table1.join('') +
  282. '</table>' +
  283. '<h2>' + __( 'Additional shortcuts,' ) + ' ' + access + '</h2>' +
  284. '<table class="wp-help-th-center fixed">' +
  285. header +
  286. table2.join('') +
  287. '</table>';
  288. if ( editor.plugins.wptextpattern && ( ! tinymce.Env.ie || tinymce.Env.ie > 8 ) ) {
  289. // Text pattern section.
  290. html = html +
  291. '<h2>' + __( 'When starting a new paragraph with one of these formatting shortcuts followed by a space, the formatting will be applied automatically. Press Backspace or Escape to undo.' ) + '</h2>' +
  292. '<table class="wp-help-th-center fixed">' +
  293. tr({ '*': 'Bullet list', '1.': 'Numbered list' }) +
  294. tr({ '-': 'Bullet list', '1)': 'Numbered list' }) +
  295. '</table>';
  296. html = html +
  297. '<h2>' + __( 'The following formatting shortcuts are replaced when pressing Enter. Press Escape or the Undo button to undo.' ) + '</h2>' +
  298. '<table class="wp-help-single">' +
  299. tr({ '>': 'Blockquote' }) +
  300. tr({ '##': 'Heading 2' }) +
  301. tr({ '###': 'Heading 3' }) +
  302. tr({ '####': 'Heading 4' }) +
  303. tr({ '#####': 'Heading 5' }) +
  304. tr({ '######': 'Heading 6' }) +
  305. tr({ '---': 'Horizontal line' }) +
  306. '</table>';
  307. }
  308. // Focus management section.
  309. html = html +
  310. '<h2>' + __( 'Focus shortcuts:' ) + '</h2>' +
  311. '<table class="wp-help-single">' +
  312. tr({ 'Alt + F8': 'Inline toolbar (when an image, link or preview is selected)' }) +
  313. tr({ 'Alt + F9': 'Editor menu (when enabled)' }) +
  314. tr({ 'Alt + F10': 'Editor toolbar' }) +
  315. tr({ 'Alt + F11': 'Elements path' }) +
  316. '</table>' +
  317. '<p>' + __( 'To move focus to other buttons use Tab or the arrow keys. To return focus to the editor press Escape or use one of the buttons.' ) + '</p>';
  318. html += '</div>';
  319. dialog = editor.windowManager.open( {
  320. title: editor.settings.classic_block_editor ? 'Classic Block Keyboard Shortcuts' : 'Keyboard Shortcuts',
  321. items: {
  322. type: 'container',
  323. classes: 'wp-help',
  324. html: html
  325. },
  326. buttons: {
  327. text: 'Close',
  328. onclick: 'close'
  329. }
  330. } );
  331. if ( dialog.$el ) {
  332. dialog.$el.find( 'div[role="application"]' ).attr( 'role', 'document' );
  333. $wrap = dialog.$el.find( '.mce-wp-help' );
  334. if ( $wrap[0] ) {
  335. $wrap.attr( 'tabindex', '0' );
  336. $wrap[0].focus();
  337. $wrap.on( 'keydown', function( event ) {
  338. // Prevent use of: page up, page down, end, home, left arrow, up arrow, right arrow, down arrow
  339. // in the dialog keydown handler.
  340. if ( event.keyCode >= 33 && event.keyCode <= 40 ) {
  341. event.stopPropagation();
  342. }
  343. });
  344. }
  345. }
  346. } );
  347. editor.addCommand( 'WP_Medialib', function() {
  348. if ( wp && wp.media && wp.media.editor ) {
  349. wp.media.editor.open( editor.id );
  350. }
  351. });
  352. // Register buttons.
  353. editor.addButton( 'wp_more', {
  354. tooltip: 'Insert Read More tag',
  355. onclick: function() {
  356. editor.execCommand( 'WP_More', 'more' );
  357. }
  358. });
  359. editor.addButton( 'wp_page', {
  360. tooltip: 'Page break',
  361. onclick: function() {
  362. editor.execCommand( 'WP_More', 'nextpage' );
  363. }
  364. });
  365. editor.addButton( 'wp_help', {
  366. tooltip: 'Keyboard Shortcuts',
  367. cmd: 'WP_Help'
  368. });
  369. editor.addButton( 'wp_code', {
  370. tooltip: 'Code',
  371. cmd: 'WP_Code',
  372. stateSelector: 'code'
  373. });
  374. // Insert->Add Media.
  375. if ( wp && wp.media && wp.media.editor ) {
  376. editor.addButton( 'wp_add_media', {
  377. tooltip: 'Add Media',
  378. icon: 'dashicon dashicons-admin-media',
  379. cmd: 'WP_Medialib'
  380. } );
  381. editor.addMenuItem( 'add_media', {
  382. text: 'Add Media',
  383. icon: 'wp-media-library',
  384. context: 'insert',
  385. cmd: 'WP_Medialib'
  386. });
  387. }
  388. // Insert "Read More...".
  389. editor.addMenuItem( 'wp_more', {
  390. text: 'Insert Read More tag',
  391. icon: 'wp_more',
  392. context: 'insert',
  393. onclick: function() {
  394. editor.execCommand( 'WP_More', 'more' );
  395. }
  396. });
  397. // Insert "Next Page".
  398. editor.addMenuItem( 'wp_page', {
  399. text: 'Page break',
  400. icon: 'wp_page',
  401. context: 'insert',
  402. onclick: function() {
  403. editor.execCommand( 'WP_More', 'nextpage' );
  404. }
  405. });
  406. editor.on( 'BeforeExecCommand', function(e) {
  407. if ( tinymce.Env.webkit && ( e.command === 'InsertUnorderedList' || e.command === 'InsertOrderedList' ) ) {
  408. if ( ! style ) {
  409. style = editor.dom.create( 'style', {'type': 'text/css'},
  410. '#tinymce,#tinymce span,#tinymce li,#tinymce li>span,#tinymce p,#tinymce p>span{font:medium sans-serif;color:#000;line-height:normal;}');
  411. }
  412. editor.getDoc().head.appendChild( style );
  413. }
  414. });
  415. editor.on( 'ExecCommand', function( e ) {
  416. if ( tinymce.Env.webkit && style &&
  417. ( 'InsertUnorderedList' === e.command || 'InsertOrderedList' === e.command ) ) {
  418. editor.dom.remove( style );
  419. }
  420. });
  421. editor.on( 'init', function() {
  422. var env = tinymce.Env,
  423. bodyClass = ['mceContentBody'], // Back-compat for themes that use this in editor-style.css...
  424. doc = editor.getDoc(),
  425. dom = editor.dom;
  426. if ( env.iOS ) {
  427. dom.addClass( doc.documentElement, 'ios' );
  428. }
  429. if ( editor.getParam( 'directionality' ) === 'rtl' ) {
  430. bodyClass.push('rtl');
  431. dom.setAttrib( doc.documentElement, 'dir', 'rtl' );
  432. }
  433. dom.setAttrib( doc.documentElement, 'lang', editor.getParam( 'wp_lang_attr' ) );
  434. if ( env.ie ) {
  435. if ( parseInt( env.ie, 10 ) === 9 ) {
  436. bodyClass.push('ie9');
  437. } else if ( parseInt( env.ie, 10 ) === 8 ) {
  438. bodyClass.push('ie8');
  439. } else if ( env.ie < 8 ) {
  440. bodyClass.push('ie7');
  441. }
  442. } else if ( env.webkit ) {
  443. bodyClass.push('webkit');
  444. }
  445. bodyClass.push('wp-editor');
  446. each( bodyClass, function( cls ) {
  447. if ( cls ) {
  448. dom.addClass( doc.body, cls );
  449. }
  450. });
  451. // Remove invalid parent paragraphs when inserting HTML.
  452. editor.on( 'BeforeSetContent', function( event ) {
  453. if ( event.content ) {
  454. event.content = event.content.replace( /<p>\s*<(p|div|ul|ol|dl|table|blockquote|h[1-6]|fieldset|pre)( [^>]*)?>/gi, '<$1$2>' )
  455. .replace( /<\/(p|div|ul|ol|dl|table|blockquote|h[1-6]|fieldset|pre)>\s*<\/p>/gi, '</$1>' );
  456. }
  457. });
  458. if ( $ ) {
  459. // Run on DOM ready. Otherwise TinyMCE may initialize earlier and handlers attached
  460. // on DOM ready of after the `tinymce.init()` call may not get triggered.
  461. $( function() {
  462. $( document ).triggerHandler( 'tinymce-editor-init', [editor] );
  463. });
  464. }
  465. if ( window.tinyMCEPreInit && window.tinyMCEPreInit.dragDropUpload ) {
  466. dom.bind( doc, 'dragstart dragend dragover drop', function( event ) {
  467. if ( $ ) {
  468. // Trigger the jQuery handlers.
  469. $( document ).trigger( new $.Event( event ) );
  470. }
  471. });
  472. }
  473. if ( editor.getParam( 'wp_paste_filters', true ) ) {
  474. editor.on( 'PastePreProcess', function( event ) {
  475. // Remove trailing <br> added by WebKit browsers to the clipboard.
  476. event.content = event.content.replace( /<br class="?Apple-interchange-newline"?>/gi, '' );
  477. // In WebKit this is handled by removeWebKitStyles().
  478. if ( ! tinymce.Env.webkit ) {
  479. // Remove all inline styles.
  480. event.content = event.content.replace( /(<[^>]+) style="[^"]*"([^>]*>)/gi, '$1$2' );
  481. // Put back the internal styles.
  482. event.content = event.content.replace(/(<[^>]+) data-mce-style=([^>]+>)/gi, '$1 style=$2' );
  483. }
  484. });
  485. editor.on( 'PastePostProcess', function( event ) {
  486. // Remove empty paragraphs.
  487. editor.$( 'p', event.node ).each( function( i, node ) {
  488. if ( dom.isEmpty( node ) ) {
  489. dom.remove( node );
  490. }
  491. });
  492. if ( tinymce.isIE ) {
  493. editor.$( 'a', event.node ).find( 'font, u' ).each( function( i, node ) {
  494. dom.remove( node, true );
  495. });
  496. }
  497. });
  498. }
  499. });
  500. editor.on( 'SaveContent', function( event ) {
  501. // If editor is hidden, we just want the textarea's value to be saved.
  502. if ( ! editor.inline && editor.isHidden() ) {
  503. event.content = event.element.value;
  504. return;
  505. }
  506. // Keep empty paragraphs :(
  507. event.content = event.content.replace( /<p>(?:<br ?\/?>|\u00a0|\uFEFF| )*<\/p>/g, '<p>&nbsp;</p>' );
  508. if ( hasWpautop ) {
  509. event.content = wp.editor.removep( event.content );
  510. } else {
  511. // Restore formatting of block boundaries.
  512. event.content = event.content.replace( /-->\s*<!-- wp:/g, '-->\n\n<!-- wp:' );
  513. }
  514. });
  515. editor.on( 'preInit', function() {
  516. var validElementsSetting = '@[id|accesskey|class|dir|lang|style|tabindex|' +
  517. 'title|contenteditable|draggable|dropzone|hidden|spellcheck|translate],' + // Global attributes.
  518. 'i,' + // Don't replace <i> with <em> and <b> with <strong> and don't remove them when empty.
  519. 'b,' +
  520. 'script[src|async|defer|type|charset|crossorigin|integrity]'; // Add support for <script>.
  521. editor.schema.addValidElements( validElementsSetting );
  522. if ( tinymce.Env.iOS ) {
  523. editor.settings.height = 300;
  524. }
  525. each( {
  526. c: 'JustifyCenter',
  527. r: 'JustifyRight',
  528. l: 'JustifyLeft',
  529. j: 'JustifyFull',
  530. q: 'mceBlockQuote',
  531. u: 'InsertUnorderedList',
  532. o: 'InsertOrderedList',
  533. m: 'WP_Medialib',
  534. t: 'WP_More',
  535. d: 'Strikethrough',
  536. p: 'WP_Page',
  537. x: 'WP_Code'
  538. }, function( command, key ) {
  539. editor.shortcuts.add( 'access+' + key, '', command );
  540. } );
  541. editor.addShortcut( 'meta+s', '', function() {
  542. if ( wp && wp.autosave ) {
  543. wp.autosave.server.triggerSave();
  544. }
  545. } );
  546. // Alt+Shift+Z removes a block in the block editor, don't add it to the Classic block.
  547. if ( ! editor.settings.classic_block_editor ) {
  548. editor.addShortcut( 'access+z', '', 'WP_Adv' );
  549. }
  550. // Workaround for not triggering the global help modal in the block editor by the Classic block shortcut.
  551. editor.on( 'keydown', function( event ) {
  552. var match;
  553. if ( tinymce.Env.mac ) {
  554. match = event.ctrlKey && event.altKey && event.code === 'KeyH';
  555. } else {
  556. match = event.shiftKey && event.altKey && event.code === 'KeyH';
  557. }
  558. if ( match ) {
  559. editor.execCommand( 'WP_Help' );
  560. event.stopPropagation();
  561. event.stopImmediatePropagation();
  562. return false;
  563. }
  564. return true;
  565. });
  566. if ( window.getUserSetting( 'editor_plain_text_paste_warning' ) > 1 ) {
  567. editor.settings.paste_plaintext_inform = false;
  568. }
  569. // Change the editor iframe title on MacOS, add the correct help shortcut.
  570. if ( tinymce.Env.mac ) {
  571. tinymce.$( editor.iframeElement ).attr( 'title', __( 'Rich Text Area. Press Control-Option-H for help.' ) );
  572. }
  573. } );
  574. editor.on( 'PastePlainTextToggle', function( event ) {
  575. // Warn twice, then stop.
  576. if ( event.state === true ) {
  577. var times = parseInt( window.getUserSetting( 'editor_plain_text_paste_warning' ), 10 ) || 0;
  578. if ( times < 2 ) {
  579. window.setUserSetting( 'editor_plain_text_paste_warning', ++times );
  580. }
  581. }
  582. });
  583. editor.on( 'beforerenderui', function() {
  584. if ( editor.theme.panel ) {
  585. each( [ 'button', 'colorbutton', 'splitbutton' ], function( buttonType ) {
  586. replaceButtonsTooltips( editor.theme.panel.find( buttonType ) );
  587. } );
  588. addShortcutsToListbox();
  589. }
  590. } );
  591. function prepareTooltips() {
  592. var access = 'Shift+Alt+';
  593. var meta = 'Ctrl+';
  594. wpTooltips = {};
  595. // For MacOS: ctrl = \u2303, cmd = \u2318, alt = \u2325.
  596. if ( tinymce.Env.mac ) {
  597. access = '\u2303\u2325';
  598. meta = '\u2318';
  599. }
  600. // Some tooltips are translated, others are not...
  601. if ( editor.settings.wp_shortcut_labels ) {
  602. each( editor.settings.wp_shortcut_labels, function( value, tooltip ) {
  603. var translated = editor.translate( tooltip );
  604. value = value.replace( 'access', access ).replace( 'meta', meta );
  605. wpTooltips[ tooltip ] = value;
  606. // Add the translated so we can match all of them.
  607. if ( tooltip !== translated ) {
  608. wpTooltips[ translated ] = value;
  609. }
  610. } );
  611. }
  612. }
  613. function getTooltip( tooltip ) {
  614. var translated = editor.translate( tooltip );
  615. var label;
  616. if ( ! wpTooltips ) {
  617. prepareTooltips();
  618. }
  619. if ( wpTooltips.hasOwnProperty( translated ) ) {
  620. label = wpTooltips[ translated ];
  621. } else if ( wpTooltips.hasOwnProperty( tooltip ) ) {
  622. label = wpTooltips[ tooltip ];
  623. }
  624. return label ? translated + ' (' + label + ')' : translated;
  625. }
  626. function replaceButtonsTooltips( buttons ) {
  627. if ( ! buttons ) {
  628. return;
  629. }
  630. each( buttons, function( button ) {
  631. var tooltip;
  632. if ( button && button.settings.tooltip ) {
  633. tooltip = getTooltip( button.settings.tooltip );
  634. button.settings.tooltip = tooltip;
  635. // Override the aria label wiht the translated tooltip + shortcut.
  636. if ( button._aria && button._aria.label ) {
  637. button._aria.label = tooltip;
  638. }
  639. }
  640. } );
  641. }
  642. function addShortcutsToListbox() {
  643. // listbox for the "blocks" drop-down.
  644. each( editor.theme.panel.find( 'listbox' ), function( listbox ) {
  645. if ( listbox && listbox.settings.text === 'Paragraph' ) {
  646. each( listbox.settings.values, function( item ) {
  647. if ( item.text && wpTooltips.hasOwnProperty( item.text ) ) {
  648. item.shortcut = '(' + wpTooltips[ item.text ] + ')';
  649. }
  650. } );
  651. }
  652. } );
  653. }
  654. /**
  655. * Experimental: create a floating toolbar.
  656. * This functionality will change in the next releases. Not recommended for use by plugins.
  657. */
  658. editor.on( 'preinit', function() {
  659. var Factory = tinymce.ui.Factory,
  660. settings = editor.settings,
  661. activeToolbar,
  662. currentSelection,
  663. timeout,
  664. container = editor.getContainer(),
  665. wpAdminbar = document.getElementById( 'wpadminbar' ),
  666. mceIframe = document.getElementById( editor.id + '_ifr' ),
  667. mceToolbar,
  668. mceStatusbar,
  669. wpStatusbar,
  670. cachedWinSize;
  671. if ( container ) {
  672. mceToolbar = tinymce.$( '.mce-toolbar-grp', container )[0];
  673. mceStatusbar = tinymce.$( '.mce-statusbar', container )[0];
  674. }
  675. if ( editor.id === 'content' ) {
  676. wpStatusbar = document.getElementById( 'post-status-info' );
  677. }
  678. function create( buttons, bottom ) {
  679. var toolbar,
  680. toolbarItems = [],
  681. buttonGroup;
  682. each( buttons, function( item ) {
  683. var itemName;
  684. var tooltip;
  685. function bindSelectorChanged() {
  686. var selection = editor.selection;
  687. if ( itemName === 'bullist' ) {
  688. selection.selectorChanged( 'ul > li', function( state, args ) {
  689. var i = args.parents.length,
  690. nodeName;
  691. while ( i-- ) {
  692. nodeName = args.parents[ i ].nodeName;
  693. if ( nodeName === 'OL' || nodeName == 'UL' ) {
  694. break;
  695. }
  696. }
  697. item.active( state && nodeName === 'UL' );
  698. } );
  699. }
  700. if ( itemName === 'numlist' ) {
  701. selection.selectorChanged( 'ol > li', function( state, args ) {
  702. var i = args.parents.length,
  703. nodeName;
  704. while ( i-- ) {
  705. nodeName = args.parents[ i ].nodeName;
  706. if ( nodeName === 'OL' || nodeName === 'UL' ) {
  707. break;
  708. }
  709. }
  710. item.active( state && nodeName === 'OL' );
  711. } );
  712. }
  713. if ( item.settings.stateSelector ) {
  714. selection.selectorChanged( item.settings.stateSelector, function( state ) {
  715. item.active( state );
  716. }, true );
  717. }
  718. if ( item.settings.disabledStateSelector ) {
  719. selection.selectorChanged( item.settings.disabledStateSelector, function( state ) {
  720. item.disabled( state );
  721. } );
  722. }
  723. }
  724. if ( item === '|' ) {
  725. buttonGroup = null;
  726. } else {
  727. if ( Factory.has( item ) ) {
  728. item = {
  729. type: item
  730. };
  731. if ( settings.toolbar_items_size ) {
  732. item.size = settings.toolbar_items_size;
  733. }
  734. toolbarItems.push( item );
  735. buttonGroup = null;
  736. } else {
  737. if ( ! buttonGroup ) {
  738. buttonGroup = {
  739. type: 'buttongroup',
  740. items: []
  741. };
  742. toolbarItems.push( buttonGroup );
  743. }
  744. if ( editor.buttons[ item ] ) {
  745. itemName = item;
  746. item = editor.buttons[ itemName ];
  747. if ( typeof item === 'function' ) {
  748. item = item();
  749. }
  750. item.type = item.type || 'button';
  751. if ( settings.toolbar_items_size ) {
  752. item.size = settings.toolbar_items_size;
  753. }
  754. tooltip = item.tooltip || item.title;
  755. if ( tooltip ) {
  756. item.tooltip = getTooltip( tooltip );
  757. }
  758. item = Factory.create( item );
  759. buttonGroup.items.push( item );
  760. if ( editor.initialized ) {
  761. bindSelectorChanged();
  762. } else {
  763. editor.on( 'init', bindSelectorChanged );
  764. }
  765. }
  766. }
  767. }
  768. } );
  769. toolbar = Factory.create( {
  770. type: 'panel',
  771. layout: 'stack',
  772. classes: 'toolbar-grp inline-toolbar-grp',
  773. ariaRoot: true,
  774. ariaRemember: true,
  775. items: [ {
  776. type: 'toolbar',
  777. layout: 'flow',
  778. items: toolbarItems
  779. } ]
  780. } );
  781. toolbar.bottom = bottom;
  782. function reposition() {
  783. if ( ! currentSelection ) {
  784. return this;
  785. }
  786. var scrollX = window.pageXOffset || document.documentElement.scrollLeft,
  787. scrollY = window.pageYOffset || document.documentElement.scrollTop,
  788. windowWidth = window.innerWidth,
  789. windowHeight = window.innerHeight,
  790. iframeRect = mceIframe ? mceIframe.getBoundingClientRect() : {
  791. top: 0,
  792. right: windowWidth,
  793. bottom: windowHeight,
  794. left: 0,
  795. width: windowWidth,
  796. height: windowHeight
  797. },
  798. toolbar = this.getEl(),
  799. toolbarWidth = toolbar.offsetWidth,
  800. toolbarHeight = toolbar.clientHeight,
  801. selection = currentSelection.getBoundingClientRect(),
  802. selectionMiddle = ( selection.left + selection.right ) / 2,
  803. buffer = 5,
  804. spaceNeeded = toolbarHeight + buffer,
  805. wpAdminbarBottom = wpAdminbar ? wpAdminbar.getBoundingClientRect().bottom : 0,
  806. mceToolbarBottom = mceToolbar ? mceToolbar.getBoundingClientRect().bottom : 0,
  807. mceStatusbarTop = mceStatusbar ? windowHeight - mceStatusbar.getBoundingClientRect().top : 0,
  808. wpStatusbarTop = wpStatusbar ? windowHeight - wpStatusbar.getBoundingClientRect().top : 0,
  809. blockedTop = Math.max( 0, wpAdminbarBottom, mceToolbarBottom, iframeRect.top ),
  810. blockedBottom = Math.max( 0, mceStatusbarTop, wpStatusbarTop, windowHeight - iframeRect.bottom ),
  811. spaceTop = selection.top + iframeRect.top - blockedTop,
  812. spaceBottom = windowHeight - iframeRect.top - selection.bottom - blockedBottom,
  813. editorHeight = windowHeight - blockedTop - blockedBottom,
  814. className = '',
  815. iosOffsetTop = 0,
  816. iosOffsetBottom = 0,
  817. top, left;
  818. if ( spaceTop >= editorHeight || spaceBottom >= editorHeight ) {
  819. this.scrolling = true;
  820. this.hide();
  821. this.scrolling = false;
  822. return this;
  823. }
  824. // Add offset in iOS to move the menu over the image, out of the way of the default iOS menu.
  825. if ( tinymce.Env.iOS && currentSelection.nodeName === 'IMG' ) {
  826. iosOffsetTop = 54;
  827. iosOffsetBottom = 46;
  828. }
  829. if ( this.bottom ) {
  830. if ( spaceBottom >= spaceNeeded ) {
  831. className = ' mce-arrow-up';
  832. top = selection.bottom + iframeRect.top + scrollY - iosOffsetBottom;
  833. } else if ( spaceTop >= spaceNeeded ) {
  834. className = ' mce-arrow-down';
  835. top = selection.top + iframeRect.top + scrollY - toolbarHeight + iosOffsetTop;
  836. }
  837. } else {
  838. if ( spaceTop >= spaceNeeded ) {
  839. className = ' mce-arrow-down';
  840. top = selection.top + iframeRect.top + scrollY - toolbarHeight + iosOffsetTop;
  841. } else if ( spaceBottom >= spaceNeeded && editorHeight / 2 > selection.bottom + iframeRect.top - blockedTop ) {
  842. className = ' mce-arrow-up';
  843. top = selection.bottom + iframeRect.top + scrollY - iosOffsetBottom;
  844. }
  845. }
  846. if ( typeof top === 'undefined' ) {
  847. top = scrollY + blockedTop + buffer + iosOffsetBottom;
  848. }
  849. left = selectionMiddle - toolbarWidth / 2 + iframeRect.left + scrollX;
  850. if ( selection.left < 0 || selection.right > iframeRect.width ) {
  851. left = iframeRect.left + scrollX + ( iframeRect.width - toolbarWidth ) / 2;
  852. } else if ( toolbarWidth >= windowWidth ) {
  853. className += ' mce-arrow-full';
  854. left = 0;
  855. } else if ( ( left < 0 && selection.left + toolbarWidth > windowWidth ) || ( left + toolbarWidth > windowWidth && selection.right - toolbarWidth < 0 ) ) {
  856. left = ( windowWidth - toolbarWidth ) / 2;
  857. } else if ( left < iframeRect.left + scrollX ) {
  858. className += ' mce-arrow-left';
  859. left = selection.left + iframeRect.left + scrollX;
  860. } else if ( left + toolbarWidth > iframeRect.width + iframeRect.left + scrollX ) {
  861. className += ' mce-arrow-right';
  862. left = selection.right - toolbarWidth + iframeRect.left + scrollX;
  863. }
  864. // No up/down arrows on the menu over images in iOS.
  865. if ( tinymce.Env.iOS && currentSelection.nodeName === 'IMG' ) {
  866. className = className.replace( / ?mce-arrow-(up|down)/g, '' );
  867. }
  868. toolbar.className = toolbar.className.replace( / ?mce-arrow-[\w]+/g, '' ) + className;
  869. DOM.setStyles( toolbar, {
  870. 'left': left,
  871. 'top': top
  872. } );
  873. return this;
  874. }
  875. toolbar.on( 'show', function() {
  876. this.reposition();
  877. } );
  878. toolbar.on( 'keydown', function( event ) {
  879. if ( event.keyCode === 27 ) {
  880. this.hide();
  881. editor.focus();
  882. }
  883. } );
  884. editor.on( 'remove', function() {
  885. toolbar.remove();
  886. } );
  887. toolbar.reposition = reposition;
  888. toolbar.hide().renderTo( document.body );
  889. return toolbar;
  890. }
  891. editor.shortcuts.add( 'alt+119', '', function() {
  892. var node;
  893. if ( activeToolbar ) {
  894. node = activeToolbar.find( 'toolbar' )[0];
  895. node && node.focus( true );
  896. }
  897. } );
  898. editor.on( 'nodechange', function( event ) {
  899. var collapsed = editor.selection.isCollapsed();
  900. var args = {
  901. element: event.element,
  902. parents: event.parents,
  903. collapsed: collapsed
  904. };
  905. editor.fire( 'wptoolbar', args );
  906. currentSelection = args.selection || args.element;
  907. if ( activeToolbar && activeToolbar !== args.toolbar ) {
  908. activeToolbar.hide();
  909. }
  910. if ( args.toolbar ) {
  911. activeToolbar = args.toolbar;
  912. if ( activeToolbar.visible() ) {
  913. activeToolbar.reposition();
  914. } else {
  915. activeToolbar.show();
  916. }
  917. } else {
  918. activeToolbar = false;
  919. }
  920. } );
  921. editor.on( 'focus', function() {
  922. if ( activeToolbar ) {
  923. activeToolbar.show();
  924. }
  925. } );
  926. function hide( event ) {
  927. var win;
  928. var size;
  929. if ( activeToolbar ) {
  930. if ( activeToolbar.tempHide || event.type === 'hide' || event.type === 'blur' ) {
  931. activeToolbar.hide();
  932. activeToolbar = false;
  933. } else if ( (
  934. event.type === 'resizewindow' ||
  935. event.type === 'scrollwindow' ||
  936. event.type === 'resize' ||
  937. event.type === 'scroll'
  938. ) && ! activeToolbar.blockHide ) {
  939. /*
  940. * Showing a tooltip may trigger a `resize` event in Chromium browsers.
  941. * That results in a flicketing inline menu; tooltips are shown on hovering over a button,
  942. * which then hides the toolbar on `resize`, then it repeats as soon as the toolbar is shown again.
  943. */
  944. if ( event.type === 'resize' || event.type === 'resizewindow' ) {
  945. win = editor.getWin();
  946. size = win.innerHeight + win.innerWidth;
  947. // Reset old cached size.
  948. if ( cachedWinSize && ( new Date() ).getTime() - cachedWinSize.timestamp > 2000 ) {
  949. cachedWinSize = null;
  950. }
  951. if ( cachedWinSize ) {
  952. if ( size && Math.abs( size - cachedWinSize.size ) < 2 ) {
  953. // `resize` fired but the window hasn't been resized. Bail.
  954. return;
  955. }
  956. } else {
  957. // First of a new series of `resize` events. Store the cached size and bail.
  958. cachedWinSize = {
  959. timestamp: ( new Date() ).getTime(),
  960. size: size,
  961. };
  962. return;
  963. }
  964. }
  965. clearTimeout( timeout );
  966. timeout = setTimeout( function() {
  967. if ( activeToolbar && typeof activeToolbar.show === 'function' ) {
  968. activeToolbar.scrolling = false;
  969. activeToolbar.show();
  970. }
  971. }, 250 );
  972. activeToolbar.scrolling = true;
  973. activeToolbar.hide();
  974. }
  975. }
  976. }
  977. if ( editor.inline ) {
  978. editor.on( 'resizewindow', hide );
  979. // Enable `capture` for the event.
  980. // This will hide/reposition the toolbar on any scrolling in the document.
  981. document.addEventListener( 'scroll', hide, true );
  982. } else {
  983. // Bind to the editor iframe and to the parent window.
  984. editor.dom.bind( editor.getWin(), 'resize scroll', hide );
  985. editor.on( 'resizewindow scrollwindow', hide );
  986. }
  987. editor.on( 'remove', function() {
  988. document.removeEventListener( 'scroll', hide, true );
  989. editor.off( 'resizewindow scrollwindow', hide );
  990. editor.dom.unbind( editor.getWin(), 'resize scroll', hide );
  991. } );
  992. editor.on( 'blur hide', hide );
  993. editor.wp = editor.wp || {};
  994. editor.wp._createToolbar = create;
  995. }, true );
  996. function noop() {}
  997. // Expose some functions (back-compat).
  998. return {
  999. _showButtons: noop,
  1000. _hideButtons: noop,
  1001. _setEmbed: noop,
  1002. _getEmbed: noop
  1003. };
  1004. });
  1005. }( window.tinymce ));