Merge remote-tracking branch 'origin/main' into travis/media-auth

This commit is contained in:
Travis Ralston 2024-06-11 16:16:30 -06:00
commit add03fe674
22 changed files with 182 additions and 227 deletions

View file

@ -193,7 +193,7 @@ jobs:
- name: " Setup Hugo" - name: " Setup Hugo"
uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0 uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0
with: with:
hugo-version: '0.113.0' hugo-version: '0.117.0'
extended: true extended: true
- name: "📥 Source checkout" - name: "📥 Source checkout"
uses: actions/checkout@v4 uses: actions/checkout@v4

View file

@ -4,3 +4,4 @@
IgnoreDirectoryMissingTrailingSlash: true IgnoreDirectoryMissingTrailingSlash: true
DirectoryPath: spec DirectoryPath: spec
CheckExternal: false CheckExternal: false
IgnoreInternalEmptyHash: true

View file

@ -61,7 +61,7 @@ place after an MSC has been accepted, not as part of a proposal itself.
1. Install the extended version (often the OS default) of Hugo: 1. Install the extended version (often the OS default) of Hugo:
<https://gohugo.io/getting-started/installing>. Note that at least Hugo <https://gohugo.io/getting-started/installing>. Note that at least Hugo
v0.113.0 is required. v0.117.0 is required.
Alternatively, use the Docker image at Alternatively, use the Docker image at
https://hub.docker.com/r/klakegg/hugo/. (The "extended edition" is required https://hub.docker.com/r/klakegg/hugo/. (The "extended edition" is required

View file

@ -0,0 +1 @@
Clarify specification by adding `/logout` to the overview list of endpoints that don't take a JSON request body.

View file

@ -0,0 +1 @@
Fix broken link to push rule condition kinds.

View file

@ -0,0 +1 @@
Fix various typos throughout the specification.

View file

@ -0,0 +1 @@
Do not add empty arrays to examples.

View file

@ -0,0 +1 @@
Generate ToC with Hugo rather than JavaScript.

View file

@ -1 +1 @@
Clarify that the `event` field of the `/v2/send_join` response is only required when `join_authorised_via_users_server` was present in the `content` field of the request. Clarify that the `event` field of the `/v2/send_join` response is only required when the event is signed by the resident server.

View file

@ -0,0 +1 @@
Clarify that the `event` field of the `/v2/send_join` response is only required when the event is signed by the resident server.

View file

@ -0,0 +1 @@
Replace references to RFC 7235 and RFC 7230 that are obsoleted by RFC 9110.

View file

@ -37,6 +37,10 @@ description = "Home of the Matrix specification for decentralised communication"
weight = 30 weight = 30
[markup] [markup]
[markup.tableOfContents]
startLevel = 2
endLevel = 6
ordered = true
[markup.goldmark] [markup.goldmark]
[markup.goldmark.renderer] [markup.goldmark.renderer]
# Enables us to render raw HTML # Enables us to render raw HTML
@ -130,7 +134,7 @@ sidebar_menu_compact = true
[module] [module]
[module.hugoVersion] [module.hugoVersion]
extended = true extended = true
min = "0.113.0" min = "0.117.0"
[[module.imports]] [[module.imports]]
path = "github.com/matrix-org/docsy" path = "github.com/matrix-org/docsy"
disable = false disable = false

View file

@ -22,12 +22,20 @@ recommended outside test environments.
Clients are authenticated using opaque `access_token` strings (see [Client Clients are authenticated using opaque `access_token` strings (see [Client
Authentication](#client-authentication) for details). Authentication](#client-authentication) for details).
All `POST` and `PUT` endpoints, with the exception of media upload endpoints All `POST` and `PUT` endpoints, with the exception of those listed below,
in the [Content Repository module](#content-repository),
require the client to supply a request body containing a (potentially empty) require the client to supply a request body containing a (potentially empty)
JSON object. Clients should supply a `Content-Type` header of JSON object. Clients should supply a `Content-Type` header of
`application/json` for all requests with JSON bodies, but this is not required. `application/json` for all requests with JSON bodies, but this is not required.
The exceptions are:
- [`POST /_matrix/media/v3/upload`](#post_matrixmediav3upload) and
[`PUT /_matrix/media/v3/upload/{serverName}/{mediaId}`](#put_matrixmediav3uploadservernamemediaid),
both of which take the uploaded media as the request body.
- [`POST /_matrix/client/v3/logout`](#post_matrixclientv3logout) and
[`POST /_matrix/client/v3/logout/all`](#post_matrixclientv3logoutall),
which take an empty request body.
Similarly, all endpoints require the server to return a JSON object, Similarly, all endpoints require the server to return a JSON object,
with the exception of 200 responses to the media download endpoints in the with the exception of 200 responses to the media download endpoints in the
[Content Repository module](#content-repository). [Content Repository module](#content-repository).
@ -225,7 +233,7 @@ return a standard error response of the form:
``` ```
Homeservers SHOULD include a [`Retry-After`](https://www.rfc-editor.org/rfc/rfc9110#field.retry-after) Homeservers SHOULD include a [`Retry-After`](https://www.rfc-editor.org/rfc/rfc9110#field.retry-after)
for any response with a 429 status code. header for any response with a 429 status code.
The `retry_after_ms` property MAY be included to tell the client how long The `retry_after_ms` property MAY be included to tell the client how long
they have to wait in milliseconds before they can try again. This property is they have to wait in milliseconds before they can try again. This property is
@ -2803,42 +2811,42 @@ operations and run in a resource constrained environment. Like embedded
applications, they are not intended to be fully-fledged communication applications, they are not intended to be fully-fledged communication
systems. systems.
{{< cs-module name="instant_messaging" >}} {{% cs-module name="instant_messaging" %}}
{{< cs-module name="rich_replies" >}} {{% cs-module name="rich_replies" %}}
{{< cs-module name="voip_events" >}} {{% cs-module name="voip_events" %}}
{{< cs-module name="typing_notifications" >}} {{% cs-module name="typing_notifications" %}}
{{< cs-module name="receipts" >}} {{% cs-module name="receipts" %}}
{{< cs-module name="read_markers" >}} {{% cs-module name="read_markers" %}}
{{< cs-module name="presence" >}} {{% cs-module name="presence" %}}
{{< cs-module name="content_repo" >}} {{% cs-module name="content_repo" %}}
{{< cs-module name="send_to_device" >}} {{% cs-module name="send_to_device" %}}
{{< cs-module name="device_management" >}} {{% cs-module name="device_management" %}}
{{< cs-module name="end_to_end_encryption" >}} {{% cs-module name="end_to_end_encryption" %}}
{{< cs-module name="secrets" >}} {{% cs-module name="secrets" %}}
{{< cs-module name="history_visibility" >}} {{% cs-module name="history_visibility" %}}
{{< cs-module name="push" >}} {{% cs-module name="push" %}}
{{< cs-module name="third_party_invites" >}} {{% cs-module name="third_party_invites" %}}
{{< cs-module name="search" >}} {{% cs-module name="search" %}}
{{< cs-module name="guest_access" >}} {{% cs-module name="guest_access" %}}
{{< cs-module name="room_previews" >}} {{% cs-module name="room_previews" %}}
{{< cs-module name="tags" >}} {{% cs-module name="tags" %}}
{{< cs-module name="account_data" >}} {{% cs-module name="account_data" %}}
{{< cs-module name="admin" >}} {{% cs-module name="admin" %}}
{{< cs-module name="event_context" >}} {{% cs-module name="event_context" %}}
{{< cs-module name="sso_login" >}} {{% cs-module name="sso_login" %}}
{{< cs-module name="dm" >}} {{% cs-module name="dm" %}}
{{< cs-module name="ignore_users" >}} {{% cs-module name="ignore_users" %}}
{{< cs-module name="stickers" >}} {{% cs-module name="stickers" %}}
{{< cs-module name="report_content" >}} {{% cs-module name="report_content" %}}
{{< cs-module name="third_party_networks" >}} {{% cs-module name="third_party_networks" %}}
{{< cs-module name="openid" >}} {{% cs-module name="openid" %}}
{{< cs-module name="server_acls" >}} {{% cs-module name="server_acls" %}}
{{< cs-module name="mentions" >}} {{% cs-module name="mentions" %}}
{{< cs-module name="room_upgrades" >}} {{% cs-module name="room_upgrades" %}}
{{< cs-module name="server_notices" >}} {{% cs-module name="server_notices" %}}
{{< cs-module name="moderation_policies" >}} {{% cs-module name="moderation_policies" %}}
{{< cs-module name="spaces" >}} {{% cs-module name="spaces" %}}
{{< cs-module name="event_replacements" >}} {{% cs-module name="event_replacements" %}}
{{< cs-module name="event_annotations" >}} {{% cs-module name="event_annotations" %}}
{{< cs-module name="threading" >}} {{% cs-module name="threading" %}}
{{< cs-module name="reference_relations" >}} {{% cs-module name="reference_relations" %}}

View file

@ -349,14 +349,14 @@ def authorization_headers(origin_name, origin_signing_key,
``` ```
The format of the Authorization header is given in The format of the Authorization header is given in
[RFC 7235](https://datatracker.ietf.org/doc/html/rfc7235#section-2.1). In [Section 11.4 of RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#section-11.4). In
summary, the header begins with authorization scheme `X-Matrix`, followed by one summary, the header begins with authorization scheme `X-Matrix`, followed by one
or more spaces, followed by a comma-separated list of parameters written as or more spaces, followed by a comma-separated list of parameters written as
name=value pairs. Zero or more spaces and tabs around each comma are allowed. name=value pairs. Zero or more spaces and tabs around each comma are allowed.
The names are case insensitive and order does not matter. The The names are case insensitive and order does not matter. The
values must be enclosed in quotes if they contain characters that are not values must be enclosed in quotes if they contain characters that are not
allowed in `token`s, as defined in allowed in `token`s, as defined in
[RFC 7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6); if a [Section 5.6.2 of RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.2); if a
value is a valid `token`, it may or may not be enclosed in quotes. Quoted value is a valid `token`, it may or may not be enclosed in quotes. Quoted
values may include backslash-escaped characters. When parsing the header, the values may include backslash-escaped characters. When parsing the header, the
recipient must unescape the characters. That is, a backslash-character pair is recipient must unescape the characters. That is, a backslash-character pair is
@ -388,6 +388,13 @@ The authorization parameters to include are:
Unknown parameters are ignored. Unknown parameters are ignored.
{{% boxes/note %}}
{{< changed-in v="1.11" >}}
This section used to reference [RFC 7235](https://datatracker.ietf.org/doc/html/rfc7235#section-2.1)
and [RFC 7230](https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.2), that
were obsoleted by RFC 9110 without changes to the sections of interest here.
{{% /boxes/note %}}
### Response Authentication ### Response Authentication
Responses are authenticated by the TLS server certificate. A homeserver Responses are authenticated by the TLS server certificate. A homeserver

View file

@ -18,7 +18,7 @@ properties:
kind: kind:
type: string type: string
description: |- description: |-
The kind of condition to apply. See [conditions](/client-server-api/#conditions) for The kind of condition to apply. See [conditions](/client-server-api/#conditions-1) for
more information on the allowed kinds and how they work. more information on the allowed kinds and how they work.
key: key:
type: string type: string

View file

@ -207,9 +207,9 @@ paths:
title: SignedMembershipEvent title: SignedMembershipEvent
x-addedInMatrixVersion: "1.2" x-addedInMatrixVersion: "1.2"
description: |- description: |-
Required if the `content` of the event in the request contained the `join_authorised_via_users_server` The membership event sent to other servers by the resident server including a signature
field. The signed copy of the membership event sent to other servers by the resident server, from the resident server. Required if the room is [restricted](/client-server-api/#restricted-rooms)
including the resident server's signature. and the joining user is authorised by one of the conditions.
servers_in_room: servers_in_room:
type: array type: array
x-addedInMatrixVersion: "1.6" x-addedInMatrixVersion: "1.6"

View file

@ -35,9 +35,9 @@
*/}} */}}
{{ if reflect.IsMap $this_object.items }} {{ if reflect.IsMap $this_object.items }}
{{ $items_example := partial "json-schema/resolve-example" $this_object.items }} {{ $items_example := partial "json-schema/resolve-example" $this_object.items }}
{{ $example = slice $items_example }} {{ if $items_example }}
{{ else }} {{ $example = slice $items_example }}
{{ $example = slice }} {{ end }}
{{ end }} {{ end }}
{{ end }} {{ end }}

View file

@ -0,0 +1,83 @@
{{/*
A modified version of the siderbar-tree.html partial in Docsy, adding:
* The "toc.html" partial at L45.
*/}}
{{/* We cache this partial for bigger sites and set the active class client side. */ -}}
{{ $sidebarCacheLimit := .Site.Params.ui.sidebar_cache_limit | default 2000 -}}
{{ $shouldDelayActive := ge (len .Site.Pages) $sidebarCacheLimit -}}
<div id="td-sidebar-menu" class="td-sidebar__inner{{ if $shouldDelayActive }} d-none{{ end }}">
{{ if not .Site.Params.ui.sidebar_search_disable -}}
<form class="td-sidebar__search d-flex align-items-center">
{{ partial "search-input.html" . }}
<button class="btn btn-link td-sidebar__toggle d-md-none p-0 ms-3 fas fa-bars" type="button" data-bs-toggle="collapse" data-bs-target="#td-section-nav" aria-controls="td-section-nav" aria-expanded="false" aria-label="Toggle section navigation">
</button>
</form>
{{ else -}}
<div id="content-mobile">
<form class="td-sidebar__search d-flex align-items-center">
{{ partial "search-input.html" . }}
<button class="btn btn-link td-sidebar__toggle d-md-none p-0 ms-3 fas fa-bars" type="button" data-bs-toggle="collapse" data-bs-target="#td-section-nav" aria-controls="td-section-nav" aria-expanded="false" aria-label="Toggle section navigation">
</button>
</form>
</div>
<div id="content-desktop"></div>
{{ end -}}
<nav class="td-sidebar-nav collapse
{{- if .Site.Params.ui.sidebar_search_disable }} td-sidebar-nav--search-disabled{{ end -}}
{{- if .Site.Params.ui.sidebar_menu_foldable }} foldable-nav{{ end -}}
" id="td-section-nav">
{{ if (gt (len .Site.Home.Translations) 0) -}}
<div class="td-sidebar-nav__section nav-item dropdown d-block d-lg-none">
{{ partial "navbar-lang-selector.html" . }}
</div>
{{ end -}}
{{ $navRoot := cond (and (ne .Params.toc_root true) (eq .Site.Home.Type "docs")) .Site.Home .FirstSection -}}
{{ $ulNr := 0 -}}
{{ $ulShow := .Site.Params.ui.ul_show | default 1 -}}
{{ $sidebarMenuTruncate := .Site.Params.ui.sidebar_menu_truncate | default 50 -}}
<ul class="td-sidebar-nav__section pe-md-3 ul-{{ $ulNr }}">
{{ template "section-tree-nav-section" (dict "page" . "section" $navRoot "shouldDelayActive" $shouldDelayActive "sidebarMenuTruncate" $sidebarMenuTruncate "ulNr" $ulNr "ulShow" (add $ulShow 1)) }}
</ul>
{{ partial "toc.html" . }}
</nav>
</div>
{{ define "section-tree-nav-section" -}}
{{ $s := .section -}}
{{ $p := .page -}}
{{ $shouldDelayActive := .shouldDelayActive -}}
{{ $sidebarMenuTruncate := .sidebarMenuTruncate -}}
{{ $treeRoot := cond (eq .ulNr 0) true false -}}
{{ $ulNr := .ulNr -}}
{{ $ulShow := .ulShow -}}
{{ $active := and (not $shouldDelayActive) (eq $s $p) -}}
{{ $activePath := and (not $shouldDelayActive) (or (eq $p $s) ($p.IsDescendant $s)) -}}
{{ $show := cond (or (lt $ulNr $ulShow) $activePath (and (not $shouldDelayActive) (eq $s.Parent $p.Parent)) (and (not $shouldDelayActive) (eq $s.Parent $p)) (not $p.Site.Params.ui.sidebar_menu_compact) (and (not $shouldDelayActive) ($p.IsDescendant $s.Parent))) true false -}}
{{ $mid := printf "m-%s" ($s.RelPermalink | anchorize) -}}
{{ $pages_tmp := where (union $s.Pages $s.Sections).ByWeight ".Params.toc_hide" "!=" true -}}
{{ $pages := $pages_tmp | first $sidebarMenuTruncate -}}
{{ $withChild := gt (len $pages) 0 -}}
{{ $manualLink := cond (isset $s.Params "manuallink") $s.Params.manualLink ( cond (isset $s.Params "manuallinkrelref") (relref $s $s.Params.manualLinkRelref) $s.RelPermalink) -}}
{{ $manualLinkTitle := cond (isset $s.Params "manuallinktitle") $s.Params.manualLinkTitle $s.Title -}}
<li class="td-sidebar-nav__section-title td-sidebar-nav__section{{ if $withChild }} with-child{{ else }} without-child{{ end }}{{ if $activePath }} active-path{{ end }}{{ if (not (or $show $p.Site.Params.ui.sidebar_menu_foldable )) }} collapse{{ end }}" id="{{ $mid }}-li">
{{ if (and $p.Site.Params.ui.sidebar_menu_foldable (ge $ulNr 1)) -}}
<input type="checkbox" id="{{ $mid }}-check"{{ if $activePath}} checked{{ end }}/>
<label for="{{ $mid }}-check"><a href="{{ $manualLink }}"{{ if ne $s.LinkTitle $manualLinkTitle }} title="{{ $manualLinkTitle }}"{{ end }}{{ with $s.Params.manualLinkTarget }} target="{{ . }}"{{ if eq . "_blank" }} rel="noopener"{{ end }}{{ end }} class="align-left ps-0 {{ if $active}} active{{ end }} td-sidebar-link{{ if $s.IsPage }} td-sidebar-link__page{{ else }} td-sidebar-link__section{{ end }}{{ if $treeRoot }} tree-root{{ end }}" id="{{ $mid }}">{{ with $s.Params.Icon}}<i class="{{ . }}"></i>{{ end }}<span class="{{ if $active }}td-sidebar-nav-active-item{{ end }}">{{ $s.LinkTitle }}</span></a></label>
{{ else -}}
<a href="{{ $manualLink }}"{{ if ne $s.LinkTitle $manualLinkTitle }} title="{{ $manualLinkTitle }}"{{ end }}{{ with $s.Params.manualLinkTarget }} target="{{ . }}"{{ if eq . "_blank" }} rel="noopener"{{ end }}{{ end }} class="align-left ps-0{{ if $active}} active{{ end }} td-sidebar-link{{ if $s.IsPage }} td-sidebar-link__page{{ else }} td-sidebar-link__section{{ end }}{{ if $treeRoot }} tree-root{{ end }}" id="{{ $mid }}">{{ with $s.Params.Icon}}<i class="{{ . }}"></i>{{ end }}<span class="{{ if $active }}td-sidebar-nav-active-item{{ end }}">{{ $s.LinkTitle }}</span></a>
{{- end }}
{{- if $withChild }}
{{- $ulNr := add $ulNr 1 }}
<ul class="ul-{{ $ulNr }}{{ if (gt $ulNr 1)}} foldable{{end}}">
{{ range $pages -}}
{{ if (not (and (eq $s $p.Site.Home) (eq .Params.toc_root true))) -}}
{{ template "section-tree-nav-section" (dict "page" $p "section" . "shouldDelayActive" $shouldDelayActive "sidebarMenuTruncate" $sidebarMenuTruncate "ulNr" $ulNr "ulShow" $ulShow) }}
{{- end }}
{{- end }}
</ul>
{{- end }}
</li>
{{- end }}

15
layouts/partials/toc.html Normal file
View file

@ -0,0 +1,15 @@
{{/*
A modified version of the toc.html partial in Docsy.
*/}}
{{ $page := .Params }}
{{ if not .Params.notoc -}}
{{ with .TableOfContents -}}
<hr>
<div id="toc">
<a id="toc-title" href="#">{{ $page.Title }}</a>
{{ . }}
</div>
{{ end -}}
{{ end -}}

View file

@ -5,6 +5,6 @@
{{ with .Page.Resources.Match "*.md" }} {{ with .Page.Resources.Match "*.md" }}
{{ range ((sort . "Params.date" "desc")) }} {{ range ((sort . "Params.date" "desc")) }}
{{ .Content }} {{ .RenderShortcodes }}
{{ end }} {{ end }}
{{ end }} {{ end }}

View file

@ -11,6 +11,6 @@
{{ with .Site.GetPage "client-server-api/modules" }} {{ with .Site.GetPage "client-server-api/modules" }}
{{ with .Resources.GetMatch (printf "%s%s" $name ".md") }} {{ with .Resources.GetMatch (printf "%s%s" $name ".md") }}
{{ .Content }} {{ .RenderShortcodes }}
{{ end }} {{ end }}
{{ end }} {{ end }}

View file

@ -14,174 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*
Account for id attributes that are in the sidebar nav
*/
function populateIds() {
const navItems = document.querySelectorAll(".td-sidebar-nav li");
return Array.from(navItems).map(item => item.id).filter(id => id != "");
}
/*
Given an ID and an array of IDs, return s version of the original ID that's
not equal to any of the IDs in the array.
*/
function uniquifyHeadingId(id, uniqueIDs) {
const baseId = id;
let counter = 0;
while (uniqueIDs.includes(id)) {
counter = counter + 1;
id = baseId + "-" + counter.toString();
}
return id;
}
/*
Given an array of heading nodes, ensure they all have unique IDs.
We have to do this mostly because of client-server modules, which are
rendered separately then glued together with a template.
Because heading IDs are generated in rendering, this means they can and will
end up with duplicate IDs.
*/
function uniquifyHeadingIds(headings) {
const uniqueIDs = populateIds();
for (let heading of headings) {
const uniqueID = uniquifyHeadingId(heading.id, uniqueIDs);
uniqueIDs.push(uniqueID);
heading.id = uniqueID;
}
}
/*
The document contains "normal" headings, and these have corresponding items
in the ToC.
The document might also contain H1 headings that act as titles for blocks of
rendered data, like HTTP APIs or event schemas. Unlike "normal" headings,
these headings don't appear in the ToC. But they do have anchor IDs to enable
links to them. When someone follows a link to one of these "rendered data"
headings we want to scroll the ToC to the item corresponding to the "normal"
heading preceding the "rendered data" heading we have visited.
To support this we need to add `data` attributes to ToC items.
These attributes identify which "rendered data" headings live underneath
the heading corresponding to that ToC item.
*/
function setTocItemChildren(toc, headings) {
let tocEntryForHeading = null;
for (const heading of headings) {
// H1 headings are rendered-data headings
if (heading.tagName !== "H1") {
tocEntryForHeading = document.querySelector(`nav li a[href="#${heading.id}"]`);
} else {
// on the ToC entry for the parent heading,
// set a data-* attribute whose name is the child's fragment ID
tocEntryForHeading.setAttribute(`data-${heading.id}`, "true");
}
}
}
/*
Generate a table of contents based on the headings in the document.
*/
function makeToc() {
// make the title from the H1
const h1 = document.body.querySelector("h1");
const title = document.createElement("a");
title.id = "toc-title";
title.setAttribute("href", "#");
title.textContent = h1.textContent;
// make the content
const content = document.body.querySelector(".td-content");
let headings = [].slice.call(content.querySelectorAll("h2, h3, h4, h5, h6, .rendered-data > details > summary > h1"));
// exclude headings that don't have IDs.
headings = headings.filter(heading => heading.id);
uniquifyHeadingIds(headings);
// exclude .rendered-data > h1 headings from the ToC
const tocTargets = headings.filter(heading => heading.tagName !== "H1");
// we have to adjust heading IDs to ensure that they are unique
const nav = document.createElement("nav");
nav.id = "TableOfContents";
const section = makeTocSection(tocTargets, 0);
nav.appendChild(section.content);
// build the TOC and append to it title and content
const toc = document.createElement("div");
toc.id = "toc";
toc.appendChild(title);
toc.appendChild(nav);
// append TOC to the section navigation
const section_nav = document.body.querySelector("#td-section-nav");
let hr = document.createElement("hr");
section_nav.appendChild(hr);
section_nav.appendChild(toc);
// tell ToC items about any rendered-data headings they contain
setTocItemChildren(section.content, headings);
}
// create a single ToC entry
function makeTocEntry(heading) {
const li = document.createElement("li");
const a = document.createElement("a");
a.setAttribute("href", `#${heading.id}`);
a.textContent = heading.textContent;
li.appendChild(a);
return li;
}
/*
Each ToC section is an `<ol>` element.
ToC entries are `<li>` elements and these contain nested ToC sections,
whenever we go to the next heading level down.
*/
function makeTocSection(headings, index) {
const ol = document.createElement("ol");
let previousHeading = null;
let previousLi = null;
let i = index;
const lis = [];
for (i; i < headings.length; i++) {
const thisHeading = headings[i];
if (previousHeading && (thisHeading.tagName > previousHeading.tagName)) {
// we are going down a heading level, create a new nested section
const section = makeTocSection(headings, i);
previousLi.appendChild(section.content);
i = section.index -1;
}
else if (previousHeading && (previousHeading.tagName > thisHeading.tagName)) {
// we have come back up a level, so a section is finished
for (let li of lis) {
ol.appendChild(li);
}
return {
content: ol,
index: i
}
}
else {
// we are still processing this section, so add this heading to the current section
previousLi = makeTocEntry(thisHeading);
lis.push(previousLi);
previousHeading = thisHeading;
}
}
for (let li of lis) {
ol.appendChild(li);
}
return {
content: ol,
index: i
}
}
/* /*
Set a new ToC entry. Set a new ToC entry.
Clear any previously highlighted ToC items, set the new one, Clear any previously highlighted ToC items, set the new one,
@ -303,8 +135,6 @@ for the corresponding ToC entry.
*/ */
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
makeToc();
const toc = document.querySelector("#toc"); const toc = document.querySelector("#toc");
toc.addEventListener("click", event => { toc.addEventListener("click", event => {
if (event.target.tagName === "A") { if (event.target.tagName === "A") {