diff --git a/site/package.json b/site/package.json
index d2875153..12872dde 100644
--- a/site/package.json
+++ b/site/package.json
@@ -31,7 +31,6 @@
"markdown-it": "^14.0.0",
"markdown-it-anchor": "^9.2.0",
"markdown-it-container": "^4.0.0",
- "node-fetch": "^3.3.2",
"npm-run-all": "^4.1.5",
"prettier": "^3.4.1",
"tailwindcss": "^3.4.15"
diff --git a/site/src/data/github.js b/site/src/data/github.js
deleted file mode 100644
index 6098bddc..00000000
--- a/site/src/data/github.js
+++ /dev/null
@@ -1,27 +0,0 @@
-const DEFAULT_DATA = { latestVersion: '' }
-let currentData = null
-
-module.exports = async function getLatestReleaseVersion() {
- if (currentData) {
- return currentData
- }
- try {
- const fetch = (await import('node-fetch')).default;
- const res = await fetch(
- 'https://api.github.com/repos/umputun/remark42/releases'
- )
-
- if (!res.ok) {
- throw new Error(`[ERROR] Status: ${res.status}: ${res.statusText}`)
- }
-
- const data = await res.json()
-
- currentData = { latestVersion: data[0].tag_name || '' }
-
- return currentData
- } catch (e) {
- console.error(e.message)
- return DEFAULT_DATA
- }
-}
diff --git a/site/src/data/site.json b/site/src/data/site.json
index d76cd0ed..8e45c6ba 100644
--- a/site/src/data/site.json
+++ b/site/src/data/site.json
@@ -4,6 +4,7 @@
"description": "Remark42 is a self-hosted, lightweight, and simple (yet functional) comment engine, which doesn't spy on users. It can be embedded into blogs, articles or any other place where readers add comments.",
"url": "https://remark42.com",
"githubUrl": "https://github.com/umputun/remark42",
+ "githubApiUrl": "https://api.github.com/repos/umputun/remark42",
"githubBranch": "master",
"remark42Host": "https://demo.remark42.com"
}
diff --git a/site/src/includes/components/head.njk b/site/src/includes/components/head.njk
index 62303099..ce224e47 100644
--- a/site/src/includes/components/head.njk
+++ b/site/src/includes/components/head.njk
@@ -12,7 +12,7 @@
{% endif %}
-
+
{% set js %}
{% include "script.js" %}
{% endset %}
diff --git a/site/src/includes/components/header.njk b/site/src/includes/components/header.njk
index c3ae6d31..fb0f69a1 100644
--- a/site/src/includes/components/header.njk
+++ b/site/src/includes/components/header.njk
@@ -8,8 +8,8 @@
Docs
-
- {{ github.latestVersion }}
+
+
diff --git a/site/src/includes/inline.js b/site/src/includes/inline.js
deleted file mode 100644
index 45ab1bd5..00000000
--- a/site/src/includes/inline.js
+++ /dev/null
@@ -1,6 +0,0 @@
-const mq = window.matchMedia('(prefers-color-scheme: dark)')
-const theme = localStorage.getItem('theme')
-
-if ((theme && theme === 'dark') || (!theme && mq.matches)) {
- document.documentElement.classList.add('dark')
-}
diff --git a/site/src/includes/inline.njk b/site/src/includes/inline.njk
new file mode 100644
index 00000000..136d1a8f
--- /dev/null
+++ b/site/src/includes/inline.njk
@@ -0,0 +1,65 @@
+const mq = window.matchMedia('(prefers-color-scheme: dark)')
+const theme = localStorage.getItem('theme')
+
+if ((theme && theme === 'dark') || (!theme && mq.matches)) {
+ document.documentElement.classList.add('dark')
+}
+
+// fetch the latest release version client-side so the badge tracks releases
+// without needing a site rebuild. cached in sessionStorage for up to 1h so
+// repeated navigations within a tab don't re-hit the GitHub API on every page
+// load. placeholder is `hidden` in the template so a failed/blocked fetch
+// leaves no stray gap.
+const versionCacheKey = 'remark42-latest-version'
+const versionCacheTTL = 60 * 60 * 1000
+
+// script is loaded synchronously in , so a cache-hit fetch can resolve
+// before is parsed and [data-remark42-version] exists. defer the DOM
+// update until the document is ready.
+function applyVersion(tag) {
+ const update = () => {
+ document.querySelectorAll('[data-remark42-version]').forEach((el) => {
+ el.textContent = tag
+ el.hidden = false
+ })
+ }
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', update)
+ } else {
+ update()
+ }
+}
+
+function readVersionCache() {
+ try {
+ const raw = sessionStorage.getItem(versionCacheKey)
+ if (!raw) return null
+ const { tag, expires } = JSON.parse(raw)
+ if (!tag || typeof expires !== 'number' || Date.now() > expires) return null
+ return tag
+ } catch (e) {
+ return null
+ }
+}
+
+function writeVersionCache(tag) {
+ try {
+ sessionStorage.setItem(versionCacheKey, JSON.stringify({ tag, expires: Date.now() + versionCacheTTL }))
+ } catch (e) {
+ // sessionStorage unavailable or full — fall through, fetch will run next load
+ }
+}
+
+const cachedVersion = readVersionCache()
+if (cachedVersion) {
+ applyVersion(cachedVersion)
+} else {
+ fetch('{{ site.githubApiUrl }}/releases/latest')
+ .then((r) => (r.ok ? r.json() : Promise.reject(new Error('HTTP ' + r.status))))
+ .then((d) => {
+ if (!d || !d.tag_name) return
+ writeVersionCache(d.tag_name)
+ applyVersion(d.tag_name)
+ })
+ .catch((err) => console.warn('remark42-site: latest version fetch failed', err))
+}
diff --git a/site/yarn.lock b/site/yarn.lock
index 505430cb..f477f78e 100644
--- a/site/yarn.lock
+++ b/site/yarn.lock
@@ -598,11 +598,6 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
-data-uri-to-buffer@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
- integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
-
data-view-buffer@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2"
@@ -922,14 +917,6 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
-fetch-blob@^3.1.2, fetch-blob@^3.1.4:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9"
- integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
- dependencies:
- node-domexception "^1.0.0"
- web-streams-polyfill "^3.0.3"
-
filelist@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
@@ -972,13 +959,6 @@ foreground-child@^3.1.0:
cross-spawn "^7.0.0"
signal-exit "^4.0.1"
-formdata-polyfill@^4.0.10:
- version "4.0.10"
- resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
- integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
- dependencies:
- fetch-blob "^3.1.2"
-
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -1770,20 +1750,6 @@ no-case@^2.2.0:
dependencies:
lower-case "^1.1.1"
-node-domexception@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
- integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
-
-node-fetch@^3.3.2:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b"
- integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==
- dependencies:
- data-uri-to-buffer "^4.0.0"
- fetch-blob "^3.1.4"
- formdata-polyfill "^4.0.10"
-
normalize-package-data@^2.3.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -2750,11 +2716,6 @@ void-elements@^3.1.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
-web-streams-polyfill@^3.0.3:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"
- integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
-
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"