plugin.js 29 KB

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