Merge branch 'develop' into sw-notification-action

This commit is contained in:
tamaina
2021-03-03 13:41:12 +09:00
88 changed files with 757 additions and 435 deletions

40
src/server/web/bios.css Normal file
View File

@@ -0,0 +1,40 @@
* {
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
}
html {
background: #ffb4e1;
}
main {
background: #dedede;
}
main > .tabs {
padding: 16px;
border-bottom: solid 4px #c3c3c3;
}
#lsEditor > .adder {
margin: 16px;
padding: 16px;
border: solid 2px #c3c3c3;
}
#lsEditor > .adder > textarea {
display: block;
width: 100%;
min-height: 5em;
box-sizing: border-box;
}
#lsEditor > .record {
padding: 16px;
border-bottom: solid 1px #c3c3c3;
}
#lsEditor > .record > header {
font-weight: bold;
}
#lsEditor > .record > textarea {
display: block;
width: 100%;
min-height: 5em;
box-sizing: border-box;
}

87
src/server/web/bios.js Normal file
View File

@@ -0,0 +1,87 @@
'use strict';
window.onload = async () => {
const account = JSON.parse(localStorage.getItem('account'));
const i = account.token;
const api = (endpoint, data = {}) => {
const promise = new Promise((resolve, reject) => {
// Append a credential
if (i) data.i = i;
// Send request
fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'omit',
cache: 'no-cache'
}).then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
resolve();
} else {
reject(body.error);
}
}).catch(reject);
});
return promise;
};
const content = document.getElementById('content');
document.getElementById('ls').addEventListener('click', () => {
content.innerHTML = '';
const lsEditor = document.createElement('div');
lsEditor.id = 'lsEditor';
const adder = document.createElement('div');
adder.classList.add('adder');
const addKeyInput = document.createElement('input');
const addValueTextarea = document.createElement('textarea');
const addButton = document.createElement('button');
addButton.textContent = 'add';
addButton.addEventListener('click', () => {
localStorage.setItem(addKeyInput.value, addValueTextarea.value);
location.reload();
});
adder.appendChild(addKeyInput);
adder.appendChild(addValueTextarea);
adder.appendChild(addButton);
lsEditor.appendChild(adder);
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
const record = document.createElement('div');
record.classList.add('record');
const header = document.createElement('header');
header.textContent = k;
const textarea = document.createElement('textarea');
textarea.textContent = localStorage.getItem(k);
const saveButton = document.createElement('button');
saveButton.textContent = 'save';
saveButton.addEventListener('click', () => {
localStorage.setItem(k, textarea.value);
location.reload();
});
const removeButton = document.createElement('button');
removeButton.textContent = 'remove';
removeButton.addEventListener('click', () => {
localStorage.removeItem(k);
location.reload();
});
record.appendChild(header);
record.appendChild(textarea);
record.appendChild(saveButton);
record.appendChild(removeButton);
lsEditor.appendChild(record);
}
content.appendChild(lsEditor);
});
};

View File

@@ -11,12 +11,15 @@
'use strict';
window.onerror = (e) => {
document.documentElement.innerHTML = '問題が発生しました。';
};
// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
(async () => {
window.onerror = (e) => {
renderError('SOMETHING_HAPPENED', e.toString());
};
window.onunhandledrejection = (e) => {
renderError('SOMETHING_HAPPENED_IN_PROMISE', e.toString());
};
const v = localStorage.getItem('v') || VERSION;
//#region Detect language & fetch translations
@@ -38,9 +41,17 @@ window.onerror = (e) => {
}
const res = await fetch(`/assets/locales/${lang}.${v}.json`);
localStorage.setItem('lang', lang);
localStorage.setItem('locale', await res.text());
localStorage.setItem('localeVersion', v);
if (res.status === 200) {
localStorage.setItem('lang', lang);
localStorage.setItem('locale', await res.text());
localStorage.setItem('localeVersion', v);
} else if (localeOutdated) {
// nop
} else {
renderError('LOCALE_FETCH_FAILED');
checkUpdate();
return;
}
}
//#endregion
@@ -56,24 +67,8 @@ window.onerror = (e) => {
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();
}
renderError('APP_FETCH_FAILED');
checkUpdate();
});
head.appendChild(script);
//#endregion
@@ -112,6 +107,44 @@ window.onerror = (e) => {
document.documentElement.style.backgroundImage = `url(${wallpaper})`;
}
// eslint-disable-next-line no-inner-declarations
function renderError(code, details) {
document.documentElement.innerHTML = `
<h1>⚠エラーが発生しました</h1>
<p>問題が解決しない場合は管理者までお問い合わせください。以下のオプションを試すこともできます:</p>
<ul>
<li><a href="/cli">簡易クライアント</a>を起動</li>
<li><a href="/bios">BIOS</a>で修復を試みる</li>
<li><a href="/flush">キャッシュをクリア</a>する</li>
</ul>
<hr>
<code>ERROR CODE: ${code}</code>
<details>
${details}
</details>
`;
}
// eslint-disable-next-line no-inner-declarations
async function checkUpdate() {
// 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();
}
}
// eslint-disable-next-line no-inner-declarations
function refresh() {
// Random

19
src/server/web/cli.css Normal file
View File

@@ -0,0 +1,19 @@
* {
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
}
html {
background: #ffb4e1;
}
main {
background: #dedede;
}
#tl > div {
padding: 16px;
border-bottom: solid 1px #c3c3c3;
}
#tl > div > header {
font-weight: bold;
}

55
src/server/web/cli.js Normal file
View File

@@ -0,0 +1,55 @@
'use strict';
window.onload = async () => {
const account = JSON.parse(localStorage.getItem('account'));
const i = account.token;
const api = (endpoint, data = {}) => {
const promise = new Promise((resolve, reject) => {
// Append a credential
if (i) data.i = i;
// Send request
fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'omit',
cache: 'no-cache'
}).then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
resolve();
} else {
reject(body.error);
}
}).catch(reject);
});
return promise;
};
document.getElementById('submit').addEventListener('click', () => {
api('notes/create', {
text: document.getElementById('text').value
}).then(() => {
location.reload();
});
});
api('notes/timeline').then(notes => {
const tl = document.getElementById('tl');
for (const note of notes) {
const el = document.createElement('div');
const name = document.createElement('header');
name.textContent = `${note.user.name} @${note.user.username}`;
const text = document.createElement('div');
text.textContent = `${note.text}`;
el.appendChild(name);
el.appendChild(text);
tl.appendChild(el);
}
});
};

View File

@@ -376,6 +376,18 @@ router.get('/info', async ctx => {
});
});
router.get('/bios', async ctx => {
await ctx.render('bios', {
version: config.version,
});
});
router.get('/cli', async ctx => {
await ctx.render('cli', {
version: config.version,
});
});
const override = (source: string, target: string, depth: number = 0) =>
[, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/');

View File

@@ -0,0 +1,20 @@
doctype html
html
head
meta(charset='utf-8')
meta(name='application-name' content='Misskey')
title Misskey BIOS
style
include ../bios.css
script
include ../bios.js
body
header
h1 Misskey BIOS #{version}
main
div.tabs
button#ls edit local storage
div#content

View File

@@ -0,0 +1,21 @@
doctype html
html
head
meta(charset='utf-8')
meta(name='application-name' content='Misskey')
title Misskey Cli
style
include ../cli.css
script
include ../cli.js
body
header
h1 Misskey Cli #{version}
main
div#form
textarea#text
button#submit submit
div#tl