PageSourceSearch

https://rui-kong.github.io/blog/assets/view-mode.js?v=dual-view-0713c

js rui-kong.github.io collected 2026-10-03 09:52:21 UTC 4,373 bytes, 140 lines download raw bytes

1(function () {
2  "use strict";
3
4  const STORAGE_KEY = "aiNewsRadarViewV2";
5  const LEGACY_STORAGE_KEY = "aiNewsRadarView";
6  const MOBILE_OVERRIDE_KEY = "aiNewsRadarMobileViewOnce";
7  const MOBILE_BREAKPOINT = "(max-width: 760px)";
8  const VALID_VIEWS = new Set(["mobile", "classic"]);
9  const script = document.currentScript;
10  const rootHint = script?.dataset.root || "./";
11  const rootUrl = new URL(rootHint, window.location.href);
12  const currentView = document.documentElement.dataset.radarView || "mobile";
13  const params = new URLSearchParams(window.location.search);
14  const requestedView = params.get("view") || "";
15  const isMobileViewport = window.matchMedia(MOBILE_BREAKPOINT).matches;
16
17  try {
18    window.localStorage.removeItem(LEGACY_STORAGE_KEY);
19  } catch {
20    // Storage can be unavailable in private or hardened browser contexts.
21  }
22
23  function readPreference() {
24    try {
25      const value = window.localStorage.getItem(STORAGE_KEY) || "";
26      return VALID_VIEWS.has(value) ? value : "";
27    } catch {
28      return "";
29    }
30  }
31
32  function writePreference(view) {
33    try {
34      if (VALID_VIEWS.has(view)) {
35        window.localStorage.setItem(STORAGE_KEY, view);
36      } else {
37        window.localStorage.removeItem(STORAGE_KEY);
38      }
39    } catch {
40      // Storage can be unavailable in private or hardened browser contexts.
41    }
42  }
43
44  function readMobileOverride() {
45    try {
46      const value = window.sessionStorage.getItem(MOBILE_OVERRIDE_KEY) || "";
47      window.sessionStorage.removeItem(MOBILE_OVERRIDE_KEY);
48      return VALID_VIEWS.has(value) ? value : "";
49    } catch {
50      return "";
51    }
52  }
53
54  function writeMobileOverride(view) {
55    try {
56      if (VALID_VIEWS.has(view)) {
57        window.sessionStorage.setItem(MOBILE_OVERRIDE_KEY, view);
58      } else {
59        window.sessionStorage.removeItem(MOBILE_OVERRIDE_KEY);
60      }
61    } catch {
62      // Storage can be unavailable in private or hardened browser contexts.
63    }
64  }
65
66  if (requestedView === "auto") {
67    writePreference("");
68    writeMobileOverride("");
69  } else if (!isMobileViewport && VALID_VIEWS.has(requestedView)) {
70    writePreference(requestedView);
71  }
72
73  const mobileOverride = isMobileViewport ? readMobileOverride() : "";
74  const preference = isMobileViewport
75    ? mobileOverride
76    : (requestedView === "auto"
77      ? ""
78      : (VALID_VIEWS.has(requestedView) ? requestedView : readPreference()));
79  const deviceDefault = "mobile";
80  const targetView = preference || deviceDefault;
81
82  function destination(view) {
83    const url = new URL(view === "classic" ? "classic/" : "./", rootUrl);
84    const passthrough = new URLSearchParams(window.location.search);
85    passthrough.delete("view");
86    url.search = passthrough.toString();
87    url.hash = window.location.hash;
88    return url;
89  }
90
91  function cleanViewQuery() {
92    if (!requestedView) return;
93    const cleanUrl = destination(currentView);
94    window.history.replaceState(null, "", `${cleanUrl.pathname}${cleanUrl.search}${cleanUrl.hash}`);
95  }
96
97  function chooseView(view) {
98    if (!VALID_VIEWS.has(view)) return;
99    if (isMobileViewport) writeMobileOverride(view);
100    else writePreference(view);
101    if (view === currentView) return;
102    window.location.assign(destination(view).href);
103  }
104
105  if (targetView !== currentView) {
106    window.location.replace(destination(targetView).href);
107    return;
108  }
109
110  cleanViewQuery();
111
112  function mountSwitches() {
113    document.querySelectorAll("[data-radar-view-target]").forEach((button) => {
114      const view = button.dataset.radarViewTarget || "";
115      const active = view === currentView;
116      button.classList.toggle("is-active", active);
117      button.setAttribute("aria-pressed", active ? "true" : "false");
118      if (active) button.setAttribute("aria-current", "page");
119      else button.removeAttribute("aria-current");
120      button.addEventListener("click", () => chooseView(view));
121    });
122  }
123
124  if (document.readyState === "loading") {
125    document.addEventListener("DOMContentLoaded", mountSwitches, { once: true });
126  } else {
127    mountSwitches();
128  }
129
130  window.AINewsRadarView = {
131    current: currentView,
132    preference: preference || "auto",
133    choose: chooseView,
134    reset() {
135      writePreference("");
136      writeMobileOverride("");
137      window.location.replace(destination(deviceDefault).href);
138    },
139  };
140})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.