messaaging-room.form.vue rewrite to compositon api
This commit is contained in:
		
							parent
							
								
									9923cfaf50
								
							
						
					
					
						commit
						19af8e845f
					
				
					 4 changed files with 197 additions and 201 deletions
				
			
		|  | @ -17,7 +17,7 @@ import MkWindow from '@/components/ui/window.vue'; | ||||||
| import MkEmojiPicker from '@/components/emoji-picker.vue'; | import MkEmojiPicker from '@/components/emoji-picker.vue'; | ||||||
| 
 | 
 | ||||||
| withDefaults(defineProps<{ | withDefaults(defineProps<{ | ||||||
| 	src?: HTMLElement; | 	src?: HTMLElement | EventTarget; | ||||||
| 	showPinned?: boolean; | 	showPinned?: boolean; | ||||||
| 	asReactionPicker?: boolean; | 	asReactionPicker?: boolean; | ||||||
| }>(), { | }>(), { | ||||||
|  |  | ||||||
|  | @ -422,7 +422,7 @@ type AwaitType<T> = | ||||||
| 	T; | 	T; | ||||||
| let openingEmojiPicker: AwaitType<ReturnType<typeof popup>> | null = null; | let openingEmojiPicker: AwaitType<ReturnType<typeof popup>> | null = null; | ||||||
| let activeTextarea: HTMLTextAreaElement | HTMLInputElement | null = null; | let activeTextarea: HTMLTextAreaElement | HTMLInputElement | null = null; | ||||||
| export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: typeof activeTextarea) { | export async function openEmojiPicker(src?: HTMLElement | EventTarget | null, opts, initialTextarea: typeof activeTextarea) { | ||||||
| 	if (openingEmojiPicker) return; | 	if (openingEmojiPicker) return; | ||||||
| 
 | 
 | ||||||
| 	activeTextarea = initialTextarea; | 	activeTextarea = initialTextarea; | ||||||
|  |  | ||||||
|  | @ -4,219 +4,212 @@ | ||||||
| 	@drop.stop="onDrop" | 	@drop.stop="onDrop" | ||||||
| > | > | ||||||
| 	<textarea | 	<textarea | ||||||
| 		ref="text" | 		ref="textEl" | ||||||
| 		v-model="text" | 		v-model="text" | ||||||
| 		:placeholder="$ts.inputMessageHere" | 		:placeholder="i18n.locale.inputMessageHere" | ||||||
| 		@keydown="onKeydown" | 		@keydown="onKeydown" | ||||||
| 		@compositionupdate="onCompositionUpdate" | 		@compositionupdate="onCompositionUpdate" | ||||||
| 		@paste="onPaste" | 		@paste="onPaste" | ||||||
| 	></textarea> | 	></textarea> | ||||||
| 	<div v-if="file" class="file" @click="file = null">{{ file.name }}</div> | 	<div v-if="file" class="file" @click="file = null">{{ file.name }}</div> | ||||||
| 	<button class="send _button" :disabled="!canSend || sending" :title="$ts.send" @click="send"> | 	<button class="send _button" :disabled="!canSend || sending" :title="i18n.locale.send" @click="send"> | ||||||
| 		<template v-if="!sending"><i class="fas fa-paper-plane"></i></template><template v-if="sending"><i class="fas fa-spinner fa-pulse fa-fw"></i></template> | 		<template v-if="!sending"><i class="fas fa-paper-plane"></i></template><template v-if="sending"><i class="fas fa-spinner fa-pulse fa-fw"></i></template> | ||||||
| 	</button> | 	</button> | ||||||
| 	<button class="_button" @click="chooseFile"><i class="fas fa-photo-video"></i></button> | 	<button class="_button" @click="chooseFile"><i class="fas fa-photo-video"></i></button> | ||||||
| 	<button class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> | 	<button class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> | ||||||
| 	<input ref="file" type="file" @change="onChangeFile"/> | 	<input ref="fileEl" type="file" @change="onChangeFile"/> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts" setup> | ||||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | import { onMounted, watch } from 'vue'; | ||||||
| import insertTextAtCursor from 'insert-text-at-cursor'; | import * as Misskey from 'misskey-js'; | ||||||
| import autosize from 'autosize'; | import autosize from 'autosize'; | ||||||
|  | import insertTextAtCursor from 'insert-text-at-cursor'; | ||||||
| import { formatTimeString } from '@/scripts/format-time-string'; | import { formatTimeString } from '@/scripts/format-time-string'; | ||||||
| import { selectFile } from '@/scripts/select-file'; | import { selectFile } from '@/scripts/select-file'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import { stream } from '@/stream'; | import { stream } from '@/stream'; | ||||||
| import { Autocomplete } from '@/scripts/autocomplete'; |  | ||||||
| import { throttle } from 'throttle-debounce'; | import { throttle } from 'throttle-debounce'; | ||||||
|  | import { defaultStore } from '@/store'; | ||||||
|  | import { i18n } from '@/i18n'; | ||||||
|  | import { Autocomplete } from '@/scripts/autocomplete'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | const props = defineProps<{ | ||||||
| 	props: { | 	user?: Misskey.entities.UserDetailed | null; | ||||||
| 		user: { | 	group?: Misskey.entities.UserGroup | null; | ||||||
| 			type: Object, | }>(); | ||||||
| 			requird: false, | 
 | ||||||
| 		}, | let textEl = $ref<HTMLTextAreaElement>(); | ||||||
| 		group: { | let fileEl = $ref<HTMLInputElement>(); | ||||||
| 			type: Object, | 
 | ||||||
| 			requird: false, | let text: string = $ref(''); | ||||||
| 		}, | let file: Misskey.entities.DriveFile | null = $ref(null); | ||||||
| 	}, | let sending = $ref(false); | ||||||
| 	data() { | const typing = throttle(3000, () => { | ||||||
| 		return { | 	stream.send('typingOnMessaging', props.user ? { partner: props.user.id } : { group: props.group?.id }); | ||||||
| 			text: null, | }); | ||||||
| 			file: null, | 
 | ||||||
| 			sending: false, | let draftKey = $computed(() => props.user ? 'user:' + props.user.id : 'group:' + props.group?.id); | ||||||
| 			typing: throttle(3000, () => { | let canSend = $computed(() => (text != null && text != '') || file != null); | ||||||
| 				stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id }); | 
 | ||||||
| 			}), | watch([$$(text), $$(file)], saveDraft); | ||||||
| 		}; | 
 | ||||||
| 	}, | onMounted(() => { | ||||||
| 	computed: { | 	autosize(textEl); | ||||||
| 		draftKey(): string { | 
 | ||||||
| 			return this.user ? 'user:' + this.user.id : 'group:' + this.group.id; | 	// TODO: detach when unmount | ||||||
| 		}, | 	// TODO | ||||||
| 		canSend(): boolean { | 	//new Autocomplete(textEl, this, { model: 'text' }); | ||||||
| 			return (this.text != null && this.text != '') || this.file != null; | 
 | ||||||
| 		}, | 	// 書きかけの投稿を復元 | ||||||
| 		room(): any { | 	const draft = JSON.parse(localStorage.getItem('message_drafts') || '{}')[draftKey]; | ||||||
| 			return this.$parent; | 	if (draft) { | ||||||
|  | 		text = draft.data.text; | ||||||
|  | 		file = draft.data.file; | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | async function onPaste(e: ClipboardEvent) { | ||||||
|  | 	if (!e.clipboardData) return; | ||||||
|  | 
 | ||||||
|  | 	const data = e.clipboardData; | ||||||
|  | 	const items = data.items; | ||||||
|  | 
 | ||||||
|  | 	if (items.length == 1) { | ||||||
|  | 		if (items[0].kind == 'file') { | ||||||
|  | 			const file = items[0].getAsFile(); | ||||||
|  | 			if (!file) return; | ||||||
|  | 			const lio = file.name.lastIndexOf('.'); | ||||||
|  | 			const ext = lio >= 0 ? file.name.slice(lio) : ''; | ||||||
|  | 			const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, '1')}${ext}`; | ||||||
|  | 			if (formatted) upload(file, formatted); | ||||||
| 		} | 		} | ||||||
| 	}, | 	} else { | ||||||
| 	watch: { | 		if (items[0].kind == 'file') { | ||||||
| 		text() { | 			os.alert({ | ||||||
| 			this.saveDraft(); | 				type: 'error', | ||||||
| 		}, | 				text: i18n.locale.onlyOneFileCanBeAttached | ||||||
| 		file() { |  | ||||||
| 			this.saveDraft(); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	mounted() { |  | ||||||
| 		autosize(this.$refs.text); |  | ||||||
| 
 |  | ||||||
| 		// TODO: detach when unmount |  | ||||||
| 		// TODO |  | ||||||
| 		//new Autocomplete(this.$refs.text, this, { model: 'text' }); |  | ||||||
| 
 |  | ||||||
| 		// 書きかけの投稿を復元 |  | ||||||
| 		const draft = JSON.parse(localStorage.getItem('message_drafts') || '{}')[this.draftKey]; |  | ||||||
| 		if (draft) { |  | ||||||
| 			this.text = draft.data.text; |  | ||||||
| 			this.file = draft.data.file; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	methods: { |  | ||||||
| 		async onPaste(e: ClipboardEvent) { |  | ||||||
| 			const data = e.clipboardData; |  | ||||||
| 			const items = data.items; |  | ||||||
| 
 |  | ||||||
| 			if (items.length == 1) { |  | ||||||
| 				if (items[0].kind == 'file') { |  | ||||||
| 					const file = items[0].getAsFile(); |  | ||||||
| 					const lio = file.name.lastIndexOf('.'); |  | ||||||
| 					const ext = lio >= 0 ? file.name.slice(lio) : ''; |  | ||||||
| 					const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.pastedFileName).replace(/{{number}}/g, '1')}${ext}`; |  | ||||||
| 					if (formatted) this.upload(file, formatted); |  | ||||||
| 				} |  | ||||||
| 			} else { |  | ||||||
| 				if (items[0].kind == 'file') { |  | ||||||
| 					os.alert({ |  | ||||||
| 						type: 'error', |  | ||||||
| 						text: this.$ts.onlyOneFileCanBeAttached |  | ||||||
| 					}); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onDragover(e) { |  | ||||||
| 			const isFile = e.dataTransfer.items[0].kind == 'file'; |  | ||||||
| 			const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; |  | ||||||
| 			if (isFile || isDriveFile) { |  | ||||||
| 				e.preventDefault(); |  | ||||||
| 				e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onDrop(e): void { |  | ||||||
| 			// ファイルだったら |  | ||||||
| 			if (e.dataTransfer.files.length == 1) { |  | ||||||
| 				e.preventDefault(); |  | ||||||
| 				this.upload(e.dataTransfer.files[0]); |  | ||||||
| 				return; |  | ||||||
| 			} else if (e.dataTransfer.files.length > 1) { |  | ||||||
| 				e.preventDefault(); |  | ||||||
| 				os.alert({ |  | ||||||
| 					type: 'error', |  | ||||||
| 					text: this.$ts.onlyOneFileCanBeAttached |  | ||||||
| 				}); |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			//#region ドライブのファイル |  | ||||||
| 			const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); |  | ||||||
| 			if (driveFile != null && driveFile != '') { |  | ||||||
| 				this.file = JSON.parse(driveFile); |  | ||||||
| 				e.preventDefault(); |  | ||||||
| 			} |  | ||||||
| 			//#endregion |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onKeydown(e) { |  | ||||||
| 			this.typing(); |  | ||||||
| 			if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) { |  | ||||||
| 				this.send(); |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onCompositionUpdate() { |  | ||||||
| 			this.typing(); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		chooseFile(e) { |  | ||||||
| 			selectFile(e.currentTarget || e.target, this.$ts.selectFile).then(file => { |  | ||||||
| 				this.file = file; |  | ||||||
| 			}); | 			}); | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		onChangeFile() { |  | ||||||
| 			this.upload((this.$refs.file as any).files[0]); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		upload(file: File, name?: string) { |  | ||||||
| 			os.upload(file, this.$store.state.uploadFolder, name).then(res => { |  | ||||||
| 				this.file = res; |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		send() { |  | ||||||
| 			this.sending = true; |  | ||||||
| 			os.api('messaging/messages/create', { |  | ||||||
| 				userId: this.user ? this.user.id : undefined, |  | ||||||
| 				groupId: this.group ? this.group.id : undefined, |  | ||||||
| 				text: this.text ? this.text : undefined, |  | ||||||
| 				fileId: this.file ? this.file.id : undefined |  | ||||||
| 			}).then(message => { |  | ||||||
| 				this.clear(); |  | ||||||
| 			}).catch(err => { |  | ||||||
| 				console.error(err); |  | ||||||
| 			}).then(() => { |  | ||||||
| 				this.sending = false; |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		clear() { |  | ||||||
| 			this.text = ''; |  | ||||||
| 			this.file = null; |  | ||||||
| 			this.deleteDraft(); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		saveDraft() { |  | ||||||
| 			const data = JSON.parse(localStorage.getItem('message_drafts') || '{}'); |  | ||||||
| 
 |  | ||||||
| 			data[this.draftKey] = { |  | ||||||
| 				updatedAt: new Date(), |  | ||||||
| 				data: { |  | ||||||
| 					text: this.text, |  | ||||||
| 					file: this.file |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			localStorage.setItem('message_drafts', JSON.stringify(data)); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		deleteDraft() { |  | ||||||
| 			const data = JSON.parse(localStorage.getItem('message_drafts') || '{}'); |  | ||||||
| 
 |  | ||||||
| 			delete data[this.draftKey]; |  | ||||||
| 
 |  | ||||||
| 			localStorage.setItem('message_drafts', JSON.stringify(data)); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		async insertEmoji(ev) { |  | ||||||
| 			os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function onDragover(e: DragEvent) { | ||||||
|  | 	if (!e.dataTransfer) return; | ||||||
|  | 
 | ||||||
|  | 	const isFile = e.dataTransfer.items[0].kind == 'file'; | ||||||
|  | 	const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; | ||||||
|  | 	if (isFile || isDriveFile) { | ||||||
|  | 		e.preventDefault(); | ||||||
|  | 		e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function onDrop(e: DragEvent): void { | ||||||
|  | 	if (!e.dataTransfer) return; | ||||||
|  | 
 | ||||||
|  | 	// ファイルだったら | ||||||
|  | 	if (e.dataTransfer.files.length == 1) { | ||||||
|  | 		e.preventDefault(); | ||||||
|  | 		upload(e.dataTransfer.files[0]); | ||||||
|  | 		return; | ||||||
|  | 	} else if (e.dataTransfer.files.length > 1) { | ||||||
|  | 		e.preventDefault(); | ||||||
|  | 		os.alert({ | ||||||
|  | 			type: 'error', | ||||||
|  | 			text: i18n.locale.onlyOneFileCanBeAttached | ||||||
|  | 		}); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	//#region ドライブのファイル | ||||||
|  | 	const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); | ||||||
|  | 	if (driveFile != null && driveFile != '') { | ||||||
|  | 		file = JSON.parse(driveFile); | ||||||
|  | 		e.preventDefault(); | ||||||
|  | 	} | ||||||
|  | 	//#endregion | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function onKeydown(e: KeyboardEvent) { | ||||||
|  | 	typing(); | ||||||
|  | 	if ((e.key === 'Enter') && (e.ctrlKey || e.metaKey) && canSend) { | ||||||
|  | 		send(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function onCompositionUpdate() { | ||||||
|  | 	typing(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function chooseFile(e: MouseEvent) { | ||||||
|  | 	selectFile(e.currentTarget || e.target, i18n.locale.selectFile).then(file => { | ||||||
|  | 		file = file; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function onChangeFile() { | ||||||
|  | 	if (fileEl?.files![0]) upload(fileEl.files[0]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function upload(fileToUpload: File, name?: string) { | ||||||
|  | 	os.upload(fileToUpload, defaultStore.state.uploadFolder, name).then(res => { | ||||||
|  | 		file = res; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function send() { | ||||||
|  | 	sending = true; | ||||||
|  | 	os.api('messaging/messages/create', { | ||||||
|  | 		userId: props.user ? props.user.id : undefined, | ||||||
|  | 		groupId: props.group ? props.group.id : undefined, | ||||||
|  | 		text: text ? text : undefined, | ||||||
|  | 		fileId: file ? file.id : undefined | ||||||
|  | 	}).then(message => { | ||||||
|  | 		clear(); | ||||||
|  | 	}).catch(err => { | ||||||
|  | 		console.error(err); | ||||||
|  | 	}).then(() => { | ||||||
|  | 		sending = false; | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function clear() { | ||||||
|  | 	text = ''; | ||||||
|  | 	file = null; | ||||||
|  | 	deleteDraft(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function saveDraft() { | ||||||
|  | 	const data = JSON.parse(localStorage.getItem('message_drafts') || '{}'); | ||||||
|  | 
 | ||||||
|  | 	data[draftKey] = { | ||||||
|  | 		updatedAt: new Date(), | ||||||
|  | 		data: { | ||||||
|  | 			text: text, | ||||||
|  | 			file: file | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	localStorage.setItem('message_drafts', JSON.stringify(data)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function deleteDraft() { | ||||||
|  | 	const data = JSON.parse(localStorage.getItem('message_drafts') || '{}'); | ||||||
|  | 
 | ||||||
|  | 	delete data[draftKey]; | ||||||
|  | 
 | ||||||
|  | 	localStorage.setItem('message_drafts', JSON.stringify(data)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function insertEmoji(ev: MouseEvent) { | ||||||
|  | 	os.openEmojiPicker(ev.currentTarget || ev.target, {}, textEl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | defineExpose({ | ||||||
|  | 	file, | ||||||
|  | 	upload, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ const props = defineProps<{ | ||||||
| 	groupId?: string; | 	groupId?: string; | ||||||
| }>(); | }>(); | ||||||
| 
 | 
 | ||||||
| let rootEl = $ref<Element>(); | let rootEl = $ref<HTMLDivElement>(); | ||||||
| let formEl = $ref<InstanceType<typeof XForm>>(); | let formEl = $ref<InstanceType<typeof XForm>>(); | ||||||
| let pagingComponent = $ref<InstanceType<typeof MkPagination>>(); | let pagingComponent = $ref<InstanceType<typeof MkPagination>>(); | ||||||
| 
 | 
 | ||||||
|  | @ -88,16 +88,18 @@ async function fetch() { | ||||||
| 	fetching = true; | 	fetching = true; | ||||||
| 
 | 
 | ||||||
| 	if (props.userAcct) { | 	if (props.userAcct) { | ||||||
| 		user = await os.api('users/show', Acct.parse(props.userAcct)); | 		const acct = Acct.parse(props.userAcct); | ||||||
|  | 		user = await os.api('users/show', { username: acct.username, host: acct.host || undefined }); | ||||||
| 		group = null; | 		group = null; | ||||||
| 		 | 		 | ||||||
| 		pagination = { | 		pagination = { | ||||||
| 			endpoint: 'messaging/messages', | 			endpoint: 'messaging/messages', | ||||||
|  | 			limit: 20, | ||||||
| 			params: { | 			params: { | ||||||
| 				userId: user.id, | 				userId: user.id, | ||||||
| 			}, | 			}, | ||||||
| 			reversed: true, | 			reversed: true, | ||||||
| 			pageEl: $$(rootEl), | 			pageEl: $$(rootEl).value, | ||||||
| 		}; | 		}; | ||||||
| 		connection = stream.useChannel('messaging', { | 		connection = stream.useChannel('messaging', { | ||||||
| 			otherparty: user.id, | 			otherparty: user.id, | ||||||
|  | @ -108,14 +110,15 @@ async function fetch() { | ||||||
| 
 | 
 | ||||||
| 		pagination = { | 		pagination = { | ||||||
| 			endpoint: 'messaging/messages', | 			endpoint: 'messaging/messages', | ||||||
|  | 			limit: 20, | ||||||
| 			params: { | 			params: { | ||||||
| 				groupId: group.id, | 				groupId: group?.id, | ||||||
| 			}, | 			}, | ||||||
| 			reversed: true, | 			reversed: true, | ||||||
| 			pageEl: $$(rootEl), | 			pageEl: $$(rootEl).value, | ||||||
| 		}; | 		}; | ||||||
| 		connection = stream.useChannel('messaging', { | 		connection = stream.useChannel('messaging', { | ||||||
| 			group: group.id, | 			group: group?.id, | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -124,7 +127,7 @@ async function fetch() { | ||||||
| 	connection.on('read', onRead); | 	connection.on('read', onRead); | ||||||
| 	connection.on('deleted', onDeleted); | 	connection.on('deleted', onDeleted); | ||||||
| 	connection.on('typers', typers => { | 	connection.on('typers', typers => { | ||||||
| 		typers = typers.filter(u => u.id !== $i.id); | 		typers = typers.filter(u => u.id !== $i?.id); | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	document.addEventListener('visibilitychange', onVisibilitychange); | 	document.addEventListener('visibilitychange', onVisibilitychange); | ||||||
|  | @ -182,7 +185,7 @@ function onMessage(message) { | ||||||
| 	const _isBottom = isBottom(rootEl, 64); | 	const _isBottom = isBottom(rootEl, 64); | ||||||
| 
 | 
 | ||||||
| 	pagingComponent.prepend(message); | 	pagingComponent.prepend(message); | ||||||
| 	if (message.userId != $i.id && !document.hidden) { | 	if (message.userId != $i?.id && !document.hidden) { | ||||||
| 		connection?.send('read', { | 		connection?.send('read', { | ||||||
| 			id: message.id | 			id: message.id | ||||||
| 		}); | 		}); | ||||||
|  | @ -193,7 +196,7 @@ function onMessage(message) { | ||||||
| 		nextTick(() => { | 		nextTick(() => { | ||||||
| 			scrollToBottom(); | 			scrollToBottom(); | ||||||
| 		}); | 		}); | ||||||
| 	} else if (message.userId != $i.id) { | 	} else if (message.userId != $i?.id) { | ||||||
| 		// Notify | 		// Notify | ||||||
| 		notifyNewMessage(); | 		notifyNewMessage(); | ||||||
| 	} | 	} | ||||||
|  | @ -251,7 +254,7 @@ function notifyNewMessage() { | ||||||
| function onVisibilitychange() { | function onVisibilitychange() { | ||||||
| 	if (document.hidden) return; | 	if (document.hidden) return; | ||||||
| 	for (const message of pagingComponent.items) { | 	for (const message of pagingComponent.items) { | ||||||
| 		if (message.userId !== $i.id && !message.isRead) { | 		if (message.userId !== $i?.id && !message.isRead) { | ||||||
| 			connection?.send('read', { | 			connection?.send('read', { | ||||||
| 				id: message.id | 				id: message.id | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue