From 3cd04c4b81190e4cfe6dc1def8335be02f8b4b37 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Sun, 23 May 2021 13:07:11 +0900 Subject: [PATCH 01/28] Fix agent type (#7532) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3d84305649..9e32e6e913 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1536,9 +1536,9 @@ acorn@^8.2.1: integrity sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg== agent-base@6: - version "6.0.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a" - integrity sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw== + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" From 7063a6925fb4cfa47747193e88c059514730ff44 Mon Sep 17 00:00:00 2001 From: okayurisotto <47853651+okayurisotto@users.noreply.github.com> Date: Sun, 23 May 2021 18:55:21 +0900 Subject: [PATCH 02/28] =?UTF-8?q?fix:=20Safari=E3=81=A7=E3=82=82=E3=83=A2?= =?UTF-8?q?=E3=83=BC=E3=83=80=E3=83=AB=E3=81=AE=E3=81=BC=E3=81=8B=E3=81=97?= =?UTF-8?q?=E5=8A=B9=E6=9E=9C=E3=81=8C=E5=8A=B9=E3=81=8F=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=97=E3=81=9F=20(#7530)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/misskey-dev/misskey/issues/7529 --- src/client/style.scss | 1 + 1 file changed, 1 insertion(+) 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); } From 47aaf044813662931fbaddd965272267fd94ed6a Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Sun, 23 May 2021 18:57:12 +0900 Subject: [PATCH 03/28] Fix search-by-tag (#7531) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix search-by-tag * Revert "Fix search-by-tag" This reverts commit c971d1d5d82f2d8b58fdec76e42f4404339ab83a. * Fix typo * Remove unused var * インジェクションは[]を返すように --- .../api/endpoints/notes/search-by-tag.ts | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/server/api/endpoints/notes/search-by-tag.ts b/src/server/api/endpoints/notes/search-by-tag.ts index 61f62dd5a6..463c5fff5a 100644 --- a/src/server/api/endpoints/notes/search-by-tag.ts +++ b/src/server/api/endpoints/notes/search-by-tag.ts @@ -104,22 +104,25 @@ export default define(meta, async (ps, me) => { generateVisibilityQuery(query, me); if (me) generateMutedUserQuery(query, me); - if (ps.tag) { - if (!safeForSql(ps.tag)) return; - query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`); - } else { - let i = 0; - query.andWhere(new Brackets(qb => { - for (const tags of ps.query!) { - qb.orWhere(new Brackets(qb => { - for (const tag of tags) { - if (!safeForSql(tag)) return; - qb.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`); - i++; - } - })); - } - })); + try { + if (ps.tag) { + if (!safeForSql(ps.tag)) throw 'Injection'; + query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`); + } else { + query.andWhere(new Brackets(qb => { + for (const tags of ps.query!) { + qb.orWhere(new Brackets(qb => { + for (const tag of tags) { + if (!safeForSql(tag)) throw 'Injection'; + qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`); + } + })); + } + })); + } + } catch (e) { + if (e === 'Injection') return []; + throw e; } if (ps.reply != null) { From f85399e355685344d45efa49d220cf1508d0bfd5 Mon Sep 17 00:00:00 2001 From: Sandy Nicko Mac Corzeta <4186454+sandycorzeta@users.noreply.github.com> Date: Sun, 23 May 2021 09:57:33 +0000 Subject: [PATCH 04/28] Add Indonesian to index language (#7528) Co-authored-by: Sandy Nicko Mac Corzeta --- locales/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/index.js b/locales/index.js index 727e0e3848..35f9972ff7 100644 --- a/locales/index.js +++ b/locales/index.js @@ -21,6 +21,7 @@ const languages = [ 'en-US', 'es-ES', 'fr-FR', + 'id-ID', 'ja-JP', 'ja-KS', 'kab-KAB', From c06091f78ab507085da3a1a4898b325a43e3201e Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 23 May 2021 21:14:29 +0900 Subject: [PATCH 05/28] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 5cff3619fd..816765af67 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,11 @@ Please see the [Contribution Guide](./CONTRIBUTING.md). To receive updates of this repo, follow [@repo@misskey.io](https://misskey.io/@repo) on fediverse. +Related projects +---------------------------------------------------------------- +- [misskey.js](https://github.com/misskey-dev/misskey.js) - Misskey SDK for JavaScript +- [mfm.js](https://github.com/misskey-dev/mfm.js) - MFM parser + :heart: Backers ---------------------------------------------------------------- From 35f075b8871f240aa94c4a21c77b9759ffd2c1b9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 23 May 2021 21:28:41 +0900 Subject: [PATCH 06/28] :art: --- assets/about/banner.svg | 17 ++++++----------- assets/banner.afdesign | Bin 65435 -> 73201 bytes 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/assets/about/banner.svg b/assets/about/banner.svg index 31b6ca95e1..75308c0950 100644 --- a/assets/about/banner.svg +++ b/assets/about/banner.svg @@ -1,11 +1,8 @@ - - - - - - + + + @@ -61,14 +58,12 @@ - - - - + + - + diff --git a/assets/banner.afdesign b/assets/banner.afdesign index def3e884d3c1145f398944c81f8bae5723f14a0b..08b5c1b4a0b4720db0f3a7cab153edfa1a103ad1 100644 GIT binary patch delta 18972 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 X&;MKcMyF9f3nb;~>gTe~DWM4f-dZV^ delta 11135 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 From c92744c3d36e371ddc8c6f9bbf9d64812ae5ace9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 24 May 2021 05:50:45 +0900 Subject: [PATCH 07/28] Create SECURITY.md --- SECURITY.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..2c026a5f33 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Reporting Security Issues + +If you discover a security issue in Misskey, please report it by sending an +email to [syuilotan@yahoo.co.jp](mailto:syuilotan@yahoo.co.jp). + +This will allow us to assess the risk, and make a fix available before we add a +bug report to the GitHub repository. + +Thanks for helping make Misskey safe for everyone. From ae2267220bb743808bffaf9a33f3bc6eed75a5b1 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 27 May 2021 17:15:08 +0900 Subject: [PATCH 08/28] wip #7533 --- package.json | 1 + src/client/components/drive.vue | 4 +- src/client/components/follow-button.vue | 2 +- src/client/components/notification.vue | 2 +- src/client/components/notifications.vue | 2 +- src/client/components/timeline.vue | 20 +- src/client/init.ts | 8 +- src/client/instance.ts | 16 +- src/client/os.ts | 24 +- src/client/pages/instance/metrics.vue | 4 +- src/client/pages/instance/queue.vue | 2 +- src/client/pages/messaging/index.vue | 2 +- src/client/pages/messaging/messaging-room.vue | 2 +- src/client/pages/reversi/game.vue | 2 +- src/client/pages/reversi/index.vue | 2 +- src/client/scripts/select-file.ts | 4 +- src/client/scripts/stream.ts | 312 ------------------ src/client/ui/_common_/common.vue | 2 +- src/client/ui/chat/timeline.vue | 20 +- src/client/widgets/job-queue.vue | 2 +- src/client/widgets/photos.vue | 2 +- src/client/widgets/server-metric/index.vue | 2 +- yarn.lock | 18 +- 23 files changed, 69 insertions(+), 386 deletions(-) delete mode 100644 src/client/scripts/stream.ts diff --git a/package.json b/package.json index 4f01c78817..610678989f 100644 --- a/package.json +++ b/package.json @@ -174,6 +174,7 @@ "markdown-it-anchor": "7.1.0", "matter-js": "0.17.1", "mfm-js": "0.16.4", + "misskey-js": "0.0.2", "mocha": "8.4.0", "moji": "0.5.1", "ms": "2.1.3", 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/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..6caf8eb8e3 100644 --- a/src/client/components/notifications.vue +++ b/src/client/components/notifications.vue @@ -87,7 +87,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/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..19b95fc50e 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', '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..e6355b45b8 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 = {}, token?: string | null | undefined) { +const apiClient = new Misskey.api.APIClient({ + origin: url, +}); + +export const api = ((endpoint: string, data: Record = {}, token?: string | null | undefined) => { pendingApiRequestsCount.value++; const onFinally = () => { @@ -90,17 +94,15 @@ export function api(endpoint: string, data: Record = {}, token?: st promise.then(onFinally, onFinally); return promise; -} +}) as typeof apiClient.request; -export function apiWithDialog( +export const apiWithDialog = (( endpoint: string, data: Record = {}, 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>( 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/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/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/ui/_common_/common.vue b/src/client/ui/_common_/common.vue index 785b1631db..1e825e0fe0 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', '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/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(); diff --git a/yarn.lock b/yarn.lock index 9e32e6e913..9296aafc4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1290,7 +1290,7 @@ "@vue/compiler-dom" "3.0.11" "@vue/shared" "3.0.11" -"@vue/reactivity@3.0.11": +"@vue/reactivity@3.0.11", "@vue/reactivity@^3.0.11": version "3.0.11" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.11.tgz#07b588349fd05626b17f3500cbef7d4bdb4dbd0b" integrity sha512-SKM3YKxtXHBPMf7yufXeBhCZ4XZDKP9/iXeQSC8bBO3ivBuzAi4aZi0bNoeE2IF2iGfP/AHEt1OU4ARj4ao/Xw== @@ -1899,7 +1899,7 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autobind-decorator@2.4.0: +autobind-decorator@2.4.0, autobind-decorator@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c" integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw== @@ -4203,7 +4203,7 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@4.0.7: +eventemitter3@4.0.7, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -6977,6 +6977,16 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" +misskey-js@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.2.tgz#233d62e5a326a00dd72f36d63436e6584c8076f2" + integrity sha512-gsq3E9lUepNapK4i/3mmqjobQV6gYlgO1O1rQt401ot3LCYlcaLhlUrwBOFtI+ALMGKgwRgkLlDQhcWgAfHHuQ== + dependencies: + "@vue/reactivity" "^3.0.11" + autobind-decorator "^2.4.0" + eventemitter3 "^4.0.7" + reconnecting-websocket "^4.4.0" + mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -9072,7 +9082,7 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" -reconnecting-websocket@4.4.0: +reconnecting-websocket@4.4.0, reconnecting-websocket@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783" integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng== From 466c083233d5f44cfcdfdfa02d8a6bb090382400 Mon Sep 17 00:00:00 2001 From: rinsuki <428rinsuki+git@gmail.com> Date: Thu, 27 May 2021 22:40:48 +0900 Subject: [PATCH 09/28] =?UTF-8?q?=E3=82=AB=E3=82=B9=E3=82=BF=E3=83=A0?= =?UTF-8?q?=E7=B5=B5=E6=96=87=E5=AD=97=E3=82=92proxy=E3=81=AB=E9=80=9A?= =?UTF-8?q?=E3=81=99=E3=82=88=E3=81=86=E3=81=AB=20(#7526)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/scripts/get-static-image-url.ts | 5 +++++ src/misc/populate-emojis.ts | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) 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/misc/populate-emojis.ts b/src/misc/populate-emojis.ts index 8052c71489..a3f67ccb98 100644 --- a/src/misc/populate-emojis.ts +++ b/src/misc/populate-emojis.ts @@ -5,6 +5,8 @@ import { Note } from '../models/entities/note'; import { Cache } from './cache'; import { isSelfHost, toPunyNullable } from './convert-host'; import { decodeReaction } from './reaction-lib'; +import config from '@/config'; +import { query } from '@/prelude/url'; const cache = new Cache(1000 * 60 * 60 * 12); @@ -59,9 +61,12 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu if (emoji == null) return null; + const isLocal = emojiName.endsWith('@.'); + const url = isLocal ? emoji.url : `${config.url}/proxy/image.png?${query({url: emoji.url})}`; + return { name: emojiName, - url: emoji.url, + url, }; } From db3724cf33c402d66700f89b319b423887466757 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 28 May 2021 09:34:42 +0900 Subject: [PATCH 10/28] improve types --- src/server/api/define.ts | 7 +++++-- .../api/endpoints/gallery/posts/create.ts | 3 ++- .../api/endpoints/gallery/posts/update.ts | 3 ++- src/services/chart/core.ts | 21 +++++++++---------- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/server/api/define.ts b/src/server/api/define.ts index 432d5017e8..cba69cfdc4 100644 --- a/src/server/api/define.ts +++ b/src/server/api/define.ts @@ -5,6 +5,8 @@ import { ApiError } from './error'; import { SchemaType } from '@/misc/schema'; import { AccessToken } from '../../models/entities/access-token'; +type NonOptional = T extends undefined ? never : T; + type SimpleUserInfo = { id: ILocalUser['id']; host: ILocalUser['host']; @@ -17,11 +19,12 @@ type SimpleUserInfo = { isSilenced: ILocalUser['isSilenced']; }; -// TODO: defaultが設定されている場合はその型も考慮する type Params = { [P in keyof T['params']]: NonNullable[P]['transform'] extends Function ? ReturnType[P]['transform']> - : ReturnType[P]['validator']['get']>[0]; + : NonNullable[P]['default'] extends null | number | string + ? NonOptional[P]['validator']['get']>[0]> + : ReturnType[P]['validator']['get']>[0]; }; export type Response = Record | void; diff --git a/src/server/api/endpoints/gallery/posts/create.ts b/src/server/api/endpoints/gallery/posts/create.ts index d1ae68b126..ed24a45f88 100644 --- a/src/server/api/endpoints/gallery/posts/create.ts +++ b/src/server/api/endpoints/gallery/posts/create.ts @@ -6,6 +6,7 @@ import { DriveFiles, GalleryPosts } from '../../../../../models'; import { genId } from '../../../../../misc/gen-id'; import { GalleryPost } from '../../../../../models/entities/gallery-post'; import { ApiError } from '../../../error'; +import { DriveFile } from '@/models/entities/drive-file'; export const meta = { tags: ['gallery'], @@ -55,7 +56,7 @@ export default define(meta, async (ps, user) => { id: fileId, userId: user.id }) - ))).filter(file => file != null); + ))).filter((file): file is DriveFile => file != null); if (files.length === 0) { throw new Error(); diff --git a/src/server/api/endpoints/gallery/posts/update.ts b/src/server/api/endpoints/gallery/posts/update.ts index c8bb8d48c9..d9176ea407 100644 --- a/src/server/api/endpoints/gallery/posts/update.ts +++ b/src/server/api/endpoints/gallery/posts/update.ts @@ -5,6 +5,7 @@ import { ID } from '../../../../../misc/cafy-id'; import { DriveFiles, GalleryPosts } from '../../../../../models'; import { GalleryPost } from '../../../../../models/entities/gallery-post'; import { ApiError } from '../../../error'; +import { DriveFile } from '@/models/entities/drive-file'; export const meta = { tags: ['gallery'], @@ -58,7 +59,7 @@ export default define(meta, async (ps, user) => { id: fileId, userId: user.id }) - ))).filter(file => file != null); + ))).filter((file): file is DriveFile => file != null); if (files.length === 0) { throw new Error(); diff --git a/src/services/chart/core.ts b/src/services/chart/core.ts index d956d33bd7..4a554daa78 100644 --- a/src/services/chart/core.ts +++ b/src/services/chart/core.ts @@ -93,7 +93,7 @@ export default abstract class Chart> { } @autobind - private static convertFlattenColumnsToObject(x: Record) { + private static convertFlattenColumnsToObject(x: Record): Record { const obj = {} as any; for (const k of Object.keys(x).filter(k => k.startsWith(Chart.columnPrefix))) { // now k is ___x_y_z @@ -285,8 +285,7 @@ export default abstract class Chart> { const latest = await this.getLatestLog(group); if (latest != null) { - const obj = Chart.convertFlattenColumnsToObject( - latest as Record); + const obj = Chart.convertFlattenColumnsToObject(latest) as T; // 空ログデータを作成 data = this.getNewLog(obj); @@ -474,13 +473,13 @@ export default abstract class Chart> { const log = logs.find(l => isTimeSame(new Date(l.date * 1000), current)); if (log) { - const data = Chart.convertFlattenColumnsToObject(log as Record); - chart.unshift(Chart.countUniqueFields(data)); + const data = Chart.convertFlattenColumnsToObject(log); + chart.unshift(Chart.countUniqueFields(data) as T); } else { // 隙間埋め const latest = logs.find(l => isTimeBefore(new Date(l.date * 1000), current)); - const data = latest ? Chart.convertFlattenColumnsToObject(latest as Record) : null; - chart.unshift(Chart.countUniqueFields(this.getNewLog(data))); + const data = latest ? Chart.convertFlattenColumnsToObject(latest) as T : null; + chart.unshift(Chart.countUniqueFields(this.getNewLog(data)) as T); } } } else if (span === 'day') { @@ -497,14 +496,14 @@ export default abstract class Chart> { if (log) { if (logsForEachDays[currentDayIndex]) { - logsForEachDays[currentDayIndex].unshift(Chart.convertFlattenColumnsToObject(log)); + logsForEachDays[currentDayIndex].unshift(Chart.convertFlattenColumnsToObject(log) as T); } else { - logsForEachDays[currentDayIndex] = [Chart.convertFlattenColumnsToObject(log)]; + logsForEachDays[currentDayIndex] = [Chart.convertFlattenColumnsToObject(log) as T]; } } else { // 隙間埋め const latest = logs.find(l => isTimeBefore(new Date(l.date * 1000), current)); - const data = latest ? Chart.convertFlattenColumnsToObject(latest as Record) : null; + const data = latest ? Chart.convertFlattenColumnsToObject(latest) as T : null; const newLog = this.getNewLog(data); if (logsForEachDays[currentDayIndex]) { logsForEachDays[currentDayIndex].unshift(newLog); @@ -516,7 +515,7 @@ export default abstract class Chart> { for (const logs of logsForEachDays) { const log = this.aggregate(logs); - chart.unshift(Chart.countUniqueFields(log)); + chart.unshift(Chart.countUniqueFields(log) as T); } } From ffb9646ce9c3d2326a3e922e58702674eb65646c Mon Sep 17 00:00:00 2001 From: nullobsi Date: Thu, 27 May 2021 17:38:09 -0700 Subject: [PATCH 11/28] Add image description support (#7518) * recieve image descriptions under the name property * fix other components * use comment for alt and title * allow editing of file comment * allow editing of file comment in note dialog * federate note comments * use file instead of this * backend should accept comment on update * update now actually accepts comment * allow multiline descriptions * image should also have description attached * Update locales/ja-JP.yml Co-authored-by: rinsuki <428rinsuki+git@gmail.com> * Use custom component with side-by-side image * improve usability on mobile devices * revert changes * Update post-form-attaches.vue * Update drive.file.vue * Update media-caption.vue Co-authored-by: rinsuki <428rinsuki+git@gmail.com> Co-authored-by: syuilo --- locales/ja-JP.yml | 3 + src/client/components/drive.file.vue | 24 ++ src/client/components/image-viewer.vue | 2 +- src/client/components/media-caption.vue | 238 ++++++++++++++++++ src/client/components/media-image.vue | 4 +- src/client/components/post-form-attaches.vue | 25 ++ src/remote/activitypub/models/image.ts | 2 +- src/remote/activitypub/renderer/document.ts | 3 +- src/remote/activitypub/renderer/image.ts | 3 +- .../api/endpoints/drive/files/update.ts | 11 + src/services/drive/upload-from-url.ts | 6 + 11 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 src/client/components/media-caption.vue diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e869f5b015..23f3bf7296 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -279,6 +279,7 @@ emptyDrive: "ドライブは空です" emptyFolder: "フォルダーは空です" unableToDelete: "削除できません" inputNewFileName: "新しいファイル名を入力してください" +inputNewDescription: "新しいキャプションを入力してください" inputNewFolderName: "新しいフォルダ名を入力してください" circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。" hasChildFilesOrFolders: "このフォルダは空でないため、削除できません。" @@ -546,6 +547,8 @@ disablePlayer: "プレイヤーを閉じる" expandTweet: "ツイートを展開する" themeEditor: "テーマエディター" description: "説明" +describeFile: "キャプションを付ける" +enterFileDescription: "キャプションを入力" author: "作者" leaveConfirm: "未保存の変更があります。破棄しますか?" manage: "管理" 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/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 @@
{{ image.name }}
- +