forked from FoundKeyGang/FoundKey
チャンネルで入力中ユーザーを表示するように、Chat UIでタイムラインでは投稿フォームを上に表示するように
This commit is contained in:
parent
5f1a6b6f64
commit
25d37302a8
11 changed files with 143 additions and 41 deletions
|
@ -706,6 +706,7 @@ receiveAnnouncementFromInstance: "インスタンスからのお知らせを受
|
||||||
emailNotification: "メール通知"
|
emailNotification: "メール通知"
|
||||||
inChannelSearch: "チャンネル内検索"
|
inChannelSearch: "チャンネル内検索"
|
||||||
useReactionPickerForContextMenu: "右クリックでリアクションピッカーを開く"
|
useReactionPickerForContextMenu: "右クリックでリアクションピッカーを開く"
|
||||||
|
typingUsers: "{users}が入力中"
|
||||||
|
|
||||||
_email:
|
_email:
|
||||||
_follow:
|
_follow:
|
||||||
|
|
|
@ -70,6 +70,7 @@ import * as os from '@/os';
|
||||||
import { selectFile } from '@/scripts/select-file';
|
import { selectFile } from '@/scripts/select-file';
|
||||||
import { notePostInterruptors, postFormActions } from '@/store';
|
import { notePostInterruptors, postFormActions } from '@/store';
|
||||||
import { isMobile } from '@/scripts/is-mobile';
|
import { isMobile } from '@/scripts/is-mobile';
|
||||||
|
import { throttle } from 'throttle-debounce';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
@ -144,6 +145,11 @@ export default defineComponent({
|
||||||
quoteId: null,
|
quoteId: null,
|
||||||
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
|
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
|
||||||
imeText: '',
|
imeText: '',
|
||||||
|
typing: throttle(3000, () => {
|
||||||
|
if (this.channel) {
|
||||||
|
os.stream.send('typingOnChannel', { channel: this.channel.id });
|
||||||
|
}
|
||||||
|
}),
|
||||||
postFormActions,
|
postFormActions,
|
||||||
faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faAt, faBiohazard, faPlug
|
faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faAt, faBiohazard, faPlug
|
||||||
};
|
};
|
||||||
|
@ -434,10 +440,12 @@ export default defineComponent({
|
||||||
onKeydown(e: KeyboardEvent) {
|
onKeydown(e: KeyboardEvent) {
|
||||||
if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post();
|
if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post();
|
||||||
if (e.which === 27) this.$emit('esc');
|
if (e.which === 27) this.$emit('esc');
|
||||||
|
this.typing();
|
||||||
},
|
},
|
||||||
|
|
||||||
onCompositionUpdate(e: CompositionEvent) {
|
onCompositionUpdate(e: CompositionEvent) {
|
||||||
this.imeText = e.data;
|
this.imeText = e.data;
|
||||||
|
this.typing();
|
||||||
},
|
},
|
||||||
|
|
||||||
onCompositionEnd(e: CompositionEvent) {
|
onCompositionEnd(e: CompositionEvent) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return h(TransitionGroup, {
|
return h(this.reversed ? 'div' : TransitionGroup, {
|
||||||
class: 'hmjzthxl',
|
class: 'hmjzthxl',
|
||||||
name: this.reversed ? 'list-reversed' : 'list',
|
name: this.reversed ? 'list-reversed' : 'list',
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
|
|
|
@ -114,14 +114,9 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="body">
|
|
||||||
<XTimeline v-if="tl.startsWith('channel:')" src="channel" :key="tl" :channel="tl.replace('channel:', '')"/>
|
<XTimeline class="body" v-if="tl.startsWith('channel:')" src="channel" :key="tl" :channel="tl.replace('channel:', '')"/>
|
||||||
<XTimeline v-else :src="tl" :key="tl"/>
|
<XTimeline class="body" v-else :src="tl" :key="tl"/>
|
||||||
</div>
|
|
||||||
<footer class="footer">
|
|
||||||
<XPostForm v-if="tl.startsWith('channel:')" :key="tl" :channel="tl.replace('channel:', '')"/>
|
|
||||||
<XPostForm v-else/>
|
|
||||||
</footer>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<XSide class="side" ref="side" @open="sideViewOpening = true" @close="sideViewOpening = false"/>
|
<XSide class="side" ref="side" @open="sideViewOpening = true" @close="sideViewOpening = false"/>
|
||||||
|
@ -143,7 +138,6 @@ import XWidgets from './widgets.vue';
|
||||||
import XCommon from '../_common_/common.vue';
|
import XCommon from '../_common_/common.vue';
|
||||||
import XSide from './side.vue';
|
import XSide from './side.vue';
|
||||||
import XTimeline from './timeline.vue';
|
import XTimeline from './timeline.vue';
|
||||||
import XPostForm from './post-form.vue';
|
|
||||||
import XHeaderClock from './header-clock.vue';
|
import XHeaderClock from './header-clock.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { router } from '@/router';
|
import { router } from '@/router';
|
||||||
|
@ -159,7 +153,6 @@ export default defineComponent({
|
||||||
XWidgets,
|
XWidgets,
|
||||||
XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる
|
XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる
|
||||||
XTimeline,
|
XTimeline,
|
||||||
XPostForm,
|
|
||||||
XHeaderClock,
|
XHeaderClock,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -584,16 +577,6 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .footer {
|
|
||||||
padding: 0 16px 16px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .body {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .side {
|
> .side {
|
||||||
|
|
|
@ -1010,7 +1010,7 @@ export default defineComponent({
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: block;
|
display: block;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 12px;
|
top: 0;
|
||||||
margin: 0 14px 0 0;
|
margin: 0 14px 0 0;
|
||||||
width: 46px;
|
width: 46px;
|
||||||
height: 46px;
|
height: 46px;
|
||||||
|
|
|
@ -65,6 +65,7 @@ import * as os from '@/os';
|
||||||
import { selectFile } from '@/scripts/select-file';
|
import { selectFile } from '@/scripts/select-file';
|
||||||
import { notePostInterruptors, postFormActions } from '@/store';
|
import { notePostInterruptors, postFormActions } from '@/store';
|
||||||
import { isMobile } from '@/scripts/is-mobile';
|
import { isMobile } from '@/scripts/is-mobile';
|
||||||
|
import { throttle } from 'throttle-debounce';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
@ -131,6 +132,11 @@ export default defineComponent({
|
||||||
quoteId: null,
|
quoteId: null,
|
||||||
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
|
recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'),
|
||||||
imeText: '',
|
imeText: '',
|
||||||
|
typing: throttle(3000, () => {
|
||||||
|
if (this.channel) {
|
||||||
|
os.stream.send('typingOnChannel', { channel: this.channel });
|
||||||
|
}
|
||||||
|
}),
|
||||||
postFormActions,
|
postFormActions,
|
||||||
faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faAt, faBiohazard, faPlug
|
faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faAt, faBiohazard, faPlug
|
||||||
};
|
};
|
||||||
|
@ -421,10 +427,12 @@ export default defineComponent({
|
||||||
onKeydown(e: KeyboardEvent) {
|
onKeydown(e: KeyboardEvent) {
|
||||||
if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post();
|
if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post();
|
||||||
if (e.which === 27) this.$emit('esc');
|
if (e.which === 27) this.$emit('esc');
|
||||||
|
this.typing();
|
||||||
},
|
},
|
||||||
|
|
||||||
onCompositionUpdate(e: CompositionEvent) {
|
onCompositionUpdate(e: CompositionEvent) {
|
||||||
this.imeText = e.data;
|
this.imeText = e.data;
|
||||||
|
this.typing();
|
||||||
},
|
},
|
||||||
|
|
||||||
onCompositionEnd(e: CompositionEvent) {
|
onCompositionEnd(e: CompositionEvent) {
|
||||||
|
|
|
@ -1,8 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="dbiokgaf">
|
<div class="dbiokgaf top" v-if="['home', 'local', 'social', 'global'].includes(src)">
|
||||||
|
<XPostForm/>
|
||||||
|
</div>
|
||||||
|
<div class="dbiokgaf tl" ref="body">
|
||||||
<div class="new" v-if="queue > 0" :style="{ width: width + 'px', [pagination.reversed ? 'bottom' : 'top']: pagination.reversed ? bottom + 'px' : top + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ $ts.newNoteRecived }}</button></div>
|
<div class="new" v-if="queue > 0" :style="{ width: width + 'px', [pagination.reversed ? 'bottom' : 'top']: pagination.reversed ? bottom + 'px' : top + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ $ts.newNoteRecived }}</button></div>
|
||||||
<XNotes class="tl" ref="tl" :pagination="pagination" @queue="queueUpdated" v-follow="pagination.reversed"/>
|
<XNotes class="tl" ref="tl" :pagination="pagination" @queue="queueUpdated" v-follow="pagination.reversed"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dbiokgaf bottom" v-if="src === 'channel'">
|
||||||
|
<div class="typers" v-if="typers.length > 0">
|
||||||
|
<I18n :src="$ts.typingUsers" text-tag="span" class="users">
|
||||||
|
<template #users>
|
||||||
|
<b v-for="user in typers" :key="user.id" class="user">{{ user.username }}</b>
|
||||||
|
</template>
|
||||||
|
</I18n>
|
||||||
|
<MkEllipsis/>
|
||||||
|
</div>
|
||||||
|
<XPostForm :channel="channel"/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -12,10 +26,12 @@ import * as os from '@/os';
|
||||||
import * as sound from '@/scripts/sound';
|
import * as sound from '@/scripts/sound';
|
||||||
import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
|
import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
|
||||||
import follow from '@/directives/follow-append';
|
import follow from '@/directives/follow-append';
|
||||||
|
import XPostForm from './post-form.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
XNotes
|
XNotes,
|
||||||
|
XPostForm,
|
||||||
},
|
},
|
||||||
|
|
||||||
directives: {
|
directives: {
|
||||||
|
@ -69,6 +85,7 @@ export default defineComponent({
|
||||||
width: 0,
|
width: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
|
typers: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -166,6 +183,9 @@ export default defineComponent({
|
||||||
channelId: this.channel
|
channelId: this.channel
|
||||||
});
|
});
|
||||||
this.connection.on('note', prepend);
|
this.connection.on('note', prepend);
|
||||||
|
this.connection.on('typers', typers => {
|
||||||
|
this.typers = this.$i ? typers.filter(u => u.id !== this.$i.id) : typers;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pagination = {
|
this.pagination = {
|
||||||
|
@ -190,21 +210,21 @@ export default defineComponent({
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
focus() {
|
focus() {
|
||||||
this.$refs.tl.focus();
|
this.$refs.body.focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
goTop() {
|
goTop() {
|
||||||
const container = getScrollContainer(this.$el);
|
const container = getScrollContainer(this.$refs.body);
|
||||||
container.scrollTop = 0;
|
container.scrollTop = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
queueUpdated(q) {
|
queueUpdated(q) {
|
||||||
if (this.$el.offsetWidth !== 0) {
|
if (this.$refs.body.offsetWidth !== 0) {
|
||||||
const rect = this.$el.getBoundingClientRect();
|
const rect = this.$refs.body.getBoundingClientRect();
|
||||||
const scrollTop = getScrollPosition(this.$el);
|
const scrollTop = getScrollPosition(this.$refs.body);
|
||||||
this.width = this.$el.offsetWidth;
|
this.width = this.$refs.body.offsetWidth;
|
||||||
this.top = rect.top + scrollTop;
|
this.top = rect.top + scrollTop;
|
||||||
this.bottom = this.$el.offsetHeight;
|
this.bottom = this.$refs.body.offsetHeight;
|
||||||
}
|
}
|
||||||
this.queue = q;
|
this.queue = q;
|
||||||
},
|
},
|
||||||
|
@ -213,11 +233,41 @@ export default defineComponent({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.dbiokgaf {
|
.dbiokgaf.top {
|
||||||
padding: 16px 0;
|
padding: 16px 16px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: これはノート追加アニメーションによるスクロール発生を抑えるために必要だが、position stickyが効かなくなるので、両者を両立させる良い方法を考える
|
.dbiokgaf.bottom {
|
||||||
overflow: hidden;
|
padding: 0 16px 16px 16px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> .typers {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
padding: 0 8px 0 8px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
background: var(--panel);
|
||||||
|
border-radius: 0 8px 0 0;
|
||||||
|
color: var(--fgTransparentWeak);
|
||||||
|
|
||||||
|
> .users {
|
||||||
|
> .user + .user:before {
|
||||||
|
content: ", ";
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .user:last-of-type:after {
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dbiokgaf.tl {
|
||||||
|
padding: 16px 0;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
> .new {
|
> .new {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import autobind from 'autobind-decorator';
|
import autobind from 'autobind-decorator';
|
||||||
import Channel from '../channel';
|
import Channel from '../channel';
|
||||||
import { Notes } from '../../../../models';
|
import { Notes, Users } from '../../../../models';
|
||||||
import { isMutedUserRelated } from '../../../../misc/is-muted-user-related';
|
import { isMutedUserRelated } from '../../../../misc/is-muted-user-related';
|
||||||
import { PackedNote } from '../../../../models/repositories/note';
|
import { PackedNote } from '../../../../models/repositories/note';
|
||||||
|
import { User } from '../../../../models/entities/user';
|
||||||
|
|
||||||
export default class extends Channel {
|
export default class extends Channel {
|
||||||
public readonly chName = 'channel';
|
public readonly chName = 'channel';
|
||||||
public static shouldShare = false;
|
public static shouldShare = false;
|
||||||
public static requireCredential = false;
|
public static requireCredential = false;
|
||||||
private channelId: string;
|
private channelId: string;
|
||||||
|
private typers: Record<User['id'], Date> = {};
|
||||||
|
private emitTypersIntervalId: ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public async init(params: any) {
|
public async init(params: any) {
|
||||||
|
@ -16,6 +19,8 @@ export default class extends Channel {
|
||||||
|
|
||||||
// Subscribe stream
|
// Subscribe stream
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
|
this.subscriber.on(`channelStream:${this.channelId}`, this.onEvent);
|
||||||
|
this.emitTypersIntervalId = setInterval(this.emitTypers, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
|
@ -41,9 +46,41 @@ export default class extends Channel {
|
||||||
this.send('note', note);
|
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
|
@autobind
|
||||||
public dispose() {
|
public dispose() {
|
||||||
// Unsubscribe events
|
// Unsubscribe events
|
||||||
this.subscriber.off('notesStream', this.onNote);
|
this.subscriber.off('notesStream', this.onNote);
|
||||||
|
this.subscriber.off(`channelStream:${this.channelId}`, this.onEvent);
|
||||||
|
|
||||||
|
clearInterval(this.emitTypersIntervalId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default class extends Channel {
|
||||||
|
|
||||||
private gameId: ReversiGame['id'] | null = null;
|
private gameId: ReversiGame['id'] | null = null;
|
||||||
private watchers: Record<User['id'], Date> = {};
|
private watchers: Record<User['id'], Date> = {};
|
||||||
private emitWatchersIntervalId: any;
|
private emitWatchersIntervalId: ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
public async init(params: any) {
|
public async init(params: any) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { Users, Followings, Mutings, UserProfiles, ChannelFollowings } from '../
|
||||||
import { ApiError } from '../error';
|
import { ApiError } from '../error';
|
||||||
import { AccessToken } from '../../../models/entities/access-token';
|
import { AccessToken } from '../../../models/entities/access-token';
|
||||||
import { UserProfile } from '../../../models/entities/user-profile';
|
import { UserProfile } from '../../../models/entities/user-profile';
|
||||||
|
import { publishChannelStream } from '../../../services/stream';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main stream connection
|
* Main stream connection
|
||||||
|
@ -27,10 +28,10 @@ export default class Connection {
|
||||||
public subscriber: EventEmitter;
|
public subscriber: EventEmitter;
|
||||||
private channels: Channel[] = [];
|
private channels: Channel[] = [];
|
||||||
private subscribingNotes: any = {};
|
private subscribingNotes: any = {};
|
||||||
private followingClock: NodeJS.Timer;
|
private followingClock: ReturnType<typeof setInterval>;
|
||||||
private mutingClock: NodeJS.Timer;
|
private mutingClock: ReturnType<typeof setInterval>;
|
||||||
private followingChannelsClock: NodeJS.Timer;
|
private followingChannelsClock: ReturnType<typeof setInterval>;
|
||||||
private userProfileClock: NodeJS.Timer;
|
private userProfileClock: ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
wsConnection: websocket.connection,
|
wsConnection: websocket.connection,
|
||||||
|
@ -93,6 +94,7 @@ export default class Connection {
|
||||||
case 'disconnect': this.onChannelDisconnectRequested(body); break;
|
case 'disconnect': this.onChannelDisconnectRequested(body); break;
|
||||||
case 'channel': this.onChannelMessageRequested(body); break;
|
case 'channel': this.onChannelMessageRequested(body); break;
|
||||||
case 'ch': this.onChannelMessageRequested(body); break; // alias
|
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
|
@autobind
|
||||||
private async updateFollowing() {
|
private async updateFollowing() {
|
||||||
const followings = await Followings.find({
|
const followings = await Followings.find({
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { ReversiGame } from '../models/entities/games/reversi/game';
|
||||||
import { UserGroup } from '../models/entities/user-group';
|
import { UserGroup } from '../models/entities/user-group';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { Antenna } from '../models/entities/antenna';
|
import { Antenna } from '../models/entities/antenna';
|
||||||
|
import { Channel } from '../models/entities/channel';
|
||||||
|
|
||||||
class Publisher {
|
class Publisher {
|
||||||
private publish = (channel: string, type: string | null, value?: any): void => {
|
private publish = (channel: string, type: string | null, value?: any): void => {
|
||||||
|
@ -38,6 +39,10 @@ class Publisher {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public publishChannelStream = (channelId: Channel['id'], type: string, value?: any): void => {
|
||||||
|
this.publish(`channelStream:${channelId}`, type, typeof value === 'undefined' ? null : value);
|
||||||
|
}
|
||||||
|
|
||||||
public publishUserListStream = (listId: UserList['id'], type: string, value?: any): void => {
|
public publishUserListStream = (listId: UserList['id'], type: string, value?: any): void => {
|
||||||
this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value);
|
this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value);
|
||||||
}
|
}
|
||||||
|
@ -84,6 +89,7 @@ export const publishMainStream = publisher.publishMainStream;
|
||||||
export const publishDriveStream = publisher.publishDriveStream;
|
export const publishDriveStream = publisher.publishDriveStream;
|
||||||
export const publishNoteStream = publisher.publishNoteStream;
|
export const publishNoteStream = publisher.publishNoteStream;
|
||||||
export const publishNotesStream = publisher.publishNotesStream;
|
export const publishNotesStream = publisher.publishNotesStream;
|
||||||
|
export const publishChannelStream = publisher.publishChannelStream;
|
||||||
export const publishUserListStream = publisher.publishUserListStream;
|
export const publishUserListStream = publisher.publishUserListStream;
|
||||||
export const publishAntennaStream = publisher.publishAntennaStream;
|
export const publishAntennaStream = publisher.publishAntennaStream;
|
||||||
export const publishMessagingStream = publisher.publishMessagingStream;
|
export const publishMessagingStream = publisher.publishMessagingStream;
|
||||||
|
|
Loading…
Reference in a new issue