diff --git a/RELEASE-NOTES-1.42 b/RELEASE-NOTES-1.42
index b897159578c..1741c7a88b7 100644
--- a/RELEASE-NOTES-1.42
+++ b/RELEASE-NOTES-1.42
@@ -616,6 +616,8 @@ because of Phabricator reports.
Vuex 4 will remain accessible for the foreseeable future. Pinia should be used
for new projects.
* Linker::makeHeadline() has been deprecated.
+* Linker::generateTOC(), Linker::tocIndent(), Linker::tocUnindent(),
+ Linker::tocLine(), Linker::tocLineEnd(), Linker::tocList() are deprecated.
* Title::getBrokenLinksFrom() has been deprecated.
* ReplicatedBagOStuff has been deprecated since 1.42.
* The third argument to ContentRenderer::getParserOutput() now accepts a
diff --git a/includes/OutputTransform/Stages/HandleTOCMarkers.php b/includes/OutputTransform/Stages/HandleTOCMarkers.php
index d7c4efc6a10..20c007e6b6d 100644
--- a/includes/OutputTransform/Stages/HandleTOCMarkers.php
+++ b/includes/OutputTransform/Stages/HandleTOCMarkers.php
@@ -4,13 +4,16 @@ namespace MediaWiki\OutputTransform\Stages;
use Language;
use MediaWiki\Context\RequestContext;
-use MediaWiki\Linker\Linker;
+use MediaWiki\Html\Html;
+use MediaWiki\MainConfigNames;
+use MediaWiki\MediaWikiServices;
use MediaWiki\OutputTransform\ContentTextTransformStage;
use MediaWiki\Parser\Parser;
use MediaWiki\Parser\ParserOutput;
use MediaWiki\Parser\Sanitizer;
use MediaWiki\Tidy\TidyDriverBase;
use ParserOptions;
+use Wikimedia\Parsoid\Core\TOCData;
/**
* Inject table of contents (or empty string if there's no sections)
@@ -45,9 +48,9 @@ class HandleTOCMarkers extends ContentTextTransformStage {
if ( $numSections === 0 ) {
$toc = '';
} else {
- $toc = Linker::generateTOC( $tocData, $lang );
- $toc =
- $this->tidy->tidy( $toc, [ Sanitizer::class, 'armorFrenchSpaces' ] );
+ $toc = self::generateTOC( $tocData, $lang );
+ // TODO: This may no longer be needed since Ic0a805f29c928d0c2edf266ea045b0d29bb45a28
+ $toc = $this->tidy->tidy( $toc, [ Sanitizer::class, 'armorFrenchSpaces' ] );
}
return Parser::replaceTableOfContentsMarker( $text, $toc );
@@ -72,4 +75,147 @@ class HandleTOCMarkers extends ContentTextTransformStage {
}
return $userLang;
}
+
+ /**
+ * Add another level to the Table of Contents
+ *
+ * @return string
+ */
+ private static function tocIndent() {
+ return "\n
\n";
+ }
+
+ /**
+ * Finish one or more sublevels on the Table of Contents
+ *
+ * @param int $level
+ * @return string
+ */
+ private static function tocUnindent( $level ) {
+ return "\n" . str_repeat( "
\n\n", $level > 0 ? $level : 0 );
+ }
+
+ /**
+ * parameter level defines if we are on an indentation level
+ *
+ * @param string $linkAnchor Identifier
+ * @param string $tocline Properly escaped HTML
+ * @param string $tocnumber Unescaped text
+ * @param int $level
+ * @param string|false $sectionIndex
+ * @return string
+ */
+ private static function tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
+ $classes = "toclevel-$level";
+
+ // Parser.php used to suppress tocLine by setting $sectionindex to false.
+ // In those circumstances, we can now encounter '' or a "T-" prefixed index
+ // for when the section comes from templates.
+ if ( $sectionIndex !== false && $sectionIndex !== '' && !str_starts_with( $sectionIndex, "T-" ) ) {
+ $classes .= " tocsection-$sectionIndex";
+ }
+
+ //
+ // $tocnumber $tocline
+ return Html::openElement( 'li', [ 'class' => $classes ] )
+ . Html::rawElement( 'a',
+ [ 'href' => "#$linkAnchor" ],
+ Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
+ . ' '
+ . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
+ );
+ }
+
+ /**
+ * End a Table Of Contents line.
+ * tocUnindent() will be used instead if we're ending a line below
+ * the new level.
+ * @return string
+ */
+ private static function tocLineEnd() {
+ return "\n";
+ }
+
+ /**
+ * Wraps the TOC in a div with ARIA navigation role and provides the hide/collapse JavaScript.
+ *
+ * @param string $toc Html of the Table Of Contents
+ * @param Language|null $lang Language for the toc title, defaults to user language
+ * @return string Full html of the TOC
+ */
+ private static function tocList( $toc, Language $lang = null ) {
+ $lang ??= RequestContext::getMain()->getLanguage();
+
+ $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
+
+ return ''
+ . Html::element( 'input', [
+ 'type' => 'checkbox',
+ 'role' => 'button',
+ 'id' => 'toctogglecheckbox',
+ 'class' => 'toctogglecheckbox',
+ 'style' => 'display:none',
+ ] )
+ . Html::openElement( 'div', [
+ 'class' => 'toctitle',
+ 'lang' => $lang->getHtmlCode(),
+ 'dir' => $lang->getDir(),
+ ] )
+ . '
' . $title . '
'
+ . ''
+ . Html::label( '', 'toctogglecheckbox', [
+ 'class' => 'toctogglelabel',
+ ] )
+ . ''
+ . ''
+ . $toc
+ . "\n\n";
+ }
+
+ /**
+ * Generate a table of contents from a section tree.
+ *
+ * @param ?TOCData $tocData Return value of ParserOutput::getSections()
+ * @param Language|null $lang Language for the toc title, defaults to user language
+ * @param array $options
+ * - 'maxtoclevel' Max TOC level to generate
+ * @return string HTML fragment
+ */
+ private static function generateTOC( ?TOCData $tocData, Language $lang = null, array $options = [] ): string {
+ $toc = '';
+ $lastLevel = 0;
+ $maxTocLevel = $options['maxtoclevel'] ?? null;
+ if ( $maxTocLevel === null ) {
+ // Use wiki-configured default
+ $services = MediaWikiServices::getInstance();
+ $config = $services->getMainConfig();
+ $maxTocLevel = $config->get( MainConfigNames::MaxTocLevel );
+ }
+ foreach ( ( $tocData ? $tocData->getSections() : [] ) as $section ) {
+ $tocLevel = $section->tocLevel;
+ if ( $tocLevel < $maxTocLevel ) {
+ if ( $tocLevel > $lastLevel ) {
+ $toc .= self::tocIndent();
+ } elseif ( $tocLevel < $lastLevel ) {
+ if ( $lastLevel < $maxTocLevel ) {
+ $toc .= self::tocUnindent(
+ $lastLevel - $tocLevel );
+ } else {
+ $toc .= self::tocLineEnd();
+ }
+ } else {
+ $toc .= self::tocLineEnd();
+ }
+
+ $toc .= self::tocLine( $section->linkAnchor,
+ $section->line, $section->number,
+ $tocLevel, $section->index );
+ $lastLevel = $tocLevel;
+ }
+ }
+ if ( $lastLevel < $maxTocLevel && $lastLevel > 0 ) {
+ $toc .= self::tocUnindent( $lastLevel - 1 );
+ }
+ return self::tocList( $toc, $lang );
+ }
}
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index 27b4f0b833b..7ac078cd715 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -2194,7 +2194,7 @@ abstract class ApiBase extends ContextSource {
* @param string[] &$help Array of help data
* @param array $options Options passed to ApiHelp::getHelp
* @param array &$tocData If a TOC is being generated, this array has keys
- * as anchors in the page and values as for Linker::generateTOC().
+ * as anchors in the page and values as for SectionMetadata::fromLegacy().
*/
public function modifyHelp( array &$help, array $options, array &$tocData ) {
}
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
index 289ddc1ff8f..cb6aa1adcbc 100644
--- a/includes/api/ApiHelp.php
+++ b/includes/api/ApiHelp.php
@@ -24,10 +24,11 @@ use MediaWiki\Context\DerivativeContext;
use MediaWiki\Context\IContextSource;
use MediaWiki\Html\Html;
use MediaWiki\Html\HtmlHelper;
-use MediaWiki\Linker\Linker;
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use MediaWiki\Output\OutputPage;
+use MediaWiki\Parser\ParserOutput;
+use MediaWiki\Parser\ParserOutputFlags;
use MediaWiki\SpecialPage\SpecialPage;
use MediaWiki\Specials\SpecialVersion;
use MediaWiki\Title\Title;
@@ -190,8 +191,11 @@ class ApiHelp extends ApiBase {
$haveModules = [];
$html = self::getHelpInternal( $context, $modules, $options, $haveModules );
if ( !empty( $options['toc'] ) && $haveModules ) {
- $tocData = TOCData::fromLegacy( array_values( $haveModules ) );
- $out->addHTML( Linker::generateTOC( $tocData, $context->getLanguage() ) );
+ $pout = new ParserOutput;
+ $pout->setTOCData( TOCData::fromLegacy( array_values( $haveModules ) ) );
+ $pout->setOutputFlag( ParserOutputFlags::SHOW_TOC );
+ $pout->setText( Parser::TOC_PLACEHOLDER );
+ $out->addParserOutput( $pout );
}
$out->addHTML( $html );
diff --git a/includes/api/Hook/APIHelpModifyOutputHook.php b/includes/api/Hook/APIHelpModifyOutputHook.php
index c46d9eadee0..2559e5afb7c 100644
--- a/includes/api/Hook/APIHelpModifyOutputHook.php
+++ b/includes/api/Hook/APIHelpModifyOutputHook.php
@@ -21,7 +21,7 @@ interface APIHelpModifyOutputHook {
* @param string[] &$help Array of HTML strings to be joined for the output
* @param array $options Array of formatting options passed to ApiHelp::getHelp
* @param array &$tocData If a TOC is being generated, this array has keys as anchors in
- * the page and values as for Linker::generateTOC().
+ * the page and values as for SectionMetadata::fromLegacy().
* @return bool|void True or no return value to continue or false to abort
*/
public function onAPIHelpModifyOutput( $module, &$help, $options, &$tocData );
diff --git a/includes/linker/Linker.php b/includes/linker/Linker.php
index 2b35ebc0330..4b561f646af 100644
--- a/includes/linker/Linker.php
+++ b/includes/linker/Linker.php
@@ -1777,27 +1777,32 @@ class Linker {
/**
* Add another level to the Table of Contents
*
+ * @deprecated since 1.42
* @since 1.16.3
* @return string
*/
public static function tocIndent() {
+ wfDeprecated( __METHOD__, '1.42' );
return "\n\n";
}
/**
* Finish one or more sublevels on the Table of Contents
*
+ * @deprecated since 1.42
* @since 1.16.3
* @param int $level
* @return string
*/
public static function tocUnindent( $level ) {
+ wfDeprecated( __METHOD__, '1.42' );
return "\n" . str_repeat( "
\n\n", $level > 0 ? $level : 0 );
}
/**
* parameter level defines if we are on an indentation level
*
+ * @deprecated since 1.42
* @since 1.16.3
* @param string $linkAnchor Identifier
* @param string $tocline Properly escaped HTML
@@ -1807,6 +1812,7 @@ class Linker {
* @return string
*/
public static function tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
+ wfDeprecated( __METHOD__, '1.42' );
$classes = "toclevel-$level";
// Parser.php used to suppress tocLine by setting $sectionindex to false.
@@ -1831,22 +1837,26 @@ class Linker {
* End a Table Of Contents line.
* tocUnindent() will be used instead if we're ending a line below
* the new level.
+ * @deprecated since 1.42
* @since 1.16.3
* @return string
*/
public static function tocLineEnd() {
+ wfDeprecated( __METHOD__, '1.42' );
return "\n";
}
/**
* Wraps the TOC in a div with ARIA navigation role and provides the hide/collapse JavaScript.
*
+ * @deprecated since 1.42
* @since 1.16.3
* @param string $toc Html of the Table Of Contents
* @param Language|null $lang Language for the toc title, defaults to user language
* @return string Full html of the TOC
*/
public static function tocList( $toc, Language $lang = null ) {
+ wfDeprecated( __METHOD__, '1.42' );
$lang ??= RequestContext::getMain()->getLanguage();
$title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
@@ -1879,6 +1889,7 @@ class Linker {
* @internal For use by ParserOutput and API modules
* Generate a table of contents from a section tree.
*
+ * @deprecated since 1.42
* @since 1.16.3. $lang added in 1.17. Parameters changed in 1.40.
* @param ?TOCData $tocData Return value of ParserOutput::getSections()
* @param Language|null $lang Language for the toc title, defaults to user language