Add reply links at the end of a line, even if the signature is in the middle

Previously they were added at the end of the text node containing the
timestamp, which was usually the end of the line, but not always.

And also fix the same problem for inserting the actual replies (or
reply widgets). This replaces an undocumented hack that prevented our
own reply links from triggering this bug (without it, the reply widget
would be inserted before the reply link rather than after).

While we're here, remove unintentional spacing that appeared before
some reply links, caused by trailing whitespace in text nodes.

Add tests for all of the above.

Bug: T245695
Change-Id: I354b63e2446bb996176a2e3d76abf944127f307e
This commit is contained in:
Bartosz Dziewoński 2020-03-02 19:50:36 +01:00
parent 711d5c4371
commit 606d6b34ec
9 changed files with 3503 additions and 7 deletions

View file

@ -176,10 +176,12 @@
"templates": [
"cases/en-big-oldparser/en-big-oldparser.html",
"cases/en-big-oldparser/en-big-oldparser-modified.html",
"cases/en-big-oldparser/en-big-oldparser-reply.html",
"cases/en-big-parsoid/en-big-parsoid.html",
"cases/en-big-parsoid/en-big-parsoid-modified.html",
"cases/pl-big-oldparser/pl-big-oldparser.html",
"cases/pl-big-oldparser/pl-big-oldparser-modified.html",
"cases/pl-big-oldparser/pl-big-oldparser-reply.html",
"cases/pl-big-parsoid/pl-big-parsoid.html",
"cases/pl-big-parsoid/pl-big-parsoid-modified.html",
"cases/no-heading/no-heading.html",
@ -188,7 +190,10 @@
"cases/split-list/split-list.html",
"cases/split-list/split-list-modified.html",
"cases/split-list2/split-list2.html",
"cases/split-list2/split-list2-modified.html"
"cases/split-list2/split-list2-modified.html",
"cases/signatures-funny/signatures-funny.html",
"cases/signatures-funny/signatures-funny-modified.html",
"cases/signatures-funny/signatures-funny-reply.html"
],
"dependencies": [
"ext.discussionTools.modifier",

View file

@ -13,8 +13,7 @@ var
mw.loader.using( 'ext.discussionTools.ReplyWidgetPlain' );
function setupComment( comment ) {
var $replyLink, widgetPromise, newListItem,
$tsNode = $( comment.range.endContainer );
var $replyLink, widgetPromise, newListItem;
// Is it possible to have a heading nested in a thread?
if ( comment.type !== 'comment' ) {
@ -91,7 +90,7 @@ function setupComment( comment ) {
} );
} );
$tsNode.after( $replyLink );
modifier.addReplyLink( comment, $replyLink[ 0 ] );
}
function traverseNode( parent ) {

View file

@ -37,6 +37,41 @@ function whitespaceParsoidHack( listItem ) {
listItem.setAttribute( 'data-parsoid', '{}' );
}
function isTalkpageListNode( node ) {
var tag = node.tagName ? node.tagName.toLowerCase() : '';
return tag === 'dl' || tag === 'ul';
}
/**
* Given a comment and a reply link, add the reply link to its document's DOM tree, at the end of
* the comment.
*
* @param {Object} comment Comment data returned by parser#groupThreads
* @param {HTMLElement} linkNode Reply link
*/
function addReplyLink( comment, linkNode ) {
var target = comment.range.endContainer;
// Skip to the end of the "paragraph".
// Actually doing this by paragraph would require us to know how the text is laid out, and
// would be more difficult and probably slower. Instead skip over anything that isn't a list
// node, which should have the same effect on discussion pages.
while ( target.nextSibling && !isTalkpageListNode( target.nextSibling ) ) {
target = target.nextSibling;
}
// Insert the link before trailing whitespace.
// In the MediaWiki parser output, <ul>/<dl> nodes are preceded by a newline. Normally it isn't
// visible on the page. But if we insert an inline element (the reply link) after it, it becomes
// meaningful and gets rendered, which results in additional spacing before some reply links.
// Split the text node, so that we can insert the link before the trailing whitespace.
if ( target.nodeType === Node.TEXT_NODE ) {
target.splitText( target.textContent.match( /\s*$/ ).index );
}
target.parentNode.insertBefore( linkNode, target.nextSibling );
}
/**
* Given a comment, add a list item to its document's DOM tree, inside of which a reply to said
* comment can be added.
@ -69,12 +104,16 @@ function addListItem( comment ) {
desiredLevel = comment.level + 1;
currLevel = currComment.level;
target = currComment.range.endContainer;
// HACK
if ( target.nextSibling && target.nextSibling.classList.contains( 'dt-init-replylink' ) ) {
// Skip to the end of the "paragraph".
// Actually doing this by paragraph would require us to know how the text is laid out, and
// would be more difficult and probably slower. Instead skip over anything that isn't a list
// node, which should have the same effect on discussion pages.
while ( target.nextSibling && !isTalkpageListNode( target.nextSibling ) ) {
target = target.nextSibling;
}
// endContainer is probably a text node, and it may also be wrapped in some formatting.
// target is a text node or an inline element at the end of a "paragraph" (not necessarily paragraph node).
// First, we need to find a block-level parent that we can mess with.
// If we can't find a surrounding list item or paragraph (e.g. maybe we're inside a table cell
// or something), take the parent node and hope for the best.
@ -214,6 +253,7 @@ function createWikitextNode( wt ) {
module.exports = {
closest: closest,
addReplyLink: addReplyLink,
addListItem: addListItem,
removeListItem: removeListItem,
addSiblingListItem: addSiblingListItem,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,10 @@
<h2><span class="mw-headline" id="multi_signatures">multi signatures</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Talk:Scratch&amp;action=edit&amp;section=5" title="Edit section: multi signatures">edit source</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>aaa <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC) amended! <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:32, 25 January 2020 (UTC)
</p>
<ul><li>bbb <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC)
<ul><li>ccc <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC) amended! <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:32, 25 January 2020 (UTC)<ul><li data-parsoid="{}">Reply to Matma Rex|2020-01-25T08:31:00.000Z|2</li></ul></li><li data-parsoid="{}">Reply to Matma Rex|2020-01-25T08:31:00.000Z|1</li></ul></li><li data-parsoid="{}">Reply to Matma Rex|2020-01-25T08:31:00.000Z|0</li></ul>
<p>ddd <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC)
</p>
<ul><li>eee <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC) amended! <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:32, 25 January 2020 (UTC)<ul><li data-parsoid="{}">Reply to Matma Rex|2020-01-25T08:31:00.000Z|4</li></ul></li>
<li>fff <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:35, 25 January 2020 (UTC) also!<ul><li data-parsoid="{}">Reply to Matma Rex|2020-01-25T08:35:00.000Z|0</li></ul></li>
<li>fff <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:35, 25 January 2020 (UTC) and <i>also</i>!<ul><li data-parsoid="{}">Reply to Matma Rex|2020-01-25T08:35:00.000Z|1</li></ul></li><li data-parsoid="{}">Reply to Matma Rex|2020-01-25T08:31:00.000Z|3</li></ul>

View file

@ -0,0 +1,10 @@
<h2><span class="mw-headline" id="multi_signatures">multi signatures</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Talk:Scratch&amp;action=edit&amp;section=5" title="Edit section: multi signatures">edit source</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>aaa <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC) amended! <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:32, 25 January 2020 (UTC)<a href="#">Reply</a>
</p>
<ul><li>bbb <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC)<a href="#">Reply</a>
<ul><li>ccc <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC) amended! <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:32, 25 January 2020 (UTC)<a href="#">Reply</a></li></ul></li></ul>
<p>ddd <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC)<a href="#">Reply</a>
</p>
<ul><li>eee <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC) amended! <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:32, 25 January 2020 (UTC)<a href="#">Reply</a></li>
<li>fff <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:35, 25 January 2020 (UTC) also!<a href="#">Reply</a></li>
<li>fff <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:35, 25 January 2020 (UTC) and <i>also</i>!<a href="#">Reply</a></li></ul>

View file

@ -0,0 +1,10 @@
<h2><span class="mw-headline" id="multi_signatures">multi signatures</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Talk:Scratch&amp;action=edit&amp;section=5" title="Edit section: multi signatures">edit source</a><span class="mw-editsection-bracket">]</span></span></h2>
<p>aaa <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC) amended! <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:32, 25 January 2020 (UTC)
</p>
<ul><li>bbb <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC)
<ul><li>ccc <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC) amended! <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:32, 25 January 2020 (UTC)</li></ul></li></ul>
<p>ddd <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC)
</p>
<ul><li>eee <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:31, 25 January 2020 (UTC) amended! <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:32, 25 January 2020 (UTC)</li>
<li>fff <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:35, 25 January 2020 (UTC) also!</li>
<li>fff <b><a href="/wiki/User:Matma_Rex" title="User:Matma Rex">Matma Rex</a> | <a href="/wiki/User_talk:Matma_Rex" title="User talk:Matma Rex">talk</a></b> 08:35, 25 January 2020 (UTC) and <i>also</i>!</li></ul>

View file

@ -52,6 +52,13 @@ QUnit.test( '#addListItem/#removeListItem', function ( assert ) {
expected: mw.template.get( 'test.DiscussionTools', 'cases/split-list2/split-list2-modified.html' ).render(),
config: require( './data/enwiki-config.json' ),
data: require( './data/enwiki-data.json' )
},
{
name: 'Signatures in funny places',
dom: mw.template.get( 'test.DiscussionTools', 'cases/signatures-funny/signatures-funny.html' ).render(),
expected: mw.template.get( 'test.DiscussionTools', 'cases/signatures-funny/signatures-funny-modified.html' ).render(),
config: require( './data/enwiki-config.json' ),
data: require( './data/enwiki-data.json' )
}
];
@ -107,3 +114,68 @@ QUnit.test( '#addListItem/#removeListItem', function ( assert ) {
);
}
} );
QUnit.test( '#addReplyLink', function ( assert ) {
var i, j, cases, actualHtml, expectedHtml, comments, linkNode, fixture;
cases = [
{
name: 'plwiki oldparser',
dom: mw.template.get( 'test.DiscussionTools', 'cases/pl-big-oldparser/pl-big-oldparser.html' ).render(),
expected: mw.template.get( 'test.DiscussionTools', 'cases/pl-big-oldparser/pl-big-oldparser-reply.html' ).render(),
config: require( './data/plwiki-config.json' ),
data: require( './data/plwiki-data.json' )
},
{
name: 'enwiki oldparser',
dom: mw.template.get( 'test.DiscussionTools', 'cases/en-big-oldparser/en-big-oldparser.html' ).render(),
expected: mw.template.get( 'test.DiscussionTools', 'cases/en-big-oldparser/en-big-oldparser-reply.html' ).render(),
config: require( './data/enwiki-config.json' ),
data: require( './data/enwiki-data.json' )
},
{
name: 'Signatures in funny places',
dom: mw.template.get( 'test.DiscussionTools', 'cases/signatures-funny/signatures-funny.html' ).render(),
expected: mw.template.get( 'test.DiscussionTools', 'cases/signatures-funny/signatures-funny-reply.html' ).render(),
config: require( './data/enwiki-config.json' ),
data: require( './data/enwiki-data.json' )
}
];
fixture = document.getElementById( 'qunit-fixture' );
for ( i = 0; i < cases.length; i++ ) {
utils.overrideMwConfig( cases[ i ].config );
utils.overrideParserData( cases[ i ].data );
$( fixture ).empty().append( cases[ i ].expected );
expectedHtml = fixture.innerHTML;
$( fixture ).empty().append( cases[ i ].dom.clone() );
comments = parser.getComments( fixture );
parser.groupThreads( comments );
// Add a reply link to every comment.
for ( j = 0; j < comments.length; j++ ) {
if ( comments[ j ].type === 'heading' ) {
continue;
}
linkNode = document.createElement( 'a' );
linkNode.textContent = 'Reply';
linkNode.href = '#';
modifier.addReplyLink( comments[ j ], linkNode );
}
// Uncomment this to get updated content for the the "reply HTML" files, for copy/paste:
// console.log( fixture.innerHTML );
actualHtml = fixture.innerHTML.trim();
assert.strictEqual(
actualHtml,
expectedHtml,
cases[ i ].name
);
}
} );