From 25d37302a8bfda954c7ede1e9d355db587c82228 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 20 Feb 2021 20:20:05 +0900 Subject: チャンネルで入力中ユーザーを表示するように、Chat UIでタイムラインでは投稿フォームを上に表示するように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/api/stream/channels/channel.ts | 39 +++++++++++++++++++++- .../api/stream/channels/games/reversi-game.ts | 2 +- src/server/api/stream/index.ts | 17 +++++++--- 3 files changed, 52 insertions(+), 6 deletions(-) (limited to 'src/server') diff --git a/src/server/api/stream/channels/channel.ts b/src/server/api/stream/channels/channel.ts index c24b3db937..aa570d1ef4 100644 --- a/src/server/api/stream/channels/channel.ts +++ b/src/server/api/stream/channels/channel.ts @@ -1,14 +1,17 @@ import autobind from 'autobind-decorator'; import Channel from '../channel'; -import { Notes } from '../../../../models'; +import { Notes, Users } from '../../../../models'; import { isMutedUserRelated } from '../../../../misc/is-muted-user-related'; import { PackedNote } from '../../../../models/repositories/note'; +import { User } from '../../../../models/entities/user'; export default class extends Channel { public readonly chName = 'channel'; public static shouldShare = false; public static requireCredential = false; private channelId: string; + private typers: Record = {}; + private emitTypersIntervalId: ReturnType; @autobind public async init(params: any) { @@ -16,6 +19,8 @@ export default class extends Channel { // Subscribe stream this.subscriber.on('notesStream', this.onNote); + this.subscriber.on(`channelStream:${this.channelId}`, this.onEvent); + this.emitTypersIntervalId = setInterval(this.emitTypers, 5000); } @autobind @@ -41,9 +46,41 @@ export default class extends Channel { this.send('note', note); } + @autobind + private onEvent(data: any) { + if (data.type === 'typing') { + const id = data.body; + const begin = this.typers[id] == null; + this.typers[id] = new Date(); + if (begin) { + this.emitTypers(); + } + } + } + + @autobind + private async emitTypers() { + const now = new Date(); + + // Remove not typing users + for (const [userId, date] of Object.entries(this.typers)) { + if (now.getTime() - date.getTime() > 5000) delete this.typers[userId]; + } + + const users = await Users.packMany(Object.keys(this.typers), null, { detail: false }); + + this.send({ + type: 'typers', + body: users, + }); + } + @autobind public dispose() { // Unsubscribe events this.subscriber.off('notesStream', this.onNote); + this.subscriber.off(`channelStream:${this.channelId}`, this.onEvent); + + clearInterval(this.emitTypersIntervalId); } } diff --git a/src/server/api/stream/channels/games/reversi-game.ts b/src/server/api/stream/channels/games/reversi-game.ts index ea62ab1e88..e1c2116ac6 100644 --- a/src/server/api/stream/channels/games/reversi-game.ts +++ b/src/server/api/stream/channels/games/reversi-game.ts @@ -15,7 +15,7 @@ export default class extends Channel { private gameId: ReversiGame['id'] | null = null; private watchers: Record = {}; - private emitWatchersIntervalId: any; + private emitWatchersIntervalId: ReturnType; @autobind public async init(params: any) { diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts index 5b975d07db..b04bed0c06 100644 --- a/src/server/api/stream/index.ts +++ b/src/server/api/stream/index.ts @@ -12,6 +12,7 @@ import { Users, Followings, Mutings, UserProfiles, ChannelFollowings } from '../ import { ApiError } from '../error'; import { AccessToken } from '../../../models/entities/access-token'; import { UserProfile } from '../../../models/entities/user-profile'; +import { publishChannelStream } from '../../../services/stream'; /** * Main stream connection @@ -27,10 +28,10 @@ export default class Connection { public subscriber: EventEmitter; private channels: Channel[] = []; private subscribingNotes: any = {}; - private followingClock: NodeJS.Timer; - private mutingClock: NodeJS.Timer; - private followingChannelsClock: NodeJS.Timer; - private userProfileClock: NodeJS.Timer; + private followingClock: ReturnType; + private mutingClock: ReturnType; + private followingChannelsClock: ReturnType; + private userProfileClock: ReturnType; constructor( wsConnection: websocket.connection, @@ -93,6 +94,7 @@ export default class Connection { case 'disconnect': this.onChannelDisconnectRequested(body); break; case 'channel': this.onChannelMessageRequested(body); break; case 'ch': this.onChannelMessageRequested(body); break; // alias + case 'typingOnChannel': this.typingOnChannel(body.channel); break; } } @@ -258,6 +260,13 @@ export default class Connection { } } + @autobind + private typingOnChannel(channel: ChannelModel['id']) { + if (this.user) { + publishChannelStream(channel, 'typing', this.user.id); + } + } + @autobind private async updateFollowing() { const followings = await Followings.find({ -- cgit v1.2.3-freya From f3aef8df756668847461a40110b44955090cb987 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 20 Feb 2021 22:28:53 +0900 Subject: タイムラインを特定の日付にジャンプする機能 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ja-JP.yml | 3 +++ src/client/ui/chat/index.vue | 23 +++++++++++++++++++---- src/client/ui/chat/timeline.vue | 17 ++++++++++++++++- src/server/api/endpoints/channels/timeline.ts | 2 +- 4 files changed, 39 insertions(+), 6 deletions(-) (limited to 'src/server') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e7057e3f89..e5700fe059 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -707,6 +707,9 @@ emailNotification: "メール通知" inChannelSearch: "チャンネル内検索" useReactionPickerForContextMenu: "右クリックでリアクションピッカーを開く" typingUsers: "{users}が入力中" +jumpToSpecifiedDate: "特定の日付にジャンプ" +showingPastTimeline: "過去のタイムラインを表示しています" +clear: "クリア" _email: _follow: diff --git a/src/client/ui/chat/index.vue b/src/client/ui/chat/index.vue index bd0fd324fa..26c81a1aa9 100644 --- a/src/client/ui/chat/index.vue +++ b/src/client/ui/chat/index.vue @@ -99,6 +99,9 @@
{{ instanceName }}
+ @@ -115,8 +118,8 @@
- - + + @@ -131,7 +134,7 @@ diff --git a/src/client/ui/chat/note.vue b/src/client/ui/chat/note.vue index 315f5c91e3..9312b99d27 100644 --- a/src/client/ui/chat/note.vue +++ b/src/client/ui/chat/note.vue @@ -1091,6 +1091,7 @@ export default defineComponent({ > .poll { font-size: 80%; + max-width: 500px; } > .renote { diff --git a/src/client/ui/chat/notes.vue b/src/client/ui/chat/notes.vue index fb9f8fe260..3a169cc20a 100644 --- a/src/client/ui/chat/notes.vue +++ b/src/client/ui/chat/notes.vue @@ -8,7 +8,7 @@
- + @@ -19,7 +19,7 @@
- + diff --git a/src/server/web/boot.js b/src/server/web/boot.js index 2bd306ea94..993b770ab1 100644 --- a/src/server/web/boot.js +++ b/src/server/web/boot.js @@ -11,6 +11,10 @@ 'use strict'; +window.onerror = (e) => { + document.documentElement.innerHTML = '問題が発生しました。'; +}; + // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので (async () => { const v = localStorage.getItem('v') || VERSION; -- cgit v1.2.3-freya From 3fa1d2bfc0aff0c94fd299f79e5e072161697bf3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 21 Feb 2021 13:38:03 +0900 Subject: fix for lint --- src/client/scripts/paging.ts | 2 -- src/server/api/stream/channels/messaging.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'src/server') diff --git a/src/client/scripts/paging.ts b/src/client/scripts/paging.ts index a8f122412c..6e3da94124 100644 --- a/src/client/scripts/paging.ts +++ b/src/client/scripts/paging.ts @@ -192,8 +192,6 @@ export default (opts) => ({ this.items = this.items.slice(-opts.displayLimit); this.more = true; } - } else { - } this.items.push(item); // TODO diff --git a/src/server/api/stream/channels/messaging.ts b/src/server/api/stream/channels/messaging.ts index 7279da3ece..4c41dc820b 100644 --- a/src/server/api/stream/channels/messaging.ts +++ b/src/server/api/stream/channels/messaging.ts @@ -98,7 +98,7 @@ export default class extends Channel { @autobind public dispose() { this.subscriber.off(this.subCh, this.onEvent); - + clearInterval(this.emitTypersIntervalId); } } -- cgit v1.2.3-freya