feat(client): 自動でもっと見るオプション (#6403)
* wip
* ugokanai
* wip
* implement setting subscribing
* fix lint
* ✌️
* Update notifications.vue
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
			
			
This commit is contained in:
		
							parent
							
								
									eda7d60c26
								
							
						
					
					
						commit
						a1e0c866aa
					
				
					 11 changed files with 96 additions and 17 deletions
				
			
		|  | @ -509,6 +509,7 @@ addedRelays: "追加済みのリレー" | |||
| serviceworkerInfo: "プッシュ通知を行うには有効する必要があります。" | ||||
| deletedNote: "削除された投稿" | ||||
| invisibleNote: "非公開の投稿" | ||||
| enableInfiniteScroll: "自動でもっと見る" | ||||
| 
 | ||||
| _theme: | ||||
|   explore: "テーマを探す" | ||||
|  |  | |||
|  | @ -19,17 +19,17 @@ | |||
| 		@drop.prevent.stop="onDrop" | ||||
| 	> | ||||
| 		<div class="contents" ref="contents"> | ||||
| 			<div class="folders" ref="foldersContainer" v-if="folders.length > 0"> | ||||
| 			<div class="folders" ref="foldersContainer" v-show="folders.length > 0"> | ||||
| 				<x-folder v-for="f in folders" :key="f.id" class="folder" :folder="f" :select-mode="select === 'folder'" :is-selected="selectedFolders.some(x => x.id === f.id)" @chosen="chooseFolder"/> | ||||
| 				<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid --> | ||||
| 				<div class="padding" v-for="(n, i) in 16" :key="i"></div> | ||||
| 				<mk-button v-if="moreFolders">{{ $t('loadMore') }}</mk-button> | ||||
| 				<mk-button ref="moreFolders" v-if="moreFolders">{{ $t('loadMore') }}</mk-button> | ||||
| 			</div> | ||||
| 			<div class="files" ref="filesContainer" v-if="files.length > 0"> | ||||
| 			<div class="files" ref="filesContainer" v-show="files.length > 0"> | ||||
| 				<x-file v-for="file in files" :key="file.id" class="file" :file="file" :select-mode="select === 'file'" :is-selected="selectedFiles.some(x => x.id === file.id)" @chosen="chooseFile"/> | ||||
| 				<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid --> | ||||
| 				<div class="padding" v-for="(n, i) in 16" :key="i"></div> | ||||
| 				<mk-button v-if="moreFiles" @click="fetchMoreFiles">{{ $t('loadMore') }}</mk-button> | ||||
| 				<mk-button ref="loadMoreFiles" @click="fetchMoreFiles" v-show="moreFiles">{{ $t('loadMore') }}</mk-button> | ||||
| 			</div> | ||||
| 			<div class="empty" v-if="files.length == 0 && folders.length == 0 && !fetching"> | ||||
| 				<p v-if="draghover">{{ $t('empty-draghover') }}</p> | ||||
|  | @ -116,6 +116,13 @@ export default Vue.extend({ | |||
| 
 | ||||
| 			fetching: true, | ||||
| 
 | ||||
| 			ilFilesObserver: new IntersectionObserver( | ||||
| 				(entries) => entries.some((entry) => entry.isIntersecting) | ||||
| 				&& !this.fetching && this.moreFiles && | ||||
| 					this.fetchMoreFiles() | ||||
| 			), | ||||
| 			moreFilesElement: null as Element, | ||||
| 
 | ||||
| 			faAngleRight | ||||
| 		}; | ||||
| 	}, | ||||
|  | @ -127,6 +134,12 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		if (this.$store.state.device.enableInfiniteScroll && this.$refs.loadMoreFiles) { | ||||
| 			this.$nextTick(() => { | ||||
| 				this.ilFilesObserver.observe((this.$refs.loadMoreFiles as Vue).$el) | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		this.connection = this.$root.stream.useSharedConnection('drive'); | ||||
| 
 | ||||
| 		this.connection.on('fileCreated', this.onStreamDriveFileCreated); | ||||
|  | @ -143,8 +156,17 @@ export default Vue.extend({ | |||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	activated() { | ||||
| 		if (this.$store.state.device.enableInfiniteScroll) { | ||||
| 			this.$nextTick(() => { | ||||
| 				this.ilFilesObserver.observe((this.$refs.loadMoreFiles as Vue).$el) | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.dispose(); | ||||
| 		this.ilFilesObserver.disconnect(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
|  |  | |||
|  | @ -7,8 +7,8 @@ | |||
| 
 | ||||
| 	<mk-error v-if="error" @retry="init()"/> | ||||
| 
 | ||||
| 	<div v-if="more && reversed" style="margin-bottom: var(--margin);"> | ||||
| 		<button class="_panel _button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()"> | ||||
| 	<div v-show="more && reversed" style="margin-bottom: var(--margin);"> | ||||
| 		<button class="_panel _button" ref="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> | ||||
| 			<template v-if="!moreFetching">{{ $t('loadMore') }}</template> | ||||
| 			<template v-if="moreFetching"><mk-loading inline/></template> | ||||
| 		</button> | ||||
|  | @ -18,8 +18,8 @@ | |||
| 		<x-note :note="note" :detail="detail" :key="note._featuredId_ || note._prId_ || note.id"/> | ||||
| 	</x-list> | ||||
| 
 | ||||
| 	<div v-if="more && !reversed" style="margin-top: var(--margin);"> | ||||
| 		<button class="_panel _button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()"> | ||||
| 	<div v-show="more && !reversed" style="margin-top: var(--margin);"> | ||||
| 		<button class="_panel _button" ref="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> | ||||
| 			<template v-if="!moreFetching">{{ $t('loadMore') }}</template> | ||||
| 			<template v-if="moreFetching"><mk-loading inline/></template> | ||||
| 		</button> | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| 		<x-notification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/> | ||||
| 	</x-list> | ||||
| 
 | ||||
| 	<button class="_panel _button" v-if="more" @click="fetchMore" :disabled="moreFetching"> | ||||
| 	<button class="_panel _button" ref="loadMore" v-show="more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> | ||||
| 		<template v-if="!moreFetching">{{ $t('loadMore') }}</template> | ||||
| 		<template v-if="moreFetching"><mk-loading inline/></template> | ||||
| 	</button> | ||||
|  |  | |||
|  | @ -4,8 +4,8 @@ | |||
| 	<div class="empty" v-if="empty" key="_empty_"> | ||||
| 		<slot name="empty"></slot> | ||||
| 	</div> | ||||
| 	<div class="more" v-if="more" key="_more_"> | ||||
| 		<mk-button class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore()" primary> | ||||
| 	<div class="more" v-show="more" key="_more_"> | ||||
| 		<mk-button class="button" ref="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary> | ||||
| 			<template v-if="!moreFetching">{{ $t('loadMore') }}</template> | ||||
| 			<template v-if="moreFetching"><mk-loading inline/></template> | ||||
| 		</mk-button> | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ | |||
| 			</div> | ||||
| 			<mk-follow-button class="koudoku-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/> | ||||
| 		</div> | ||||
| 		<button class="more" :class="{ fetching: moreFetching }" v-if="more" @click="fetchMore()" :disabled="moreFetching"> | ||||
| 		<button class="more" ref="loadMore" :class="{ fetching: moreFetching }" v-show="more" :disabled="moreFetching"> | ||||
| 			<template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>{{ moreFetching ? $t('loading') : $t('loadMore') }} | ||||
| 		</button> | ||||
| 	</div> | ||||
|  |  | |||
|  | @ -15,8 +15,7 @@ | |||
| 				</div> | ||||
| 			</router-link> | ||||
| 		</div> | ||||
| 
 | ||||
| 		<button class="more _button" v-if="more" @click="fetchMore" :disabled="moreFetching"> | ||||
| 		<button class="more _button" ref="loadMore" v-show="more" @click="fetchMore" :disabled="moreFetching"> | ||||
| 			<template v-if="!moreFetching">{{ $t('loadMore') }}</template> | ||||
| 			<template v-if="moreFetching"><fa :icon="faSpinner" pulse fixed-width/></template> | ||||
| 		</button> | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
| 		<mk-loading v-if="fetching"/> | ||||
| 		<p class="empty" v-if="!fetching && messages.length == 0"><fa :icon="faInfoCircle"/>{{ $t('noMessagesYet') }}</p> | ||||
| 		<p class="no-history" v-if="!fetching && messages.length > 0 && !existMoreMessages"><fa :icon="faFlag"/>{{ $t('noMoreHistory') }}</p> | ||||
| 		<button class="more _button" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages"> | ||||
| 		<button class="more _button" ref="loadMore" :class="{ fetching: fetchingMoreMessages }" v-show="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages"> | ||||
| 			<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('loading') : $t('loadMore') }} | ||||
| 		</button> | ||||
| 		<x-list class="messages" :items="messages" v-slot="{ item: message }" direction="up" reversed> | ||||
|  | @ -40,7 +40,6 @@ import { faArrowCircleDown, faFlag, faUsers, faInfoCircle } from '@fortawesome/f | |||
| import XList from '../../components/date-separated-list.vue'; | ||||
| import XMessage from './messaging-room.message.vue'; | ||||
| import XForm from './messaging-room.form.vue'; | ||||
| import { url } from '../../config'; | ||||
| import parseAcct from '../../../misc/acct/parse'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
|  | @ -61,6 +60,13 @@ export default Vue.extend({ | |||
| 			connection: null, | ||||
| 			showIndicator: false, | ||||
| 			timer: null, | ||||
| 			ilObserver: new IntersectionObserver( | ||||
| 				(entries) => entries.some((entry) => entry.isIntersecting) | ||||
| 					&& !this.fetching | ||||
| 					&& !this.fetchingMoreMessages | ||||
| 					&& this.existMoreMessages | ||||
| 					&& this.fetchMoreMessages() | ||||
| 			), | ||||
| 			faArrowCircleDown, faFlag, faUsers, faInfoCircle | ||||
| 		}; | ||||
| 	}, | ||||
|  | @ -77,6 +83,9 @@ export default Vue.extend({ | |||
| 
 | ||||
| 	mounted() { | ||||
| 		this.fetch(); | ||||
| 		if (this.$store.state.device.enableInfiniteScroll) { | ||||
| 			this.$nextTick(() => this.ilObserver.observe(this.$refs.loadMore as Element)); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	beforeDestroy() { | ||||
|  | @ -85,6 +94,8 @@ export default Vue.extend({ | |||
| 		window.removeEventListener('scroll', this.onScroll); | ||||
| 
 | ||||
| 		document.removeEventListener('visibilitychange', this.onVisibilitychange); | ||||
| 
 | ||||
| 		this.ilObserver.disconnect(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
|  | @ -112,8 +123,12 @@ export default Vue.extend({ | |||
| 			document.addEventListener('visibilitychange', this.onVisibilitychange); | ||||
| 
 | ||||
| 			this.fetchMessages().then(() => { | ||||
| 				this.fetching = false; | ||||
| 				this.scrollToBottom(); | ||||
| 
 | ||||
| 				// もっと見るの交差検知を発火させないためにfetchは | ||||
| 				// スクロールが終わるまでfalseにしておく | ||||
| 				// scrollendのようなイベントはないのでsetTimeoutで | ||||
| 				setTimeout(() => this.fetching = false, 300); | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
|  |  | |||
|  | @ -67,6 +67,7 @@ | |||
| 				<template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template> | ||||
| 			</mk-switch> | ||||
| 			<mk-switch v-model="showFixedPostForm">{{ $t('showFixedPostForm') }}</mk-switch> | ||||
| 			<mk-switch v-model="enableInfiniteScroll">{{ $t('enableInfiniteScroll') }}</mk-switch> | ||||
| 			<mk-switch v-model="disablePagesScript">{{ $t('disablePagesScript') }}</mk-switch> | ||||
| 		</div> | ||||
| 		<div class="_content"> | ||||
|  | @ -182,6 +183,11 @@ export default Vue.extend({ | |||
| 			set(value) { this.$store.commit('device/set', { key: 'showFixedPostForm', value }); } | ||||
| 		}, | ||||
| 
 | ||||
| 		enableInfiniteScroll: { | ||||
| 			get() { return this.$store.state.device.enableInfiniteScroll; }, | ||||
| 			set(value) { this.$store.commit('device/setInfiniteScrollEnabling', value); } | ||||
| 		}, | ||||
| 
 | ||||
| 		sfxVolume: { | ||||
| 			get() { return this.$store.state.device.sfxVolume; }, | ||||
| 			set(value) { this.$store.commit('device/set', { key: 'sfxVolume', value: parseFloat(value, 10) }); } | ||||
|  |  | |||
|  | @ -15,6 +15,14 @@ export default (opts) => ({ | |||
| 			more: false, | ||||
| 			backed: false, | ||||
| 			isBackTop: false, | ||||
| 			ilObserver: new IntersectionObserver( | ||||
| 				(entries) => entries.some((entry) => entry.isIntersecting) | ||||
| 					&& !this.moreFetching | ||||
| 					&& !this.fetching | ||||
| 					&& this.fetchMore() | ||||
| 				), | ||||
| 			loadMoreElement: null as Element, | ||||
| 			unsubscribeInfiniteScrollMutation: null as any, | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
|  | @ -51,6 +59,29 @@ export default (opts) => ({ | |||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.$nextTick(() => { | ||||
| 			if (this.$refs.loadMore) { | ||||
| 				this.loadMoreElement = this.$refs.loadMore instanceof Element ? this.$refs.loadMore : this.$refs.loadMore.$el; | ||||
| 				if (this.$store.state.device.enableInfiniteScroll) this.ilObserver.observe(this.loadMoreElement); | ||||
| 				this.loadMoreElement.addEventListener('click', this.fetchMore); | ||||
| 
 | ||||
| 				this.unsubscribeInfiniteScrollMutation = this.$store.subscribe(mutation => { | ||||
| 					if (mutation.type !== 'device/setInfiniteScrollEnabling') return; | ||||
| 
 | ||||
| 					if (mutation.payload) return this.ilObserver.observe(this.loadMoreElement); | ||||
| 					return this.ilObserver.unobserve(this.loadMoreElement); | ||||
| 				}); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	beforeDestroy() { | ||||
| 		this.ilObserver.disconnect(); | ||||
| 		if (this.$refs.loadMore) this.loadMoreElement.removeEventListener('click', this.fetchMore); | ||||
| 		if (this.unsubscribeInfiniteScrollMutation) this.unsubscribeInfiniteScrollMutation(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		updateItem(i, item) { | ||||
| 			Vue.set((this as any).items, i, item); | ||||
|  |  | |||
|  | @ -56,6 +56,7 @@ export const defaultDeviceSettings = { | |||
| 	imageNewTab: false, | ||||
| 	showFixedPostForm: false, | ||||
| 	disablePagesScript: true, | ||||
| 	enableInfiniteScroll: true, | ||||
| 	roomGraphicsQuality: 'medium', | ||||
| 	roomUseOrthographicCamera: true, | ||||
| 	sfxVolume: 0.3, | ||||
|  | @ -333,6 +334,10 @@ export default () => new Vuex.Store({ | |||
| 				setUserData(state, x: { userId: string; data: any }) { | ||||
| 					state.userData[x.userId] = copy(x.data); | ||||
| 				}, | ||||
| 
 | ||||
| 				setInfiniteScrollEnabling(state, x: boolean) { | ||||
| 					state.enableInfiniteScroll = x; | ||||
| 				}, | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue