@xingwangzhe/stalux 1.28.3 → 1.28.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.28.3",
3
+ "version": "1.28.5",
4
4
  "description": "A powerful, modern Astro blog theme — use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -1,19 +1 @@
1
- ---
2
- import { getCollection } from "astro:content";
3
- import { getHeadData } from "@utils/config-utils";
4
-
5
- const configEntries = await getCollection("config");
6
- const headConfig = getHeadData(configEntries);
7
- const umamiConfig = headConfig?.umami;
8
- const umamiID = umamiConfig?.id;
9
- const umamiURL = umamiConfig?.url;
10
- ---
11
-
12
- <script
13
- is:inline
14
- defer
15
- src={umamiURL}
16
- data-website-id={umamiID}
17
- type="text/javascript"
18
- transition:persist="stalux-umami"
19
- ></script>
1
+ <script src="../../../scripts/umami.ts"></script>
@@ -6,3 +6,24 @@ import styles from "@styles/components/common/date.module.css";
6
6
  <time id={styles.date} data-ref="date" datetime=""></time>
7
7
  <time id={styles.time} data-ref="time" datetime=""></time>
8
8
  </div>
9
+
10
+ <script is:inline>
11
+ (() => {
12
+ const dateElement = document.querySelector('[data-ref="date"]');
13
+ const timeElement = document.querySelector('[data-ref="time"]');
14
+ if (!dateElement || !timeElement) return;
15
+ const now = new Date();
16
+ const pad = (value) => String(value).padStart(2, "0");
17
+ const lang = document.documentElement.lang || "zh-CN";
18
+ dateElement.textContent = lang.startsWith("zh")
19
+ ? `${now.getFullYear()}年${pad(now.getMonth() + 1)}月${pad(now.getDate())}日`
20
+ : new Intl.DateTimeFormat("en-US", {
21
+ year: "numeric",
22
+ month: "short",
23
+ day: "2-digit",
24
+ }).format(now);
25
+ dateElement.dateTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
26
+ timeElement.textContent = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
27
+ timeElement.dateTime = timeElement.textContent;
28
+ })();
29
+ </script>
@@ -84,6 +84,8 @@ const routeAnimation = getRouteAnimation(routeKind);
84
84
  data-stalux-site-lang={siteConfig.lang}
85
85
  data-stalux-ga-id={headConfig?.googleAnalyticsId}
86
86
  data-stalux-clarity-id={headConfig?.bingClarityId}
87
+ data-stalux-umami-id={headConfig?.umami?.id}
88
+ data-stalux-umami-url={headConfig?.umami?.url}
87
89
  >
88
90
  {llmPromote && (
89
91
  <section class="stalux-ai-prompt" data-stalux-ai-prompt aria-label="AI site context">
@@ -0,0 +1,27 @@
1
+ type IdleWindow = Window & {
2
+ requestIdleCallback?: (callback: () => void, options?: { timeout: number }) => number;
3
+ };
4
+
5
+ /** Run non-essential work after the initial page load and during an idle period. */
6
+ export function afterLoadAndIdle(callback: () => void): void {
7
+ let scheduled = false;
8
+ const schedule = () => {
9
+ if (scheduled) return;
10
+ scheduled = true;
11
+ const idleWindow = window as IdleWindow;
12
+ if (idleWindow.requestIdleCallback) {
13
+ idleWindow.requestIdleCallback(callback, { timeout: 2_000 });
14
+ } else {
15
+ window.setTimeout(callback, 0);
16
+ }
17
+ };
18
+
19
+ if (document.readyState === "complete") {
20
+ schedule();
21
+ return;
22
+ }
23
+
24
+ window.addEventListener("load", schedule, { once: true });
25
+ // Still load analytics if a third-party resource prevents the load event.
26
+ window.setTimeout(schedule, 5_000);
27
+ }
@@ -1,3 +1,4 @@
1
+ import { afterLoadAndIdle } from "./after-load";
1
2
  import { createClientLogger } from "./logger";
2
3
 
3
4
  const logger = createClientLogger("clarity");
@@ -14,22 +15,28 @@ function load() {
14
15
  const id = document.body?.dataset.staluxClarityId;
15
16
  if (!id || window.__staluxClarityLoaded || document.getElementById("stalux-clarity-script"))
16
17
  return;
17
- window.__staluxClarityLoaded = true;
18
- window.clarity =
19
- window.clarity ||
20
- Object.assign((...args: unknown[]) => window.clarity?.q?.push(args), { q: [] });
21
- const script = document.createElement("script");
22
- script.id = "stalux-clarity-script";
23
- script.async = true;
24
- script.src = `https://www.clarity.ms/tag/${encodeURIComponent(id)}`;
25
- script.addEventListener("load", () => logger.debug("external script loaded"), { once: true });
26
- script.addEventListener(
27
- "error",
28
- () => logger.warn("external script failed to load (network or content blocker)"),
29
- { once: true },
30
- );
31
- logger.debug("loading external script");
32
- document.head.appendChild(script);
18
+ afterLoadAndIdle(() => {
19
+ if (window.__staluxClarityLoaded || document.getElementById("stalux-clarity-script"))
20
+ return;
21
+ window.__staluxClarityLoaded = true;
22
+ window.clarity =
23
+ window.clarity ||
24
+ Object.assign((...args: unknown[]) => window.clarity?.q?.push(args), { q: [] });
25
+ const script = document.createElement("script");
26
+ script.id = "stalux-clarity-script";
27
+ script.async = true;
28
+ script.src = `https://www.clarity.ms/tag/${encodeURIComponent(id)}`;
29
+ script.addEventListener("load", () => logger.debug("external script loaded"), {
30
+ once: true,
31
+ });
32
+ script.addEventListener(
33
+ "error",
34
+ () => logger.warn("external script failed to load (network or content blocker)"),
35
+ { once: true },
36
+ );
37
+ logger.debug("loading external script after page load and idle time");
38
+ document.head.appendChild(script);
39
+ });
33
40
  }
34
41
 
35
42
  if (!window.__staluxClarityPageLoadListener) {
@@ -12,6 +12,36 @@ declare global {
12
12
  }
13
13
  }
14
14
 
15
+ function appendWhenIdle(script: HTMLScriptElement): void {
16
+ const append = () => document.head.appendChild(script);
17
+ const requestIdle = (
18
+ window as Window & {
19
+ requestIdleCallback?: (callback: () => void, options?: { timeout: number }) => number;
20
+ }
21
+ ).requestIdleCallback;
22
+
23
+ if (requestIdle) requestIdle.call(window, append, { timeout: 1200 });
24
+ else window.setTimeout(append, 0);
25
+ }
26
+
27
+ function appendAfterPageLoad(script: HTMLScriptElement): void {
28
+ let appended = false;
29
+ const schedule = () => {
30
+ if (appended) return;
31
+ appended = true;
32
+ appendWhenIdle(script);
33
+ };
34
+
35
+ if (document.readyState === "complete") {
36
+ schedule();
37
+ return;
38
+ }
39
+
40
+ window.addEventListener("load", schedule, { once: true });
41
+ // Avoid indefinitely delaying analytics if an image or other resource stalls load.
42
+ window.setTimeout(schedule, 5000);
43
+ }
44
+
15
45
  function load() {
16
46
  const gaID = document.body?.dataset.staluxGaId;
17
47
  if (!gaID) return;
@@ -21,6 +51,7 @@ function load() {
21
51
  window.__staluxGoogleScriptLoaded = true;
22
52
  const script = document.createElement("script");
23
53
  script.async = true;
54
+ script.fetchPriority = "low";
24
55
  script.dataset.staluxGoogle = "true";
25
56
  script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(gaID)}`;
26
57
  script.addEventListener("load", () => logger.debug("external script loaded"), {
@@ -31,8 +62,8 @@ function load() {
31
62
  () => logger.warn("external script failed to load (network or content blocker)"),
32
63
  { once: true },
33
64
  );
34
- logger.debug("loading external script");
35
- document.head.appendChild(script);
65
+ logger.debug("deferred external script until page load and idle time");
66
+ appendAfterPageLoad(script);
36
67
  }
37
68
  if (!window.__staluxAnalyticsLoaded) {
38
69
  window.__staluxAnalyticsLoaded = true;
@@ -0,0 +1,28 @@
1
+ import { afterLoadAndIdle } from "./after-load";
2
+ import { createClientLogger } from "./logger";
3
+
4
+ const logger = createClientLogger("umami");
5
+
6
+ const id = document.body?.dataset.staluxUmamiId;
7
+ const url = document.body?.dataset.staluxUmamiUrl;
8
+
9
+ if (id && url) {
10
+ afterLoadAndIdle(() => {
11
+ if (document.querySelector("script[data-stalux-umami]")) return;
12
+ const script = document.createElement("script");
13
+ script.async = true;
14
+ script.dataset.staluxUmami = "true";
15
+ script.dataset.websiteId = id;
16
+ script.src = url;
17
+ script.addEventListener("load", () => logger.debug("external script loaded"), {
18
+ once: true,
19
+ });
20
+ script.addEventListener(
21
+ "error",
22
+ () => logger.warn("external script failed to load (network or content blocker)"),
23
+ { once: true },
24
+ );
25
+ logger.debug("loading external script after page load and idle time");
26
+ document.head.appendChild(script);
27
+ });
28
+ }
@@ -1,3 +1,4 @@
1
+ import { afterLoadAndIdle } from "./after-load";
1
2
  import { createClientLogger } from "./logger";
2
3
 
3
4
  const logger = createClientLogger("vercount");
@@ -10,19 +11,25 @@ declare global {
10
11
 
11
12
  function load() {
12
13
  if (document.querySelector("script[data-stalux-vercount]")) return;
13
- window.__staluxVerCountLoaded = true;
14
- const script = document.createElement("script");
15
- script.defer = true;
16
- script.dataset.staluxVercount = "true";
17
- script.src = "https://events.vercount.one/js";
18
- script.addEventListener("load", () => logger.debug("external script loaded"), { once: true });
19
- script.addEventListener(
20
- "error",
21
- () => logger.warn("external script failed to load (network or content blocker)"),
22
- { once: true },
23
- );
24
- logger.debug("loading external script");
25
- document.head.appendChild(script);
14
+ afterLoadAndIdle(() => {
15
+ if (window.__staluxVerCountLoaded || document.querySelector("script[data-stalux-vercount]"))
16
+ return;
17
+ window.__staluxVerCountLoaded = true;
18
+ const script = document.createElement("script");
19
+ script.async = true;
20
+ script.dataset.staluxVercount = "true";
21
+ script.src = "https://events.vercount.one/js";
22
+ script.addEventListener("load", () => logger.debug("external script loaded"), {
23
+ once: true,
24
+ });
25
+ script.addEventListener(
26
+ "error",
27
+ () => logger.warn("external script failed to load (network or content blocker)"),
28
+ { once: true },
29
+ );
30
+ logger.debug("loading external script after page load and idle time");
31
+ document.head.appendChild(script);
32
+ });
26
33
  }
27
34
 
28
35
  document.addEventListener("astro:page-load", load);
@@ -0,0 +1,23 @@
1
+ import { init, type WalineInitOptions } from "@waline/client";
2
+ import walineCss from "@waline/client/style?inline";
3
+ import staluxWalineCss from "../styles/components/posts/waline.css?inline";
4
+
5
+ export function mountWaline(config: Record<string, unknown>, element: HTMLElement): () => void {
6
+ const style = document.createElement("style");
7
+ style.dataset.staluxWaline = "true";
8
+ style.textContent = `${walineCss}\n${staluxWalineCss}`;
9
+ document.head.append(style);
10
+
11
+ const options = {
12
+ ...config,
13
+ el: element,
14
+ serverURL: config.serverURL,
15
+ path: typeof config.path === "string" ? config.path : window.location.pathname,
16
+ } as WalineInitOptions;
17
+ const instance = init(options);
18
+
19
+ return () => {
20
+ instance?.destroy();
21
+ style.remove();
22
+ };
23
+ }
@@ -1,6 +1,3 @@
1
- import type { WalineInitOptions } from "@waline/client";
2
- import walineCss from "@waline/client/style?inline";
3
- import staluxWalineCss from "../styles/components/posts/waline.css?inline";
4
1
  import { createClientLogger } from "./logger";
5
2
  import { registerPageLifecycle } from "./page-runtime";
6
3
 
@@ -34,32 +31,39 @@ registerPageLifecycle("waline", () => {
34
31
 
35
32
  let disposed = false;
36
33
  let destroy: (() => void) | undefined;
34
+ let observer: IntersectionObserver | undefined;
37
35
 
38
- void import("@waline/client")
39
- .then((waline) => {
40
- if (disposed || !commentElement.isConnected) return;
41
- const style = document.createElement("style");
42
- style.dataset.staluxWaline = "true";
43
- style.textContent = `${walineCss}\n${staluxWalineCss}`;
44
- document.head.append(style);
45
- const options = {
46
- ...decoded,
47
- el: commentElement,
48
- serverURL: decoded.serverURL,
49
- path: typeof decoded.path === "string" ? decoded.path : window.location.pathname,
50
- } as WalineInitOptions;
51
- const instance = waline.init(options);
52
- destroy = () => instance?.destroy();
53
- logger.debug("comment widget initialized");
54
- })
55
- .catch((error) => {
56
- logger.warn(
57
- `comment assets failed to load (${error instanceof Error ? error.name : "unknown error"})`,
58
- );
59
- });
36
+ const load = () => {
37
+ observer?.disconnect();
38
+ observer = undefined;
39
+ void import("./waline-client")
40
+ .then(({ mountWaline }) => {
41
+ if (disposed || !commentElement.isConnected) return;
42
+ destroy = mountWaline(decoded, commentElement);
43
+ logger.debug("comment widget initialized near viewport");
44
+ })
45
+ .catch((error) => {
46
+ logger.warn(
47
+ `comment assets failed to load (${error instanceof Error ? error.name : "unknown error"})`,
48
+ );
49
+ });
50
+ };
51
+
52
+ if ("IntersectionObserver" in window) {
53
+ observer = new IntersectionObserver(
54
+ (entries) => {
55
+ if (entries.some((entry) => entry.isIntersecting)) load();
56
+ },
57
+ { rootMargin: "1000px 0px" },
58
+ );
59
+ observer.observe(container);
60
+ } else {
61
+ load();
62
+ }
60
63
 
61
64
  return () => {
62
65
  disposed = true;
66
+ observer?.disconnect();
63
67
  destroy?.();
64
68
  };
65
69
  });