plugin.js 33 KB

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