Add copyright statements to SCSS and JS; fix indentation for JS

pull/977/head
Will 4 years ago
parent 1dadff5701
commit 9b2d9cf6b7
No known key found for this signature in database
GPG Key ID: 385872BB265E8BF8

@ -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; $primary: #FFF;
$secondary: #0098D4; $secondary: #0098D4;
$dark: #333; $dark: #333;

@ -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 Custom SCSS for the Matrix spec
*/ */

@ -1,301 +1,314 @@
/* /*
Account for id attributes that are in the sidebar nav Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
*/
function populateIds() { Licensed under the Apache License, Version 2.0 (the "License");
const navItems = document.querySelectorAll(".td-sidebar-nav li"); you may not use this file except in compliance with the License.
return Array.from(navItems).map(item => item.id).filter(id => id != ""); 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();
} }
return 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. /*
*/ Given an array of heading nodes, ensure they all have unique IDs.
function uniquifyHeadingId(id, uniqueIDs) {
const baseId = id; We have to do this mostly because of client-server modules, which are
let counter = 0; rendered separately then glued together with a template.
while (uniqueIDs.includes(id)) { Because heading IDs are generated in rendering, this means they can and will
counter = counter + 1; end up with duplicate IDs.
id = baseId + "-" + counter.toString(); */
} function uniquifyHeadingIds(headings) {
return id; 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. /*
The document contains "normal" headings, and these have corresponding items
We have to do this mostly because of client-server modules, which are in the ToC.
rendered separately then glued together with a template.
Because heading IDs are generated in rendering, this means they can and will The document might also contain H1 headings that act as titles for blocks of
end up with duplicate IDs. 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
function uniquifyHeadingIds(headings) { links to them. When someone follows a link to one of these "rendered data"
const uniqueIDs = populateIds(); headings we want to scroll the ToC to the item corresponding to the "normal"
for (let heading of headings) { heading preceding the "rendered data" heading we have visited.
const uniqueID = uniquifyHeadingId(heading.id, uniqueIDs);
uniqueIDs.push(uniqueID); To support this we need to add `data` attributes to ToC items.
heading.id = uniqueID; 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. Generate a table of contents based on the headings in the document.
*/
The document might also contain H1 headings that act as titles for blocks of function makeToc() {
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 // make the title from the H1
links to them. When someone follows a link to one of these "rendered data" const h1 = document.body.querySelector("h1");
headings we want to scroll the ToC to the item corresponding to the "normal" const title = document.createElement("a");
heading preceding the "rendered data" heading we have visited. title.id = "toc-title";
title.setAttribute("href", "#");
To support this we need to add `data` attributes to ToC items. title.textContent = h1.textContent;
These attributes identify which "rendered data" headings live underneath
the heading corresponding to that ToC item. // make the content
*/ const content = document.body.querySelector(".td-content");
function setTocItemChildren(toc, headings) { let headings = [].slice.call(content.querySelectorAll("h2, h3, h4, h5, h6, .rendered-data > details > summary > h1"));
let tocEntryForHeading = null;
for (const heading of headings) { // exclude headings that don't have IDs.
// H1 headings are rendered-data headings headings = headings.filter(heading => heading.id);
if (heading.tagName !== "H1") { uniquifyHeadingIds(headings);
tocEntryForHeading = document.querySelector(`nav li a[href="#${heading.id}"]`);
} else { // exclude .rendered-data > h1 headings from the ToC
// on the ToC entry for the parent heading, const tocTargets = headings.filter(heading => heading.tagName !== "H1");
// set a data-* attribute whose name is the child's fragment ID
tocEntryForHeading.setAttribute(`data-${heading.id}`, "true"); // 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 `<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);
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);
} }
return {
// create a single ToC entry content: ol,
function makeTocEntry(heading) { index: i
const li = document.createElement("li"); }
const a = document.createElement("a"); }
a.setAttribute("href", `#${heading.id}`);
a.textContent = heading.textContent; /*
li.appendChild(a); Set a new ToC entry.
return li; 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');
} }
/* newEntry.classList.add('active');
Each ToC section is an `<ol>` element. // don't scroll the sidebar nav if the main content is not scrolled
ToC entries are `<li>` elements and these contain nested ToC sections, const nav = document.querySelector("#td-section-nav");
whenever we go to the next heading level down. const content = document.querySelector("html");
*/ if (content.scrollTop !== 0) {
function makeTocSection(headings, index) { nav.scrollTop = newEntry.offsetTop - 100;
const ol = document.createElement("ol"); } else {
let previousHeading = null; nav.scrollTop = 0;
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
}
} }
}
/*
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. Special case: If the current URL hash matches a ToC entry, and
Clear any previously highlighted ToC items, set the new one, the corresponding heading is visible in the viewport, then that is
and adjust the ToC scroll position. 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 hash = document.location.hash;
const activeEntries = document.querySelectorAll("#toc a.active"); if (hash) {
for (const activeEntry of activeEntries) { let tocEntryForHash = document.querySelector(`nav li a[href="${hash}"]`);
activeEntry.classList.remove('active'); // 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) {
newEntry.classList.add('active'); const headingForHash = document.querySelector(hash);
if (headingForHash && isInViewport(headingForHash)) {
// don't scroll the sidebar nav if the main content is not scrolled setTocEntry(tocEntryForHash);
const nav = document.querySelector("#td-section-nav"); return;
const content = document.querySelector("html"); }
if (content.scrollTop !== 0) {
nav.scrollTop = newEntry.offsetTop - 100;
} else {
nav.scrollTop = 0;
} }
} }
/* let newEntry = null;
Test whether a node is in the viewport
*/ for (const entry of entries) {
function isInViewport(node) { if (entry.intersectionRatio > 0) {
const rect = node.getBoundingClientRect(); const heading = entry.target;
/*
return ( This sidebar nav consists of two sections:
rect.top >= 0 && * at the top, a sitenav containing links to other pages
rect.left >= 0 && * under that, the ToC for the current page
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth) 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.
The callback we pass to the IntersectionObserver constructor.
So: if the H1 (title) for the current page has started
Called when any of our observed nodes starts or stops intersecting intersecting, then always scroll the sidebar back to the top.
with the viewport. */
*/ if (heading.tagName === "H1" && heading.parentNode.tagName === "DIV") {
function handleIntersectionUpdate(entries) { const nav = document.querySelector("#td-section-nav");
nav.scrollTop = 0;
/* return;
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); Otherwise, get the ToC entry for the first entry that
if (headingForHash && isInViewport(headingForHash)) { entered the viewport, if there was one.
setTocEntry(tocEntryForHash); */
return; 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;
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;
}
} }
/* if (newEntry) {
Track when headings enter the viewport, and use this to update the highlight setTocEntry(newEntry);
for the corresponding ToC entry. return;
*/ }
window.addEventListener('DOMContentLoaded', () => { }
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"); makeToc();
toc.addEventListener("click", event => {
if (event.target.tagName === "A") {
setTocEntry(event.target);
}
});
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) => { const observer = new IntersectionObserver(handleIntersectionUpdate);
observer.observe(section);
});
document.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((section) => {
observer.observe(section);
}); });
});

Loading…
Cancel
Save