2020-10-21 15:52:04 +00:00
|
|
|
/* global moment */
|
2020-06-25 12:23:17 +00:00
|
|
|
|
2020-07-20 14:48:41 +00:00
|
|
|
var utils = require( './utils.js' );
|
|
|
|
|
2020-06-25 12:23:17 +00:00
|
|
|
/**
|
|
|
|
* A thread item, either a heading or a comment
|
|
|
|
*
|
|
|
|
* @class ThreadItem
|
|
|
|
* @constructor
|
|
|
|
* @param {string} type `heading` or `comment`
|
2020-10-01 19:36:11 +00:00
|
|
|
* @param {number} level Indentation level
|
2020-06-25 12:23:17 +00:00
|
|
|
* @param {Object} range Object describing the extent of the comment, including the
|
|
|
|
* signature and timestamp. It has the same properties as a Range object: `startContainer`,
|
|
|
|
* `startOffset`, `endContainer`, `endOffset` (we don't use a real Range because they change
|
|
|
|
* magically when the DOM structure changes).
|
|
|
|
*/
|
2020-05-22 16:26:05 +00:00
|
|
|
function ThreadItem( type, level, range ) {
|
|
|
|
this.type = type;
|
|
|
|
this.level = level;
|
|
|
|
this.range = range;
|
|
|
|
|
2020-06-25 12:23:17 +00:00
|
|
|
/**
|
2021-02-12 19:16:13 +00:00
|
|
|
* @member {string} Name for this comment, intended to be used to
|
2020-06-25 12:23:17 +00:00
|
|
|
* find this comment in other revisions of the same page
|
|
|
|
*/
|
2021-02-12 19:16:13 +00:00
|
|
|
this.name = null;
|
|
|
|
/**
|
|
|
|
* @member {string} Unique ID (within the page) for this comment
|
|
|
|
*/
|
2020-05-22 16:26:05 +00:00
|
|
|
this.id = null;
|
2022-02-19 06:31:34 +00:00
|
|
|
/**
|
|
|
|
* @member {string|null} Unique ID (within the page) for this comment, according to an older algorithm
|
|
|
|
*/
|
|
|
|
this.legacyId = null;
|
2020-06-25 12:23:17 +00:00
|
|
|
/**
|
2020-12-12 12:52:17 +00:00
|
|
|
* @member {ThreadItem[]} Replies to this thread item
|
2020-06-25 12:23:17 +00:00
|
|
|
*/
|
2020-05-22 16:26:05 +00:00
|
|
|
this.replies = [];
|
2020-07-29 23:57:51 +00:00
|
|
|
|
2020-11-02 18:35:38 +00:00
|
|
|
/**
|
|
|
|
* @member {string[]} Warnings
|
|
|
|
*/
|
|
|
|
this.warnings = [];
|
|
|
|
|
2020-07-29 23:57:51 +00:00
|
|
|
this.rootNode = null;
|
2020-05-22 16:26:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
OO.initClass( ThreadItem );
|
|
|
|
|
2020-09-16 12:07:27 +00:00
|
|
|
/**
|
|
|
|
* Create a new ThreadItem from a JSON serialization
|
|
|
|
*
|
|
|
|
* @param {string|Object} json JSON serialization or hash object
|
|
|
|
* @return {ThreadItem}
|
|
|
|
* @throws {Error} Unknown ThreadItem type
|
|
|
|
*/
|
2022-02-19 06:31:34 +00:00
|
|
|
ThreadItem.static.newFromJSON = function ( json ) {
|
2020-10-21 15:52:04 +00:00
|
|
|
// The page can be served from the HTTP cache (Varnish), and the JSON may be generated
|
|
|
|
// by an older version of our PHP code. Code below must be able to handle that.
|
|
|
|
// See ThreadItem::jsonSerialize() in PHP.
|
|
|
|
|
2021-04-08 13:46:09 +00:00
|
|
|
var hash = typeof json === 'string' ? JSON.parse( json ) : json;
|
2020-10-27 12:18:50 +00:00
|
|
|
|
2021-04-08 13:46:09 +00:00
|
|
|
var item;
|
2020-09-16 12:07:27 +00:00
|
|
|
switch ( hash.type ) {
|
|
|
|
case 'comment':
|
|
|
|
// Late require to avoid circular dependency
|
2021-04-08 13:46:09 +00:00
|
|
|
var CommentItem = require( './CommentItem.js' );
|
2020-09-16 12:07:27 +00:00
|
|
|
item = new CommentItem(
|
|
|
|
hash.level,
|
|
|
|
hash.range,
|
|
|
|
hash.signatureRanges,
|
2020-10-21 15:52:04 +00:00
|
|
|
moment( hash.timestamp ),
|
2020-09-16 12:07:27 +00:00
|
|
|
hash.author
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
case 'heading':
|
2021-04-08 13:46:09 +00:00
|
|
|
var HeadingItem = require( './HeadingItem.js' );
|
2020-09-16 12:07:27 +00:00
|
|
|
item = new HeadingItem(
|
|
|
|
hash.range,
|
2020-10-01 19:36:11 +00:00
|
|
|
hash.headingLevel,
|
2020-09-16 12:07:27 +00:00
|
|
|
hash.placeholderHeading
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error( 'Unknown ThreadItem type ' + hash.name );
|
|
|
|
}
|
2021-02-12 19:16:13 +00:00
|
|
|
item.name = hash.name;
|
2020-10-01 19:36:11 +00:00
|
|
|
item.id = hash.id;
|
|
|
|
|
2021-04-08 13:46:09 +00:00
|
|
|
var idEscaped = $.escapeSelector( item.id );
|
2021-05-27 14:29:22 +00:00
|
|
|
var startMarker = document.getElementById( item.id );
|
|
|
|
var endMarker = document.querySelector( '[data-mw-comment-end="' + idEscaped + '"]' );
|
|
|
|
|
2020-10-27 12:18:50 +00:00
|
|
|
item.range = {
|
2021-05-27 19:28:28 +00:00
|
|
|
// Start range after startMarker, because it produces funny results from getBoundingClientRect
|
2021-05-27 14:29:22 +00:00
|
|
|
startContainer: startMarker.parentNode,
|
|
|
|
startOffset: utils.childIndexOf( startMarker ) + 1,
|
2021-05-27 19:28:28 +00:00
|
|
|
// End range inside endMarker, because modifier crashes if endContainer is a <p>/<dd>/<li> node
|
|
|
|
endContainer: endMarker,
|
|
|
|
endOffset: 0
|
2020-10-27 12:18:50 +00:00
|
|
|
};
|
2020-09-16 12:07:27 +00:00
|
|
|
|
|
|
|
return item;
|
|
|
|
};
|
|
|
|
|
2020-07-20 14:13:59 +00:00
|
|
|
/**
|
|
|
|
* Get the list of authors in the comment tree below this thread item.
|
|
|
|
*
|
|
|
|
* Usually called on a HeadingItem to find all authors in a thread.
|
|
|
|
*
|
|
|
|
* @return {string[]} Author usernames
|
|
|
|
*/
|
|
|
|
ThreadItem.prototype.getAuthorsBelow = function () {
|
|
|
|
var authors = {};
|
|
|
|
function getAuthorSet( comment ) {
|
2022-05-31 14:17:50 +00:00
|
|
|
if ( comment.type === 'comment' ) {
|
|
|
|
authors[ comment.author ] = true;
|
|
|
|
}
|
2020-07-20 14:13:59 +00:00
|
|
|
// Get the set of authors in the same format from each reply
|
2022-02-21 00:22:39 +00:00
|
|
|
comment.replies.forEach( getAuthorSet );
|
2020-07-20 14:13:59 +00:00
|
|
|
}
|
|
|
|
|
2022-02-21 00:22:39 +00:00
|
|
|
this.replies.forEach( getAuthorSet );
|
2020-07-20 14:13:59 +00:00
|
|
|
|
|
|
|
return Object.keys( authors ).sort();
|
|
|
|
};
|
|
|
|
|
2022-02-21 00:22:39 +00:00
|
|
|
/**
|
|
|
|
* Get the list of thread items in the comment tree below this thread item.
|
|
|
|
*
|
|
|
|
* @return {ThreadItem[]} Thread items
|
|
|
|
*/
|
|
|
|
ThreadItem.prototype.getThreadItemsBelow = function () {
|
|
|
|
var threadItems = [];
|
|
|
|
function getReplies( comment ) {
|
|
|
|
threadItems.push( comment );
|
|
|
|
comment.replies.forEach( getReplies );
|
|
|
|
}
|
|
|
|
|
|
|
|
this.replies.forEach( getReplies );
|
|
|
|
|
|
|
|
return threadItems;
|
|
|
|
};
|
|
|
|
|
2020-07-22 18:34:08 +00:00
|
|
|
/**
|
|
|
|
* Return a native Range object corresponding to the item's range.
|
|
|
|
*
|
|
|
|
* @return {Range}
|
|
|
|
*/
|
|
|
|
ThreadItem.prototype.getNativeRange = function () {
|
2021-04-08 13:46:09 +00:00
|
|
|
var doc = this.range.startContainer.ownerDocument;
|
|
|
|
var nativeRange = doc.createRange();
|
2020-07-22 18:34:08 +00:00
|
|
|
nativeRange.setStart( this.range.startContainer, this.range.startOffset );
|
2020-08-11 04:22:57 +00:00
|
|
|
nativeRange.setEnd( this.range.endContainer, this.range.endOffset );
|
2020-07-22 18:34:08 +00:00
|
|
|
return nativeRange;
|
|
|
|
};
|
|
|
|
|
2020-07-22 18:25:34 +00:00
|
|
|
// TODO: Implement getHTML/getText if required
|
|
|
|
|
2020-05-22 16:26:05 +00:00
|
|
|
module.exports = ThreadItem;
|