diff --git a/gulpfile.ts b/gulpfile.ts index bdc20089cdab5efb8eeb1ff473a60df9bac22094..b394e4f44cfce47c0736aa398d4ce247d2627d8e 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -45,7 +45,7 @@ gulp.task('build:copy:locales', cb => { }); gulp.task('build:client:script', () => { - return gulp.src(['./src/server/web/boot.js']) + return gulp.src(['./src/server/web/boot.js', './src/server/web/bios.js', './src/server/web/cli.js']) .pipe(replace('VERSION', JSON.stringify(meta.version))) .pipe(replace('LANGS', JSON.stringify(Object.keys(locales)))) .pipe(terser({ @@ -55,7 +55,7 @@ gulp.task('build:client:script', () => { }); gulp.task('build:client:style', () => { - return gulp.src(['./src/server/web/style.css']) + return gulp.src(['./src/server/web/style.css', './src/server/web/bios.css', './src/server/web/cli.css']) .pipe(cssnano()) .pipe(gulp.dest('./built/server/web/')); }); diff --git a/src/client/components/form/link.vue b/src/client/components/form/link.vue index 7093f50397f51c98626abfac5f8ba52a8a1d9c7e..2efc6b58c9cf81c9861c66b494e1f4cf305f2436 100644 --- a/src/client/components/form/link.vue +++ b/src/client/components/form/link.vue @@ -8,7 +8,7 @@ <Fa :icon="faExternalLinkAlt" class="icon"/> </span> </a> - <MkA class="main _button _formPanel _formClickable" :class="{ active }" :to="to" v-else> + <MkA class="main _button _formPanel _formClickable" :class="{ active }" :to="to" :behavior="behavior" v-else> <span class="icon"><slot name="icon"></slot></span> <span class="text"><slot></slot></span> <span class="right"> @@ -38,6 +38,10 @@ export default defineComponent({ type: Boolean, required: false }, + behavior: { + type: String, + required: false, + }, }, data() { return { diff --git a/src/client/components/global/a.vue b/src/client/components/global/a.vue index cf894deaba042645ebaf94d562370471bebaa401..d293cb571f8706b3e53dd41205a889626d57230d 100644 --- a/src/client/components/global/a.vue +++ b/src/client/components/global/a.vue @@ -98,6 +98,11 @@ export default defineComponent({ }, nav() { + if (this.behavior === 'browser') { + location.href = this.to; + return; + } + if (this.to.startsWith('/my/messaging')) { if (ColdDeviceStorage.get('chatOpenBehavior') === 'window') return this.window(); if (ColdDeviceStorage.get('chatOpenBehavior') === 'popout') return this.popout(); diff --git a/src/client/pages/settings/other.vue b/src/client/pages/settings/other.vue index c0b9625098c7a844bed6b88099c9702cb1d1b26d..a14e101328dbb72ca4a600719b4e52258afc6977 100644 --- a/src/client/pages/settings/other.vue +++ b/src/client/pages/settings/other.vue @@ -23,13 +23,16 @@ <FormLink to="/settings/registry"><template #icon><Fa :icon="faCogs"/></template>{{ $ts.registry }}</FormLink> + <FormLink to="/bios" behavior="browser"><template #icon><Fa :icon="faDoorOpen"/></template>BIOS</FormLink> + <FormLink to="/cli" behavior="browser"><template #icon><Fa :icon="faDoorOpen"/></template>CLI</FormLink> + <FormButton @click="closeAccount" danger>{{ $ts.closeAccount }}</FormButton> </FormBase> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import { faEllipsisH, faCogs } from '@fortawesome/free-solid-svg-icons'; +import { faEllipsisH, faCogs, faDoorOpen } from '@fortawesome/free-solid-svg-icons'; import FormSwitch from '@/components/form/switch.vue'; import FormSelect from '@/components/form/select.vue'; import FormLink from '@/components/form/link.vue'; @@ -61,7 +64,7 @@ export default defineComponent({ icon: faEllipsisH }, debug, - faCogs + faCogs, faDoorOpen, } }, diff --git a/src/server/web/bios.css b/src/server/web/bios.css new file mode 100644 index 0000000000000000000000000000000000000000..b0da3ee39b1c1a23ab0244c3ea57e3e8f43b8aa1 --- /dev/null +++ b/src/server/web/bios.css @@ -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; +} diff --git a/src/server/web/bios.js b/src/server/web/bios.js new file mode 100644 index 0000000000000000000000000000000000000000..d06dee801aea3c6ba254da81acde5ac1f1ee05fa --- /dev/null +++ b/src/server/web/bios.js @@ -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); + }); +}; diff --git a/src/server/web/cli.css b/src/server/web/cli.css new file mode 100644 index 0000000000000000000000000000000000000000..07cd27830b692b0824a57c54a0feb33ba91ed097 --- /dev/null +++ b/src/server/web/cli.css @@ -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; +} diff --git a/src/server/web/cli.js b/src/server/web/cli.js new file mode 100644 index 0000000000000000000000000000000000000000..3dff1d4860431a305e7c1857c62e8816a94275d4 --- /dev/null +++ b/src/server/web/cli.js @@ -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); + } + }); +}; diff --git a/src/server/web/index.ts b/src/server/web/index.ts index 8ea7e157519bd8d509d24d7598ef5cf2993cb422..a1d79100a6ddefe4f58ca4de087fbc58b1f35f69 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -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('/'); diff --git a/src/server/web/views/bios.pug b/src/server/web/views/bios.pug new file mode 100644 index 0000000000000000000000000000000000000000..d81a3ee67fb439792f7de55cf720021450183e5f --- /dev/null +++ b/src/server/web/views/bios.pug @@ -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 diff --git a/src/server/web/views/cli.pug b/src/server/web/views/cli.pug new file mode 100644 index 0000000000000000000000000000000000000000..d2cf7c4335dff40b2ad05a369695bad5ad9b9515 --- /dev/null +++ b/src/server/web/views/cli.pug @@ -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