2019-11-05 13:55:01 +00:00
|
|
|
'use strict';
|
|
|
|
|
2019-12-10 21:46:22 +00:00
|
|
|
var
|
2020-06-11 17:27:30 +00:00
|
|
|
api = new mw.Api( { parameters: { formatversion: 2 } } ),
|
2020-04-30 13:20:41 +00:00
|
|
|
$pageContainer,
|
2020-02-25 02:10:27 +00:00
|
|
|
parser = require( './parser.js' ),
|
2020-04-28 19:20:07 +00:00
|
|
|
utils = require( './utils.js' ),
|
2020-04-30 13:20:41 +00:00
|
|
|
pageDataCache = {};
|
2019-11-05 13:55:01 +00:00
|
|
|
|
2020-02-25 02:10:27 +00:00
|
|
|
mw.messages.set( require( './controller/contLangMessages.json' ) );
|
2020-03-10 13:03:45 +00:00
|
|
|
|
2020-07-20 14:13:59 +00:00
|
|
|
function traverseNode( parent ) {
|
2020-04-30 13:20:41 +00:00
|
|
|
// Loads later to avoid circular dependency
|
|
|
|
var CommentController = require( './CommentController.js' );
|
2020-04-27 15:50:02 +00:00
|
|
|
parent.replies.forEach( function ( comment ) {
|
|
|
|
if ( comment.type === 'comment' ) {
|
|
|
|
// eslint-disable-next-line no-new
|
2020-07-20 14:13:59 +00:00
|
|
|
new CommentController( $pageContainer, comment );
|
2020-04-27 15:50:02 +00:00
|
|
|
}
|
2020-07-20 14:13:59 +00:00
|
|
|
traverseNode( comment );
|
2020-04-27 15:50:02 +00:00
|
|
|
} );
|
2019-11-05 13:55:01 +00:00
|
|
|
}
|
|
|
|
|
2019-11-13 13:57:57 +00:00
|
|
|
function highlight( comment ) {
|
|
|
|
var padding = 5,
|
2020-01-15 16:07:05 +00:00
|
|
|
// $container must be position:relative/absolute
|
|
|
|
$container = OO.ui.getDefaultOverlay(),
|
|
|
|
containerRect = $container[ 0 ].getBoundingClientRect(),
|
2020-02-05 18:30:21 +00:00
|
|
|
nativeRange, rect,
|
2019-11-13 13:57:57 +00:00
|
|
|
$highlight = $( '<div>' ).addClass( 'dt-init-highlight' );
|
|
|
|
|
2020-04-28 19:20:07 +00:00
|
|
|
nativeRange = utils.getNativeRange( comment );
|
2020-02-05 18:30:21 +00:00
|
|
|
rect = RangeFix.getBoundingClientRect( nativeRange );
|
|
|
|
|
2019-11-13 13:57:57 +00:00
|
|
|
$highlight.css( {
|
|
|
|
top: rect.top - containerRect.top - padding,
|
|
|
|
left: rect.left - containerRect.left - padding,
|
|
|
|
width: rect.width + ( padding * 2 ),
|
|
|
|
height: rect.height + ( padding * 2 )
|
|
|
|
} );
|
2020-06-11 16:36:59 +00:00
|
|
|
$container.prepend( $highlight );
|
2019-11-13 13:57:57 +00:00
|
|
|
|
2020-06-11 16:36:59 +00:00
|
|
|
OO.ui.Element.static.scrollIntoView( $highlight[ 0 ], { padding: { top: 10, bottom: 10 } } ).then( function () {
|
2019-11-13 13:57:57 +00:00
|
|
|
setTimeout( function () {
|
2020-06-11 16:36:59 +00:00
|
|
|
$highlight.addClass( 'dt-init-highlight-fade' );
|
|
|
|
setTimeout( function () {
|
|
|
|
$highlight.remove();
|
|
|
|
}, 500 );
|
2019-11-13 13:57:57 +00:00
|
|
|
}, 500 );
|
2020-06-11 16:36:59 +00:00
|
|
|
} );
|
2019-11-13 13:57:57 +00:00
|
|
|
}
|
|
|
|
|
2019-11-24 15:39:52 +00:00
|
|
|
function commentsById( comments ) {
|
|
|
|
var byId = {};
|
|
|
|
comments.forEach( function ( comment ) {
|
|
|
|
byId[ comment.id ] = comment;
|
|
|
|
} );
|
|
|
|
return byId;
|
|
|
|
}
|
|
|
|
|
2020-02-15 04:03:18 +00:00
|
|
|
/**
|
|
|
|
* Get the Parsoid document HTML and metadata needed to edit this page from the API.
|
|
|
|
*
|
|
|
|
* This method caches responses. If you call it again with the same parameters, you'll get the exact
|
|
|
|
* same Promise object, and no API request will be made.
|
|
|
|
*
|
2020-03-04 16:19:19 +00:00
|
|
|
* TODO: Resolve the naming conflict between this raw "pageData" from the API, and the
|
|
|
|
* plain object "pageData" that gets attached to parsoidData.
|
|
|
|
*
|
2020-02-15 04:03:18 +00:00
|
|
|
* @param {string} pageName Page title
|
|
|
|
* @param {number} oldId Revision ID
|
|
|
|
* @return {jQuery.Promise}
|
|
|
|
*/
|
|
|
|
function getPageData( pageName, oldId ) {
|
2020-06-03 22:19:44 +00:00
|
|
|
var lintPromise;
|
2020-02-15 04:03:18 +00:00
|
|
|
pageDataCache[ pageName ] = pageDataCache[ pageName ] || {};
|
|
|
|
if ( pageDataCache[ pageName ][ oldId ] ) {
|
|
|
|
return pageDataCache[ pageName ][ oldId ];
|
|
|
|
}
|
2020-06-02 23:00:59 +00:00
|
|
|
|
|
|
|
lintPromise = api.get( {
|
|
|
|
action: 'query',
|
|
|
|
list: 'linterrors',
|
|
|
|
lntcategories: 'fostered',
|
|
|
|
lntlimit: 1,
|
2020-06-09 18:52:38 +00:00
|
|
|
lnttitle: pageName
|
2020-06-02 23:00:59 +00:00
|
|
|
} ).then( function ( response ) {
|
|
|
|
return OO.getProp( response, 'query', 'linterrors' ) || [];
|
|
|
|
} );
|
|
|
|
|
2020-02-15 04:03:18 +00:00
|
|
|
pageDataCache[ pageName ][ oldId ] = mw.loader.using( 'ext.visualEditor.targetLoader' ).then( function () {
|
2020-06-03 22:19:44 +00:00
|
|
|
var pageDataPromise = mw.libs.ve.targetLoader.requestPageData(
|
|
|
|
'visual', pageName, { oldId: oldId }
|
2020-02-15 04:03:18 +00:00
|
|
|
);
|
2020-06-03 22:19:44 +00:00
|
|
|
return $.when( lintPromise, pageDataPromise ).then( function ( linterrors, pageData ) {
|
|
|
|
pageData.linterrors = linterrors;
|
|
|
|
return pageData;
|
|
|
|
} );
|
2020-02-15 04:03:18 +00:00
|
|
|
}, function () {
|
|
|
|
// Clear on failure
|
|
|
|
pageDataCache[ pageName ][ oldId ] = null;
|
|
|
|
} );
|
|
|
|
return pageDataCache[ pageName ][ oldId ];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the Parsoid document DOM, parse comments and threads, and find a specific comment in it.
|
|
|
|
*
|
2020-03-12 19:58:36 +00:00
|
|
|
* @param {string} pageName Page title
|
|
|
|
* @param {number} oldId Revision ID
|
|
|
|
* @param {string} commentId Comment ID
|
2020-02-15 04:03:18 +00:00
|
|
|
* @return {jQuery.Promise}
|
|
|
|
*/
|
2020-03-12 19:58:36 +00:00
|
|
|
function getParsoidCommentData( pageName, oldId, commentId ) {
|
|
|
|
var parsoidPageData, parsoidDoc, parsoidComments, parsoidCommentsById;
|
2020-02-15 04:03:18 +00:00
|
|
|
|
|
|
|
return getPageData( pageName, oldId )
|
|
|
|
.then( function ( response ) {
|
2020-04-15 19:04:09 +00:00
|
|
|
var data, comment, transcludedFrom, transcludedErrMsg, mwTitle, follow,
|
2020-06-03 22:19:44 +00:00
|
|
|
lintType,
|
|
|
|
lintErrors = response.linterrors;
|
2020-03-09 20:53:41 +00:00
|
|
|
|
|
|
|
data = response.visualeditor;
|
2020-03-02 17:11:53 +00:00
|
|
|
parsoidDoc = ve.parseXhtml( data.content );
|
2020-03-19 17:37:19 +00:00
|
|
|
// Remove section wrappers, they interfere with transclusion handling
|
|
|
|
mw.libs.ve.unwrapParsoidSections( parsoidDoc.body );
|
2020-03-04 21:46:08 +00:00
|
|
|
// Mirror VE's ve.init.mw.Target.prototype.fixBase behavior:
|
|
|
|
ve.fixBase( parsoidDoc, document, ve.resolveUrl(
|
|
|
|
// Don't replace $1 with the page name, because that'll break if
|
|
|
|
// the page name contains a slash
|
|
|
|
mw.config.get( 'wgArticlePath' ).replace( '$1', '' ),
|
|
|
|
document
|
|
|
|
) );
|
2020-02-15 04:03:18 +00:00
|
|
|
parsoidComments = parser.getComments( parsoidDoc.body );
|
|
|
|
|
|
|
|
parsoidPageData = {
|
|
|
|
pageName: pageName,
|
2020-03-03 13:20:23 +00:00
|
|
|
oldId: oldId,
|
|
|
|
startTimeStamp: data.starttimestamp,
|
|
|
|
etag: data.etag
|
2020-02-15 04:03:18 +00:00
|
|
|
};
|
|
|
|
|
2020-03-09 20:53:41 +00:00
|
|
|
// getThreads builds the tree structure, currently only
|
|
|
|
// used to set 'replies' and 'id'
|
2020-02-15 04:03:18 +00:00
|
|
|
parser.groupThreads( parsoidComments );
|
|
|
|
parsoidCommentsById = commentsById( parsoidComments );
|
2020-03-09 20:53:41 +00:00
|
|
|
comment = parsoidCommentsById[ commentId ];
|
2020-02-15 04:03:18 +00:00
|
|
|
|
2020-03-09 20:53:41 +00:00
|
|
|
if ( !comment ) {
|
2020-02-15 04:31:06 +00:00
|
|
|
return $.Deferred().reject( 'comment-disappeared', { errors: [ {
|
|
|
|
code: 'comment-disappeared',
|
|
|
|
html: mw.message( 'discussiontools-error-comment-disappeared' ).parse()
|
|
|
|
} ] } ).promise();
|
2020-02-15 04:03:18 +00:00
|
|
|
}
|
|
|
|
|
2020-07-20 14:48:41 +00:00
|
|
|
transcludedFrom = comment.getTranscludedFrom();
|
2020-03-09 20:53:41 +00:00
|
|
|
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
|
2020-03-12 19:58:36 +00:00
|
|
|
follow = mwTitle && mwTitle.getNamespaceId() !== mw.config.get( 'wgNamespaceIds' ).template;
|
|
|
|
|
|
|
|
if ( follow ) {
|
2020-04-25 18:07:00 +00:00
|
|
|
transcludedErrMsg = mw.message(
|
|
|
|
'discussiontools-error-comment-is-transcluded-title',
|
|
|
|
mwTitle.getPrefixedText()
|
|
|
|
).parse();
|
2020-03-09 20:53:41 +00:00
|
|
|
} else {
|
2020-04-25 18:07:00 +00:00
|
|
|
transcludedErrMsg = mw.message(
|
|
|
|
'discussiontools-error-comment-is-transcluded',
|
|
|
|
// eslint-disable-next-line no-jquery/no-global-selector
|
|
|
|
$( '#ca-edit' ).text()
|
|
|
|
).parse();
|
2020-03-09 20:53:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $.Deferred().reject( 'comment-is-transcluded', { errors: [ {
|
2020-03-12 19:58:36 +00:00
|
|
|
data: {
|
|
|
|
transcludedFrom: transcludedFrom,
|
|
|
|
follow: follow
|
|
|
|
},
|
2020-03-09 20:53:41 +00:00
|
|
|
code: 'comment-is-transcluded',
|
|
|
|
html: transcludedErrMsg
|
|
|
|
} ] } ).promise();
|
|
|
|
}
|
|
|
|
|
2020-06-03 22:19:44 +00:00
|
|
|
if ( lintErrors.length ) {
|
|
|
|
// We currently only request the first error
|
|
|
|
lintType = lintErrors[ 0 ].category;
|
|
|
|
|
|
|
|
return $.Deferred().reject( 'lint', { errors: [ {
|
|
|
|
code: 'lint',
|
|
|
|
html: mw.message( 'discussiontools-error-lint',
|
|
|
|
'https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Lint_errors/' + lintType,
|
|
|
|
'https://www.mediawiki.org/wiki/Special:MyLanguage/Help_talk:Lint_errors/' + lintType,
|
|
|
|
mw.util.getUrl( pageName, { action: 'edit', lintid: lintErrors[ 0 ].lintId } ) ).parse()
|
|
|
|
} ] } ).promise();
|
2020-04-15 19:04:09 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 04:03:18 +00:00
|
|
|
return {
|
|
|
|
comment: parsoidCommentsById[ commentId ],
|
|
|
|
doc: parsoidDoc,
|
|
|
|
pageData: parsoidPageData
|
|
|
|
};
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2020-03-04 16:19:19 +00:00
|
|
|
function getCheckboxesPromise( pageData ) {
|
|
|
|
return getPageData(
|
|
|
|
pageData.pageName,
|
|
|
|
pageData.oldId
|
|
|
|
).then( function ( response ) {
|
|
|
|
var data = response.visualeditor,
|
|
|
|
checkboxesDef = {};
|
|
|
|
|
|
|
|
mw.messages.set( data.checkboxesMessages );
|
|
|
|
|
|
|
|
// Only show the watch checkbox for now
|
|
|
|
if ( 'wpWatchthis' in data.checkboxesDef ) {
|
|
|
|
checkboxesDef.wpWatchthis = data.checkboxesDef.wpWatchthis;
|
|
|
|
}
|
|
|
|
// targetLoader was loaded by getPageData
|
|
|
|
return mw.libs.ve.targetLoader.createCheckboxFields( checkboxesDef );
|
|
|
|
// TODO: createCheckboxField doesn't make links in the label open in a new
|
|
|
|
// window as that method currently lives in ve.utils
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2019-11-13 13:57:57 +00:00
|
|
|
function init( $container, state ) {
|
2019-11-24 15:39:52 +00:00
|
|
|
var
|
|
|
|
pageComments, pageThreads, pageCommentsById,
|
2020-02-15 04:03:18 +00:00
|
|
|
repliedToComment;
|
2019-11-05 13:55:01 +00:00
|
|
|
|
|
|
|
$pageContainer = $container;
|
2019-12-10 21:46:22 +00:00
|
|
|
pageComments = parser.getComments( $pageContainer[ 0 ] );
|
|
|
|
pageThreads = parser.groupThreads( pageComments );
|
2019-11-24 15:39:52 +00:00
|
|
|
pageCommentsById = commentsById( pageComments );
|
|
|
|
|
2020-04-29 16:43:11 +00:00
|
|
|
$pageContainer.removeClass( 'dt-init-replylink-open' );
|
|
|
|
|
2020-07-20 14:13:59 +00:00
|
|
|
pageThreads.forEach( traverseNode );
|
2019-11-05 13:55:01 +00:00
|
|
|
|
|
|
|
// For debugging
|
|
|
|
mw.dt.pageThreads = pageThreads;
|
|
|
|
|
2019-11-24 15:39:52 +00:00
|
|
|
if ( state.repliedTo ) {
|
|
|
|
// Find the comment we replied to, then highlight the last reply
|
|
|
|
repliedToComment = pageCommentsById[ state.repliedTo ];
|
|
|
|
highlight( repliedToComment.replies[ repliedToComment.replies.length - 1 ] );
|
2019-11-13 13:57:57 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 04:03:18 +00:00
|
|
|
// Preload the Parsoid document.
|
|
|
|
// TODO: Isn't this too early to load it? We will only need it if the user tries replying...
|
|
|
|
getPageData(
|
|
|
|
mw.config.get( 'wgRelevantPageName' ),
|
2020-02-15 04:49:58 +00:00
|
|
|
mw.config.get( 'wgCurRevisionId' )
|
2020-02-15 04:03:18 +00:00
|
|
|
);
|
2019-11-05 13:55:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
init: init,
|
2020-02-15 04:03:18 +00:00
|
|
|
getParsoidCommentData: getParsoidCommentData,
|
2020-06-26 22:24:14 +00:00
|
|
|
getCheckboxesPromise: getCheckboxesPromise
|
2019-11-05 13:55:01 +00:00
|
|
|
};
|