mirror of
https://github.com/matrix-org/matrix-spec
synced 2026-03-23 19:44:09 +01:00
Merge branch 'main' into no-canonifyurls
This commit is contained in:
commit
1bc409e222
235
assets/js/toc.js
235
assets/js/toc.js
|
|
@ -15,18 +15,122 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
/*
|
||||
Set a new ToC entry.
|
||||
Clear any previously highlighted ToC items, set the new one,
|
||||
and adjust the ToC scroll position.
|
||||
Only call the given function once every 250 milliseconds to avoid impacting
|
||||
the performance of the browser.
|
||||
Source: https://remysharp.com/2010/07/21/throttling-function-calls
|
||||
*/
|
||||
function setTocEntry(newEntry) {
|
||||
const activeEntries = document.querySelectorAll("#toc a.active");
|
||||
function throttle(fn) {
|
||||
const threshold = 250;
|
||||
let last = null;
|
||||
let deferTimer = null;
|
||||
|
||||
return function (...args) {
|
||||
const now = new Date();
|
||||
|
||||
if (last && now < last + threshold) {
|
||||
// Hold on to it.
|
||||
clearTimeout(deferTimer);
|
||||
deferTimer = setTimeout(() => {
|
||||
last = now;
|
||||
fn.apply(this, args);
|
||||
}, threshold);
|
||||
} else {
|
||||
last = now;
|
||||
fn.apply(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Get the list of headings that appear in the ToC.
|
||||
This is not as simple as querying all the headings in the content, because
|
||||
some headings are not rendered in the ToC (e.g. in the endpoint definitions).
|
||||
*/
|
||||
function getHeadings() {
|
||||
let headings = [];
|
||||
|
||||
// First get the anchors in the ToC.
|
||||
const toc_anchors = document.querySelectorAll("#toc nav a");
|
||||
|
||||
for (const anchor of toc_anchors) {
|
||||
// Then get the heading from its selector in the anchor's href.
|
||||
const selector = anchor.getAttribute("href");
|
||||
if (!selector) {
|
||||
console.error("Got ToC anchor without href");
|
||||
continue;
|
||||
}
|
||||
|
||||
const heading = document.querySelector(selector);
|
||||
if (!heading) {
|
||||
console.error("Heading not found for selector:", selector);
|
||||
continue;
|
||||
}
|
||||
|
||||
headings.push(heading);
|
||||
}
|
||||
|
||||
return headings;
|
||||
}
|
||||
|
||||
/*
|
||||
Get the heading of the text visible at the top of the viewport.
|
||||
This is the first heading above or at the top of the viewport.
|
||||
*/
|
||||
function getCurrentHeading(headings, headerOffset) {
|
||||
const scrollTop = document.documentElement.scrollTop;
|
||||
let prevHeading = null;
|
||||
let currentHeading = null;
|
||||
let index = 0;
|
||||
|
||||
for (const heading of headings) {
|
||||
// Compute the position compared to the viewport.
|
||||
const rect = heading.getBoundingClientRect();
|
||||
|
||||
if (rect.top >= headerOffset && rect.top <= headerOffset + 30) {
|
||||
// This heading is at the top of the viewport, this is the current heading.
|
||||
currentHeading = heading;
|
||||
break;
|
||||
}
|
||||
if (rect.top >= headerOffset) {
|
||||
// This is in or below the viewport, the current heading should be the
|
||||
// previous one.
|
||||
if (prevHeading) {
|
||||
currentHeading = prevHeading;
|
||||
} else {
|
||||
// The first heading does not have a prevHeading.
|
||||
currentHeading = heading;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
prevHeading = heading;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return currentHeading;
|
||||
}
|
||||
|
||||
/*
|
||||
Select the ToC entry that points to the given ID.
|
||||
Clear any previously highlighted ToC items, select the new one,
|
||||
and adjust the ToC scroll position.
|
||||
*/
|
||||
function selectTocEntry(id) {
|
||||
// Deselect previously selected entries.
|
||||
const activeEntries = document.querySelectorAll("#toc nav a.active");
|
||||
for (const activeEntry of activeEntries) {
|
||||
activeEntry.classList.remove('active');
|
||||
}
|
||||
|
||||
// Find the new entry and select it.
|
||||
const newEntry = document.querySelector(`#toc nav a[href="#${id}"]`);
|
||||
if (!newEntry) {
|
||||
console.error("ToC entry not found for ID:", id);
|
||||
return;
|
||||
}
|
||||
newEntry.classList.add('active');
|
||||
// don't scroll the sidebar nav if the main content is not scrolled
|
||||
|
||||
// Don't scroll the sidebar nav if the main content is not scrolled
|
||||
const nav = document.querySelector("#td-section-nav");
|
||||
const content = document.querySelector("html");
|
||||
if (content.scrollTop !== 0) {
|
||||
|
|
@ -37,115 +141,24 @@ function setTocEntry(newEntry) {
|
|||
}
|
||||
|
||||
/*
|
||||
Test whether a node is in the viewport
|
||||
*/
|
||||
function isInViewport(node) {
|
||||
const rect = node.getBoundingClientRect();
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
The callback we pass to the IntersectionObserver constructor.
|
||||
|
||||
Called when any of our observed nodes starts or stops intersecting
|
||||
with the viewport.
|
||||
*/
|
||||
function handleIntersectionUpdate(entries) {
|
||||
|
||||
/*
|
||||
Special case: If the current URL hash matches a ToC entry, and
|
||||
the corresponding heading is visible in the viewport, then that is
|
||||
made the current ToC entry, and we don't even look at the intersection
|
||||
observer data.
|
||||
This means that if the user has clicked on a ToC entry,
|
||||
we won't unselect it through the intersection observer.
|
||||
*/
|
||||
const hash = document.location.hash;
|
||||
if (hash) {
|
||||
let tocEntryForHash = document.querySelector(`nav li a[href="${hash}"]`);
|
||||
// if the hash isn't a direct match for a ToC item, check the data attributes
|
||||
if (!tocEntryForHash) {
|
||||
const fragment = hash.substring(1);
|
||||
tocEntryForHash = document.querySelector(`nav li a[data-${fragment}]`);
|
||||
}
|
||||
if (tocEntryForHash) {
|
||||
const headingForHash = document.querySelector(hash);
|
||||
if (headingForHash && isInViewport(headingForHash)) {
|
||||
setTocEntry(tocEntryForHash);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let newEntry = null;
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.intersectionRatio > 0) {
|
||||
const heading = entry.target;
|
||||
/*
|
||||
This sidebar nav consists of two sections:
|
||||
* at the top, a sitenav containing links to other pages
|
||||
* under that, the ToC for the current page
|
||||
|
||||
Since the sidebar scrolls to match the document position,
|
||||
the sitenav will tend to scroll off the screen.
|
||||
|
||||
If the user has scrolled up to (or near) the top of the page,
|
||||
we want to show the sitenav so.
|
||||
|
||||
So: if the H1 (title) for the current page has started
|
||||
intersecting, then always scroll the sidebar back to the top.
|
||||
*/
|
||||
if (heading.tagName === "H1" && heading.parentNode.tagName === "DIV") {
|
||||
const nav = document.querySelector("#td-section-nav");
|
||||
nav.scrollTop = 0;
|
||||
return;
|
||||
}
|
||||
/*
|
||||
Otherwise, get the ToC entry for the first entry that
|
||||
entered the viewport, if there was one.
|
||||
*/
|
||||
const id = entry.target.getAttribute('id');
|
||||
let tocEntry = document.querySelector(`nav li a[href="#${id}"]`);
|
||||
// if the id isn't a direct match for a ToC item,
|
||||
// check the ToC entry's `data-*` attributes
|
||||
if (!tocEntry) {
|
||||
tocEntry = document.querySelector(`nav li a[data-${id}]`);
|
||||
}
|
||||
if (tocEntry && !newEntry) {
|
||||
newEntry = tocEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newEntry) {
|
||||
setTocEntry(newEntry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Track when headings enter the viewport, and use this to update the highlight
|
||||
for the corresponding ToC entry.
|
||||
Track when the view is scrolled, and use this to update the highlight for the
|
||||
corresponding ToC entry.
|
||||
*/
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// Part of the viewport is below the header so we should take it into account.
|
||||
const headerOffset = document.querySelector("body > header > nav").clientHeight;
|
||||
const headings = getHeadings();
|
||||
|
||||
const toc = document.querySelector("#toc");
|
||||
toc.addEventListener("click", event => {
|
||||
if (event.target.tagName === "A") {
|
||||
setTocEntry(event.target);
|
||||
}
|
||||
const onScroll = throttle((_e) => {
|
||||
// Update the ToC.
|
||||
let heading = getCurrentHeading(headings, headerOffset);
|
||||
selectTocEntry(heading.id);
|
||||
});
|
||||
|
||||
const observer = new IntersectionObserver(handleIntersectionUpdate);
|
||||
|
||||
document.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((section) => {
|
||||
observer.observe(section);
|
||||
});
|
||||
// Initialize the state of the ToC.
|
||||
onScroll();
|
||||
|
||||
// Listen to scroll and resizing changes.
|
||||
document.addEventListener('scroll', onScroll, false);
|
||||
document.addEventListener('resize', onScroll, false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Correct OpenAPI specification for query parameters to `GET /_matrix/client/v3/thirdparty/location/{protocol}` endpoint.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Clarify formats of string types.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Clarify formats of string types.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Clarify formats of string types.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Clarify that the async upload endpoint will return 404 in some cases.
|
||||
1
changelogs/internal/newsfragments/1991.clarification
Normal file
1
changelogs/internal/newsfragments/1991.clarification
Normal file
|
|
@ -0,0 +1 @@
|
|||
Improve the JS script to highlight the current ToC entry.
|
||||
1
changelogs/push_gateway/newsfragments/1974.clarification
Normal file
1
changelogs/push_gateway/newsfragments/1974.clarification
Normal file
|
|
@ -0,0 +1 @@
|
|||
The path of HTTP pusher URLs is fixed to `/_matrix/push/v1/notify`.
|
||||
|
|
@ -126,6 +126,25 @@ paths:
|
|||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Cannot upload this content"
|
||||
}
|
||||
"404":
|
||||
description: |-
|
||||
The user has provided an invalid MXC ID. Some reasons for this error include:
|
||||
|
||||
- The MXC ID was not created with [POST /_matrix/media/v1/create](/client-server-api/#post_matrixmediav1create).
|
||||
- The MXC ID has expired.
|
||||
|
||||
A [standard error response](/client-server-api/#standard-error-response)
|
||||
will be returned with the `errcode` `M_NOT_FOUND`.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: definitions/errors/error.yaml
|
||||
examples:
|
||||
response:
|
||||
value: {
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Unknown media ID"
|
||||
}
|
||||
"409":
|
||||
description: |-
|
||||
The endpoint was called with a media ID that already has content. A
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ properties:
|
|||
arbitrary string with no specified maximum length.
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
description: |
|
||||
A link to the text of this document, in the appropriate
|
||||
language. MUST be a valid URI with scheme `https://` or
|
||||
|
|
|
|||
|
|
@ -172,6 +172,8 @@ paths:
|
|||
properties:
|
||||
user_id:
|
||||
type: string
|
||||
format: mx-user-id
|
||||
pattern: "^@"
|
||||
description: The fully-qualified Matrix ID for the account.
|
||||
access_token:
|
||||
type: string
|
||||
|
|
@ -197,6 +199,7 @@ paths:
|
|||
x-addedInMatrixVersion: "1.3"
|
||||
home_server:
|
||||
type: string
|
||||
format: mx-server-name
|
||||
deprecated: true
|
||||
description: |-
|
||||
The server_name of the homeserver on which the account has
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ paths:
|
|||
properties:
|
||||
matrix_id:
|
||||
type: string
|
||||
format: mx-user-id
|
||||
pattern: "^@"
|
||||
description: |-
|
||||
A [Matrix User ID](/appendices/#user-identifiers)
|
||||
representing the administrator.
|
||||
|
|
@ -66,6 +68,7 @@ paths:
|
|||
required.
|
||||
email_address:
|
||||
type: string
|
||||
format: email
|
||||
description: |-
|
||||
An email address to reach the administrator.
|
||||
|
||||
|
|
@ -95,6 +98,7 @@ paths:
|
|||
}
|
||||
support_page:
|
||||
type: string
|
||||
format: uri
|
||||
description: |-
|
||||
The URL of a page to give users help specific to the
|
||||
homeserver, like extra login/registration steps.
|
||||
|
|
|
|||
|
|
@ -98,12 +98,14 @@ paths:
|
|||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchFields
|
||||
name: fields
|
||||
description: |-
|
||||
One or more custom fields to help identify the third-party
|
||||
location.
|
||||
schema:
|
||||
type: string
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: At least one portal room was found.
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ paths:
|
|||
updating counts of unread notifications should be idempotent and
|
||||
therefore do not require duplicate suppression.
|
||||
|
||||
Notifications are sent to the URL configured when the pusher is created.
|
||||
This means that the HTTP path may be different depending on the push
|
||||
gateway.
|
||||
Clients interested in receiving notifications via this endpoint MUST
|
||||
configure its full URI when creating the associated pusher via
|
||||
[`/_matrix/client/v3/pushers/set`](/client-server-api/#post_matrixclientv3pushersset).
|
||||
operationId: notify
|
||||
requestBody:
|
||||
content:
|
||||
|
|
|
|||
|
|
@ -64,3 +64,7 @@ mx-mxc-uri:
|
|||
uri:
|
||||
title: URI
|
||||
url: https://datatracker.ietf.org/doc/html/rfc3986
|
||||
|
||||
email:
|
||||
title: Email Address
|
||||
url: https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.2
|
||||
|
|
|
|||
|
|
@ -2,15 +2,8 @@
|
|||
|
||||
This template is included at the end of each page's `<body>`.
|
||||
|
||||
We're using it here to:
|
||||
|
||||
1) include the JS that generates the table of contents. It would be better
|
||||
to generate the table of contents as part of the Hugo build process, but
|
||||
that doesn't work nicely with the way we want to author client-server modules
|
||||
as separate files.
|
||||
|
||||
2) highlight and scroll the ToC in the sidebar to match the place we are at
|
||||
in the document.
|
||||
We're using it here to highlight and scroll the ToC in the sidebar to match
|
||||
the place we are at in the document.
|
||||
|
||||
*/}}
|
||||
{{ $toc := resources.Get "js/toc.js" -}}
|
||||
|
|
|
|||
Loading…
Reference in a new issue