summary refs log tree commit diff stats
path: root/src/themes/serene/static/js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/themes/serene/static/js/lightense.min.js2
-rw-r--r--src/themes/serene/static/js/main.js259
2 files changed, 261 insertions, 0 deletions
diff --git a/src/themes/serene/static/js/lightense.min.js b/src/themes/serene/static/js/lightense.min.js
new file mode 100644
index 0000000..1f979a6
--- /dev/null
+++ b/src/themes/serene/static/js/lightense.min.js
@@ -0,0 +1,2 @@
+/*! lightense-images v1.0.17 | © Tunghsiao Liu | MIT */
+!function (e, t) { "object" == typeof exports && "object" == typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.Lightense = t() : e.Lightense = t() }(this, (function () { return e = { 352: e => { function t(e, t) { var n = Object.keys(e); if (Object.getOwnPropertySymbols) { var r = Object.getOwnPropertySymbols(e); t && (r = r.filter((function (t) { return Object.getOwnPropertyDescriptor(e, t).enumerable }))), n.push.apply(n, r) } return n } function n(e) { for (var n = 1; n < arguments.length; n++) { var i = null != arguments[n] ? arguments[n] : {}; n % 2 ? t(Object(i), !0).forEach((function (t) { r(e, t, i[t]) })) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(i)) : t(Object(i)).forEach((function (t) { Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(i, t)) })) } return e } function r(e, t, n) { return t in e ? Object.defineProperty(e, t, { value: n, enumerable: !0, configurable: !0, writable: !0 }) : e[t] = n, e } function i(e) { return (i = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (e) { return typeof e } : function (e) { return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e })(e) } var o = function () { "use strict"; var e, t = { time: 300, padding: 40, offset: 40, keyboard: !0, cubicBezier: "cubic-bezier(.2, 0, .1, 1)", background: "var(--bg-color-80, rgba(255, 255, 255, .98))", zIndex: 1e6, beforeShow: void 0, afterShow: void 0, beforeHide: void 0, afterHide: void 0 }, r = {}; function o(e) { var t = r[e]; if (t) { if ("function" != typeof t) throw "config.".concat(e, " must be a function!"); Reflect.apply(t, r, [r]) } } function a(e) { switch (i(e)) { case "undefined": throw "You need to pass an element!"; case "string": return document.querySelectorAll(e); case "object": return e } } function c(e) { var t = e.length; if (t) for (var n = 0; n < t; n++)s(e[n]); else s(e) } function s(e) { e.src && !e.classList.contains("lightense-target") && (e.classList.add("lightense-target"), e.addEventListener("click", (function (i) { if (r.keyboard && (i.metaKey || i.ctrlKey)) return window.open(e.src, "_blank"); !function (e) { if (r.target = e, r.target.classList.contains("lightense-open")) return g(); o("beforeShow"), r.scrollY = window.scrollY, function (e, t, n) { e.addEventListener(t, (function r(i) { Reflect.apply(n, this, i), e.removeEventListener(t, r) })) }(r.target, "transitionend", (function () { o("afterShow") })); var i = new Image; i.onload = function () { !function (e) { var n = e.width, i = e.height, o = window.pageYOffset || document.documentElement.scrollTop || 0, a = window.pageXOffset || document.documentElement.scrollLeft || 0, c = r.target.getBoundingClientRect(), s = n / c.width, d = window.innerWidth || document.documentElement.clientWidth || 0, l = window.innerHeight || document.documentElement.clientHeight || 0, u = r.target.getAttribute("data-lightense-padding") || r.target.getAttribute("data-padding") || r.padding, g = d > u ? d - u : d - t.padding, p = l > u ? l - u : l - t.padding, f = n / i, b = g / p; r.scaleFactor = n < g && i < p ? s : f < b ? p / i * s : g / n * s; var h = d / 2, m = o + l / 2, v = c.left + a + c.width / 2, y = c.top + o + c.height / 2; r.translateX = Math.round(h - v), r.translateY = Math.round(m - y) }(this), function () { r.target.classList.add("lightense-open"), r.wrap = document.createElement("div"), r.wrap.className = "lightense-wrap", setTimeout((function () { r.target.style.transform = "scale(" + r.scaleFactor + ")" }), 20), r.target.parentNode.insertBefore(r.wrap, r.target), r.wrap.appendChild(r.target), setTimeout((function () { r.wrap.style.transform = "translate3d(" + r.translateX + "px, " + r.translateY + "px, 0)" }), 20); var e = { cubicBezier: r.target.getAttribute("data-lightense-cubic-bezier") || r.cubicBezier, background: r.target.getAttribute("data-lightense-background") || r.target.getAttribute("data-background") || r.background, zIndex: r.target.getAttribute("data-lightense-z-index") || r.zIndex }, t = n(n({}, r), e); d("lightense-images-css-computed", "\n    :root {\n      --lightense-z-index: ".concat(t.zIndex - 1, ";\n      --lightense-backdrop: ").concat(t.background, ";\n      --lightense-duration: ").concat(t.time, "ms;\n      --lightense-timing-func: ").concat(t.cubicBezier, ";\n    }")), r.container.style.visibility = "visible", setTimeout((function () { r.container.style.opacity = "1" }), 20) }(), window.addEventListener("keyup", f, !1), window.addEventListener("scroll", p, !1), r.container.addEventListener("click", g, !1) }, i.src = r.target.src }(this) }), !1)) } function d(e, t) { var n = document.head || document.getElementsByTagName("head")[0]; document.getElementById(e) && document.getElementById(e).remove(); var r = document.createElement("style"); r.id = e, r.styleSheet ? r.styleSheet.cssText = t : r.appendChild(document.createTextNode(t)), n.appendChild(r) } function l() { d("lightense-images-css", "\n:root {\n  --lightense-z-index: ".concat(r.zIndex - 1, ";\n  --lightense-backdrop: ").concat(r.background, ";\n  --lightense-duration: ").concat(r.time, "ms;\n  --lightense-timing-func: ").concat(r.cubicBezier, ";\n}\n\n.lightense-backdrop {\n  box-sizing: border-box;\n  width: 100%;\n  height: 100%;\n  position: fixed;\n  top: 0;\n  left: 0;\n  overflow: hidden;\n  z-index: calc(var(--lightense-z-index) - 1);\n  padding: 0;\n  margin: 0;\n  transition: opacity var(--lightense-duration) ease;\n  cursor: zoom-out;\n  opacity: 0;\n  background-color: var(--lightense-backdrop);\n  visibility: hidden;\n}\n\n@supports (-webkit-backdrop-filter: blur(30px)) {\n  .lightense-backdrop {\n    background-color: var(--lightense-backdrop);\n    -webkit-backdrop-filter: blur(30px);\n  }\n}\n\n@supports (backdrop-filter: blur(30px)) {\n  .lightense-backdrop {\n    background-color: var(--lightense-backdrop);\n    backdrop-filter: blur(30px);\n  }\n}\n\n.lightense-wrap {\n  position: relative;\n  transition: transform var(--lightense-duration) var(--lightense-timing-func);\n  z-index: var(--lightense-z-index);\n  pointer-events: none;\n}\n\n.lightense-target {\n  cursor: zoom-in;\n  transition: transform var(--lightense-duration) var(--lightense-timing-func);\n  pointer-events: auto;\n}\n\n.lightense-open {\n  cursor: zoom-out;\n}\n\n.lightense-transitioning {\n  pointer-events: none;\n}")) } function u() { null === document.querySelector(".lightense-backdrop") ? (r.container = document.createElement("div"), r.container.className = "lightense-backdrop", document.body.appendChild(r.container)) : r.container = document.querySelector(".lightense-backdrop") } function g() { o("beforeHide"), window.removeEventListener("keyup", f, !1), window.removeEventListener("scroll", p, !1), r.container.removeEventListener("click", g, !1), r.target.classList.remove("lightense-open"), r.wrap.style.transform = "", r.target.style.transform = "", r.target.classList.add("lightense-transitioning"), r.container.style.opacity = "", setTimeout((function () { o("afterHide"), r.container.style.visibility = "", r.container.style.backgroundColor = "", r.wrap.parentNode.replaceChild(r.target, r.wrap), r.target.classList.remove("lightense-transitioning") }), r.time) } function p() { Math.abs(r.scrollY - window.scrollY) >= r.offset && g() } function f(e) { e.preventDefault(), 27 === e.keyCode && g() } return function (i) { var o = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}; e = a(i), r = n(n({}, t), o), l(), u(), c(e) } }(); e.exports = o } }, t = {}, function n(r) { var i = t[r]; if (void 0 !== i) return i.exports; var o = t[r] = { exports: {} }; return e[r](o, o.exports, n), o.exports }(352); var e, t }));
\ No newline at end of file
diff --git a/src/themes/serene/static/js/main.js b/src/themes/serene/static/js/main.js
new file mode 100644
index 0000000..b0aee0c
--- /dev/null
+++ b/src/themes/serene/static/js/main.js
@@ -0,0 +1,259 @@
+function enableThemeToggle() {
+  const themeToggle = document.querySelector('#theme-toggle');
+  if (!themeToggle) return;
+  const hlLink = document.querySelector('link#hl');
+  const preferDark = window.matchMedia("(prefers-color-scheme: dark)");
+  function toggleTheme(theme) {
+    if (theme == "dark") document.body.classList.add('dark'); else document.body.classList.remove('dark');
+    if (hlLink) hlLink.href = `/hl-${theme}.css`;
+    sessionStorage.setItem("theme", theme);
+    toggleGiscusTheme(theme);
+  }
+  function toggleGiscusTheme(theme) {
+    const iframe = document.querySelector('iframe.giscus-frame');
+    if (iframe) iframe.contentWindow.postMessage({ giscus: { setConfig: { theme: `${location.origin}/giscus_${theme}.css` } } }, 'https://giscus.app');
+  }
+  function initGiscusTheme(evt) {
+    if (evt.origin !== 'https://giscus.app') return;
+    if (!(typeof evt.data === 'object' && evt.data.giscus)) return;
+    toggleGiscusTheme(sessionStorage.getItem("theme") || (preferDark.matches ? "dark" : "light"));
+    window.removeEventListener('message', initGiscusTheme);
+  }
+  window.addEventListener('message', initGiscusTheme);
+  themeToggle.addEventListener('click', () => toggleTheme(sessionStorage.getItem("theme") == "dark" ? "light" : "dark"));
+  preferDark.addEventListener("change", e => toggleTheme(e.matches ? "dark" : "light"));
+  if (!sessionStorage.getItem("theme") && preferDark.matches) toggleTheme("dark");
+  if (sessionStorage.getItem("theme") == "dark") toggleTheme("dark");
+}
+
+function enablePrerender() {
+  const prerender = (a) => {
+    if (!a.classList.contains('instant')) return;
+    const script = document.createElement('script');
+    script.type = 'speculationrules';
+    script.textContent = JSON.stringify({ prerender: [{ source: 'list', urls: [a.href] }] });
+    document.body.append(script);
+    a.classList.remove('instant');
+  }
+  const prefetch = (a) => {
+    if (!a.classList.contains('instant')) return;
+    const link = document.createElement('link');
+    link.rel = 'prefetch';
+    link.href = a.href;
+    document.head.append(link);
+    a.classList.remove('instant');
+  }
+  const support = HTMLScriptElement.supports && HTMLScriptElement.supports('speculationrules');
+  const handle = support ? prerender : prefetch;
+  document.querySelectorAll('a.instant').forEach(a => {
+    if (a.href.endsWith(window.location.pathname)) return;
+    let timer;
+    a.addEventListener('mouseenter', () => {
+      timer = setTimeout(() => handle(a), 50);
+    });
+    a.addEventListener('mouseleave', () => clearTimeout(timer));
+    a.addEventListener('touchstart', () => handle(a), { passive: true });
+  });
+}
+
+function enableRssMask() {
+  const rssBtn = document.querySelector('#rss-btn');
+  const mask = document.querySelector('#rss-mask');
+  const copyBtn = document.querySelector('#rss-mask button');
+  if (!rssBtn || !mask) return;
+  rssBtn.addEventListener('click', (e) => {
+    e.preventDefault();
+    mask.showModal();
+  });
+  const close = (e) => {
+    if (e.target == mask) mask.close();
+  };
+  mask.addEventListener('click', close);
+  const copy = () => {
+    navigator.clipboard.writeText(copyBtn.dataset.link).then(() => {
+      copyBtn.innerHTML = copyBtn.dataset.checkIcon;
+      copyBtn.classList.add('copied');
+      copyBtn.removeEventListener('click', copy);
+      setTimeout(() => {
+        mask.close();
+        copyBtn.innerHTML = copyBtn.dataset.copyIcon;
+        copyBtn.classList.remove('copied');
+        copyBtn.addEventListener('click', copy);
+      }, 400);
+    });
+  }
+  copyBtn.addEventListener('click', copy);
+}
+
+function enableOutdateAlert() {
+  const alert = document.querySelector('#outdate_alert');
+  if (!alert) return;
+  const publish = document.querySelector('#publish');
+  const updated = document.querySelector('#updated');
+  const updateDate = new Date(updated ? updated.textContent : publish.textContent);
+  const intervalDays = Math.floor((Date.now() - updateDate.getTime()) / (24 * 60 * 60 * 1000));
+  const alertDays = parseInt(alert.dataset.days);
+  if (intervalDays >= alertDays) {
+    const msg = alert.dataset.alertTextBefore + intervalDays + alert.dataset.alertTextAfter;
+    alert.querySelector('.content').textContent = msg;
+    alert.classList.remove('hidden');
+  }
+}
+
+function enableTocTooltip() {
+  const anchors = document.querySelectorAll('aside nav a');
+  if (anchors.length == 0) return;
+  const toggleTooltip = () => {
+    anchors.forEach(anchor => {
+      if (anchor.offsetWidth < anchor.scrollWidth) {
+        anchor.setAttribute('title', anchor.textContent);
+      } else {
+        anchor.removeAttribute('title');
+      }
+    });
+  };
+  window.addEventListener('resize', toggleTooltip);
+  toggleTooltip();
+}
+
+function addCopyBtns() {
+  const cfg = document.querySelector('#copy-cfg');
+  if (!cfg) return;
+  const copyIcon = cfg.dataset.copyIcon;
+  const checkIcon = cfg.dataset.checkIcon;
+  document.querySelectorAll('pre').forEach(block => {
+    if (block.classList.contains('mermaid')) return;
+    const wrapper = document.createElement('div');
+    wrapper.className = 'codeblock';
+    const btn = document.createElement('button');
+    btn.className = 'copy';
+    btn.ariaLabel = 'copy';
+    btn.innerHTML = copyIcon;
+    const copy = () => {
+      navigator.clipboard.writeText(block.textContent).then(() => {
+        btn.innerHTML = checkIcon;
+        btn.classList.add('copied');
+        btn.removeEventListener('click', copy);
+        setTimeout(() => {
+          btn.innerHTML = copyIcon;
+          btn.classList.remove('copied');
+          btn.addEventListener('click', copy);
+        }, 1500);
+      });
+    };
+    btn.addEventListener('click', copy);
+    wrapper.appendChild(block.cloneNode(true));
+    wrapper.appendChild(btn);
+    block.replaceWith(wrapper);
+  });
+}
+
+function addBackToTopBtn() {
+  const backBtn = document.querySelector('#back-to-top');
+  if (!backBtn) return;
+  const toTop = () => window.scrollTo({ top: 0 });
+  const toggle = () => {
+    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
+    if (scrollTop > 200 && !backBtn.classList.contains('shown')) {
+      backBtn.classList.add('shown');
+      backBtn.setAttribute('tabindex', 0);
+      backBtn.addEventListener('click', toTop);
+    } else if (scrollTop <= 200 && backBtn.classList.contains('shown')) {
+      backBtn.classList.remove('shown');
+      backBtn.setAttribute('tabindex', -1);
+      backBtn.removeEventListener('click', toTop);
+    }
+  };
+  window.addEventListener('scroll', toggle);
+  toggle();
+}
+
+function addFootnoteBacklink() {
+  const footnotes = document.querySelectorAll('.footnote-definition');
+  footnotes.forEach(footnote => {
+    const backlink = document.createElement('button');
+    backlink.className = 'backlink';
+    backlink.ariaLabel = 'backlink';
+    backlink.innerHTML = '↩︎';
+    backlink.addEventListener('click', () => window.scrollTo({
+      top: document.querySelector(`.footnote-reference a[href="#${footnote.id}"]`).getBoundingClientRect().top + window.scrollY,
+    }));
+    const lastEl = footnote.lastElementChild || footnote;
+    lastEl.appendChild(backlink);
+  });
+}
+
+function enableImgLightense() {
+  window.addEventListener("load", () => Lightense(".prose img:not(.no-lightense)", { background: 'rgba(43, 43, 43, 0.19)' }));
+}
+
+function enableReaction() {
+  const container = document.querySelector('.reaction');
+  if (!container) return;
+  const endpoint = container.dataset.endpoint;
+  const slug = location.pathname.split('/').filter(Boolean).pop();
+  let state = { error: false, reaction: {} };
+  const render = () => {
+    const btns = Object.entries(state.reaction).map(([emoji, [count, reacted]])=> {
+      const span = document.createElement('span');
+      span.textContent = count;
+      const btn = document.createElement('button');
+      if (reacted) btn.classList.add('reacted');
+      btn.append(emoji, span);
+      btn.onclick = () => toggle(emoji);
+      return btn;
+    });
+    if (state.error) {
+      container.classList.add('error');
+    } else {
+      container.classList.remove('error');
+    }
+    container.replaceChildren(...btns);
+  };
+  const toggle = async (target) => {
+    const [count, reacted] = state.reaction[target];
+    state.reaction[target] = reacted ? [count - 1, false] : [count + 1, true];
+    render();
+    try {
+      const resp = await fetch(endpoint, {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({ slug, target, reacted: !reacted }),
+      });
+      if (resp.status === 200) {
+        error = false;
+      } else {
+        throw new Error();
+      }
+    } catch (err) {
+      state.error = true;
+      state.reaction[target] = [count, reacted];
+      render();
+    }
+  };
+  const init = async () => {
+    const resp = await fetch(`${endpoint}?slug=${slug}`);
+    if (resp.status === 200) {
+      state.reaction = await resp.json();
+      render();
+    }
+  };
+  init();
+}
+
+enableThemeToggle();
+enablePrerender();
+enableRssMask();
+if (document.body.classList.contains('post')) {
+  enableOutdateAlert();
+  addBackToTopBtn();
+  enableTocTooltip();
+}
+if (document.querySelector('.prose')) {
+  addCopyBtns();
+  addFootnoteBacklink();
+  enableImgLightense();
+  enableReaction();
+}