pages/messaging/messaging-room.vue

This commit is contained in:
tamaina 2022-01-27 18:15:49 +09:00
parent 47edc18931
commit a1f346a549
2 changed files with 220 additions and 276 deletions

View file

@ -62,10 +62,6 @@ function dragClear(fn) {
} }
export default defineComponent({ export default defineComponent({
provide: {
inWindow: true
},
props: { props: {
padding: { padding: {
type: Boolean, type: Boolean,

View file

@ -2,6 +2,7 @@
<div class="_section" <div class="_section"
@dragover.prevent.stop="onDragover" @dragover.prevent.stop="onDragover"
@drop.prevent.stop="onDrop" @drop.prevent.stop="onDrop"
ref="rootEl"
> >
<div class="_content mk-messaging-room"> <div class="_content mk-messaging-room">
<div class="body"> <div class="body">
@ -35,9 +36,10 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, markRaw } from 'vue'; import { computed, watch, onMounted, nextTick, onBeforeUnmount } from 'vue';
import XList from '@/components/date-separated-list.vue'; import * as Misskey from 'misskey-js';
import MkPagination from '@/components/ui/pagination.vue';
import XMessage from './messaging-room.message.vue'; import XMessage from './messaging-room.message.vue';
import XForm from './messaging-room.form.vue'; import XForm from './messaging-room.form.vue';
import * as Acct from 'misskey-js/built/acct'; import * as Acct from 'misskey-js/built/acct';
@ -47,126 +49,72 @@ import { stream } from '@/stream';
import { popout } from '@/scripts/popout'; import { popout } from '@/scripts/popout';
import * as sound from '@/scripts/sound'; import * as sound from '@/scripts/sound';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
import { $i } from '@/account';
import { router } from '@/router';
const Component = defineComponent({ const props = defineProps<{
components: { userAcct?: string;
XMessage, groupId?: string;
XForm, }>();
XList,
},
inject: ['inWindow'], let fetching = $ref(true);
let user: Misskey.entities.UserDetailed | null = $ref(null);
props: { let group: Misskey.entities.UserGroup | null = $ref(null);
userAcct: { let fetchingMoreMessages = $ref(false);
type: String, let messages = $ref<Misskey.entities.MessagingMessage[]>([]);
required: false, let existMoreMessages = $ref(false);
}, let connection: Misskey.ChannelConnection<Misskey.Channels['messaging']> | null = $ref(null);
groupId: { let showIndicator = $ref(false);
type: String, let timer: number | null = $ref(null);
required: false, const ilObserver = new IntersectionObserver(
},
},
data() {
return {
[symbols.PAGE_INFO]: computed(() => !this.fetching ? this.user ? {
userName: this.user,
avatar: this.user,
action: {
icon: 'fas fa-ellipsis-h',
handler: this.menu,
},
} : {
title: this.group.name,
icon: 'fas fa-users',
action: {
icon: 'fas fa-ellipsis-h',
handler: this.menu,
},
} : null),
fetching: true,
user: null,
group: null,
fetchingMoreMessages: false,
messages: [],
existMoreMessages: false,
connection: null,
showIndicator: false,
timer: null,
typers: [],
ilObserver: new IntersectionObserver(
(entries) => entries.some((entry) => entry.isIntersecting) (entries) => entries.some((entry) => entry.isIntersecting)
&& !this.fetching && !fetching
&& !this.fetchingMoreMessages && !fetchingMoreMessages
&& this.existMoreMessages && existMoreMessages
&& this.fetchMoreMessages() && fetchMoreMessages()
), );
};
},
computed: { let rootEl = $ref<Element>();
form(): any { let form = $ref<InstanceType<typeof XForm>>();
return this.$refs.form; let loadMore = $ref<HTMLDivElement>();
}
},
watch: { watch([() => props.userAcct, () => props.groupId], () => {
userAcct: 'fetch', if (connection) connection.dispose();
groupId: 'fetch', fetch();
},
mounted() {
this.fetch();
if (this.$store.state.enableInfiniteScroll) {
this.$nextTick(() => this.ilObserver.observe(this.$refs.loadMore as Element));
}
},
beforeUnmount() {
this.connection.dispose();
document.removeEventListener('visibilitychange', this.onVisibilitychange);
this.ilObserver.disconnect();
},
methods: {
async fetch() {
this.fetching = true;
if (this.userAcct) {
const user = await os.api('users/show', Acct.parse(this.userAcct));
this.user = user;
} else {
const group = await os.api('users/groups/show', { groupId: this.groupId });
this.group = group;
}
this.connection = markRaw(stream.useChannel('messaging', {
otherparty: this.user ? this.user.id : undefined,
group: this.group ? this.group.id : undefined,
}));
this.connection.on('message', this.onMessage);
this.connection.on('read', this.onRead);
this.connection.on('deleted', this.onDeleted);
this.connection.on('typers', typers => {
this.typers = typers.filter(u => u.id !== this.$i.id);
}); });
document.addEventListener('visibilitychange', this.onVisibilitychange); async function fetch() {
fetching = true;
this.fetchMessages().then(() => { connection = stream.useChannel('messaging', {
this.scrollToBottom(); otherparty: user ? user.id : undefined,
group: group ? group.id : undefined,
});
connection?.on('message', onMessage);
connection?.on('read', onRead);
connection?.on('deleted', onDeleted);
connection?.on('typers', typers => {
typers = typers.filter(u => u.id !== $i.id);
});
document.addEventListener('visibilitychange', onVisibilitychange);
fetchMessages().then(() => {
scrollToBottom();
// fetch // fetch
// false // false
// scrollendsetTimeout // scrollendsetTimeout
window.setTimeout(() => this.fetching = false, 300); window.setTimeout(() => fetching = false, 300);
}); });
}, }
function onDragover(e: DragEvent) {
if (!e.dataTransfer) return;
onDragover(e) {
const isFile = e.dataTransfer.items[0].kind == 'file'; const isFile = e.dataTransfer.items[0].kind == 'file';
const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
@ -175,17 +123,19 @@ const Component = defineComponent({
} else { } else {
e.dataTransfer.dropEffect = 'none'; e.dataTransfer.dropEffect = 'none';
} }
}, }
function onDrop(e: DragEvent): void {
if (!e.dataTransfer) return;
onDrop(e): void {
// //
if (e.dataTransfer.files.length == 1) { if (e.dataTransfer.files.length == 1) {
this.form.upload(e.dataTransfer.files[0]); form.upload(e.dataTransfer.files[0]);
return; return;
} else if (e.dataTransfer.files.length > 1) { } else if (e.dataTransfer.files.length > 1) {
os.alert({ os.alert({
type: 'error', type: 'error',
text: this.$ts.onlyOneFileCanBeAttached text: i18n.locale.onlyOneFileCanBeAttached
}); });
return; return;
} }
@ -194,153 +144,151 @@ const Component = defineComponent({
const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile != '') { if (driveFile != null && driveFile != '') {
const file = JSON.parse(driveFile); const file = JSON.parse(driveFile);
this.form.file = file; form.file = file;
} }
//#endregion //#endregion
},
fetchMessages() {
return new Promise((resolve, reject) => {
const max = this.existMoreMessages ? 20 : 10;
os.api('messaging/messages', {
userId: this.user ? this.user.id : undefined,
groupId: this.group ? this.group.id : undefined,
limit: max + 1,
untilId: this.existMoreMessages ? this.messages[0].id : undefined
}).then(messages => {
if (messages.length == max + 1) {
this.existMoreMessages = true;
messages.pop();
} else {
this.existMoreMessages = false;
} }
this.messages.unshift.apply(this.messages, messages.reverse()); function fetchMessages() {
return new Promise<void>((resolve, reject) => {
const max = existMoreMessages ? 20 : 10;
os.api('messaging/messages', {
userId: user ? user.id : undefined,
groupId: group ? group.id : undefined,
limit: max + 1,
untilId: existMoreMessages ? messages[0].id : undefined
}).then(messages => {
if (messages.length == max + 1) {
existMoreMessages = true;
messages.pop();
} else {
existMoreMessages = false;
}
messages.unshift.apply(messages, messages.reverse());
resolve(); resolve();
}); });
}); });
}, }
fetchMoreMessages() { function fetchMoreMessages() {
this.fetchingMoreMessages = true; fetchingMoreMessages = true;
this.fetchMessages().then(() => { fetchMessages().then(() => {
this.fetchingMoreMessages = false; fetchingMoreMessages = false;
}); });
}, }
onMessage(message) { function onMessage(message) {
sound.play('chat'); sound.play('chat');
const _isBottom = isBottom(this.$el, 64); const _isBottom = isBottom(rootEl, 64);
this.messages.push(message); messages.push(message);
if (message.userId != this.$i.id && !document.hidden) { if (message.userId != $i.id && !document.hidden) {
this.connection.send('read', { connection?.send('read', {
id: message.id id: message.id
}); });
} }
if (_isBottom) { if (_isBottom) {
// Scroll to bottom // Scroll to bottom
this.$nextTick(() => { nextTick(() => {
this.scrollToBottom(); scrollToBottom();
}); });
} else if (message.userId != this.$i.id) { } else if (message.userId != $i.id) {
// Notify // Notify
this.notifyNewMessage(); notifyNewMessage();
}
} }
},
onRead(x) { function onRead(x) {
if (this.user) { if (user) {
if (!Array.isArray(x)) x = [x]; if (!Array.isArray(x)) x = [x];
for (const id of x) { for (const id of x) {
if (this.messages.some(x => x.id == id)) { if (messages.some(x => x.id == id)) {
const exist = this.messages.map(x => x.id).indexOf(id); const exist = messages.map(x => x.id).indexOf(id);
this.messages[exist] = { messages[exist] = {
...this.messages[exist], ...messages[exist],
isRead: true, isRead: true,
}; };
} }
} }
} else if (this.group) { } else if (group) {
for (const id of x.ids) { for (const id of x.ids) {
if (this.messages.some(x => x.id == id)) { if (messages.some(x => x.id == id)) {
const exist = this.messages.map(x => x.id).indexOf(id); const exist = messages.map(x => x.id).indexOf(id);
this.messages[exist] = { messages[exist] = {
...this.messages[exist], ...messages[exist],
reads: [...this.messages[exist].reads, x.userId] reads: [...messages[exist].reads, x.userId]
}; };
} }
} }
} }
},
onDeleted(id) {
const msg = this.messages.find(m => m.id === id);
if (msg) {
this.messages = this.messages.filter(m => m.id !== msg.id);
} }
},
scrollToBottom() { function onDeleted(id) {
scroll(this.$el, { top: this.$el.offsetHeight }); const msg = messages.find(m => m.id === id);
}, if (msg) {
messages = messages.filter(m => m.id !== msg.id);
}
}
onIndicatorClick() { function scrollToBottom() {
this.showIndicator = false; scroll(rootEl, { top: rootEl.offsetHeight });
this.scrollToBottom(); }
},
notifyNewMessage() { function onIndicatorClick() {
this.showIndicator = true; showIndicator = false;
scrollToBottom();
}
onScrollBottom(this.$el, () => { function notifyNewMessage() {
this.showIndicator = false; showIndicator = true;
onScrollBottom(rootEl, () => {
showIndicator = false;
}); });
if (this.timer) window.clearTimeout(this.timer); if (timer) window.clearTimeout(timer);
timer = window.setTimeout(() => {
this.timer = window.setTimeout(() => { showIndicator = false;
this.showIndicator = false;
}, 4000); }, 4000);
}, }
onVisibilitychange() { function onVisibilitychange() {
if (document.hidden) return; if (document.hidden) return;
for (const message of this.messages) { for (const message of messages) {
if (message.userId !== this.$i.id && !message.isRead) { if (message.userId !== $i.id && !message.isRead) {
this.connection.send('read', { connection?.send('read', {
id: message.id id: message.id
}); });
} }
} }
},
menu(ev) {
const path = this.groupId ? `/my/messaging/group/${this.groupId}` : `/my/messaging/${this.userAcct}`;
os.popupMenu([this.inWindow ? undefined : {
text: this.$ts.openInWindow,
icon: 'fas fa-window-maximize',
action: () => {
os.pageWindow(path);
this.$router.back();
},
}, this.inWindow ? undefined : {
text: this.$ts.popout,
icon: 'fas fa-external-link-alt',
action: () => {
popout(path);
this.$router.back();
},
}], ev.currentTarget || ev.target);
} }
onMounted(() => {
fetch();
if (defaultStore.state.enableInfiniteScroll) {
nextTick(() => ilObserver.observe(loadMore));
} }
}); });
export default Component; onBeforeUnmount(() => {
connection?.dispose();
document.removeEventListener('visibilitychange', onVisibilitychange);
ilObserver.disconnect();
});
defineExpose({
[symbols.PAGE_INFO]: computed(() => !fetching ? user ? {
userName: user,
avatar: user,
} : {
title: group?.name,
icon: 'fas fa-users',
} : null),
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>