blob: 76edae307392eaa3b59cbfba82ac4e4fac9a64c4 [file] [edit]
// Copyright (c) 2009 Giampaolo Rodola. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Blog comments, backed by giscus + GitHub Discussions.
(function () {
const container = document.querySelector(".giscus");
if (!container) {
return;
}
const ORIGIN = "https://giscus.app";
const section = container.closest(".comments");
const url = new URL(container.dataset.themeUrl, location.href).href;
let sheet = null;
let ready = false;
let pushed = null;
function isDark() {
return document.documentElement.getAttribute("data-theme") === "dark";
}
// css/giscus.css branches on prefers-color-scheme, which inside
// giscus' iframe follows the reader's OS rather than our toggle.
// Swap the conditions for ones that are always (or never) true so
// the branch we want is the one that applies.
function themeFor(dark) {
const yes = "(min-width: 0px)";
const no = "(min-width: 99999px)";
const css = sheet
.replace(/\(prefers-color-scheme:\s*dark\)/g, dark ? yes : no)
.replace(/\(prefers-color-scheme:\s*light\)/g, dark ? no : yes);
return "data:text/css;base64," + btoa(css);
}
function inject() {
const attrs = {
"data-repo": container.dataset.repo,
"data-repo-id": container.dataset.repoId,
"data-category": container.dataset.category,
"data-category-id": container.dataset.categoryId,
// Thread key is the source path, not the URL.
"data-mapping": "specific",
"data-term": container.dataset.term,
// Without this giscus matches by fuzzy title search,
// and our terms all share a "blog/<year>/" prefix.
"data-strict": "1",
"data-reactions-enabled": "1",
"data-emit-metadata": "0",
"data-input-position": "top",
"data-theme": sheet ? themeFor(isDark()) : url,
"data-lang": "en",
"data-loading": "lazy",
};
pushed = isDark();
const script = document.createElement("script");
script.src = ORIGIN + "/client.js";
script.crossOrigin = "anonymous";
script.async = true;
Object.keys(attrs).forEach((name) => {
script.setAttribute(name, attrs[name]);
});
// A blocked script fires "error", not "load", so the section
// stays hidden rather than showing an empty widget.
script.addEventListener("load", () => {
if (section) {
section.removeAttribute("hidden");
}
});
container.appendChild(script);
}
function push() {
if (!ready || !sheet || isDark() === pushed) {
return;
}
const frame = document.querySelector("iframe.giscus-frame");
if (!frame || !frame.contentWindow) {
return;
}
const dark = isDark();
frame.contentWindow.postMessage(
{ giscus: { setConfig: { theme: themeFor(dark) } } },
ORIGIN,
);
pushed = dark;
}
new MutationObserver(push).observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});
// contentWindow exists well before giscus can receive messages,
// so wait for it to talk to us first.
window.addEventListener("message", (event) => {
if (event.origin !== ORIGIN || !event.data || !event.data.giscus) {
return;
}
ready = true;
push();
});
// giscus fetches its theme with crossorigin="anonymous", which
// needs CORS headers and is blocked outright from localhost.
// Inlining it sidesteps both: this fetch is same-origin.
fetch(url)
.then((resp) => {
if (!resp.ok) {
throw new Error(resp.status);
}
return resp.text();
})
.then((css) => {
sheet = css;
})
.catch((err) => {
// Fall back to the URL, fine wherever CORS allows it.
console.warn("giscus: inlining theme failed:", err);
})
.finally(inject);
})();