PageSourceSearch

https://kekkj123.github.io/js/color-schema.js

js kekkj123.github.io collected 2026-10-03 09:27:13 UTC 9,006 bytes, 286 lines download raw bytes

1/* global Fluid */
2
3/**
4 * Modified from https://blog.skk.moe/post/hello-darkmode-my-old-friend/
5 */
6(function(window, document) {
7  var rootElement = document.documentElement;
8  var colorSchemaStorageKey = 'Fluid_Color_Scheme';
9  var colorSchemaMediaQueryKey = '--color-mode';
10  var userColorSchemaAttributeName = 'data-user-color-scheme';
11  var defaultColorSchemaAttributeName = 'data-default-color-scheme';
12  var colorToggleButtonSelector = '#color-toggle-btn';
13  var colorToggleIconSelector = '#color-toggle-icon';
14  var iframeSelector = 'iframe';
15
16  function setLS(k, v) {
17    try {
18      localStorage.setItem(k, v);
19    } catch (e) {}
20  }
21
22  function removeLS(k) {
23    try {
24      localStorage.removeItem(k);
25    } catch (e) {}
26  }
27
28  function getLS(k) {
29    try {
30      return localStorage.getItem(k);
31    } catch (e) {
32      return null;
33    }
34  }
35
36  function getSchemaFromHTML() {
37    var res = rootElement.getAttribute(defaultColorSchemaAttributeName);
38    if (typeof res === 'string') {
39      return res.replace(/["'\s]/g, '');
40    }
41    return null;
42  }
43
44  function getSchemaFromCSSMediaQuery() {
45    var res = getComputedStyle(rootElement).getPropertyValue(
46      colorSchemaMediaQueryKey
47    );
48    if (typeof res === 'string') {
49      return res.replace(/["'\s]/g, '');
50    }
51    return null;
52  }
53
54  function resetSchemaAttributeAndLS() {
55    rootElement.setAttribute(userColorSchemaAttributeName, getDefaultColorSchema());
56    removeLS(colorSchemaStorageKey);
57  }
58
59  var validColorSchemaKeys = {
60    dark : true,
61    light: true
62  };
63
64  function getDefaultColorSchema() {
65    // 取默认字段的值
66    var schema = getSchemaFromHTML();
67    // 如果明确指定了 schema 则返回
68    if (validColorSchemaKeys[schema]) {
69      return schema;
70    }
71    // 默认优先按 prefers-color-scheme
72    schema = getSchemaFromCSSMediaQuery();
73    if (validColorSchemaKeys[schema]) {
74      return schema;
75    }
76    // 否则按本地时间是否大于 18 点或凌晨 0 ~ 6 点
77    var hours = new Date().getHours();
78    if (hours >= 18 || (hours >= 0 && hours <= 6)) {
79      return 'dark';
80    }
81    return 'light';
82  }
83
84  function applyCustomColorSchemaSettings(schema) {
85    // 接受从「开关」处传来的模式,或者从 localStorage 读取,否则按默认设置值
86    var current = schema || getLS(colorSchemaStorageKey) || getDefaultColorSchema();
87
88    if (current === getDefaultColorSchema()) {
89      // 当用户切换的显示模式和默认模式相同时,则恢复为自动模式
90      resetSchemaAttributeAndLS();
91    } else if (validColorSchemaKeys[current]) {
92      rootElement.setAttribute(
93        userColorSchemaAttributeName,
94        current
95      );
96    } else {
97      // 特殊情况重置
98      resetSchemaAttributeAndLS();
99      return;
100    }
101
102    // 根据当前模式设置图标
103    setButtonIcon(current);
104
105    // 设置代码高亮
106    setHighlightCSS(current);
107
108    // 设置其他应用
109    setApplications(current);
110  }
111
112  var invertColorSchemaObj = {
113    dark : 'light',
114    light: 'dark'
115  };
116
117  function getIconClass(scheme) {
118    return 'icon-' + scheme;
119  }
120
121  function toggleCustomColorSchema() {
122    var currentSetting = getLS(colorSchemaStorageKey);
123
124    if (validColorSchemaKeys[currentSetting]) {
125      // 从 localStorage 中读取模式,并取相反的模式
126      currentSetting = invertColorSchemaObj[currentSetting];
127    } else if (currentSetting === null) {
128      // 当 localStorage 中没有相关值,或者 localStorage 抛了 Error
129      // 先按照按钮的状态进行切换
130      var iconElement = document.querySelector(colorToggleIconSelector);
131      if (iconElement) {
132        currentSetting = iconElement.getAttribute('data');
133      }
134      if (!iconElement || !validColorSchemaKeys[currentSetting]) {
135        // 当 localStorage 中没有相关值,或者 localStorage 抛了 Error,则读取默认值并切换到相反的模式
136        currentSetting = invertColorSchemaObj[getSchemaFromCSSMediaQuery()];
137      }
138    } else {
139      return;
140    }
141    // 将相反的模式写入 localStorage
142    setLS(colorSchemaStorageKey, currentSetting);
143
144    return currentSetting;
145  }
146
147  function setButtonIcon(schema) {
148    if (validColorSchemaKeys[schema]) {
149      // 切换图标
150      var icon = getIconClass('dark');
151      if (schema) {
152        icon = getIconClass(schema);
153      }
154      var iconElement = document.querySelector(colorToggleIconSelector);
155      if (iconElement) {
156        iconElement.setAttribute(
157          'class',
158          'iconfont ' + icon
159        );
160        iconElement.setAttribute(
161          'data',
162          invertColorSchemaObj[schema]
163        );
164      } else {
165        // 如果图标不存在则说明图标还没加载出来,等到页面全部加载再尝试切换
166        Fluid.utils.waitElementLoaded(colorToggleIconSelector, function() {
167          var iconElement = document.querySelector(colorToggleIconSelector);
168          if (iconElement) {
169            iconElement.setAttribute(
170              'class',
171              'iconfont ' + icon
172            );
173            iconElement.setAttribute(
174              'data',
175              invertColorSchemaObj[schema]
176            );
177          }
178        });
179      }
180      if (document.documentElement.getAttribute('data-user-color-scheme')) {
181        var color = getComputedStyle(document.documentElement).getPropertyValue('--navbar-bg-color').trim()
182        document.querySelector('meta[name="theme-color"]').setAttribute('content', color)
183      }
184    }
185  }
186
187  function setHighlightCSS(schema) {
188    // 启用对应的代码高亮的样式
189    var lightCss = document.getElementById('highlight-css');
190    var darkCss = document.getElementById('highlight-css-dark');
191    if (schema === 'dark') {
192      if (darkCss) {
193        darkCss.removeAttribute('disabled');
194      }
195      if (lightCss) {
196        lightCss.setAttribute('disabled', '');
197      }
198    } else {
199      if (lightCss) {
200        lightCss.removeAttribute('disabled');
201      }
202      if (darkCss) {
203        darkCss.setAttribute('disabled', '');
204      }
205    }
206
207    setTimeout(function() {
208      // 设置代码块组件样式
209      document.querySelectorAll('.markdown-body pre').forEach((pre) => {
210        var cls = Fluid.utils.getBackgroundLightness(pre) >= 0 ? 'code-widget-light' : 'code-widget-dark';
211        var widget = pre.querySelector('.code-widget-light, .code-widget-dark');
212        if (widget) {
213          widget.classList.remove('code-widget-light', 'code-widget-dark');
214          widget.classList.add(cls);
215        }
216      });
217    }, 200);
218  }
219
220  function setApplications(schema) {
221    // 设置 remark42 评论主题
222    if (window.REMARK42) {
223      window.REMARK42.changeTheme(schema);
224    }
225
226    // 设置 cusdis 评论主题
227    if (window.CUSDIS) {
228      window.CUSDIS.setTheme(schema);
229    }
230
231    // 设置 utterances 评论主题
232    var utterances = document.querySelector('.utterances-frame');
233    if (utterances) {
234      var utterancesTheme = schema === 'dark' ? window.UtterancesThemeDark : window.UtterancesThemeLight;
235      const message = {
236        type : 'set-theme',
237        theme: utterancesTheme
238      };
239      utterances.contentWindow.postMessage(message, 'https://utteranc.es');
240    }
241
242    // 设置 giscus 评论主题
243    var giscus = document.querySelector('iframe.giscus-frame');
244    if (giscus) {
245      var giscusTheme = schema === 'dark' ? window.GiscusThemeDark : window.GiscusThemeLight;
246      const message = {
247        setConfig: {
248          theme: giscusTheme,
249        }
250      };
251      // giscus.style.cssText += 'color-scheme: normal;';
252      giscus.contentWindow.postMessage({ 'giscus': message }, 'https://giscus.app');
253    }
254  }
255
256  // 当页面加载时,将显示模式设置为 localStorage 中自定义的值(如果有的话)
257  applyCustomColorSchemaSettings();
258
259  Fluid.utils.waitElementLoaded(colorToggleIconSelector, function() {
260    applyCustomColorSchemaSettings();
261    var button = document.querySelector(colorToggleButtonSelector);
262    if (button) {
263      // 当用户点击切换按钮时,获得新的显示模式、写入 localStorage、并在页面上生效
264      button.addEventListener('click', function() {
265        applyCustomColorSchemaSettings(toggleCustomColorSchema());
266      });
267      var icon = document.querySelector(colorToggleIconSelector);
268      if (icon) {
269        // 光标悬停在按钮上时,切换图标
270        button.addEventListener('mouseenter', function() {
271          var current = icon.getAttribute('data');
272          icon.classList.replace(getIconClass(invertColorSchemaObj[current]), getIconClass(current));
273        });
274        button.addEventListener('mouseleave', function() {
275          var current = icon.getAttribute('data');
276          icon.classList.replace(getIconClass(current), getIconClass(invertColorSchemaObj[current]));
277        });
278      }
279    }
280  });
281
282  Fluid.utils.waitElementLoaded(iframeSelector, function() {
283    applyCustomColorSchemaSettings();
284  });
285  
286})(window, document);

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.