Page MenuHomePhabricator

Refactor table of contents
Closed, ResolvedPublic

Description

We need a better Table of Contents. (T114051) We need to determine who the people holding the stakes are and we need to find a path forward.

Specific pain points with the current table of contents implementation

  • Caching problems due to partial i18n (it's treated as part of the content, but localised)
  • Improving it is unnecessarily difficult (source is scattered across several different classes), and patches improving it are also quickly obsoleted by unrelated changes due to how spread out it is
  • Extensions cannot easily reuse it (technically it is possible to replicate the several different calls required to generate its html, but it's a mess and results in duplicate ids and stuff - Extension:DeToc, Skin:BlueSky, and others all do this)
  • Extensions cannot hook into it to modify it or reuse it for different content models

Extension use-cases modifying/adding to the ToC:

  • Parser extensions dealing with particularly long, sectionable content. A discrete example is the ScreenPlay extension, which introduces a parser to render screenplay formatting. This includes scene headings, which would ideally appear in the table of contents as, well, the contents of the page (part of T114033). As is, however, even ordinary == == sections within the screenplay tags do not appear within the table of contents because it is being parsed separately from this part of normal wikitext. Currently there is no way to hook back into the table of contents and fix this for either case.
  • Formatted discussion extensions, which implement sections for different threads. LQT got around this by just making its own. Flow gets around this by not having a ToC at all (unless it also just implements its own now; I haven't checked recently). While they can get away with this due to replacing all the usual content on the page (and thus avoiding ever having two ToCs), this is not ideal. They should be able to hook into the core table of contents and reuse that, not reinvent the wheel in a different way every time.
  • Anything else that wants a ToC of unusual content. Books (T33075, T78329), Special pages (T15862, T113459), preferences, whatever.

If special pages and whatever could also register sections with a standard ToC object and place that on the page, this would standardise output for all of them, regardless of what generated the content.

Reusing the ToC:

  • Skins such as BlueSky, and Naiad which incorporate the table of contents into their interfaces. This is currently possible, but incredibly hacky, and it does not persist even on edit. There are also issues (when using more than one) with duplicated id's because all the HTML is pretty much hardcoded into the output.

Other uses:

  • Multiple tables of contents on a page. For when you're insane or dealing with insanely long pages. Or have multiple pages on a page or something.
  • Getting a ToC for a page by title. Possibly useful for semantic stuff. Or generating contents for see also links. Or for the skins mentioned above. Or for the API. Having a single object to interact with should vastly simplify the process whether it exists already or not.

Event Timeline

Isarra raised the priority of this task from to Needs Triage.
Isarra updated the task description. (Show Details)
Isarra subscribed.
Isarra renamed this task from Figure out what the hell to do with the table of contents to Figure out what to do with the table of contents.Sep 28 2015, 11:24 PM
Isarra updated the task description. (Show Details)
Isarra set Security to None.
Isarra added a subscriber: Performance Issue.

Other things:

  • Current status of the discussion: For the most part I get the feeling it's not really discussed. People just sort of avoid it. Because it's scary. It is scary. And generally for an RFC you have something to actually propose to begin with that's not just 'do something'.
  • Background information: There isn't a whole lot of documentation. We've got https://www.mediawiki.org/wiki/Manual:Table_of_contents, but beyond that your best bet is to just go following the component references around in core. It's a hell of a ride.

It is also currently causing issues with caching due to how it handles i18n.

To expand on this, the current system forces the parser cache to be split by user language whenever there is a ToC. If you exlcude commons and their {{int: hacks, this feature probably represents the largest cause of parser cache splitting in MediaWiki. And it seems rather unnecessary. I suspect the Performance-Team team would be interested in eliminating this cause of parser cache splitting.

To get the ball rolling, I think we should do something similar to how edit sections are handled

That is

  • There's a marker in wikitext for where the ToC should be (but the actual TOC html is not there, unlike current <mw:toc>... )
  • During ParserOutput::getText, the marker is replaced, with the results of calling a $wgOut->getSkin()->makeTOC( $this->getSections(), $wgLang ).
    • The base implementation of makeToc() would be roughly equivalent to what the toc related methods in the Linker do.
  • The getSections() of ParserOutput is also available to the skin generally ( Probably via OutputPage???), in case they want to put the TOC somewhere else, and not in the body of the article
  • Parser should have some method for adding things to the TOC, in case parser functions want to do something weird.

thoughts?

I suppose it would help if we knew all the use cases we'd want to support, too. Skins often duplicate or replace to in-page ToC. Parser functions... would they ever want to do anything besides add sections to it?

how is this the same or different as T114072, sections ?

The table of contents is generated based on the sections, but it is a discreet thing independent (or should be) of what those sections actually are.

I suppose it would help if we knew all the use cases we'd want to support, too. Skins often duplicate or replace to in-page ToC. Parser functions... would they ever want to do anything besides add sections to it?

Someone might want a parser function that could modify the ToC in someway, e.g. Use a different numbering scheme for headers, only show top level headers. Cause ToC to move to a different location. In theory anyways.

How often do parser functions actually want to add things to the ToC? I don't think I can really think of any that do that.

Congratulations! This is one of the 52 proposals that made it through the first deadline of the Wikimedia-Developer-Summit-2016 selection process. Please pay attention to the next one: > By 6 Nov 2015, all Summit proposals must have active discussions and a Summit plan documented in the description. Proposals not reaching this critical mass can continue at their own path out of the Summit.

This proposal has the basic details and discussion to be considered On Track. However, it looks like the participation of more people (additional maintainers? related WMF teams?) is needed to assure that there is a critical mass of interest for the Summit.

How do you get people interested in core architectural problems that are large to actually resolve, but that usually don't directly impact them? The table of contents has caused issues for skin creators, parser problems, issues for extensions trying to hook into it. We try to add features to MediaWiki, but the moment they touch this thing stuck in the past, they just need to go around it.

Who do we need to actually sort out what would be needed of a discreet TOC object? Who can implement such a thing?

I looked at this task and T114051 and while I can somewhat see the problem statement here, the specific use-cases are still blurry for me.

Are there specific pain points with the current table of contents implementation? Are these pain points documented in Phabricator Maniphest?

Related to these questions and what I'm more wondering about is: if a table of contents box is merely meant to serve as an index of the current page's headers, what in the current HTML output is missing and what needs to be changed? A lot of the potential/theoretical use-cases seem to extend the table of contents beyond what a typical table of contents would be. That seems like a potential sticky wicket.

Someone might want a parser function that could modify the ToC in someway, e.g. Use a different numbering scheme for headers, only show top level headers. Cause ToC to move to a different location. In theory anyways.

I don't like theoretical use-cases here, we should focus on the concrete. Regarding the positioning or numbering, that should be (and is, I believe) handled at the styling layer. That is, you should be able to change the table of contents box positioning, background color, and numbering via CSS directives.

How often do parser functions actually want to add things to the ToC? I don't think I can really think of any that do that.

Right... so why would we do all this work again? It seems like I'm not the only person struggling for use-cases here.

Somewhat tangential to T114057#1795082, MediaWiki's api.php should be able to output an ordered list of a page's section headings and links to those sections. This would de-couple (or separate) the content from its presentation. I don't know if there's an api.php module currently for this "get a page's section headings and links" functionality, but if not, a concrete and very beneficial action item would be to create such an API module, in my opinion.

Are there specific pain points with the current table of contents implementation?

  • Caching problems (it's treated as part of the content, but localised)
  • Improving it is unnecessarily difficult (source is scattered across several different classes), and patches improving it are quickly obsoleted by unrelated changes
  • Extensions cannot reuse it (in good conscience; technically it is possible to replicate the several different calls required to generate its html, but it's a mess and results in duplicate ids and stuff)
  • Extensions cannot hook into it to modify it

It seems like I'm not the only person struggling for use-cases here.

Extension use cases modifying/adding to the ToC:

  • Parser extensions dealing with particularly long, sectionable content. A discrete example is the ScreenPlay extension, which introduces a parser to render screenplay formatting. This includes scene headings, which would ideally appear in the table of contents as, well, the contents of the page (part of T114033). As is, however, even ordinary == == sections within the screenplay tags do not appear within the table of contents because it is being parsed separately from this part of normal wikitext. Currently there is no way to hook back into the table of contents and fix this for either case.
  • Formatted discussion extensions, which implement sections for different threads. LQT got around this by just making its own. Flow gets around this by not having a ToC at all (unless it also just implements its own now; I haven't checked recently). While they can get away with this due to replacing all the usual content on the page (and thus avoiding ever having two ToCs), this is not ideal. They should be able to hook into the core table of contents and reuse that, not reinvent the wheel in a different way every time.
  • Anything else that wants a ToC of unusual content. Books (T33075, T78329), Special pages (T15862), preferences, whatever.

If special pages and whatever could also register sections with a standard ToC object and place that on the page, this would standardise output for all of them, regardless of what generated the content.

Reusing the ToC:

  • Skins like BlueSky and Refreshed, which incorporate the table of contents into their interface. This is currently possible, but incredibly hacky, and it does not persist even on edit. There are also issues (when using more than one) with duplicated ids because all the html is pretty much hardcoded into the output.

Other uses:

  • Multiple tables of contents on a page. For when you're insane or dealing with insanely long pages. Or have multiple pages on a page. Or something.
  • Getting a ToC for a page by title. Possibly useful for semantic stuff. Or generating contents for see also links. Or for the skins mentioned above. Or for the API. Having a single object to interact with should vastly simplify the process whether it exists already or not.

Skins often duplicate or replace to in-page ToC. Parser functions... would they ever want to do anything besides add sections to it?

Yes - deleting sections nested too deeply, add/replace/delete/alter numbering schemes beyond CSS and browsers capabilities, truncating long section titles, render them with a different HTML markup type, etc.

If special pages and whatever could also register sections with a standard ToC object and place that on the page, this would standardise output for all of them, regardless of what generated the content.

Very good point!

Parsoid doesn't generate ToC, perhaps because of the parser-cache-splitting issue. Can someone explain to me how user (language?) preferences affect the ToC?

In any case, I think the parser team generally supports refactoring the ToC out of the parser.

I think it's only the "Contents" heading? I thought that the section numbers also get localized, but apparently they use content language, which I think is a bug.

I went ahead and shoved the giant comment into the description. Please add to or modify it.

I think it's only the "Contents" heading? I thought that the section numbers also get localized, but apparently they use content language, which I think is a bug.

Call it a bug or not. It depends:

  • Bug, if the TOC is to be part of the page content. The it must be localized in the page content language.
  • Not a bug, if you see it as part of the user interface. Then it has be localized in the current interface language.

The cache split issue is already a hint that there must be a bug in the program design somewhere, imho.

My proposed solution:

  • Make the TOC an object of its own.
  • Give it a function to be localized, passing a language object.
  • Leave it to skins where / if to output the TOC (Position may govern the language).
  • Maybe let the parser "suggest" a default position somehow.
  • By the way get rid of the "more than 3 sections" limitation.
  • Create an API function to retrieve the raw TOC data of a page.

Wikimedia Developer Summit 2016 ended two weeks ago. This task is still open. If the session in this task took place, please make sure 1) that the session Etherpad notes are linked from this task, 2) that followup tasks for any actions identified have been created and linked from this task, 3) to change the status of this task to "resolved". If this session did not take place, change the task status to "declined". If this task itself has become a well-defined action which is not finished yet, drag and drop this task into the "Work continues after Summit" column on the project workboard. Thank you for your help!

No formal session took place, but considering most of the meaningful discussion has taken place on this task, I'd rather just remove it from the summit project and merge the general task (T114051) in.

Isarra renamed this task from Figure out what to do with the table of contents to Refactor table of contents.Jan 25 2016, 9:58 AM
Isarra triaged this task as Medium priority.
Isarra edited projects, added Technical-Debt; removed Wikimedia-Developer-Summit-2016.
Isarra added a subscriber: Krinkle.

Edited up above, Refreshed (in later versions) no longer incorporate the TOC into the UI, it now just shows within the article content.

matmarex claimed this task.

I think this has basically happened, within the last year or two, thanks to the combination of the work on Parsoid parser unification, Vector 2022, and DiscussionTools.

Specifically:

  • The table of contents data is now stored in a structured format. MediaWiki API can output it like so: (1). Parsoid will also use it.
  • Skins may generate their table of contents from this data and display it anywhere. Currently only Vector 2022 does it. If they don't, MediaWiki core will generate a table of contents in the page content as before, but the code doing this has been refactored, and it all happens in Linker::generateTOC() and related methods: (2).
  • Similar table of contents data can be constructed for content that isn't generated by the parser. Currently this is used on a few special pages, Special:SpecialPages is probably the simplest example: (3). Extensions that add parser tags or custom content models could also make use of this.
  • Extensions may also extend the table of contents data. Currently, DiscussionTools adds information about discussion comments found in each section: (4). It is up to the skin how to display this additional data.