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 3d8430564..9e32e6e91 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 39bf6ef2d..dc419bd87 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 61f62dd5a..463c5fff5 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 727e0e384..35f9972ff 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 5cff3619f..816765af6 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 31b6ca95e..75308c095 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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 CS}B$Q 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 000000000..2c026a5f3 --- /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 4f01c7881..610678989 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 06f9cf780..ca637e3f3 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 7199183c6..49bf67849 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 9badd7a70..c7063b0aa 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 161419f89..6caf8eb8e 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 753eba2ba..c21e1ec2a 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 a4465d75c..19b95fc50 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 024ff1acb..04d335320 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 b159cf509..e6355b45b 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 18cfe5eee..407cce9e7 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 2dccf48d3..8f56fd74b 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 9f3323f62..832cce5ab 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 44bfd6c51..f1d55ee28 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 62c99d775..dc4d11ca4 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 37126fca1..dd329084a 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 c193e7dc7..b8039fb67 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 065059221..000000000 --- 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 785b1631d..1e825e0fe 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 13032cce0..2245a9d8a 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 31a322e6e..162ffe9c8 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 65843385b..7f6fa8272 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 6331b5bdf..2398e9920 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 9e32e6e91..9296aafc4 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 e2728d73f..92c31914c 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 8052c7148..a3f67ccb9 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 432d5017e..cba69cfdc 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 d1ae68b12..ed24a45f8 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 c8bb8d48c..d9176ea40 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 d956d33bd..4a554daa7 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 e869f5b01..23f3bf729 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 37b1afc1b..3d20de23e 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 ec22bd98e..7701ae926 100644 --- a/src/client/components/image-viewer.vue +++ b/src/client/components/image-viewer.vue @@ -2,7 +2,7 @@
{{ image.name }}
- +