From 8b436e40076505c4e7b194520c07f001b5bf5e81 Mon Sep 17 00:00:00 2001 From: iseulde Date: Thu, 23 Feb 2017 15:44:18 +0100 Subject: [PATCH] Merge Single TinyMCE Instance --- shared/gridicons.svg | 251 +++++++++++ shared/index.css | 85 ++++ shared/tinymce/clean-paste.js | 19 + shared/tinymce/toolbar.js | 390 +++++++++++++++++ shared/tinymce/wplink.js | 613 +++++++++++++++++++++++++++ shared/tinymce/wptextpattern.js | 348 +++++++++++++++ tinymce-single/index.html | 40 ++ tinymce-single/tinymce/block.css | 448 ++++++++++++++++++++ tinymce-single/tinymce/block.js | 563 ++++++++++++++++++++++++ tinymce-single/tinymce/config.js | 111 +++++ tinymce-single/tinymce/formatting.js | 108 +++++ tinymce-single/tinymce/new.js | 56 +++ 12 files changed, 3032 insertions(+) create mode 100644 shared/gridicons.svg create mode 100644 shared/index.css create mode 100644 shared/tinymce/clean-paste.js create mode 100644 shared/tinymce/toolbar.js create mode 100644 shared/tinymce/wplink.js create mode 100644 shared/tinymce/wptextpattern.js create mode 100644 tinymce-single/index.html create mode 100644 tinymce-single/tinymce/block.css create mode 100644 tinymce-single/tinymce/block.js create mode 100644 tinymce-single/tinymce/config.js create mode 100644 tinymce-single/tinymce/formatting.js create mode 100644 tinymce-single/tinymce/new.js diff --git a/shared/gridicons.svg b/shared/gridicons.svg new file mode 100644 index 0000000000000..00f3e4ad9e9b6 --- /dev/null +++ b/shared/gridicons.svg @@ -0,0 +1,251 @@ +gridicons-add-image gridicons-add-outline gridicons-add gridicons-align-center gridicons-align-image-center gridicons-align-image-left gridicons-align-image-none gridicons-align-image-right gridicons-align-justify gridicons-align-left gridicons-align-right gridicons-arrow-down gridicons-arrow-left gridicons-arrow-right gridicons-arrow-up gridicons-aside gridicons-attachment gridicons-audio gridicons-bell gridicons-block gridicons-bold gridicons-book gridicons-bookmark-outline gridicons-bookmark gridicons-briefcase gridicons-calendar gridicons-camera gridicons-caption gridicons-cart gridicons-chat gridicons-checkmark-circle gridicons-checkmark gridicons-chevron-down gridicons-chevron-left gridicons-chevron-right gridicons-chevron-up gridicons-clear-formatting gridicons-clipboard gridicons-cloud-download gridicons-cloud-outline gridicons-cloud-upload gridicons-cloud gridicons-code gridicons-cog gridicons-comment gridicons-computer gridicons-coupon gridicons-create gridicons-credit-card gridicons-crop gridicons-cross-circle gridicons-cross-small gridicons-crossgridicons-custom-post-type gridicons-customize gridicons-domains gridicons-dropdown gridicons-ellipsis-circle gridicons-ellipsis gridicons-external gridicons-filter gridicons-flag gridicons-flip-horizontal gridicons-flip-vertical gridicons-folder-multiple gridicons-folder gridicons-fullscreen-exitgridicons-fullscreengridicons-globe gridicons-grid gridicons-heading gridicons-heart-outline gridicons-heart gridicons-help-outline gridicons-help gridicons-history gridicons-house gridicons-image-multiple gridicons-image gridicons-indent-left gridicons-indent-right gridicons-info-outline gridicons-info gridicons-ink gridicons-institution gridicons-italic gridicons-layout-blocks gridicons-layout gridicons-link-break gridicons-link gridicons-list-checkmark gridicons-list-ordered gridicons-list-unordered gridicons-location gridicons-lock gridicons-mail gridicons-mentiongridicons-menu gridicons-menus gridicons-microphonegridicons-minus-small gridicons-minus gridicons-money gridicons-my-sites-horizon gridicons-my-sites gridicons-not-visible gridicons-notice-outline gridicons-notice gridicons-offlinegridicons-pages gridicons-pause gridicons-pencil gridicons-phone gridicons-plugins gridicons-plus-small gridicons-plusgridicons-popout gridicons-posts gridicons-print gridicons-product-downloadable gridicons-product-external gridicons-product-virtual gridicons-product gridicons-quote gridicons-read-more gridicons-reader-follow gridicons-reader-following gridicons-reader gridicons-reblog gridicons-redo gridicons-refresh gridicons-refund gridicons-reply gridicons-resizegridicons-rotate gridicons-scheduled gridicons-search gridicons-share-ios gridicons-share gridicons-shipping gridicons-sign-out gridicons-spam gridicons-speaker gridicons-special-character gridicons-star-outline gridicons-star gridicons-stats-alt gridicons-stats gridicons-status gridicons-strikethrough gridicons-sync gridicons-tablet gridicons-tag gridicons-text-color gridicons-themes gridicons-thumbs-up gridicons-time gridicons-trash gridicons-trophy gridicons-types gridicons-underline gridicons-undo gridicons-user-addgridicons-user-circle gridicons-user gridicons-video-camera gridicons-video gridicons-visible diff --git a/shared/index.css b/shared/index.css new file mode 100644 index 0000000000000..b807bb87ef24c --- /dev/null +++ b/shared/index.css @@ -0,0 +1,85 @@ +body { + background: #f3f6f8; + color: #2f4452; + font-family: Merriweather, Georgia, "Times New Roman", Times, serif; + font-size: 1.25em; + line-height: 1.5; + margin: 1em; +} + +a { + color: inherit; +} + +nav { + font-size: 16px; +} + +nav:target { + display: none; +} + +nav li.is-active { + font-weight: bold; +} + +#editor { + background: #fff; + margin: 5em auto; + outline: none; + padding: 5em; + width: 37.5em; +} + +#editor iframe { + max-width: 100%; + border: 0; +} + +#editor img { + max-width: 100%; + height: auto; +} + +#editor figure { + margin: 0; +} + +#editor figure.aligncenter { + display: block; + margin-left: auto; + margin-right: auto; +} + +#editor figure.alignleft { + float: left; + margin: 0.5em 1em 0.5em 0; + width: 50%; +} + +#editor figure.alignright { + float: right; + margin: 0.5em 0 0.5em 1em; + width: 50%; +} + +#editor figcaption { + font-size: 0.8em; + margin-top: 0.5em; +} + +#editor figure img { + display: block; +} + +#editor pre { + overflow: auto; +} + +#editor:after { + content: "."; + visibility: hidden; + display: block; + height: 0; + clear: both; +} diff --git a/shared/tinymce/clean-paste.js b/shared/tinymce/clean-paste.js new file mode 100644 index 0000000000000..d3763edfdaaec --- /dev/null +++ b/shared/tinymce/clean-paste.js @@ -0,0 +1,19 @@ +( function( tinymce ) { + tinymce.PluginManager.add( 'clean-paste', function( editor ) { + // To do: remove pasted classes but keep when internally pasted. + + editor.on( 'BeforePastePreProcess', function( event ) { + var content = event.content; + + // Remove all external styles + content = content.replace( /(<[^>]+) style="[^"]*"([^>]*>)/gi, '$1$2' ); + + // Keep internal styles + content = content.replace( /(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, function( all, before, value, after ) { + return before + ' style="' + value + '"' + after; + } ); + + event.content = content; + } ); + } ); +} )( window.tinymce ); diff --git a/shared/tinymce/toolbar.js b/shared/tinymce/toolbar.js new file mode 100644 index 0000000000000..4af5555c8bea1 --- /dev/null +++ b/shared/tinymce/toolbar.js @@ -0,0 +1,390 @@ +( function( tinymce ) { + tinymce.ui.svgbutton = tinymce.ui.Button.extend( { + renderHtml: function() { + var id = this._id; + var prefix = this.classPrefix; + var icon = this.state.get( 'icon' ); + var text = this.state.get( 'text' ); + var html = ''; + + if ( icon && icon.indexOf( 'gridicons-' ) === 0 ) { + html += ( + '' + + '' + + '' + ); + } else if ( icon ) { + html += ''; + } + + if ( text ) { + this.classes.add( 'btn-has-text' ); + html += '' + this.encode( text ) + ''; + } + + return ( + '
' + + '' + + '
' + ); + }, + bindStates: function() { + var $el = this.$( this.getEl() ); + + this._super(); + + this.state.on( 'change:icon', function( event ) { + var icon = event.value; + var $i = $el.find( 'i' ); + var $svg = $el.find( 'svg' ); + + if ( icon && icon.indexOf( 'gridicons-' ) === 0 ) { + $i.remove(); + $svg.find( 'use' ).attr( 'xlink:href', '../shared/gridicons.svg#' + icon ); + } else { + $svg.remove(); + } + } ); + + return this; + } + } ); + + tinymce.PluginManager.add( 'toolbar', function( editor ) { + var each = tinymce.each; + var DOM = tinymce.DOM; + + editor.on( 'preinit', function() { + var Factory = tinymce.ui.Factory, + settings = editor.settings, + activeToolbar, + currentSelection, + timeout, + container = editor.getContainer(); + + function create( buttons, bottom ) { + var toolbar, + toolbarItems = [], + buttonGroup; + + each( buttons, function( item ) { + var itemName; + + function bindSelectorChanged() { + var selection = editor.selection; + + if ( itemName === 'bullist' ) { + selection.selectorChanged( 'ul > li', function( state, args ) { + var i = args.parents.length, + nodeName; + + while ( i-- ) { + nodeName = args.parents[ i ].nodeName; + + if ( nodeName === 'OL' || nodeName == 'UL' ) { + break; + } + } + + item.active( state && nodeName === 'UL' ); + } ); + } + + if ( itemName === 'numlist' ) { + selection.selectorChanged( 'ol > li', function( state, args ) { + var i = args.parents.length, + nodeName; + + while ( i-- ) { + nodeName = args.parents[ i ].nodeName; + + if ( nodeName === 'OL' || nodeName === 'UL' ) { + break; + } + } + + item.active( state && nodeName === 'OL' ); + } ); + } + + if ( item.settings.stateSelector ) { + selection.selectorChanged( item.settings.stateSelector, function( state ) { + item.active( state ); + }, true ); + } + + if ( item.settings.disabledStateSelector ) { + selection.selectorChanged( item.settings.disabledStateSelector, function( state ) { + item.disabled( state ); + } ); + } + } + + if ( item === '|' ) { + buttonGroup = null; + } else { + if ( Factory.has( item ) ) { + item = { + type: item + }; + + if ( settings.toolbar_items_size ) { + item.size = settings.toolbar_items_size; + } + + toolbarItems.push( item ); + + buttonGroup = null; + } else { + if ( ! buttonGroup ) { + buttonGroup = { + type: 'buttongroup', + items: [] + }; + + toolbarItems.push( buttonGroup ); + } + + if ( editor.buttons[ item ] ) { + itemName = item; + item = editor.buttons[ itemName ]; + + if ( typeof item === 'function' ) { + item = item(); + } + + item.type = item.type || 'svgbutton'; + + if ( settings.toolbar_items_size ) { + item.size = settings.toolbar_items_size; + } + + item = Factory.create( item ); + + buttonGroup.items.push( item ); + + if ( editor.initialized ) { + bindSelectorChanged(); + } else { + editor.on( 'init', bindSelectorChanged ); + } + } + } + } + } ); + + toolbar = Factory.create( { + type: 'panel', + layout: 'stack', + classes: 'toolbar-grp inline-toolbar-grp', + ariaRoot: true, + ariaRemember: true, + items: [ { + type: 'toolbar', + layout: 'flow', + items: toolbarItems + } ] + } ); + + toolbar.bottom = bottom; + + function reposition() { + if ( ! currentSelection ) { + return this; + } + + var scrollX = window.pageXOffset || document.documentElement.scrollLeft, + scrollY = window.pageYOffset || document.documentElement.scrollTop, + windowWidth = window.innerWidth, + windowHeight = window.innerHeight, + toolbar = this.getEl(), + toolbarWidth = toolbar.offsetWidth, + toolbarHeight = toolbar.clientHeight + 10, + selection = currentSelection.getBoundingClientRect(), + selectionMiddle = ( selection.left + selection.right ) / 2, + buffer = 5, + spaceNeeded = toolbarHeight + buffer, + spaceTop = selection.top, + spaceBottom = windowHeight - selection.bottom, + editorHeight = windowHeight, + className = '', + top, left; + + if ( spaceTop >= editorHeight || spaceBottom >= editorHeight ) { + this.scrolling = true; + this.hide(); + this.scrolling = false; + return this; + } + + if ( this.bottom ) { + if ( spaceBottom >= spaceNeeded ) { + className = ' mce-arrow-up'; + top = selection.bottom + scrollY; + } else if ( spaceTop >= spaceNeeded ) { + className = ' mce-arrow-down'; + top = selection.top + scrollY - toolbarHeight; + } + } else { + if ( spaceTop >= spaceNeeded ) { + className = ' mce-arrow-down'; + top = selection.top + scrollY - toolbarHeight; + } else if ( spaceBottom >= spaceNeeded && editorHeight / 2 > selection.bottom ) { + className = ' mce-arrow-up'; + top = selection.bottom + scrollY; + } + } + + if ( typeof top === 'undefined' ) { + top = scrollY + buffer; + } + + left = selectionMiddle - toolbarWidth / 2 + scrollX; + + if ( selection.left < 0 || selection.right > windowWidth ) { + left = scrollX + ( windowWidth - toolbarWidth ) / 2; + } else if ( toolbarWidth >= windowWidth ) { + className += ' mce-arrow-full'; + left = 0; + } else if ( ( left < 0 && selection.left + toolbarWidth > windowWidth ) || ( left + toolbarWidth > windowWidth && selection.right - toolbarWidth < 0 ) ) { + left = ( windowWidth - toolbarWidth ) / 2; + } else if ( left < scrollX ) { + className += ' mce-arrow-left'; + left = selection.left + scrollX; + } else if ( left + toolbarWidth > windowWidth + scrollX ) { + className += ' mce-arrow-right'; + left = selection.right - toolbarWidth + scrollX; + } + + toolbar.className = toolbar.className.replace( / ?mce-arrow-[\w]+/g, '' ) + className; + + DOM.setStyles( toolbar, { + 'left': left, + 'top': top + } ); + + return this; + } + + toolbar.on( 'show', function() { + this.reposition(); + } ); + + toolbar.on( 'keydown', function( event ) { + if ( event.keyCode === 27 ) { + this.hide(); + editor.focus(); + } + } ); + + editor.on( 'remove', function() { + toolbar.remove(); + } ); + + toolbar.reposition = reposition; + toolbar.hide().renderTo( document.body ); + + return toolbar; + } + + editor.shortcuts.add( 'alt+119', '', function() { + var node; + + if ( activeToolbar ) { + node = activeToolbar.find( 'toolbar' )[0]; + node && node.focus( true ); + } + } ); + + editor.on( 'nodechange', function( event ) { + // This can move the carect unexpectedly! + // event.element.normalize(); + + var range = editor.selection.getRng() + + var empty = ( + editor.dom.isEmpty( event.element ) && + ( event.element.nodeName === 'P' || ( + event.element.nodeName === 'BR' && + event.element.parentNode.nodeName === 'P' + ) ) + ); + + var args = { + element: event.element, + empty: empty, + parents: event.parents, + range: range, + }; + + editor.fire( 'wptoolbar', args ); + + currentSelection = args.selection || args.element; + + if ( activeToolbar && activeToolbar !== args.toolbar ) { + activeToolbar.hide(); + } + + if ( args.toolbar ) { + if ( activeToolbar !== args.toolbar ) { + activeToolbar = args.toolbar; + activeToolbar.show(); + } else { + activeToolbar.reposition(); + } + } else { + activeToolbar = false; + } + } ); + + editor.on( 'focus', function() { + if ( activeToolbar ) { + activeToolbar.show(); + } + } ); + + function hide( event ) { + if ( activeToolbar ) { + if ( activeToolbar.tempHide || event.type === 'hide' ) { + activeToolbar.hide(); + activeToolbar = false; + // } else if ( ( + // event.type === 'resizewindow' || + // event.type === 'scrollwindow' || + // event.type === 'resize' || + // event.type === 'scroll' + // ) && ! activeToolbar.blockHide ) { + // clearTimeout( timeout ); + + // timeout = setTimeout( function() { + // if ( activeToolbar && typeof activeToolbar.show === 'function' ) { + // activeToolbar.scrolling = false; + // activeToolbar.show(); + // } + // }, 250 ); + + // activeToolbar.scrolling = true; + // activeToolbar.hide(); + } + } + } + + // For full height editor. + editor.on( 'resizewindow scrollwindow', hide ); + // For scrollable editor. + editor.dom.bind( editor.getWin(), 'resize scroll', hide ); + + editor.on( 'remove', function() { + editor.off( 'resizewindow scrollwindow', hide ); + editor.dom.unbind( editor.getWin(), 'resize scroll', hide ); + } ); + + editor.on( 'blur hide', hide ); + + editor.wp = editor.wp || {}; + editor.wp._createToolbar = create; + }, true ); + }); +} )( window.tinymce ); diff --git a/shared/tinymce/wplink.js b/shared/tinymce/wplink.js new file mode 100644 index 0000000000000..9b2f3212be15e --- /dev/null +++ b/shared/tinymce/wplink.js @@ -0,0 +1,613 @@ +( function( tinymce ) { + tinymce.ui.WPLinkPreview = tinymce.ui.Control.extend( { + url: '#', + renderHtml: function() { + return ( + '' + ); + }, + setURL: function( url ) { + var index, lastIndex; + + if ( this.url !== url ) { + this.url = url; + + url = window.decodeURIComponent( url ); + + url = url.replace( /^(?:https?:)?\/\/(?:www\.)?/, '' ); + + if ( ( index = url.indexOf( '?' ) ) !== -1 ) { + url = url.slice( 0, index ); + } + + if ( ( index = url.indexOf( '#' ) ) !== -1 ) { + url = url.slice( 0, index ); + } + + url = url.replace( /(?:index)?\.html$/, '' ); + + if ( url.charAt( url.length - 1 ) === '/' ) { + url = url.slice( 0, -1 ); + } + + // If nothing's left (maybe the URL was just a fragment), use the whole URL. + if ( url === '' ) { + url = this.url; + } + + // If the URL is longer that 40 chars, concatenate the beginning (after the domain) and ending with ... + if ( url.length > 40 && ( index = url.indexOf( '/' ) ) !== -1 && ( lastIndex = url.lastIndexOf( '/' ) ) !== -1 && lastIndex !== index ) { + // If the beginning + ending are shorter that 40 chars, show more of the ending + if ( index + url.length - lastIndex < 40 ) { + lastIndex = -( 40 - ( index + 1 ) ); + } + + url = url.slice( 0, index + 1 ) + '\u2026' + url.slice( lastIndex ); + } + + tinymce.$( this.getEl().firstChild ).attr( 'href', this.url ).text( url ); + } + } + } ); + + tinymce.ui.WPLinkInput = tinymce.ui.Control.extend( { + renderHtml: function() { + return ( + '' + ); + }, + setURL: function( url ) { + this.getEl().firstChild.value = url; + }, + getURL: function() { + return tinymce.trim( this.getEl().firstChild.value ); + }, + getLinkText: function() { + var text = this.getEl().firstChild.nextSibling.value; + + if ( ! tinymce.trim( text ) ) { + return ''; + } + + return text.replace( /[\r\n\t ]+/g, ' ' ); + }, + reset: function() { + var urlInput = this.getEl().firstChild; + + urlInput.value = ''; + urlInput.nextSibling.value = ''; + } + } ); + + tinymce.PluginManager.add( 'wplink', function( editor ) { + var toolbar; + var editToolbar; + var previewInstance; + var inputInstance; + var linkNode; + var doingUndoRedo; + var doingUndoRedoTimer; + var $ = window.jQuery; + var emailRegex = /^(mailto:)?[a-z0-9._%+-]+@[a-z0-9][a-z0-9.-]*\.[a-z]{2,63}$/i; + var urlRegex1 = /^https?:\/\/([^\s/?.#-][^\s\/?.#]*\.?)+(\/[^\s"]*)?$/i; + var urlRegex2 = /^https?:\/\/[^\/]+\.[^\/]+($|\/)/i; + var speak = ( typeof window.wp !== 'undefined' && window.wp.a11y && window.wp.a11y.speak ) ? window.wp.a11y.speak : function() {}; + var hasLinkError = false; + + function getSelectedLink() { + var href, html, + node = editor.selection.getNode(), + link = editor.dom.getParent( node, 'a[href]' ); + + if ( ! link ) { + html = editor.selection.getContent({ format: 'raw' }); + + if ( html && html.indexOf( '' ) !== -1 ) { + href = html.match( /href="([^">]+)"/ ); + + if ( href && href[1] ) { + link = editor.$( 'a[href="' + href[1] + '"]', node )[0]; + } + + if ( link ) { + editor.selection.select( link ); + } + } + } + + return link; + } + + function removePlaceholders() { + editor.$( 'a' ).each( function( i, element ) { + var $element = editor.$( element ); + + if ( $element.attr( 'href' ) === '_wp_link_placeholder' ) { + editor.dom.remove( element, true ); + } else if ( $element.attr( 'data-wplink-edit' ) ) { + $element.attr( 'data-wplink-edit', null ); + } + }); + } + + function removePlaceholderStrings( content, dataAttr ) { + return content.replace( /(]+>)([\s\S]*?)<\/a>/g, function( all, tag, text ) { + if ( tag.indexOf( ' href="_wp_link_placeholder"' ) > -1 ) { + return text; + } + + if ( dataAttr ) { + tag = tag.replace( / data-wplink-edit="true"/g, '' ); + } + + tag = tag.replace( / data-wplink-url-error="true"/g, '' ); + + return tag + text + ''; + }); + } + + function checkLink( node ) { + var $link = editor.$( node ); + var href = $link.attr( 'href' ); + + if ( ! href || typeof $ === 'undefined' ) { + return; + } + + hasLinkError = false; + + if ( /^http/i.test( href ) && ( ! urlRegex1.test( href ) || ! urlRegex2.test( href ) ) ) { + hasLinkError = true; + $link.attr( 'data-wplink-url-error', 'true' ); + speak( editor.translate( 'Warning: the link has been inserted but may have errors. Please test it.' ), 'assertive' ); + } else { + $link.removeAttr( 'data-wplink-url-error' ); + } + } + + editor.on( 'preinit', function() { + if ( editor.wp && editor.wp._createToolbar ) { + toolbar = editor.wp._createToolbar( [ + 'wp_link_preview', + 'wp_link_edit', + 'wp_link_remove' + ], true ); + + var editButtons = [ + 'wp_link_input', + 'wp_link_apply' + ]; + + if ( typeof window.wpLink !== 'undefined' ) { + editButtons.push( 'wp_link_advanced' ); + } + + editToolbar = editor.wp._createToolbar( editButtons, true ); + + editToolbar.on( 'show', function() { + if ( typeof window.wpLink === 'undefined' || ! window.wpLink.modalOpen ) { + window.setTimeout( function() { + var element = editToolbar.$el.find( 'input.ui-autocomplete-input' )[0], + selection = linkNode && ( linkNode.textContent || linkNode.innerText ); + + if ( element ) { + if ( ! element.value && selection && typeof window.wpLink !== 'undefined' ) { + element.value = window.wpLink.getUrlFromSelection( selection ); + } + + if ( ! doingUndoRedo ) { + element.focus(); + element.select(); + } + } + } ); + } + } ); + + editToolbar.on( 'hide', function() { + if ( ! editToolbar.scrolling ) { + editor.execCommand( 'wp_link_cancel' ); + } + } ); + } + } ); + + editor.addCommand( 'WP_Link', function() { + if ( tinymce.Env.ie && tinymce.Env.ie < 10 && typeof window.wpLink !== 'undefined' ) { + window.wpLink.open( editor.id ); + return; + } + + linkNode = getSelectedLink(); + editToolbar.tempHide = false; + + if ( linkNode ) { + editor.dom.setAttribs( linkNode, { 'data-wplink-edit': true } ); + } else { + removePlaceholders(); + editor.execCommand( 'mceInsertLink', false, { href: '_wp_link_placeholder' } ); + + linkNode = editor.$( 'a[href="_wp_link_placeholder"]' )[0]; + editor.nodeChanged(); + } + } ); + + editor.addCommand( 'wp_link_apply', function() { + if ( editToolbar.scrolling ) { + return; + } + + var href, text; + + if ( linkNode ) { + href = inputInstance.getURL(); + text = inputInstance.getLinkText(); + editor.focus(); + + if ( ! href ) { + editor.dom.remove( linkNode, true ); + return; + } + + if ( ! /^(?:[a-z]+:|#|\?|\.|\/)/.test( href ) && ! emailRegex.test( href ) ) { + href = 'http://' + href; + } + + editor.dom.setAttribs( linkNode, { href: href, 'data-wplink-edit': null } ); + + if ( ! tinymce.trim( linkNode.innerHTML ) ) { + editor.$( linkNode ).text( text || href ); + } + + checkLink( linkNode ); + } + + inputInstance.reset(); + editor.nodeChanged(); + + // Audible confirmation message when a link has been inserted in the Editor. + if ( typeof window.wpLinkL10n !== 'undefined' && ! hasLinkError ) { + speak( window.wpLinkL10n.linkInserted ); + } + } ); + + editor.addCommand( 'wp_link_cancel', function() { + if ( ! editToolbar.tempHide ) { + inputInstance.reset(); + removePlaceholders(); + } + } ); + + editor.addCommand( 'wp_unlink', function() { + editor.execCommand( 'unlink' ); + editToolbar.tempHide = false; + editor.execCommand( 'wp_link_cancel' ); + } ); + + // WP default shortcuts + editor.addShortcut( 'access+a', '', 'WP_Link' ); + editor.addShortcut( 'access+s', '', 'wp_unlink' ); + // The "de-facto standard" shortcut, see #27305 + editor.addShortcut( 'meta+k', '', 'WP_Link' ); + + editor.addButton( 'link', { + icon: 'link', + tooltip: 'Insert/edit link', + cmd: 'WP_Link', + stateSelector: 'a[href]' + }); + + editor.addButton( 'unlink', { + icon: 'unlink', + tooltip: 'Remove link', + cmd: 'unlink' + }); + + editor.addMenuItem( 'link', { + icon: 'link', + text: 'Insert/edit link', + cmd: 'WP_Link', + stateSelector: 'a[href]', + context: 'insert', + prependToContext: true + }); + + editor.on( 'pastepreprocess', function( event ) { + var pastedStr = event.content, + regExp = /^(?:https?:)?\/\/\S+$/i; + + if ( ! editor.selection.isCollapsed() && ! regExp.test( editor.selection.getContent() ) ) { + pastedStr = pastedStr.replace( /<[^>]+>/g, '' ); + pastedStr = tinymce.trim( pastedStr ); + + if ( regExp.test( pastedStr ) ) { + editor.execCommand( 'mceInsertLink', false, { + href: editor.dom.decode( pastedStr ) + } ); + + event.preventDefault(); + } + } + } ); + + // Remove any remaining placeholders on saving. + editor.on( 'savecontent', function( event ) { + event.content = removePlaceholderStrings( event.content, true ); + }); + + // Prevent adding undo levels on inserting link placeholder. + editor.on( 'BeforeAddUndo', function( event ) { + if ( event.lastLevel && event.lastLevel.content && event.level.content && + event.lastLevel.content === removePlaceholderStrings( event.level.content ) ) { + + event.preventDefault(); + } + }); + + // When doing undo and redo with keyboard shortcuts (Ctrl|Cmd+Z, Ctrl|Cmd+Shift+Z, Ctrl|Cmd+Y), + // set a flag to not focus the inline dialog. The editor has to remain focused so the users can do consecutive undo/redo. + editor.on( 'keydown', function( event ) { + if ( event.keyCode === 27 ) { // Esc + editor.execCommand( 'wp_link_cancel' ); + } + + if ( event.altKey || ( tinymce.Env.mac && ( ! event.metaKey || event.ctrlKey ) ) || + ( ! tinymce.Env.mac && ! event.ctrlKey ) ) { + + return; + } + + if ( event.keyCode === 89 || event.keyCode === 90 ) { // Y or Z + doingUndoRedo = true; + + window.clearTimeout( doingUndoRedoTimer ); + doingUndoRedoTimer = window.setTimeout( function() { + doingUndoRedo = false; + }, 500 ); + } + } ); + + editor.addButton( 'wp_link_preview', { + type: 'WPLinkPreview', + onPostRender: function() { + previewInstance = this; + } + } ); + + editor.addButton( 'wp_link_input', { + type: 'WPLinkInput', + onPostRender: function() { + var element = this.getEl(), + input = element.firstChild, + $input, cache, last; + + inputInstance = this; + + if ( $ && $.ui && $.ui.autocomplete ) { + $input = $( input ); + + $input.on( 'keydown', function() { + $input.removeAttr( 'aria-activedescendant' ); + } ) + .autocomplete( { + source: function( request, response ) { + if ( last === request.term ) { + response( cache ); + return; + } + + if ( /^https?:/.test( request.term ) || request.term.indexOf( '.' ) !== -1 ) { + return response(); + } + + $.post( window.ajaxurl, { + action: 'wp-link-ajax', + page: 1, + search: request.term, + _ajax_linking_nonce: $( '#_ajax_linking_nonce' ).val() + }, function( data ) { + cache = data; + response( data ); + }, 'json' ); + + last = request.term; + }, + focus: function( event, ui ) { + $input.attr( 'aria-activedescendant', 'mce-wp-autocomplete-' + ui.item.ID ); + /* + * Don't empty the URL input field, when using the arrow keys to + * highlight items. See api.jqueryui.com/autocomplete/#event-focus + */ + event.preventDefault(); + }, + select: function( event, ui ) { + $input.val( ui.item.permalink ); + $( element.firstChild.nextSibling ).val( ui.item.title ); + + if ( 9 === event.keyCode && typeof window.wpLinkL10n !== 'undefined' ) { + // Audible confirmation message when a link has been selected. + speak( window.wpLinkL10n.linkSelected ); + } + + return false; + }, + open: function() { + $input.attr( 'aria-expanded', 'true' ); + editToolbar.blockHide = true; + }, + close: function() { + $input.attr( 'aria-expanded', 'false' ); + editToolbar.blockHide = false; + }, + minLength: 2, + position: { + my: 'left top+2' + }, + messages: { + noResults: ( typeof window.uiAutocompleteL10n !== 'undefined' ) ? window.uiAutocompleteL10n.noResults : '', + results: function( number ) { + if ( typeof window.uiAutocompleteL10n !== 'undefined' ) { + if ( number > 1 ) { + return window.uiAutocompleteL10n.manyResults.replace( '%d', number ); + } + + return window.uiAutocompleteL10n.oneResult; + } + } + } + } ).autocomplete( 'instance' )._renderItem = function( ul, item ) { + return $( '
  • ' ) + .append( '' + item.title + ' ' + item.info + '' ) + .appendTo( ul ); + }; + + $input.attr( { + 'role': 'combobox', + 'aria-autocomplete': 'list', + 'aria-expanded': 'false', + 'aria-owns': $input.autocomplete( 'widget' ).attr( 'id' ) + } ) + .on( 'focus', function() { + var inputValue = $input.val(); + /* + * Don't trigger a search if the URL field already has a link or is empty. + * Also, avoids screen readers announce `No search results`. + */ + if ( inputValue && ! /^https?:/.test( inputValue ) ) { + $input.autocomplete( 'search' ); + } + } ) + // Returns a jQuery object containing the menu element. + .autocomplete( 'widget' ) + .addClass( 'wplink-autocomplete' ) + .attr( 'role', 'listbox' ) + .removeAttr( 'tabindex' ) // Remove the `tabindex=0` attribute added by jQuery UI. + /* + * Looks like Safari and VoiceOver need an `aria-selected` attribute. See ticket #33301. + * The `menufocus` and `menublur` events are the same events used to add and remove + * the `ui-state-focus` CSS class on the menu items. See jQuery UI Menu Widget. + */ + .on( 'menufocus', function( event, ui ) { + ui.item.attr( 'aria-selected', 'true' ); + }) + .on( 'menublur', function() { + /* + * The `menublur` event returns an object where the item is `null` + * so we need to find the active item with other means. + */ + $( this ).find( '[aria-selected="true"]' ).removeAttr( 'aria-selected' ); + }); + } + + tinymce.$( input ).on( 'keydown', function( event ) { + if ( event.keyCode === 13 ) { + editor.execCommand( 'wp_link_apply' ); + event.preventDefault(); + } + } ); + } + } ); + + editor.on( 'wptoolbar', function( event ) { + var linkNode = editor.dom.getParent( event.element, 'a' ), + $linkNode, href, edit; + + if ( typeof window.wpLink !== 'undefined' && window.wpLink.modalOpen ) { + editToolbar.tempHide = true; + return; + } + + editToolbar.tempHide = false; + + if ( linkNode ) { + $linkNode = editor.$( linkNode ); + href = $linkNode.attr( 'href' ); + edit = $linkNode.attr( 'data-wplink-edit' ); + + if ( href === '_wp_link_placeholder' || edit ) { + if ( href !== '_wp_link_placeholder' && ! inputInstance.getURL() ) { + inputInstance.setURL( href ); + } + + event.element = linkNode; + event.toolbar = editToolbar; + } else if ( href && ! $linkNode.find( 'img' ).length ) { + previewInstance.setURL( href ); + event.element = linkNode; + event.toolbar = toolbar; + + if ( $linkNode.attr( 'data-wplink-url-error' ) === 'true' ) { + toolbar.$el.find( '.wp-link-preview a' ).addClass( 'wplink-url-error' ); + } else { + toolbar.$el.find( '.wp-link-preview a' ).removeClass( 'wplink-url-error' ); + hasLinkError = false; + } + } + } else if ( editToolbar.visible() ) { + editor.execCommand( 'wp_link_cancel' ); + } + } ); + + editor.addButton( 'wp_link_edit', { + tooltip: 'Edit ', // trailing space is needed, used for context + icon: 'gridicons-pencil', + cmd: 'WP_Link' + } ); + + editor.addButton( 'wp_link_remove', { + tooltip: 'Remove link', + icon: 'gridicons-link-break', + cmd: 'wp_unlink' + } ); + + editor.addButton( 'wp_link_advanced', { + tooltip: 'Link options', + icon: 'dashicon dashicons-admin-generic', + onclick: function() { + if ( typeof window.wpLink !== 'undefined' ) { + var url = inputInstance.getURL() || null, + text = inputInstance.getLinkText() || null; + + /* + * Accessibility note: moving focus back to the editor confuses + * screen readers. They will announce again the Editor ARIA role + * `application` and the iframe `title` attribute. + * + * Unfortunately IE looses the selection when the editor iframe + * looses focus, so without returning focus to the editor, the code + * in the modal will not be able to get the selection, place the caret + * at the same location, etc. + */ + if ( tinymce.Env.ie ) { + editor.focus(); // Needed for IE + } + + window.wpLink.open( editor.id, url, text, linkNode ); + + editToolbar.tempHide = true; + inputInstance.reset(); + } + } + } ); + + editor.addButton( 'wp_link_apply', { + tooltip: 'Apply', + icon: 'gridicons-checkmark', + cmd: 'wp_link_apply', + classes: 'widget btn primary' + } ); + + return { + close: function() { + editToolbar.tempHide = false; + editor.execCommand( 'wp_link_cancel' ); + }, + checkLink: checkLink + }; + } ); +} )( window.tinymce ); diff --git a/shared/tinymce/wptextpattern.js b/shared/tinymce/wptextpattern.js new file mode 100644 index 0000000000000..236775034cce3 --- /dev/null +++ b/shared/tinymce/wptextpattern.js @@ -0,0 +1,348 @@ +/** + * Text pattern plugin for TinyMCE + * + * @since 4.3.0 + * + * This plugin can automatically format text patterns as you type. It includes several groups of patterns. + * + * Start of line patterns: + * As-you-type: + * - Unordered list (`* ` and `- `). + * - Ordered list (`1. ` and `1) `). + * + * On enter: + * - h2 (## ). + * - h3 (### ). + * - h4 (#### ). + * - h5 (##### ). + * - h6 (###### ). + * - blockquote (> ). + * - hr (---). + * + * Inline patterns: + * - (`) (backtick). + * + * If the transformation in unwanted, the user can undo the change by pressing backspace, + * using the undo shortcut, or the undo button in the toolbar. + * + * Setting for the patterns can be overridden by plugins by using the `tiny_mce_before_init` PHP filter. + * The setting name is `wptextpattern` and the value is an object containing override arrays for each + * patterns group. There are three groups: "space", "enter", and "inline". Example (PHP): + * + * add_filter( 'tiny_mce_before_init', 'my_mce_init_wptextpattern' ); + * function my_mce_init_wptextpattern( $init ) { + * $init['wptextpattern'] = wp_json_encode( array( + * 'inline' => array( + * array( 'delimiter' => '**', 'format' => 'bold' ), + * array( 'delimiter' => '__', 'format' => 'italic' ), + * ), + * ) ); + * + * return $init; + * } + * + * Note that setting this will override the default text patterns. You will need to include them + * in your settings array if you want to keep them working. + */ +( function( tinymce, setTimeout ) { + if ( tinymce.Env.ie && tinymce.Env.ie < 9 ) { + return; + } + + /** + * Escapes characters for use in a Regular Expression. + * + * @param {String} string Characters to escape + * + * @return {String} Escaped characters + */ + function escapeRegExp( string ) { + return string.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&' ); + } + + tinymce.PluginManager.add( 'wptextpattern', function( editor ) { + var VK = tinymce.util.VK; + var settings = editor.settings.wptextpattern || {}; + + var spacePatterns = settings.space || [ + { regExp: /^[*-]\s/, cmd: 'InsertUnorderedList' }, + { regExp: /^1[.)]\s/, cmd: 'InsertOrderedList' } + ]; + + var enterPatterns = settings.enter || [ + { start: '##', format: 'h2' }, + { start: '###', format: 'h3' }, + { start: '####', format: 'h4' }, + { start: '#####', format: 'h5' }, + { start: '######', format: 'h6' }, + { start: '>', format: 'blockquote' }, + { regExp: /^(-){3,}$/, element: 'hr' } + ]; + + var inlinePatterns = settings.inline || [ + { delimiter: '`', format: 'code' } + ]; + + var canUndo; + + editor.on( 'selectionchange', function() { + canUndo = null; + } ); + + editor.on( 'keydown', function( event ) { + if ( ( canUndo && event.keyCode === 27 /* ESCAPE */ ) || ( canUndo === 'space' && event.keyCode === VK.BACKSPACE ) ) { + editor.undoManager.undo(); + event.preventDefault(); + event.stopImmediatePropagation(); + } + + if ( VK.metaKeyPressed( event ) ) { + return; + } + + if ( event.keyCode === VK.ENTER ) { + enter(); + // Wait for the browser to insert the character. + } else if ( event.keyCode === VK.SPACEBAR ) { + setTimeout( space ); + } else if ( event.keyCode > 47 && ! ( event.keyCode >= 91 && event.keyCode <= 93 ) ) { + setTimeout( inline ); + } + }, true ); + + function inline() { + var rng = editor.selection.getRng(); + var node = rng.startContainer; + var offset = rng.startOffset; + var startOffset; + var endOffset; + var pattern; + var format; + var zero; + + // We need a non empty text node with an offset greater than zero. + if ( ! node || node.nodeType !== 3 || ! node.data.length || ! offset ) { + return; + } + + var string = node.data.slice( 0, offset ); + var lastChar = node.data.charAt( offset - 1 ); + + tinymce.each( inlinePatterns, function( p ) { + // Character before selection should be delimiter. + if ( lastChar !== p.delimiter.slice( -1 ) ) { + return; + } + + var escDelimiter = escapeRegExp( p.delimiter ); + var delimiterFirstChar = p.delimiter.charAt( 0 ); + var regExp = new RegExp( '(.*)' + escDelimiter + '.+' + escDelimiter + '$' ); + var match = string.match( regExp ); + + if ( ! match ) { + return; + } + + startOffset = match[1].length; + endOffset = offset - p.delimiter.length; + + var before = string.charAt( startOffset - 1 ); + var after = string.charAt( startOffset + p.delimiter.length ); + + // test*test* => format applied + // test *test* => applied + // test* test* => not applied + if ( startOffset && /\S/.test( before ) ) { + if ( /\s/.test( after ) || before === delimiterFirstChar ) { + return; + } + } + + // Do not replace when only whitespace and delimiter characters. + if ( ( new RegExp( '^[\\s' + escapeRegExp( delimiterFirstChar ) + ']+$' ) ).test( string.slice( startOffset, endOffset ) ) ) { + return; + } + + pattern = p; + + return false; + } ); + + if ( ! pattern ) { + return; + } + + format = editor.formatter.get( pattern.format ); + + if ( format && format[0].inline ) { + editor.undoManager.add(); + + editor.undoManager.transact( function() { + node.insertData( offset, '\uFEFF' ); + + node = node.splitText( startOffset ); + zero = node.splitText( offset - startOffset ); + + node.deleteData( 0, pattern.delimiter.length ); + node.deleteData( node.data.length - pattern.delimiter.length, pattern.delimiter.length ); + + editor.formatter.apply( pattern.format, {}, node ); + + editor.selection.setCursorLocation( zero, 1 ); + } ); + + // We need to wait for native events to be triggered. + setTimeout( function() { + canUndo = 'space'; + + editor.once( 'selectionchange', function() { + var offset; + + if ( zero ) { + offset = zero.data.indexOf( '\uFEFF' ); + + if ( offset !== -1 ) { + zero.deleteData( offset, offset + 1 ); + } + } + } ); + } ); + } + } + + function firstTextNode( node ) { + var parent = editor.dom.getParent( node, 'p' ), + child; + + if ( ! parent ) { + return; + } + + while ( child = parent.firstChild ) { + if ( child.nodeType !== 3 ) { + parent = child; + } else { + break; + } + } + + if ( ! child ) { + return; + } + + if ( ! child.data ) { + if ( child.nextSibling && child.nextSibling.nodeType === 3 ) { + child = child.nextSibling; + } else { + child = null; + } + } + + return child; + } + + function space() { + var rng = editor.selection.getRng(), + node = rng.startContainer, + parent, + text; + + if ( ! node || firstTextNode( node ) !== node ) { + return; + } + + parent = node.parentNode; + text = node.data; + + tinymce.each( spacePatterns, function( pattern ) { + var match = text.match( pattern.regExp ); + + if ( ! match || rng.startOffset !== match[0].length ) { + return; + } + + editor.undoManager.add(); + + editor.undoManager.transact( function() { + node.deleteData( 0, match[0].length ); + + if ( ! parent.innerHTML ) { + parent.appendChild( document.createElement( 'br' ) ); + } + + editor.selection.setCursorLocation( parent ); + editor.execCommand( pattern.cmd ); + } ); + + // We need to wait for native events to be triggered. + setTimeout( function() { + canUndo = 'space'; + } ); + + return false; + } ); + } + + function enter() { + var rng = editor.selection.getRng(), + start = rng.startContainer, + node = firstTextNode( start ), + i = enterPatterns.length, + text, pattern, parent; + + if ( ! node ) { + return; + } + + text = node.data; + + while ( i-- ) { + if ( enterPatterns[ i ].start ) { + if ( text.indexOf( enterPatterns[ i ].start ) === 0 ) { + pattern = enterPatterns[ i ]; + break; + } + } else if ( enterPatterns[ i ].regExp ) { + if ( enterPatterns[ i ].regExp.test( text ) ) { + pattern = enterPatterns[ i ]; + break; + } + } + } + + if ( ! pattern ) { + return; + } + + if ( node === start && tinymce.trim( text ) === pattern.start ) { + return; + } + + editor.once( 'keyup', function() { + editor.undoManager.add(); + + editor.undoManager.transact( function() { + if ( pattern.format ) { + editor.formatter.apply( pattern.format, {}, node ); + node.replaceData( 0, node.data.length, ltrim( node.data.slice( pattern.start.length ) ) ); + } else if ( pattern.element ) { + parent = node.parentNode && node.parentNode.parentNode; + + if ( parent ) { + parent.replaceChild( document.createElement( pattern.element ), node.parentNode ); + } + } + } ); + + // We need to wait for native events to be triggered. + setTimeout( function() { + canUndo = 'enter'; + } ); + } ); + } + + function ltrim( text ) { + return text ? text.replace( /^\s+/, '' ) : ''; + } + } ); +} )( window.tinymce, window.setTimeout ); diff --git a/tinymce-single/index.html b/tinymce-single/index.html new file mode 100644 index 0000000000000..e6a33a4d91a06 --- /dev/null +++ b/tinymce-single/index.html @@ -0,0 +1,40 @@ + + + + + Editor Blocks Demo (Single TinyMCE Instance) + + + + + + +
    +

    1.0 Is The Loneliest Number

    + +

    Many entrepreneurs idolize Steve Jobs. He’s such a perfectionist, they say. Nothing leaves the doors of 1 Infinite Loop in Cupertino without a polish and finish that makes geeks everywhere drool. No compromise!

    + +
    + +
    + +

    I like Apple for the opposite reason: they’re not afraid of getting a rudimentary 1.0 out into the world.

    +
    + + + + + + + + + + + + diff --git a/tinymce-single/tinymce/block.css b/tinymce-single/tinymce/block.css new file mode 100644 index 0000000000000..73af591759877 --- /dev/null +++ b/tinymce-single/tinymce/block.css @@ -0,0 +1,448 @@ +#editor img::selection { + background-color: transparent; +} + +#editor *[data-mce-selected="block"] { + outline: 2px solid #87a6bc; + outline-offset: 11px; + /*background-color: rgba( 135, 166, 188, 0.3 ); + box-shadow: 0px 0px 0px 11px rgba( 135, 166, 188, 0.3 ); + position: relative;*/ +} + +/*#editor *[data-mce-selected="block"]:before { + content: ''; + border-top: 2px solid #87a6bc; + border-left: 2px solid #87a6bc; + border-right: 2px solid #87a6bc; + display: block; + position: absolute; + top: -14px; + left: -12px; + right: -12px; + bottom: -14px; +} + +#editor *[data-mce-selected="block"] ~ [data-mce-selected="block"]:before { + border-top: none; + border-bottom: 2px solid #87a6bc; +}*/ + +#editor *[data-mce-selected="move"] { + color: #87a6bc; + outline: 1px dashed #87a6bc; + outline-offset: 11px; +} + +#editor.is-moving-block > *:before { + content: '\21E2'; + display: block; + position: absolute; + font-size: 24px; + /*background-color: #0087be; + padding: 0 3px; + border-radius: 3px; + color: #fff;*/ + color: #87a6bc; + font-weight: normal; + margin-top: -24px; + left: 40px; +} + +#editor.is-moving-block > .alignright:before, +#editor.is-moving-block > [data-mce-selected="move"]:before, +#editor.is-moving-block > [data-mce-selected="move"] + *:before { + content: ''; +} + +.mce-container, +.mce-container *, +.mce-container button, +.mce-container button:focus, +.mce-container button:hover, +.mce-container button:active, +.mce-container input { + -webkit-appearance: none; + background: transparent; + border: 0; + box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + color: inherit; + cursor: inherit; + direction: ltr; + float: none; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", sans-serif; + font-size: 14px; + -webkit-font-smoothing: subpixel-antialiased; + -moz-osx-font-smoothing: auto; + font-weight: normal; + height: auto; + letter-spacing: normal; + line-height: normal; + margin: 0; + max-width: none; + outline: 0; + padding: 0; + position: static; + -webkit-tap-highlight-color: transparent; + text-align: left; + text-decoration: none; + text-shadow: none; + text-transform: none; + transition: none; + -webkit-transition: none; + vertical-align: top; + white-space: nowrap; + width: auto; +} + +.mce-tooltip { + display: none; +} + +.mce-btn button::-moz-focus-inner { + border: 0; + padding: 0; +} + +.mce-inline-toolbar-grp .mce-btn { + border: 1px solid transparent; + position: relative; + display: inline-block; + background: none; + border-radius: 2px; + margin: 2px; + color: #23282d; +} + +.mce-inline-toolbar-grp .mce-btn:hover, +.mce-inline-toolbar-grp .mce-btn:focus { + background-color: #fafafa; + border-color: #23282d; + -webkit-box-shadow: inset 0 1px 0 #fff, 0 1px 0 rgba( 0, 0, 0, 0.08 ); + box-shadow: inset 0 1px 0 #fff, 0 1px 0 rgba( 0, 0, 0, 0.08 ); +} + +.mce-inline-toolbar-grp .mce-btn.mce-active, +.mce-inline-toolbar-grp .mce-btn:active { + background-color: #ebebeb; + border-color: #555d66; + -webkit-box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, 0.3 ); + box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, 0.3 ); +} + +.mce-inline-toolbar-grp .mce-btn button { + padding: 1px 2px; + display: block; +} + +svg.gridicon { + width: 24px; + height: 24px; + fill: #000; +} + +.mce-inline-toolbar-grp .mce-btn.mce-primary { + background: #0085ba; + border-color: #0073aa #006799 #006799; + -webkit-box-shadow: 0 1px 0 #006799; + box-shadow: 0 1px 0 #006799; + text-decoration: none; +} + +.mce-inline-toolbar-grp .mce-btn.mce-primary .mce-txt, +.mce-inline-toolbar-grp .mce-btn.mce-primary .mce-ico { + color: #fff; + text-shadow: 0 -1px 1px #006799, + 1px 0 1px #006799, + 0 1px 1px #006799, + -1px 0 1px #006799; +} + +.mce-inline-toolbar-grp .mce-btn.mce-primary svg { + fill: #fff; +} + +.mce-flow-layout-item { + margin: 1px; +} + +.mce-btn .mce-txt { + display: block; + height: 24px; + min-width: 24px; + text-align: center; + font-size: 18px; + font-weight: 500; + line-height: 24px; +} + +.mce-btn select { + width: 26px; + padding: 0; + border: 0; + height: 24px; + opacity: 0; + -webkit-appearance: none; + position: absolute; + top: 0; +} + +div.mce-inline-toolbar-grp { + background-color: #f5f5f5; + border: 1px solid #a0a5aa; + -webkit-border-radius: 2px; + border-radius: 2px; + -webkit-box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.15 ); + box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.15 ); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin-bottom: 8px; + position: absolute; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + max-width: 98%; + z-index: 99998; /* Under adminbar */ +} + +div.mce-inline-toolbar-grp > div.mce-stack-layout { + padding: 1px; +} + +div.mce-inline-toolbar-grp.mce-arrow-up { + margin-bottom: 0; + margin-top: 8px; +} + +div.mce-inline-toolbar-grp:before, +div.mce-inline-toolbar-grp:after { + position: absolute; + left: 50%; + display: block; + width: 0; + height: 0; + border-style: solid; + border-color: transparent; + content: ''; +} + +div.mce-inline-toolbar-grp.mce-arrow-up:before { + top: -9px; + border-bottom-color: #a0a5aa; + border-width: 0 9px 9px; + margin-left: -9px; +} + +div.mce-inline-toolbar-grp.mce-arrow-down:before { + bottom: -9px; + border-top-color: #a0a5aa; + border-width: 9px 9px 0; + margin-left: -9px; +} + +div.mce-inline-toolbar-grp.mce-arrow-up:after { + top: -8px; + border-bottom-color: #f5f5f5; + border-width: 0 8px 8px; + margin-left: -8px; +} + +div.mce-inline-toolbar-grp.mce-arrow-down:after { + bottom: -8px; + border-top-color: #f5f5f5; + border-width: 8px 8px 0; + margin-left: -8px; +} + +div.mce-inline-toolbar-grp.mce-arrow-left:before, +div.mce-inline-toolbar-grp.mce-arrow-left:after { + margin: 0; +} + +div.mce-inline-toolbar-grp.mce-arrow-left:before { + left: 20px; +} +div.mce-inline-toolbar-grp.mce-arrow-left:after { + left: 21px; +} + +div.mce-inline-toolbar-grp.mce-arrow-right:before, +div.mce-inline-toolbar-grp.mce-arrow-right:after { + left: auto; + margin: 0; +} + +div.mce-inline-toolbar-grp.mce-arrow-right:before { + right: 20px; +} + +div.mce-inline-toolbar-grp.mce-arrow-right:after { + right: 21px; +} + +div.mce-inline-toolbar-grp.mce-arrow-full { + right: 0; +} + +div.mce-inline-toolbar-grp.mce-arrow-full > div { + width: 100%; + overflow-x: auto; +} + +div.mce-inline-toolbar-grp.mce-arrow-left-side:before, +div.mce-inline-toolbar-grp.mce-arrow-left-side:after { + left: auto; + top: 50%; +} + +div.mce-inline-toolbar-grp.mce-arrow-left-side:before { + left: -9px; + border-right-color: #a0a5aa; + border-width: 9px 9px 9px 0; + margin-top: -9px; +} + +div.mce-inline-toolbar-grp.mce-arrow-left-side:after { + left: -8px; + border-right-color: #f5f5f5; + border-width: 8px 8px 8px 0; + margin-top: -8px; +} + +div.wp-link-preview { + float: left; + margin: 2px; + max-width: 694px; + overflow: hidden; + text-overflow: ellipsis; +} + +div.wp-link-preview a { + display: block; + color: #0073aa; + font-size: 16px; + padding: 5px; + text-decoration: underline; + -webkit-transition-property: border, background, color; + transition-property: border, background, color; + -webkit-transition-duration: .05s; + transition-duration: .05s; + -webkit-transition-timing-function: ease-in-out; + transition-timing-function: ease-in-out; + cursor: pointer; +} + +div.wp-link-input { + float: left; + margin: 2px; + max-width: 694px; +} + +div.wp-link-input input { + font-size: 16px; + width: 300px; + padding: 4px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + border-width: 1px; + border-style: solid; + background-color: #fff; + color: #333; + border-color: #ddd; + box-shadow: inset 0 1px 2px rgba(0,0,0,0.07); +} + +div.wp-link-input input:focus { + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.1); + box-shadow: 0 1px 2px rgba(0,0,0,0.1); + border-color: #999; +} + +@media screen and ( max-width: 782px ) { + div.wp-link-preview { + margin: 8px 0 8px 5px; + max-width: 70%; + max-width: -webkit-calc(100% - 86px); + max-width: calc(100% - 86px); + } +} + +div.mce-inline-toolbar-grp.block-toolbar { + border: none; + background: none; + box-shadow: none; +} + +.mce-btn.mce-move-up { + position: absolute; + top: -10px; + left: -30px; +} + +.mce-btn.mce-move-down { + position: absolute; + bottom: -10px; + left: -30px; +} + +/*.mce-btn.mce-move-up, +.mce-btn.mce-move-down { + float: left; + border: none; +} + +.mce-btn.mce-move-up { + margin-bottom: 0; +} + +.mce-btn.mce-move-down { + margin-top: 0; + position: absolute; + bottom: 2px; + left: 2px; +} + +.mce-btn.mce-move-up:focus, +.mce-btn.mce-move-down:focus, +.mce-btn.mce-move-up:hover, +.mce-btn.mce-move-down:hover { + background: none; + box-shadow: none; +} + +.mce-btn.mce-move-up button, +.mce-btn.mce-move-down button { + height: 12px; + padding: 1px; +} + +.mce-move-up svg.gridicon, +.mce-move-down svg.gridicon { + position: relative; + top: -6px; + width: 20px; +}*/ + +.block-toolbar .mce-btn { + margin: 0; +} + +.block-toolbar .mce-btn:hover, +.block-toolbar .mce-btn:active { + border-color: transparent; + background: none; + box-shadow: none; +} + +.block-toolbar .mce-flow-layout-item { + margin: 0; +} + +div.mce-inline-toolbar-grp.block-toolbar > div.mce-stack-layout { + padding: 0; +} diff --git a/tinymce-single/tinymce/block.js b/tinymce-single/tinymce/block.js new file mode 100644 index 0000000000000..8c26fc85358cf --- /dev/null +++ b/tinymce-single/tinymce/block.js @@ -0,0 +1,563 @@ +( function( tinymce ) { + tinymce.PluginManager.add( 'block', function( editor ) { + function focusToolbar( toolbar ) { + var node = toolbar.find( 'toolbar' )[0]; + node && node.focus( true ); + } + + editor.on( 'preinit', function() { + var DOM = tinymce.DOM; + var element; + var blockToolbar; + var blockToolbars = {}; + var blockSelection = false; + + tinymce.each( '123456'.split(''), function( level ) { + editor.addCommand( 'heading' + level, function() { + editor.formatter.apply( 'h' + level ); + editor.nodeChanged(); + } ); + + editor.addButton( 'heading' + level, { + text: level, + cmd: 'heading' + level, + onpostrender: function() { + var button = this; + + editor.on( 'nodechange', function( event ) { + button.active( element.nodeName === 'H' + level ); + } ); + } + } ); + } ); + + editor.addButton( 'heading', { + icon: 'gridicons-heading', + cmd: 'heading1' + } ); + + editor.addCommand( 'removeheading', function() { + editor.formatter.apply( 'p' ); + editor.nodeChanged(); + }); + + editor.addButton( 'removeheading', { + icon: 'gridicons-posts', + cmd: 'removeheading' + } ); + + editor.addCommand( 'preformatted', function() { + editor.formatter.apply( 'pre' ); + editor.nodeChanged(); + } ); + + editor.addButton( 'preformatted', { + icon: 'gridicons-code', + cmd: 'preformatted' + } ); + + editor.addCommand( 'removepreformatted', function() { + editor.formatter.remove( 'pre' ); + editor.nodeChanged(); + }); + + editor.addButton( 'removepreformatted', { + icon: 'gridicons-posts', + cmd: 'removepreformatted' + } ); + + editor.addButton( 'syntax', { + text: 'syntax', + onclick: function() {} + } ); + + editor.addCommand( 'removeblockquote', function() { + editor.formatter.remove( 'blockquote' ); + editor.nodeChanged(); + }); + + editor.addButton( 'removeblockquote', { + icon: 'gridicons-posts', + cmd: 'removeblockquote' + } ); + + editor.addCommand( 'alignleft', function() { + editor.formatter.remove( 'alignleft' ); + editor.formatter.remove( 'aligncenter' ); + editor.formatter.remove( 'alignright' ); + + editor.nodeChanged(); + }); + + editor.addButton( 'alignleft', { + icon: 'gridicons-align-left', + cmd: 'alignleft', + onpostrender: function() { + var button = this; + + editor.on( 'nodechange', function( event ) { + button.active( ! editor.formatter.matchNode( element, 'aligncenter' ) && + ! editor.formatter.matchNode( element, 'alignright' ) ); + } ); + } + } ); + + // Adjust icon of TinyMCE core buttons. + editor.buttons.aligncenter.icon = 'gridicons-align-center'; + editor.buttons.alignright.icon = 'gridicons-align-right'; + editor.buttons.blockquote.icon = 'gridicons-quote'; + editor.buttons.bullist.icon = 'gridicons-list-unordered'; + editor.buttons.numlist.icon = 'gridicons-list-ordered'; + + editor.addCommand( 'removelist', function() { + editor.selection.select( element ); + + if ( element.nodeName === 'UL' ) { + editor.execCommand( 'InsertUnorderedList' ); + } else if ( element.nodeName === 'OL' ) { + editor.execCommand( 'InsertOrderedList' ); + } + + editor.nodeChanged(); + }); + + editor.addButton( 'removelist', { + icon: 'gridicons-posts', + cmd: 'removelist' + } ); + + tinymce.each( [ 'left', 'center', 'right' ], function( position ) { + editor.addCommand( 'imgalign' + position, function() { + tinymce.each( [ 'left', 'center', 'right' ], function( position ) { + editor.formatter.remove( 'align' + position, element ); + } ); + + editor.formatter.apply( 'align' + position, element ); + + editor.nodeChanged(); + } ); + + editor.addButton( 'imgalign' + position, { + icon: 'gridicons-align-image-' + position, + cmd: 'imgalign' + position, + onPostRender: function() { + var button = this; + + editor.on( 'nodechange', function( event ) { + element = event.parents[ event.parents.length - 1 ]; + + button.active( editor.$( element ).hasClass( 'align' + position ) ); + } ); + } + } ); + } ); + + editor.addCommand( 'addfigcaption', function() { + if ( ! editor.$( element ).find( 'figcaption' ).length ) { + var figcaption = editor.$( '

    ' ); + + editor.undoManager.transact( function() { + editor.$( element ).append( figcaption ); + editor.selection.setCursorLocation( figcaption[0], 0 ); + } ); + } + } ); + + editor.addCommand( 'removefigcaption', function() { + var figcaption = editor.$( element ).find( 'figcaption' ); + + if ( figcaption.length ) { + editor.undoManager.transact( function() { + figcaption.remove(); + } ); + } + } ); + + editor.addCommand( 'togglefigcaption', function() { + if ( editor.$( element ).find( 'figcaption' ).length ) { + editor.execCommand( 'removefigcaption' ); + } else { + editor.execCommand( 'addfigcaption' ); + } + } ); + + editor.addButton( 'togglefigcaption', { + icon: 'gridicons-caption', + cmd: 'togglefigcaption', + onPostRender: function() { + var button = this; + + editor.on( 'nodechange', function( event ) { + element = event.parents[ event.parents.length - 1 ]; + + button.active( editor.$( element ).find( 'figcaption' ).length > 0 ); + } ); + } + } ); + + editor.addCommand( 'selectblock', function() { + editor.$( element ).attr( 'data-mce-selected', 'block' ); + editor.nodeChanged(); + + editor.once('click keydown', function ( event ) { + if ( tinymce.util.VK.modifierPressed( event ) ) { + return; + } + + editor.$('*[data-mce-selected="block"]').removeAttr('data-mce-selected'); + editor.nodeChanged(); + } ); + } ); + + // editor.addCommand( 'moveblock', function() { + // blockSelection = false; + + // } ); + + editor.addButton( 'moveblock', { + icon: 'gridicons-reblog', + onclick: function() { + editor.$( element ).attr( 'data-mce-selected', 'move' ); + editor.$( editor.getBody() ).addClass( 'is-moving-block' ); + editor.nodeChanged(); + } + } ); + + editor.addButton( 'block', { + icon: 'gridicons-posts', + tooltip: 'Add Block', + cmd: 'selectblock', + onPostRender: function() { + var button = this; + + editor.on( 'nodechange', function( event ) { + element = event.parents[ event.parents.length - 1 ]; + + tinymce.each( editor.settings.blocks, function( block, key ) { + if ( block.match( element ) ) { + button.icon( block.icon || '' ); + button.text( block.text || '' ); + } + } ); + } ); + } + }); + + editor.addCommand( 'removeblock', function() { + var $blocks = editor.$( '*[data-mce-selected="block"]' ).add( element ); + var p = editor.$( '


    ' ); + + editor.undoManager.transact( function() { + $blocks.first().before( p ); + editor.selection.setCursorLocation( p[0], 0 ); + $blocks.remove(); + } ); + + setTimeout( function() { + editor.$( p ).attr( 'data-mce-selected', null ); + } ); + } ); + + editor.addButton( 'removeblock', { + icon: 'gridicons-trash', + cmd: 'removeblock' + } ); + + editor.addCommand( 'up', function() { + $blocks = editor.$( '*[data-mce-selected="block"]' ).add( element ); + rect = element.getBoundingClientRect(); + $prev = $blocks.first().prev(); + + if ( $prev.length ) { + $blocks.last().after( $prev ); + editor.nodeChanged(); + window.scrollBy( 0, - rect.top + element.getBoundingClientRect().top ); + } + } ); + + editor.addCommand( 'down', function() { + $blocks = editor.$( '*[data-mce-selected="block"]' ).add( element ); + rect = element.getBoundingClientRect(); + $next = $blocks.last().next(); + + if ( $next.length ) { + $blocks.first().before( $next ); + editor.nodeChanged(); + window.scrollBy( 0, - rect.top + element.getBoundingClientRect().top ); + } + } ); + + editor.addButton( 'up', { + icon: 'gridicons-chevron-up', + tooltip: 'Up', + cmd: 'up', + classes: 'widget btn move-up' + } ); + + editor.addButton( 'down', { + icon: 'gridicons-chevron-down', + tooltip: 'Down', + cmd: 'down', + classes: 'widget btn move-down' + } ); + + blockToolbar = editor.wp._createToolbar( [ 'up', 'block', 'down' ] ) + + blockToolbar.$el.addClass('block-toolbar') + + blockToolbar.reposition = function () { + if (!element) return + + var toolbar = this.getEl() + var toolbarRect = toolbar.getBoundingClientRect() + var elementRect = element.getBoundingClientRect() + + var contentRect = editor.getBody().getBoundingClientRect(); + + DOM.setStyles(toolbar, { + position: 'absolute', + left: contentRect.left + 50 + 'px', + top: elementRect.top + window.pageYOffset + 'px' + } ); + + this.show() + } + + editor.on('blur', function () { + blockToolbar.hide() + } ); + + editor.on( 'nodechange', function( event ) { + var empty = ( + editor.dom.isEmpty( event.element ) && + ( event.element.nodeName === 'P' || ( + event.element.nodeName === 'BR' && + event.element.parentNode.nodeName === 'P' + ) ) + ); + + element = event.parents[ event.parents.length - 1 ]; + + if ( ! empty && editor.dom.isBlock( element ) ) { + blockToolbar.reposition(); + } else { + blockToolbar.hide(); + } + + if ( element.nodeName === 'FIGURE' ) { + editor.$( element ).attr( 'data-mce-selected', 'block' ); + + editor.once('click keydown', function ( event ) { + if ( tinymce.util.VK.modifierPressed( event ) ) { + return; + } + + editor.$('*[data-mce-selected="block"]').removeAttr('data-mce-selected'); + editor.nodeChanged(); + } ); + } + + var range = editor.selection.getRng() + + var $start = editor.$( editor.dom.getParent( range.startContainer, function( element ) { + return element.parentNode === editor.getBody(); + } ) ); + + var $end = editor.$( editor.dom.getParent( range.endContainer, function( element ) { + return element.parentNode === editor.getBody(); + } ) ); + + // Selection only has the start of a new block. + if ( $end[0] === range.endContainer && range.endOffset === 0 ) { + $end = $end.prev(); + } + + if ( ! empty && $start[0] !== $end[0] ) { + // $start.add( $start.nextUntil( $end ) ).add( $end ).attr( 'data-mce-selected', 'block' ); + + // editor.once('click keydown', function ( event ) { + // if ( tinymce.util.VK.modifierPressed( event ) ) { + // return; + // } + + // editor.$('*[data-mce-selected="block"]').removeAttr('data-mce-selected'); + // editor.nodeChanged(); + // } ); + + return; + } + + if ( ! empty && editor.$( element ).attr( 'data-mce-selected' ) === 'block' ) { + blockSelection = true; + + tinymce.each( editor.settings.blocks, function( block, key ) { + if ( block.match( element ) ) { + blockToolbars[ key ].reposition(); + + if ( element.nodeName !== 'FIGURE' ) { + focusToolbar( blockToolbars[ key ] ); + } + } else { + blockToolbars[ key ].hide(); + } + } ); + } else { + blockSelection = false; + + tinymce.each( editor.settings.blocks, function( block, key ) { + blockToolbars[ key ].hide(); + } ); + } + } ) + + tinymce.each( editor.settings.blocks, function( block, key ) { + blockToolbars[ key ] = editor.wp._createToolbar( block.buttons ); + blockToolbars[ key ].reposition = function () { + if (!element) return + + var toolbar = this.getEl() + var toolbarRect = toolbar.getBoundingClientRect() + var elementRect = element.getBoundingClientRect() + + var contentRect = editor.getBody().getBoundingClientRect(); + + DOM.setStyles(toolbar, { + position: 'absolute', + left: elementRect.left + 'px', + top: elementRect.top + window.pageYOffset - toolbarRect.height - 8 + 'px' + }) + + this.show() + } + + blockToolbars[ key ].on( 'keydown', function( event ) { + if ( event.keyCode === 27 ) { + editor.$('*[data-mce-selected="block"]').removeAttr('data-mce-selected'); + editor.nodeChanged(); + editor.focus(); + } + + // if ( event.keyCode === tinymce.util.VK.DOWN ) { + // editor.execCommand( 'down' ); + // event.preventDefault(); + // event.stopImmediatePropagation(); + // } + + // if ( event.keyCode === tinymce.util.VK.UP ) { + // editor.execCommand( 'up' ); + // event.preventDefaultntDefault(); + // event.stopImmediatePropagation(); + // } + }, true ); + } ); + + editor.on( 'beforeexeccommand', function( event ) { + var block = blockSelection; + + editor.once( 'nodechange', function( event ) { + setTimeout( function() { + if ( block ) { + editor.$('*[data-mce-selected="block"]').removeAttr('data-mce-selected'); + editor.$( element ).attr( 'data-mce-selected', 'block' ); + editor.nodeChanged(); + } + } ); + }, true ); + } ); + + var prevEmpty; + + // Throttle? + editor.on( 'keyup', function() { + var empty = editor.dom.isEmpty( element ); + + if ( ( empty && ! prevEmpty ) || ( ! empty && prevEmpty ) ) { + editor.nodeChanged() + } + + prevEmpty = empty; + } ); + + editor.on( 'keydown', function( event ) { + if ( editor.$( element ).attr( 'data-mce-selected' ) === 'block' ) { + if ( event.keyCode === tinymce.util.VK.DOWN ) { + editor.execCommand( 'down' ); + event.preventDefault(); + } + + if ( event.keyCode === tinymce.util.VK.UP ) { + editor.execCommand( 'up' ); + event.preventDefault(); + } + } + } ); + + var metaCount = 0; + + editor.on( 'keydown', function( event ) { + var keyCode = event.keyCode; + var VK = tinymce.util.VK; + + if ( element.nodeName === 'FIGURE' ) { + if ( keyCode === VK.ENTER ) { + editor.execCommand( 'addfigcaption' ); + event.preventDefault(); + } + + if ( keyCode === VK.BACKSPACE ) { + var caretEl = editor.selection.getNode(); + + if ( caretEl.nodeName !== 'FIGCAPTION' ) { + editor.execCommand( 'removeblock' ); + event.preventDefault(); + } else { + var range = editor.selection.getRng(); + + if ( range.collapsed && range.startOffset === 0 ) { + editor.execCommand( 'removefigcaption' ); + event.preventDefault(); + } + } + } + + if ( keyCode === VK.LEFT ) { + var range = editor.selection.getRng(); + + if ( keyCode === VK.LEFT && range.startOffset === 0 ) { + event.preventDefault(); + } + } + } + + if ( VK.metaKeyPressed( event ) ) { + metaCount ++; + } else { + metaCount = 0; + } + + if ( keyCode === 27 ) { + editor.$('*[data-mce-selected="block"]').removeAttr('data-mce-selected'); + editor.nodeChanged(); + } + }, true ); + + editor.on( 'keyup', function( event ) { + if ( metaCount === 1 ) { + editor.execCommand( 'selectblock' ); + } + + metaCount = 0; + } ); + + editor.on( 'dragstart', function( event ) { + if ( element.nodeName === 'FIGURE' ) { + event.preventDefault(); + } + } ); + } ); + + editor.on( 'init', function() { + editor.focus(); + } ); + } ); +} )( window.tinymce ); diff --git a/tinymce-single/tinymce/config.js b/tinymce-single/tinymce/config.js new file mode 100644 index 0000000000000..5d4b62f5cce32 --- /dev/null +++ b/tinymce-single/tinymce/config.js @@ -0,0 +1,111 @@ +window.tinymce.init( { + browser_spellcheck: true, + // Enter twice in a nested block creates a fresh paragraph. + end_container_on_empty_block: true, + inline: true, + // Enter creates a fresh paragraph. + keep_styles: false, + menubar: false, + object_resizing: false, + plugins: [ + 'block', + 'new', + 'formatting', + 'clean-paste', + 'lists', + 'paste', + 'toolbar', + 'wplink', + 'wptextpattern' + ], + schema: 'html5-strict', + selector: '#editor', + theme: false, + toolbar: false, + formats: { + alignleft: [ + { + selector: 'p,h1,h2,h3,h4,h5,h6', + styles: { textAlign: 'left' } + }, + { + selector: 'figure', + classes: 'alignleft' + } + ], + aligncenter: [ + { + selector: 'p,h1,h2,h3,h4,h5,h6', + styles: { textAlign: 'center' } + }, + { + selector: 'figure', + classes: 'aligncenter' + } + ], + alignright: [ + { + selector: 'p,h1,h2,h3,h4,h5,h6', + styles: { textAlign: 'right' } + }, + { + selector: 'figure', + classes: 'alignright' + } + ], + strikethrough: { inline: 'del' } + }, + blocks: { + paragraph: { + match: function( element ) { + return element.nodeName === 'P'; + }, + buttons: [ 'alignleft', 'aligncenter', 'alignright', 'heading', 'blockquote', 'bullist', 'preformatted', 'removeblock' ], + icon: 'gridicons-posts' + }, + heading: { + match: function( element ) { + var nodeName = element.nodeName; + + return ( + nodeName === 'H1' || + nodeName === 'H2' || + nodeName === 'H3' || + nodeName === 'H4' || + nodeName === 'H5' || + nodeName === 'H6' + ); + }, + buttons: [ 'alignleft', 'aligncenter', 'alignright', 'heading1', 'heading2', 'heading3', 'heading4', 'heading5', 'heading6', 'removeheading', 'removeblock' ], + icon: 'gridicons-heading' + }, + list: { + match: function( element ) { + return element.nodeName === 'UL' || element.nodeName === 'OL'; + }, + buttons: [ 'bullist', 'numlist', 'removelist', 'removeblock' ], + icon: 'gridicons-list-unordered' + }, + image: { + match: function( element ) { + return element.nodeName === 'FIGURE'; + }, + buttons: [ 'imgalignleft', 'imgaligncenter', 'imgalignright', 'togglefigcaption', 'removeblock' ], + icon: 'gridicons-image' + }, + blockquote: { + match: function( element ) { + return element.nodeName === 'BLOCKQUOTE'; + }, + buttons: [ 'removeblockquote', 'removeblock' ], + icon: 'gridicons-quote' + }, + preformatted: { + match: function( element ) { + return element.nodeName === 'PRE'; + }, + buttons: [ 'syntax', 'removepreformatted', 'removeblock' ], + icon: 'gridicons-code' + } + } +} ); diff --git a/tinymce-single/tinymce/formatting.js b/tinymce-single/tinymce/formatting.js new file mode 100644 index 0000000000000..63ef381c3ef0e --- /dev/null +++ b/tinymce-single/tinymce/formatting.js @@ -0,0 +1,108 @@ +( function( tinymce ) { + tinymce.PluginManager.add( 'formatting', function( editor ) { + var each = tinymce.each + var DOM = tinymce.DOM + + editor.on( 'preinit', function() { + // Adjust icon of TinyMCE core buttons. + editor.buttons.bold.icon = 'gridicons-bold'; + editor.buttons.italic.icon = 'gridicons-italic'; + editor.buttons.strikethrough.icon = 'gridicons-strikethrough'; + editor.buttons.link.icon = 'gridicons-link'; + } ); + + editor.on('focus', function () { + if (editor.wp && editor.wp._createToolbar) { + var element + var toolbarInline = editor.wp._createToolbar( [ 'bold', 'italic', 'strikethrough', 'link' ] ) + var toolbarMulti = editor.wp._createToolbar( [ 'alignleft', 'aligncenter', 'alignright', 'blockquote', 'bullist', 'removeblock' ] ) + + toolbarMulti.reposition = function () { + if (!element) return + + var toolbar = this.getEl() + var toolbarRect = toolbar.getBoundingClientRect() + var elementRect = element.getBoundingClientRect() + + var contentRect = document.getElementById( 'content' ).getBoundingClientRect(); + + DOM.setStyles(toolbar, { + position: 'absolute', + left: elementRect.left + 'px', + top: elementRect.top + window.pageYOffset - toolbarRect.height - 8 + 'px' + }) + + this.show() + } + + editor.on('wptoolbar', function (event) { + element = event.element + range = event.range + + var content = editor.selection.getContent() + var parent = editor.dom.getParent(range.startContainer, '*[data-mce-selected="block"]') + + // No collapsed selection. + if (range.collapsed) { + return + } + + // No non editable elements. + if ( + element.getAttribute('contenteditable') === 'false' || + element.getAttribute('data-mce-bogus') === 'all' + ) { + return + } + + // No images. + if (element.nodeName === 'IMG') { + return + } + + // No horizontal rules. + if (element.nodeName === 'HR') { + return + } + + // No links. + if (element.nodeName === 'A') { + return + } + + // No empty selection. + if (!content.replace(/<[^>]+>/g, '').replace(/(?:\s| )/g, '')) { + return + } + + // Block where the selection starts. + var $start = editor.$( editor.dom.getParent( range.startContainer, editor.dom.isBlock ) ); + // Block where the selection ends. + var $end = editor.$( editor.dom.getParent( range.endContainer, editor.dom.isBlock ) ); + + // Selection end only has the start of a new block. + if ( $end[0] === range.endContainer && range.endOffset === 0 ) { + $end = $end.prev(); + } + + // Start and end blocks are not the same. + if ( $start[0] !== $end[0] ) { + event.toolbar = toolbarMulti + event.selection = range + } else if ( ! parent ) { + event.toolbar = toolbarInline + event.selection = range + } + + // Click inside selection does not trigger nodechange. + editor.once( 'click', function ( event ) { + window.setTimeout( function() { + editor.$('*[data-mce-selected="block"]').removeAttr('data-mce-selected'); + editor.nodeChanged(); + } ); + } ); + }) + } + }) + } ); +} )( window.tinymce ); diff --git a/tinymce-single/tinymce/new.js b/tinymce-single/tinymce/new.js new file mode 100644 index 0000000000000..f016d7740a220 --- /dev/null +++ b/tinymce-single/tinymce/new.js @@ -0,0 +1,56 @@ +( function( tinymce ) { + tinymce.PluginManager.add( 'new', function( editor ) { + var DOM = tinymce.DOM; + + editor.addButton( 'add', { + icon: 'gridicons-add-outline', + tooltip: 'Add Block' + } ); + + editor.on('focus', function () { + if (editor.wp && editor.wp._createToolbar) { + var element + var toolbarCaret = editor.wp._createToolbar( [ 'add' ] ) + + toolbarCaret.$el.addClass('block-toolbar') + + toolbarCaret.reposition = function () { + if (!element) return + + var toolbar = this.getEl() + var toolbarRect = toolbar.getBoundingClientRect() + var elementRect = element.getBoundingClientRect() + + var contentRect = editor.getBody().getBoundingClientRect(); + + DOM.setStyles(toolbar, { + position: 'absolute', + left: contentRect.left + 50 + 'px', + top: elementRect.top + window.pageYOffset + 'px' + // top: elementRect.top + window.pageYOffset + elementRect.height / 2 - toolbarRect.height / 2 + 'px' + }) + + this.show() + } + + editor.on('blur', function () { + toolbarCaret.hide() + }) + + editor.on('wptoolbar', function (event) { + element = event.element + range = event.range + + // No collapsed selection. + if (range.collapsed) { + if ( event.empty ) { + event.toolbar = toolbarCaret + } + + return + } + } ); + } + } ); + } ); +} )( window.tinymce );