JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
/*!
|
2015-12-10 16:07:50 +00:00
|
|
|
* VisualEditor MediaWiki Initialization ArticleTarget class.
|
2012-07-19 21:25:16 +00:00
|
|
|
*
|
2019-01-01 13:24:23 +00:00
|
|
|
* @copyright 2011-2019 VisualEditor Team and others; see AUTHORS.txt
|
2012-07-19 00:11:26 +00:00
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
2019-01-08 17:00:09 +00:00
|
|
|
/* eslint-disable jquery/no-global-selector */
|
|
|
|
|
2018-02-12 09:46:01 +00:00
|
|
|
/* global EasyDeflate */
|
2013-01-22 22:41:22 +00:00
|
|
|
|
2012-06-11 06:54:41 +00:00
|
|
|
/**
|
2015-12-10 16:07:50 +00:00
|
|
|
* Initialization MediaWiki article target.
|
2012-06-11 06:54:41 +00:00
|
|
|
*
|
|
|
|
* @class
|
2015-12-11 14:57:49 +00:00
|
|
|
* @extends ve.init.mw.Target
|
2013-02-20 19:44:44 +00:00
|
|
|
*
|
2012-06-11 06:54:41 +00:00
|
|
|
* @constructor
|
2015-07-30 09:32:40 +00:00
|
|
|
* @param {Object} [config] Configuration options
|
2012-06-11 06:54:41 +00:00
|
|
|
*/
|
2017-09-11 14:59:38 +00:00
|
|
|
ve.init.mw.ArticleTarget = function VeInitMwArticleTarget( config ) {
|
2015-07-30 09:32:40 +00:00
|
|
|
config = config || {};
|
|
|
|
config.toolbarConfig = $.extend( {
|
2015-04-09 09:18:22 +00:00
|
|
|
shadow: true,
|
|
|
|
actions: true,
|
2015-08-10 12:31:46 +00:00
|
|
|
floatable: true
|
2015-07-30 09:32:40 +00:00
|
|
|
}, config.toolbarConfig );
|
|
|
|
|
|
|
|
// Parent constructor
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.super.call( this, config );
|
2014-12-03 00:04:07 +00:00
|
|
|
|
2012-06-11 06:54:41 +00:00
|
|
|
// Properties
|
2015-08-05 21:43:23 +00:00
|
|
|
this.saveDialog = null;
|
|
|
|
this.saveDeferred = null;
|
2018-11-23 17:01:25 +00:00
|
|
|
this.saveFields = {};
|
2015-08-05 21:43:23 +00:00
|
|
|
this.docToSave = null;
|
2017-07-17 16:38:32 +00:00
|
|
|
this.originalDmDocPromise = null;
|
2017-10-05 16:31:15 +00:00
|
|
|
this.originalHtml = null;
|
2015-07-31 11:03:14 +00:00
|
|
|
this.toolbarSaveButton = null;
|
2016-06-30 14:01:24 +00:00
|
|
|
this.pageExists = mw.config.get( 'wgRelevantArticleId', 0 ) !== 0;
|
2015-08-10 12:31:46 +00:00
|
|
|
this.toolbarScrollOffset = mw.config.get( 'wgVisualEditorToolbarScrollOffset', 0 );
|
2018-08-09 14:10:53 +00:00
|
|
|
// A workaround, as default URI does not get updated after pushState (T74334)
|
2017-09-11 14:59:38 +00:00
|
|
|
this.currentUri = new mw.Uri( location.href );
|
2016-09-06 19:16:55 +00:00
|
|
|
this.section = null;
|
2016-12-08 19:24:10 +00:00
|
|
|
this.sectionTitle = null;
|
2017-03-09 23:26:08 +00:00
|
|
|
this.editSummaryValue = null;
|
|
|
|
this.initialEditSummary = null;
|
2014-05-14 21:11:19 +00:00
|
|
|
|
2016-12-18 19:04:20 +00:00
|
|
|
this.$templatesUsed = null;
|
2016-11-05 06:16:26 +00:00
|
|
|
this.checkboxFields = null;
|
|
|
|
this.checkboxesByName = null;
|
2017-10-05 16:31:15 +00:00
|
|
|
this.$saveAccessKeyElements = null;
|
2016-11-05 06:16:26 +00:00
|
|
|
|
2014-05-14 21:11:19 +00:00
|
|
|
// Sometimes we actually don't want to send a useful oldid
|
|
|
|
// if we do, PostEdit will give us a 'page restored' message
|
2017-09-11 14:59:38 +00:00
|
|
|
this.requestedRevId = mw.config.get( 'wgRevisionId' );
|
2016-03-24 09:51:40 +00:00
|
|
|
this.currentRevisionId = mw.config.get( 'wgCurRevisionId' );
|
2016-03-29 11:49:35 +00:00
|
|
|
this.revid = this.requestedRevId || this.currentRevisionId;
|
2014-05-14 21:11:19 +00:00
|
|
|
|
2018-03-26 14:23:56 +00:00
|
|
|
this.edited = false;
|
2016-03-29 11:49:35 +00:00
|
|
|
this.restoring = !!this.requestedRevId && this.requestedRevId !== this.currentRevisionId;
|
2015-08-04 13:37:13 +00:00
|
|
|
this.pageDeletedWarning = false;
|
2018-05-04 13:30:10 +00:00
|
|
|
this.submitUrl = ( new mw.Uri( mw.util.getUrl( this.getPageName() ) ) )
|
2015-11-10 23:01:49 +00:00
|
|
|
.extend( {
|
|
|
|
action: 'submit',
|
|
|
|
veswitched: 1
|
|
|
|
} );
|
2019-01-08 17:00:09 +00:00
|
|
|
this.events = {
|
|
|
|
track: function () {},
|
|
|
|
trackActivationStart: function () {},
|
|
|
|
trackActivationComplete: function () {}
|
|
|
|
};
|
2013-10-11 13:04:11 +00:00
|
|
|
|
2016-02-19 18:47:33 +00:00
|
|
|
this.welcomeDialog = null;
|
|
|
|
this.welcomeDialogPromise = null;
|
|
|
|
|
2013-11-06 08:22:11 +00:00
|
|
|
this.preparedCacheKeyPromise = null;
|
2013-12-03 02:29:11 +00:00
|
|
|
this.clearState();
|
2015-07-01 11:11:36 +00:00
|
|
|
|
2015-07-30 09:32:40 +00:00
|
|
|
// Initialization
|
2015-12-10 16:07:50 +00:00
|
|
|
this.$element.addClass( 've-init-mw-articleTarget' );
|
2012-06-11 06:54:41 +00:00
|
|
|
};
|
|
|
|
|
2014-04-10 18:47:34 +00:00
|
|
|
/* Inheritance */
|
|
|
|
|
2015-12-11 14:57:49 +00:00
|
|
|
OO.inheritClass( ve.init.mw.ArticleTarget, ve.init.mw.Target );
|
2014-04-10 18:47:34 +00:00
|
|
|
|
2013-12-19 02:06:55 +00:00
|
|
|
/* Events */
|
2013-03-20 07:09:43 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @event editConflict
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event save
|
2013-03-20 07:09:43 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event showChanges
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
*/
|
|
|
|
|
2013-05-14 17:40:00 +00:00
|
|
|
/**
|
|
|
|
* @event noChanges
|
|
|
|
*/
|
|
|
|
|
2013-11-26 19:29:14 +00:00
|
|
|
/**
|
|
|
|
* @event saveErrorEmpty
|
|
|
|
* Fired when save API returns no data object
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event saveErrorSpamBlacklist
|
|
|
|
* Fired when save is considered spam or blacklisted
|
2018-11-23 17:01:25 +00:00
|
|
|
* TODO: Move this to the extension
|
2013-11-26 19:29:14 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event saveErrorAbuseFilter
|
|
|
|
* Fired when AbuseFilter throws warnings
|
2018-11-23 17:01:25 +00:00
|
|
|
* TODO: Move this to the extension
|
2013-11-26 19:29:14 +00:00
|
|
|
*/
|
|
|
|
|
2013-12-10 01:39:46 +00:00
|
|
|
/**
|
|
|
|
* @event saveErrorBadToken
|
2016-03-05 01:34:56 +00:00
|
|
|
* @param {boolean} willRetry Whether an automatic retry will occur
|
|
|
|
* Fired on save if we have to fetch a new edit token.
|
|
|
|
* This is mainly for analytical purposes.
|
2013-12-10 01:39:46 +00:00
|
|
|
*/
|
|
|
|
|
2013-11-26 19:29:14 +00:00
|
|
|
/**
|
|
|
|
* @event saveErrorNewUser
|
|
|
|
* Fired when user is logged in as a new user
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event saveErrorCaptcha
|
|
|
|
* Fired when saveError indicates captcha field is required
|
2018-11-23 17:01:25 +00:00
|
|
|
* TODO: Move this to the extension
|
2013-11-26 19:29:14 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event saveErrorUnknown
|
2016-03-05 00:46:12 +00:00
|
|
|
* @param {string} errorMsg Error message shown to the user
|
2013-11-26 19:29:14 +00:00
|
|
|
* Fired for any other type of save error
|
|
|
|
*/
|
|
|
|
|
2014-10-30 00:36:02 +00:00
|
|
|
/**
|
|
|
|
* @event saveErrorPageDeleted
|
|
|
|
* Fired when user tries to save page that was deleted after opening VE
|
|
|
|
*/
|
|
|
|
|
2015-03-18 21:19:17 +00:00
|
|
|
/**
|
|
|
|
* @event saveErrorTitleBlacklist
|
|
|
|
* Fired when the user tries to save page in violation of the TitleBlacklist
|
|
|
|
*/
|
|
|
|
|
2017-01-11 18:46:30 +00:00
|
|
|
/**
|
|
|
|
* @event saveErrorHookAborted
|
|
|
|
* Fired when the user tries to save page in violation of an extension
|
|
|
|
*/
|
|
|
|
|
2016-03-10 18:58:41 +00:00
|
|
|
/**
|
|
|
|
* @event saveErrorReadOnly
|
|
|
|
* Fired when the user tries to save page but the database is locked
|
|
|
|
*/
|
|
|
|
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
/**
|
|
|
|
* @event loadError
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event showChangesError
|
2013-03-20 07:09:43 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event serializeError
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
*/
|
|
|
|
|
2013-12-10 01:39:46 +00:00
|
|
|
/**
|
|
|
|
* @event serializeComplete
|
|
|
|
* Fired when serialization is complete
|
|
|
|
*/
|
|
|
|
|
2013-12-13 20:24:54 +00:00
|
|
|
/* Static Properties */
|
|
|
|
|
2013-12-10 01:39:46 +00:00
|
|
|
/**
|
2016-04-21 11:28:00 +00:00
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.static.name = 'article';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tracking name of target class. Used by ArticleTargetEvents to identify which target we are tracking.
|
2013-12-10 01:39:46 +00:00
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @property {string}
|
|
|
|
* @inheritable
|
|
|
|
*/
|
2016-04-21 11:28:00 +00:00
|
|
|
ve.init.mw.ArticleTarget.static.trackingName = 'mwTarget';
|
2013-12-10 01:39:46 +00:00
|
|
|
|
2014-10-29 01:19:52 +00:00
|
|
|
/**
|
2015-12-11 14:57:49 +00:00
|
|
|
* @inheritdoc
|
2014-10-29 01:19:52 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.static.integrationType = 'page';
|
2014-10-29 01:19:52 +00:00
|
|
|
|
2015-09-03 01:24:48 +00:00
|
|
|
/**
|
2015-12-11 14:57:49 +00:00
|
|
|
* @inheritdoc
|
2015-09-03 01:24:48 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.static.platformType = 'other';
|
2015-09-03 01:24:48 +00:00
|
|
|
|
2017-09-11 16:00:17 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.static.documentCommands = ve.init.mw.ArticleTarget.super.static.documentCommands.concat( [
|
|
|
|
// Make save commands triggerable from anywhere
|
|
|
|
'showSave',
|
|
|
|
'showChanges',
|
|
|
|
'showPreview',
|
|
|
|
'showMinoredit',
|
|
|
|
'showWatchthis'
|
|
|
|
] );
|
|
|
|
|
2017-11-17 21:15:27 +00:00
|
|
|
/* Static methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2018-03-12 12:24:18 +00:00
|
|
|
ve.init.mw.ArticleTarget.static.parseDocument = function ( documentString, mode, section ) {
|
2017-11-21 15:42:04 +00:00
|
|
|
// Add trailing linebreak to non-empty wikitext documents for consistency
|
2017-11-17 21:15:27 +00:00
|
|
|
// with old editor and usability. Will be stripped on save. T156609
|
2017-11-21 15:42:04 +00:00
|
|
|
if ( mode === 'source' && documentString ) {
|
2017-11-17 21:15:27 +00:00
|
|
|
documentString += '\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parent method
|
2018-03-12 12:24:18 +00:00
|
|
|
return ve.init.mw.ArticleTarget.super.static.parseDocument.call( this, documentString, mode, section );
|
2017-11-17 21:15:27 +00:00
|
|
|
};
|
|
|
|
|
2018-04-02 16:28:01 +00:00
|
|
|
/**
|
|
|
|
* Build DOM for the redirect page subtitle (#redirectsub).
|
|
|
|
*
|
|
|
|
* @return {jQuery}
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.static.buildRedirectSub = function () {
|
|
|
|
// Page subtitle
|
|
|
|
// Compare: Article::view()
|
|
|
|
return $( '<span>' )
|
|
|
|
.attr( 'id', 'redirectsub' )
|
|
|
|
.append( mw.message( 'redirectpagesub' ).parseDom() );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Build DOM for the redirect page content header (.redirectMsg).
|
|
|
|
*
|
|
|
|
* @param {string} title Redirect target
|
|
|
|
* @return {jQuery}
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.static.buildRedirectMsg = function ( title ) {
|
|
|
|
var $link;
|
|
|
|
|
|
|
|
$link = $( '<a>' )
|
|
|
|
.attr( {
|
|
|
|
href: mw.Title.newFromText( title ).getUrl(),
|
|
|
|
title: mw.msg( 'visualeditor-redirect-description', title )
|
|
|
|
} )
|
|
|
|
.text( title );
|
|
|
|
ve.init.platform.linkCache.styleElement( title, $link );
|
|
|
|
|
|
|
|
// Page content header
|
|
|
|
// Compare: Article::getRedirectHeaderHtml()
|
|
|
|
return $( '<div>' )
|
|
|
|
.addClass( 'redirectMsg' )
|
|
|
|
// Hack: This is normally inside #mw-content-text, but we may insert it before, so we need this.
|
|
|
|
.addClass( 'mw-content-' + mw.config.get( 'wgVisualEditor' ).pageLanguageDir )
|
|
|
|
.append(
|
|
|
|
$( '<p>' ).text( mw.msg( 'redirectto' ) ),
|
|
|
|
$( '<ul>' )
|
|
|
|
.addClass( 'redirectText' )
|
|
|
|
.append( $( '<li>' ).append( $link ) )
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2015-07-31 15:03:33 +00:00
|
|
|
/* Methods */
|
|
|
|
|
2016-05-26 12:08:26 +00:00
|
|
|
/**
|
2016-10-27 22:29:38 +00:00
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2016-11-30 12:03:56 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.setDefaultMode = function () {
|
|
|
|
var oldDefaultMode = this.defaultMode;
|
2016-10-27 22:29:38 +00:00
|
|
|
// Parent method
|
2016-11-30 12:03:56 +00:00
|
|
|
ve.init.mw.ArticleTarget.super.prototype.setDefaultMode.apply( this, arguments );
|
2016-10-27 22:29:38 +00:00
|
|
|
|
2016-11-30 12:03:56 +00:00
|
|
|
if ( this.defaultMode !== oldDefaultMode ) {
|
2016-08-25 21:02:40 +00:00
|
|
|
this.updateTabs( true );
|
2016-11-05 05:50:32 +00:00
|
|
|
if ( mw.libs.ve.setEditorPreference ) {
|
|
|
|
// only set up by DAT.init
|
2016-11-30 12:03:56 +00:00
|
|
|
mw.libs.ve.setEditorPreference( this.defaultMode === 'visual' ? 'visualeditor' : 'wikitext' );
|
2016-11-05 05:50:32 +00:00
|
|
|
}
|
2016-05-26 12:08:26 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-25 21:02:40 +00:00
|
|
|
/**
|
|
|
|
* Update state of editing tabs
|
2016-10-28 00:22:30 +00:00
|
|
|
*
|
|
|
|
* @param {boolean} editing Whether the editor is loaded.
|
2016-08-25 21:02:40 +00:00
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.updateTabs = function ( editing ) {
|
2017-03-28 17:01:46 +00:00
|
|
|
var $tab;
|
2016-08-25 21:02:40 +00:00
|
|
|
|
|
|
|
// Deselect current mode (e.g. "view" or "history"). In skins like monobook that don't have
|
|
|
|
// separate tab sections for content actions and namespaces the below is a no-op.
|
|
|
|
$( '#p-views' ).find( 'li.selected' ).removeClass( 'selected' );
|
|
|
|
|
|
|
|
if ( editing ) {
|
2016-12-08 19:21:48 +00:00
|
|
|
if ( this.section === 'new' ) {
|
2017-03-28 17:01:46 +00:00
|
|
|
$tab = $( '#ca-addsection' );
|
2016-12-13 15:19:05 +00:00
|
|
|
} else if ( $( '#ca-ve-edit' ).length ) {
|
|
|
|
if ( this.getDefaultMode() === 'visual' ) {
|
2017-03-28 17:01:46 +00:00
|
|
|
$tab = $( '#ca-ve-edit' );
|
2016-12-08 19:21:48 +00:00
|
|
|
} else {
|
2017-03-28 17:01:46 +00:00
|
|
|
$tab = $( '#ca-edit' );
|
2016-12-08 19:21:48 +00:00
|
|
|
}
|
2016-08-25 21:02:40 +00:00
|
|
|
} else {
|
|
|
|
// Single edit tab
|
2017-03-28 17:01:46 +00:00
|
|
|
$tab = $( '#ca-edit' );
|
2016-08-25 21:02:40 +00:00
|
|
|
}
|
|
|
|
} else {
|
2017-03-28 17:01:46 +00:00
|
|
|
$tab = $( '#ca-view' );
|
2016-08-25 21:02:40 +00:00
|
|
|
}
|
2017-03-28 17:01:46 +00:00
|
|
|
$tab.addClass( 'selected' );
|
2016-08-25 21:02:40 +00:00
|
|
|
};
|
|
|
|
|
2012-06-18 20:12:32 +00:00
|
|
|
/**
|
|
|
|
* Handle response to a successful load request.
|
|
|
|
*
|
|
|
|
* This method is called within the context of a target instance. If successful the DOM from the
|
2016-03-01 22:03:07 +00:00
|
|
|
* server will be parsed, stored in {this.doc} and then {this.documentReady} will be called.
|
2012-06-18 20:12:32 +00:00
|
|
|
*
|
|
|
|
* @method
|
2015-01-24 00:22:17 +00:00
|
|
|
* @param {Object} response API response data
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
* @param {string} status Text status message
|
2012-06-18 20:12:32 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.loadSuccess = function ( response ) {
|
2018-11-08 18:23:47 +00:00
|
|
|
var data = response ? ( response.visualeditor || response.visualeditoredit ) : null;
|
2012-12-13 00:22:10 +00:00
|
|
|
|
2016-10-26 00:19:05 +00:00
|
|
|
if ( !data || typeof data.content !== 'string' ) {
|
2016-12-21 03:19:19 +00:00
|
|
|
this.loadFail( 've-api', 'No HTML content in response from server' );
|
2012-06-18 20:12:32 +00:00
|
|
|
} else {
|
2017-12-07 11:14:00 +00:00
|
|
|
this.track( 'trace.parseResponse.enter' );
|
2013-04-18 01:22:39 +00:00
|
|
|
this.originalHtml = data.content;
|
2015-10-08 22:16:56 +00:00
|
|
|
this.etag = data.etag;
|
2018-11-29 14:38:53 +00:00
|
|
|
this.fromEditedState = !!data.fromEditedState;
|
2016-04-04 16:31:39 +00:00
|
|
|
this.switched = data.switched || 'wteswitched' in new mw.Uri( location.href ).query;
|
2017-06-08 13:43:09 +00:00
|
|
|
this.doc = this.constructor.static.parseDocument( this.originalHtml, this.getDefaultMode() );
|
2012-12-13 00:22:10 +00:00
|
|
|
|
2018-11-08 18:23:47 +00:00
|
|
|
// Properties that don't come from the API
|
2018-01-23 20:51:11 +00:00
|
|
|
this.initialSourceRange = data.initialSourceRange;
|
2018-11-08 18:23:47 +00:00
|
|
|
this.recovered = data.recovered;
|
2014-03-11 00:46:26 +00:00
|
|
|
|
2018-11-29 03:19:08 +00:00
|
|
|
// Parse data this not available in RESTBase
|
|
|
|
if ( !this.parseMetadata( response ) ) {
|
|
|
|
// Invalid metadata, loadFail() or load() has been called
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-12-07 11:14:00 +00:00
|
|
|
this.track( 'trace.parseResponse.exit' );
|
2018-02-21 22:58:50 +00:00
|
|
|
|
Load RL modules in one load.php request, rather than in two stages
This introduces TargetLoader, which manages plugins and RL modules
in a slightly more generic fashion so that Targets themselves don't
have to. This allows us to load all RL modules in one load.php
request, rather than first loading ViewPageTarget which then
loads the other modules.
TargetLoader loads in the bottom queue, so it will be loaded
as part of the main load.php request, but in VPT.init.js we
still have to wait for it with using() because it might not
have arrived yet. This also degrades gracefully on cached pages
where TargetLoader isn't in the bottom queue: it'll be loaded
as a separate request instead, which is suboptimal but no
worse that what we were doing before.
Right now TargetLoader is small enough that it could also be in
the top queue, but in the future we want to add things like
the action=visualeditor API request to it, and mw.Api is
relatively big.
Note: this also makes a breaking change to the plugin API:
plugin callbacks no longer receive the target instance
as a parameter, as they're now executed before the target
has been constructed rather than after. In the long term,
if we want to give plugins access to the target instance,
we could give them the target promise somehow. For now,
I've killed this feature because nothing used it and
the change from a direct object reference to a promise
would have been a breaking change anyway.
Also fixed incorrect documentation index for ve.init.mw.ViewPageTarget.init.
Bug: T53569
Change-Id: Ibfa6abbeaf872ae2aadc6ed9d5beba7473ea441a
2015-02-26 01:22:44 +00:00
|
|
|
// Everything worked, the page was loaded, continue initializing the editor
|
2015-12-11 14:57:49 +00:00
|
|
|
this.documentReady( this.doc );
|
2012-06-18 20:12:32 +00:00
|
|
|
}
|
2016-11-05 06:16:26 +00:00
|
|
|
|
|
|
|
if ( [ 'edit', 'submit' ].indexOf( mw.util.getParamValue( 'action' ) ) !== -1 ) {
|
2017-03-28 17:47:51 +00:00
|
|
|
$( '#firstHeading' ).text(
|
2018-05-04 13:30:10 +00:00
|
|
|
mw.Title.newFromText( this.getPageName() ).getPrefixedText()
|
2016-11-05 06:16:26 +00:00
|
|
|
);
|
|
|
|
}
|
2018-11-08 18:23:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse document metadata from the API response
|
|
|
|
*
|
|
|
|
* @param {Object} response API response data
|
2018-11-29 03:19:08 +00:00
|
|
|
* @return {boolean} Whether metadata was loaded successfully. If true, you should call
|
|
|
|
* loadSuccess(). If false, either that loadFail() has been called or we're retrying via load().
|
2018-11-08 18:23:47 +00:00
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.parseMetadata = function ( response ) {
|
|
|
|
var i, len, linkData, aboutDoc, docRevIdMatches, docRevId,
|
|
|
|
name, options, accesskey, title, $label, checkbox,
|
|
|
|
data = response ? ( response.visualeditor || response.visualeditoredit ) : null;
|
|
|
|
|
|
|
|
if ( !data ) {
|
|
|
|
this.loadFail( 've-api', 'No metadata content in response from server' );
|
2018-11-29 03:19:08 +00:00
|
|
|
return false;
|
2018-11-08 18:23:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.remoteNotices = ve.getObjectValues( data.notices );
|
|
|
|
this.protectedClasses = data.protectedClasses;
|
|
|
|
|
|
|
|
this.baseTimeStamp = data.basetimestamp;
|
|
|
|
this.startTimeStamp = data.starttimestamp;
|
|
|
|
this.revid = data.oldid;
|
2018-11-29 14:38:53 +00:00
|
|
|
this.preloaded = !!data.preloaded;
|
2018-11-08 18:23:47 +00:00
|
|
|
|
|
|
|
this.checkboxesDef = data.checkboxesDef;
|
|
|
|
this.checkboxesMessages = data.checkboxesMessages;
|
|
|
|
mw.messages.set( data.checkboxesMessages );
|
|
|
|
this.$templatesUsed = $( data.templates );
|
|
|
|
this.links = data.links;
|
|
|
|
|
|
|
|
// Populate link cache
|
|
|
|
if ( this.links ) {
|
|
|
|
// Format from the API: { missing: [titles], known: 1|[titles] }
|
|
|
|
// Format expected by LinkCache: { title: { missing: true|false } }
|
|
|
|
linkData = {};
|
|
|
|
for ( i = 0, len = this.links.missing.length; i < len; i++ ) {
|
|
|
|
linkData[ this.links.missing[ i ] ] = { missing: true };
|
|
|
|
}
|
|
|
|
if ( this.links.known === 1 ) {
|
|
|
|
// Set back to false by surfaceReady()
|
|
|
|
ve.init.platform.linkCache.setAssumeExistence( true );
|
|
|
|
} else {
|
|
|
|
for ( i = 0, len = this.links.known.length; i < len; i++ ) {
|
|
|
|
linkData[ this.links.known[ i ] ] = { missing: false };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ve.init.platform.linkCache.setMissing( linkData );
|
|
|
|
}
|
|
|
|
|
|
|
|
aboutDoc = this.doc.documentElement && this.doc.documentElement.getAttribute( 'about' );
|
|
|
|
if ( aboutDoc ) {
|
|
|
|
docRevIdMatches = aboutDoc.match( /revision\/([0-9]*)$/ );
|
|
|
|
if ( docRevIdMatches.length >= 2 ) {
|
|
|
|
docRevId = parseInt( docRevIdMatches[ 1 ] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( docRevId && docRevId !== this.revid ) {
|
|
|
|
if ( this.retriedRevIdConflict ) {
|
|
|
|
// Retried already, just error the second time.
|
|
|
|
this.loadFail(
|
|
|
|
've-api',
|
|
|
|
'Revision IDs (doc=' + docRevId + ',api=' + this.revid + ') ' +
|
|
|
|
'returned by server do not match'
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this.retriedRevIdConflict = true;
|
|
|
|
// TODO this retries both requests, in RESTbase mode we should only retry
|
|
|
|
// the request that gave us the lower revid
|
|
|
|
this.loading = false;
|
|
|
|
// HACK: Load with explicit revid to hopefully prevent this from happening again
|
|
|
|
this.requestedRevId = Math.max( docRevId, this.revid );
|
|
|
|
this.load();
|
|
|
|
}
|
2018-11-29 03:19:08 +00:00
|
|
|
return false;
|
2018-11-08 18:23:47 +00:00
|
|
|
} else {
|
|
|
|
// Set this to false after a successful load, so we don't immediately give up
|
|
|
|
// if a subsequent load mismatches again
|
|
|
|
this.retriedRevIdConflict = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.checkboxFields = [];
|
|
|
|
this.checkboxesByName = {};
|
2016-11-05 06:16:26 +00:00
|
|
|
|
2017-08-31 15:18:45 +00:00
|
|
|
if ( this.checkboxesDef ) {
|
|
|
|
for ( name in this.checkboxesDef ) {
|
|
|
|
options = this.checkboxesDef[ name ];
|
2016-11-05 06:16:26 +00:00
|
|
|
|
2017-08-31 15:18:45 +00:00
|
|
|
accesskey = null;
|
|
|
|
title = null;
|
|
|
|
if ( options.tooltip ) {
|
|
|
|
accesskey = mw.message( 'accesskey-' + options.tooltip ).text();
|
|
|
|
title = mw.message( 'tooltip-' + options.tooltip ).text();
|
2016-11-05 06:16:26 +00:00
|
|
|
}
|
2017-08-31 15:18:45 +00:00
|
|
|
if ( options[ 'title-message' ] ) {
|
|
|
|
title = mw.message( options[ 'title-message' ] ).text();
|
2016-11-05 06:16:26 +00:00
|
|
|
}
|
2017-08-31 15:18:45 +00:00
|
|
|
$label = $( '<span>' ).append( mw.message( options[ 'label-message' ] ).parseDom() );
|
|
|
|
ve.targetLinksToNewWindow( $label[ 0 ] );
|
|
|
|
|
2016-11-05 06:16:26 +00:00
|
|
|
checkbox = new OO.ui.CheckboxInputWidget( {
|
2017-08-31 15:18:45 +00:00
|
|
|
accessKey: accesskey,
|
|
|
|
selected: options.default,
|
2016-11-05 06:16:26 +00:00
|
|
|
classes: [ 've-ui-mwSaveDialog-checkbox-' + name ]
|
|
|
|
} );
|
2017-08-31 15:18:45 +00:00
|
|
|
|
|
|
|
this.checkboxFields.push(
|
2016-11-05 06:16:26 +00:00
|
|
|
new OO.ui.FieldLayout( checkbox, {
|
|
|
|
align: 'inline',
|
2017-08-31 15:18:45 +00:00
|
|
|
label: $label.contents(),
|
2016-11-05 06:16:26 +00:00
|
|
|
title: title
|
|
|
|
} )
|
|
|
|
);
|
2017-08-31 15:18:45 +00:00
|
|
|
this.checkboxesByName[ name ] = checkbox;
|
|
|
|
}
|
2016-11-05 06:16:26 +00:00
|
|
|
}
|
2018-11-29 03:19:08 +00:00
|
|
|
|
|
|
|
return true;
|
2012-06-18 20:12:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2015-12-11 14:57:49 +00:00
|
|
|
* @inheritdoc
|
2012-06-18 20:12:32 +00:00
|
|
|
*/
|
2015-12-11 14:57:49 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.documentReady = function () {
|
2016-03-01 22:03:07 +00:00
|
|
|
// We need to wait until documentReady as local notices may require special messages
|
2015-03-30 19:35:38 +00:00
|
|
|
this.editNotices = this.remoteNotices.concat(
|
|
|
|
this.localNoticeMessages.map( function ( msgKey ) {
|
|
|
|
return '<p>' + ve.init.platform.getParsedMessage( msgKey ) + '</p>';
|
|
|
|
} )
|
|
|
|
);
|
|
|
|
|
2012-06-18 20:12:32 +00:00
|
|
|
this.loading = false;
|
2015-10-08 22:16:56 +00:00
|
|
|
this.edited = this.fromEditedState;
|
2015-12-11 14:57:49 +00:00
|
|
|
|
|
|
|
// Parent method
|
|
|
|
ve.init.mw.ArticleTarget.super.prototype.documentReady.apply( this, arguments );
|
2012-06-18 20:12:32 +00:00
|
|
|
};
|
|
|
|
|
2015-07-01 11:11:36 +00:00
|
|
|
/**
|
2015-12-11 14:57:49 +00:00
|
|
|
* @inheritdoc
|
2015-07-01 11:11:36 +00:00
|
|
|
*/
|
2015-12-11 14:57:49 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.surfaceReady = function () {
|
2018-02-27 16:00:21 +00:00
|
|
|
var name, i, triggers,
|
2018-03-01 18:25:10 +00:00
|
|
|
target = this,
|
2016-12-13 21:55:01 +00:00
|
|
|
accessKeyPrefix = $.fn.updateTooltipAccessKeys.getAccessKeyPrefix().replace( /-/g, '+' ),
|
2018-02-21 22:58:50 +00:00
|
|
|
accessKeyModifiers = new ve.ui.Trigger( accessKeyPrefix + '-' ).modifiers,
|
|
|
|
surfaceModel = this.getSurface().getModel();
|
2016-12-13 21:55:01 +00:00
|
|
|
|
2015-12-11 14:57:49 +00:00
|
|
|
// loadSuccess() may have called setAssumeExistence( true );
|
|
|
|
ve.init.platform.linkCache.setAssumeExistence( false );
|
2018-02-21 22:58:50 +00:00
|
|
|
surfaceModel.connect( this, {
|
2015-12-11 14:57:49 +00:00
|
|
|
history: 'updateToolbarSaveButtonState'
|
|
|
|
} );
|
2015-07-01 11:11:36 +00:00
|
|
|
this.restoreEditSection();
|
2015-12-11 14:57:49 +00:00
|
|
|
|
2016-12-13 21:55:01 +00:00
|
|
|
// Iterate over the trigger registry and resolve any access key conflicts
|
|
|
|
for ( name in ve.ui.triggerRegistry.registry ) {
|
|
|
|
triggers = ve.ui.triggerRegistry.registry[ name ];
|
|
|
|
for ( i = 0; i < triggers.length; i++ ) {
|
|
|
|
if ( ve.compare( triggers[ i ].modifiers, accessKeyModifiers ) ) {
|
|
|
|
this.disableAccessKey( triggers[ i ].primary );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-21 22:58:50 +00:00
|
|
|
// Auto-save
|
|
|
|
if ( this.recovered ) {
|
|
|
|
// Restore auto-saved transactions if document state was recovered
|
|
|
|
try {
|
2018-03-04 23:31:49 +00:00
|
|
|
surfaceModel.restoreChanges();
|
2018-08-25 15:40:41 +00:00
|
|
|
ve.init.platform.notify(
|
|
|
|
ve.msg( 'visualeditor-autosave-recovered-text' ),
|
|
|
|
ve.msg( 'visualeditor-autosave-recovered-title' )
|
|
|
|
);
|
2018-02-21 22:58:50 +00:00
|
|
|
} catch ( e ) {
|
2018-02-27 16:00:21 +00:00
|
|
|
mw.log.warn( e );
|
2018-08-25 15:40:41 +00:00
|
|
|
ve.init.platform.notify(
|
|
|
|
ve.msg( 'visualeditor-autosave-not-recovered-text' ),
|
|
|
|
ve.msg( 'visualeditor-autosave-not-recovered-title' ),
|
|
|
|
{ type: 'error' }
|
|
|
|
);
|
2018-02-21 22:58:50 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// ...otherwise store this document state for later recovery
|
2018-03-07 18:38:35 +00:00
|
|
|
if ( this.fromEditedState ) {
|
2018-03-04 23:31:49 +00:00
|
|
|
// Store immediately if the document was previously edited
|
|
|
|
// (e.g. in a different mode)
|
|
|
|
this.storeDocState( this.originalHtml );
|
|
|
|
} else {
|
|
|
|
// Only store after the first change if this is an unmodified document
|
|
|
|
surfaceModel.once( 'undoStackChange', function () {
|
|
|
|
// Check the surface hasn't been destroyed
|
|
|
|
if ( target.getSurface() ) {
|
|
|
|
target.storeDocState( target.originalHtml );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
}
|
2018-02-21 22:58:50 +00:00
|
|
|
}
|
|
|
|
// Start auto-saving transactions
|
2018-02-27 16:00:21 +00:00
|
|
|
surfaceModel.startStoringChanges();
|
2018-02-28 14:36:58 +00:00
|
|
|
// TODO: Listen to autosaveFailed event to notify user
|
2018-02-21 22:58:50 +00:00
|
|
|
|
2018-06-10 15:04:15 +00:00
|
|
|
// Start loading easy-deflate module in the background, so it's
|
|
|
|
// already loaded when the save dialog is opened.
|
|
|
|
setTimeout( function () {
|
|
|
|
mw.loader.load( 'easy-deflate.deflate' );
|
|
|
|
}, 500 );
|
|
|
|
|
2015-12-11 14:57:49 +00:00
|
|
|
// Parent method
|
|
|
|
ve.init.mw.ArticleTarget.super.prototype.surfaceReady.apply( this, arguments );
|
2018-11-29 14:38:53 +00:00
|
|
|
|
|
|
|
mw.hook( 've.activationComplete' ).fire();
|
2015-07-01 11:11:36 +00:00
|
|
|
};
|
|
|
|
|
2018-02-21 22:58:50 +00:00
|
|
|
/**
|
|
|
|
* Store a snapshot of the current document state.
|
|
|
|
*
|
|
|
|
* @param {string} [html] Document HTML, will generate from current state if not provided
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.storeDocState = function ( html ) {
|
|
|
|
var mode = this.getSurface().getMode();
|
2018-02-27 16:00:21 +00:00
|
|
|
this.getSurface().getModel().storeDocState( {
|
2018-02-21 22:58:50 +00:00
|
|
|
request: {
|
2018-05-04 13:30:10 +00:00
|
|
|
pageName: this.getPageName(),
|
2018-02-21 22:58:50 +00:00
|
|
|
mode: mode,
|
|
|
|
// Only source mode fetches data by section
|
|
|
|
section: mode === 'source' ? this.section : null
|
|
|
|
},
|
|
|
|
response: {
|
|
|
|
etag: this.etag,
|
|
|
|
fromEditedState: this.fromEditedState,
|
|
|
|
switched: this.switched,
|
|
|
|
preloaded: this.preloaded,
|
|
|
|
notices: this.remoteNotices,
|
|
|
|
protectedClasses: this.protectedClasses,
|
|
|
|
basetimestamp: this.baseTimeStamp,
|
|
|
|
starttimestamp: this.startTimeStamp,
|
|
|
|
oldid: this.revid,
|
|
|
|
checkboxesDef: this.checkboxesDef,
|
2018-03-16 00:14:29 +00:00
|
|
|
checkboxesMessages: this.checkboxesMessages,
|
2018-02-21 22:58:50 +00:00
|
|
|
// Use $.prop as $templatesUsed may be empty
|
|
|
|
templates: this.$templatesUsed.prop( 'outerHTML' ) || '',
|
|
|
|
links: this.links
|
|
|
|
}
|
2018-02-27 16:00:21 +00:00
|
|
|
}, html );
|
2018-02-21 22:58:50 +00:00
|
|
|
};
|
|
|
|
|
2016-12-13 21:55:01 +00:00
|
|
|
/**
|
|
|
|
* Disable an access key by removing the attribute from any element containing it
|
|
|
|
*
|
|
|
|
* @param {string} key Access key
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.disableAccessKey = function ( key ) {
|
|
|
|
$( '[accesskey=' + key + ']' ).each( function () {
|
|
|
|
var $this = $( this );
|
|
|
|
|
|
|
|
$this
|
|
|
|
.attr( 'data-old-accesskey', $this.attr( 'accesskey' ) )
|
|
|
|
.removeAttr( 'accesskey' );
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Re-enable all access keys
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.restoreAccessKeys = function () {
|
|
|
|
$( '[data-old-accesskey]' ).each( function () {
|
|
|
|
var $this = $( this );
|
|
|
|
|
|
|
|
$this
|
|
|
|
.attr( 'accesskey', $this.attr( 'data-old-accesskey' ) )
|
|
|
|
.removeAttr( 'data-old-accesskey' );
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
2012-06-18 20:12:32 +00:00
|
|
|
/**
|
2013-01-15 23:38:49 +00:00
|
|
|
* Handle an unsuccessful load request.
|
2012-06-18 20:12:32 +00:00
|
|
|
*
|
|
|
|
* This method is called within the context of a target instance.
|
|
|
|
*
|
|
|
|
* @method
|
2016-12-21 03:19:19 +00:00
|
|
|
* @param {string} code Error type text from mw.Api
|
|
|
|
* @param {Object|string} errorDetails Either an object containing xhr, textStatus and exception keys, or a string.
|
2013-10-22 17:54:59 +00:00
|
|
|
* @fires loadError
|
2012-06-18 20:12:32 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.loadFail = function () {
|
2012-06-18 20:12:32 +00:00
|
|
|
this.loading = false;
|
2015-08-04 13:37:13 +00:00
|
|
|
this.emit( 'loadError' );
|
2012-06-18 20:12:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2013-01-15 23:38:49 +00:00
|
|
|
* Handle a successful save request.
|
2012-06-18 20:12:32 +00:00
|
|
|
*
|
|
|
|
* This method is called within the context of a target instance.
|
|
|
|
*
|
|
|
|
* @method
|
2014-04-23 19:30:48 +00:00
|
|
|
* @param {HTMLDocument} doc HTML document we tried to save
|
|
|
|
* @param {Object} saveData Options that were used
|
2012-12-07 16:23:23 +00:00
|
|
|
* @param {Object} response Response data
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
* @param {string} status Text status message
|
2012-06-18 20:12:32 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.saveSuccess = function ( doc, saveData, response ) {
|
2013-07-11 17:09:28 +00:00
|
|
|
var data = response.visualeditoredit;
|
2015-08-19 18:05:01 +00:00
|
|
|
this.saving = false;
|
2015-03-19 01:51:30 +00:00
|
|
|
if ( !data ) {
|
2017-05-02 15:39:04 +00:00
|
|
|
this.saveFail( doc, saveData, false, null, 'Invalid response from server', response );
|
2012-11-24 01:41:07 +00:00
|
|
|
} else if ( data.result !== 'success' ) {
|
2013-06-29 01:55:09 +00:00
|
|
|
// Note, this could be any of db failure, hookabort, badtoken or even a captcha
|
2017-05-02 15:39:04 +00:00
|
|
|
this.saveFail( doc, saveData, false, null, 'Save failure', response );
|
2012-06-18 20:12:32 +00:00
|
|
|
} else if ( typeof data.content !== 'string' ) {
|
2017-05-02 15:39:04 +00:00
|
|
|
this.saveFail( doc, saveData, false, null, 'Invalid HTML content in response from server', response );
|
2012-06-18 20:12:32 +00:00
|
|
|
} else {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.saveComplete(
|
2014-06-21 01:37:30 +00:00
|
|
|
data.content,
|
|
|
|
data.categorieshtml,
|
|
|
|
data.newrevid,
|
2015-08-04 01:01:04 +00:00
|
|
|
data.isRedirect,
|
2014-10-31 00:26:32 +00:00
|
|
|
data.displayTitleHtml,
|
2014-11-07 00:31:34 +00:00
|
|
|
data.lastModified,
|
2015-07-27 19:27:03 +00:00
|
|
|
data.contentSub,
|
|
|
|
data.modules,
|
|
|
|
data.jsconfigvars
|
2014-06-21 01:37:30 +00:00
|
|
|
);
|
2012-06-18 20:12:32 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-08-04 13:37:13 +00:00
|
|
|
/**
|
|
|
|
* Handle successful DOM save event.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {string} html Rendered page HTML from server
|
|
|
|
* @param {string} categoriesHtml Rendered categories HTML from server
|
|
|
|
* @param {number} newid New revision id, undefined if unchanged
|
|
|
|
* @param {boolean} isRedirect Whether this page is a redirect or not
|
|
|
|
* @param {string} displayTitle What HTML to show as the page title
|
|
|
|
* @param {Object} lastModified Object containing user-formatted date
|
|
|
|
* and time strings, or undefined if we made no change.
|
|
|
|
* @param {string} contentSub HTML to show as the content subtitle
|
2015-07-27 19:27:03 +00:00
|
|
|
* @param {Array} modules The modules to be loaded on the page
|
|
|
|
* @param {Object} jsconfigvars The mw.config values needed on the page
|
2015-08-04 13:37:13 +00:00
|
|
|
* @fires save
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.saveComplete = function () {
|
2017-03-09 23:26:08 +00:00
|
|
|
this.editSummaryValue = null;
|
|
|
|
this.initialEditSummary = null;
|
|
|
|
|
2015-08-05 21:43:23 +00:00
|
|
|
this.saveDeferred.resolve();
|
2015-08-04 13:37:13 +00:00
|
|
|
this.emit( 'save' );
|
|
|
|
};
|
|
|
|
|
2012-06-18 20:12:32 +00:00
|
|
|
/**
|
2013-01-15 23:38:49 +00:00
|
|
|
* Handle an unsuccessful save request.
|
2012-06-18 20:12:32 +00:00
|
|
|
*
|
2012-12-07 16:23:23 +00:00
|
|
|
* @method
|
2014-04-23 19:30:48 +00:00
|
|
|
* @param {HTMLDocument} doc HTML document we tried to save
|
|
|
|
* @param {Object} saveData Options that were used
|
2017-04-20 20:39:25 +00:00
|
|
|
* @param {boolean} wasRetry Whether this was a retry after a 'badtoken' error
|
2012-12-07 16:23:23 +00:00
|
|
|
* @param {Object} jqXHR
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
* @param {string} status Text status message
|
2013-06-29 01:55:09 +00:00
|
|
|
* @param {Object|null} data API response data
|
2012-12-07 16:23:23 +00:00
|
|
|
*/
|
2017-04-20 20:39:25 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.saveFail = function ( doc, saveData, wasRetry, jqXHR, status, data ) {
|
2018-11-23 17:01:25 +00:00
|
|
|
var editApi, name, handler,
|
2018-11-29 15:47:03 +00:00
|
|
|
saveErrorHandlerFactory = ve.init.mw.saveErrorHandlerFactory,
|
2014-11-27 20:51:04 +00:00
|
|
|
target = this;
|
2015-08-04 13:37:13 +00:00
|
|
|
|
2012-12-07 16:23:23 +00:00
|
|
|
this.saving = false;
|
2015-08-04 13:37:13 +00:00
|
|
|
this.pageDeletedWarning = false;
|
2013-11-26 19:29:14 +00:00
|
|
|
|
|
|
|
// Handle empty response
|
|
|
|
if ( !data ) {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.saveErrorEmpty();
|
2013-11-26 19:29:14 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-03-19 01:51:30 +00:00
|
|
|
|
2013-11-26 19:29:14 +00:00
|
|
|
// Handle token errors
|
|
|
|
if ( data.error && data.error.code === 'badtoken' ) {
|
2017-04-20 20:39:25 +00:00
|
|
|
if ( wasRetry ) {
|
|
|
|
this.saveErrorBadToken( null, true );
|
|
|
|
return;
|
|
|
|
}
|
2017-02-07 17:17:54 +00:00
|
|
|
this.refreshEditToken().done( function ( userChanged ) {
|
|
|
|
// target.editToken has been refreshed
|
|
|
|
if ( userChanged ) {
|
|
|
|
target.saveErrorBadToken( mw.user.isAnon() ? null : mw.user.getName(), false );
|
|
|
|
} else {
|
|
|
|
// New session is the same user still; retry
|
|
|
|
target.emit( 'saveErrorBadToken', true );
|
2017-04-20 20:39:25 +00:00
|
|
|
target.save( doc, saveData, true );
|
2017-02-07 17:17:54 +00:00
|
|
|
}
|
|
|
|
} ).fail( function () {
|
|
|
|
target.saveErrorBadToken( null, true );
|
|
|
|
} );
|
2013-11-26 19:29:14 +00:00
|
|
|
return;
|
2015-03-19 01:51:30 +00:00
|
|
|
} else if ( data.error && data.error.code === 'editconflict' ) {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.editConflict();
|
2015-03-19 01:51:30 +00:00
|
|
|
return;
|
2014-10-30 00:36:02 +00:00
|
|
|
} else if ( data.error && data.error.code === 'pagedeleted' ) {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.saveErrorPageDeleted();
|
2015-03-19 01:51:30 +00:00
|
|
|
return;
|
2017-01-11 18:46:30 +00:00
|
|
|
} else if ( data.error && data.error.code === 'hookaborted' ) {
|
|
|
|
this.saveErrorHookAborted();
|
|
|
|
return;
|
2016-03-10 18:58:41 +00:00
|
|
|
} else if ( data.error && data.error.code === 'readonly' ) {
|
2017-04-20 22:06:34 +00:00
|
|
|
this.saveErrorReadOnly();
|
2016-03-10 18:58:41 +00:00
|
|
|
return;
|
2013-11-26 19:29:14 +00:00
|
|
|
}
|
|
|
|
|
2018-11-29 15:47:03 +00:00
|
|
|
for ( name in saveErrorHandlerFactory.registry ) {
|
|
|
|
handler = saveErrorHandlerFactory.lookup( name );
|
2018-12-07 19:17:16 +00:00
|
|
|
if ( handler.static.matchFunction( data ) ) {
|
|
|
|
handler.static.process( data, this );
|
2018-11-23 17:01:25 +00:00
|
|
|
// Error was handled
|
|
|
|
return;
|
|
|
|
}
|
2013-11-26 19:29:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handle (other) unknown and/or unrecoverable errors
|
2018-12-07 19:17:16 +00:00
|
|
|
editApi = ve.getProp( data, 'visualeditoredit', 'edit' ) || {};
|
2015-08-04 13:37:13 +00:00
|
|
|
this.saveErrorUnknown( editApi, data );
|
2012-12-07 16:23:23 +00:00
|
|
|
};
|
|
|
|
|
2015-08-04 13:37:13 +00:00
|
|
|
/**
|
|
|
|
* Show an save process error message
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {string|jQuery|Node[]} msg Message content (string of HTML, jQuery object or array of
|
|
|
|
* Node objects)
|
|
|
|
* @param {boolean} [allowReapply=true] Whether or not to allow the user to reapply.
|
|
|
|
* Reset when swapping panels. Assumed to be true unless explicitly set to false.
|
|
|
|
* @param {boolean} [warning=false] Whether or not this is a warning.
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.showSaveError = function ( msg, allowReapply, warning ) {
|
2015-08-05 21:43:23 +00:00
|
|
|
this.saveDeferred.reject( [ new OO.ui.Error( msg, { recoverable: allowReapply, warning: warning } ) ] );
|
2015-08-04 13:37:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle general save error
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @fires saveErrorEmpty
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.saveErrorEmpty = function () {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.showSaveError( ve.msg( 'visualeditor-saveerror', 'Empty server response' ), false /* prevents reapply */ );
|
|
|
|
this.emit( 'saveErrorEmpty' );
|
|
|
|
};
|
|
|
|
|
2017-01-11 18:46:30 +00:00
|
|
|
/**
|
|
|
|
* Handle hook abort save error
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @fires saveErrorHookAborted
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.saveErrorHookAborted = function () {
|
|
|
|
this.showSaveError( mw.msg( 'visualeditor-saveerror-hookaborted' ) );
|
|
|
|
this.emit( 'saveErrorHookAborted' );
|
|
|
|
};
|
|
|
|
|
2015-08-04 13:37:13 +00:00
|
|
|
/**
|
2016-03-05 01:34:56 +00:00
|
|
|
* Handle token fetch indicating another user is logged in, and token fetch errors.
|
2015-08-04 13:37:13 +00:00
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {string|null} username Name of newly logged-in user, or null if anonymous
|
2016-03-05 01:34:56 +00:00
|
|
|
* @param {boolean} [error=false] Whether there was an error trying to figure out who we're logged in as
|
|
|
|
* @fires saveErrorBadToken
|
2015-08-04 13:37:13 +00:00
|
|
|
* @fires saveErrorNewUser
|
|
|
|
*/
|
2016-03-05 01:34:56 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.saveErrorBadToken = function ( username, error ) {
|
|
|
|
var userMsg,
|
|
|
|
$msg = $( document.createTextNode( mw.msg( 'visualeditor-savedialog-error-badtoken' ) + ' ' ) );
|
|
|
|
|
|
|
|
if ( error ) {
|
|
|
|
this.emit( 'saveErrorBadToken', false );
|
|
|
|
$msg = $msg.add( document.createTextNode( mw.msg( 'visualeditor-savedialog-identify-trylogin' ) ) );
|
2015-08-04 13:37:13 +00:00
|
|
|
} else {
|
2016-03-05 01:34:56 +00:00
|
|
|
this.emit( 'saveErrorNewUser' );
|
|
|
|
if ( username === null ) {
|
|
|
|
userMsg = 'visualeditor-savedialog-identify-anon';
|
|
|
|
} else {
|
|
|
|
userMsg = 'visualeditor-savedialog-identify-user';
|
|
|
|
}
|
2016-12-20 02:18:31 +00:00
|
|
|
$msg = $msg.add( mw.message( userMsg, username ).parseDom() );
|
2015-08-04 13:37:13 +00:00
|
|
|
}
|
2016-03-05 01:34:56 +00:00
|
|
|
this.showSaveError( $msg );
|
2015-08-04 13:37:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle unknown save error
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {Object} editApi
|
|
|
|
* @param {Object|null} data API response data
|
2016-03-05 00:46:12 +00:00
|
|
|
* @fires saveErrorUnknown
|
2015-08-04 13:37:13 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.saveErrorUnknown = function ( editApi, data ) {
|
2016-03-11 17:34:53 +00:00
|
|
|
var errorMsg = ( editApi && editApi.info ) || ( data && data.error && data.error.info ),
|
2016-12-16 11:28:38 +00:00
|
|
|
errorCode = ( editApi && editApi.code ) || ( data && data.error && data.error.code ),
|
|
|
|
unknown = 'Unknown error';
|
|
|
|
|
2017-01-11 18:45:43 +00:00
|
|
|
if ( data.xhr && data.xhr.status !== 200 ) {
|
2016-12-16 11:28:38 +00:00
|
|
|
unknown += ', HTTP status ' + data.xhr.status;
|
|
|
|
}
|
2016-03-05 00:46:12 +00:00
|
|
|
|
2015-08-04 13:37:13 +00:00
|
|
|
this.showSaveError(
|
2016-12-16 11:28:38 +00:00
|
|
|
$( document.createTextNode( errorMsg || errorCode || unknown ) ),
|
2015-08-04 13:37:13 +00:00
|
|
|
false // prevents reapply
|
|
|
|
);
|
2016-12-16 11:28:38 +00:00
|
|
|
this.emit( 'saveErrorUnknown', errorCode || errorMsg || unknown );
|
2015-08-04 13:37:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle page deleted error
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @fires saveErrorPageDeleted
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.saveErrorPageDeleted = function () {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.pageDeletedWarning = true;
|
|
|
|
this.showSaveError( mw.msg( 'visualeditor-recreate', mw.msg( 'ooui-dialog-process-continue' ) ), true, true );
|
|
|
|
this.emit( 'saveErrorPageDeleted' );
|
|
|
|
};
|
|
|
|
|
2016-03-10 18:58:41 +00:00
|
|
|
/**
|
|
|
|
* Handle read only error
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @fires saveErrorReadOnly
|
|
|
|
*/
|
2017-04-20 22:06:34 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.saveErrorReadOnly = function () {
|
|
|
|
this.showSaveError( $( $.parseHTML( mw.message( 'apierror-readonly' ).parse() ) ), true, true );
|
2016-03-10 18:58:41 +00:00
|
|
|
this.emit( 'saveErrorReadOnly' );
|
|
|
|
};
|
|
|
|
|
2015-08-04 13:37:13 +00:00
|
|
|
/**
|
|
|
|
* Handle an edit conflict
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @fires editConflict
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.editConflict = function () {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.emit( 'editConflict' );
|
2015-09-25 22:59:08 +00:00
|
|
|
this.saveDialog.popPending();
|
|
|
|
this.saveDialog.swapPanel( 'conflict' );
|
2015-08-04 13:37:13 +00:00
|
|
|
};
|
|
|
|
|
2012-11-28 23:57:00 +00:00
|
|
|
/**
|
2013-01-15 23:38:49 +00:00
|
|
|
* Handle a successful serialize request.
|
2012-11-28 23:57:00 +00:00
|
|
|
*
|
|
|
|
* This method is called within the context of a target instance.
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @method
|
2015-08-19 18:33:59 +00:00
|
|
|
* @param {Object} response API response data
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
* @param {string} status Text status message
|
2013-12-10 01:39:46 +00:00
|
|
|
* @fires serializeComplete
|
2012-11-28 23:57:00 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.serializeSuccess = function ( response ) {
|
2016-09-08 04:28:59 +00:00
|
|
|
var data = response.visualeditoredit;
|
2015-08-19 18:05:01 +00:00
|
|
|
this.serializing = false;
|
2012-11-28 23:57:00 +00:00
|
|
|
if ( !data && !response.error ) {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.serializeFail( null, 'Invalid response from server', null );
|
2013-05-15 15:28:51 +00:00
|
|
|
} else if ( response.error ) {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.serializeFail(
|
2015-07-31 15:03:33 +00:00
|
|
|
null, 'Unsuccessful request: ' + response.error.info, null
|
2013-05-15 15:28:51 +00:00
|
|
|
);
|
|
|
|
} else if ( data.result === 'error' ) {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.serializeFail( null, 'Server error', null );
|
2012-11-28 23:57:00 +00:00
|
|
|
} else if ( typeof data.content !== 'string' ) {
|
2015-08-04 13:37:13 +00:00
|
|
|
this.serializeFail(
|
2015-07-31 15:03:33 +00:00
|
|
|
null, 'No Wikitext content in response from server', null
|
2012-11-28 23:57:00 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
if ( typeof this.serializeCallback === 'function' ) {
|
|
|
|
this.serializeCallback( data.content );
|
2013-12-10 01:39:46 +00:00
|
|
|
this.emit( 'serializeComplete' );
|
2012-11-28 23:57:00 +00:00
|
|
|
delete this.serializeCallback;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2013-01-15 23:38:49 +00:00
|
|
|
* Handle an unsuccessful serialize request.
|
2012-11-28 23:57:00 +00:00
|
|
|
*
|
|
|
|
* This method is called within the context of a target instance.
|
|
|
|
*
|
|
|
|
* @method
|
2013-03-20 07:09:43 +00:00
|
|
|
* @param {jqXHR|null} jqXHR
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
* @param {string} status Text status message
|
2013-03-20 07:09:43 +00:00
|
|
|
* @param {Mixed|null} error HTTP status text
|
2013-10-22 17:54:59 +00:00
|
|
|
* @fires serializeError
|
2012-11-28 23:57:00 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.serializeFail = function () {
|
2012-11-28 23:57:00 +00:00
|
|
|
this.serializing = false;
|
2015-08-04 13:37:13 +00:00
|
|
|
this.emit( 'serializeError' );
|
2012-11-28 23:57:00 +00:00
|
|
|
};
|
|
|
|
|
2015-08-05 21:43:23 +00:00
|
|
|
/**
|
|
|
|
* Handle clicks on the review button in the save dialog.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @fires saveReview
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.onSaveDialogReview = function () {
|
2016-10-31 17:17:50 +00:00
|
|
|
if ( !this.saveDialog.hasDiff ) {
|
2015-08-05 21:43:23 +00:00
|
|
|
this.emit( 'saveReview' );
|
|
|
|
this.saveDialog.pushPending();
|
|
|
|
if ( this.pageExists ) {
|
|
|
|
// Has no callback, handled via target.showChangesDiff
|
2016-09-02 19:06:04 +00:00
|
|
|
this.showChanges( this.getDocToSave() );
|
2015-08-05 21:43:23 +00:00
|
|
|
} else {
|
2016-09-02 19:06:04 +00:00
|
|
|
this.serialize( this.getDocToSave(), this.onSaveDialogReviewComplete.bind( this ) );
|
2015-08-05 21:43:23 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.saveDialog.swapPanel( 'review' );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-09-01 23:54:56 +00:00
|
|
|
/**
|
|
|
|
* Handle clicks on the show preview button in the save dialog.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @fires savePreview
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.onSaveDialogPreview = function () {
|
2016-12-08 19:24:10 +00:00
|
|
|
var wikitext,
|
|
|
|
target = this;
|
2017-03-30 16:37:54 +00:00
|
|
|
|
2016-09-01 23:54:56 +00:00
|
|
|
if ( !this.saveDialog.$previewViewer.children().length ) {
|
|
|
|
this.emit( 'savePreview' );
|
|
|
|
this.saveDialog.pushPending();
|
2016-12-08 19:24:10 +00:00
|
|
|
|
|
|
|
wikitext = this.getDocToSave();
|
|
|
|
if ( this.sectionTitle && this.sectionTitle.getValue() ) {
|
|
|
|
wikitext = '== ' + this.sectionTitle.getValue() + ' ==\n\n' + wikitext;
|
|
|
|
}
|
|
|
|
|
2018-05-04 13:30:10 +00:00
|
|
|
this.getContentApi().post( {
|
2016-09-01 23:54:56 +00:00
|
|
|
action: 'visualeditor',
|
2017-08-13 19:22:04 +00:00
|
|
|
paction: 'parsedoc',
|
2018-05-04 13:30:10 +00:00
|
|
|
page: this.getPageName(),
|
2016-12-08 19:24:10 +00:00
|
|
|
wikitext: wikitext,
|
2016-09-20 23:04:31 +00:00
|
|
|
pst: true
|
2016-09-01 23:54:56 +00:00
|
|
|
} ).always( function ( response, details ) {
|
2017-08-13 19:22:04 +00:00
|
|
|
var doc,
|
2017-03-30 16:37:54 +00:00
|
|
|
baseDoc = target.getSurface().getModel().getDocument().getHtmlDocument();
|
|
|
|
|
2016-09-01 23:54:56 +00:00
|
|
|
if ( ve.getProp( response, 'visualeditor', 'result' ) === 'success' ) {
|
2017-08-13 19:22:04 +00:00
|
|
|
doc = target.constructor.static.parseDocument( response.visualeditor.content, 'visual' );
|
2017-06-17 01:20:06 +00:00
|
|
|
target.saveDialog.showPreview( doc, baseDoc );
|
2017-03-30 16:37:54 +00:00
|
|
|
|
2016-09-01 23:54:56 +00:00
|
|
|
} else {
|
2017-03-30 16:37:54 +00:00
|
|
|
target.saveDialog.showPreview(
|
2017-06-17 01:20:06 +00:00
|
|
|
ve.msg(
|
2017-03-30 16:37:54 +00:00
|
|
|
'visualeditor-loaderror-message',
|
|
|
|
ve.getProp( details, 'error', 'info' ) || 'Failed to connect'
|
2017-06-17 01:20:06 +00:00
|
|
|
)
|
2017-03-30 16:37:54 +00:00
|
|
|
);
|
2016-09-01 23:54:56 +00:00
|
|
|
}
|
2016-12-08 19:24:10 +00:00
|
|
|
target.bindSaveDialogClearDiff();
|
2016-09-01 23:54:56 +00:00
|
|
|
} );
|
|
|
|
} else {
|
|
|
|
this.saveDialog.swapPanel( 'preview' );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-12-08 19:24:10 +00:00
|
|
|
/**
|
|
|
|
* Clear the save dialog's diff cache when the document changes
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.bindSaveDialogClearDiff = function () {
|
|
|
|
// Invalidate the viewer wikitext on next change
|
|
|
|
this.getSurface().getModel().getDocument().once( 'transact',
|
|
|
|
this.saveDialog.clearDiff.bind( this.saveDialog )
|
|
|
|
);
|
|
|
|
if ( this.sectionTitle ) {
|
|
|
|
this.sectionTitle.once( 'change', this.saveDialog.clearDiff.bind( this.saveDialog ) );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-08-05 21:43:23 +00:00
|
|
|
/**
|
|
|
|
* Handle completed serialize request for diff views for new page creations.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {string} wikitext
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.onSaveDialogReviewComplete = function ( wikitext ) {
|
2016-12-08 19:24:10 +00:00
|
|
|
this.bindSaveDialogClearDiff();
|
2017-03-30 16:37:54 +00:00
|
|
|
this.saveDialog.setDiffAndReview(
|
2017-04-10 11:38:11 +00:00
|
|
|
$.Deferred().resolve( $( '<pre>' ).text( wikitext ) ).promise(),
|
2017-05-08 18:57:43 +00:00
|
|
|
this.getVisualDiffGeneratorPromise(),
|
2017-03-30 16:37:54 +00:00
|
|
|
this.getSurface().getModel().getDocument().getHtmlDocument()
|
|
|
|
);
|
2016-10-31 17:17:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a visual diff object for the current document state
|
|
|
|
*
|
2017-07-17 16:38:32 +00:00
|
|
|
* @return {jQuery.Promise} Promise resolving with a generator for a ve.dm.VisualDiff visual diff
|
2016-10-31 17:17:50 +00:00
|
|
|
*/
|
2017-05-08 18:57:43 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.getVisualDiffGeneratorPromise = function () {
|
2017-07-17 16:38:32 +00:00
|
|
|
var target = this;
|
2017-04-09 21:28:53 +00:00
|
|
|
|
2017-07-17 16:38:32 +00:00
|
|
|
return mw.loader.using( 'ext.visualEditor.diffLoader' ).then( function () {
|
|
|
|
var newRevPromise;
|
2017-04-09 21:28:53 +00:00
|
|
|
|
2017-07-17 16:38:32 +00:00
|
|
|
if ( !target.originalDmDocPromise ) {
|
|
|
|
if ( !target.fromEditedState && target.getSurface().getMode() === 'visual' ) {
|
|
|
|
// If this.doc was loaded from an un-edited state and in visual mode,
|
|
|
|
// then just parse it to get originalDmDoc, otherwise we need to
|
|
|
|
// re-fetch the HTML
|
|
|
|
target.originalDmDocPromise = $.Deferred().resolve( target.constructor.static.createModelFromDom( target.doc, 'visual' ) ).promise();
|
|
|
|
} else {
|
2018-05-04 13:30:10 +00:00
|
|
|
target.originalDmDocPromise = mw.libs.ve.diffLoader.fetchRevision( target.revid, target.getPageName(), undefined, target.section !== null ? target.section : undefined );
|
2017-07-17 16:38:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( target.getSurface().getMode() === 'source' ) {
|
2018-08-16 20:45:28 +00:00
|
|
|
newRevPromise = target.getContentApi().post( {
|
|
|
|
action: 'visualeditor',
|
|
|
|
paction: 'parsedoc',
|
|
|
|
page: target.getPageName(),
|
|
|
|
wikitext: ve.init.target.getSurface().getDom(),
|
|
|
|
pst: true
|
|
|
|
} ).then( function ( response ) {
|
|
|
|
// Use anonymous function to avoid passing through API promise argument
|
|
|
|
return mw.libs.ve.diffLoader.getModelFromResponse( response );
|
|
|
|
} );
|
2017-07-17 16:38:32 +00:00
|
|
|
|
|
|
|
return mw.libs.ve.diffLoader.getVisualDiffGeneratorPromise( target.originalDmDocPromise, newRevPromise );
|
|
|
|
} else {
|
|
|
|
return target.originalDmDocPromise.then( function ( originalDmDoc ) {
|
|
|
|
return function () {
|
|
|
|
return new ve.dm.VisualDiff( originalDmDoc, target.getSurface().getModel().getDocument() );
|
|
|
|
};
|
2017-04-09 21:28:53 +00:00
|
|
|
} );
|
|
|
|
}
|
2017-07-17 16:38:32 +00:00
|
|
|
} );
|
2015-08-05 21:43:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle clicks on the resolve conflict button in the conflict dialog.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.onSaveDialogResolveConflict = function () {
|
2017-03-29 21:21:17 +00:00
|
|
|
var fields = { wpSave: 1 };
|
|
|
|
|
|
|
|
if ( this.getSurface().getMode() === 'source' && this.section !== null ) {
|
|
|
|
fields.section = this.section;
|
|
|
|
}
|
2015-08-05 21:43:23 +00:00
|
|
|
// Get Wikitext from the DOM, and set up a submit call when it's done
|
|
|
|
this.serialize(
|
2016-09-02 19:06:04 +00:00
|
|
|
this.getDocToSave(),
|
2017-03-29 21:21:17 +00:00
|
|
|
this.submitWithSaveFields.bind( this, fields )
|
2015-08-05 21:43:23 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle dialog retry events
|
|
|
|
* So we can handle trying to save again after page deletion warnings
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.onSaveDialogRetry = function () {
|
2015-08-05 21:43:23 +00:00
|
|
|
if ( this.pageDeletedWarning ) {
|
|
|
|
this.recreating = true;
|
|
|
|
this.pageExists = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle dialog close events.
|
|
|
|
*
|
|
|
|
* @fires saveWorkflowEnd
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.onSaveDialogClose = function () {
|
2015-08-05 21:43:23 +00:00
|
|
|
this.emit( 'saveWorkflowEnd' );
|
|
|
|
};
|
|
|
|
|
2015-03-31 19:52:14 +00:00
|
|
|
/**
|
|
|
|
* Get deflated HTML. This function is async because easy-deflate may not have finished loading yet.
|
|
|
|
*
|
|
|
|
* @param {HTMLDocument} newDoc Document to get HTML for
|
|
|
|
* @return {jQuery.Promise} Promise resolved with deflated HTML
|
|
|
|
* @see #getHtml
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.deflateHtml = function ( newDoc ) {
|
2015-12-11 14:57:49 +00:00
|
|
|
var html = this.getHtml( newDoc, this.doc );
|
2015-03-31 19:52:14 +00:00
|
|
|
return mw.loader.using( 'easy-deflate.deflate' )
|
|
|
|
.then( function () {
|
|
|
|
return EasyDeflate.deflate( html );
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
2012-06-11 06:54:41 +00:00
|
|
|
/**
|
2015-02-23 20:51:33 +00:00
|
|
|
* Load the editor.
|
2012-06-11 06:54:41 +00:00
|
|
|
*
|
2015-03-13 15:50:23 +00:00
|
|
|
* This method initiates an API request for the page data unless dataPromise is passed in,
|
|
|
|
* in which case it waits for that promise instead.
|
2012-06-11 06:54:41 +00:00
|
|
|
*
|
2015-03-13 15:50:23 +00:00
|
|
|
* @param {jQuery.Promise} [dataPromise] Promise for pending request, if any
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {boolean} Loading has been started
|
2012-06-11 06:54:41 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.load = function ( dataPromise ) {
|
2012-06-11 06:54:41 +00:00
|
|
|
// Prevent duplicate requests
|
2012-06-18 20:12:32 +00:00
|
|
|
if ( this.loading ) {
|
2012-06-11 06:54:41 +00:00
|
|
|
return false;
|
|
|
|
}
|
2015-04-09 03:48:46 +00:00
|
|
|
this.events.trackActivationStart( mw.libs.ve.activationStart );
|
|
|
|
mw.libs.ve.activationStart = null;
|
2013-07-05 07:56:28 +00:00
|
|
|
|
2018-05-04 13:30:10 +00:00
|
|
|
this.loading = dataPromise || mw.libs.ve.targetLoader.requestPageData( this.getDefaultMode(), this.getPageName(), {
|
2018-02-21 22:58:50 +00:00
|
|
|
sessionStore: true,
|
2017-09-11 14:53:50 +00:00
|
|
|
section: this.section,
|
|
|
|
oldId: this.requestedRevId,
|
2017-10-02 21:20:53 +00:00
|
|
|
targetName: this.constructor.static.trackingName
|
2017-09-11 14:53:50 +00:00
|
|
|
} );
|
2015-02-23 20:51:33 +00:00
|
|
|
this.loading
|
2015-07-31 15:03:33 +00:00
|
|
|
.done( this.loadSuccess.bind( this ) )
|
|
|
|
.fail( this.loadFail.bind( this ) );
|
2015-02-23 20:51:33 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2013-12-03 02:29:11 +00:00
|
|
|
/**
|
|
|
|
* Clear the state of this target, preparing it to be reactivated later.
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.clearState = function () {
|
2016-12-13 21:55:01 +00:00
|
|
|
this.restoreAccessKeys();
|
2013-12-03 02:29:11 +00:00
|
|
|
this.clearPreparedCacheKey();
|
|
|
|
this.loading = false;
|
|
|
|
this.saving = false;
|
2017-06-22 17:54:04 +00:00
|
|
|
this.clearDiff();
|
2013-12-03 02:29:11 +00:00
|
|
|
this.serializing = false;
|
|
|
|
this.submitting = false;
|
|
|
|
this.baseTimeStamp = null;
|
|
|
|
this.startTimeStamp = null;
|
2016-12-21 22:35:52 +00:00
|
|
|
this.checkboxes = null;
|
2018-01-23 20:51:11 +00:00
|
|
|
this.initialSourceRange = null;
|
2013-12-03 02:29:11 +00:00
|
|
|
this.doc = null;
|
2017-07-17 16:38:32 +00:00
|
|
|
this.originalDmDocPromise = null;
|
2013-12-03 02:29:11 +00:00
|
|
|
this.originalHtml = null;
|
2017-10-05 16:31:15 +00:00
|
|
|
this.toolbarSaveButton = null;
|
2016-09-06 19:16:55 +00:00
|
|
|
this.section = null;
|
2015-12-11 16:41:01 +00:00
|
|
|
this.editNotices = [];
|
2013-12-03 02:29:11 +00:00
|
|
|
this.remoteNotices = [];
|
|
|
|
this.localNoticeMessages = [];
|
2018-02-21 22:58:50 +00:00
|
|
|
this.recovered = false;
|
2018-11-30 16:53:53 +00:00
|
|
|
this.teardownPromise = null;
|
2013-12-03 02:29:11 +00:00
|
|
|
};
|
|
|
|
|
2015-07-30 11:08:56 +00:00
|
|
|
/**
|
|
|
|
* Switch to edit source mode
|
|
|
|
*
|
|
|
|
* @abstract
|
|
|
|
* @method
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.editSource = null;
|
2015-07-30 11:08:56 +00:00
|
|
|
|
2016-09-02 19:06:04 +00:00
|
|
|
/**
|
|
|
|
* Get a document to save, cached until the surface is modified
|
|
|
|
*
|
|
|
|
* The default implementation returns an HTMLDocument, but other targets
|
|
|
|
* may use a different document model (e.g. plain text for source mode).
|
|
|
|
*
|
|
|
|
* @return {Object} Document to save
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.getDocToSave = function () {
|
|
|
|
var surface;
|
|
|
|
if ( !this.docToSave ) {
|
|
|
|
this.docToSave = this.createDocToSave();
|
|
|
|
// Cache clearing events
|
|
|
|
surface = this.getSurface();
|
|
|
|
surface.getModel().getDocument().once( 'transact', this.clearDocToSave.bind( this ) );
|
|
|
|
surface.once( 'destroy', this.clearDocToSave.bind( this ) );
|
|
|
|
}
|
|
|
|
return this.docToSave;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a document to save
|
|
|
|
*
|
|
|
|
* @return {Object} Document to save
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.createDocToSave = function () {
|
2016-11-30 18:41:50 +00:00
|
|
|
return this.getSurface().getDom();
|
2016-09-02 19:06:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear the document to save from the cache
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.clearDocToSave = function () {
|
|
|
|
this.docToSave = null;
|
|
|
|
this.clearPreparedCacheKey();
|
|
|
|
};
|
|
|
|
|
2013-11-06 08:22:11 +00:00
|
|
|
/**
|
|
|
|
* Serialize the current document and store the result in the serialization cache on the server.
|
|
|
|
*
|
|
|
|
* This function returns a promise that is resolved once serialization is complete, with the
|
|
|
|
* cache key passed as the first parameter.
|
|
|
|
*
|
|
|
|
* If there's already a request pending for the same (reference-identical) HTMLDocument, this
|
|
|
|
* function will not initiate a new request but will return the promise for the pending request.
|
|
|
|
* If a request for the same document has already been completed, this function will keep returning
|
|
|
|
* the same promise (which will already have been resolved) until clearPreparedCacheKey() is called.
|
|
|
|
*
|
|
|
|
* @param {HTMLDocument} doc Document to serialize
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.prepareCacheKey = function ( doc ) {
|
2015-03-31 19:52:14 +00:00
|
|
|
var xhr, deflated,
|
|
|
|
aborted = false,
|
2015-01-31 00:41:37 +00:00
|
|
|
start = ve.now(),
|
|
|
|
target = this;
|
2013-11-06 08:22:11 +00:00
|
|
|
|
2016-11-30 12:03:56 +00:00
|
|
|
if ( this.getSurface().getMode() === 'source' ) {
|
2016-11-14 16:07:13 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-11-06 08:22:11 +00:00
|
|
|
if ( this.preparedCacheKeyPromise && this.preparedCacheKeyPromise.doc === doc ) {
|
|
|
|
return this.preparedCacheKeyPromise;
|
|
|
|
}
|
|
|
|
this.clearPreparedCacheKey();
|
|
|
|
|
2015-03-31 19:52:14 +00:00
|
|
|
this.preparedCacheKeyPromise = this.deflateHtml( doc )
|
|
|
|
.then( function ( deflatedHtml ) {
|
|
|
|
deflated = deflatedHtml;
|
|
|
|
if ( aborted ) {
|
|
|
|
return $.Deferred().reject();
|
2013-11-06 08:22:11 +00:00
|
|
|
}
|
2018-05-04 13:30:10 +00:00
|
|
|
xhr = target.getContentApi().postWithToken( 'csrf',
|
2015-03-31 19:52:14 +00:00
|
|
|
{
|
2016-09-08 04:28:59 +00:00
|
|
|
action: 'visualeditoredit',
|
2015-03-31 19:52:14 +00:00
|
|
|
paction: 'serializeforcache',
|
|
|
|
html: deflatedHtml,
|
2018-05-04 13:30:10 +00:00
|
|
|
page: target.getPageName(),
|
2015-10-08 22:16:56 +00:00
|
|
|
oldid: target.revid,
|
|
|
|
etag: target.etag
|
2015-03-31 19:52:14 +00:00
|
|
|
},
|
|
|
|
{ contentType: 'multipart/form-data' }
|
|
|
|
);
|
|
|
|
return xhr.then(
|
|
|
|
function ( response ) {
|
|
|
|
var trackData = { duration: ve.now() - start };
|
2016-09-08 04:28:59 +00:00
|
|
|
if ( response.visualeditoredit && typeof response.visualeditoredit.cachekey === 'string' ) {
|
2015-03-31 19:52:14 +00:00
|
|
|
target.events.track( 'performance.system.serializeforcache', trackData );
|
2016-09-08 04:28:59 +00:00
|
|
|
return response.visualeditoredit.cachekey;
|
2015-03-31 19:52:14 +00:00
|
|
|
} else {
|
|
|
|
target.events.track( 'performance.system.serializeforcache.nocachekey', trackData );
|
|
|
|
return $.Deferred().reject();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function () {
|
|
|
|
target.events.track( 'performance.system.serializeforcache.fail', { duration: ve.now() - start } );
|
|
|
|
}
|
|
|
|
);
|
2013-11-06 08:22:11 +00:00
|
|
|
} )
|
2015-03-31 19:52:14 +00:00
|
|
|
.promise( {
|
|
|
|
abort: function () {
|
|
|
|
if ( xhr ) {
|
|
|
|
xhr.abort();
|
|
|
|
}
|
|
|
|
aborted = true;
|
|
|
|
},
|
|
|
|
getDeflatedHtml: function () {
|
|
|
|
return deflated;
|
|
|
|
},
|
|
|
|
doc: doc
|
2013-11-06 08:22:11 +00:00
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the prepared wikitext, if any. Same as prepareWikitext() but does not initiate a request
|
|
|
|
* if one isn't already pending or finished. Instead, it returns a rejected promise in that case.
|
|
|
|
*
|
|
|
|
* @param {HTMLDocument} doc Document to serialize
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {jQuery.Promise} Abortable promise, resolved with the cache key.
|
2013-11-06 08:22:11 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.getPreparedCacheKey = function ( doc ) {
|
2013-11-06 08:22:11 +00:00
|
|
|
var deferred;
|
|
|
|
if ( this.preparedCacheKeyPromise && this.preparedCacheKeyPromise.doc === doc ) {
|
|
|
|
return this.preparedCacheKeyPromise;
|
|
|
|
}
|
|
|
|
deferred = $.Deferred();
|
|
|
|
deferred.reject();
|
|
|
|
return deferred.promise();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clear the promise for the prepared wikitext cache key, and abort it if it's still in progress.
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.clearPreparedCacheKey = function () {
|
2013-11-06 08:22:11 +00:00
|
|
|
if ( this.preparedCacheKeyPromise ) {
|
|
|
|
this.preparedCacheKeyPromise.abort();
|
|
|
|
this.preparedCacheKeyPromise = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Try submitting an API request with a cache key for prepared wikitext, falling back to submitting
|
|
|
|
* HTML directly if there is no cache key present or pending, or if the request for the cache key
|
|
|
|
* fails, or if using the cache key fails with a badcachekey error.
|
|
|
|
*
|
2016-09-08 04:28:59 +00:00
|
|
|
* If options.token is set, this function will use mw.Api#post and let the caller handle badtoken
|
|
|
|
* errors. If options.token is not set, this function will use mw.Api#postWithToken which retries
|
|
|
|
* automatically when encountering a badtoken error. If you do not want the automatic retry behavior
|
|
|
|
* and want to control badtoken retries, you have to set options.token.
|
|
|
|
*
|
2016-11-14 16:07:13 +00:00
|
|
|
* @param {HTMLDocument|string} doc Document to submit or string in source mode
|
2013-11-06 08:22:11 +00:00
|
|
|
* @param {Object} options POST parameters to send. Do not include 'html', 'cachekey' or 'format'.
|
|
|
|
* @param {string} [eventName] If set, log an event when the request completes successfully. The
|
|
|
|
* full event name used will be 'performance.system.{eventName}.withCacheKey' or .withoutCacheKey
|
|
|
|
* depending on whether or not a cache key was used.
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {jQuery.Promise}
|
2013-11-06 08:22:11 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.tryWithPreparedCacheKey = function ( doc, options, eventName ) {
|
2018-05-04 13:30:10 +00:00
|
|
|
var data, postData, preparedCacheKey, api,
|
2015-01-31 00:41:37 +00:00
|
|
|
target = this;
|
|
|
|
|
2016-11-30 12:03:56 +00:00
|
|
|
if ( this.getSurface().getMode() === 'source' ) {
|
2016-11-14 16:07:13 +00:00
|
|
|
data = {
|
|
|
|
wikitext: doc,
|
|
|
|
format: 'json'
|
|
|
|
};
|
2016-12-08 19:24:10 +00:00
|
|
|
postData = ve.extendObject( {}, options, data );
|
2016-11-14 16:07:13 +00:00
|
|
|
if ( this.section !== null ) {
|
2016-12-08 19:24:10 +00:00
|
|
|
postData.section = this.section;
|
2016-11-14 16:07:13 +00:00
|
|
|
}
|
2016-12-08 19:24:10 +00:00
|
|
|
if ( this.sectionTitle ) {
|
|
|
|
postData.sectiontitle = this.sectionTitle.getValue();
|
|
|
|
postData.summary = undefined;
|
|
|
|
}
|
2018-05-04 13:30:10 +00:00
|
|
|
api = this.getContentApi();
|
2016-12-08 19:24:10 +00:00
|
|
|
if ( postData.token ) {
|
2018-05-04 13:30:10 +00:00
|
|
|
return api.post( postData, { contentType: 'multipart/form-data' } );
|
2016-11-14 16:07:13 +00:00
|
|
|
}
|
2018-05-04 13:30:10 +00:00
|
|
|
return api.postWithToken( 'csrf', postData, { contentType: 'multipart/form-data' } );
|
2016-11-14 16:07:13 +00:00
|
|
|
}
|
|
|
|
|
2017-04-29 11:36:17 +00:00
|
|
|
preparedCacheKey = this.getPreparedCacheKey( doc );
|
2014-08-22 20:50:48 +00:00
|
|
|
data = ve.extendObject( {}, options, { format: 'json' } );
|
2013-11-06 08:22:11 +00:00
|
|
|
|
2015-03-06 01:45:30 +00:00
|
|
|
function ajaxRequest( cachekey, isRetried ) {
|
2015-03-31 19:52:14 +00:00
|
|
|
var fullEventName,
|
|
|
|
start = ve.now(),
|
|
|
|
deflatePromise = $.Deferred().resolve().promise();
|
2015-01-24 00:22:17 +00:00
|
|
|
|
2013-11-06 08:22:11 +00:00
|
|
|
if ( typeof cachekey === 'string' ) {
|
|
|
|
data.cachekey = cachekey;
|
|
|
|
} else {
|
|
|
|
// Getting a cache key failed, fall back to sending the HTML
|
2015-03-31 19:52:14 +00:00
|
|
|
data.html = preparedCacheKey && preparedCacheKey.getDeflatedHtml && preparedCacheKey.getDeflatedHtml();
|
|
|
|
if ( !data.html ) {
|
|
|
|
deflatePromise = target.deflateHtml( doc ).then( function ( deflatedHtml ) {
|
|
|
|
data.html = deflatedHtml;
|
|
|
|
} );
|
|
|
|
}
|
2013-11-06 08:22:11 +00:00
|
|
|
// If using the cache key fails, we'll come back here with cachekey still set
|
|
|
|
delete data.cachekey;
|
|
|
|
}
|
2015-03-31 19:52:14 +00:00
|
|
|
return deflatePromise
|
|
|
|
.then( function () {
|
2018-05-04 13:30:10 +00:00
|
|
|
var api = target.getContentApi();
|
2016-09-08 04:28:59 +00:00
|
|
|
if ( data.token ) {
|
2018-05-04 13:30:10 +00:00
|
|
|
return api.post( data, { contentType: 'multipart/form-data' } );
|
2016-09-08 04:28:59 +00:00
|
|
|
}
|
2018-05-04 13:30:10 +00:00
|
|
|
return api.postWithToken( 'csrf', data, { contentType: 'multipart/form-data' } );
|
2015-03-31 19:52:14 +00:00
|
|
|
} )
|
2015-01-24 00:22:17 +00:00
|
|
|
.then(
|
|
|
|
function ( response, jqxhr ) {
|
|
|
|
var eventData = {
|
2018-02-27 21:35:29 +00:00
|
|
|
bytes: require( 'mediawiki.String' ).byteLength( jqxhr.responseText ),
|
2015-03-25 02:36:44 +00:00
|
|
|
duration: ve.now() - start
|
2015-01-24 00:22:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Log data about the request if eventName was set
|
2013-11-06 08:22:11 +00:00
|
|
|
if ( eventName ) {
|
2015-01-24 00:22:17 +00:00
|
|
|
fullEventName = 'performance.system.' + eventName +
|
|
|
|
( typeof cachekey === 'string' ? '.withCacheKey' : '.withoutCacheKey' );
|
2013-12-10 01:39:46 +00:00
|
|
|
target.events.track( fullEventName, eventData );
|
2013-11-06 08:22:11 +00:00
|
|
|
}
|
2015-01-24 00:22:17 +00:00
|
|
|
return jqxhr;
|
|
|
|
},
|
|
|
|
function ( errorName, errorObject ) {
|
2016-02-01 16:45:19 +00:00
|
|
|
var responseText = ve.getProp( errorObject, 'xhr', 'responseText' ),
|
|
|
|
eventData;
|
|
|
|
if ( responseText ) {
|
2015-01-24 00:22:17 +00:00
|
|
|
eventData = {
|
2018-02-27 21:35:29 +00:00
|
|
|
bytes: require( 'mediawiki.String' ).byteLength( responseText ),
|
2015-03-25 02:36:44 +00:00
|
|
|
duration: ve.now() - start
|
2015-01-24 00:22:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if ( eventName ) {
|
|
|
|
if ( errorName === 'badcachekey' ) {
|
|
|
|
fullEventName = 'performance.system.' + eventName + '.badCacheKey';
|
|
|
|
} else {
|
|
|
|
fullEventName = 'performance.system.' + eventName + '.withoutCacheKey';
|
|
|
|
}
|
|
|
|
target.events.track( fullEventName, eventData );
|
|
|
|
}
|
|
|
|
}
|
2013-11-06 08:22:11 +00:00
|
|
|
// This cache key is evidently bad, clear it
|
|
|
|
target.clearPreparedCacheKey();
|
2015-08-21 01:01:56 +00:00
|
|
|
if ( !isRetried && errorName === 'badcachekey' ) {
|
2015-03-06 01:45:30 +00:00
|
|
|
// Try again without a cache key
|
|
|
|
return ajaxRequest( null, true );
|
|
|
|
} else {
|
|
|
|
// Failed twice in a row, must be some other error - let caller handle it.
|
|
|
|
// FIXME Can't just `return this` because all callers are broken.
|
|
|
|
return $.Deferred().reject( null, errorName, errorObject ).promise();
|
|
|
|
}
|
2013-11-06 08:22:11 +00:00
|
|
|
}
|
2015-01-24 00:22:17 +00:00
|
|
|
);
|
2013-11-06 08:22:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If we successfully get prepared wikitext, then invoke ajaxRequest() with the cache key,
|
|
|
|
// otherwise invoke it without.
|
|
|
|
return preparedCacheKey.then( ajaxRequest, ajaxRequest );
|
|
|
|
};
|
|
|
|
|
2015-08-04 13:37:13 +00:00
|
|
|
/**
|
2016-03-23 11:09:38 +00:00
|
|
|
* Handle the save dialog's save event
|
|
|
|
*
|
|
|
|
* Validates the inputs then starts the save process
|
2015-08-04 13:37:13 +00:00
|
|
|
*
|
2015-08-05 21:43:23 +00:00
|
|
|
* @param {jQuery.Deferred} saveDeferred Deferred object to resolve/reject when the save
|
|
|
|
* succeeds/fails.
|
2015-08-04 13:37:13 +00:00
|
|
|
* @fires saveInitiated
|
|
|
|
*/
|
2016-03-23 11:09:38 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.onSaveDialogSave = function ( saveDeferred ) {
|
2015-08-19 18:05:01 +00:00
|
|
|
var saveOptions;
|
|
|
|
|
2015-08-05 21:43:23 +00:00
|
|
|
if ( this.deactivating ) {
|
2016-10-28 00:22:30 +00:00
|
|
|
return;
|
2015-08-05 21:43:23 +00:00
|
|
|
}
|
|
|
|
|
2015-08-19 18:05:01 +00:00
|
|
|
saveOptions = this.getSaveOptions();
|
2015-08-17 13:06:59 +00:00
|
|
|
|
2015-08-05 21:43:23 +00:00
|
|
|
if (
|
|
|
|
+mw.user.options.get( 'forceeditsummary' ) &&
|
2016-02-21 08:35:05 +00:00
|
|
|
( saveOptions.summary === '' || saveOptions.summary === this.initialEditSummary ) &&
|
2015-08-05 21:43:23 +00:00
|
|
|
!this.saveDialog.messages.missingsummary
|
|
|
|
) {
|
|
|
|
this.saveDialog.showMessage(
|
|
|
|
'missingsummary',
|
|
|
|
// Wrap manually since this core message already includes a bold "Warning:" label
|
|
|
|
$( '<p>' ).append( ve.init.platform.getParsedMessage( 'missingsummary' ) ),
|
|
|
|
{ wrap: false }
|
|
|
|
);
|
|
|
|
this.saveDialog.popPending();
|
|
|
|
} else {
|
|
|
|
this.emit( 'saveInitiated' );
|
2016-03-23 11:09:38 +00:00
|
|
|
this.startSave( saveOptions );
|
2015-08-05 21:43:23 +00:00
|
|
|
this.saveDeferred = saveDeferred;
|
2015-08-04 13:37:13 +00:00
|
|
|
}
|
2015-08-05 21:43:23 +00:00
|
|
|
};
|
|
|
|
|
2016-03-23 11:09:38 +00:00
|
|
|
/**
|
|
|
|
* Start the save process
|
|
|
|
*
|
|
|
|
* @param {Object} saveOptions Save options
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.startSave = function ( saveOptions ) {
|
2016-09-02 19:06:04 +00:00
|
|
|
this.save( this.getDocToSave(), saveOptions );
|
2016-03-23 11:09:38 +00:00
|
|
|
};
|
|
|
|
|
2015-08-05 21:43:23 +00:00
|
|
|
/**
|
|
|
|
* Get save form fields from the save dialog form.
|
|
|
|
*
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {Object} Form data for submission to the MediaWiki action=edit UI
|
2015-08-05 21:43:23 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.getSaveFields = function () {
|
2016-11-05 06:16:26 +00:00
|
|
|
var name,
|
|
|
|
fields = {
|
2018-11-23 17:01:25 +00:00
|
|
|
wpSummary: this.saveDialog ?
|
|
|
|
this.saveDialog.editSummaryInput.getValue() :
|
|
|
|
( this.editSummaryValue || this.initialEditSummary )
|
2016-11-05 06:16:26 +00:00
|
|
|
};
|
2018-11-23 17:01:25 +00:00
|
|
|
|
|
|
|
// Extra save fields added by extensions
|
|
|
|
for ( name in this.saveFields ) {
|
|
|
|
fields[ name ] = this.saveFields[ name ]();
|
|
|
|
}
|
|
|
|
|
2015-08-05 21:43:23 +00:00
|
|
|
if ( this.recreating ) {
|
|
|
|
fields.wpRecreate = true;
|
|
|
|
}
|
2016-11-05 06:16:26 +00:00
|
|
|
|
|
|
|
for ( name in this.checkboxesByName ) {
|
|
|
|
if ( this.checkboxesByName[ name ].isSelected() ) {
|
|
|
|
fields[ name ] = this.checkboxesByName[ name ].getValue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-05 21:43:23 +00:00
|
|
|
return fields;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Invoke #submit with the data from #getSaveFields
|
|
|
|
*
|
|
|
|
* @param {Object} fields Fields to add in addition to those from #getSaveFields
|
|
|
|
* @param {string} wikitext Wikitext to submit
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {boolean} Whether submission was started
|
2015-08-05 21:43:23 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.submitWithSaveFields = function ( fields, wikitext ) {
|
2015-08-05 21:43:23 +00:00
|
|
|
return this.submit( wikitext, $.extend( this.getSaveFields(), fields ) );
|
2015-08-04 13:37:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get edit API options from the save dialog form.
|
|
|
|
*
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {Object} Save options for submission to the MediaWiki API
|
2015-08-04 13:37:13 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.getSaveOptions = function () {
|
2015-08-05 21:43:23 +00:00
|
|
|
var key,
|
|
|
|
options = this.getSaveFields(),
|
|
|
|
fieldMap = {
|
|
|
|
wpSummary: 'summary',
|
|
|
|
wpMinoredit: 'minor',
|
|
|
|
wpWatchthis: 'watch',
|
|
|
|
wpCaptchaId: 'captchaid',
|
|
|
|
wpCaptchaWord: 'captchaword'
|
|
|
|
};
|
|
|
|
|
|
|
|
for ( key in fieldMap ) {
|
2015-08-19 17:33:02 +00:00
|
|
|
if ( options[ key ] !== undefined ) {
|
|
|
|
options[ fieldMap[ key ] ] = options[ key ];
|
|
|
|
delete options[ key ];
|
2015-08-05 21:43:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return options;
|
2015-08-04 13:37:13 +00:00
|
|
|
};
|
|
|
|
|
2012-06-11 06:54:41 +00:00
|
|
|
/**
|
2013-01-15 23:38:49 +00:00
|
|
|
* Post DOM data to the Parsoid API.
|
2012-06-11 06:54:41 +00:00
|
|
|
*
|
|
|
|
* This method performs an asynchronous action and uses a callback function to handle the result.
|
|
|
|
*
|
2014-08-22 20:50:48 +00:00
|
|
|
* target.save( dom, { summary: 'test', minor: true, watch: false } );
|
2012-06-11 06:54:41 +00:00
|
|
|
*
|
|
|
|
* @method
|
2013-02-11 19:46:58 +00:00
|
|
|
* @param {HTMLDocument} doc Document to save
|
Render check boxes from EditPage
EditPage has a lovely getCheckboxes() function which includes the
minor and watch checkboxes as rendered by MW core, as well as any
checkboxes extensions like FlaggedRevs might have added. Output
these in the API, render them, and send their values back.
ApiVisualEditor.php:
* Build a fake EditPage, get its checkboxes, and return them
ApiVisualEditorEdit.php:
* Pass through posted request data to ApiEdit, which passes it
through to EditPage thanks to Idab5b524b0e3 in core
ve.init.mw.ViewPageTarget.js:
* Remove minor and watch checkboxes from the save dialog template
and replace them with a generic checkbox container
* Have getSaveOptions() pull the state of all checkboxes in
** Special-case minor and watch, and pass the rest straight through
** Move normalization from true/false to presence/absence here, from
ve.init.mw.Target.prototype.save(), because here we know which ones
are checkboxes and we don't know that in save() without
special-casing
* Remove getSaveDialogHtml(), we don't need to hide checkboxes based on
rights anymore because in that case the API just won't send them to us.
** Moved logic for checking the watch checkbox down to where the same
logic for the minor checkbox already is
* Unwrap getSaveDialogHtml() in setupSaveDialog()
* Access minor and watch by their new IDs throughout
ve.init.mw.Target.js:
* Get and store checkboxes from the API
* Pass all keys straight through to the API
Bug: 49699
Change-Id: I09d02a42b05146bc9b7080ab38338ae869bf15e3
2013-07-24 06:39:03 +00:00
|
|
|
* @param {Object} options Saving options. All keys are passed through, including unrecognized ones.
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
* - {string} summary Edit summary
|
|
|
|
* - {boolean} minor Edit is a minor edit
|
2013-01-15 23:38:49 +00:00
|
|
|
* - {boolean} watch Watch the page
|
2017-04-20 20:39:25 +00:00
|
|
|
* @param {boolean} [isRetry=false] Whether this is a retry after a 'badtoken' error
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {boolean} Saving has been started
|
2012-06-11 06:54:41 +00:00
|
|
|
*/
|
2017-04-20 20:39:25 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.save = function ( doc, options, isRetry ) {
|
2013-11-06 08:22:11 +00:00
|
|
|
var data;
|
2012-06-11 06:54:41 +00:00
|
|
|
// Prevent duplicate requests
|
2012-06-18 20:12:32 +00:00
|
|
|
if ( this.saving ) {
|
2012-06-11 06:54:41 +00:00
|
|
|
return false;
|
|
|
|
}
|
2013-06-23 23:09:47 +00:00
|
|
|
|
2014-02-20 21:30:29 +00:00
|
|
|
data = ve.extendObject( {}, options, {
|
2014-08-22 20:50:48 +00:00
|
|
|
action: 'visualeditoredit',
|
2016-09-08 04:28:59 +00:00
|
|
|
paction: 'save',
|
2018-05-04 13:30:10 +00:00
|
|
|
page: this.getPageName(),
|
2014-08-22 20:50:48 +00:00
|
|
|
oldid: this.revid,
|
|
|
|
basetimestamp: this.baseTimeStamp,
|
|
|
|
starttimestamp: this.startTimeStamp,
|
2016-09-08 04:28:59 +00:00
|
|
|
etag: this.etag,
|
|
|
|
// Pass in token to prevent automatic badtoken retries
|
|
|
|
token: this.editToken
|
Render check boxes from EditPage
EditPage has a lovely getCheckboxes() function which includes the
minor and watch checkboxes as rendered by MW core, as well as any
checkboxes extensions like FlaggedRevs might have added. Output
these in the API, render them, and send their values back.
ApiVisualEditor.php:
* Build a fake EditPage, get its checkboxes, and return them
ApiVisualEditorEdit.php:
* Pass through posted request data to ApiEdit, which passes it
through to EditPage thanks to Idab5b524b0e3 in core
ve.init.mw.ViewPageTarget.js:
* Remove minor and watch checkboxes from the save dialog template
and replace them with a generic checkbox container
* Have getSaveOptions() pull the state of all checkboxes in
** Special-case minor and watch, and pass the rest straight through
** Move normalization from true/false to presence/absence here, from
ve.init.mw.Target.prototype.save(), because here we know which ones
are checkboxes and we don't know that in save() without
special-casing
* Remove getSaveDialogHtml(), we don't need to hide checkboxes based on
rights anymore because in that case the API just won't send them to us.
** Moved logic for checking the watch checkbox down to where the same
logic for the minor checkbox already is
* Unwrap getSaveDialogHtml() in setupSaveDialog()
* Access minor and watch by their new IDs throughout
ve.init.mw.Target.js:
* Get and store checkboxes from the API
* Pass all keys straight through to the API
Bug: 49699
Change-Id: I09d02a42b05146bc9b7080ab38338ae869bf15e3
2013-07-24 06:39:03 +00:00
|
|
|
} );
|
2013-06-23 23:09:47 +00:00
|
|
|
|
2013-11-06 08:22:11 +00:00
|
|
|
this.saving = this.tryWithPreparedCacheKey( doc, data, 'save' )
|
2015-08-04 10:34:07 +00:00
|
|
|
.done( this.saveSuccess.bind( this, doc, data ) )
|
2017-04-20 20:39:25 +00:00
|
|
|
.fail( this.saveFail.bind( this, doc, data, !!isRetry ) );
|
2013-10-11 22:00:10 +00:00
|
|
|
|
2012-06-11 06:54:41 +00:00
|
|
|
return true;
|
|
|
|
};
|
2012-11-28 23:57:00 +00:00
|
|
|
|
2017-04-10 11:38:11 +00:00
|
|
|
/**
|
|
|
|
* Show changes in the save dialog
|
|
|
|
*
|
|
|
|
* @param {Object} doc Document
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.showChanges = function ( doc ) {
|
|
|
|
var target = this;
|
|
|
|
// Invalidate the viewer diff on next change
|
|
|
|
this.getSurface().getModel().getDocument().once( 'transact', function () {
|
2017-06-22 17:54:04 +00:00
|
|
|
target.clearDiff();
|
2017-04-10 11:38:11 +00:00
|
|
|
} );
|
|
|
|
this.saveDialog.setDiffAndReview(
|
|
|
|
this.getWikitextDiffPromise( doc ),
|
2017-05-08 18:57:43 +00:00
|
|
|
this.getVisualDiffGeneratorPromise(),
|
2017-04-10 11:38:11 +00:00
|
|
|
this.getSurface().getModel().getDocument().getHtmlDocument()
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2017-06-22 17:54:04 +00:00
|
|
|
/**
|
|
|
|
* Clear all state associated with the diff
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.clearDiff = function () {
|
|
|
|
if ( this.saveDialog ) {
|
|
|
|
this.saveDialog.clearDiff();
|
|
|
|
}
|
|
|
|
this.wikitextDiffPromise = null;
|
|
|
|
};
|
|
|
|
|
2012-12-07 16:23:23 +00:00
|
|
|
/**
|
2013-11-06 08:22:11 +00:00
|
|
|
* Post DOM data to the Parsoid API to retrieve wikitext diff.
|
2012-12-07 16:23:23 +00:00
|
|
|
*
|
|
|
|
* @method
|
2013-03-20 07:09:43 +00:00
|
|
|
* @param {HTMLDocument} doc Document to compare against (via wikitext)
|
2017-04-10 11:38:11 +00:00
|
|
|
* @return {jQuery.Promise} Promise which resolves with the wikitext diff, or rejects with an error
|
|
|
|
* @fires showChanges
|
|
|
|
* @fires showChangesError
|
2012-12-07 16:23:23 +00:00
|
|
|
*/
|
2017-04-10 11:38:11 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.getWikitextDiffPromise = function ( doc ) {
|
|
|
|
var target = this;
|
|
|
|
if ( !this.wikitextDiffPromise ) {
|
|
|
|
this.wikitextDiffPromise = this.tryWithPreparedCacheKey( doc, {
|
|
|
|
action: 'visualeditoredit',
|
|
|
|
paction: 'diff',
|
2018-05-04 13:30:10 +00:00
|
|
|
page: this.getPageName(),
|
2017-04-10 11:38:11 +00:00
|
|
|
oldid: this.revid,
|
|
|
|
etag: this.etag
|
|
|
|
}, 'diff' ).then( function ( response ) {
|
|
|
|
var data = response.visualeditoredit;
|
|
|
|
if ( !data && !response.error ) {
|
|
|
|
return $.Deferred().reject( 'Invalid response from server' ).promise();
|
|
|
|
} else if ( response.error ) {
|
|
|
|
return $.Deferred().reject( response.error.info ).promise();
|
|
|
|
} else if ( data.result === 'nochanges' ) {
|
|
|
|
target.emit( 'noChanges' );
|
|
|
|
return null;
|
|
|
|
} else if ( data.result !== 'success' ) {
|
|
|
|
return $.Deferred().reject( 'Failed request: ' + data.result ).promise();
|
|
|
|
} else if ( typeof data.diff !== 'string' ) {
|
|
|
|
return $.Deferred().reject( 'Invalid HTML content in response from server' ).promise();
|
|
|
|
} else {
|
|
|
|
return data.diff;
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
this.wikitextDiffPromise
|
|
|
|
.done( this.emit.bind( this, 'showChanges' ) )
|
|
|
|
.fail( this.emit.bind( this, 'showChangesError' ) );
|
2013-11-06 08:22:11 +00:00
|
|
|
}
|
2017-04-10 11:38:11 +00:00
|
|
|
return this.wikitextDiffPromise;
|
2012-12-07 16:23:23 +00:00
|
|
|
};
|
|
|
|
|
2012-11-28 23:57:00 +00:00
|
|
|
/**
|
2013-11-15 20:30:57 +00:00
|
|
|
* Post wikitext to MediaWiki.
|
2012-11-28 23:57:00 +00:00
|
|
|
*
|
|
|
|
* This method performs a synchronous action and will take the user to a new page when complete.
|
|
|
|
*
|
2014-08-22 20:50:48 +00:00
|
|
|
* target.submit( wikitext, { wpSummary: 'test', wpMinorEdit: 1, wpSave: 1 } );
|
2012-11-28 23:57:00 +00:00
|
|
|
*
|
|
|
|
* @method
|
JSDuck: Generated code documentation!
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
2013-01-04 08:54:17 +00:00
|
|
|
* @param {string} wikitext Wikitext to submit
|
2013-11-15 20:30:57 +00:00
|
|
|
* @param {Object} fields Other form fields to add (e.g. wpSummary, wpWatchthis, etc.). To actually
|
2014-08-22 20:50:48 +00:00
|
|
|
* save the wikitext, add { wpSave: 1 }. To go to the diff view, add { wpDiff: 1 }.
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {boolean} Submitting has been started
|
2012-11-28 23:57:00 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.submit = function ( wikitext, fields ) {
|
2015-08-19 18:05:01 +00:00
|
|
|
var key, $form, params;
|
|
|
|
|
2012-11-28 23:57:00 +00:00
|
|
|
// Prevent duplicate requests
|
|
|
|
if ( this.submitting ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Save DOM
|
|
|
|
this.submitting = true;
|
2018-02-19 15:41:20 +00:00
|
|
|
$form = $( '<form>' ).attr( { method: 'post', enctype: 'multipart/form-data' } ).addClass( 'oo-ui-element-hidden' );
|
2015-08-19 18:05:01 +00:00
|
|
|
params = ve.extendObject( {
|
|
|
|
format: 'text/x-wiki',
|
|
|
|
model: 'wikitext',
|
|
|
|
oldid: this.requestedRevId,
|
|
|
|
wpStarttime: this.startTimeStamp,
|
|
|
|
wpEdittime: this.baseTimeStamp,
|
|
|
|
wpTextbox1: wikitext,
|
2018-08-02 03:32:29 +00:00
|
|
|
wpEditToken: this.editToken,
|
|
|
|
// MediaWiki function-verification parameters, mostly relevant to the
|
|
|
|
// classic editpage, but still required here:
|
2017-10-02 15:09:04 +00:00
|
|
|
wpUnicodeCheck: 'ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ',
|
2018-08-02 03:32:29 +00:00
|
|
|
wpUltimateParam: true
|
2015-08-19 18:05:01 +00:00
|
|
|
}, fields );
|
2012-11-28 23:57:00 +00:00
|
|
|
// Add params as hidden fields
|
|
|
|
for ( key in params ) {
|
2015-08-19 17:33:02 +00:00
|
|
|
$form.append( $( '<input>' ).attr( { type: 'hidden', name: key, value: params[ key ] } ) );
|
2012-11-28 23:57:00 +00:00
|
|
|
}
|
|
|
|
// Submit the form, mimicking a traditional edit
|
2013-11-15 20:30:57 +00:00
|
|
|
// Firefox requires the form to be attached
|
2019-01-08 17:00:09 +00:00
|
|
|
$form.attr( 'action', this.submitUrl ).appendTo( 'body' ).trigger( 'submit' );
|
2012-11-28 23:57:00 +00:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2013-01-15 23:38:49 +00:00
|
|
|
* Get Wikitext data from the Parsoid API.
|
2012-11-28 23:57:00 +00:00
|
|
|
*
|
|
|
|
* This method performs an asynchronous action and uses a callback function to handle the result.
|
|
|
|
*
|
|
|
|
* target.serialize(
|
|
|
|
* dom,
|
|
|
|
* function ( wikitext ) {
|
|
|
|
* // Do something with the loaded DOM
|
|
|
|
* }
|
|
|
|
* );
|
|
|
|
*
|
|
|
|
* @method
|
2013-02-11 19:46:58 +00:00
|
|
|
* @param {HTMLDocument} doc Document to serialize
|
2012-11-28 23:57:00 +00:00
|
|
|
* @param {Function} callback Function to call when complete, accepts error and wikitext arguments
|
2015-08-19 18:09:34 +00:00
|
|
|
* @return {boolean} Serializing has been started
|
2012-11-28 23:57:00 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.serialize = function ( doc, callback ) {
|
2012-11-28 23:57:00 +00:00
|
|
|
// Prevent duplicate requests
|
|
|
|
if ( this.serializing ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
this.serializeCallback = callback;
|
2013-11-06 08:22:11 +00:00
|
|
|
this.serializing = this.tryWithPreparedCacheKey( doc, {
|
2016-09-08 04:28:59 +00:00
|
|
|
action: 'visualeditoredit',
|
2014-08-22 20:50:48 +00:00
|
|
|
paction: 'serialize',
|
2018-05-04 13:30:10 +00:00
|
|
|
page: this.getPageName(),
|
2015-10-08 22:16:56 +00:00
|
|
|
oldid: this.revid,
|
|
|
|
etag: this.etag
|
2013-11-06 08:22:11 +00:00
|
|
|
}, 'serialize' )
|
2016-06-13 22:37:27 +00:00
|
|
|
.done( this.serializeSuccess.bind( this ) )
|
|
|
|
.fail( this.serializeFail.bind( this ) );
|
2012-11-28 23:57:00 +00:00
|
|
|
return true;
|
|
|
|
};
|
2013-10-17 17:35:16 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get list of edit notices.
|
|
|
|
*
|
2015-12-11 16:41:01 +00:00
|
|
|
* @return {Array} List of edit notices
|
2013-10-17 17:35:16 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.getEditNotices = function () {
|
2013-10-17 17:35:16 +00:00
|
|
|
return this.editNotices;
|
|
|
|
};
|
2013-10-11 18:42:46 +00:00
|
|
|
|
|
|
|
// FIXME: split out view specific functionality, emit to subclass
|
|
|
|
|
|
|
|
/**
|
2015-12-11 14:57:49 +00:00
|
|
|
* @inheritdoc
|
2013-10-11 18:42:46 +00:00
|
|
|
*/
|
2015-12-11 14:57:49 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.track = function ( name ) {
|
2017-12-07 11:14:00 +00:00
|
|
|
var mode = this.surface ? this.surface.getMode() : this.getDefaultMode();
|
|
|
|
ve.track( name, { mode: mode } );
|
2015-12-11 14:57:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.createSurface = function () {
|
|
|
|
// Parent method
|
|
|
|
var surface = ve.init.mw.ArticleTarget.super.prototype.createSurface.apply( this, arguments );
|
2015-02-03 04:23:57 +00:00
|
|
|
|
2015-12-11 14:57:49 +00:00
|
|
|
surface.$element.addClass( this.protectedClasses );
|
2014-12-03 00:04:07 +00:00
|
|
|
|
2015-12-11 14:57:49 +00:00
|
|
|
return surface;
|
2013-10-11 18:42:46 +00:00
|
|
|
};
|
|
|
|
|
2017-10-05 16:31:15 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.teardown = function () {
|
2018-11-29 14:38:53 +00:00
|
|
|
var surface,
|
|
|
|
target = this;
|
2018-11-30 16:53:53 +00:00
|
|
|
if ( !this.teardownPromise ) {
|
|
|
|
surface = this.getSurface();
|
|
|
|
|
|
|
|
// Restore access keys
|
|
|
|
if ( this.$saveAccessKeyElements ) {
|
|
|
|
this.$saveAccessKeyElements.attr( 'accesskey', ve.msg( 'accesskey-save' ) );
|
|
|
|
this.$saveAccessKeyElements = null;
|
|
|
|
}
|
|
|
|
if ( surface ) {
|
|
|
|
// If target is closed cleanly (after save or deliberate close) then remove autosave state
|
|
|
|
surface.getModel().removeDocStateAndChanges();
|
|
|
|
// Disconnect history listener
|
|
|
|
surface.getModel().disconnect( this );
|
|
|
|
}
|
|
|
|
// Parent method
|
2018-11-29 14:38:53 +00:00
|
|
|
this.teardownPromise = ve.init.mw.ArticleTarget.super.prototype.teardown.call( this ).then( function () {
|
|
|
|
mw.hook( 've.deactivationComplete' ).fire( target.edited );
|
|
|
|
} );
|
2018-03-14 16:45:31 +00:00
|
|
|
}
|
2018-11-30 16:53:53 +00:00
|
|
|
return this.teardownPromise;
|
2017-10-05 16:31:15 +00:00
|
|
|
};
|
|
|
|
|
2018-03-26 14:23:56 +00:00
|
|
|
/**
|
|
|
|
* Try to tear down the target, but leave ready for re-activation later
|
|
|
|
*
|
|
|
|
* Will first prompt the user if required, then call #teardown.
|
|
|
|
*
|
|
|
|
* @param {boolean} [noPrompt] Do not display a prompt to the user
|
|
|
|
* @param {string} [trackMechanism] Abort mechanism; used for event tracking if present
|
|
|
|
* @return {jQuery.Promise} Promise which resolves when the target has been torn down
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.tryTeardown = function ( noPrompt, trackMechanism ) {
|
|
|
|
var target = this;
|
|
|
|
|
|
|
|
if ( noPrompt || !this.edited ) {
|
|
|
|
return this.teardown( trackMechanism );
|
|
|
|
} else {
|
2018-10-31 13:38:05 +00:00
|
|
|
return this.getSurface().dialogs.openWindow( 'abandonedit' )
|
2018-03-26 14:23:56 +00:00
|
|
|
.closed.then( function ( data ) {
|
|
|
|
if ( data && data.action === 'discard' ) {
|
|
|
|
return target.teardown( trackMechanism );
|
|
|
|
}
|
|
|
|
return $.Deferred().reject().promise();
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-05-26 10:56:08 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.setupToolbar = function () {
|
|
|
|
// Parent method
|
|
|
|
ve.init.mw.ArticleTarget.super.prototype.setupToolbar.apply( this, arguments );
|
|
|
|
|
|
|
|
this.setupToolbarSaveButton();
|
|
|
|
this.attachToolbarSaveButton();
|
2016-05-26 12:07:19 +00:00
|
|
|
|
|
|
|
if ( this.saveDialog ) {
|
2017-03-09 20:23:36 +00:00
|
|
|
this.editSummaryValue = this.saveDialog.editSummaryInput.getValue();
|
2016-05-26 12:07:19 +00:00
|
|
|
this.saveDialog.disconnect( this );
|
|
|
|
this.saveDialog = null;
|
|
|
|
}
|
2016-05-26 10:56:08 +00:00
|
|
|
};
|
|
|
|
|
2016-06-30 14:01:24 +00:00
|
|
|
/**
|
2016-06-30 14:04:51 +00:00
|
|
|
* Getting the message for the toolbar / save dialog save / publish button
|
2016-06-30 14:01:24 +00:00
|
|
|
*
|
2018-03-19 14:49:23 +00:00
|
|
|
* @param {boolean} [startProcess] Use version of the label for starting that process, i.e. with an ellipsis after it
|
2016-06-30 14:01:24 +00:00
|
|
|
* @return {Function|string} An i18n message or resolveable function
|
|
|
|
*/
|
2018-03-19 14:49:23 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.getSaveButtonLabel = function ( startProcess ) {
|
|
|
|
var suffix = startProcess ? '-start' : '';
|
|
|
|
// The following messages can be used here
|
|
|
|
// * publishpage
|
|
|
|
// * pubishhpage-start
|
|
|
|
// * publishchanges
|
|
|
|
// * pubishhchanges-start
|
|
|
|
// * savearticle
|
|
|
|
// * savearticle-start
|
|
|
|
// * savechanges
|
|
|
|
// * savechanges-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( ( !this.pageExists ? 'publishpage' : 'publishchanges' ) + suffix );
|
2016-06-30 14:04:51 +00:00
|
|
|
}
|
|
|
|
|
2018-03-19 14:49:23 +00:00
|
|
|
return OO.ui.deferMsg( ( !this.pageExists ? 'savearticle' : 'savechanges' ) + suffix );
|
2016-06-30 14:01:24 +00:00
|
|
|
};
|
|
|
|
|
2015-07-01 11:11:36 +00:00
|
|
|
/**
|
|
|
|
* Add content and event bindings to toolbar save button.
|
|
|
|
*
|
|
|
|
* @param {Object} [config] Configuration options for the button
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.setupToolbarSaveButton = function ( config ) {
|
2016-05-26 10:56:08 +00:00
|
|
|
if ( !this.toolbarSaveButton ) {
|
|
|
|
this.toolbarSaveButton = new OO.ui.ButtonWidget( ve.extendObject( {
|
2018-03-19 14:49:23 +00:00
|
|
|
label: this.getSaveButtonLabel( true ),
|
2016-05-26 10:56:08 +00:00
|
|
|
flags: [ 'progressive', 'primary' ],
|
|
|
|
disabled: !this.restoring
|
|
|
|
}, config ) );
|
|
|
|
|
|
|
|
// NOTE (phuedx, 2014-08-20): This class is used by the firsteditve guided
|
|
|
|
// tour to attach a guider to the "Save page" button.
|
|
|
|
this.toolbarSaveButton.$element.addClass( 've-ui-toolbar-saveButton' );
|
|
|
|
|
|
|
|
if ( ve.msg( 'accesskey-save' ) !== '-' && ve.msg( 'accesskey-save' ) !== '' ) {
|
|
|
|
// FlaggedRevs tries to use this - it's useless on VE pages because all that stuff gets hidden, but it will still conflict so get rid of it
|
2017-10-05 16:31:15 +00:00
|
|
|
this.$saveAccessKeyElements = $( '[accesskey="' + ve.msg( 'accesskey-save' ) + '"]' ).removeAttr( 'accesskey' );
|
2016-05-26 10:56:08 +00:00
|
|
|
this.toolbarSaveButton.$button.attr( 'accesskey', ve.msg( 'accesskey-save' ) );
|
|
|
|
}
|
2015-07-01 11:11:36 +00:00
|
|
|
|
2016-05-26 10:56:08 +00:00
|
|
|
this.toolbarSaveButton.connect( this, { click: 'onToolbarSaveButtonClick' } );
|
|
|
|
}
|
2015-07-01 11:11:36 +00:00
|
|
|
this.updateToolbarSaveButtonState();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add the save button to the user interface.
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.attachToolbarSaveButton = function () {
|
2015-07-01 11:11:36 +00:00
|
|
|
this.toolbar.$actions.append( this.toolbarSaveButton.$element );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2018-03-10 15:26:11 +00:00
|
|
|
* Re-evaluate whether the article can be saved
|
|
|
|
*
|
|
|
|
* @return {boolean} The article can be saved
|
2015-07-01 11:11:36 +00:00
|
|
|
*/
|
2018-03-10 15:26:11 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.isSaveable = function () {
|
|
|
|
var surface = this.getSurface();
|
|
|
|
if ( !surface ) {
|
2017-09-18 15:05:32 +00:00
|
|
|
// Called before we're attached, so meaningless; abandon for now
|
2018-03-10 15:26:11 +00:00
|
|
|
return false;
|
2017-09-18 15:05:32 +00:00
|
|
|
}
|
|
|
|
|
2018-03-10 15:26:11 +00:00
|
|
|
this.edited =
|
|
|
|
// Document was edited before loading
|
|
|
|
this.fromEditedState || this.preloaded ||
|
|
|
|
// Document was edited
|
|
|
|
surface.getModel().hasBeenModified() ||
|
|
|
|
// Section title (if it exists) was edited
|
2018-11-29 14:38:53 +00:00
|
|
|
( !!this.sectionTitle && this.sectionTitle.getValue() !== '' );
|
2018-03-10 15:26:11 +00:00
|
|
|
|
|
|
|
return this.edited || this.restoring;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the toolbar save button to reflect if the article can be saved
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.updateToolbarSaveButtonState = function () {
|
|
|
|
// Disable the save button if we can't save
|
|
|
|
var wasDisabled = this.toolbarSaveButton.isDisabled(),
|
|
|
|
isDisabled = !this.isSaveable();
|
|
|
|
if ( wasDisabled !== isDisabled ) {
|
|
|
|
this.toolbarSaveButton.setDisabled( isDisabled );
|
|
|
|
mw.hook( 've.toolbarSaveButton.stateChanged' ).fire( isDisabled );
|
2016-12-08 19:24:10 +00:00
|
|
|
}
|
2015-07-01 11:11:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle clicks on the save button in the toolbar.
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.onToolbarSaveButtonClick = function () {
|
2016-11-30 17:21:38 +00:00
|
|
|
this.showSaveDialog();
|
2015-07-01 11:11:36 +00:00
|
|
|
};
|
|
|
|
|
2015-07-31 12:15:31 +00:00
|
|
|
/**
|
|
|
|
* Show a save dialog
|
2015-08-04 13:37:13 +00:00
|
|
|
*
|
2016-11-30 17:21:38 +00:00
|
|
|
* @param {string} [action] Window action to trigger after opening
|
2016-12-22 00:07:30 +00:00
|
|
|
* @param {string} [checkboxName] Checkbox to toggle after opening
|
2016-11-30 17:21:38 +00:00
|
|
|
*
|
2015-08-04 13:37:13 +00:00
|
|
|
* @fires saveWorkflowBegin
|
2015-07-31 12:15:31 +00:00
|
|
|
*/
|
2016-12-22 00:07:30 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.showSaveDialog = function ( action, checkboxName ) {
|
2018-01-08 17:52:06 +00:00
|
|
|
var checkbox, currentWindow,
|
2016-12-22 00:07:30 +00:00
|
|
|
target = this;
|
2016-06-30 14:01:24 +00:00
|
|
|
|
2018-03-10 15:26:11 +00:00
|
|
|
if ( !this.isSaveable() ) {
|
2016-11-30 17:21:38 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-01-08 17:52:06 +00:00
|
|
|
currentWindow = this.getSurface().getDialogs().getCurrentWindow();
|
|
|
|
if ( currentWindow && currentWindow.constructor.static.name === 'mwSave' && ( action === 'save' || action === null ) ) {
|
|
|
|
// The current window is the save dialog, and we've gotten here via
|
|
|
|
// the save action. Trigger a save. We're doing this here instead of
|
|
|
|
// relying on an accesskey on the save button, because that has some
|
|
|
|
// cross-browser issues that makes it not work in Firefox.
|
|
|
|
currentWindow.executeAction( 'save' );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-08-04 13:37:13 +00:00
|
|
|
this.emit( 'saveWorkflowBegin' );
|
|
|
|
|
|
|
|
// Preload the serialization
|
2016-09-02 19:06:04 +00:00
|
|
|
this.prepareCacheKey( this.getDocToSave() );
|
2015-08-05 21:43:23 +00:00
|
|
|
|
2016-12-13 21:55:01 +00:00
|
|
|
// Get the save dialog
|
2015-08-05 21:43:23 +00:00
|
|
|
this.getSurface().getDialogs().getWindow( 'mwSave' ).done( function ( win ) {
|
2016-12-22 00:07:30 +00:00
|
|
|
var data, checked,
|
2016-12-13 21:55:01 +00:00
|
|
|
windowAction = ve.ui.actionFactory.create( 'window', target.getSurface() );
|
|
|
|
|
2015-08-05 21:43:23 +00:00
|
|
|
if ( !target.saveDialog ) {
|
|
|
|
target.saveDialog = win;
|
|
|
|
|
|
|
|
// Connect to save dialog
|
|
|
|
target.saveDialog.connect( target, {
|
2016-03-23 11:09:38 +00:00
|
|
|
save: 'onSaveDialogSave',
|
2015-08-05 21:43:23 +00:00
|
|
|
review: 'onSaveDialogReview',
|
2016-09-01 23:54:56 +00:00
|
|
|
preview: 'onSaveDialogPreview',
|
2015-08-05 21:43:23 +00:00
|
|
|
resolve: 'onSaveDialogResolveConflict',
|
|
|
|
retry: 'onSaveDialogRetry',
|
|
|
|
close: 'onSaveDialogClose'
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2016-12-13 21:55:01 +00:00
|
|
|
data = target.getSaveDialogOpeningData();
|
|
|
|
|
|
|
|
if (
|
|
|
|
( action === 'review' && !data.canReview ) ||
|
|
|
|
( action === 'preview' && !data.canPreview )
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-12-22 00:07:30 +00:00
|
|
|
if ( checkboxName && ( checkbox = target.checkboxesByName[ checkboxName ] ) ) {
|
|
|
|
checked = !checkbox.isSelected();
|
|
|
|
// Wait for native access key change to happen
|
|
|
|
setTimeout( function () {
|
|
|
|
checkbox.setSelected( checked );
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2016-12-13 21:55:01 +00:00
|
|
|
// When calling review/preview action, switch to those panels immediately
|
|
|
|
if ( action === 'review' || action === 'preview' ) {
|
|
|
|
data.initialPanel = action;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the dialog
|
|
|
|
windowAction.open( 'mwSave', data, action );
|
|
|
|
} );
|
2015-08-05 21:43:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2016-06-30 14:01:24 +00:00
|
|
|
* Get opening data to pass to the save dialog
|
2015-08-05 21:43:23 +00:00
|
|
|
*/
|
2016-12-13 21:55:01 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.getSaveDialogOpeningData = function () {
|
|
|
|
var mode = this.getSurface().getMode();
|
2016-06-30 14:01:24 +00:00
|
|
|
return {
|
2016-12-13 21:55:01 +00:00
|
|
|
canPreview: mode === 'source',
|
|
|
|
canReview: !( mode === 'source' && this.section === 'new' ),
|
|
|
|
sectionTitle: this.sectionTitle && this.sectionTitle.getValue(),
|
2016-11-05 06:16:26 +00:00
|
|
|
saveButtonLabel: this.getSaveButtonLabel(),
|
|
|
|
checkboxFields: this.checkboxFields,
|
2016-12-13 21:55:01 +00:00
|
|
|
checkboxesByName: this.checkboxesByName
|
2016-06-30 14:01:24 +00:00
|
|
|
};
|
2015-07-31 12:15:31 +00:00
|
|
|
};
|
|
|
|
|
2014-02-06 23:13:32 +00:00
|
|
|
/**
|
|
|
|
* Move the cursor in the editor to section specified by this.section.
|
|
|
|
* Do nothing if this.section is undefined.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.restoreEditSection = function () {
|
2016-12-14 21:22:10 +00:00
|
|
|
var headingText,
|
2016-12-15 00:07:22 +00:00
|
|
|
section,
|
2016-12-14 21:22:10 +00:00
|
|
|
surface = this.getSurface(),
|
|
|
|
mode = surface.getMode(),
|
2016-11-30 12:03:56 +00:00
|
|
|
surfaceView, $documentNode, $section, headingNode;
|
2015-08-19 18:05:01 +00:00
|
|
|
|
2016-12-15 00:07:22 +00:00
|
|
|
if ( this.section !== null && this.section !== 'new' && this.section !== 0 && this.section !== 'T-0' ) {
|
2016-12-14 21:22:10 +00:00
|
|
|
if ( mode === 'visual' ) {
|
2016-12-15 00:07:22 +00:00
|
|
|
// Get numerical part of section (strip 'T-'' if present)
|
|
|
|
section = this.section.toString().indexOf( 'T-' ) === 0 ? +this.section.slice( 2 ) : this.section;
|
2016-12-14 21:22:10 +00:00
|
|
|
surfaceView = surface.getView();
|
|
|
|
$documentNode = surfaceView.getDocument().getDocumentNode().$element;
|
2016-07-11 20:48:02 +00:00
|
|
|
// Find all headings including those inside templates, not just HeadingNodes
|
|
|
|
$section = $documentNode.find( 'h1, h2, h3, h4, h5, h6' )
|
|
|
|
// Ignore headings inside TOC
|
|
|
|
.filter( function () {
|
|
|
|
return $( this ).closest( '.ve-ui-mwTocWidget' ).length === 0;
|
|
|
|
} )
|
|
|
|
.eq( section - 1 );
|
2016-12-14 21:22:10 +00:00
|
|
|
headingNode = $section.data( 'view' );
|
2014-02-06 23:13:32 +00:00
|
|
|
|
2016-12-14 21:22:10 +00:00
|
|
|
if ( $section.length && new mw.Uri().query.summary === undefined ) {
|
2017-03-21 15:56:03 +00:00
|
|
|
// Due to interactions with Translate, strip out mw-
|
|
|
|
// editsection from the heading.
|
|
|
|
headingText = $section.clone().find( 'span.mw-editsection' ).remove().end().text();
|
2016-12-14 21:22:10 +00:00
|
|
|
}
|
2014-02-06 23:13:32 +00:00
|
|
|
|
2016-12-14 21:22:10 +00:00
|
|
|
if ( headingNode ) {
|
|
|
|
this.goToHeading( headingNode );
|
|
|
|
}
|
|
|
|
} else if ( mode === 'source' ) {
|
2017-04-05 16:06:46 +00:00
|
|
|
// 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.
|
2016-12-14 21:22:10 +00:00
|
|
|
headingText = surface.getModel().getDocument().data.getText(
|
|
|
|
false,
|
|
|
|
surface.getModel().getDocument().getDocumentNode().children[ 0 ].getRange()
|
2017-04-05 16:06:46 +00:00
|
|
|
)
|
|
|
|
// Extract the title
|
|
|
|
.replace( /^\s*=+\s*(.*?)\s*=+\s*$/, '$1' )
|
|
|
|
// Remove links
|
|
|
|
.replace( /\[\[:?([^[|]+)\|([^[]+)\]\]/, '$2' )
|
|
|
|
.replace( /\[\[:?([^[]+)\|?\]\]/, '$1' )
|
|
|
|
.replace( new RegExp( '\\[(?:' + ve.init.platform.getUnanchoredExternalLinkUrlProtocolsRegExp().source + ')([^ ]+?) ([^\\[]+)\\]', 'i' ), '$3' )
|
2017-08-29 17:01:33 +00:00
|
|
|
// Cheap HTML removal
|
2018-06-10 14:59:26 +00:00
|
|
|
.replace( /<[^>]+?>/g, '' );
|
2016-12-14 21:22:10 +00:00
|
|
|
}
|
|
|
|
if ( headingText ) {
|
|
|
|
this.initialEditSummary =
|
|
|
|
'/* ' +
|
|
|
|
ve.graphemeSafeSubstring( headingText, 0, 244 ) +
|
|
|
|
' */ ';
|
2014-02-06 23:13:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2014-07-23 22:30:38 +00:00
|
|
|
|
2014-08-22 00:37:12 +00:00
|
|
|
/**
|
|
|
|
* Move the cursor to a given heading and scroll to it.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {ve.ce.HeadingNode} headingNode Heading node to scroll to
|
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.goToHeading = function ( headingNode ) {
|
2014-08-22 00:37:12 +00:00
|
|
|
var nextNode, offset,
|
|
|
|
target = this,
|
|
|
|
offsetNode = headingNode,
|
2015-07-08 14:34:43 +00:00
|
|
|
surface = this.getSurface(),
|
|
|
|
surfaceModel = surface.getModel(),
|
|
|
|
surfaceView = surface.getView(),
|
2014-08-22 00:37:12 +00:00
|
|
|
lastHeadingLevel = -1;
|
|
|
|
|
|
|
|
// Find next sibling which isn't a heading
|
|
|
|
while ( offsetNode instanceof ve.ce.HeadingNode && offsetNode.getModel().getAttribute( 'level' ) > lastHeadingLevel ) {
|
|
|
|
lastHeadingLevel = offsetNode.getModel().getAttribute( 'level' );
|
|
|
|
// Next sibling
|
2015-08-19 17:33:02 +00:00
|
|
|
nextNode = offsetNode.parent.children[ offsetNode.parent.children.indexOf( offsetNode ) + 1 ];
|
2014-08-22 00:37:12 +00:00
|
|
|
if ( !nextNode ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
offsetNode = nextNode;
|
|
|
|
}
|
|
|
|
offset = surfaceModel.getDocument().data.getNearestContentOffset(
|
|
|
|
offsetNode.getModel().getOffset(), 1
|
|
|
|
);
|
2016-07-06 21:11:30 +00:00
|
|
|
|
|
|
|
function scrollAndSetSelection() {
|
2014-09-30 16:34:33 +00:00
|
|
|
surfaceModel.setLinearSelection( new ve.Range( offset ) );
|
2015-07-08 14:34:43 +00:00
|
|
|
// Focussing the document triggers showSelection which calls scrollIntoView
|
|
|
|
// which uses a jQuery animation, so make sure this is aborted.
|
2015-08-19 17:33:02 +00:00
|
|
|
$( OO.ui.Element.static.getClosestScrollableContainer( surfaceView.$element[ 0 ] ) ).stop( true );
|
2014-08-22 00:37:12 +00:00
|
|
|
target.scrollToHeading( headingNode );
|
2016-07-06 21:11:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( surfaceView.isFocused() ) {
|
|
|
|
scrollAndSetSelection();
|
|
|
|
} else {
|
|
|
|
// onDocumentFocus is debounced, so wait for that to happen before setting
|
|
|
|
// the model selection, otherwise it will get reset
|
|
|
|
surfaceView.once( 'focus', scrollAndSetSelection );
|
|
|
|
}
|
2014-08-22 00:37:12 +00:00
|
|
|
};
|
|
|
|
|
2014-07-23 22:30:38 +00:00
|
|
|
/**
|
2014-07-28 21:54:12 +00:00
|
|
|
* Scroll to a given heading in the document.
|
2014-07-23 22:30:38 +00:00
|
|
|
*
|
|
|
|
* @method
|
2014-07-28 21:54:12 +00:00
|
|
|
* @param {ve.ce.HeadingNode} headingNode Heading node to scroll to
|
2014-07-23 22:30:38 +00:00
|
|
|
*/
|
2015-12-10 16:07:50 +00:00
|
|
|
ve.init.mw.ArticleTarget.prototype.scrollToHeading = function ( headingNode ) {
|
2014-12-06 02:06:53 +00:00
|
|
|
var $window = $( OO.ui.Element.static.getWindow( this.$element ) );
|
2014-07-28 21:54:12 +00:00
|
|
|
|
2014-12-03 00:04:07 +00:00
|
|
|
$window.scrollTop( headingNode.$element.offset().top - this.getToolbar().$element.height() );
|
2014-07-23 22:30:38 +00:00
|
|
|
};
|
2016-02-19 18:47:33 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Show the beta dialog as needed
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.maybeShowWelcomeDialog = function () {
|
|
|
|
var usePrefs, prefSaysShow, urlSaysHide,
|
|
|
|
windowManager = this.getSurface().dialogs,
|
2018-01-10 16:58:38 +00:00
|
|
|
target = this;
|
2016-02-19 18:47:33 +00:00
|
|
|
|
|
|
|
this.welcomeDialogPromise = $.Deferred();
|
|
|
|
|
|
|
|
if ( mw.config.get( 'wgVisualEditorConfig' ).showBetaWelcome ) {
|
|
|
|
// Only use the preference value if the user is logged-in.
|
|
|
|
// If the user is anonymous, we can't save the preference
|
|
|
|
// after showing the dialog. And we don't intend to use this
|
|
|
|
// preference to influence anonymous users (use the config
|
|
|
|
// variable for that; besides the pref value would be stale if
|
|
|
|
// the wiki uses static html caching).
|
|
|
|
usePrefs = !mw.user.isAnon();
|
|
|
|
prefSaysShow = usePrefs && !mw.user.options.get( 'visualeditor-hidebetawelcome' );
|
|
|
|
urlSaysHide = 'vehidebetadialog' in new mw.Uri( location.href ).query;
|
|
|
|
|
|
|
|
if (
|
|
|
|
!urlSaysHide &&
|
|
|
|
(
|
|
|
|
prefSaysShow ||
|
|
|
|
(
|
|
|
|
!usePrefs &&
|
2018-01-10 16:58:38 +00:00
|
|
|
mw.storage.get( 've-beta-welcome-dialog' ) === null &&
|
2016-02-19 18:47:33 +00:00
|
|
|
$.cookie( 've-beta-welcome-dialog' ) === null
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) {
|
2016-04-28 21:08:26 +00:00
|
|
|
this.welcomeDialog = new mw.libs.ve.WelcomeDialog();
|
|
|
|
windowManager.addWindows( [ this.welcomeDialog ] );
|
2016-02-19 18:47:33 +00:00
|
|
|
windowManager.openWindow(
|
2016-04-28 21:08:26 +00:00
|
|
|
this.welcomeDialog,
|
|
|
|
{
|
|
|
|
switchable: this.constructor.static.trackingName !== 'mobile',
|
2016-11-30 12:03:56 +00:00
|
|
|
editor: this.getDefaultMode()
|
2016-04-28 21:08:26 +00:00
|
|
|
}
|
2016-02-19 18:47:33 +00:00
|
|
|
)
|
2017-06-01 22:21:58 +00:00
|
|
|
.closed.then( function ( data ) {
|
2016-02-19 18:47:33 +00:00
|
|
|
target.welcomeDialogPromise.resolve();
|
|
|
|
target.welcomeDialog = null;
|
2016-09-15 00:18:10 +00:00
|
|
|
// switchToWikitextEditor and switchToVisualEditor are actually
|
|
|
|
// only defined in subclasses :/
|
2016-04-28 21:08:26 +00:00
|
|
|
if ( data && data.action === 'switch-wte' ) {
|
2016-02-19 18:47:33 +00:00
|
|
|
// TODO: Make this work on mobile - right now we can only
|
|
|
|
// get away with it because the button which triggers this
|
|
|
|
// action is hidden on mobile
|
|
|
|
target.switchToWikitextEditor( true, true );
|
2016-09-15 00:18:10 +00:00
|
|
|
} else if ( data && data.action === 'switch-ve' ) {
|
|
|
|
target.switchToVisualEditor();
|
2016-02-19 18:47:33 +00:00
|
|
|
}
|
|
|
|
} );
|
|
|
|
} else {
|
|
|
|
this.welcomeDialogPromise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( prefSaysShow ) {
|
2018-05-04 13:30:10 +00:00
|
|
|
ve.init.target.getLocalApi().saveOption( 'visualeditor-hidebetawelcome', '1' );
|
2017-07-19 15:42:40 +00:00
|
|
|
mw.user.options.set( 'visualeditor-hidebetawelcome', '1' );
|
2016-02-19 18:47:33 +00:00
|
|
|
|
2016-06-07 16:17:02 +00:00
|
|
|
// No need to set a cookie every time for logged-in users that have already
|
|
|
|
// set the hidebetawelcome=1 preference, but only if this isn't a one-off
|
|
|
|
// view of the page via the hiding GET parameter.
|
2016-02-19 18:47:33 +00:00
|
|
|
} else if ( !usePrefs && !urlSaysHide ) {
|
2018-01-10 16:58:38 +00:00
|
|
|
if ( !mw.storage.set( 've-beta-welcome-dialog', 1 ) ) {
|
2016-02-19 18:47:33 +00:00
|
|
|
$.cookie( 've-beta-welcome-dialog', 1, { path: '/', expires: 30 } );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.welcomeDialogPromise.reject();
|
|
|
|
}
|
|
|
|
};
|
2017-01-19 13:25:00 +00:00
|
|
|
|
2017-01-19 22:09:20 +00:00
|
|
|
/**
|
|
|
|
* Switches to the wikitext editor, either keeping (default) or discarding changes.
|
|
|
|
*
|
|
|
|
* @param {boolean} [discardChanges] Whether to discard changes or not.
|
|
|
|
* @param {boolean} [modified] Whether there were any changes at all.
|
|
|
|
* @param {boolean} [leaveVE] Leave VE, even if source mode is available
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.switchToWikitextEditor = function ( discardChanges, modified, leaveVE ) {
|
|
|
|
var dataPromise,
|
|
|
|
target = this;
|
|
|
|
|
|
|
|
// We may have this.section but VE is always full page at the moment
|
|
|
|
this.section = null;
|
|
|
|
|
|
|
|
if ( ve.init.target.isModeAvailable( 'source' ) && !leaveVE ) {
|
|
|
|
if ( discardChanges ) {
|
2018-05-04 13:30:10 +00:00
|
|
|
dataPromise = mw.libs.ve.targetLoader.requestPageData( 'source', this.getPageName(), {
|
2018-02-21 22:58:50 +00:00
|
|
|
sessionStore: true,
|
2017-09-11 14:53:50 +00:00
|
|
|
section: this.section,
|
|
|
|
oldId: this.requestedRevId,
|
2017-10-02 21:20:53 +00:00
|
|
|
targetName: this.constructor.static.trackingName
|
2017-09-11 14:53:50 +00:00
|
|
|
} ).then(
|
2017-01-19 22:09:20 +00:00
|
|
|
function ( response ) { return response; },
|
|
|
|
function () {
|
|
|
|
// TODO: Some sort of progress bar?
|
|
|
|
target.switchToWikitextEditor( discardChanges, modified, true );
|
|
|
|
// Keep everything else waiting so our error handler can do its business
|
|
|
|
return $.Deferred().promise();
|
|
|
|
}
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this.serialize( this.getDocToSave() );
|
|
|
|
dataPromise = this.serializing.then( function ( response ) {
|
|
|
|
// HACK - add parameters the API doesn't provide for a VE->WT switch
|
|
|
|
var data = response.visualeditoredit;
|
|
|
|
data.etag = target.etag;
|
|
|
|
data.fromEditedState = modified;
|
|
|
|
data.notices = target.remoteNotices;
|
|
|
|
data.protectedClasses = target.protectedClasses;
|
|
|
|
data.basetimestamp = target.baseTimeStamp;
|
|
|
|
data.starttimestamp = target.startTimeStamp;
|
|
|
|
data.oldid = target.revid;
|
2017-08-31 15:18:45 +00:00
|
|
|
data.checkboxesDef = target.checkboxesDef;
|
2017-01-19 22:09:20 +00:00
|
|
|
return response;
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
this.reloadSurface( 'source', dataPromise );
|
|
|
|
} else {
|
|
|
|
this.switchToFallbackWikitextEditor( discardChanges, modified );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Switches to the fallback wikitext editor, either keeping (default) or discarding changes.
|
|
|
|
*
|
|
|
|
* @param {boolean} [discardChanges] Whether to discard changes or not.
|
|
|
|
* @param {boolean} [modified] Whether there were any changes at all.
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.switchToFallbackWikitextEditor = function () {
|
2018-03-10 18:04:44 +00:00
|
|
|
// Assume the fallback editor won't support VE auto-save. Changes need to
|
|
|
|
// be wiped in case the user makes changes in the other editor then comes
|
|
|
|
// back to VE.
|
|
|
|
this.getSurface().getModel().removeDocStateAndChanges();
|
2017-01-19 22:09:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Switch to the visual editor.
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.switchToVisualEditor = function () {
|
|
|
|
var dataPromise, windowManager, switchWindow,
|
|
|
|
target = this;
|
|
|
|
|
2018-08-01 02:07:44 +00:00
|
|
|
if ( !this.edited ) {
|
|
|
|
this.section = null;
|
|
|
|
this.reloadSurface( 'visual' );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-10 01:09:14 +00:00
|
|
|
// Show a discard-only confirm dialog, and then reload the whole page, if:
|
|
|
|
if (
|
|
|
|
// * section editing in WT, as WT -> VE is not yet supported, or
|
|
|
|
this.section !== null ||
|
|
|
|
// * the server can't switch for us because that's not supported.
|
|
|
|
!mw.config.get( 'wgVisualEditorConfig' ).fullRestbaseUrl
|
|
|
|
) {
|
2017-01-19 22:09:20 +00:00
|
|
|
windowManager = new OO.ui.WindowManager();
|
|
|
|
switchWindow = new mw.libs.ve.SwitchConfirmDialog();
|
2018-12-13 16:44:25 +00:00
|
|
|
$( document.body ).append( windowManager.$element );
|
2017-01-19 22:09:20 +00:00
|
|
|
windowManager.addWindows( [ switchWindow ] );
|
|
|
|
windowManager.openWindow( switchWindow, { mode: 'simple' } )
|
2017-06-01 22:21:58 +00:00
|
|
|
.closed.then( function ( data ) {
|
2017-01-19 22:09:20 +00:00
|
|
|
if ( data && data.action === 'discard' ) {
|
|
|
|
target.section = null;
|
|
|
|
target.reloadSurface( 'visual' );
|
|
|
|
}
|
|
|
|
windowManager.destroy();
|
|
|
|
} );
|
|
|
|
} else {
|
2018-05-04 13:30:10 +00:00
|
|
|
dataPromise = mw.libs.ve.targetLoader.requestParsoidData( this.getPageName(), {
|
2017-09-11 14:53:50 +00:00
|
|
|
oldId: this.revid,
|
2017-10-02 21:20:53 +00:00
|
|
|
targetName: this.constructor.static.trackingName,
|
2017-09-11 14:53:50 +00:00
|
|
|
modified: this.edited,
|
|
|
|
wikitext: this.getDocToSave()
|
|
|
|
} );
|
2017-01-19 22:09:20 +00:00
|
|
|
|
|
|
|
this.reloadSurface( 'visual', dataPromise );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Switch to a different wikitext section
|
|
|
|
*
|
|
|
|
* @param {number|string|null} section New section, number, 'new' or null (whole document)
|
2018-06-27 23:25:36 +00:00
|
|
|
* @param {boolean} noConfirm Switch without prompting (changes will be lost either way)
|
2017-01-19 22:09:20 +00:00
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.switchToWikitextSection = function ( section, noConfirm ) {
|
|
|
|
var promise,
|
|
|
|
target = this;
|
|
|
|
if ( section === this.section ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( !noConfirm && this.edited && mw.user.options.get( 'useeditwarning' ) ) {
|
|
|
|
promise = OO.ui.confirm( mw.msg( 'visualeditor-viewpage-savewarning' ) );
|
|
|
|
} else {
|
|
|
|
promise = $.Deferred().resolve( true ).promise();
|
|
|
|
}
|
|
|
|
promise.then( function ( confirmed ) {
|
|
|
|
if ( confirmed ) {
|
|
|
|
target.section = section;
|
|
|
|
target.reloadSurface( 'source' );
|
|
|
|
target.updateTabs( true );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reload the target surface in the new editor mode
|
|
|
|
*
|
|
|
|
* @param {string} newMode New mode
|
|
|
|
* @param {jQuery.Promise} [dataPromise] Data promise, if any
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.reloadSurface = function ( newMode, dataPromise ) {
|
|
|
|
this.setDefaultMode( newMode );
|
2017-06-22 17:54:04 +00:00
|
|
|
this.clearDiff();
|
2017-01-19 22:09:20 +00:00
|
|
|
// Create progress - will be discarded when surface is destroyed.
|
|
|
|
this.getSurface().createProgress(
|
|
|
|
$.Deferred().promise(),
|
|
|
|
ve.msg( newMode === 'source' ? 'visualeditor-mweditmodesource-progress' : 'visualeditor-mweditmodeve-progress' ),
|
|
|
|
true /* non-cancellable */
|
|
|
|
);
|
|
|
|
this.load( dataPromise );
|
|
|
|
};
|
2018-04-02 16:28:01 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Display the given redirect subtitle and redirect page content header on the page.
|
|
|
|
*
|
|
|
|
* @param {jQuery} $sub Redirect subtitle, see #buildRedirectSub
|
|
|
|
* @param {jQuery} $msg Redirect page content header, see #buildRedirectMsg
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.updateRedirectInterface = function ( $sub, $msg ) {
|
|
|
|
var $currentSub, $currentMsg, $subtitle,
|
|
|
|
target = this;
|
|
|
|
|
|
|
|
// For the subtitle, replace the real one with ours.
|
|
|
|
// This is more complicated than it should be because we have to fiddle with the <br>.
|
|
|
|
$currentSub = $( '#redirectsub' );
|
|
|
|
if ( $currentSub.length ) {
|
|
|
|
if ( $sub.length ) {
|
|
|
|
$currentSub.replaceWith( $sub );
|
|
|
|
} else {
|
|
|
|
$currentSub.prev().filter( 'br' ).remove();
|
|
|
|
$currentSub.remove();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$subtitle = $( '#contentSub' );
|
|
|
|
if ( $sub.length ) {
|
|
|
|
if ( $subtitle.children().length ) {
|
|
|
|
$subtitle.append( $( '<br>' ) );
|
|
|
|
}
|
|
|
|
$subtitle.append( $sub );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $msg.length ) {
|
|
|
|
$msg
|
|
|
|
// We need to be able to tell apart the real one and our fake one
|
|
|
|
.addClass( 've-redirect-header' )
|
|
|
|
.on( 'click', function ( e ) {
|
|
|
|
var windowAction = ve.ui.actionFactory.create( 'window', target.getSurface() );
|
|
|
|
windowAction.open( 'meta', { page: 'settings' } );
|
|
|
|
e.preventDefault();
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
// For the content header, the real one is hidden, insert ours before it.
|
|
|
|
$currentMsg = $( '.ve-redirect-header' );
|
|
|
|
if ( $currentMsg.length ) {
|
|
|
|
$currentMsg.replaceWith( $msg );
|
|
|
|
} else {
|
|
|
|
// Hack: This is normally inside #mw-content-text, but that's hidden while editing.
|
|
|
|
$( '#mw-content-text' ).before( $msg );
|
|
|
|
}
|
|
|
|
};
|
2018-05-09 20:12:26 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Render a list of categories
|
|
|
|
*
|
|
|
|
* @param {ve.dm.MetaItem[]} categoryItems Array of category metaitems to display
|
|
|
|
* @return {jQuery.Promise} A promise which will be resolved with the rendered categories
|
|
|
|
*/
|
|
|
|
ve.init.mw.ArticleTarget.prototype.renderCategories = function ( categoryItems ) {
|
|
|
|
var $normal, $hidden,
|
|
|
|
promises = [],
|
|
|
|
categories = { hidden: [], normal: [] };
|
2018-07-10 17:22:33 +00:00
|
|
|
categoryItems.forEach( function ( categoryItem, index ) {
|
2018-05-09 20:12:26 +00:00
|
|
|
var attributes = ve.cloneObject( ve.getProp( categoryItem, 'element', 'attributes' ) );
|
2018-07-10 17:22:33 +00:00
|
|
|
attributes.index = index;
|
2018-05-09 20:12:26 +00:00
|
|
|
promises.push( ve.init.platform.linkCache.get( attributes.category ).done( function ( result ) {
|
|
|
|
if ( result.hidden ) {
|
|
|
|
categories.hidden.push( attributes );
|
|
|
|
} else {
|
|
|
|
categories.normal.push( attributes );
|
|
|
|
}
|
|
|
|
} ) );
|
|
|
|
} );
|
|
|
|
return $.when.apply( $, promises ).then( function () {
|
2018-11-21 18:47:19 +00:00
|
|
|
var $output = $( '<div>' ).addClass( 'catlinks' );
|
2018-05-09 20:12:26 +00:00
|
|
|
function renderPageLink( page ) {
|
|
|
|
var title = mw.Title.newFromText( page.category || page );
|
|
|
|
return $( '<a>' ).attr( 'rel', 'mw:WikiLink' ).attr( 'href', title.getUrl() ).text( title.getMainText() );
|
|
|
|
}
|
|
|
|
function renderPageLinks( pages ) {
|
|
|
|
var i, $list = $( '<ul />' );
|
|
|
|
for ( i = 0; i < pages.length; i++ ) {
|
|
|
|
$list.append( $( '<li />' ).append( renderPageLink( pages[ i ] ) ) );
|
|
|
|
}
|
|
|
|
return $list;
|
|
|
|
}
|
2018-07-10 17:22:33 +00:00
|
|
|
function categorySort( a, b ) {
|
|
|
|
return a.index - b.index;
|
|
|
|
}
|
2018-05-09 20:12:26 +00:00
|
|
|
if ( categories.normal.length ) {
|
2018-07-10 17:22:33 +00:00
|
|
|
categories.normal.sort( categorySort );
|
2018-11-21 18:47:19 +00:00
|
|
|
$normal = $( '<div>' ).addClass( 'mw-normal-catlinks' );
|
2018-05-09 20:12:26 +00:00
|
|
|
$normal.append(
|
|
|
|
renderPageLink( ve.msg( 'pagecategorieslink' ) ).text( ve.msg( 'pagecategories', categories.normal.length ) ),
|
|
|
|
ve.msg( 'colon-separator' ),
|
|
|
|
renderPageLinks( categories.normal )
|
|
|
|
);
|
|
|
|
$output.append( $normal );
|
|
|
|
}
|
|
|
|
if ( categories.hidden.length ) {
|
2018-07-10 17:22:33 +00:00
|
|
|
categories.hidden.sort( categorySort );
|
2018-11-21 18:47:19 +00:00
|
|
|
$hidden = $( '<div>' ).addClass( 'mw-hidden-catlinks' );
|
2018-05-09 20:12:26 +00:00
|
|
|
if ( mw.user.options.get( 'showhiddencats' ) ) {
|
|
|
|
$hidden.addClass( 'mw-hidden-cats-user-shown' );
|
|
|
|
} else if ( mw.config.get( 'wgNamespaceIds' ).category === mw.config.get( 'wgNamespaceNumber' ) ) {
|
|
|
|
$hidden.addClass( 'mw-hidden-cats-ns-shown' );
|
|
|
|
} else {
|
|
|
|
$hidden.addClass( 'mw-hidden-cats-hidden' );
|
|
|
|
}
|
|
|
|
$hidden.append(
|
|
|
|
ve.msg( 'hidden-categories', categories.hidden.length ),
|
|
|
|
ve.msg( 'colon-separator' ),
|
|
|
|
renderPageLinks( categories.hidden )
|
|
|
|
);
|
|
|
|
$output.append( $hidden );
|
|
|
|
}
|
|
|
|
return $output;
|
|
|
|
} );
|
|
|
|
};
|
2018-10-31 13:38:05 +00:00
|
|
|
|
|
|
|
// Used in tryTeardown
|
|
|
|
ve.ui.windowFactory.register( mw.widgets.AbandonEditDialog );
|