You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1037 lines
28 KiB

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