diff --git a/assets/scss/_variables_project.scss b/assets/scss/_variables_project.scss
index 99f8112e..e8fb3633 100644
--- a/assets/scss/_variables_project.scss
+++ b/assets/scss/_variables_project.scss
@@ -1,3 +1,19 @@
+/*
+Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
$primary: #FFF;
$secondary: #0098D4;
$dark: #333;
diff --git a/assets/scss/custom.scss b/assets/scss/custom.scss
index 877b8b9e..13676acb 100644
--- a/assets/scss/custom.scss
+++ b/assets/scss/custom.scss
@@ -1,3 +1,19 @@
+/*
+Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
/*
Custom SCSS for the Matrix spec
*/
diff --git a/static/js/toc.js b/static/js/toc.js
index 2c5b5f1f..fa36ba4a 100644
--- a/static/js/toc.js
+++ b/static/js/toc.js
@@ -1,301 +1,314 @@
- /*
- 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 != "");
+/*
+Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+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();
}
-
- /*
- 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;
+ 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;
}
-
- /*
- 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");
}
}
-
- /*
- 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);
+ // append title and content to the #toc placeholder
+ const toc = document.body.querySelector("#toc");
+ toc.appendChild(title);
+ toc.appendChild(nav);
+
+ // 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 `
` element.
+ToC entries are `- ` 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;
}
}
-
- /*
- 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);
- // append title and content to the #toc placeholder
- const toc = document.body.querySelector("#toc");
- toc.appendChild(title);
- toc.appendChild(nav);
-
- // tell ToC items about any rendered-data headings they contain
- setTocItemChildren(section.content, headings);
+ for (let li of lis) {
+ ol.appendChild(li);
}
-
- // 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;
+ return {
+ content: ol,
+ index: i
+ }
+}
+
+/*
+Set a new ToC entry.
+Clear any previously highlighted ToC items, set the new one,
+and adjust the ToC scroll position.
+*/
+function setTocEntry(newEntry) {
+ const activeEntries = document.querySelectorAll("#toc a.active");
+ for (const activeEntry of activeEntries) {
+ activeEntry.classList.remove('active');
}
- /*
- Each ToC section is an `
` element.
- ToC entries are `- ` 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
- }
+ newEntry.classList.add('active');
+ // 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) {
+ nav.scrollTop = newEntry.offsetTop - 100;
+ } else {
+ nav.scrollTop = 0;
}
+}
+
+/*
+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) {
/*
- Set a new ToC entry.
- Clear any previously highlighted ToC items, set the new one,
- and adjust the ToC scroll position.
+ 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.
*/
- function setTocEntry(newEntry) {
- const activeEntries = document.querySelectorAll("#toc a.active");
- for (const activeEntry of activeEntries) {
- activeEntry.classList.remove('active');
+ 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}]`);
}
-
- newEntry.classList.add('active');
-
- // 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) {
- nav.scrollTop = newEntry.offsetTop - 100;
- } else {
- nav.scrollTop = 0;
+ if (tocEntryForHash) {
+ const headingForHash = document.querySelector(hash);
+ if (headingForHash && isInViewport(headingForHash)) {
+ setTocEntry(tocEntryForHash);
+ return;
+ }
}
}
- /*
- 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}]`);
+ 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;
}
- if (tocEntryForHash) {
- const headingForHash = document.querySelector(hash);
- if (headingForHash && isInViewport(headingForHash)) {
- setTocEntry(tocEntryForHash);
- 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}]`);
}
- }
-
- 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 (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.
- */
- window.addEventListener('DOMContentLoaded', () => {
+ if (newEntry) {
+ setTocEntry(newEntry);
+ return;
+ }
+}
- makeToc();
+/*
+Track when headings enter the viewport, and use this to update the highlight
+for the corresponding ToC entry.
+*/
+window.addEventListener('DOMContentLoaded', () => {
- const toc = document.querySelector("#toc");
- toc.addEventListener("click", event => {
- if (event.target.tagName === "A") {
- setTocEntry(event.target);
- }
- });
+ makeToc();
- const observer = new IntersectionObserver(handleIntersectionUpdate);
+ const toc = document.querySelector("#toc");
+ toc.addEventListener("click", event => {
+ if (event.target.tagName === "A") {
+ setTocEntry(event.target);
+ }
+ });
- document.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((section) => {
- observer.observe(section);
- });
+ const observer = new IntersectionObserver(handleIntersectionUpdate);
+ document.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((section) => {
+ observer.observe(section);
});
+
+});