mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/DiscussionTools
synced 2024-09-24 02:48:18 +00:00
Merge "Save the reply directly to the transcluded page"
This commit is contained in:
commit
6020bef2b6
|
@ -90,7 +90,8 @@
|
|||
"discussiontools-replywidget-placeholder-reply",
|
||||
"discussiontools-replywidget-preview",
|
||||
"discussiontools-replywidget-reply",
|
||||
"discussiontools-replywidget-terms-click"
|
||||
"discussiontools-replywidget-terms-click",
|
||||
"discussiontools-replywidget-transcluded"
|
||||
],
|
||||
"dependencies": [
|
||||
"ext.discussionTools.init",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"discussiontools-replywidget-preview": "Preview",
|
||||
"discussiontools-replywidget-reply": "Reply",
|
||||
"discussiontools-replywidget-terms-click": "By clicking \"$1\", you agree to the terms of use for this wiki.",
|
||||
"discussiontools-replywidget-transcluded": "Your comment will be saved at [[$1]].",
|
||||
"discussiontools-error-comment-disappeared": "Could not find the comment you're replying to on the page. It might have been deleted or moved to another page. Please reload the page and try again.",
|
||||
"discussiontools-error-comment-is-transcluded": "This comment can't be replied to using this tool. Please try using the full page editor instead.",
|
||||
"discussiontools-error-comment-is-transcluded-title": "This comment can't be replied to here (yet), because it is loaded from another page. Please go to [[$1]] to reply to it.",
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"discussiontools-replywidget-preview": "Label for the preview area of the reply widget",
|
||||
"discussiontools-replywidget-reply": "Label for the button to submit a reply in the reply widget",
|
||||
"discussiontools-replywidget-terms-click": "Terms of use for posting a reply.\n\n* $1 is the label of the button to be clicked, e.g. {{msg-mw|discussiontools-replywidget-reply}}.",
|
||||
"discussiontools-replywidget-transcluded": "Message explaining that the comment will be saved on a different page than the one you're viewing right now (because it was transcluded from it). Parameter: $1 – page name",
|
||||
"discussiontools-error-comment-disappeared": "Error message.",
|
||||
"discussiontools-error-comment-is-transcluded": "Error message.",
|
||||
"discussiontools-error-comment-is-transcluded-title": "Error message. Parameter: $1 – page name",
|
||||
|
|
|
@ -61,17 +61,15 @@ function setupComment( comment ) {
|
|||
|
||||
if ( !widgetPromise ) {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
parsoidPromise = getParsoidCommentData( comment.id );
|
||||
parsoidPromise = getParsoidTranscludedCommentData( comment.id );
|
||||
|
||||
widgetPromise = parsoidPromise.then( function () {
|
||||
widgetPromise = parsoidPromise.then( function ( parsoidData ) {
|
||||
return replyWidgetPromise.then( function () {
|
||||
var
|
||||
ReplyWidget = config.useVisualEditor ?
|
||||
require( 'ext.discussionTools.ReplyWidgetVisual' ) :
|
||||
require( 'ext.discussionTools.ReplyWidgetPlain' ),
|
||||
replyWidget = new ReplyWidget(
|
||||
comment
|
||||
);
|
||||
replyWidget = new ReplyWidget( parsoidData );
|
||||
|
||||
replyWidget.on( 'teardown', teardown );
|
||||
|
||||
|
@ -181,6 +179,25 @@ function postReply( widget, parsoidData ) {
|
|||
return $.Deferred().resolve().promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest revision ID of the page.
|
||||
*
|
||||
* @param {string} pageName
|
||||
* @return {jQuery.Promise}
|
||||
*/
|
||||
function getLatestRevId( pageName ) {
|
||||
return ( new mw.Api() ).get( {
|
||||
action: 'query',
|
||||
prop: 'revisions',
|
||||
rvprop: 'ids',
|
||||
rvlimit: 1,
|
||||
titles: pageName,
|
||||
formatversion: 2
|
||||
} ).then( function ( resp ) {
|
||||
return resp.query.pages[ 0 ].revisions[ 0 ].revid;
|
||||
} );
|
||||
}
|
||||
|
||||
function save( widget, parsoidData ) {
|
||||
var root, summaryPrefix, summary, promise,
|
||||
mode = widget.getMode(),
|
||||
|
@ -226,21 +243,9 @@ function save( widget, parsoidData ) {
|
|||
// comment has been deleted from the page, or if retry also fails for some other reason, the
|
||||
// error is handled as normal below.
|
||||
if ( code === 'editconflict' ) {
|
||||
return widget.api.get( {
|
||||
action: 'query',
|
||||
prop: 'revisions',
|
||||
rvprop: 'ids',
|
||||
rvlimit: 1,
|
||||
titles: mw.config.get( 'wgRelevantPageName' ),
|
||||
formatversion: 2
|
||||
} ).then( function ( resp ) {
|
||||
var latestRevId = resp.query.pages[ 0 ].revisions[ 0 ].revid;
|
||||
mw.config.set( {
|
||||
wgCurRevisionId: latestRevId,
|
||||
wgRevisionId: latestRevId
|
||||
} );
|
||||
return getLatestRevId( pageData.pageName ).then( function ( latestRevId ) {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
return getParsoidCommentData( comment.id ).then( function ( parsoidData ) {
|
||||
return getParsoidCommentData( pageData.pageName, latestRevId, comment.id ).then( function ( parsoidData ) {
|
||||
return save( widget, parsoidData );
|
||||
} );
|
||||
} );
|
||||
|
@ -316,17 +321,17 @@ function getPageData( pageName, oldId ) {
|
|||
/**
|
||||
* Get the Parsoid document DOM, parse comments and threads, and find a specific comment in it.
|
||||
*
|
||||
* @param {string} commentId Comment ID, from a comment parsed in the local document
|
||||
* @param {string} pageName Page title
|
||||
* @param {number} oldId Revision ID
|
||||
* @param {string} commentId Comment ID
|
||||
* @return {jQuery.Promise}
|
||||
*/
|
||||
function getParsoidCommentData( commentId ) {
|
||||
var parsoidPageData, parsoidDoc, parsoidComments, parsoidCommentsById,
|
||||
pageName = mw.config.get( 'wgRelevantPageName' ),
|
||||
oldId = mw.config.get( 'wgCurRevisionId' );
|
||||
function getParsoidCommentData( pageName, oldId, commentId ) {
|
||||
var parsoidPageData, parsoidDoc, parsoidComments, parsoidCommentsById;
|
||||
|
||||
return getPageData( pageName, oldId )
|
||||
.then( function ( response ) {
|
||||
var data, comment, transcludedFrom, transcludedErrMsg, mwTitle;
|
||||
var data, comment, transcludedFrom, transcludedErrMsg, mwTitle, follow;
|
||||
|
||||
data = response.visualeditor;
|
||||
parsoidDoc = ve.parseXhtml( data.content );
|
||||
|
@ -362,10 +367,10 @@ function getParsoidCommentData( commentId ) {
|
|||
transcludedFrom = parser.getTranscludedFrom( comment );
|
||||
if ( transcludedFrom ) {
|
||||
mwTitle = transcludedFrom === true ? null : mw.Title.newFromText( transcludedFrom );
|
||||
|
||||
// If this refers to a template rather than a subpage, we never want to edit it
|
||||
if ( mwTitle && mwTitle.getNamespaceId() !== mw.config.get( 'wgNamespaceIds' ).template ) {
|
||||
// TODO: Post the reply to the target page instead
|
||||
follow = mwTitle && mwTitle.getNamespaceId() !== mw.config.get( 'wgNamespaceIds' ).template;
|
||||
|
||||
if ( follow ) {
|
||||
transcludedErrMsg = mw.message( 'discussiontools-error-comment-is-transcluded-title',
|
||||
mwTitle.getPrefixedText() ).parse();
|
||||
} else {
|
||||
|
@ -373,6 +378,10 @@ function getParsoidCommentData( commentId ) {
|
|||
}
|
||||
|
||||
return $.Deferred().reject( 'comment-is-transcluded', { errors: [ {
|
||||
data: {
|
||||
transcludedFrom: transcludedFrom,
|
||||
follow: follow
|
||||
},
|
||||
code: 'comment-is-transcluded',
|
||||
html: transcludedErrMsg
|
||||
} ] } ).promise();
|
||||
|
@ -386,6 +395,41 @@ function getParsoidCommentData( commentId ) {
|
|||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Like #getParsoidCommentData, but assumes the comment was found on the current page,
|
||||
* and then follows transclusions to determine the source page where it is written.
|
||||
*
|
||||
* @param {string} commentId Comment ID, from a comment parsed in the local document
|
||||
* @return {jQuery.Promise}
|
||||
*/
|
||||
function getParsoidTranscludedCommentData( commentId ) {
|
||||
var promise,
|
||||
pageName = mw.config.get( 'wgRelevantPageName' ),
|
||||
oldId = mw.config.get( 'wgCurRevisionId' );
|
||||
|
||||
function followTransclusion( recursionLimit, code, data ) {
|
||||
var errorData;
|
||||
if ( recursionLimit > 0 && code === 'comment-is-transcluded' ) {
|
||||
errorData = data.errors[ 0 ].data;
|
||||
if ( errorData.follow && typeof errorData.transcludedFrom === 'string' ) {
|
||||
return getLatestRevId( errorData.transcludedFrom ).then( function ( latestRevId ) {
|
||||
// Fetch the transcluded page, until we cross the recursion limit
|
||||
return getParsoidCommentData( errorData.transcludedFrom, latestRevId, commentId )
|
||||
.catch( followTransclusion.bind( null, recursionLimit - 1 ) );
|
||||
} );
|
||||
}
|
||||
}
|
||||
return $.Deferred().reject( code, data );
|
||||
}
|
||||
|
||||
// Arbitrary limit of 10 steps, which should be more than anyone could ever need
|
||||
// (there are reasonable use cases for at least 2)
|
||||
promise = getParsoidCommentData( pageName, oldId, commentId )
|
||||
.catch( followTransclusion.bind( null, 10 ) );
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function init( $container, state ) {
|
||||
var
|
||||
pageComments, pageThreads, pageCommentsById,
|
||||
|
|
|
@ -8,11 +8,11 @@ var controller = require( 'ext.discussionTools.init' ).controller,
|
|||
* @class mw.dt.ReplyWidget
|
||||
* @extends OO.ui.Widget
|
||||
* @constructor
|
||||
* @param {Object} comment Parsed comment object
|
||||
* @param {Object} parsoidData Result from controller#getParsoidCommentData
|
||||
* @param {Object} [config] Configuration options
|
||||
* @param {Object} [config.input] Configuration options for the comment input widget
|
||||
*/
|
||||
function ReplyWidget( comment, config ) {
|
||||
function ReplyWidget( parsoidData, config ) {
|
||||
var returnTo, contextNode, inputConfig;
|
||||
|
||||
config = config || {};
|
||||
|
@ -21,12 +21,13 @@ function ReplyWidget( comment, config ) {
|
|||
ReplyWidget.super.call( this, config );
|
||||
|
||||
this.pending = false;
|
||||
this.comment = comment;
|
||||
this.comment = parsoidData.comment;
|
||||
this.pageData = parsoidData.pageData;
|
||||
contextNode = utils.closestElement( this.comment.range.endContainer, [ 'dl', 'ul', 'ol' ] );
|
||||
this.context = contextNode ? contextNode.nodeName.toLowerCase() : 'dl';
|
||||
|
||||
inputConfig = $.extend(
|
||||
{ placeholder: mw.msg( 'discussiontools-replywidget-placeholder-reply', comment.author ) },
|
||||
{ placeholder: mw.msg( 'discussiontools-replywidget-placeholder-reply', this.comment.author ) },
|
||||
config.input
|
||||
);
|
||||
this.replyBodyWidget = this.createReplyBodyWidget( inputConfig );
|
||||
|
@ -46,10 +47,16 @@ function ReplyWidget( comment, config ) {
|
|||
this.cancelButton.$element,
|
||||
this.replyButton.$element
|
||||
);
|
||||
this.$terms = $( '<div>' ).addClass( 'dt-ui-replyWidget-terms' ).append(
|
||||
this.$footer = $( '<div>' ).addClass( 'dt-ui-replyWidget-footer' );
|
||||
if ( this.pageData.pageName !== mw.config.get( 'wgRelevantPageName' ) ) {
|
||||
this.$footer.append( $( '<p>' ).append(
|
||||
mw.message( 'discussiontools-replywidget-transcluded', this.pageData.pageName ).parseDom()
|
||||
) );
|
||||
}
|
||||
this.$footer.append( $( '<p>' ).append(
|
||||
mw.message( 'discussiontools-replywidget-terms-click', mw.msg( 'discussiontools-replywidget-reply' ) ).parseDom()
|
||||
);
|
||||
this.$actionsWrapper.append( this.$terms, this.$actions );
|
||||
) );
|
||||
this.$actionsWrapper.append( this.$footer, this.$actions );
|
||||
|
||||
// Events
|
||||
this.replyButton.connect( this, { click: 'onReplyClick' } );
|
||||
|
@ -84,7 +91,7 @@ function ReplyWidget( comment, config ) {
|
|||
.parseDom()
|
||||
} );
|
||||
this.anonWarning.$element.append( this.$actions );
|
||||
this.$element.append( this.anonWarning.$element, this.$terms );
|
||||
this.$element.append( this.anonWarning.$element, this.$footer );
|
||||
this.$actionsWrapper.detach();
|
||||
}
|
||||
|
||||
|
@ -291,39 +298,81 @@ ReplyWidget.prototype.onReplyClick = function () {
|
|||
|
||||
logger( { action: 'saveIntent' } );
|
||||
|
||||
// TODO: When editing a transcluded page, VE API returning the page HTML is a waste, since we won't use it
|
||||
|
||||
// We must get a new copy of the document every time, otherwise any unsaved replies will pile up
|
||||
controller.getParsoidCommentData( this.comment.id ).then( function ( parsoidData ) {
|
||||
controller.getParsoidCommentData( this.pageData.pageName, this.pageData.oldId, this.comment.id ).then( function ( parsoidData ) {
|
||||
logger( { action: 'saveAttempt' } );
|
||||
return controller.save( widget, parsoidData );
|
||||
} ).then( function ( data ) {
|
||||
// eslint-disable-next-line no-jquery/no-global-selector
|
||||
var $container = $( '#mw-content-text' );
|
||||
var
|
||||
pageUpdated = $.Deferred(),
|
||||
// eslint-disable-next-line no-jquery/no-global-selector
|
||||
$container = $( '#mw-content-text' );
|
||||
|
||||
widget.teardown();
|
||||
// TODO: Tell controller to teardown all other open widgets
|
||||
|
||||
// Update page state
|
||||
$container.html( data.content );
|
||||
mw.config.set( {
|
||||
wgCurRevisionId: data.newrevid,
|
||||
wgRevisionId: data.newrevid
|
||||
} );
|
||||
mw.config.set( data.jsconfigvars );
|
||||
mw.loader.load( data.modules );
|
||||
// TODO update categories, lastmodified
|
||||
// (see ve.init.mw.DesktopArticleTarget.prototype.replacePageContent)
|
||||
if ( widget.pageData.pageName === mw.config.get( 'wgRelevantPageName' ) ) {
|
||||
// We can use the result from the VisualEditor API
|
||||
$container.html( data.content );
|
||||
mw.config.set( {
|
||||
wgCurRevisionId: data.newrevid,
|
||||
wgRevisionId: data.newrevid
|
||||
} );
|
||||
mw.config.set( data.jsconfigvars );
|
||||
// Note: VE API merges 'modules' and 'modulestyles'
|
||||
mw.loader.load( data.modules );
|
||||
// TODO update categories, displaytitle, lastmodified
|
||||
// (see ve.init.mw.DesktopArticleTarget.prototype.replacePageContent)
|
||||
|
||||
// Re-initialize
|
||||
controller.init( $container.find( '.mw-parser-output' ), {
|
||||
repliedTo: widget.comment.id
|
||||
} );
|
||||
mw.hook( 'wikipage.content' ).fire( $container );
|
||||
pageUpdated.resolve();
|
||||
|
||||
logger( {
|
||||
action: 'saveSuccess',
|
||||
// eslint-disable-next-line camelcase
|
||||
revision_id: data.newrevid
|
||||
} else {
|
||||
// We saved to another page, we must purge and then fetch the current page
|
||||
widget.api.post( {
|
||||
action: 'purge',
|
||||
titles: mw.config.get( 'wgRelevantPageName' )
|
||||
} ).then( function () {
|
||||
return widget.api.get( {
|
||||
formatversion: 2,
|
||||
action: 'parse',
|
||||
prop: [ 'text', 'modules', 'jsconfigvars' ],
|
||||
page: mw.config.get( 'wgRelevantPageName' )
|
||||
} );
|
||||
} ).then( function ( parseResp ) {
|
||||
$container.html( parseResp.parse.text );
|
||||
mw.config.set( parseResp.parse.jsconfigvars );
|
||||
mw.loader.load( parseResp.parse.modulestyles );
|
||||
mw.loader.load( parseResp.parse.modules );
|
||||
// TODO update categories, displaytitle, lastmodified
|
||||
// We may not be able to use prop=displaytitle without making changes in the action=parse API,
|
||||
// VE API has some confusing code that changes the HTML escaping on it before returning???
|
||||
|
||||
pageUpdated.resolve();
|
||||
|
||||
} ).catch( function () {
|
||||
// We saved the reply, but couldn't purge or fetch the updated page. Seems difficult to
|
||||
// explain this problem. Redirect to the page where the user can at least see their reply…
|
||||
window.location = mw.util.getUrl( widget.pageData.pageName );
|
||||
} );
|
||||
}
|
||||
|
||||
pageUpdated.then( function () {
|
||||
// Re-initialize
|
||||
controller.init( $container.find( '.mw-parser-output' ), {
|
||||
repliedTo: widget.comment.id
|
||||
} );
|
||||
mw.hook( 'wikipage.content' ).fire( $container );
|
||||
|
||||
logger( {
|
||||
action: 'saveSuccess',
|
||||
// eslint-disable-next-line camelcase
|
||||
revision_id: data.newrevid
|
||||
} );
|
||||
} );
|
||||
|
||||
}, function ( code, data ) {
|
||||
var typeMap = {
|
||||
// Compare to ve.init.mw.ArticleTargetEvents.js in VisualEditor.
|
||||
|
|
|
@ -17,10 +17,14 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-terms {
|
||||
&-footer {
|
||||
flex-grow: 1;
|
||||
font-size: 0.75em;
|
||||
color: #54595d;
|
||||
|
||||
> p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-preview {
|
||||
|
|
Loading…
Reference in a new issue