Merge remote-tracking branch 'upstream/main' into clokep/conditions-link

This commit is contained in:
Patrick Cloke 2024-06-11 17:41:12 -04:00
commit f75fe264c2
33 changed files with 230 additions and 234 deletions

View file

@ -1,6 +1,6 @@
---
name: [SCT] Release checklist
about: Used by the Spec Core Team to create a new release.
name: '[SCT] Release checklist'
about: 'Used by the Spec Core Team to create a new release.'
title: 'Matrix 1.X'
labels: 'release-blocker'
assignees: ''

View file

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

View file

@ -4,3 +4,4 @@
IgnoreDirectoryMissingTrailingSlash: true
DirectoryPath: spec
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:
<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
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 @@
Do not require UIA when first uploading cross-signing keys, as per [MSC3967](https://github.com/matrix-org/matrix-spec-proposals/pull/3967).

View file

@ -0,0 +1 @@
Clarify that per-request UIA for /login/get_token is an RFC 2119 MUST requirement.

View file

@ -0,0 +1 @@
Add the new `unsigned.membership` property to events served over the client-server API, as per [MSC4115](https://github.com/matrix-org/matrix-spec-proposals/pull/4115).

View file

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

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

@ -0,0 +1 @@
Fix syntax errors in the spec release issue template.

View file

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

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
[markup]
[markup.tableOfContents]
startLevel = 2
endLevel = 6
ordered = true
[markup.goldmark]
[markup.goldmark.renderer]
# Enables us to render raw HTML
@ -130,7 +134,7 @@ sidebar_menu_compact = true
[module]
[module.hugoVersion]
extended = true
min = "0.113.0"
min = "0.117.0"
[[module.imports]]
path = "github.com/matrix-org/docsy"
disable = false

View file

@ -22,13 +22,20 @@ recommended outside test environments.
Clients are authenticated using opaque `access_token` strings (see [Client
Authentication](#client-authentication) for details).
All `POST` and `PUT` endpoints, with the exception of [`POST
/_matrix/media/v3/upload`](#post_matrixmediav3upload) and [`PUT
/_matrix/media/v3/upload/{serverName}/{mediaId}`](#put_matrixmediav3uploadservernamemediaid),
All `POST` and `PUT` endpoints, with the exception of those listed below,
require the client to supply a request body containing a (potentially empty)
JSON object. Clients should supply a `Content-Type` header of
`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,
with the exception of 200 responses to
[`GET /_matrix/media/v3/download/{serverName}/{mediaId}`](#get_matrixmediav3downloadservernamemediaid)
@ -227,7 +234,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)
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
they have to wait in milliseconds before they can try again. This property is
@ -2316,7 +2323,7 @@ following endpoint.
This endpoint is particularly useful if the client has lost context on the aggregation for
a parent event and needs to rebuild/verify it.
When using the `recurse` parameter, note that there no way for a client to
When using the `recurse` parameter, note that there is no way for a client to
control how far the server recurses. If the client decides that the server's
recursion level is insufficient, it could, for example, perform the recursion
itself, or disable whatever feature requires more recursion.
@ -2805,42 +2812,42 @@ operations and run in a resource constrained environment. Like embedded
applications, they are not intended to be fully-fledged communication
systems.
{{< cs-module name="instant_messaging" >}}
{{< cs-module name="rich_replies" >}}
{{< cs-module name="voip_events" >}}
{{< cs-module name="typing_notifications" >}}
{{< cs-module name="receipts" >}}
{{< cs-module name="read_markers" >}}
{{< cs-module name="presence" >}}
{{< cs-module name="content_repo" >}}
{{< cs-module name="send_to_device" >}}
{{< cs-module name="device_management" >}}
{{< cs-module name="end_to_end_encryption" >}}
{{< cs-module name="secrets" >}}
{{< cs-module name="history_visibility" >}}
{{< cs-module name="push" >}}
{{< cs-module name="third_party_invites" >}}
{{< cs-module name="search" >}}
{{< cs-module name="guest_access" >}}
{{< cs-module name="room_previews" >}}
{{< cs-module name="tags" >}}
{{< cs-module name="account_data" >}}
{{< cs-module name="admin" >}}
{{< cs-module name="event_context" >}}
{{< cs-module name="sso_login" >}}
{{< cs-module name="dm" >}}
{{< cs-module name="ignore_users" >}}
{{< cs-module name="stickers" >}}
{{< cs-module name="report_content" >}}
{{< cs-module name="third_party_networks" >}}
{{< cs-module name="openid" >}}
{{< cs-module name="server_acls" >}}
{{< cs-module name="mentions" >}}
{{< cs-module name="room_upgrades" >}}
{{< cs-module name="server_notices" >}}
{{< cs-module name="moderation_policies" >}}
{{< cs-module name="spaces" >}}
{{< cs-module name="event_replacements" >}}
{{< cs-module name="event_annotations" >}}
{{< cs-module name="threading" >}}
{{< cs-module name="reference_relations" >}}
{{% cs-module name="instant_messaging" %}}
{{% cs-module name="rich_replies" %}}
{{% cs-module name="voip_events" %}}
{{% cs-module name="typing_notifications" %}}
{{% cs-module name="receipts" %}}
{{% cs-module name="read_markers" %}}
{{% cs-module name="presence" %}}
{{% cs-module name="content_repo" %}}
{{% cs-module name="send_to_device" %}}
{{% cs-module name="device_management" %}}
{{% cs-module name="end_to_end_encryption" %}}
{{% cs-module name="secrets" %}}
{{% cs-module name="history_visibility" %}}
{{% cs-module name="push" %}}
{{% cs-module name="third_party_invites" %}}
{{% cs-module name="search" %}}
{{% cs-module name="guest_access" %}}
{{% cs-module name="room_previews" %}}
{{% cs-module name="tags" %}}
{{% cs-module name="account_data" %}}
{{% cs-module name="admin" %}}
{{% cs-module name="event_context" %}}
{{% cs-module name="sso_login" %}}
{{% cs-module name="dm" %}}
{{% cs-module name="ignore_users" %}}
{{% cs-module name="stickers" %}}
{{% cs-module name="report_content" %}}
{{% cs-module name="third_party_networks" %}}
{{% cs-module name="openid" %}}
{{% cs-module name="server_acls" %}}
{{% cs-module name="mentions" %}}
{{% cs-module name="room_upgrades" %}}
{{% cs-module name="server_notices" %}}
{{% cs-module name="moderation_policies" %}}
{{% cs-module name="spaces" %}}
{{% cs-module name="event_replacements" %}}
{{% cs-module name="event_annotations" %}}
{{% cs-module name="threading" %}}
{{% cs-module name="reference_relations" %}}

View file

@ -72,7 +72,7 @@ The `redacts` property of `m.room.redaction` events is moved from a top-level
event property to a property under the event `content`.
For backwards-compatibility with older clients, servers should add a `redacts` property
to the top level of `m.room.redaction` events in when serving such events over the
to the top level of `m.room.redaction` events when serving such events over the
Client-Server API.
For improved compatibility with newer clients, servers should add a `redacts` property

View file

@ -349,14 +349,14 @@ def authorization_headers(origin_name, origin_signing_key,
```
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
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.
The names are case insensitive and order does not matter. The
values must be enclosed in quotes if they contain characters that are not
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
values may include backslash-escaped characters. When parsing the header, the
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.
{{% 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
Responses are authenticated by the TLS server certificate. A homeserver

View file

@ -19,11 +19,26 @@ paths:
/keys/device_signing/upload:
post:
x-addedInMatrixVersion: "1.1"
x-changedInMatrixVersion:
"1.11": UIA is not always required for this endpoint.
summary: Upload cross-signing keys.
description: |-
Publishes cross-signing keys for the user.
This API endpoint uses the [User-Interactive Authentication API](/client-server-api/#user-interactive-authentication-api).
User-Interactive Authentication MUST be performed, except in these cases:
- there is no existing cross-signing master key uploaded to the homeserver, OR
- there is an existing cross-signing master key and it exactly matches the
cross-signing master key provided in the request body. If there are any additional
keys provided in the request (self-signing key, user-signing key) they MUST also
match the existing keys stored on the server. In other words, the request contains
no new keys.
This allows clients to freely upload one set of keys, but not modify/overwrite keys if
they already exist. Allowing clients to upload the same set of keys more than once
makes this endpoint idempotent in the case where the response is lost over the network,
which would otherwise cause a UIA challenge upon retry.
operationId: uploadCrossSigningKeys
security:
- accessTokenQuery: []

View file

@ -90,6 +90,7 @@ properties:
"origin_server_ts": 1632491098485,
"unsigned": {
"age": 1257,
"membership": "leave"
}
}
transaction_id:
@ -112,3 +113,23 @@ properties:
this.
title: EventContent
type: object
membership:
description: |
The room membership of the user making the request, at the time of the event.
This property is the value of the `membership` property of the
requesting user's [`m.room.member`](/client-server-api#mroommember)
state at the point of the event, including any changes caused by the
event. If the user had yet to join the room at the time of the event
(i.e, they have no `m.room.member` state), this property is set to
`leave`.
Homeservers SHOULD populate this property
wherever practical, but they MAY omit it if necessary (for example,
if calculating the value is expensive, servers might choose to only
implement it in encrypted rooms). The property is *not* normally populated
in events pushed to application services via the application service transaction API
(where there is no clear definition of "requesting user").
type: string
example: join
x-addedInMatrixVersion: "1.11"

View file

@ -45,7 +45,7 @@ paths:
intend to log in multiple devices must generate a token for each.
With other User-Interactive Authentication (UIA)-supporting endpoints, servers sometimes do not re-prompt
for verification if the session recently passed UIA. For this endpoint, servers should always re-prompt
for verification if the session recently passed UIA. For this endpoint, servers MUST always re-prompt
the user for verification to ensure explicit consent is gained for each additional client.
Servers are encouraged to apply stricter than normal rate limiting to this endpoint, such as maximum

View file

@ -312,7 +312,7 @@ components:
Whether to additionally include events which only relate indirectly to the
given event, i.e. events related to the given event via two or more direct relationships.
If set to `false`, only events which have direct a relation with the given
If set to `false`, only events which have a direct relation with the given
event will be included.
If set to `true`, all events which relate to the given event, or relate to

View file

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

View file

@ -5,6 +5,7 @@
"sender": "@example:example.org",
"origin_server_ts": 1432735824653,
"unsigned": {
"age": 1234
"age": 1234,
"membership": "join"
}
}

View file

@ -35,9 +35,9 @@
*/}}
{{ if reflect.IsMap $this_object.items }}
{{ $items_example := partial "json-schema/resolve-example" $this_object.items }}
{{ $example = slice $items_example }}
{{ else }}
{{ $example = slice }}
{{ if $items_example }}
{{ $example = slice $items_example }}
{{ 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" }}
{{ range ((sort . "Params.date" "desc")) }}
{{ .Content }}
{{ .RenderShortcodes }}
{{ end }}
{{ end }}

View file

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

View file

@ -14,174 +14,6 @@ See the License for the specific language governing permissions and
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.
Clear any previously highlighted ToC items, set the new one,
@ -303,8 +135,6 @@ for the corresponding ToC entry.
*/
window.addEventListener('DOMContentLoaded', () => {
makeToc();
const toc = document.querySelector("#toc");
toc.addEventListener("click", event => {
if (event.target.tagName === "A") {