diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-05-31 13:06:40 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-05-31 13:06:40 +0900 |
| commit | 929e5455149fd770c53dc03dabd9572f586772c1 (patch) | |
| tree | c34d7f85b0a7b324f412b2a6bb4168574da30f6f /src/client | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.82.0 (diff) | |
| download | misskey-929e5455149fd770c53dc03dabd9572f586772c1.tar.gz misskey-929e5455149fd770c53dc03dabd9572f586772c1.tar.bz2 misskey-929e5455149fd770c53dc03dabd9572f586772c1.zip | |
Merge branch 'develop'
Diffstat (limited to 'src/client')
32 files changed, 365 insertions, 398 deletions
diff --git a/src/client/components/date-separated-list.vue b/src/client/components/date-separated-list.vue index 34085cc070..6a0c7f29f2 100644 --- a/src/client/components/date-separated-list.vue +++ b/src/client/components/date-separated-list.vue @@ -1,11 +1,11 @@ <script lang="ts"> -import { defineComponent, h, TransitionGroup } from 'vue'; +import { defineComponent, h, PropType, TransitionGroup } from 'vue'; import MkAd from '@client/components/global/ad.vue'; export default defineComponent({ props: { items: { - type: Array, + type: Array as PropType<{ id: string; createdAt: string; _shouldInsertAd_: boolean; }[]>, required: true, }, direction: { diff --git a/src/client/components/drive.file.vue b/src/client/components/drive.file.vue index 37b1afc1b3..3d20de23e9 100644 --- a/src/client/components/drive.file.vue +++ b/src/client/components/drive.file.vue @@ -87,6 +87,10 @@ export default defineComponent({ text: this.file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, icon: this.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash', action: this.toggleSensitive + }, { + text: this.$ts.describeFile, + icon: 'fas fa-i-cursor', + action: this.describe }, null, { text: this.$ts.copyUrl, icon: 'fas fa-link', @@ -150,6 +154,26 @@ export default defineComponent({ }); }, + describe() { + os.popup(import('@client/components/media-caption.vue'), { + title: this.$ts.describeFile, + input: { + placeholder: this.$ts.inputNewDescription, + default: this.file.comment !== null ? this.file.comment : '', + }, + image: this.file + }, { + done: result => { + if (!result || result.canceled) return; + let comment = result.result; + os.api('drive/files/update', { + fileId: this.file.id, + comment: comment.length == 0 ? null : comment + }); + } + }, 'closed'); + }, + toggleSensitive() { os.api('drive/files/update', { fileId: this.file.id, diff --git a/src/client/components/drive.vue b/src/client/components/drive.vue index 06f9cf7806..ca637e3f3d 100644 --- a/src/client/components/drive.vue +++ b/src/client/components/drive.vue @@ -139,7 +139,7 @@ export default defineComponent({ }); } - this.connection = os.stream.useSharedConnection('drive'); + this.connection = os.stream.useChannel('drive'); this.connection.on('fileCreated', this.onStreamDriveFileCreated); this.connection.on('fileUpdated', this.onStreamDriveFileUpdated); @@ -301,7 +301,7 @@ export default defineComponent({ } }).then(({ canceled, result: url }) => { if (canceled) return; - os.api('drive/files/upload_from_url', { + os.api('drive/files/upload-from-url', { url: url, folderId: this.folder ? this.folder.id : undefined }); diff --git a/src/client/components/follow-button.vue b/src/client/components/follow-button.vue index 7199183c66..49bf678491 100644 --- a/src/client/components/follow-button.vue +++ b/src/client/components/follow-button.vue @@ -71,7 +71,7 @@ export default defineComponent({ }, mounted() { - this.connection = os.stream.useSharedConnection('main'); + this.connection = os.stream.useChannel('main'); this.connection.on('follow', this.onFollowChange); this.connection.on('unfollow', this.onFollowChange); diff --git a/src/client/components/image-viewer.vue b/src/client/components/image-viewer.vue index ec22bd98ec..7701ae926f 100644 --- a/src/client/components/image-viewer.vue +++ b/src/client/components/image-viewer.vue @@ -2,7 +2,7 @@ <MkModal ref="modal" @click="$refs.modal.close()" @closed="$emit('closed')"> <div class="xubzgfga"> <header>{{ image.name }}</header> - <img :src="image.url" :alt="image.name" :title="image.name" @click="$refs.modal.close()"/> + <img :src="image.url" :alt="image.comment" :title="image.comment" @click="$refs.modal.close()"/> <footer> <span>{{ image.type }}</span> <span>{{ bytes(image.size) }}</span> diff --git a/src/client/components/media-caption.vue b/src/client/components/media-caption.vue new file mode 100644 index 0000000000..690927d4c5 --- /dev/null +++ b/src/client/components/media-caption.vue @@ -0,0 +1,238 @@ +<template> + <MkModal ref="modal" @click="done(true)" @closed="$emit('closed')"> + <div class="container"> + <div class="fullwidth top-caption"> + <div class="mk-dialog"> + <header v-if="title"><Mfm :text="title"/></header> + <textarea autofocus v-model="inputValue" :placeholder="input.placeholder" @keydown="onInputKeydown"></textarea> + <div class="buttons" v-if="(showOkButton || showCancelButton)"> + <MkButton inline @click="ok" primary>{{ $ts.ok }}</MkButton> + <MkButton inline @click="cancel" >{{ $ts.cancel }}</MkButton> + </div> + </div> + </div> + <div class="hdrwpsaf fullwidth"> + <header>{{ image.name }}</header> + <img :src="image.url" :alt="image.comment" :title="image.comment" @click="$refs.modal.close()"/> + <footer> + <span>{{ image.type }}</span> + <span>{{ bytes(image.size) }}</span> + <span v-if="image.properties && image.properties.width">{{ number(image.properties.width) }}px × {{ number(image.properties.height) }}px</span> + </footer> + </div> + </div> + </MkModal> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import MkModal from '@client/components/ui/modal.vue'; +import MkButton from '@client/components/ui/button.vue'; +import bytes from '@client/filters/bytes'; +import number from '@client/filters/number'; + +export default defineComponent({ + components: { + MkModal, + MkButton, + }, + + props: { + image: { + type: Object, + required: true, + }, + title: { + type: String, + required: false + }, + input: { + required: true + }, + showOkButton: { + type: Boolean, + default: true + }, + showCancelButton: { + type: Boolean, + default: true + }, + cancelableByBgClick: { + type: Boolean, + default: true + }, + }, + + emits: ['done', 'closed'], + + data() { + return { + inputValue: this.input.default ? this.input.default : null + }; + }, + + mounted() { + document.addEventListener('keydown', this.onKeydown); + }, + + beforeUnmount() { + document.removeEventListener('keydown', this.onKeydown); + }, + + methods: { + bytes, + number, + + done(canceled, result?) { + this.$emit('done', { canceled, result }); + this.$refs.modal.close(); + }, + + async ok() { + if (!this.showOkButton) return; + + const result = this.inputValue; + this.done(false, result); + }, + + cancel() { + this.done(true); + }, + + onBgClick() { + if (this.cancelableByBgClick) { + this.cancel(); + } + }, + + onKeydown(e) { + if (e.which === 27) { // ESC + this.cancel(); + } + }, + + onInputKeydown(e) { + if (e.which === 13) { // Enter + if (e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + this.ok(); + } + } + } + } +}); +</script> + +<style lang="scss" scoped> +.container { + display: flex; + width: 100%; + height: 100%; + flex-direction: row; +} +@media (max-width: 850px) { + .container { + flex-direction: column; + } + .top-caption { + padding-bottom: 8px; + } +} +.fullwidth { + width: 100%; + margin: auto; +} +.mk-dialog { + position: relative; + padding: 32px; + min-width: 320px; + max-width: 480px; + box-sizing: border-box; + text-align: center; + background: var(--panel); + border-radius: var(--radius); + margin: auto; + + > header { + margin: 0 0 8px 0; + font-weight: bold; + font-size: 20px; + } + + > .buttons { + margin-top: 16px; + + > * { + margin: 0 8px; + } + } + + > textarea { + display: block; + box-sizing: border-box; + padding: 0 24px; + margin: 0; + width: 100%; + font-size: 16px; + border: none; + border-radius: 0; + background: transparent; + color: var(--fg); + font-family: inherit; + max-width: 100%; + min-width: 100%; + min-height: 90px; + + &:focus { + outline: none; + } + + &:disabled { + opacity: 0.5; + } + } +} +.hdrwpsaf { + display: flex; + flex-direction: column; + height: 100%; + + > header, + > footer { + align-self: center; + display: inline-block; + padding: 6px 9px; + font-size: 90%; + background: rgba(0, 0, 0, 0.5); + border-radius: 6px; + color: #fff; + } + + > header { + margin-bottom: 8px; + opacity: 0.9; + } + + > img { + display: block; + flex: 1; + min-height: 0; + object-fit: contain; + width: 100%; + cursor: zoom-out; + image-orientation: from-image; + } + + > footer { + margin-top: 8px; + opacity: 0.8; + + > span + span { + margin-left: 0.5em; + padding-left: 0.5em; + border-left: solid 1px rgba(255, 255, 255, 0.5); + } + } +} +</style> diff --git a/src/client/components/media-image.vue b/src/client/components/media-image.vue index 267e4debd2..863eb10272 100644 --- a/src/client/components/media-image.vue +++ b/src/client/components/media-image.vue @@ -1,6 +1,6 @@ <template> <div class="qjewsnkg" v-if="hide" @click="hide = false"> - <ImgWithBlurhash class="bg" :hash="image.blurhash" :title="image.name"/> + <ImgWithBlurhash class="bg" :hash="image.blurhash" :title="image.comment" :alt="image.comment"/> <div class="text"> <div> <b><i class="fas fa-exclamation-triangle"></i> {{ $ts.sensitive }}</b> @@ -14,7 +14,7 @@ :title="image.name" @click.prevent="onClick" > - <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.name" :title="image.name" :cover="false"/> + <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment" :title="image.comment" :cover="false"/> <div class="gif" v-if="image.type === 'image/gif'">GIF</div> </a> <i class="fas fa-eye-slash" @click="hide = true"></i> diff --git a/src/client/components/notification.vue b/src/client/components/notification.vue index 9badd7a708..c7063b0aa2 100644 --- a/src/client/components/notification.vue +++ b/src/client/components/notification.vue @@ -109,7 +109,7 @@ export default defineComponent({ this.readObserver.observe(this.$el); - this.connection = os.stream.useSharedConnection('main'); + this.connection = os.stream.useChannel('main'); this.connection.on('readAllNotifications', () => this.readObserver.unobserve(this.$el)); } }, diff --git a/src/client/components/notifications.vue b/src/client/components/notifications.vue index 161419f891..092c00f14e 100644 --- a/src/client/components/notifications.vue +++ b/src/client/components/notifications.vue @@ -12,10 +12,10 @@ <XNotification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/> </XList> - <button class="_buttonPrimary" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" v-show="more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> + <MkButton primary style="margin: var(--margin) auto;" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" v-show="more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <template v-if="!moreFetching">{{ $ts.loadMore }}</template> <template v-if="moreFetching"><MkLoading inline/></template> - </button> + </MkButton> </div> </transition> </template> @@ -28,12 +28,14 @@ import XList from './date-separated-list.vue'; import XNote from './note.vue'; import { notificationTypes } from '../../types'; import * as os from '@client/os'; +import MkButton from '@client/components/ui/button.vue'; export default defineComponent({ components: { XNotification, XList, XNote, + MkButton, }, mixins: [ @@ -87,7 +89,7 @@ export default defineComponent({ }, mounted() { - this.connection = os.stream.useSharedConnection('main'); + this.connection = os.stream.useChannel('main'); this.connection.on('notification', this.onNotification); }, diff --git a/src/client/components/post-form-attaches.vue b/src/client/components/post-form-attaches.vue index f832ea87b5..27e20fdfa8 100644 --- a/src/client/components/post-form-attaches.vue +++ b/src/client/components/post-form-attaches.vue @@ -89,6 +89,27 @@ export default defineComponent({ file.name = result; }); }, + + async describe(file) { + os.popup(import("@client/components/media-caption.vue"), { + title: this.$ts.describeFile, + input: { + placeholder: this.$ts.inputNewDescription, + default: file.comment !== null ? file.comment : "", + }, + image: file + }, { + done: result => { + if (!result || result.canceled) return; + let comment = result.result; + os.api('drive/files/update', { + fileId: file.id, + comment: comment.length == 0 ? null : comment + }); + } + }, 'closed'); + }, + showFileMenu(file, ev: MouseEvent) { if (this.menu) return; this.menu = os.modalMenu([{ @@ -100,6 +121,10 @@ export default defineComponent({ icon: file.isSensitive ? 'fas fa-eye-slash' : 'fas fa-eye', action: () => { this.toggleSensitive(file) } }, { + text: this.$ts.describeFile, + icon: 'fas fa-i-cursor', + action: () => { this.describe(file) } + }, { text: this.$ts.attachCancel, icon: 'fas fa-times-circle', action: () => { this.detachMedia(file.id) } diff --git a/src/client/components/timeline.vue b/src/client/components/timeline.vue index 753eba2ba1..c21e1ec2a6 100644 --- a/src/client/components/timeline.vue +++ b/src/client/components/timeline.vue @@ -92,33 +92,33 @@ export default defineComponent({ this.query = { antennaId: this.antenna }; - this.connection = os.stream.connectToChannel('antenna', { + this.connection = os.stream.useChannel('antenna', { antennaId: this.antenna }); this.connection.on('note', prepend); } else if (this.src == 'home') { endpoint = 'notes/timeline'; - this.connection = os.stream.useSharedConnection('homeTimeline'); + this.connection = os.stream.useChannel('homeTimeline'); this.connection.on('note', prepend); - this.connection2 = os.stream.useSharedConnection('main'); + this.connection2 = os.stream.useChannel('main'); this.connection2.on('follow', onChangeFollowing); this.connection2.on('unfollow', onChangeFollowing); } else if (this.src == 'local') { endpoint = 'notes/local-timeline'; - this.connection = os.stream.useSharedConnection('localTimeline'); + this.connection = os.stream.useChannel('localTimeline'); this.connection.on('note', prepend); } else if (this.src == 'social') { endpoint = 'notes/hybrid-timeline'; - this.connection = os.stream.useSharedConnection('hybridTimeline'); + this.connection = os.stream.useChannel('hybridTimeline'); this.connection.on('note', prepend); } else if (this.src == 'global') { endpoint = 'notes/global-timeline'; - this.connection = os.stream.useSharedConnection('globalTimeline'); + this.connection = os.stream.useChannel('globalTimeline'); this.connection.on('note', prepend); } else if (this.src == 'mentions') { endpoint = 'notes/mentions'; - this.connection = os.stream.useSharedConnection('main'); + this.connection = os.stream.useChannel('main'); this.connection.on('mention', prepend); } else if (this.src == 'directs') { endpoint = 'notes/mentions'; @@ -130,14 +130,14 @@ export default defineComponent({ prepend(note); } }; - this.connection = os.stream.useSharedConnection('main'); + this.connection = os.stream.useChannel('main'); this.connection.on('mention', onNote); } else if (this.src == 'list') { endpoint = 'notes/user-list-timeline'; this.query = { listId: this.list }; - this.connection = os.stream.connectToChannel('userList', { + this.connection = os.stream.useChannel('userList', { listId: this.list }); this.connection.on('note', prepend); @@ -148,7 +148,7 @@ export default defineComponent({ this.query = { channelId: this.channel }; - this.connection = os.stream.connectToChannel('channel', { + this.connection = os.stream.useChannel('channel', { channelId: this.channel }); this.connection.on('note', prepend); diff --git a/src/client/init.ts b/src/client/init.ts index a4465d75c3..12bb0d58c1 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -163,8 +163,6 @@ fetchInstance().then(() => { initializeSw(); }); -stream.init($i); - const app = createApp(await ( window.location.search === '?zen' ? import('@client/ui/zen.vue') : !$i ? import('@client/ui/visitor.vue') : @@ -296,7 +294,7 @@ if ($i) { } } - const main = stream.useSharedConnection('main', 'System'); + const main = stream.useChannel('main', null, 'System'); // 自分の情報が更新されたとき main.on('meUpdated', i => { @@ -358,10 +356,6 @@ if ($i) { sound.play('channel'); }); - main.on('readAllAnnouncements', () => { - updateAccount({ hasUnreadAnnouncement: false }); - }); - // トークンが再生成されたとき // このままではMisskeyが利用できないので強制的にサインアウトさせる main.on('myTokenRegenerated', () => { diff --git a/src/client/instance.ts b/src/client/instance.ts index 024ff1acbd..04d3353208 100644 --- a/src/client/instance.ts +++ b/src/client/instance.ts @@ -1,26 +1,14 @@ import { computed, reactive } from 'vue'; +import * as Misskey from 'misskey-js'; import { api } from './os'; // TODO: 他のタブと永続化されたstateを同期 -export type Instance = { - emojis: { - category: string; - }[]; - ads: { - id: string; - ratio: number; - place: string; - url: string; - imageUrl: string; - }[]; -}; - const data = localStorage.getItem('instance'); // TODO: instanceをリアクティブにするかは再考の余地あり -export const instance: Instance = reactive(data ? JSON.parse(data) : { +export const instance: Misskey.entities.InstanceMetadata = reactive(data ? JSON.parse(data) : { // TODO: set default values }); diff --git a/src/client/os.ts b/src/client/os.ts index b159cf509d..987844b2d2 100644 --- a/src/client/os.ts +++ b/src/client/os.ts @@ -3,16 +3,16 @@ import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vue'; import { EventEmitter } from 'eventemitter3'; import insertTextAtCursor from 'insert-text-at-cursor'; +import * as Misskey from 'misskey-js'; import * as Sentry from '@sentry/browser'; -import Stream from '@client/scripts/stream'; -import { apiUrl, debug } from '@client/config'; +import { apiUrl, debug, url } from '@client/config'; import MkPostFormDialog from '@client/components/post-form-dialog.vue'; import MkWaitingDialog from '@client/components/waiting-dialog.vue'; import { resolve } from '@client/router'; import { $i } from '@client/account'; import { defaultStore } from '@client/store'; -export const stream = markRaw(new Stream()); +export const stream = markRaw(new Misskey.Stream(url, $i)); export const pendingApiRequestsCount = ref(0); let apiRequestsCount = 0; // for debug @@ -20,7 +20,11 @@ export const apiRequests = ref([]); // for debug export const windows = new Map(); -export function api(endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) { +const apiClient = new Misskey.api.APIClient({ + origin: url, +}); + +export const api = ((endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) => { pendingApiRequestsCount.value++; const onFinally = () => { @@ -56,7 +60,7 @@ export function api(endpoint: string, data: Record<string, any> = {}, token?: st if (res.status === 200) { resolve(body); if (debug) { - log!.res = markRaw(body); + log!.res = markRaw(JSON.parse(JSON.stringify(body))); log!.state = 'success'; } } else if (res.status === 204) { @@ -90,17 +94,15 @@ export function api(endpoint: string, data: Record<string, any> = {}, token?: st promise.then(onFinally, onFinally); return promise; -} +}) as typeof apiClient.request; -export function apiWithDialog( +export const apiWithDialog = (( endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined, - onSuccess?: (res: any) => void, - onFailure?: (e: Error) => void, -) { +) => { const promise = api(endpoint, data, token); - promiseDialog(promise, onSuccess, onFailure ? onFailure : (e) => { + promiseDialog(promise, null, (e) => { dialog({ type: 'error', text: e.message + '\n' + (e as any).id, @@ -108,7 +110,7 @@ export function apiWithDialog( }); return promise; -} +}) as typeof api; export function promiseDialog<T extends Promise<any>>( promise: T, diff --git a/src/client/pages/instance/metrics.vue b/src/client/pages/instance/metrics.vue index 18cfe5eee2..407cce9e7f 100644 --- a/src/client/pages/instance/metrics.vue +++ b/src/client/pages/instance/metrics.vue @@ -90,7 +90,7 @@ export default defineComponent({ stats: null, serverInfo: null, connection: null, - queueConnection: os.stream.useSharedConnection('queueStats'), + queueConnection: os.stream.useChannel('queueStats'), memUsage: 0, chartCpuMem: null, chartNet: null, @@ -121,7 +121,7 @@ export default defineComponent({ os.api('admin/server-info', {}).then(res => { this.serverInfo = res; - this.connection = os.stream.useSharedConnection('serverStats'); + this.connection = os.stream.useChannel('serverStats'); this.connection.on('stats', this.onStats); this.connection.on('statsLog', this.onStatsLog); this.connection.send('requestLog', { diff --git a/src/client/pages/instance/overview.vue b/src/client/pages/instance/overview.vue index dca2529e1b..cb9cff9fc5 100644 --- a/src/client/pages/instance/overview.vue +++ b/src/client/pages/instance/overview.vue @@ -92,6 +92,7 @@ export default defineComponent({ version, url, stats: null, + meta: null, fetchStats: () => os.api('stats', {}), fetchServerInfo: () => os.api('admin/server-info', {}), fetchJobs: () => os.api('admin/queue/deliver-delayed', {}), diff --git a/src/client/pages/instance/queue.vue b/src/client/pages/instance/queue.vue index 2dccf48d31..8f56fd74bf 100644 --- a/src/client/pages/instance/queue.vue +++ b/src/client/pages/instance/queue.vue @@ -35,7 +35,7 @@ export default defineComponent({ title: this.$ts.jobQueue, icon: 'fas fa-clipboard-list', }, - connection: os.stream.useSharedConnection('queueStats'), + connection: os.stream.useChannel('queueStats'), } }, diff --git a/src/client/pages/messaging/index.vue b/src/client/pages/messaging/index.vue index 9f3323f629..832cce5ab9 100644 --- a/src/client/pages/messaging/index.vue +++ b/src/client/pages/messaging/index.vue @@ -63,7 +63,7 @@ export default defineComponent({ }, mounted() { - this.connection = os.stream.useSharedConnection('messagingIndex'); + this.connection = os.stream.useChannel('messagingIndex'); this.connection.on('message', this.onMessage); this.connection.on('read', this.onRead); diff --git a/src/client/pages/messaging/messaging-room.vue b/src/client/pages/messaging/messaging-room.vue index 44bfd6c51d..f1d55ee288 100644 --- a/src/client/pages/messaging/messaging-room.vue +++ b/src/client/pages/messaging/messaging-room.vue @@ -141,7 +141,7 @@ const Component = defineComponent({ this.group = group; } - this.connection = os.stream.connectToChannel('messaging', { + this.connection = os.stream.useChannel('messaging', { otherparty: this.user ? this.user.id : undefined, group: this.group ? this.group.id : undefined, }); diff --git a/src/client/pages/reversi/game.vue b/src/client/pages/reversi/game.vue index 62c99d7755..dc4d11ca4a 100644 --- a/src/client/pages/reversi/game.vue +++ b/src/client/pages/reversi/game.vue @@ -61,7 +61,7 @@ export default defineComponent({ if (this.connection) { this.connection.dispose(); } - this.connection = os.stream.connectToChannel('gamesReversiGame', { + this.connection = os.stream.useChannel('gamesReversiGame', { gameId: this.game.id }); this.connection.on('started', this.onStarted); diff --git a/src/client/pages/reversi/index.vue b/src/client/pages/reversi/index.vue index 37126fca10..dd329084a8 100644 --- a/src/client/pages/reversi/index.vue +++ b/src/client/pages/reversi/index.vue @@ -92,7 +92,7 @@ export default defineComponent({ mounted() { if (this.$i) { - this.connection = os.stream.useSharedConnection('gamesReversi'); + this.connection = os.stream.useChannel('gamesReversi'); this.connection.on('invited', this.onInvited); diff --git a/src/client/pages/settings/integration.vue b/src/client/pages/settings/integration.vue index 2d2be04051..f1c0a88afc 100644 --- a/src/client/pages/settings/integration.vue +++ b/src/client/pages/settings/integration.vue @@ -4,8 +4,8 @@ <div class="_formLabel"><i class="fab fa-twitter"></i> Twitter</div> <div class="_formPanel" style="padding: 16px;"> <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> - <MkButton v-if="integrations.twitter" @click="disconnectTwitter" danger>{{ $ts.disconnectSerice }}</MkButton> - <MkButton v-else @click="connectTwitter" primary>{{ $ts.connectSerice }}</MkButton> + <MkButton v-if="integrations.twitter" @click="disconnectTwitter" danger>{{ $ts.disconnectService }}</MkButton> + <MkButton v-else @click="connectTwitter" primary>{{ $ts.connectService }}</MkButton> </div> </div> @@ -13,8 +13,8 @@ <div class="_formLabel"><i class="fab fa-discord"></i> Discord</div> <div class="_formPanel" style="padding: 16px;"> <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> - <MkButton v-if="integrations.discord" @click="disconnectDiscord" danger>{{ $ts.disconnectSerice }}</MkButton> - <MkButton v-else @click="connectDiscord" primary>{{ $ts.connectSerice }}</MkButton> + <MkButton v-if="integrations.discord" @click="disconnectDiscord" danger>{{ $ts.disconnectService }}</MkButton> + <MkButton v-else @click="connectDiscord" primary>{{ $ts.connectService }}</MkButton> </div> </div> @@ -22,8 +22,8 @@ <div class="_formLabel"><i class="fab fa-github"></i> GitHub</div> <div class="_formPanel" style="padding: 16px;"> <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> - <MkButton v-if="integrations.github" @click="disconnectGithub" danger>{{ $ts.disconnectSerice }}</MkButton> - <MkButton v-else @click="connectGithub" primary>{{ $ts.connectSerice }}</MkButton> + <MkButton v-if="integrations.github" @click="disconnectGithub" danger>{{ $ts.disconnectService }}</MkButton> + <MkButton v-else @click="connectGithub" primary>{{ $ts.connectService }}</MkButton> </div> </div> </FormBase> diff --git a/src/client/scripts/get-static-image-url.ts b/src/client/scripts/get-static-image-url.ts index e2728d73f4..92c31914c7 100644 --- a/src/client/scripts/get-static-image-url.ts +++ b/src/client/scripts/get-static-image-url.ts @@ -3,6 +3,11 @@ import * as url from '../../prelude/url'; export function getStaticImageUrl(baseUrl: string): string { const u = new URL(baseUrl); + if (u.href.startsWith(`${instanceUrl}/proxy/`)) { + // もう既にproxyっぽそうだったらsearchParams付けるだけ + u.searchParams.set('static', '1'); + return u.href; + } const dummy = `${u.host}${u.pathname}`; // 拡張子がないとキャッシュしてくれないCDNがあるので return `${instanceUrl}/proxy/${dummy}?${url.query({ url: u.href, diff --git a/src/client/scripts/select-file.ts b/src/client/scripts/select-file.ts index c193e7dc71..b8039fb670 100644 --- a/src/client/scripts/select-file.ts +++ b/src/client/scripts/select-file.ts @@ -47,7 +47,7 @@ export function selectFile(src: any, label: string | null, multiple = false) { const marker = Math.random().toString(); // TODO: UUIDとか使う - const connection = os.stream.useSharedConnection('main'); + const connection = os.stream.useChannel('main'); connection.on('urlUploadFinished', data => { if (data.marker === marker) { res(multiple ? [data.file] : data.file); @@ -55,7 +55,7 @@ export function selectFile(src: any, label: string | null, multiple = false) { } }); - os.api('drive/files/upload_from_url', { + os.api('drive/files/upload-from-url', { url: url, marker }); diff --git a/src/client/scripts/stream.ts b/src/client/scripts/stream.ts deleted file mode 100644 index 065059221d..0000000000 --- a/src/client/scripts/stream.ts +++ /dev/null @@ -1,312 +0,0 @@ -import autobind from 'autobind-decorator'; -import { EventEmitter } from 'eventemitter3'; -import ReconnectingWebsocket from 'reconnecting-websocket'; -import { markRaw } from 'vue'; -import { debug, wsUrl } from '@client/config'; -import { query as urlQuery } from '../../prelude/url'; - -/** - * Misskey stream connection - */ -export default class Stream extends EventEmitter { - private stream: ReconnectingWebsocket; - public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing'; - private sharedConnectionPools: Pool[] = []; - private sharedConnections: SharedConnection[] = []; - private nonSharedConnections: NonSharedConnection[] = []; - - @autobind - public init(user): void { - const query = urlQuery({ - i: user?.token, - _t: Date.now(), - }); - - this.stream = new ReconnectingWebsocket(`${wsUrl}?${query}`, '', { minReconnectionDelay: 1 }); // https://github.com/pladaria/reconnecting-websocket/issues/91 - this.stream.addEventListener('open', this.onOpen); - this.stream.addEventListener('close', this.onClose); - this.stream.addEventListener('message', this.onMessage); - } - - @autobind - public useSharedConnection(channel: string, name?: string): SharedConnection { - let pool = this.sharedConnectionPools.find(p => p.channel === channel); - - if (pool == null) { - pool = new Pool(this, channel); - this.sharedConnectionPools.push(pool); - } - - const connection = markRaw(new SharedConnection(this, channel, pool, name)); - this.sharedConnections.push(connection); - return connection; - } - - @autobind - public removeSharedConnection(connection: SharedConnection) { - this.sharedConnections = this.sharedConnections.filter(c => c !== connection); - } - - @autobind - public removeSharedConnectionPool(pool: Pool) { - this.sharedConnectionPools = this.sharedConnectionPools.filter(p => p !== pool); - } - - @autobind - public connectToChannel(channel: string, params?: any): NonSharedConnection { - const connection = markRaw(new NonSharedConnection(this, channel, params)); - this.nonSharedConnections.push(connection); - return connection; - } - - @autobind - public disconnectToChannel(connection: NonSharedConnection) { - this.nonSharedConnections = this.nonSharedConnections.filter(c => c !== connection); - } - - /** - * Callback of when open connection - */ - @autobind - private onOpen() { - const isReconnect = this.state === 'reconnecting'; - - this.state = 'connected'; - this.emit('_connected_'); - - // チャンネル再接続 - if (isReconnect) { - for (const p of this.sharedConnectionPools) - p.connect(); - for (const c of this.nonSharedConnections) - c.connect(); - } - } - - /** - * Callback of when close connection - */ - @autobind - private onClose() { - if (this.state === 'connected') { - this.state = 'reconnecting'; - this.emit('_disconnected_'); - } - } - - /** - * Callback of when received a message from connection - */ - @autobind - private onMessage(message) { - const { type, body } = JSON.parse(message.data); - - if (type === 'channel') { - const id = body.id; - - let connections: Connection[]; - - connections = this.sharedConnections.filter(c => c.id === id); - - if (connections.length === 0) { - connections = [this.nonSharedConnections.find(c => c.id === id)]; - } - - for (const c of connections.filter(c => c != null)) { - c.emit(body.type, Object.freeze(body.body)); - if (debug) c.inCount++; - } - } else { - this.emit(type, Object.freeze(body)); - } - } - - /** - * Send a message to connection - */ - @autobind - public send(typeOrPayload, payload?) { - const data = payload === undefined ? typeOrPayload : { - type: typeOrPayload, - body: payload - }; - - this.stream.send(JSON.stringify(data)); - } - - /** - * Close this connection - */ - @autobind - public close() { - this.stream.removeEventListener('open', this.onOpen); - this.stream.removeEventListener('message', this.onMessage); - } -} - -let idCounter = 0; - -class Pool { - public channel: string; - public id: string; - protected stream: Stream; - public users = 0; - private disposeTimerId: any; - private isConnected = false; - - constructor(stream: Stream, channel: string) { - this.channel = channel; - this.stream = stream; - - this.id = (++idCounter).toString(); - - this.stream.on('_disconnected_', this.onStreamDisconnected); - } - - @autobind - private onStreamDisconnected() { - this.isConnected = false; - } - - @autobind - public inc() { - if (this.users === 0 && !this.isConnected) { - this.connect(); - } - - this.users++; - - // タイマー解除 - if (this.disposeTimerId) { - clearTimeout(this.disposeTimerId); - this.disposeTimerId = null; - } - } - - @autobind - public dec() { - this.users--; - - // そのコネクションの利用者が誰もいなくなったら - if (this.users === 0) { - // また直ぐに再利用される可能性があるので、一定時間待ち、 - // 新たな利用者が現れなければコネクションを切断する - this.disposeTimerId = setTimeout(() => { - this.disconnect(); - }, 3000); - } - } - - @autobind - public connect() { - if (this.isConnected) return; - this.isConnected = true; - this.stream.send('connect', { - channel: this.channel, - id: this.id - }); - } - - @autobind - private disconnect() { - this.stream.off('_disconnected_', this.onStreamDisconnected); - this.stream.send('disconnect', { id: this.id }); - this.stream.removeSharedConnectionPool(this); - } -} - -abstract class Connection extends EventEmitter { - public channel: string; - protected stream: Stream; - public abstract id: string; - - public name?: string; // for debug - public inCount: number = 0; // for debug - public outCount: number = 0; // for debug - - constructor(stream: Stream, channel: string, name?: string) { - super(); - - this.stream = stream; - this.channel = channel; - this.name = name; - } - - @autobind - public send(id: string, typeOrPayload, payload?) { - const type = payload === undefined ? typeOrPayload.type : typeOrPayload; - const body = payload === undefined ? typeOrPayload.body : payload; - - this.stream.send('ch', { - id: id, - type: type, - body: body - }); - - if (debug) this.outCount++; - } - - public abstract dispose(): void; -} - -class SharedConnection extends Connection { - private pool: Pool; - - public get id(): string { - return this.pool.id; - } - - constructor(stream: Stream, channel: string, pool: Pool, name?: string) { - super(stream, channel, name); - - this.pool = pool; - this.pool.inc(); - } - - @autobind - public send(typeOrPayload, payload?) { - super.send(this.pool.id, typeOrPayload, payload); - } - - @autobind - public dispose() { - this.pool.dec(); - this.removeAllListeners(); - this.stream.removeSharedConnection(this); - } -} - -class NonSharedConnection extends Connection { - public id: string; - protected params: any; - - constructor(stream: Stream, channel: string, params?: any) { - super(stream, channel); - - this.params = params; - this.id = (++idCounter).toString(); - - this.connect(); - } - - @autobind - public connect() { - this.stream.send('connect', { - channel: this.channel, - id: this.id, - params: this.params - }); - } - - @autobind - public send(typeOrPayload, payload?) { - super.send(this.id, typeOrPayload, payload); - } - - @autobind - public dispose() { - this.removeAllListeners(); - this.stream.send('disconnect', { id: this.id }); - this.stream.disconnectToChannel(this); - } -} diff --git a/src/client/style.scss b/src/client/style.scss index 39bf6ef2d5..dc419bd872 100644 --- a/src/client/style.scss +++ b/src/client/style.scss @@ -146,6 +146,7 @@ hr { width: 100%; height: 100%; background: var(--modalBg); + -webkit-backdrop-filter: var(--modalBgFilter); backdrop-filter: var(--modalBgFilter); } diff --git a/src/client/ui/_common_/common.vue b/src/client/ui/_common_/common.vue index 785b1631db..8da19a0984 100644 --- a/src/client/ui/_common_/common.vue +++ b/src/client/ui/_common_/common.vue @@ -43,7 +43,7 @@ export default defineComponent({ }; if ($i) { - const connection = stream.useSharedConnection('main', 'UI'); + const connection = stream.useChannel('main', null, 'UI'); connection.on('notification', onNotification); } diff --git a/src/client/ui/chat/timeline.vue b/src/client/ui/chat/timeline.vue index 13032cce09..2245a9d8a5 100644 --- a/src/client/ui/chat/timeline.vue +++ b/src/client/ui/chat/timeline.vue @@ -121,33 +121,33 @@ export default defineComponent({ this.query = { antennaId: this.antenna }; - this.connection = os.stream.connectToChannel('antenna', { + this.connection = os.stream.useChannel('antenna', { antennaId: this.antenna }); this.connection.on('note', prepend); } else if (this.src == 'home') { endpoint = 'notes/timeline'; - this.connection = os.stream.useSharedConnection('homeTimeline'); + this.connection = os.stream.useChannel('homeTimeline'); this.connection.on('note', prepend); - this.connection2 = os.stream.useSharedConnection('main'); + this.connection2 = os.stream.useChannel('main'); this.connection2.on('follow', onChangeFollowing); this.connection2.on('unfollow', onChangeFollowing); } else if (this.src == 'local') { endpoint = 'notes/local-timeline'; - this.connection = os.stream.useSharedConnection('localTimeline'); + this.connection = os.stream.useChannel('localTimeline'); this.connection.on('note', prepend); } else if (this.src == 'social') { endpoint = 'notes/hybrid-timeline'; - this.connection = os.stream.useSharedConnection('hybridTimeline'); + this.connection = os.stream.useChannel('hybridTimeline'); this.connection.on('note', prepend); } else if (this.src == 'global') { endpoint = 'notes/global-timeline'; - this.connection = os.stream.useSharedConnection('globalTimeline'); + this.connection = os.stream.useChannel('globalTimeline'); this.connection.on('note', prepend); } else if (this.src == 'mentions') { endpoint = 'notes/mentions'; - this.connection = os.stream.useSharedConnection('main'); + this.connection = os.stream.useChannel('main'); this.connection.on('mention', prepend); } else if (this.src == 'directs') { endpoint = 'notes/mentions'; @@ -159,14 +159,14 @@ export default defineComponent({ prepend(note); } }; - this.connection = os.stream.useSharedConnection('main'); + this.connection = os.stream.useChannel('main'); this.connection.on('mention', onNote); } else if (this.src == 'list') { endpoint = 'notes/user-list-timeline'; this.query = { listId: this.list }; - this.connection = os.stream.connectToChannel('userList', { + this.connection = os.stream.useChannel('userList', { listId: this.list }); this.connection.on('note', prepend); @@ -178,7 +178,7 @@ export default defineComponent({ this.query = { channelId: this.channel }; - this.connection = os.stream.connectToChannel('channel', { + this.connection = os.stream.useChannel('channel', { channelId: this.channel }); this.connection.on('note', prepend); diff --git a/src/client/ui/default.sidebar.vue b/src/client/ui/default.sidebar.vue index 725fd844d9..c7e2d30c7a 100644 --- a/src/client/ui/default.sidebar.vue +++ b/src/client/ui/default.sidebar.vue @@ -241,7 +241,6 @@ export default defineComponent({ > .text { display: none; } - } } @@ -309,7 +308,7 @@ export default defineComponent({ > .indicator { position: absolute; top: 0; - left: 20px; + left: 0; color: var(--navIndicator); font-size: 8px; animation: blink 1s infinite; diff --git a/src/client/widgets/job-queue.vue b/src/client/widgets/job-queue.vue index 31a322e6e2..162ffe9c89 100644 --- a/src/client/widgets/job-queue.vue +++ b/src/client/widgets/job-queue.vue @@ -65,7 +65,7 @@ export default defineComponent({ extends: widget, data() { return { - connection: os.stream.useSharedConnection('queueStats'), + connection: os.stream.useChannel('queueStats'), inbox: { activeSincePrevTick: 0, active: 0, diff --git a/src/client/widgets/photos.vue b/src/client/widgets/photos.vue index 65843385b6..7f6fa82722 100644 --- a/src/client/widgets/photos.vue +++ b/src/client/widgets/photos.vue @@ -48,7 +48,7 @@ export default defineComponent({ }; }, mounted() { - this.connection = os.stream.useSharedConnection('main'); + this.connection = os.stream.useChannel('main'); this.connection.on('driveFileCreated', this.onDriveFileCreated); diff --git a/src/client/widgets/server-metric/index.vue b/src/client/widgets/server-metric/index.vue index 6331b5bdf1..2398e9920f 100644 --- a/src/client/widgets/server-metric/index.vue +++ b/src/client/widgets/server-metric/index.vue @@ -63,7 +63,7 @@ export default defineComponent({ os.api('server-info', {}).then(res => { this.meta = res; }); - this.connection = os.stream.useSharedConnection('serverStats'); + this.connection = os.stream.useChannel('serverStats'); }, unmounted() { this.connection.dispose(); |