1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
|
/* Add elements listener */
window.addEventListener("load", () => {
// Menubar burgers click listenr
const burgers: Element[] = Array.prototype.slice.call(
document.querySelectorAll(".navbar-burger"), 0);
if (burgers.length > 0) {
burgers.forEach((element) => {
element.addEventListener("click", () => {
const idstr = element.getAttribute("data-target");
element.classList.toggle("is-active");
if (!idstr) return;
const target = document.getElementById(idstr);
target?.classList.toggle("is-active");
}, false);
});
}
// Add header hover page class changer
const colorman = (mode: SystemDarkmodePreference) => {
let clsname: string = ".auto-dark";
const elements: Element[] = Array.prototype.slice.call(
document.querySelectorAll(clsname), 0);
elements.forEach((element) => {
if (mode === SystemDarkmodePreference.dark) {
element?.classList.add("is-dark");
element?.classList.remove("is-light");
} else {
// If mode == null , fallback to light mode
element?.classList.add("is-light");
element?.classList.remove("is-dark");
}
})
console.debug(`${clsname} class changed to ${enumModeToStringMode(mode)}`);
}
darklistener.add(colorman);
// Add logo color selector
const logoman = (mode: SystemDarkmodePreference) => {
const logo = document.getElementById("logo");
const darksrc = logo?.getAttribute("data-src-darkmode");
const lightsrc = logo?.getAttribute("data-src-lightmode");
if (!logo || !darksrc || !lightsrc)
return;
let src = (mode === SystemDarkmodePreference.dark) ? darksrc : lightsrc;
logo.setAttribute("src", src);
console.debug(`logo src changed to ${enumModeToStringMode(mode)}src`);
}
darklistener.add(logoman);
// Add player theme handler
const playerman = (mode: SystemDarkmodePreference) => {
const shikwasa = document.body.querySelector('div[data-name="shikwasa"]');
shikwasa?.setAttribute("data-theme", enumModeToStringMode(mode) ? enumModeToStringMode(mode)! : "light");
console.debug(`shikwasa theme: ${shikwasa?.getAttribute("data-theme")}`);
};
darklistener.add(playerman);
// Listen after all the setups (to get the handler work :p).
darklistener.listen();
});
/* Darkmode listener */
enum SystemDarkmodePreference {
dark = 0,
light = 1
};
const modeMap = {
dark: SystemDarkmodePreference.dark,
light: SystemDarkmodePreference.light
};
const invertDarkModeObj: Object = {
'dark': 'light',
'light': 'dark'
};
const enumModeToStringMode = (i: SystemDarkmodePreference) => {
let keysArray = Object.keys(modeMap);
let result = keysArray.filter(key => isValidKey(key, modeMap) && modeMap[key] === i);
return result ? result[0] : undefined;
};
class DarkmodeListener {
private _mode: SystemDarkmodePreference;
private _handlers: ((mode: SystemDarkmodePreference) => void)[];
public constructor() {
let darking = window.matchMedia('(prefers-color-scheme: dark)').matches;
this._mode = darking ? SystemDarkmodePreference.dark : SystemDarkmodePreference.light;
this._handlers = [];
}
public listen(): void {
const rootElement = document.documentElement;
const darkModeStorageKey = 'user-color-scheme';
const rootElementDarkModeAttributeName = 'data-user-color-scheme';
const darkModeTogglebuttonElement = document.getElementById('btn-toggle-dark');
const resetRootDarkModeAttributeAndLS = () => {
rootElement.removeAttribute(rootElementDarkModeAttributeName);
removeLS(darkModeStorageKey);
}
// Partially taken from https://blog.skk.moe/post/hello-darkmode-my-old-friend, CC BY-NC-SA 4.0
const applyCustomDarkModeSettings = (mode: any) => {
// 接受从「开关」处传来的模式,或者从 localStorage 读取
let LSSetting = getLS(darkModeStorageKey) == 'dark' ? SystemDarkmodePreference.dark : 'light' ? SystemDarkmodePreference.light : null;
let currentSetting: SystemDarkmodePreference = mode || LSSetting;
console.debug(`applyCustomDarkModeSettings: ${currentSetting}`);
if (currentSetting === this._mode) {
// 当用户自定义的显示模式和 prefers-color-scheme 相同时重置、恢复到自动模式
resetRootDarkModeAttributeAndLS();
console.debug('Resetting to auto mode...');
} else if (currentSetting == SystemDarkmodePreference.dark || currentSetting == SystemDarkmodePreference.light) {
// 否则设置为用户自定义的显示模式
rootElement.setAttribute(rootElementDarkModeAttributeName, enumModeToStringMode(currentSetting)!);
console.debug('Setting prop: "data-user-color-scheme" in HTML...');
} else {
// 首次访问或从未使用过开关、localStorage 中没有存储的值,currentSetting 是 null
// 或者 localStorage 被篡改,currentSetting 不是合法值
// 默认显示浅色主题
resetRootDarkModeAttributeAndLS();
console.debug('Initial setup, setting theme to light as default...')
currentSetting = SystemDarkmodePreference.light;
}
let lightCSS = document.getElementById("bulma-light")!
let darkCSS = document.getElementById("bulma-dark")!
if (currentSetting == SystemDarkmodePreference.dark) {
rootElement.setAttribute(rootElementDarkModeAttributeName, 'dark');
lightCSS.setAttribute("media", "none");
darkCSS.setAttribute("media", "all");
this._handlers.forEach(handler => {
handler(SystemDarkmodePreference.dark);
console.debug('Invoking dark theme handler...');
});
console.debug('Dark theme applied.');
} else if (currentSetting == SystemDarkmodePreference.light) {
rootElement.setAttribute(rootElementDarkModeAttributeName, 'light');
lightCSS.setAttribute("media", "all");
darkCSS.setAttribute("media", "none");
this._handlers.forEach(handler => {
handler(SystemDarkmodePreference.light);
console.debug('Invoking light theme handler...');
});
console.debug('Light theme applied.');
}
}
const toggleCustomDarkMode = () => {
let currentSetting = getLS(darkModeStorageKey);
if (currentSetting === null) {
let curMode = enumModeToStringMode(this._mode)!;
currentSetting = isValidKey(curMode, invertDarkModeObj) ? invertDarkModeObj[curMode] : currentSetting;
} else if (currentSetting == 'dark' || 'light') {
// 从 localStorage 中读取模式,并取相反的模式
currentSetting = isValidKey(currentSetting, invertDarkModeObj) ? invertDarkModeObj[currentSetting] : currentSetting;
} else {
// 不知道出了什么幺蛾子,比如 localStorage 被篡改成非法值
return; // 直接 return;
}
// 将相反的模式写入 localStorage
setLS(darkModeStorageKey, currentSetting);
return isValidKey(currentSetting!, invertDarkModeObj) ? modeMap[currentSetting!] : null;
}
applyCustomDarkModeSettings(null);
darkModeTogglebuttonElement?.addEventListener('click', () => {
// 当用户点击「开关」时,获得新的显示模式、写入 localStorage、并在页面上生效
console.debug('User clicked button. Doing black magic now...');
applyCustomDarkModeSettings(toggleCustomDarkMode());
});
}
public add(callback: (mode: SystemDarkmodePreference) => void): void {
callback(this._mode);
this._handlers.push(callback);
}
public mode(): SystemDarkmodePreference {
return this._mode;
}
};
var darklistener = new DarkmodeListener();
function setLS(k: string, v: any) {
try {
localStorage.setItem(k, v);
} catch (e) { }
}
function removeLS(k: string) {
try {
localStorage.removeItem(k);
} catch (e) { }
}
function getLS(k: string) {
try {
return localStorage.getItem(k);
} catch (e) {
return null; // 与 localStorage 中没有找到对应 key 的行为一致
}
}
// 使用isValidKey判断key是否存在对象类型中
function isValidKey(key: string, obj: object): key is keyof typeof obj {
return key in obj;
}
|