mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-15 02:23:58 +00:00
Pass visibleSection & visibleSectionOffset to target
* Find the first section below the top of the viewport (usually visible) and measure its offset. * After loading the editor, ensure this heading is still at the same position on the page. Bug: T296910 Change-Id: I9a05ea74ba3c19a4a91ddc1bc0afe311851c53e6
This commit is contained in:
parent
e1b9e6a98e
commit
c0f3fc3a78
|
@ -51,6 +51,8 @@ ve.init.mw.ArticleTarget = function VeInitMwArticleTarget( config ) {
|
|||
// A workaround, as default URI does not get updated after pushState (T74334)
|
||||
this.currentUri = new mw.Uri( location.href );
|
||||
this.section = null;
|
||||
this.visibleSection = null;
|
||||
this.visibleSectionOffset = null;
|
||||
this.sectionTitle = null;
|
||||
this.editSummaryValue = null;
|
||||
this.initialEditSummary = null;
|
||||
|
@ -1023,6 +1025,8 @@ ve.init.mw.ArticleTarget.prototype.clearState = function () {
|
|||
this.originalHtml = null;
|
||||
this.toolbarSaveButton = null;
|
||||
this.section = null;
|
||||
this.visibleSection = null;
|
||||
this.visibleSectionOffset = null;
|
||||
this.editNotices = [];
|
||||
this.remoteNotices = [];
|
||||
this.localNoticeMessages = [];
|
||||
|
@ -1880,62 +1884,76 @@ ve.init.mw.ArticleTarget.prototype.getSaveDialogOpeningData = function () {
|
|||
* Do nothing if this.section is undefined.
|
||||
*/
|
||||
ve.init.mw.ArticleTarget.prototype.restoreEditSection = function () {
|
||||
var section = this.section,
|
||||
surface = this.getSurface(),
|
||||
mode = surface.getMode();
|
||||
var section = this.section !== null ? this.section : this.visibleSection;
|
||||
|
||||
if ( section !== null && section !== 'new' && section !== '0' && section !== 'T-0' ) {
|
||||
var headingText;
|
||||
if ( mode === 'visual' ) {
|
||||
var dmDoc = surface.getModel().getDocument();
|
||||
// In mw.libs.ve.unwrapParsoidSections we copy the data-mw-section-id from the section element
|
||||
// to the heading. Iterate over headings to find the one with the correct attribute
|
||||
// in originalDomElements.
|
||||
var headingModel;
|
||||
dmDoc.getNodesByType( 'mwHeading' ).some( function ( heading ) {
|
||||
var domElements = heading.getOriginalDomElements( dmDoc.getStore() );
|
||||
if (
|
||||
domElements && domElements[ 0 ].nodeType === Node.ELEMENT_NODE &&
|
||||
domElements[ 0 ].getAttribute( 'data-mw-section-id' ) === section
|
||||
) {
|
||||
headingModel = heading;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} );
|
||||
if ( headingModel ) {
|
||||
var headingView = surface.getView().getDocument().getDocumentNode().getNodeFromOffset( headingModel.getRange().start );
|
||||
if ( new mw.Uri().query.summary === undefined ) {
|
||||
headingText = headingView.$element.text();
|
||||
}
|
||||
if ( !this.enableVisualSectionEditing ) {
|
||||
this.goToHeading( headingView );
|
||||
}
|
||||
if ( this.enableVisualSectionEditing && this.section !== null ) {
|
||||
$( this.getElementWindow() ).scrollTop( 0 );
|
||||
}
|
||||
|
||||
if ( section === null || section === 'new' || section === '0' || section === 'T-0' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var surface = this.getSurface(),
|
||||
mode = surface.getMode(),
|
||||
setExactScrollOffset = this.section === null && this.visibleSection !== null && this.visibleSectionOffset !== null,
|
||||
// User clicked section edit link with visual section editing not available:
|
||||
// Take them to the top of the section using goToHeading
|
||||
goToStartOfHeading = this.section !== null && !this.enableVisualSectionEditing,
|
||||
setEditSummary = this.section !== null;
|
||||
|
||||
var headingText;
|
||||
if ( mode === 'visual' ) {
|
||||
var dmDoc = surface.getModel().getDocument();
|
||||
// In mw.libs.ve.unwrapParsoidSections we copy the data-mw-section-id from the section element
|
||||
// to the heading. Iterate over headings to find the one with the correct attribute
|
||||
// in originalDomElements.
|
||||
var headingModel;
|
||||
dmDoc.getNodesByType( 'mwHeading' ).some( function ( heading ) {
|
||||
var domElements = heading.getOriginalDomElements( dmDoc.getStore() );
|
||||
if (
|
||||
domElements && domElements[ 0 ].nodeType === Node.ELEMENT_NODE &&
|
||||
domElements[ 0 ].getAttribute( 'data-mw-section-id' ) === section
|
||||
) {
|
||||
headingModel = heading;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} );
|
||||
if ( headingModel ) {
|
||||
var headingView = surface.getView().getDocument().getDocumentNode().getNodeFromOffset( headingModel.getRange().start );
|
||||
if ( setEditSummary && new mw.Uri().query.summary === undefined ) {
|
||||
headingText = headingView.$element.text();
|
||||
}
|
||||
if ( setExactScrollOffset ) {
|
||||
this.scrollToHeading( headingView, this.visibleSectionOffset );
|
||||
} else if ( goToStartOfHeading ) {
|
||||
this.goToHeading( headingView );
|
||||
}
|
||||
} else if ( mode === 'source' ) {
|
||||
// With elements of extractSectionTitle + stripSectionName TODO:
|
||||
// Arguably, we should just throw this through the API and then do
|
||||
// the same extract-text pass we do in visual mode. Would save us
|
||||
// having to think about wikitext here.
|
||||
headingText = surface.getModel().getDocument().data.getText(
|
||||
false,
|
||||
surface.getModel().getDocument().getDocumentNode().children[ 0 ].getRange()
|
||||
)
|
||||
// Extract the title
|
||||
.replace( /^\s*=+\s*(.*?)\s*=+\s*$/, '$1' )
|
||||
// Remove links
|
||||
.replace( /\[\[:?([^[|]+)\|([^[]+)\]\]/g, '$2' )
|
||||
.replace( /\[\[:?([^[]+)\|?\]\]/g, '$1' )
|
||||
.replace( new RegExp( '\\[(?:' + ve.init.platform.getUnanchoredExternalLinkUrlProtocolsRegExp().source + ')([^ ]+?) ([^\\[]+)\\]', 'ig' ), '$3' )
|
||||
// Cheap HTML removal
|
||||
.replace( /<[^>]+?>/g, '' );
|
||||
}
|
||||
if ( headingText ) {
|
||||
this.initialEditSummary =
|
||||
'/* ' +
|
||||
ve.graphemeSafeSubstring( headingText, 0, 244 ) +
|
||||
' */ ';
|
||||
}
|
||||
} else if ( mode === 'source' && setEditSummary ) {
|
||||
// With elements of extractSectionTitle + stripSectionName TODO:
|
||||
// Arguably, we should just throw this through the API and then do
|
||||
// the same extract-text pass we do in visual mode. Would save us
|
||||
// having to think about wikitext here.
|
||||
headingText = surface.getModel().getDocument().data.getText(
|
||||
false,
|
||||
surface.getModel().getDocument().getDocumentNode().children[ 0 ].getRange()
|
||||
)
|
||||
// Extract the title
|
||||
.replace( /^\s*=+\s*(.*?)\s*=+\s*$/, '$1' )
|
||||
// Remove links
|
||||
.replace( /\[\[:?([^[|]+)\|([^[]+)\]\]/g, '$2' )
|
||||
.replace( /\[\[:?([^[]+)\|?\]\]/g, '$1' )
|
||||
.replace( new RegExp( '\\[(?:' + ve.init.platform.getUnanchoredExternalLinkUrlProtocolsRegExp().source + ')([^ ]+?) ([^\\[]+)\\]', 'ig' ), '$3' )
|
||||
// Cheap HTML removal
|
||||
.replace( /<[^>]+?>/g, '' );
|
||||
}
|
||||
if ( headingText ) {
|
||||
this.initialEditSummary =
|
||||
'/* ' +
|
||||
ve.graphemeSafeSubstring( headingText, 0, 244 ) +
|
||||
' */ ';
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1988,11 +2006,13 @@ ve.init.mw.ArticleTarget.prototype.goToHeading = function ( headingNode ) {
|
|||
* Scroll to a given heading in the document.
|
||||
*
|
||||
* @param {ve.ce.HeadingNode} headingNode Heading node to scroll to
|
||||
* @param {number} [headingOffset=0] Set the top offset of the heading to a specific amount, relative
|
||||
* to the surface viewport.
|
||||
*/
|
||||
ve.init.mw.ArticleTarget.prototype.scrollToHeading = function ( headingNode ) {
|
||||
ve.init.mw.ArticleTarget.prototype.scrollToHeading = function ( headingNode, headingOffset ) {
|
||||
var $window = $( this.getElementWindow() );
|
||||
|
||||
$window.scrollTop( headingNode.$element.offset().top - this.getSurface().padding.top );
|
||||
$window.scrollTop( headingNode.$element.offset().top - ( this.getSurface().padding.top + ( headingOffset || 0 ) ) );
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -238,6 +238,9 @@ ve.init.mw.DesktopArticleTarget.prototype.setupToolbar = function ( surface ) {
|
|||
|
||||
var toolbar = this.getToolbar();
|
||||
|
||||
// Allow the toolbar to start floating now if necessary
|
||||
this.onContainerScroll();
|
||||
|
||||
ve.track( 'trace.setupToolbar.exit', { mode: mode } );
|
||||
if ( !wasSetup ) {
|
||||
// eslint-disable-next-line no-jquery/no-class-state
|
||||
|
@ -249,14 +252,21 @@ ve.init.mw.DesktopArticleTarget.prototype.setupToolbar = function ( surface ) {
|
|||
this.toolbarSetupDeferred.resolve();
|
||||
} else {
|
||||
setTimeout( function () {
|
||||
var isFloating = toolbar.isFloating();
|
||||
toolbar.$element
|
||||
.css( 'height', toolbar.$bar[ 0 ].offsetHeight )
|
||||
.addClass( 've-init-mw-desktopArticleTarget-toolbar-open' );
|
||||
// For unfloated toolbar, transition the container hide to smoothly
|
||||
// push the content down. Don't do this if the toolbar is floating to avoid movement.
|
||||
if ( !isFloating ) {
|
||||
toolbar.$element.css( 'height', toolbar.$bar[ 0 ].offsetHeight );
|
||||
}
|
||||
setTimeout( function () {
|
||||
// Clear to allow growth during use and when resizing window
|
||||
toolbar.$element
|
||||
.css( 'height', '' )
|
||||
.addClass( 've-init-mw-desktopArticleTarget-toolbar-opened' );
|
||||
if ( !isFloating ) {
|
||||
toolbar.$element.css( 'height', '' );
|
||||
}
|
||||
target.toolbarSetupDeferred.resolve();
|
||||
}, 250 );
|
||||
} );
|
||||
|
@ -423,9 +433,6 @@ ve.init.mw.DesktopArticleTarget.prototype.activate = function ( dataPromise ) {
|
|||
this.originalEditondbclick = mw.user.options.get( 'editondblclick' );
|
||||
mw.user.options.set( 'editondblclick', 0 );
|
||||
|
||||
// Save the scroll position; will be restored by surfaceReady()
|
||||
this.saveScrollPosition();
|
||||
|
||||
// User interface changes
|
||||
this.changeDocumentTitle();
|
||||
this.transformPage();
|
||||
|
@ -730,8 +737,7 @@ ve.init.mw.DesktopArticleTarget.prototype.surfaceReady = function () {
|
|||
|
||||
var editNotices = this.getEditNotices(),
|
||||
actionTools = this.actionsToolbar.tools,
|
||||
surface = this.getSurface(),
|
||||
target = this;
|
||||
surface = this.getSurface();
|
||||
|
||||
this.activating = false;
|
||||
|
||||
|
@ -754,12 +760,6 @@ ve.init.mw.DesktopArticleTarget.prototype.surfaceReady = function () {
|
|||
// existing page, or loading via an edit URL.
|
||||
this.rebuildCategories( metaList.getItemsInGroup( 'mwCategory' ), true );
|
||||
|
||||
// Support: IE<=11
|
||||
// IE requires us to defer before restoring the scroll position
|
||||
setTimeout( function () {
|
||||
target.restoreScrollPosition();
|
||||
} );
|
||||
|
||||
// Parent method
|
||||
ve.init.mw.DesktopArticleTarget.super.prototype.surfaceReady.apply( this, arguments );
|
||||
|
||||
|
@ -1070,28 +1070,6 @@ ve.init.mw.DesktopArticleTarget.prototype.getSaveDialogOpeningData = function ()
|
|||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remember the window's scroll position.
|
||||
*/
|
||||
ve.init.mw.DesktopArticleTarget.prototype.saveScrollPosition = function () {
|
||||
if ( ( this.getDefaultMode() === 'source' || this.enableVisualSectionEditing ) && this.section !== null ) {
|
||||
// Reset scroll to top if doing real section editing
|
||||
this.scrollTop = 0;
|
||||
} else {
|
||||
this.scrollTop = $( window ).scrollTop();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the window's scroll position.
|
||||
*/
|
||||
ve.init.mw.DesktopArticleTarget.prototype.restoreScrollPosition = function () {
|
||||
if ( this.scrollTop !== null ) {
|
||||
$( window ).scrollTop( this.scrollTop );
|
||||
this.scrollTop = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
|
|
@ -20,13 +20,13 @@
|
|||
*/
|
||||
|
||||
/* Only hide the #toc inside the original article, not generated ones in VE (T187636) */
|
||||
.ve-activated .ve-init-mw-desktopArticleTarget-editableContent #toc,
|
||||
.ve-activated #siteNotice,
|
||||
.ve-activated .mw-indicators,
|
||||
.ve-activated #t-print,
|
||||
.ve-activated #t-permalink,
|
||||
.ve-activated #p-coll-print_export,
|
||||
.ve-activated #t-cite,
|
||||
.ve-active .ve-init-mw-desktopArticleTarget-editableContent #toc,
|
||||
.ve-active #siteNotice,
|
||||
.ve-active .mw-indicators,
|
||||
.ve-active #t-print,
|
||||
.ve-active #t-permalink,
|
||||
.ve-active #p-coll-print_export,
|
||||
.ve-active #t-cite,
|
||||
.ve-deactivating .ve-ui-surface,
|
||||
.ve-active .ve-init-mw-desktopArticleTarget-editableContent,
|
||||
.ve-active .ve-init-mw-tempWikitextEditorWidget {
|
||||
|
|
|
@ -346,7 +346,9 @@
|
|||
*
|
||||
* @private
|
||||
* @param {string} mode Target mode: 'visual' or 'source'
|
||||
* @param {string} [section] Section to edit (currently just source mode)
|
||||
* @param {string} [section] Section to edit.
|
||||
* If visual section editing is not enabled, we will jump to the start of this section, and still
|
||||
* the heading to prefix the edit summary.
|
||||
* @param {jQuery.Promise} [tPromise] Promise that will be resolved with a ve.init.mw.DesktopArticleTarget
|
||||
* @param {boolean} [modified] The page was been modified before loading (e.g. in source mode)
|
||||
*/
|
||||
|
@ -393,6 +395,29 @@
|
|||
.then( incrementLoadingProgress );
|
||||
}
|
||||
|
||||
var visibleSection = null;
|
||||
var visibleSectionOffset = null;
|
||||
if ( section === null ) {
|
||||
var firstVisibleEditSection = null;
|
||||
$( '#mw-content-text .mw-editsection' ).each( function () {
|
||||
var top = this.getBoundingClientRect().top;
|
||||
if ( top > 0 ) {
|
||||
firstVisibleEditSection = this;
|
||||
// break
|
||||
return false;
|
||||
}
|
||||
} );
|
||||
|
||||
if ( firstVisibleEditSection ) {
|
||||
var firstVisibleSectionLink = firstVisibleEditSection.querySelector( 'a' );
|
||||
var linkUri = new mw.Uri( firstVisibleSectionLink.href );
|
||||
visibleSection = parseSection( linkUri.query.section );
|
||||
|
||||
var firstVisibleHeading = $( firstVisibleEditSection ).closest( 'h1, h2, h3, h4, h5, h6' )[ 0 ];
|
||||
visibleSectionOffset = firstVisibleHeading.getBoundingClientRect().top;
|
||||
}
|
||||
}
|
||||
|
||||
showLoading( mode );
|
||||
incrementLoadingProgress();
|
||||
active = true;
|
||||
|
@ -400,6 +425,9 @@
|
|||
tPromise = tPromise || getTarget( mode, section );
|
||||
tPromise
|
||||
.then( function ( target ) {
|
||||
target.visibleSection = visibleSection;
|
||||
target.visibleSectionOffset = visibleSectionOffset;
|
||||
|
||||
incrementLoadingProgress();
|
||||
target.on( 'deactivate', function () {
|
||||
active = false;
|
||||
|
|
Loading…
Reference in a new issue