2013-10-11 18:42:46 +00:00
|
|
|
/*!
|
2015-07-29 13:41:30 +00:00
|
|
|
* VisualEditor MediaWiki Initialization MobileArticleTarget class.
|
2013-10-11 18:42:46 +00:00
|
|
|
*
|
2019-01-01 13:24:23 +00:00
|
|
|
* @copyright 2011-2019 VisualEditor Team and others; see AUTHORS.txt
|
2013-10-11 18:42:46 +00:00
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2015-08-04 13:37:13 +00:00
|
|
|
* MediaWiki mobile article target.
|
2013-10-11 18:42:46 +00:00
|
|
|
*
|
|
|
|
* @class
|
2015-12-10 16:07:50 +00:00
|
|
|
* @extends ve.init.mw.ArticleTarget
|
2013-10-11 18:42:46 +00:00
|
|
|
*
|
|
|
|
* @constructor
|
2019-03-21 20:47:25 +00:00
|
|
|
* @param {VisualEditorOverlay} overlay Mobile frontend overlay
|
2014-02-06 23:26:52 +00:00
|
|
|
* @param {Object} [config] Configuration options
|
|
|
|
* @cfg {number} [section] Number of the section target should scroll to
|
2013-10-11 18:42:46 +00:00
|
|
|
*/
|
2019-03-21 20:47:25 +00:00
|
|
|
ve.init.mw.MobileArticleTarget = function VeInitMwMobileArticleTarget( overlay, config ) {
|
|
|
|
this.overlay = overlay;
|
|
|
|
this.$overlay = overlay.$el;
|
|
|
|
this.$overlaySurface = overlay.$el.find( '.surface' );
|
|
|
|
|
2014-02-06 23:26:52 +00:00
|
|
|
config = config || {};
|
2015-08-15 17:00:37 +00:00
|
|
|
config.toolbarConfig = $.extend( {
|
|
|
|
actions: false
|
|
|
|
}, config.toolbarConfig );
|
2013-10-11 18:42:46 +00:00
|
|
|
|
|
|
|
// Parent constructor
|
2017-09-11 14:59:38 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.super.call( this, config );
|
2013-12-06 20:01:03 +00:00
|
|
|
|
2014-02-06 23:26:52 +00:00
|
|
|
this.section = config.section;
|
2019-05-22 14:05:42 +00:00
|
|
|
this.adjustContentPaddingDebounced = OO.ui.debounce( this.adjustContentPadding.bind( this ) );
|
2015-07-30 09:32:40 +00:00
|
|
|
|
|
|
|
// Initialization
|
2019-07-22 19:12:35 +00:00
|
|
|
this.$element.addClass( 've-init-mw-mobileArticleTarget ve-init-mobileTarget' );
|
2013-10-11 18:42:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
2015-12-10 16:07:50 +00:00
|
|
|
OO.inheritClass( ve.init.mw.MobileArticleTarget, ve.init.mw.ArticleTarget );
|
2013-10-11 18:42:46 +00:00
|
|
|
|
|
|
|
/* Static Properties */
|
2014-11-24 17:43:16 +00:00
|
|
|
|
2015-07-29 13:41:30 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.static.toolbarGroups = [
|
2015-08-06 14:22:15 +00:00
|
|
|
// History
|
2018-05-18 13:59:15 +00:00
|
|
|
{
|
|
|
|
name: 'history',
|
2019-02-01 18:24:20 +00:00
|
|
|
include: [ 'undo' ]
|
|
|
|
},
|
2014-05-02 20:08:16 +00:00
|
|
|
// Style
|
2015-07-29 16:11:06 +00:00
|
|
|
{
|
2018-05-18 13:59:15 +00:00
|
|
|
name: 'style',
|
2015-07-29 16:11:06 +00:00
|
|
|
classes: [ 've-test-toolbar-style' ],
|
|
|
|
type: 'list',
|
|
|
|
icon: 'textStyle',
|
|
|
|
title: OO.ui.deferMsg( 'visualeditor-toolbar-style-tooltip' ),
|
|
|
|
include: [ { group: 'textStyle' }, 'language', 'clear' ],
|
|
|
|
forceExpand: [ 'bold', 'italic', 'clear' ],
|
|
|
|
promote: [ 'bold', 'italic' ],
|
|
|
|
demote: [ 'strikethrough', 'code', 'underline', 'language', 'clear' ]
|
|
|
|
},
|
2014-05-02 20:08:16 +00:00
|
|
|
// Link
|
2018-05-18 13:59:15 +00:00
|
|
|
{
|
|
|
|
name: 'link',
|
|
|
|
include: [ 'link' ]
|
|
|
|
},
|
2018-05-24 15:33:00 +00:00
|
|
|
// Placeholder for reference tools (e.g. Cite and/or Citoid)
|
|
|
|
{
|
|
|
|
name: 'reference'
|
2018-05-18 13:59:15 +00:00
|
|
|
}
|
2013-10-11 18:42:46 +00:00
|
|
|
];
|
|
|
|
|
2016-04-21 11:28:00 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.static.trackingName = 'mobile';
|
2013-12-10 01:39:46 +00:00
|
|
|
|
2015-09-03 01:24:48 +00:00
|
|
|
// FIXME Some of these users will be on tablets, check for this
|
|
|
|
ve.init.mw.MobileArticleTarget.static.platformType = 'phone';
|
|
|
|
|
2019-03-21 20:47:25 +00:00
|
|
|
/* Static Methods */
|
|
|
|
|
|
|
|
// FIXME This method is overridden in the MobileFrontend extension,
|
|
|
|
// figure out a way to make it public there so that we can use it here
|
|
|
|
/**
|
|
|
|
* @method
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.static.parseSaveError = null;
|
|
|
|
|
2013-12-06 20:01:03 +00:00
|
|
|
/* Methods */
|
|
|
|
|
2019-03-29 00:31:51 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.deactivateSurfaceForToolbar = function () {
|
|
|
|
// Parent method
|
|
|
|
ve.init.mw.MobileArticleTarget.super.prototype.deactivateSurfaceForToolbar.call( this );
|
|
|
|
|
|
|
|
if ( this.wasSurfaceActive && ve.init.platform.constructor.static.isIos() ) {
|
|
|
|
this.prevScrollPosition = this.getSurface().$scrollContainer.scrollTop();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.activateSurfaceForToolbar = function () {
|
|
|
|
// Parent method
|
|
|
|
ve.init.mw.MobileArticleTarget.super.prototype.activateSurfaceForToolbar.call( this );
|
|
|
|
|
|
|
|
if ( this.wasSurfaceActive && ve.init.platform.constructor.static.isIos() ) {
|
|
|
|
// Setting the cursor can cause unwanted scrolling on iOS, so manually
|
|
|
|
// restore the scroll offset from before the toolbar was opened (T218650).
|
|
|
|
this.getSurface().$scrollContainer.scrollTop( this.prevScrollPosition );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-06-11 18:08:20 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.updateIosContextPadding = function () {
|
|
|
|
var browserMenuCollapsedHeight, currentHeight;
|
|
|
|
|
|
|
|
if ( !this.$screenMeasure ) {
|
|
|
|
this.$screenMeasure = $( '<div>' ).addClass( 've-init-mw-mobileArticleTarget-iosScreenMeasure' );
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$screenMeasure.appendTo( document.body );
|
|
|
|
// This element is sized using 'vh' units, which iOS does not actually update to match the
|
|
|
|
// viewport when viewport size changes due to browser menu bar collapsing/expanding.
|
|
|
|
browserMenuCollapsedHeight = this.$screenMeasure.height();
|
|
|
|
this.$screenMeasure.detach();
|
|
|
|
|
|
|
|
currentHeight = window.innerHeight;
|
|
|
|
|
|
|
|
if ( browserMenuCollapsedHeight === currentHeight ) {
|
|
|
|
// Looks like the browser menu bar is collapsed. Tapping near the bottom of the screen will not
|
|
|
|
// trigger any events on our widgets, but instead it will expand the browser menu bar. Reserve
|
|
|
|
// some space where the browser menu bar will appear.
|
|
|
|
this.surface.getContext().$element.css( 'padding-bottom', 44 );
|
|
|
|
} else {
|
|
|
|
// Looks like the browser menu is expanded, so we can remove the silly padding. Even if our
|
|
|
|
// check here breaks in future versions of iOS, that's okay, the user will just need to tap
|
|
|
|
// things in this area twice.
|
|
|
|
this.surface.getContext().$element.css( 'padding-bottom', 0 );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-05-27 19:12:04 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.clearSurfaces = function () {
|
|
|
|
if ( ve.init.platform.constructor.static.isIos() && this.viewportZoomHandler ) {
|
|
|
|
this.viewportZoomHandler.detach();
|
|
|
|
this.viewportZoomHandler = null;
|
|
|
|
}
|
|
|
|
// Parent method
|
|
|
|
ve.init.mw.MobileArticleTarget.super.prototype.clearSurfaces.call( this );
|
|
|
|
};
|
|
|
|
|
2019-03-21 20:47:25 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.onContainerScroll = function () {
|
2019-03-21 22:10:56 +00:00
|
|
|
var surfaceView, isActiveWithKeyboard, $header, $overlaySurface;
|
|
|
|
// Editor may not have loaded yet, in which case `this.surface` is undefined
|
|
|
|
surfaceView = this.surface && this.surface.getView();
|
2019-04-09 14:27:20 +00:00
|
|
|
isActiveWithKeyboard = surfaceView && surfaceView.isFocused() && !surfaceView.isDeactivated();
|
2019-03-21 22:10:56 +00:00
|
|
|
|
2019-07-02 12:44:26 +00:00
|
|
|
if ( ve.init.platform.constructor.static.isIos() ) {
|
2019-06-11 18:08:20 +00:00
|
|
|
this.updateIosContextPadding();
|
|
|
|
}
|
|
|
|
|
2019-03-21 22:10:56 +00:00
|
|
|
$header = this.overlay.$el.find( '.overlay-header-container' );
|
|
|
|
$overlaySurface = this.$overlaySurface;
|
|
|
|
|
|
|
|
// On iOS Safari, when the keyboard is open, the layout viewport reported by the browser is not
|
|
|
|
// updated to match the real viewport reduced by the keyboard (diagram: T218414#5027607). On all
|
|
|
|
// modern non-iOS browsers the layout viewport is updated to match real viewport.
|
|
|
|
//
|
|
|
|
// This allows the fixed toolbar to be scrolled out of view, ignoring `position: fixed` (because
|
|
|
|
// it refers to the layout viewport).
|
|
|
|
//
|
|
|
|
// When this happens, bring it back in by scrolling down a bit and back up until the top of the
|
|
|
|
// fake viewport is aligned with the top of the real viewport.
|
|
|
|
|
|
|
|
clearTimeout( this.onContainerScrollTimer );
|
|
|
|
if ( !isActiveWithKeyboard ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Wait until after the scroll, because 'scroll' events are not emitted for every frame the
|
|
|
|
// browser paints, so the toolbar would lag behind in a very unseemly manner. Additionally,
|
|
|
|
// getBoundingClientRect returns incorrect values during scrolling, so make sure to calculate
|
|
|
|
// it only after the scrolling ends (https://openradar.appspot.com/radar?id=6668472289329152).
|
|
|
|
this.onContainerScrollTimer = setTimeout( function () {
|
|
|
|
var pos, viewportHeight, scrollPos, headerHeight, headerTranslateY;
|
|
|
|
|
|
|
|
// Check if toolbar is offscreen. In a better world, this would reject all negative values
|
|
|
|
// (pos >= 0), but getBoundingClientRect often returns funny small fractional values after
|
|
|
|
// this function has done its job (which triggers another 'scroll' event) and before the
|
|
|
|
// user scrolled again. If we allowed it to run, it would trigger a hilarious loop! Toolbar
|
|
|
|
// being 1px offscreen is not a big deal anyway.
|
|
|
|
pos = $header[ 0 ].getBoundingClientRect().top;
|
|
|
|
if ( pos >= -1 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't know how much we have to scroll because we don't know how large the real
|
|
|
|
// viewport is, but it's no larger than the layout viewport.
|
|
|
|
viewportHeight = window.innerHeight;
|
|
|
|
scrollPos = document.body.scrollTop;
|
|
|
|
|
|
|
|
// Scroll down and translate the surface by the same amount, otherwise the content at new
|
|
|
|
// scroll position visibly flashes.
|
|
|
|
$overlaySurface.css( 'transform', 'translateY( ' + viewportHeight + 'px )' );
|
|
|
|
document.body.scrollTop += viewportHeight;
|
|
|
|
|
|
|
|
// (Note that the scrolling we just did will naturally trigger another 'scroll' event,
|
|
|
|
// and run this handler again after 250ms. This is okay.)
|
|
|
|
|
|
|
|
// Prepate to animate toolbar sliding into view
|
|
|
|
$header.removeClass( 'toolbar-shown toolbar-shown-done' );
|
|
|
|
headerHeight = $header[ 0 ].offsetHeight;
|
|
|
|
headerTranslateY = Math.max( -headerHeight, pos );
|
|
|
|
$header.css( 'transform', 'translateY( ' + headerTranslateY + 'px )' );
|
|
|
|
|
|
|
|
// The scroll back up must be after a delay, otherwise no scrolling happens and the
|
|
|
|
// viewports are not aligned. requestAnimationFrame() seems to minimize weird flashes
|
|
|
|
// of white (although they still happen and I have no explanation for them).
|
|
|
|
requestAnimationFrame( function () {
|
|
|
|
// Scroll back up
|
|
|
|
$overlaySurface.css( 'transform', '' );
|
|
|
|
document.body.scrollTop = scrollPos;
|
|
|
|
|
2019-03-29 01:16:40 +00:00
|
|
|
// Animate toolbar sliding into view
|
|
|
|
$header.addClass( 'toolbar-shown' ).css( 'transform', '' );
|
|
|
|
setTimeout( function () {
|
|
|
|
$header.addClass( 'toolbar-shown-done' );
|
|
|
|
}, 250 );
|
2019-03-21 22:10:56 +00:00
|
|
|
} );
|
|
|
|
}, 250 );
|
2019-03-21 20:47:25 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle surface scroll events
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.onSurfaceScroll = function () {
|
|
|
|
var nativeSelection, range;
|
|
|
|
|
|
|
|
if ( ve.init.platform.constructor.static.isIos() ) {
|
|
|
|
// iOS has a bug where if you change the scroll offset of a
|
|
|
|
// contentEditable or textarea with a cursor visible, it disappears.
|
|
|
|
// This function works around it by removing and reapplying the selection.
|
|
|
|
nativeSelection = this.getSurface().getView().nativeSelection;
|
|
|
|
if ( nativeSelection.rangeCount && document.activeElement.contentEditable === 'true' ) {
|
|
|
|
range = nativeSelection.getRangeAt( 0 );
|
|
|
|
nativeSelection.removeAllRanges();
|
|
|
|
nativeSelection.addRange( range );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.createSurface = function ( dmDoc, config ) {
|
|
|
|
var surface;
|
|
|
|
if ( this.overlay.isNewPage ) {
|
|
|
|
config = ve.extendObject( {
|
|
|
|
placeholder: this.overlay.options.placeholder
|
|
|
|
}, config );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parent method
|
|
|
|
surface = ve.init.mw.MobileArticleTarget
|
|
|
|
.super.prototype.createSurface.call( this, dmDoc, config );
|
|
|
|
|
|
|
|
surface.connect( this, { scroll: 'onSurfaceScroll' } );
|
|
|
|
|
|
|
|
return surface;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.setSurface = function ( surface ) {
|
|
|
|
var changed = surface !== this.surface;
|
|
|
|
|
|
|
|
// Parent method
|
|
|
|
// FIXME This actually skips ve.init.mw.Target.prototype.setSurface. Why?
|
|
|
|
ve.init.mw.Target.super.prototype.setSurface.apply( this, arguments );
|
|
|
|
|
|
|
|
if ( changed ) {
|
|
|
|
surface.$element.addClass( 'content' );
|
|
|
|
this.$overlaySurface.append( surface.$element );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-12-06 20:01:03 +00:00
|
|
|
/**
|
2015-08-04 13:37:13 +00:00
|
|
|
* @inheritdoc
|
2013-12-06 20:01:03 +00:00
|
|
|
*/
|
2015-12-11 14:57:49 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.surfaceReady = function () {
|
2019-08-02 07:21:14 +00:00
|
|
|
var surfaceView, surfaceModel;
|
2019-03-21 20:47:25 +00:00
|
|
|
|
|
|
|
if ( this.teardownPromise ) {
|
|
|
|
// Loading was cancelled, the overlay is already closed at this point. Do nothing.
|
|
|
|
// Otherwise e.g. scrolling from #goToHeading would kick in and mess things up.
|
|
|
|
return;
|
|
|
|
}
|
2015-08-19 18:05:01 +00:00
|
|
|
|
2019-06-11 11:02:48 +00:00
|
|
|
// Calls scrollSelectionIntoView so must be called before parent,
|
|
|
|
// which calls goToHeading. (T225292)
|
|
|
|
this.adjustContentPadding();
|
|
|
|
|
2015-07-01 11:11:36 +00:00
|
|
|
// Parent method
|
2015-12-11 14:57:49 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.super.prototype.surfaceReady.apply( this, arguments );
|
2015-07-01 11:11:36 +00:00
|
|
|
|
2019-08-02 07:21:14 +00:00
|
|
|
// Place a selection at the start of the document, but with the surface
|
|
|
|
// disabled, so toolbar tools have a thing to focus on without us showing the
|
|
|
|
// keyboard.
|
|
|
|
surfaceView = this.getSurface().getView();
|
|
|
|
surfaceView.deactivate();
|
2015-08-19 18:05:01 +00:00
|
|
|
surfaceModel = this.getSurface().getModel();
|
2019-08-02 07:21:14 +00:00
|
|
|
surfaceModel.selectFirstContentOffset();
|
2015-08-06 14:22:15 +00:00
|
|
|
|
2015-04-09 03:48:46 +00:00
|
|
|
this.events.trackActivationComplete();
|
2019-03-21 20:47:25 +00:00
|
|
|
|
|
|
|
this.overlay.hideSpinner();
|
|
|
|
|
|
|
|
this.maybeShowWelcomeDialog();
|
2019-05-27 19:12:04 +00:00
|
|
|
|
|
|
|
if ( ve.init.platform.constructor.static.isIos() ) {
|
|
|
|
if ( this.viewportZoomHandler ) {
|
|
|
|
this.viewportZoomHandler.detach();
|
|
|
|
}
|
|
|
|
this.viewportZoomHandler = new ve.init.mw.ViewportZoomHandler();
|
|
|
|
this.viewportZoomHandler.attach( this.getSurface() );
|
|
|
|
}
|
2019-03-21 20:47:25 +00:00
|
|
|
};
|
|
|
|
|
2019-07-10 18:03:39 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2019-07-10 20:10:27 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.maybeShowWelcomeDialog = function () {
|
2019-07-10 18:03:39 +00:00
|
|
|
// Never show the dialog (T227670), but set up this promise in case something depends on it
|
|
|
|
this.welcomeDialogPromise = $.Deferred().reject();
|
|
|
|
};
|
|
|
|
|
2019-03-21 20:47:25 +00:00
|
|
|
/**
|
|
|
|
* Match the content padding to the toolbar height
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.adjustContentPadding = function () {
|
2019-05-14 19:52:21 +00:00
|
|
|
var surface = this.getSurface(),
|
|
|
|
surfaceView = surface.getView(),
|
2019-07-23 21:50:16 +00:00
|
|
|
toolbarHeight = this.getToolbar().$element[ 0 ].clientHeight;
|
2019-05-14 19:52:21 +00:00
|
|
|
|
|
|
|
surface.setPadding( {
|
2019-07-23 21:50:16 +00:00
|
|
|
top: toolbarHeight
|
2019-05-14 19:52:21 +00:00
|
|
|
} );
|
2019-03-28 19:17:20 +00:00
|
|
|
surfaceView.$attachedRootNode.css( 'padding-top', toolbarHeight );
|
2019-04-01 22:45:11 +00:00
|
|
|
surface.$placeholder.css( 'padding-top', toolbarHeight );
|
2019-03-28 14:40:44 +00:00
|
|
|
surfaceView.emit( 'position' );
|
2019-04-08 11:17:28 +00:00
|
|
|
surface.scrollSelectionIntoView();
|
2013-12-06 20:01:03 +00:00
|
|
|
};
|
2014-02-06 23:33:21 +00:00
|
|
|
|
2015-07-01 11:11:36 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2018-03-19 14:49:23 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.getSaveButtonLabel = function ( startProcess ) {
|
|
|
|
var suffix = startProcess ? '-start' : '';
|
|
|
|
// The following messages can be used here:
|
|
|
|
// * visualeditor-savedialog-label-publish-short
|
|
|
|
// * visualeditor-savedialog-label-publish-short-start
|
|
|
|
// * visualeditor-savedialog-label-save-short
|
|
|
|
// * visualeditor-savedialog-label-save-short-start
|
2016-08-29 17:46:18 +00:00
|
|
|
if ( mw.config.get( 'wgEditSubmitButtonLabelPublish' ) ) {
|
2018-03-19 14:49:23 +00:00
|
|
|
return OO.ui.deferMsg( 'visualeditor-savedialog-label-publish-short' + suffix );
|
2016-06-30 14:04:51 +00:00
|
|
|
}
|
|
|
|
|
2018-03-19 14:49:23 +00:00
|
|
|
return OO.ui.deferMsg( 'visualeditor-savedialog-label-save-short' + suffix );
|
2015-07-01 11:11:36 +00:00
|
|
|
};
|
|
|
|
|
2019-03-23 03:36:10 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.loadFail = function ( key, text ) {
|
|
|
|
// Parent method
|
|
|
|
ve.init.mw.MobileArticleTarget.super.prototype.loadFail.apply( this, arguments );
|
|
|
|
|
|
|
|
this.overlay.reportError( text );
|
|
|
|
this.overlay.hide();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.editSource = function () {
|
2019-04-03 15:43:09 +00:00
|
|
|
var modified = this.fromEditedState || this.getSurface().getModel().hasBeenModified();
|
|
|
|
|
2019-04-15 22:39:04 +00:00
|
|
|
this.switchToWikitextEditor( modified );
|
2019-04-03 15:43:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2019-04-15 22:39:04 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.switchToWikitextEditor = function ( modified ) {
|
2019-04-03 15:43:09 +00:00
|
|
|
var dataPromise;
|
|
|
|
if ( modified ) {
|
|
|
|
dataPromise = this.getWikitextDataPromiseForDoc( modified ).then( function ( response ) {
|
|
|
|
var content = ve.getProp( response, 'visualeditoredit', 'content' );
|
|
|
|
return { text: content };
|
2019-03-23 03:36:10 +00:00
|
|
|
} );
|
|
|
|
}
|
2019-04-03 15:43:09 +00:00
|
|
|
this.overlay.switchToSourceEditor( dataPromise );
|
2019-03-23 03:36:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.save = function () {
|
|
|
|
// Parent method
|
|
|
|
ve.init.mw.MobileArticleTarget.super.prototype.save.apply( this, arguments );
|
|
|
|
|
|
|
|
this.overlay.log( {
|
|
|
|
action: 'saveAttempt'
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.showSaveDialog = function () {
|
|
|
|
// Parent method
|
|
|
|
ve.init.mw.MobileArticleTarget.super.prototype.showSaveDialog.apply( this, arguments );
|
|
|
|
|
|
|
|
this.overlay.log( {
|
|
|
|
action: 'saveIntent'
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2019-07-01 20:59:55 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.saveComplete = function ( html, categoriesHtml, newRevId ) {
|
2019-03-26 17:14:16 +00:00
|
|
|
// TODO: parsing this is expensive just for the section details. We should
|
|
|
|
// change MobileFrontend+this to behave like desktop does and just rerender
|
|
|
|
// the page with the provided HTML (T219420).
|
|
|
|
var fragment = this.getSectionFragmentFromPage( $.parseHTML( html ) );
|
2019-03-23 03:36:10 +00:00
|
|
|
// Parent method
|
|
|
|
ve.init.mw.MobileArticleTarget.super.prototype.saveComplete.apply( this, arguments );
|
|
|
|
|
|
|
|
this.overlay.sectionId = fragment;
|
2019-07-01 20:59:55 +00:00
|
|
|
this.overlay.onSaveComplete( newRevId );
|
2019-03-23 03:36:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
ve.init.mw.ArticleTarget: Use errorformat=html when saving
When saving fails for a reason we don't handle explicitly, the error
message will have HTML formatting and will respect any on-wiki
overridden messages, rather than being plain text generic message.
Extensions providing custom SaveErrorHandlers may need to be updated.
The only one in Gerrit that requires a fix is TitleBlacklist:
Ibeae79c95557a7af699716c9d921f34c310bee6d.
* Remove handling for errors returned in .visualeditoredit.edit.info
rather than .errors (.error in old format). AFAIK this is only used
by some extensions, it is probably incorrect to do (T229539) and all
extensions I know of that do this (AbuseFilter, SpamBlacklist,
ConfirmEdit) have custom SaveErrorHandlers.
* Remove custom error messages for 'readonly' (identical to API
response) and for 'hookaborted' (very unhelpful and there is a
chance that the API response is better, if the extension causing
this error generates any error message).
* Add a silly shim for MobileFrontend integration, because we allow it
to handle error responses, and it expects them in the old format.
This is probably subtly wrong in many ways, but MobileFrontend code
only uses this for logging, so it shouldn't explode. In the future
we will hopefully change it to use errorformat=html (T228897#5366960).
Bug: T229532
Change-Id: I3b9c4fefc0869ef7999c21cef754434febd852ec
2019-08-01 03:32:42 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.saveFail = function ( doc, saveData, wasRetry, jqXHR, status, data ) {
|
2019-03-23 03:36:10 +00:00
|
|
|
// parent method
|
|
|
|
ve.init.mw.MobileArticleTarget.super.prototype.saveFail.apply( this, arguments );
|
|
|
|
|
ve.init.mw.ArticleTarget: Use errorformat=html when saving
When saving fails for a reason we don't handle explicitly, the error
message will have HTML formatting and will respect any on-wiki
overridden messages, rather than being plain text generic message.
Extensions providing custom SaveErrorHandlers may need to be updated.
The only one in Gerrit that requires a fix is TitleBlacklist:
Ibeae79c95557a7af699716c9d921f34c310bee6d.
* Remove handling for errors returned in .visualeditoredit.edit.info
rather than .errors (.error in old format). AFAIK this is only used
by some extensions, it is probably incorrect to do (T229539) and all
extensions I know of that do this (AbuseFilter, SpamBlacklist,
ConfirmEdit) have custom SaveErrorHandlers.
* Remove custom error messages for 'readonly' (identical to API
response) and for 'hookaborted' (very unhelpful and there is a
chance that the API response is better, if the extension causing
this error generates any error message).
* Add a silly shim for MobileFrontend integration, because we allow it
to handle error responses, and it expects them in the old format.
This is probably subtly wrong in many ways, but MobileFrontend code
only uses this for logging, so it shouldn't explode. In the future
we will hopefully change it to use errorformat=html (T228897#5366960).
Bug: T229532
Change-Id: I3b9c4fefc0869ef7999c21cef754434febd852ec
2019-08-01 03:32:42 +00:00
|
|
|
// Massage errorformat=html responses to look more like errorformat=bc expected by MF
|
|
|
|
if ( data.errors ) {
|
|
|
|
data.error = data.errors[ 0 ];
|
|
|
|
}
|
|
|
|
|
|
|
|
this.overlay.onSaveFailure( this.constructor.static.parseSaveError( data, status ) );
|
2019-03-23 03:36:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.tryTeardown = function () {
|
2019-06-04 19:50:47 +00:00
|
|
|
window.history.back();
|
2019-03-23 03:36:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.load = function () {
|
|
|
|
var surface;
|
|
|
|
|
|
|
|
// Create dummy surface to show toolbar while loading
|
|
|
|
// Call ve.init.Target directly to avoid firing surfaceReady
|
|
|
|
surface = ve.init.Target.prototype.addSurface.call( this, new ve.dm.Document( [
|
|
|
|
{ type: 'paragraph' }, { type: '/paragraph' },
|
|
|
|
{ type: 'internalList' }, { type: '/internalList' }
|
|
|
|
] ) );
|
|
|
|
surface.setReadOnly( true );
|
|
|
|
// setSurface creates dummy toolbar
|
|
|
|
this.setSurface( surface );
|
|
|
|
|
|
|
|
return ve.init.mw.MobileArticleTarget.super.prototype.load.apply( this, arguments );
|
|
|
|
};
|
|
|
|
|
2014-02-06 23:33:21 +00:00
|
|
|
/**
|
2014-02-07 22:04:35 +00:00
|
|
|
* @inheritdoc
|
2014-02-06 23:33:21 +00:00
|
|
|
*/
|
2015-07-29 13:41:30 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.setupToolbar = function ( surface ) {
|
2019-08-02 07:21:14 +00:00
|
|
|
var originalToolbarGroups = this.constructor.static.toolbarGroups;
|
|
|
|
|
|
|
|
// We don't want any of these tools to show up in subordinate widgets, so we
|
|
|
|
// temporarily add them here. We need to do it _here_ rather than in their
|
|
|
|
// own static variable to make sure that other tools which meddle with
|
|
|
|
// toolbarGroups (Cite, mostly) have a chance to do so.
|
|
|
|
this.constructor.static.toolbarGroups = [].concat(
|
|
|
|
[
|
|
|
|
// Back
|
|
|
|
{
|
|
|
|
name: 'back',
|
|
|
|
include: [ 'back' ]
|
|
|
|
}
|
|
|
|
],
|
|
|
|
ve.init.mw.MobileArticleTarget.static.toolbarGroups,
|
|
|
|
[
|
|
|
|
{
|
|
|
|
name: 'editMode',
|
|
|
|
type: 'list',
|
|
|
|
icon: 'edit',
|
|
|
|
title: OO.ui.deferMsg( 'visualeditor-mweditmode-tooltip' ),
|
|
|
|
include: [ 'editModeVisual', 'editModeSource' ]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'save',
|
|
|
|
type: 'bar',
|
|
|
|
include: [ 'showMobileSave' ]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
);
|
2019-04-15 22:39:56 +00:00
|
|
|
|
2014-02-07 22:04:35 +00:00
|
|
|
// Parent method
|
2015-07-01 11:11:36 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.super.prototype.setupToolbar.call( this, surface );
|
2014-02-07 22:04:35 +00:00
|
|
|
|
2019-08-02 07:21:14 +00:00
|
|
|
this.constructor.static.toolbarGroups = originalToolbarGroups;
|
2019-04-15 22:39:56 +00:00
|
|
|
|
|
|
|
this.toolbar.$group.addClass( 've-init-mw-mobileArticleTarget-editTools' );
|
2015-07-30 11:08:56 +00:00
|
|
|
this.toolbar.$element.addClass( 've-init-mw-mobileArticleTarget-toolbar' );
|
2015-02-19 18:22:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2015-07-29 13:41:30 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.attachToolbar = function () {
|
2015-02-19 18:22:20 +00:00
|
|
|
// Move the toolbar to the overlay header
|
2019-03-21 20:47:25 +00:00
|
|
|
this.overlay.$el.find( '.overlay-header > .toolbar' ).append( this.toolbar.$element );
|
2015-08-10 12:31:46 +00:00
|
|
|
this.toolbar.initialize();
|
2014-02-06 23:33:21 +00:00
|
|
|
};
|
2014-07-23 22:30:38 +00:00
|
|
|
|
2015-07-30 11:08:56 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2019-04-15 22:39:56 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.setupToolbarSaveButton = function () {
|
2019-08-02 07:21:14 +00:00
|
|
|
this.toolbarSaveButton = this.toolbar.getToolGroupByName( 'save' ).items[ 0 ];
|
2015-07-30 11:08:56 +00:00
|
|
|
};
|
|
|
|
|
2014-07-23 22:30:38 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2015-07-29 13:41:30 +00:00
|
|
|
ve.init.mw.MobileArticleTarget.prototype.goToHeading = function ( headingNode ) {
|
2014-08-22 00:37:12 +00:00
|
|
|
this.scrollToHeading( headingNode );
|
|
|
|
};
|
2014-07-28 21:54:12 +00:00
|
|
|
|
2015-08-06 14:22:15 +00:00
|
|
|
/**
|
|
|
|
* Done with the editing toolbar
|
|
|
|
*/
|
|
|
|
ve.init.mw.MobileArticleTarget.prototype.done = function () {
|
2019-04-10 21:51:08 +00:00
|
|
|
this.getSurface().getModel().setNullSelection();
|
2015-08-06 14:22:15 +00:00
|
|
|
this.getSurface().getView().blur();
|
|
|
|
};
|
|
|
|
|
2016-04-21 11:28:00 +00:00
|
|
|
/* Registration */
|
|
|
|
|
|
|
|
ve.init.mw.targetFactory.register( ve.init.mw.MobileArticleTarget );
|
|
|
|
|
2015-07-01 11:11:36 +00:00
|
|
|
/**
|
|
|
|
* Back tool
|
|
|
|
*/
|
|
|
|
ve.ui.MWBackTool = function VeUiMwBackTool() {
|
|
|
|
// Parent constructor
|
|
|
|
ve.ui.MWBackTool.super.apply( this, arguments );
|
|
|
|
};
|
|
|
|
OO.inheritClass( ve.ui.MWBackTool, ve.ui.Tool );
|
|
|
|
ve.ui.MWBackTool.static.name = 'back';
|
2015-08-04 13:37:13 +00:00
|
|
|
ve.ui.MWBackTool.static.group = 'navigation';
|
2019-02-12 23:26:20 +00:00
|
|
|
ve.ui.MWBackTool.static.icon = 'close';
|
2015-07-01 11:11:36 +00:00
|
|
|
ve.ui.MWBackTool.static.title =
|
|
|
|
OO.ui.deferMsg( 'visualeditor-backbutton-tooltip' );
|
|
|
|
ve.ui.MWBackTool.static.commandName = 'back';
|
2015-08-06 14:22:15 +00:00
|
|
|
|
|
|
|
/** */
|
|
|
|
ve.ui.MWBackTool.prototype.onUpdateState = function () {
|
|
|
|
// Parent method
|
|
|
|
ve.ui.MWBackTool.super.prototype.onUpdateState.apply( this, arguments );
|
|
|
|
|
|
|
|
this.setActive( false );
|
|
|
|
this.setDisabled( false );
|
|
|
|
};
|
|
|
|
|
2015-07-01 11:11:36 +00:00
|
|
|
ve.ui.toolFactory.register( ve.ui.MWBackTool );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Back command
|
|
|
|
*/
|
2015-08-18 12:54:51 +00:00
|
|
|
ve.ui.MWBackCommand = function VeUiMWBackCommand() {
|
2015-07-01 11:11:36 +00:00
|
|
|
// Parent constructor
|
|
|
|
ve.ui.MWBackCommand.super.call( this, 'back' );
|
|
|
|
};
|
|
|
|
OO.inheritClass( ve.ui.MWBackCommand, ve.ui.Command );
|
|
|
|
ve.ui.MWBackCommand.prototype.execute = function () {
|
2018-03-26 14:27:20 +00:00
|
|
|
ve.init.target.tryTeardown();
|
2015-07-01 11:11:36 +00:00
|
|
|
};
|
|
|
|
ve.ui.commandRegistry.register( new ve.ui.MWBackCommand() );
|
2015-08-06 14:22:15 +00:00
|
|
|
|
|
|
|
/**
|
2019-08-02 07:21:14 +00:00
|
|
|
* Mobile save tool
|
2015-08-06 14:22:15 +00:00
|
|
|
*/
|
2019-08-02 07:21:14 +00:00
|
|
|
ve.ui.MWMobileSaveTool = function VeUiMWMobileSaveTool() {
|
|
|
|
// Parent Constructor
|
|
|
|
ve.ui.MWMobileSaveTool.super.apply( this, arguments );
|
2015-08-06 14:22:15 +00:00
|
|
|
};
|
2019-08-02 07:21:14 +00:00
|
|
|
OO.inheritClass( ve.ui.MWMobileSaveTool, ve.ui.MWSaveTool );
|
|
|
|
ve.ui.MWMobileSaveTool.static.name = 'showMobileSave';
|
|
|
|
ve.ui.MWMobileSaveTool.static.icon = 'next';
|
|
|
|
ve.ui.MWMobileSaveTool.static.displayBothIconAndLabel = false;
|
|
|
|
|
|
|
|
ve.ui.toolFactory.register( ve.ui.MWMobileSaveTool );
|