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
|
/**
* BOOT LOADER
* サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで、以下の役割を持ちます。
* - 翻訳ファイルをフェッチする。
* - バージョンに基づいて適切なメインスクリプトを読み込む。
* - キャッシュされたコンパイル済みテーマを適用する。
* - クライアントの設定値に基づいて対応するHTMLクラス等を設定する。
* テーマをこの段階で設定するのは、メインスクリプトが読み込まれる間もテーマを適用したいためです。
* 注: webpackは介さないため、このファイルではrequireやimportは使えません。
*/
'use strict';
window.onerror = (e) => {
document.documentElement.innerHTML = '問題が発生しました。';
};
// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
(async () => {
const v = localStorage.getItem('v') || VERSION;
//#region Detect language & fetch translations
const localeVersion = localStorage.getItem('localeVersion');
const localeOutdated = (localeVersion == null || localeVersion !== v);
if (!localStorage.hasOwnProperty('locale') || localeOutdated) {
const supportedLangs = LANGS;
let lang = localStorage.getItem('lang');
if (lang == null || !supportedLangs.includes(lang)) {
if (supportedLangs.includes(navigator.language)) {
lang = navigator.language;
} else {
lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
// Fallback
if (lang == null) lang = 'en-US';
}
}
const res = await fetch(`/assets/locales/${lang}.${v}.json`);
localStorage.setItem('lang', lang);
localStorage.setItem('locale', await res.text());
localStorage.setItem('localeVersion', v);
}
//#endregion
//#region Script
const salt = localStorage.getItem('salt')
? `?salt=${localStorage.getItem('salt')}`
: '';
const head = document.getElementsByTagName('head')[0];
const script = document.createElement('script');
script.setAttribute('src', `/assets/app.${v}.js${salt}`);
script.setAttribute('async', 'true');
script.setAttribute('defer', 'true');
script.addEventListener('error', async () => {
document.documentElement.innerHTML = '読み込みに失敗しました。';
// TODO: サーバーが落ちている場合などのエラーハンドリング
const res = await fetch('/api/meta', {
method: 'POST',
cache: 'no-cache'
});
const meta = await res.json();
if (meta.version != v) {
localStorage.setItem('v', meta.version);
alert(
'Misskeyの新しいバージョンがあります。ページを再度読み込みします。' +
'\n\n' +
'New version of Misskey available. The page will be reloaded.');
refresh();
}
});
head.appendChild(script);
//#endregion
//#region Theme
const theme = localStorage.getItem('theme');
if (theme) {
for (const [k, v] of Object.entries(JSON.parse(theme))) {
document.documentElement.style.setProperty(`--${k}`, v.toString());
// HTMLの theme-color 適用
if (k === 'htmlThemeColor') {
for (const tag of document.head.children) {
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
tag.setAttribute('content', v);
break;
}
}
}
}
}
//#endregion
const fontSize = localStorage.getItem('fontSize');
if (fontSize) {
document.documentElement.classList.add('f-' + fontSize);
}
const useSystemFont = localStorage.getItem('useSystemFont');
if (useSystemFont) {
document.documentElement.classList.add('useSystemFont');
}
const wallpaper = localStorage.getItem('wallpaper');
if (wallpaper) {
document.documentElement.style.backgroundImage = `url(${wallpaper})`;
}
// eslint-disable-next-line no-inner-declarations
function refresh() {
// Random
localStorage.setItem('salt', Math.random().toString().substr(2, 8));
// Clear cache (service worker)
try {
navigator.serviceWorker.controller.postMessage('clear');
navigator.serviceWorker.getRegistrations().then(registrations => {
registrations.forEach(registration => registration.unregister());
});
} catch (e) {
console.error(e);
}
location.reload();
}
})();
|