diff --git a/wp-admin/edit-form-advanced.php b/wp-admin/edit-form-advanced.php index 50796814a6..2db7e90c5f 100644 --- a/wp-admin/edit-form-advanced.php +++ b/wp-admin/edit-form-advanced.php @@ -628,6 +628,7 @@ if ( post_type_supports($post_type, 'editor') ) { 'resize' => false, 'wp_autoresize_on' => $_wp_editor_expand, 'add_unload_trigger' => false, + 'wp_keep_scroll_position' => true, ), ) ); ?> diff --git a/wp-admin/js/editor.js b/wp-admin/js/editor.js index 0e47b3e022..cabde4f6b8 100644 --- a/wp-admin/js/editor.js +++ b/wp-admin/js/editor.js @@ -99,8 +99,18 @@ window.wp = window.wp || {}; editorHeight = parseInt( textarea.style.height, 10 ) || 0; - // Save the selection - addHTMLBookmarkInTextAreaContent( $textarea, $ ); + var keepSelection = false; + if ( editor ) { + keepSelection = editor.getParam( 'wp_keep_scroll_position' ) + } else { + keepSelection = window.tinyMCEPreInit.mceInit[ id ] && + window.tinyMCEPreInit.mceInit[ id ]['wp_keep_scroll_position'] + } + + if ( keepSelection ) { + // Save the selection + addHTMLBookmarkInTextAreaContent( $textarea ); + } if ( editor ) { editor.show(); @@ -116,8 +126,10 @@ window.wp = window.wp || {}; } } - // Restore the selection - focusHTMLBookmarkInVisualEditor( editor ); + if ( editor.getParam( 'wp_keep_scroll_position' ) ) { + // Restore the selection + focusHTMLBookmarkInVisualEditor( editor ); + } } else { tinymce.init( window.tinyMCEPreInit.mceInit[ id ] ); } @@ -132,7 +144,6 @@ window.wp = window.wp || {}; return false; } - var selectionRange = null; if ( editor ) { // Don't resize the textarea in iOS. The iframe is forced to 100% height there, we shouldn't match it. if ( ! tinymce.Env.iOS ) { @@ -150,7 +161,11 @@ window.wp = window.wp || {}; } } - selectionRange = findBookmarkedPosition( editor ); + var selectionRange = null; + + if ( editor.getParam( 'wp_keep_scroll_position' ) ) { + selectionRange = findBookmarkedPosition( editor ); + } editor.hide(); @@ -234,9 +249,13 @@ window.wp = window.wp || {}; function getShortcodeWrapperInfo( content, cursorPosition ) { var contentShortcodes = getShortCodePositionsInText( content ); - return _.find( contentShortcodes, function( element ) { - return cursorPosition >= element.startIndex && cursorPosition <= element.endIndex; - } ); + for ( var i = 0; i < contentShortcodes.length; i++ ) { + var element = contentShortcodes[ i ]; + + if ( cursorPosition >= element.startIndex && cursorPosition <= element.endIndex ) { + return element; + } + } } /** @@ -245,13 +264,20 @@ window.wp = window.wp || {}; * @param {string} content The content we want to scan for shortcodes. */ function getShortcodesInText( content ) { - var shortcodes = content.match( /\[+([\w_-])+/g ); + var shortcodes = content.match( /\[+([\w_-])+/g ), + result = []; - return _.uniq( - _.map( shortcodes, function( element ) { - return element.replace( /^\[+/g, '' ); - } ) - ); + if ( shortcodes ) { + for ( var i = 0; i < shortcodes.length; i++ ) { + var shortcode = shortcodes[ i ].replace( /^\[+/g, '' ); + + if ( result.indexOf( shortcode ) === -1 ) { + result.push( shortcode ); + } + } + } + + return result; } /** @@ -335,6 +361,34 @@ window.wp = window.wp || {}; shortcodesDetails.push( shortcodeInfo ); } + /** + * Get all URL matches, and treat them as embeds. + * + * Since there isn't a good way to detect if a URL by itself on a line is a previewable + * object, it's best to treat all of them as such. + * + * This means that the selection will capture the whole URL, in a similar way shrotcodes + * are treated. + */ + var urlRegexp = new RegExp( + '(^|[\\n\\r][\\n\\r]|

)(https?:\\/\\/[^\s"]+?)(<\\/p>\s*|[\\n\\r][\\n\\r]|$)', 'gi' + ); + + while ( shortcodeMatch = urlRegexp.exec( content ) ) { + shortcodeInfo = { + shortcodeName: 'url', + showAsPlainText: false, + startIndex: shortcodeMatch.index, + endIndex: shortcodeMatch.index + shortcodeMatch[ 0 ].length, + length: shortcodeMatch[ 0 ].length, + isPreviewable: true, + urlAtStartOfContent: shortcodeMatch[ 1 ] === '', + urlAtEndOfContent: shortcodeMatch[ 3 ] === '' + }; + + shortcodesDetails.push( shortcodeInfo ); + } + return shortcodesDetails; } @@ -399,8 +453,7 @@ window.wp = window.wp || {}; */ if ( voidElements.indexOf( isCursorStartInTag.tagType ) !== -1 ) { cursorStart = isCursorStartInTag.ltPos; - } - else { + } else { cursorStart = isCursorStartInTag.gtPos; } } @@ -412,12 +465,28 @@ window.wp = window.wp || {}; var isCursorStartInShortcode = getShortcodeWrapperInfo( content, cursorStart ); if ( isCursorStartInShortcode && isCursorStartInShortcode.isPreviewable ) { - cursorStart = isCursorStartInShortcode.startIndex; + /** + * If a URL is at the start or the end of the content, + * the selection doesn't work, because it inserts a marker in the text, + * which breaks the embedURL detection. + * + * The best way to avoid that and not modify the user content is to + * adjust the cursor to either after or before URL. + */ + if ( isCursorStartInShortcode.urlAtStartOfContent ) { + cursorStart = isCursorStartInShortcode.endIndex; + } else { + cursorStart = isCursorStartInShortcode.startIndex; + } } var isCursorEndInShortcode = getShortcodeWrapperInfo( content, cursorEnd ); if ( isCursorEndInShortcode && isCursorEndInShortcode.isPreviewable ) { - cursorEnd = isCursorEndInShortcode.endIndex; + if ( isCursorEndInShortcode.urlAtEndOfContent ) { + cursorEnd = isCursorEndInShortcode.startIndex; + } else { + cursorEnd = isCursorEndInShortcode.endIndex; + } } return { @@ -455,7 +524,7 @@ window.wp = window.wp || {}; mode = htmlModeCursorStartPosition !== htmlModeCursorEndPosition ? 'range' : 'single', selectedText = null, - cursorMarkerSkeleton = getCursorMarkerSpan( $$, '' ); + cursorMarkerSkeleton = getCursorMarkerSpan( $$, '' ).attr( 'data-mce-type','bookmark' ); if ( mode === 'range' ) { var markedText = textArea.value.slice( htmlModeCursorStartPosition, htmlModeCursorEndPosition ), @@ -470,7 +539,7 @@ window.wp = window.wp || {}; textArea.value = [ textArea.value.slice( 0, htmlModeCursorStartPosition ), // text until the cursor/selection position cursorMarkerSkeleton.clone() // cursor/selection start marker - .addClass( 'mce_SELRES_start')[0].outerHTML, + .addClass( 'mce_SELRES_start' )[0].outerHTML, selectedText, // selected text with end cursor/position marker textArea.value.slice( htmlModeCursorEndPosition ) // text from last cursor/selection position to end ].join( '' ); @@ -487,8 +556,8 @@ window.wp = window.wp || {}; * @param {Object} editor TinyMCE editor instance. */ function focusHTMLBookmarkInVisualEditor( editor ) { - var startNode = editor.$( '.mce_SELRES_start' ), - endNode = editor.$( '.mce_SELRES_end' ); + var startNode = editor.$( '.mce_SELRES_start' ).attr( 'data-mce-bogus', 1 ), + endNode = editor.$( '.mce_SELRES_end' ).attr( 'data-mce-bogus', 1 ); if ( startNode.length ) { editor.focus(); @@ -505,8 +574,9 @@ window.wp = window.wp || {}; } } - scrollVisualModeToStartElement( editor, startNode ); - + if ( editor.getParam( 'wp_keep_scroll_position' ) ) { + scrollVisualModeToStartElement( editor, startNode ); + } removeSelectionMarker( startNode ); removeSelectionMarker( endNode ); @@ -548,13 +618,18 @@ window.wp = window.wp || {}; var elementTop = editor.$( element ).offset().top, TinyMCEContentAreaTop = editor.$( editor.getContentAreaContainer() ).offset().top, - edTools = $( '#wp-content-editor-tools' ), - edToolsHeight = edTools.height(), - edToolsOffsetTop = edTools.offset().top, - toolbarHeight = getToolbarHeight( editor ), - windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight, + edTools = $( '#wp-content-editor-tools' ), + edToolsHeight = 0, + edToolsOffsetTop = 0; + + if ( edTools.length ) { + edToolsHeight = edTools.height(); + edToolsOffsetTop = edTools.offset().top; + } + + var windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight, selectionPosition = TinyMCEContentAreaTop + elementTop, visibleAreaHeight = windowHeight - ( edToolsHeight + toolbarHeight ); @@ -675,8 +750,8 @@ window.wp = window.wp || {}; * This way we can adjust the selection to properly select only the content, ignoring * whitespace inserted around the selected object by the Editor. */ - startElement.attr('data-mce-object-selection', 'true'); - endElement.attr('data-mce-object-selection', 'true'); + startElement.attr( 'data-mce-object-selection', 'true' ); + endElement.attr( 'data-mce-object-selection', 'true' ); editor.$( startNode ).before( startElement[0] ); editor.$( startNode ).after( endElement[0] ); diff --git a/wp-admin/js/editor.min.js b/wp-admin/js/editor.min.js index 2f38355d82..b420cad561 100644 --- a/wp-admin/js/editor.min.js +++ b/wp-admin/js/editor.min.js @@ -1 +1 @@ -window.wp=window.wp||{},function(a,b){function c(){function c(){!x&&window.tinymce&&(x=window.tinymce,y=x.$,y(document).on("click",function(a){var b,c,d=y(a.target);d.hasClass("wp-switch-editor")&&(b=d.attr("data-wp-editor-id"),c=d.hasClass("switch-tmce")?"tmce":"html",e(b,c))}))}function d(a){var b=y(".mce-toolbar-grp",a.getContainer())[0],c=b&&b.clientHeight;return c&&c>10&&c<200?parseInt(c,10):30}function e(b,c){b=b||"content",c=c||"toggle";var e,f,g,h=x.get(b),i=y("#wp-"+b+"-wrap"),j=y("#"+b),k=j[0];if("toggle"===c&&(c=h&&!h.isHidden()?"html":"tmce"),"tmce"===c||"tinymce"===c){if(h&&!h.isHidden())return!1;"undefined"!=typeof window.QTags&&window.QTags.closeAllTags(b),e=parseInt(k.style.height,10)||0,m(j,a),h?(h.show(),!x.Env.iOS&&e&&(f=d(h),e=e-f+14,e>50&&e<5e3&&h.theme.resizeTo(null,e)),n(h)):x.init(window.tinyMCEPreInit.mceInit[b]),i.removeClass("html-active").addClass("tmce-active"),j.attr("aria-hidden",!0),window.setUserSetting("editor","tinymce")}else if("html"===c){if(h&&h.isHidden())return!1;var l=null;h?(x.Env.iOS||(g=h.iframeElement,e=g?parseInt(g.style.height,10):0,e&&(f=d(h),e=e+f-14,e>50&&e<5e3&&(k.style.height=e+"px"))),l=r(h),h.hide(),l&&s(h,l)):j.css({display:"",visibility:""}),i.removeClass("tmce-active").addClass("html-active"),j.attr("aria-hidden",!1),window.setUserSetting("editor","html")}}function f(a,b){var c=a.lastIndexOf("<",b-1),d=a.lastIndexOf(">",b);if(c>d||">"===a.substr(b,1)){var e=a.substr(c),f=e.match(/<\s*(\/)?(\w+)/);if(!f)return null;var g=f[2],h=e.indexOf(">");return{ltPos:c,gtPos:c+h+1,tagType:g,isClosingTag:!!f[1]}}return null}function g(a,b){var c=j(a);return _.find(c,function(a){return b>=a.startIndex&&b<=a.endIndex})}function h(a){var b=a.match(/\[+([\w_-])+/g);return _.uniq(_.map(b,function(a){return a.replace(/^\[+/g,"")}))}function i(a){var c=["caption"];return c.indexOf(a)!==-1||void 0!==b.mce.views.get(a)}function j(a){var c=h(a);if(0===c.length)return[];for(var d,e=b.shortcode.regexp(c.join("|")),f=[];d=e.exec(a);){var g="["===d[1],j=!g&&i(d[2]),k={shortcodeName:d[2],showAsPlainText:g,startIndex:d.index,endIndex:d.index+d[0].length,length:d[0].length,isPreviewable:j};f.push(k)}return f}function k(a,b){return a("").css({display:"inline-block",width:0,overflow:"hidden","line-height":0}).html(b?b:"")}function l(a,b){var c=["area","base","br","col","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"],d=b.cursorStart,e=b.cursorEnd,h=f(a,d);h&&(d=c.indexOf(h.tagType)!==-1?h.ltPos:h.gtPos);var i=f(a,e);i&&(e=i.gtPos);var j=g(a,d);j&&j.isPreviewable&&(d=j.startIndex);var k=g(a,e);return k&&k.isPreviewable&&(e=k.endIndex),{cursorStart:d,cursorEnd:e}}function m(a){if(a&&a.length){var b=a[0],c=b.value,d=l(c,{cursorStart:b.selectionStart,cursorEnd:b.selectionEnd}),e=d.cursorStart,f=d.cursorEnd,g=e!==f?"range":"single",h=null,i=k(y,"");if("range"===g){var j=b.value.slice(e,f),m=i.clone().addClass("mce_SELRES_end");h=[j,m[0].outerHTML].join("")}b.value=[b.value.slice(0,e),i.clone().addClass("mce_SELRES_start")[0].outerHTML,h,b.value.slice(f)].join("")}}function n(a){var b=a.$(".mce_SELRES_start"),c=a.$(".mce_SELRES_end");if(b.length)if(a.focus(),c.length){var d=a.getDoc().createRange();d.setStartAfter(b[0]),d.setEndBefore(c[0]),a.selection.setRng(d)}else a.selection.select(b[0]);p(a,b),o(b),o(c)}function o(a){var b=a.parent();a.remove(),!b.is("p")||b.children().length||b.text()||b.remove()}function p(b,c){var e=b.$(c).offset().top,f=b.$(b.getContentAreaContainer()).offset().top,g=a("#wp-content-editor-tools"),h=g.height(),i=g.offset().top,j=d(b),k=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight,l=f+e,m=k-(h+j),n=Math.max(l-m/2,i-h);a("html,body").animate({scrollTop:parseInt(n,10)},100)}function q(a){a.content=a.content.replace(/

(?:
|\u00a0|\uFEFF| )*<\/p>/g,"

 

")}function r(a){var b=a.getWin(),c=b.getSelection();if(!(c.rangeCount<=0)){var d="SELRES_"+Math.random(),e=k(a.$,d),f=e.clone().addClass("mce_SELRES_start"),g=e.clone().addClass("mce_SELRES_end"),h=c.getRangeAt(0),i=h.startContainer,j=h.startOffset,l=h.cloneRange();a.$(i).parents(".mce-offscreen-selection").length>0?(i=a.$("[data-mce-selected]")[0],f.attr("data-mce-object-selection","true"),g.attr("data-mce-object-selection","true"),a.$(i).before(f[0]),a.$(i).after(g[0])):(l.collapse(!1),l.insertNode(g[0]),l.setStart(i,j),l.collapse(!0),l.insertNode(f[0]),h.setStartAfter(f[0]),h.setEndBefore(g[0]),c.removeAllRanges(),c.addRange(h)),a.on("GetContent",q);var m=t(a.getContent());a.off("GetContent",q),f.remove(),g.remove();var n=new RegExp(']*\\s*class="mce_SELRES_start"[^>]+>\\s*'+d+"[^<]*<\\/span>(\\s*)"),o=new RegExp('(\\s*)]*\\s*class="mce_SELRES_end"[^>]+>\\s*'+d+"[^<]*<\\/span>"),p=m.match(n),r=m.match(o);if(!p)return null;var s=p.index,u=p[0].length,v=null;if(r){p[0].indexOf("data-mce-object-selection")!==-1&&(u-=p[1].length);var w=r.index;r[0].indexOf("data-mce-object-selection")!==-1&&(w-=r[1].length),v=w-u}return{start:s,end:v}}}function s(a,b){if(b){var c=a.getElement(),d=b.start,e=b.end||b.start;c.focus&&(setTimeout(function(){c.blur&&c.blur(),c.focus()},100),c.focus()),c.setSelectionRange(d,e)}}function t(a){var b="blockquote|ul|ol|li|dl|dt|dd|table|thead|tbody|tfoot|tr|th|td|h[1-6]|fieldset|figure",c=b+"|div|p",d=b+"|pre",e=!1,f=!1,g=[];return a?(a.indexOf("]*>[\s\S]*?<\/\1>/g,function(a){return g.push(a),""})),a.indexOf("]*>[\s\S]+?<\/pre>/g,function(a){return a=a.replace(/
(\r\n|\n)?/g,""),a=a.replace(/<\/?p( [^>]*)?>(\r\n|\n)?/g,""),a.replace(/\r?\n/g,"")})),a.indexOf("[caption")!==-1&&(f=!0,a=a.replace(/\[caption[\s\S]+?\[\/caption\]/g,function(a){return a.replace(/]*)>/g,"").replace(/[\r\n\t]+/,"")})),a=a.replace(new RegExp("\\s*\\s*","g"),"\n"),a=a.replace(new RegExp("\\s*<((?:"+c+")(?: [^>]*)?)>","g"),"\n<$1>"),a=a.replace(/(

]+>.*?)<\/p>/g,"$1"),a=a.replace(/]*)?>\s*

/gi,"\n\n"),a=a.replace(/\s*

/gi,""),a=a.replace(/\s*<\/p>\s*/gi,"\n\n"),a=a.replace(/\n[\s\u00a0]+\n/g,"\n\n"),a=a.replace(/(\s*)
\s*/gi,function(a,b){return b&&b.indexOf("\n")!==-1?"\n\n":"\n"}),a=a.replace(/\s*

\s*/g,"
\n"),a=a.replace(/\s*\[caption([^\[]+)\[\/caption\]\s*/gi,"\n\n[caption$1[/caption]\n\n"),a=a.replace(/caption\]\n\n+\[caption/g,"caption]\n\n[caption"),a=a.replace(new RegExp("\\s*<((?:"+d+")(?: [^>]*)?)\\s*>","g"),"\n<$1>"),a=a.replace(new RegExp("\\s*\\s*","g"),"\n"),a=a.replace(/<((li|dt|dd)[^>]*)>/g," \t<$1>"),a.indexOf("/g,"\n")),a.indexOf("]*)?>\s*/g,"\n\n\n\n")),a.indexOf("/g,function(a){return a.replace(/[\r\n]+/g,"")})),a=a.replace(/<\/p#>/g,"

\n"),a=a.replace(/\s*(

]+>[\s\S]*?<\/p>)/g,"\n$1"),a=a.replace(/^\s+/,""),a=a.replace(/[\s\u00a0]+$/,""),e&&(a=a.replace(//g,"\n")),f&&(a=a.replace(/]*)>/g,"")),g.length&&(a=a.replace(//g,function(){return g.shift()})),a):""}function u(a){var b=!1,c=!1,d="table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary";return a=a.replace(/\r\n|\r/g,"\n"),a.indexOf("\n")===-1?a:(a.indexOf("/g,function(a){return a.replace(/\n+/g,"")})),a=a.replace(/<[^<>]+>/g,function(a){return a.replace(/[\n\t ]+/g," ")}),a.indexOf("]*>[\s\S]*?<\/\1>/g,function(a){return a.replace(/\n/g,"")})),a.indexOf("]*>)/g,"$1"),a=a.replace(/<\/figcaption>\s*/g,"")),a.indexOf("[caption")!==-1&&(c=!0,a=a.replace(/\[caption[\s\S]+?\[\/caption\]/g,function(a){return a=a.replace(/]*)>/g,""),a=a.replace(/<[^<>]+>/g,function(a){return a.replace(/[\n\t ]+/," ")}),a.replace(/\s*\n\s*/g,"")})),a+="\n\n",a=a.replace(/
\s*
/gi,"\n\n"),a=a.replace(new RegExp("(<(?:"+d+")(?: [^>]*)?>)","gi"),"\n\n$1"),a=a.replace(new RegExp("()","gi"),"$1\n\n"),a=a.replace(/]*)?>/gi,"\n\n"),a=a.replace(/\s*"),a=a.replace(/\n\s*\n+/g,"\n\n"),a=a.replace(/([\s\S]+?)\n\n/g,"

$1

\n"),a=a.replace(/

\s*?<\/p>/gi,""),a=a.replace(new RegExp("

\\s*(]*)?>)\\s*

","gi"),"$1"),a=a.replace(/

(/gi,"$1"),a=a.replace(/

\s*]*)>/gi,"

"),a=a.replace(/<\/blockquote>\s*<\/p>/gi,"

"),a=a.replace(new RegExp("

\\s*(]*)?>)","gi"),"$1"),a=a.replace(new RegExp("(]*)?>)\\s*

","gi"),"$1"),a=a.replace(/(]*>)\s*\n/gi,"$1"),a=a.replace(/\s*\n/g,"
\n"),a=a.replace(new RegExp("(]*>)\\s*
","gi"),"$1"),a=a.replace(/
(\s*<\/?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)>)/gi,"$1"),a=a.replace(/(?:

|
)*\s*\[caption([^\[]+)\[\/caption\]\s*(?:<\/p>|
)*/gi,"[caption$1[/caption]"),a=a.replace(/(<(?:div|th|td|form|fieldset|dd)[^>]*>)(.*?)<\/p>/g,function(a,b,c){return c.match(/]*)?>/)?a:b+"

"+c+"

"}),b&&(a=a.replace(//g,"\n")),c&&(a=a.replace(/]*)>/g,"")),a)}function v(b){var c={o:z,data:b,unfiltered:b};return a&&a("body").trigger("beforePreWpautop",[c]),c.data=t(c.data),a&&a("body").trigger("afterPreWpautop",[c]),c.data}function w(b){var c={o:z,data:b,unfiltered:b};return a&&a("body").trigger("beforeWpautop",[c]),c.data=u(c.data),a&&a("body").trigger("afterWpautop",[c]),c.data}var x,y,z={};return a(document).on("tinymce-editor-init.keep-scroll-position",function(a,b){b.$(".mce_SELRES_start").length&&n(b)}),a?a(document).ready(c):document.addEventListener?(document.addEventListener("DOMContentLoaded",c,!1),window.addEventListener("load",c,!1)):window.attachEvent&&(window.attachEvent("onload",c),document.attachEvent("onreadystatechange",function(){"complete"===document.readyState&&c()})),b.editor.autop=w,b.editor.removep=v,z={go:e,wpautop:w,pre_wpautop:v,_wp_Autop:u,_wp_Nop:t}}b.editor=b.editor||{},window.switchEditors=new c,b.editor.initialize=function(c,d){var e,f;if(a&&c&&b.editor.getDefaultSettings){if(f=b.editor.getDefaultSettings(),d||(d={tinymce:!0}),d.tinymce&&d.quicktags){var g=a("#"+c),h=a("
").attr({"class":"wp-core-ui wp-editor-wrap tmce-active",id:"wp-"+c+"-wrap"}),i=a('
'),j=a("