Compare commits

...

3 commits

Author SHA1 Message Date
Tulir Asokan 666a13a84e
Merge fe6c97f498 into c47fa4d093 2026-01-16 10:52:00 -05:00
Kévin Commaille c47fa4d093
Bump docsy to v0.13.0 (#2287)
Some checks are pending
Spec / 🔎 Validate OpenAPI specifications (push) Waiting to run
Spec / 🔎 Check Event schema examples (push) Waiting to run
Spec / 🔎 Check OpenAPI definitions examples (push) Waiting to run
Spec / 🔎 Check JSON Schemas inline examples (push) Waiting to run
Spec / ⚙️ Calculate baseURL for later jobs (push) Waiting to run
Spec / 🐍 Build OpenAPI definitions (push) Blocked by required conditions
Spec / 📢 Run towncrier for changelog (push) Waiting to run
Spec / 📖 Build the spec (push) Blocked by required conditions
Spec / 🔎 Validate generated HTML (push) Blocked by required conditions
Spec / 📖 Build the historical backup spec (push) Blocked by required conditions
Spec / Create release (push) Blocked by required conditions
Spell Check / Spell Check with Typos (push) Waiting to run
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2026-01-15 17:01:07 +00:00
Tulir Asokan fe6c97f498 Specify basic validation for federation membership endpoints
Signed-off-by: Tulir Asokan <tulir@maunium.net>
2025-12-21 15:31:36 +02:00
44 changed files with 528 additions and 285 deletions

View file

@ -1,8 +1,9 @@
name: "Spec" name: "Spec"
env: env:
HUGO_VERSION: 0.148.1 HUGO_VERSION: 0.153.3
PYTHON_VERSION: 3.13 PYTHON_VERSION: 3.13
NODE_VERSION: 24
on: on:
push: push:
@ -27,7 +28,7 @@ jobs:
- name: " Setup Node" - name: " Setup Node"
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: ${{ env.NODE_VERSION }}
- name: "🔎 Run validator" - name: "🔎 Run validator"
run: | run: |
npx @redocly/cli@latest lint data/api/*/*.yaml npx @redocly/cli@latest lint data/api/*/*.yaml
@ -201,7 +202,7 @@ jobs:
- name: " Setup Node" - name: " Setup Node"
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: ${{ env.NODE_VERSION }}
- name: " Setup Hugo" - name: " Setup Hugo"
uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0 uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0
with: with:
@ -288,7 +289,7 @@ jobs:
- name: " Setup Node" - name: " Setup Node"
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: ${{ env.NODE_VERSION }}
- name: " Setup Hugo" - name: " Setup Hugo"
uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0 uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0
with: with:

View file

@ -50,7 +50,7 @@ function getHeadings() {
let headings = []; let headings = [];
// First get the anchors in the ToC. // First get the anchors in the ToC.
const toc_anchors = document.querySelectorAll("#toc nav a"); const toc_anchors = document.querySelectorAll("#TableOfContents a");
for (const anchor of toc_anchors) { for (const anchor of toc_anchors) {
// Then get the heading from its selector in the anchor's href. // Then get the heading from its selector in the anchor's href.
@ -59,7 +59,7 @@ function getHeadings() {
console.error("Got ToC anchor without href"); console.error("Got ToC anchor without href");
continue; continue;
} }
const heading = document.querySelector(selector); const heading = document.querySelector(selector);
if (!heading) { if (!heading) {
console.error("Heading not found for selector:", selector); console.error("Heading not found for selector:", selector);
@ -122,13 +122,13 @@ function getCurrentHeading(headings, headerOffset) {
*/ */
function selectTocEntry(id) { function selectTocEntry(id) {
// Deselect previously selected entries. // Deselect previously selected entries.
const activeEntries = document.querySelectorAll("#toc nav a.active"); const activeEntries = document.querySelectorAll("#TableOfContents a.active");
for (const activeEntry of activeEntries) { for (const activeEntry of activeEntries) {
activeEntry.classList.remove('active'); activeEntry.classList.remove('active');
} }
// Find the new entry and select it. // Find the new entry and select it.
const newEntry = document.querySelector(`#toc nav a[href="#${id}"]`); const newEntry = document.querySelector(`#TableOfContents a[href="#${id}"]`);
if (!newEntry) { if (!newEntry) {
console.error("ToC entry not found for ID:", id); console.error("ToC entry not found for ID:", id);
return; return;

View file

@ -76,52 +76,126 @@ Custom SCSS for the Matrix spec
scroll-behavior: smooth; scroll-behavior: smooth;
overscroll-behavior: contain; overscroll-behavior: contain;
&>.td-sidebar-nav__section { & > .td-sidebar-nav__section {
margin-top: 1rem; margin-top: 1rem;
}
.td-sidebar-nav__section .ul-1 ul { .ul-1 ul {
padding-left: 0; padding-left: 0;
}
/* This is to make the width of the items that have sub-items (like room versions)
the same as the width of items that don't (like changelog) */
.pr-md-3, .px-md-3 {
padding-right: 0 !important;
}
.ul-1 > li > a {
padding-left: 1rem !important;
}
.ul-2 > li > a {
padding-left: 2rem !important;
}
a.td-sidebar-link.tree-root {
color: $gray-800;
font-weight: $font-weight-bold;
font-size: 1.3rem;
margin-bottom: 0;
border-bottom: none;
}
a, a.td-sidebar-link {
color: $gray-800;
font-weight: $font-weight-normal;
padding-top: .2rem;
padding-bottom: .2rem;
display: block;
transition: all 100ms ease-in-out;
&:hover {
background-color: $secondary-lighter-background;
color: $gray-800;
} }
&.active, &active:hover { /* This is to make the width of the items that have sub-items (like room versions)
background-color: $secondary-background; the same as the width of items that don't (like changelog) */
.pr-md-3, .px-md-3 {
padding-right: 0 !important;
}
.ul-1 > li > a {
padding-left: 1rem !important;
}
.ul-2 > li > a {
padding-left: 2rem !important;
}
}
/* Styles for the table of contents */
& > .td-toc {
padding-top: 1rem;
padding-left: 1.5rem;
/* Add border above the toc */
border-top: 1px solid var(--bs-tertiary-color);
ol {
padding-left: 1rem;
counter-reset: section;
list-style-type: none;
}
#TableOfContents {
/* Remove the space between the title and the ToC */
margin-top: 0;
&>ol>li {
margin-bottom: .5rem;
&>a {
font-weight: $font-weight-bold;
}
}
ol {
padding-left: 0;
}
&>ol>li>a {
padding-left: 1rem;
}
&>ol>li>ol>li>a {
padding-left: 2rem;
}
&>ol>li>ol>li>ol>li>a {
padding-left: 3rem;
}
&>ol>li>ol>li>ol>li>ol>li>a {
padding-left: 4rem;
}
&>ol>li>ol>li>ol>li>ol>li>ol>li>a {
padding-left: 5rem;
}
}
li a:before {
counter-increment: section;
content: counters(section, ".") " ";
}
.td-toc-title {
font-weight: $font-weight-bold;
font-size: 1.3rem;
/* Remove the border under the title */
border-bottom: 0;
/* Remove the space under the title */
margin-bottom: 0;
/* Fix the top of page link color */
a, a:hover {
color: $secondary;
}
}
}
/* Apply the same style to links in the navigation and in the ToC */
& > .td-sidebar-nav__section, & > .td-toc #TableOfContents {
li a.td-sidebar-link.tree-root {
color: $gray-800;
font-weight: $font-weight-bold;
font-size: 1.3rem;
margin-bottom: 0;
border-bottom: none;
}
li a, li a.td-sidebar-link {
color: $gray-800;
font-weight: $font-weight-normal;
padding-top: .2rem;
padding-bottom: .2rem;
transition: all 100ms ease-in-out;
&:hover {
background-color: $secondary-lighter-background;
color: $gray-800;
}
&.active, &active:hover {
background-color: $secondary-background;
}
} }
} }
} }
@ -199,64 +273,6 @@ Custom SCSS for the Matrix spec
scroll-margin-top: 5.5rem; scroll-margin-top: 5.5rem;
} }
/* Styles for the table of contents */
#toc {
padding-top: .5rem;
padding-left: 1.5rem;
ol {
padding-left: 1rem;
counter-reset: section;
list-style-type: none;
}
#TableOfContents {
&>ol>li {
margin-bottom: .5rem;
&>a {
font-weight: $font-weight-bold;
}
}
ol {
padding-left: 0;
}
&>ol>li>a {
padding-left: 1rem;
}
&>ol>li>ol>li>a {
padding-left: 2rem;
}
&>ol>li>ol>li>ol>li>a {
padding-left: 3rem;
}
&>ol>li>ol>li>ol>li>ol>li>a {
padding-left: 4rem;
}
&>ol>li>ol>li>ol>li>ol>li>ol>li>a {
padding-left: 5rem;
}
}
li a:before {
counter-increment: section;
content: counters(section, ".") " ";
}
#toc-title {
font-weight: $font-weight-bold;
font-size: 1.3rem;
}
}
.endpoints-toc { .endpoints-toc {
summary { summary {
cursor: pointer; cursor: pointer;

View file

@ -18,6 +18,7 @@ $primary: #FFF;
$secondary: #0098D4; $secondary: #0098D4;
$dark: #333; $dark: #333;
$gray-100: #FBFBFB; $gray-100: #FBFBFB;
$code-color: #005b7f;
$secondary-background: #E5F5FB; $secondary-background: #E5F5FB;
$secondary-lighter-background: #F4FAFC; $secondary-lighter-background: #F4FAFC;

View file

@ -0,0 +1 @@
Upgrade to docsy v0.13.0.

View file

@ -0,0 +1 @@
Specified input validation for PDUs passed to federation membership endpoints.

View file

@ -166,3 +166,11 @@ sidebar_menu_compact = true
mediaType = "text/markdown" mediaType = "text/markdown"
isPlainText = true isPlainText = true
baseName = "checklist" baseName = "checklist"
# Add font media types for downloading KaTeX fonts.
# See: https://www.docsy.dev/docs/content/diagrams-and-formulae/#create-media-types-for-katex-fonts
[mediaTypes]
[mediaTypes.'font/woff']
suffixes = ['woff']
[mediaTypes.'font/woff2']
suffixes = ['woff2']

View file

@ -172,6 +172,17 @@ paths:
} }
"400": "400":
description: |- description: |-
The `M_INVALID_PARAM` error code is used to indicate one or more of the following:
* The invite event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events).
* The event type is not `m.room.member`.
* The `membership` field inside the event content is not `invite`.
* The event sender is not a user ID on the origin server.
* The `state_key` is not a user ID on the receiving server.
Servers MUST apply the validation above to the invite event before
signing it regardless of room version.
The `M_MISSING_PARAM` error code is used to indicate one or more of The `M_MISSING_PARAM` error code is used to indicate one or more of
the following: the following:
@ -186,9 +197,9 @@ paths:
Servers MAY apply the validation above to room versions 1 through 11, Servers MAY apply the validation above to room versions 1 through 11,
and SHOULD apply the validation above to all other room versions. and SHOULD apply the validation above to all other room versions.
If `M_MISSING_PARAM` is returned and the request is associated with a If `M_MISSING_PARAM` or `M_INVALID_PARAM` is returned and the request
Client-Server API request, the Client-Server API request SHOULD fail is associated with a Client-Server API request, the Client-Server API
with a 5xx error rather than being passed through. request SHOULD fail with a 5xx error rather than being passed through.
content: content:
application/json: application/json:
schema: schema:

View file

@ -154,6 +154,17 @@ paths:
The error should be passed through to clients so that they The error should be passed through to clients so that they
may give better feedback to users. may give better feedback to users.
The `M_INVALID_PARAM` error code is used to indicate one or more of the following:
* The invite event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events).
* The event type is not `m.room.member`.
* The `membership` field inside the event content is not `invite`.
* The event sender is not a user ID on the origin server.
* The `state_key` is not a user ID on the receiving server.
Servers MUST apply the validation above to the invite event before
signing it regardless of room version.
The `M_MISSING_PARAM` error code is used to indicate one or more of The `M_MISSING_PARAM` error code is used to indicate one or more of
the following: the following:
@ -168,9 +179,9 @@ paths:
Servers MAY apply the validation above to room versions 1 through 11, Servers MAY apply the validation above to room versions 1 through 11,
and SHOULD apply the validation above to all other room versions. and SHOULD apply the validation above to all other room versions.
If `M_MISSING_PARAM` is returned and the request is associated with a If `M_MISSING_PARAM` or `M_INVALID_PARAM` is returned and the request
Client-Server API request, the Client-Server API request SHOULD fail is associated with a Client-Server API request, the Client-Server API
with a 5xx error rather than being passed through. request SHOULD fail with a 5xx error rather than being passed through.
content: content:
application/json: application/json:
schema: schema:

View file

@ -36,7 +36,7 @@ paths:
type: string type: string
- in: path - in: path
name: userId name: userId
description: The user ID the join event will be for. description: The user ID the join event will be for. This MUST be a user ID on the origin server.
required: true required: true
example: "@someone:example.org" example: "@someone:example.org"
schema: schema:
@ -388,6 +388,43 @@ paths:
} }
} }
] ]
"400":
description: |-
The request is invalid in some way.
The `M_INVALID_PARAM` error code is used to indicate one or more of the following:
* The join event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events).
* The event type is not `m.room.member`.
* The `membership` field inside the event content is not `join`.
* The event sender is not a user ID on the origin server.
* The `state_key` is not equal to the `sender`.
Servers MUST apply the validation above to the join event.
content:
application/json:
schema:
$ref: ../client-server/definitions/errors/error.yaml
examples:
response:
value: {
"errcode": "M_INVALID_PARAM",
"error": "Not a join event."
}
"403":
description: |-
The room that the joining server is attempting to join does not permit the user
to join.
content:
application/json:
schema:
$ref: ../client-server/definitions/errors/error.yaml
examples:
response:
value: {
"errcode": "M_FORBIDDEN",
"error": "You are not invited to this room"
}
servers: servers:
- url: "{protocol}://{hostname}{basePath}" - url: "{protocol}://{hostname}{basePath}"
variables: variables:

View file

@ -247,6 +247,16 @@ paths:
The error should be passed through to clients so that they The error should be passed through to clients so that they
may give better feedback to users. may give better feedback to users.
The `M_INVALID_PARAM` error code is used to indicate one or more of the following:
* The join event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events).
* The event type is not `m.room.member`.
* The `membership` field inside the event content is not `join`.
* The event sender is not a user ID on the origin server.
* The `state_key` is not equal to the `sender`.
Servers MUST apply the validation above to the join event.
New in `v1.2`, the following error conditions might happen: New in `v1.2`, the following error conditions might happen:
If the room is [restricted](/client-server-api/#restricted-rooms) If the room is [restricted](/client-server-api/#restricted-rooms)

View file

@ -36,7 +36,7 @@ paths:
type: string type: string
- in: path - in: path
name: userId name: userId
description: The user ID the knock event will be for. description: The user ID the knock event will be for. This MUST be a user ID on the origin server.
required: true required: true
example: "@someone:example.org" example: "@someone:example.org"
schema: schema:
@ -330,6 +330,27 @@ paths:
"$ref": "./examples/invite_or_knock_state.json" "$ref": "./examples/invite_or_knock_state.json"
} }
} }
"400":
description: |-
The `M_INVALID_PARAM` error code is used to indicate one or more of the following:
* The knock event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events).
* The event type is not `m.room.member`.
* The `membership` field inside the event content is not `knock`.
* The event sender is not a user ID on the origin server.
* The `state_key` is not equal to the `sender`.
Servers MUST apply the validation above to the knock event.
content:
application/json:
schema:
$ref: ../client-server/definitions/errors/error.yaml
examples:
response:
value: {
"errcode": "M_INVALID_PARAM",
"error": "Not a knock event."
}
"403": "403":
description: |- description: |-
The knocking server or user is not permitted to knock on the room, such as when the The knocking server or user is not permitted to knock on the room, such as when the

View file

@ -36,7 +36,7 @@ paths:
type: string type: string
- in: path - in: path
name: userId name: userId
description: The user ID the leave event will be for. description: The user ID the leave event will be for. This MUST be a user ID on the origin server.
required: true required: true
example: "@someone:example.org" example: "@someone:example.org"
schema: schema:
@ -249,6 +249,27 @@ paths:
200, 200,
{} {}
] ]
"400":
description: |-
The `M_INVALID_PARAM` error code is used to indicate one or more of the following:
* The leave event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events).
* The event type is not `m.room.member`.
* The `membership` field inside the event content is not `leave`.
* The event sender is not a user ID on the origin server.
* The `state_key` is not equal to the `sender`.
Servers MUST apply the validation above to the leave event.
content:
application/json:
schema:
$ref: ../client-server/definitions/errors/error.yaml
examples:
response:
value: {
"errcode": "M_INVALID_PARAM",
"error": "Not a leave event."
}
servers: servers:
- url: "{protocol}://{hostname}{basePath}" - url: "{protocol}://{hostname}{basePath}"
variables: variables:

View file

@ -134,6 +134,27 @@ paths:
examples: examples:
response: response:
value: {} value: {}
"400":
description: |-
The `M_INVALID_PARAM` error code is used to indicate one or more of the following:
* The leave event fails a [signature check](/server-server-api/#validating-hashes-and-signatures-on-received-events).
* The event type is not `m.room.member`.
* The `membership` field inside the event content is not `leave`.
* The event sender is not a user ID on the origin server.
* The `state_key` is not equal to the `sender`.
Servers MUST apply the validation above to the leave event.
content:
application/json:
schema:
$ref: ../client-server/definitions/errors/error.yaml
examples:
response:
value: {
"errcode": "M_INVALID_PARAM",
"error": "Not a leave event."
}
servers: servers:
- url: "{protocol}://{hostname}{basePath}" - url: "{protocol}://{hostname}{basePath}"
variables: variables:

2
go.mod
View file

@ -2,4 +2,4 @@ module github.com/matrix-org/matrix-spec
go 1.12 go 1.12
require github.com/matrix-org/docsy v0.0.0-20250722140156-5df72519f5af // indirect require github.com/matrix-org/docsy v0.0.0-20260106184755-71d103ebb20a // indirect

6
go.sum
View file

@ -1,4 +1,4 @@
github.com/FortAwesome/Font-Awesome v0.0.0-20241216213156-af620534bfc3/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo= github.com/FortAwesome/Font-Awesome v0.0.0-20241216213156-af620534bfc3/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo=
github.com/matrix-org/docsy v0.0.0-20250722140156-5df72519f5af h1:XghgUC0H5BoGrvtT9/oWBUi+5Zux875qRHhpAZ0RURI= github.com/matrix-org/docsy v0.0.0-20260106184755-71d103ebb20a h1:WB3unuZJy7ewAf33sxbtEwYnC+i+Jt1sJpAR3BtzvEo=
github.com/matrix-org/docsy v0.0.0-20250722140156-5df72519f5af/go.mod h1:4/t21g/nPraob/DVMm3jrk26k0CDL5I7Mxf+ar0IAgs= github.com/matrix-org/docsy v0.0.0-20260106184755-71d103ebb20a/go.mod h1:mdn1m5HJug6ZddQgrOyCrXNegbtdl5evHiqqbEQLzdI=
github.com/twbs/bootstrap v5.3.6+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0= github.com/twbs/bootstrap v5.3.8+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0=

View file

@ -5,15 +5,7 @@
We use it to send the delimited passthrough element through KaTeX to render maths We use it to send the delimited passthrough element through KaTeX to render maths
in the Olm / Megolm spec. in the Olm / Megolm spec.
See: https://gohugo.io/functions/transform/tomath/#step-2 See: https://www.docsy.dev/docs/content/diagrams-and-formulae/#add-passthrough-render-hook
*/ -}} */ -}}
{{- $opts := dict "output" "htmlAndMathml" "displayMode" (eq .Type "block") }} {{ partial "scripts/math.html" . }}
{{- with try (transform.ToMath .Inner $opts) }}
{{- with .Err }}
{{- errorf "Unable to render mathematical markup to HTML using the transform.ToMath function. The KaTeX display engine threw the following error: %s: see %s." . $.Position }}
{{- else }}
{{- .Value }}
{{- $.Page.Store.Set "hasMath" true }}
{{- end }}
{{- end -}}

View file

@ -1,28 +1,35 @@
{{/* {{/*
A copy of the breadcrumb.html partial in Docsy, modified A copy of the breadcrumb.html partial in Docsy, modified to:
to:
* show the breadcrumbs by default by removing the `td-breadcrumbs__single`
class
* omit breadcrumbs when this is the homepage * omit breadcrumbs when this is the homepage
* otherwise, include the homepage in the breadcrumbs * otherwise, include the homepage in the breadcrumbs
*/}} */}}
{{ if not .IsHome }} {{ if not .IsHome -}}
<nav aria-label="breadcrumb" class="td-breadcrumbs"> <nav aria-label="breadcrumb" class="td-breadcrumbs">
<ol class="breadcrumb"> <ol class="breadcrumb">
{{ template "breadcrumbnav" (dict "p1" . "p2" .) }} {{- template "breadcrumbnav" (dict "p1" . "p2" .) }}
</ol> </ol>
</nav > </nav>
{{ end }} {{ end -}}
{{ define "breadcrumbnav" }} {{- define "breadcrumbnav" -}}
{{ if .p1.Parent }} {{ if .p1.Parent -}}
{{ template "breadcrumbnav" (dict "p1" .p1.Parent "p2" .p2 ) }} {{ template "breadcrumbnav" (dict "p1" .p1.Parent "p2" .p2 ) -}}
{{ else if not .p1.IsHome }} {{ else if not .p1.IsHome -}}
{{ template "breadcrumbnav" (dict "p1" .p1.Site.Home "p2" .p2 ) }} {{ template "breadcrumbnav" (dict "p1" .p1.Site.Home "p2" .p2 ) -}}
{{ end }} {{ end -}}
{{ $isActive := eq .p1 .p2 }} {{ $isActive := eq .p1 .p2 }}
<li class="breadcrumb-item{{ if $isActive }} active{{ end }}" {{ if $isActive }}aria-current="page"{{ end }}> <li class="breadcrumb-item{{ if $isActive }} active{{ end }}"
<a href="{{ .p1.RelPermalink }}">{{ .p1.LinkTitle }}</a> {{- if $isActive }}aria-current="page"{{ end }}>
</li> {{ if $isActive -}}
{{ end }} {{ .p1.LinkTitle -}}
{{ else -}}
<a href="{{ .p1.RelPermalink }}">{{ .p1.LinkTitle }}</a>
{{- end -}}
</li>
{{- end -}}

View file

@ -1,8 +1,11 @@
{{- /* {{- /*
A version of the navbar.html partial in Docsy, only modified A copy of the navbar.html partial in Docsy, modified to:
to include the spec version, which is calculated using an
inline `version-string` partial. * remove `data-bs-theme` at L20, otherwise the title disappears on hover.
* replace the site title with "specification" at L31.
* include the spec version from the config at L34-35, which is calculated
using an inline `version-string` partial.
*/ -}} */ -}}
@ -13,8 +16,8 @@
{{ $baseURL := urls.Parse $.Site.Params.Baseurl -}} {{ $baseURL := urls.Parse $.Site.Params.Baseurl -}}
<nav class="td-navbar js-navbar-scroll <nav class="td-navbar js-navbar-scroll
{{- if $cover }} td-navbar-cover {{- end }}" data-bs-theme="light"> {{- if $cover }} td-navbar-cover {{- end }}">
<div class="container-fluid flex-column flex-md-row"> <div class="td-navbar-container container-fluid flex-column flex-md-row">
<a class="navbar-brand" href="{{ .Site.Home.RelPermalink }}"> <a class="navbar-brand" href="{{ .Site.Home.RelPermalink }}">
{{- /**/ -}} {{- /**/ -}}
<span class="navbar-brand__logo navbar-logo"> <span class="navbar-brand__logo navbar-logo">
@ -32,7 +35,8 @@
<span class="navbar-version"> &mdash; {{ partial "version-string" . }}</span> <span class="navbar-version"> &mdash; {{ partial "version-string" . }}</span>
{{- /**/ -}} {{- /**/ -}}
</a> </a>
<div class="td-navbar-nav-scroll ms-md-auto" id="main_navbar"> <div class="td-navbar-nav-scroll td-navbar-nav-scroll--indicator" id="main_navbar">
<div class="scroll-indicator scroll-left"></div>
<ul class="navbar-nav"> <ul class="navbar-nav">
{{ $p := . -}} {{ $p := . -}}
{{ range .Site.Menus.main -}} {{ range .Site.Menus.main -}}
@ -58,39 +62,41 @@
</li> </li>
{{ end -}} {{ end -}}
{{ if .Site.Params.versions -}} {{ if .Site.Params.versions -}}
<li class="nav-item dropdown d-none d-lg-block"> <li class="nav-item dropdown d-none d-lg-block td-navbar__version-menu">
{{ partial "navbar-version-selector.html" . -}} {{ partial "navbar-version-selector.html" . -}}
</li> </li>
{{ end -}} {{ end -}}
{{ if (gt (len .Site.Home.Translations) 0) -}} {{ if (gt (len .Site.Home.Translations) 0) -}}
<li class="nav-item dropdown d-none d-lg-block"> <li class="nav-item td-navbar__lang-menu">
{{ partial "navbar-lang-selector.html" . -}} {{ partial "navbar-lang-selector.html" . -}}
</li> </li>
{{ end -}} {{ end -}}
{{ if .Site.Params.ui.showLightDarkModeMenu -}} {{- $darkMode := partialCached "dark-mode-config.html" "dark-mode-global" -}}
<li class="td-light-dark-menu nav-item dropdown"> {{ if $darkMode.showMenu -}}
<li class="nav-item td-navbar__light-dark-menu">
{{ partial "theme-toggler" . }} {{ partial "theme-toggler" . }}
</li> </li>
{{ end -}} {{ end -}}
</ul> </ul>
<div class="scroll-indicator scroll-right"></div>
</div> </div>
<div class="d-none d-lg-block"> <div class="d-none d-lg-block td-navbar__search">
{{ partial "search-input.html" . }} {{ partial "search-input.html" . }}
</div> </div>
</div> </div>
</nav> </nav>
{{ define "_partials/version-string" }} {{- define "_partials/version-string" -}}
{{ $ret := "unstable version"}} {{ $ret := "unstable version" -}}
{{ $status := .Site.Params.version.status }} {{ $status := .Site.Params.version.status -}}
{{ if ne $status "unstable"}} {{ if ne $status "unstable" -}}
{{ $path := path.Join "changelogs" }} {{ $path := path.Join "changelogs" -}}
{{/* produces a string similar to "version v1.5" */}} {{/* produces a string similar to "version v1.5" */ -}}
{{ $ret = delimit (slice "version v" .Site.Params.version.major "." .Site.Params.version.minor) "" }} {{ $ret = delimit (slice "version v" .Site.Params.version.major "." .Site.Params.version.minor) "" -}}
{{ end }} {{ end -}}
{{ return $ret }} {{ return $ret -}}
{{ end }} {{- end -}}

View file

@ -1,37 +1,57 @@
{{- /* {{- /*
A modified version of the siderbar-tree.html partial in Docsy, adding: A copy of the siderbar-tree.html partial in Docsy, modified to:
* The "toc.html" partial at L45. * Ignore the `sidebarRoot` parameter, because of this regression:
<https://github.com/google/docsy/issues/2426>
* Add the "toc.html" partial at L68.
*/ -}} */ -}}
{{/* We cache this partial for bigger sites and set the active class client side. */ -}} {{ $context := .context -}}
{{ $sidebarCacheLimit := .Site.Params.ui.sidebar_cache_limit | default 2000 -}} {{ $sidebarRoot := .sidebarRoot -}}
{{ $shouldDelayActive := ge (len .Site.Pages) $sidebarCacheLimit -}} {{ $sidebarRootID := .sidebarRootID -}}
{{ $cacheSidebar := .cacheSidebar -}}
{{ with $context -}}
{{/* When the sidebar is cached, "active" class is set client side. */ -}}
{{ $shouldDelayActive := $cacheSidebar -}}
<div id="td-sidebar-menu" class="td-sidebar__inner{{ if $shouldDelayActive }} d-none{{ end }}"> <div id="td-sidebar-menu" class="td-sidebar__inner{{ if $shouldDelayActive }} d-none{{ end }}">
{{ if not .Site.Params.ui.sidebar_search_disable -}} {{ if not .Site.Params.ui.sidebar_search_disable -}}
<form class="td-sidebar__search d-flex align-items-center"> <form class="td-sidebar__search d-flex align-items-center">
{{ partial "search-input.html" . }} {{ 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 class="btn btn-link td-sidebar__toggle" 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> </button>
</form> </form>
{{ else -}}
{{- else -}}
<div id="content-mobile"> <div id="content-mobile">
<form class="td-sidebar__search d-flex align-items-center"> <form class="td-sidebar__search d-flex align-items-center">
{{ partial "search-input.html" . }} {{ 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 class="btn btn-link td-sidebar__toggle" 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> </button>
</form> </form>
</div> </div>
<div id="content-desktop"></div> <div id="content-desktop"></div>
{{ end -}}
{{- end }}
{{/* */ -}}
<nav class="td-sidebar-nav collapse <nav class="td-sidebar-nav collapse
{{- if .Site.Params.ui.sidebar_search_disable }} td-sidebar-nav--search-disabled{{ end -}} {{- if .Site.Params.ui.sidebar_search_disable }} td-sidebar-nav--search-disabled{{ end -}}
{{- if .Site.Params.ui.sidebar_menu_foldable }} foldable-nav{{ end -}} {{- if .Site.Params.ui.sidebar_menu_foldable }} foldable-nav{{ end }}" {{/**/ -}}
" id="td-section-nav"> id="td-section-nav"
{{ if (gt (len .Site.Home.Translations) 0) -}} {{- if .Site.Params.ui.sidebar_root_enabled }} data-sidebar-root-id="{{ $sidebarRootID }}"{{ end -}}
<div class="td-sidebar-nav__section nav-item dropdown d-block d-lg-none"> >
{{ if and .Site.Params.ui.sidebar_lang_menu (gt (len .Site.Home.Translations) 0) -}}
<div class="td-sidebar-nav__section nav-item d-block d-lg-none">
{{ partial "navbar-lang-selector.html" . }} {{ partial "navbar-lang-selector.html" . }}
</div> </div>
{{ end -}} {{ end -}}
@ -45,44 +65,129 @@
{{ partial "toc.html" . }} {{ partial "toc.html" . }}
</nav> </nav>
</div> </div>
{{- end }}{{/* with $context */ -}}
{{ define "section-tree-nav-section" -}} {{ define "section-tree-nav-section" -}}
{{ $s := .section -}} {{/* cSpell:ignore manuallink manuallinkrelref manuallinktitle */ -}}
{{ $p := .page -}} {{ $s := .section -}}
{{ $shouldDelayActive := .shouldDelayActive -}} {{ $p := .page -}}
{{ $sidebarMenuTruncate := .sidebarMenuTruncate -}} {{ $shouldDelayActive := .shouldDelayActive -}}
{{ $treeRoot := cond (eq .ulNr 0) true false -}} {{ $sidebarMenuTruncate := .sidebarMenuTruncate -}}
{{ $ulNr := .ulNr -}} {{ $treeRoot := cond (eq .ulNr 0) true false -}}
{{ $ulShow := .ulShow -}} {{ $ulNr := .ulNr -}}
{{ $active := and (not $shouldDelayActive) (eq $s $p) -}} {{ $ulShow := .ulShow -}}
{{ $activePath := and (not $shouldDelayActive) (or (eq $p $s) ($p.IsDescendant $s)) -}} {{ $active := and (not $shouldDelayActive) (eq $s $p) -}}
{{ $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 -}} {{ $activePath := and (not $shouldDelayActive) (or (eq $p $s) ($p.IsDescendant $s)) -}}
{{ $mid := printf "m-%s" ($s.RelPermalink | anchorize) -}} {{ $show := cond
{{ $pages_tmp := where (union $s.Pages $s.Sections).ByWeight ".Params.toc_hide" "!=" true -}} (or
{{ $pages := $pages_tmp | first $sidebarMenuTruncate -}} (lt $ulNr $ulShow)
{{ $truncatedEntryCount := sub (len $pages_tmp) $sidebarMenuTruncate -}} $activePath
{{ if gt $truncatedEntryCount 0 -}} (and (not $shouldDelayActive) (eq $s.Parent $p.Parent))
{{ warnf "WARNING: %d sidebar entries have been truncated. To avoid this, increase `params.ui.sidebar_menu_truncate` to at least %d (from %d) in your config file. Section: %s" (and (not $shouldDelayActive) (eq $s.Parent $p))
$truncatedEntryCount (len $pages_tmp) $sidebarMenuTruncate $s.Path -}} (not $p.Site.Params.ui.sidebar_menu_compact)
{{ end -}} (and (not $shouldDelayActive) ($p.IsDescendant $s.Parent))
{{ $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) -}} true false
{{ $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"> {{ $mid := printf "m-%s" ($s.RelPermalink | anchorize) -}}
{{ if (and $p.Site.Params.ui.sidebar_menu_foldable (ge $ulNr 1)) -}} {{ $pages_tmp := where (union $s.Pages $s.Sections).ByWeight ".Params.toc_hide" "!=" true -}}
<input type="checkbox" id="{{ $mid }}-check"{{ if $activePath}} checked{{ end }}/> {{ $pages := $pages_tmp | first $sidebarMenuTruncate -}}
<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> {{ $truncatedEntryCount := sub (len $pages_tmp) $sidebarMenuTruncate -}}
{{ 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> {{ if gt $truncatedEntryCount 0 -}}
{{- end }} {{ warnf "WARNING: %d sidebar entries have been truncated. To avoid this, increase `params.ui.sidebar_menu_truncate` to at least %d (from %d) in your config file. Section: %s"
{{- if $withChild }} $truncatedEntryCount (len $pages_tmp) $sidebarMenuTruncate $s.Path -}}
{{- $ulNr := add $ulNr 1 }} {{ end -}}
<ul class="ul-{{ $ulNr }}{{ if (gt $ulNr 1)}} foldable{{end}}">
{{ range $pages -}} {{ $withChild := gt (len $pages) 0 -}}
{{ if (not (and (eq $s $p.Site.Home) (eq .Params.toc_root true))) -}} {{ $manualLink :=
{{ template "section-tree-nav-section" (dict "page" $p "section" . "shouldDelayActive" $shouldDelayActive "sidebarMenuTruncate" $sidebarMenuTruncate "ulNr" $ulNr "ulShow" $ulShow) }} 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
-}}
{{ if and $treeRoot (eq $s.Params.sidebar_root_for "self") -}}
{{ with $s.Parent -}}
{{ $manualLink = .RelPermalink -}}
{{ end -}}
{{ end -}}
<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 -}}
{{- if and $s.Params.sidebar_root_for site.Params.ui.sidebar_root_enabled }} td-sidebar-root-up-icon{{ 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 }} {{- end }}
{{- end }} </li>
</ul> {{- end }}
{{- end }}
</li>
{{- end -}}

View file

@ -1,15 +1,29 @@
{{/* {{/*
A modified version of the toc.html partial in Docsy. A copy of the toc.html partial in Docsy, modified to:
*/}} * show the page's title instead of "on this page"
{{ $page := .Params }}
*/ -}}
{{/*
Always render the td-toc element. ScrollSpy is counting on it to exist,
even if it's empty.
cSpell:ignore notoc
*/ -}}
<div class="td-toc" data-proofer-ignore>
{{ if not .Params.notoc -}} {{ if not .Params.notoc -}}
{{ with .TableOfContents -}} {{ $toc := .TableOfContents -}}
<hr> {{ if and $toc (ne $toc `<nav id="TableOfContents"></nav>`) -}}
<div id="toc"> <div class="td-toc-title">
<a id="toc-title" href="#">{{ $page.Title }}</a> <span class="td-toc-title__text">{{ .Params.Title }}</span>
{{ . }} <a class="td-toc-title__link" title="{{ i18n "toc_top_of_page" }}" href="#"></a>
</div> </div>
{{ $toc | safeHTML }}
{{ end -}} {{ end -}}
{{ end -}} {{ end -}}
</div>
{{/* */ -}}

View file

@ -1,11 +1,14 @@
{{/* {{/*
A copy of the baseof.html partial in Docsy, modified A copy of the baseof.html partial in Docsy, modified to:
to remove the right-hand column from the layout.
*/}} * generate a static file `versions.json` that can be used to populate the
version picker.
* remove the right-hand column from the layout.
{{/* Generate a static file versions.json that can be used to populate the version picker */}} */ -}}
{{/* Generate a static file versions.json that can be used to populate the version picker */ -}}
{{ if .IsHome }} {{ if .IsHome }}
{{- /* Load all changelog subpages, sorted by release date */ -}} {{- /* Load all changelog subpages, sorted by release date */ -}}
{{ $changelog := site.GetPage "changelog" }} {{ $changelog := site.GetPage "changelog" }}
@ -28,20 +31,18 @@
<html itemscope itemtype="http://schema.org/WebPage" <html itemscope itemtype="http://schema.org/WebPage"
{{- with .Site.Language.LanguageDirection }} dir="{{ . }}" {{- end -}} {{- with .Site.Language.LanguageDirection }} dir="{{ . }}" {{- end -}}
{{ with .Site.Language.Lang }} lang="{{ . }}" {{- end }} {{/**/ -}} {{ with .Site.Language.Lang }} lang="{{ . }}" {{- end }} {{/**/ -}}
class="no-js"> class="no-js"
{{- $darkMode := partialCached "dark-mode-config.html" "dark-mode-global" -}}
{{- if $darkMode.enable }} data-theme-init{{ end }}>
<head> <head>
{{ partial "head.html" . }} {{ partial "head.html" . }}
{{ if .Page.Store.Get "hasMath" }}
<link href="{{ relURL "css/katex.min.css" }}" rel="preload" as="style">
<link href="{{ relURL "css/katex.min.css" }}" rel="stylesheet">
{{ end }}
</head> </head>
<body class="td-{{ .Kind }}{{ with .Page.Params.body_class }} {{ . }}{{ end }}"> <body class="td-{{ .Kind }}{{ with .Page.Params.body_class }} {{ . }}{{ end }}">
<header> <header>
{{ partial "navbar.html" . }} {{ partial "navbar.html" . }}
</header> </header>
<div class="container-fluid td-outer"> <div class="container-fluid td-outer">
<div class="td-main"> <div class="td-main" {{- partialCached "td/scrollspy-attr.txt" . .Section | safeHTMLAttr }}>
<div class="row flex-xl-nowrap"> <div class="row flex-xl-nowrap">
<aside class="col-12 col-md-3 col-xl-2 td-sidebar d-print-none"> <aside class="col-12 col-md-3 col-xl-2 td-sidebar d-print-none">
{{ partial "sidebar.html" . }} {{ partial "sidebar.html" . }}

View file

@ -1,41 +0,0 @@
#!/bin/bash
#
# Download the KaTeX fonts and CSS, and copy them into `static`.
set -e
root=$(dirname "$0")/..
# Check that the caller supplied a version.
version=$1
if [[ -z $1 || $1 = "-h" || $1 = "--help" ]]; then
>&2 echo "Usage: download-katex-assets.sh VERSION (e.g. v0.16.23)"
>&2 echo
>&2 echo "Downloads KaTeX fonts and CSS from the specified release"
>&2 echo "on GitHub and puts the files into static/."
exit 1
fi
# Create a temporary directory and register a handler to clean it up on exit.
tmp_dir=$(mktemp -d)
clean_up () {
rm -rf "$tmp_dir"
}
trap clean_up EXIT
# Fetch the release archive.
archive=$tmp_dir/katex.tar.gz
url=https://github.com/KaTeX/KaTeX/releases/download/$version/katex.tar.gz
echo "GET $url"
curl -L --output "$archive" "$url"
# Unpack the archive.
tar -xzvf "$archive" -C "$tmp_dir"
# Move the CSS file into place.
install -vm644 "$tmp_dir/katex/katex.min.css" "$root/static/css/katex.min.css"
# Remove any existing fonts and move the new ones into place.
rm -rvf "$root"/static/css/fonts/KaTeX*
while IFS= read -r -d '' file; do
install -vm644 "$file" "$root/static/css/fonts"
done < <(find "$tmp_dir/katex/fonts" -maxdepth 1 -name "KaTeX*.woff2" -print0)

File diff suppressed because one or more lines are too long