Merge "Enhance vector-2022 table of contents"

This commit is contained in:
jenkins-bot 2022-08-26 20:57:39 +00:00 committed by Gerrit Code Review
commit b9e3043415
4 changed files with 82 additions and 14 deletions

View file

@ -11,6 +11,7 @@ use MediaWiki\MediaWikiServices;
use MediaWiki\User\UserIdentity;
use MWExceptionHandler;
use MWTimestamp;
use Parser;
use ParserOutput;
use Sanitizer;
use Throwable;
@ -46,14 +47,50 @@ class CommentFormatter {
*
* @param string &$text Parser text output (modified by reference)
* @param ParserOutput $pout ParserOutput object for metadata, e.g. parser limit report
* @param Title $title
* @param Parser $parser
*/
public static function addDiscussionTools( string &$text, ParserOutput $pout, Title $title ): void {
public static function addDiscussionTools( string &$text, ParserOutput $pout, Parser $parser ): void {
$title = $parser->getTitle();
$start = microtime( true );
$requestId = null;
try {
$text = static::addDiscussionToolsInternal( $text, $title );
[ 'html' => $text, 'tocInfo' => $tocInfo ] =
static::addDiscussionToolsInternal( $text, $title );
// Enhance the table of contents in supporting skins (vector-2022)
// Only do the work if the HTML would be shown. It looks like we can only check this
// by checking whether the HTML for the normal TOC has been generated. Code in
// OutputPage::addParserOutputMetadata does the same.
if ( $pout->getTOCHTML() ) {
// If the TOC HTML has been generated, then the parser cache is already split by user
// language (because of the "Contents" header in the TOC), so we can render text in user
// language as well. If that behavior in core changes, then we'll have to change this to
// happen in a post-processing step (like all other transformations) to avoid splitting it.
$lang = $parser->getOptions()->getUserLangObj();
$sections = $pout->getSections();
foreach ( $sections as &$item ) {
$key = str_replace( '_', ' ', $item['anchor'] );
// Unset if we did not format this section as a topic container
if ( isset( $tocInfo[$key] ) ) {
$count = $lang->formatNum( $tocInfo[$key]['commentCount'] );
$commentCount = wfMessage(
'discussiontools-topicheader-commentcount',
$count
)->inLanguage( $lang )->text();
$summary = Html::element( 'span', [
'class' => 'ext-discussiontools-init-sidebar-meta'
], wfMessage( 'parentheses', $commentCount )->inLanguage( $lang )->text() );
// This also shows up in API action=parse&prop=sections output.
$item['html-summary'] = $summary;
}
}
$pout->setSections( $sections );
}
} catch ( Throwable $e ) {
// Catch errors, so that they don't cause the entire page to not display.
// Log it and report the request ID to make it easier to find in the logs.
@ -85,8 +122,13 @@ class CommentFormatter {
*
* @param Element $headingElement Heading element
* @param ContentHeadingItem|null $headingItem Heading item
* @param array|null &$tocInfo TOC info
*/
protected static function addTopicContainer( Element $headingElement, ?ContentHeadingItem $headingItem = null ) {
protected static function addTopicContainer(
Element $headingElement,
?ContentHeadingItem $headingItem = null,
&$tocInfo = null
) {
$doc = $headingElement->ownerDocument;
DOMCompat::getClassList( $headingElement )->add( 'ext-discussiontools-init-section' );
@ -158,6 +200,8 @@ class CommentFormatter {
$headingElement->appendChild( $ellipsisButton );
$headingElement->appendChild( $bar );
}
$tocInfo[ $headingItem->getLinkableTitle() ] = $summary;
}
/**
@ -165,9 +209,9 @@ class CommentFormatter {
*
* @param string $html HTML
* @param Title $title
* @return string HTML with discussion tools
* @return array HTML with discussion tools and TOC info
*/
protected static function addDiscussionToolsInternal( string $html, Title $title ): string {
protected static function addDiscussionToolsInternal( string $html, Title $title ): array {
// The output of this method can end up in the HTTP cache (Varnish). Avoid changing it;
// and when doing so, ensure that frontend code can handle both the old and new outputs.
// See controller#init in JS.
@ -178,6 +222,8 @@ class CommentFormatter {
$threadItemSet = static::getParser()->parse( $container, $title->getTitleValue() );
$threadItems = $threadItemSet->getThreadItems();
$tocInfo = [];
$newestComment = null;
$newestCommentJSON = null;
@ -231,7 +277,7 @@ class CommentFormatter {
$headingElement = CommentUtils::closestElement( $headline, [ 'h2' ] );
if ( $headingElement ) {
static::addTopicContainer( $headingElement, $threadItem );
static::addTopicContainer( $headingElement, $threadItem, $tocInfo );
}
}
} elseif ( $threadItem instanceof ContentCommentItem ) {
@ -269,7 +315,9 @@ class CommentFormatter {
// Like DOMCompat::getInnerHTML(), but disable 'smartQuote' for compatibility with
// ParserOutput::EDITSECTION_REGEX matching 'mw:editsection' tags (T274709)
return XMLSerializer::serialize( $container, [ 'innerXML' => true, 'smartQuote' => false ] )['html'];
$html = XMLSerializer::serialize( $container, [ 'innerXML' => true, 'smartQuote' => false ] )['html'];
return [ 'html' => $html, 'tocInfo' => $tocInfo ];
}
/**

View file

@ -39,6 +39,7 @@ class ParserHooks implements ParserAfterTidyHook {
}
$title = $parser->getTitle();
$pout = $parser->getOutput();
// This condition must be unreliant on current enablement config or user preference.
// In other words, include parser output of talk pages with DT disabled.
@ -46,13 +47,13 @@ class ParserHooks implements ParserAfterTidyHook {
// This is similar to HookUtils::isAvailableForTitle, but instead of querying the
// database for the latest metadata of a page that exists, we check metadata of
// the given ParserOutput object only (this runs before the edit is saved).
if ( $title->isTalkPage() || $parser->getOutput()->getNewSection() ) {
if ( $title->isTalkPage() || $pout->getNewSection() ) {
$dtConfig = $this->configFactory->makeConfig( 'discussiontools' );
$talkExpiry = $dtConfig->get( 'DiscussionToolsTalkPageParserCacheExpiry' );
// Override parser cache expiry of talk pages (T280605).
// Note, this can only shorten it. MediaWiki ignores values higher than the default.
if ( $talkExpiry > 0 ) {
$parser->getOutput()->updateCacheExpiry( $talkExpiry );
$pout->updateCacheExpiry( $talkExpiry );
}
}
@ -63,13 +64,13 @@ class ParserHooks implements ParserAfterTidyHook {
// the user doesn't have DiscussionTools features enabled.
if ( HookUtils::isAvailableForTitle( $title ) ) {
// This modifies $text
CommentFormatter::addDiscussionTools( $text, $parser->getOutput(), $parser->getTitle() );
CommentFormatter::addDiscussionTools( $text, $pout, $parser );
if ( $parser->getOptions()->getIsPreview() ) {
$text = CommentFormatter::removeInteractiveTools( $text );
}
$parser->getOutput()->addModuleStyles( [
$pout->addModuleStyles( [
'ext.discussionTools.init.styles',
] );
}

View file

@ -275,7 +275,8 @@ h1, h2, h3, h4, h5, h6 {
.ext-discussiontools-init-section-bar,
.ext-discussiontools-init-replybutton.oo-ui-buttonElement,
.ext-discussiontools-init-readAsWikiPage,
.ext-discussiontools-init-pageframe-latestcomment {
.ext-discussiontools-init-pageframe-latestcomment,
.ext-discussiontools-init-sidebar-meta {
display: none;
}
@ -323,6 +324,24 @@ h1, h2, h3, h4, h5, h6 {
.ext-discussiontools-init-pageframe-latestcomment {
display: block;
}
// stylelint-disable-next-line selector-class-pattern
.sidebar-toc .sidebar-toc-text {
display: inline;
}
// stylelint-disable-next-line selector-class-pattern
.sidebar-toc .sidebar-toc-list-item {
padding-top: 4px;
padding-bottom: 4px;
}
.ext-discussiontools-init-sidebar-meta {
display: inline;
color: #666;
white-space: nowrap;
font-size: 0.875em;
}
}
// Main feature (topic containers)

View file

@ -28,7 +28,7 @@ class CommentFormatterTest extends IntegrationTestCase {
$commentFormatter = TestingAccessWrapper::newFromClass( MockCommentFormatter::class );
$preprocessed = $commentFormatter->addDiscussionToolsInternal( $dom, $title );
[ 'html' => $preprocessed ] = $commentFormatter->addDiscussionToolsInternal( $dom, $title );
$mockSubStore = new MockSubscriptionStore();
$qqxLang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'qqx' );