チャンネルで入力中ユーザーを表示するように、Chat UIでタイムラインでは投稿フォームを上に表示するように

This commit is contained in:
syuilo 2021-02-20 20:20:05 +09:00
parent 5f1a6b6f64
commit 25d37302a8
11 changed files with 143 additions and 41 deletions

View file

@ -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<User['id'], Date> = {};
private emitTypersIntervalId: ReturnType<typeof setInterval>;
@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);
}
}

View file

@ -15,7 +15,7 @@ export default class extends Channel {
private gameId: ReversiGame['id'] | null = null;
private watchers: Record<User['id'], Date> = {};
private emitWatchersIntervalId: any;
private emitWatchersIntervalId: ReturnType<typeof setInterval>;
@autobind
public async init(params: any) {

View file

@ -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<typeof setInterval>;
private mutingClock: ReturnType<typeof setInterval>;
private followingChannelsClock: ReturnType<typeof setInterval>;
private userProfileClock: ReturnType<typeof setInterval>;
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({