wip?
This commit is contained in:
		
							parent
							
								
									364ac37c0a
								
							
						
					
					
						commit
						a8af328e5b
					
				
					 6 changed files with 128 additions and 80 deletions
				
			
		| 
						 | 
				
			
			@ -93,13 +93,19 @@ export default defineComponent({
 | 
			
		|||
		return () => h(
 | 
			
		||||
			defaultStore.state.animation ? TransitionGroup : 'div',
 | 
			
		||||
			defaultStore.state.animation ? {
 | 
			
		||||
					class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''),
 | 
			
		||||
					class: {
 | 
			
		||||
						'sqadhkmv': true,
 | 
			
		||||
						'noGap': props.noGap
 | 
			
		||||
					},
 | 
			
		||||
					name: 'list',
 | 
			
		||||
					tag: 'div',
 | 
			
		||||
					'data-direction': props.direction,
 | 
			
		||||
					'data-reversed': props.reversed ? 'true' : 'false',
 | 
			
		||||
				} : {
 | 
			
		||||
					class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''),
 | 
			
		||||
					class: {
 | 
			
		||||
						'sqadhkmv': true,
 | 
			
		||||
						'noGap': props.noGap
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			{ default: renderChildren });
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -117,24 +123,30 @@ export default defineComponent({
 | 
			
		|||
	> *:not(:last-child) {
 | 
			
		||||
		margin-bottom: var(--margin);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .list-move {
 | 
			
		||||
	
 | 
			
		||||
	&:not(.deny-move-transition) > * {
 | 
			
		||||
		transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .list-leave-active,
 | 
			
		||||
	> .list-enter-active {
 | 
			
		||||
		transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
 | 
			
		||||
	}
 | 
			
		||||
	> .list-leave-active {
 | 
			
		||||
		position: absolute;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&[data-direction="up"] {
 | 
			
		||||
		> .list-enter-from {
 | 
			
		||||
		> .list-enter-from,
 | 
			
		||||
		> .list-leave-to {
 | 
			
		||||
			opacity: 0;
 | 
			
		||||
			transform: translateY(64px);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	&[data-direction="down"] {
 | 
			
		||||
		> .list-enter-from {
 | 
			
		||||
		> .list-enter-from,
 | 
			
		||||
		> .list-leave-to {
 | 
			
		||||
			opacity: 0;
 | 
			
		||||
			transform: translateY(-64px);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,14 +15,14 @@
 | 
			
		|||
 | 
			
		||||
	<div v-else ref="rootEl">
 | 
			
		||||
		<div v-if="pagination.reversed" v-show="more" key="_more_" class="cxiknjgy _gap">
 | 
			
		||||
			<MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !props.disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
 | 
			
		||||
			<MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
 | 
			
		||||
				{{ $ts.loadMore }}
 | 
			
		||||
			</MkButton>
 | 
			
		||||
			<MkLoading v-else class="loading"/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<slot :items="items"></slot>
 | 
			
		||||
		<slot :items="items" :fetching="fetching || moreFetching"></slot>
 | 
			
		||||
		<div v-if="!pagination.reversed" v-show="more" key="_more_" class="cxiknjgy _gap">
 | 
			
		||||
			<MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !props.disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
 | 
			
		||||
			<MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
 | 
			
		||||
				{{ $ts.loadMore }}
 | 
			
		||||
			</MkButton>
 | 
			
		||||
			<MkLoading v-else class="loading"/>
 | 
			
		||||
| 
						 | 
				
			
			@ -31,12 +31,13 @@
 | 
			
		|||
</transition>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { computed, ComputedRef, isRef, markRaw, nextTick, onActivated, onDeactivated, onMounted, Ref, ref, watch } from 'vue';
 | 
			
		||||
import * as misskey from 'misskey-js';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottom } from '@/scripts/scroll';
 | 
			
		||||
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottom } from '@/scripts/scroll';
 | 
			
		||||
import MkButton from '@/components/ui/button.vue';
 | 
			
		||||
import { defaultStore } from '@/store';
 | 
			
		||||
 | 
			
		||||
const SECOND_FETCH_LIMIT = 30;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -58,9 +59,10 @@ export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints>
 | 
			
		|||
 | 
			
		||||
	offsetMode?: boolean;
 | 
			
		||||
 | 
			
		||||
	pageEl?: Element;
 | 
			
		||||
	pageEl?: HTMLElement;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
const props = withDefaults(defineProps<{
 | 
			
		||||
	pagination: Paging;
 | 
			
		||||
	disableAutoLoad?: boolean;
 | 
			
		||||
| 
						 | 
				
			
			@ -86,8 +88,19 @@ const backed = ref(false); // 遡り中か否か
 | 
			
		|||
const isBackTop = ref(false);
 | 
			
		||||
const empty = computed(() => items.value.length === 0);
 | 
			
		||||
const error = ref(false);
 | 
			
		||||
const {
 | 
			
		||||
	enableInfiniteScroll
 | 
			
		||||
} = defaultStore.reactiveState;
 | 
			
		||||
 | 
			
		||||
const contentEl = $computed(() => props.pagination.pageEl || rootEl);
 | 
			
		||||
const scrollableElement = $computed(() => {
 | 
			
		||||
	if (contentEl) {
 | 
			
		||||
		const container = getScrollContainer(contentEl);
 | 
			
		||||
		return container || contentEl;
 | 
			
		||||
	}
 | 
			
		||||
	return null;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const init = async (): Promise<void> => {
 | 
			
		||||
	queue.value = [];
 | 
			
		||||
| 
						 | 
				
			
			@ -99,19 +112,15 @@ const init = async (): Promise<void> => {
 | 
			
		|||
	}).then(res => {
 | 
			
		||||
		for (let i = 0; i < res.length; i++) {
 | 
			
		||||
			const item = res[i];
 | 
			
		||||
			/*if (props.pagination.reversed) {
 | 
			
		||||
				if (i === res.length - 2) item._shouldInsertAd_ = true;
 | 
			
		||||
			} else {*/
 | 
			
		||||
				if (i === 3) item._shouldInsertAd_ = true;
 | 
			
		||||
			/*}*/
 | 
			
		||||
			if (i === 3) item._shouldInsertAd_ = true;
 | 
			
		||||
		}
 | 
			
		||||
		if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) {
 | 
			
		||||
			res.pop();
 | 
			
		||||
			if (props.pagination.reversed) moreFetching.value = true;
 | 
			
		||||
			items.value = /*props.pagination.reversed ? [...res].reverse() : */res;
 | 
			
		||||
			items.value = res;
 | 
			
		||||
			more.value = true;
 | 
			
		||||
		} else {
 | 
			
		||||
			items.value = /*props.pagination.reversed ? [...res].reverse() : */res;
 | 
			
		||||
			items.value = res;
 | 
			
		||||
			more.value = false;
 | 
			
		||||
		}
 | 
			
		||||
		offset.value = res.length;
 | 
			
		||||
| 
						 | 
				
			
			@ -139,28 +148,57 @@ const fetchMore = async (): Promise<void> => {
 | 
			
		|||
		...(props.pagination.offsetMode ? {
 | 
			
		||||
			offset: offset.value,
 | 
			
		||||
		} : {
 | 
			
		||||
			untilId: /*props.pagination.reversed ? items.value[0].id : */items.value[items.value.length - 1].id,
 | 
			
		||||
			untilId: items.value[items.value.length - 1].id,
 | 
			
		||||
		}),
 | 
			
		||||
	}).then(res => {
 | 
			
		||||
		for (let i = 0; i < res.length; i++) {
 | 
			
		||||
			const item = res[i];
 | 
			
		||||
			/*if (props.pagination.reversed) {
 | 
			
		||||
				if (i === res.length - 9) item._shouldInsertAd_ = true;
 | 
			
		||||
			} else {*/
 | 
			
		||||
				if (i === 10) item._shouldInsertAd_ = true;
 | 
			
		||||
			//}
 | 
			
		||||
			if (i === 10) item._shouldInsertAd_ = true;
 | 
			
		||||
		}
 | 
			
		||||
		const 
 | 
			
		||||
 | 
			
		||||
		const reverseConcat = _res => {
 | 
			
		||||
			const oldHeight = scrollableElement ? scrollableElement.scrollHeight : getBodyScrollHeight();
 | 
			
		||||
			const oldScroll = scrollableElement ? scrollableElement.scrollTop : window.scrollY;
 | 
			
		||||
 | 
			
		||||
			items.value = items.value.concat(_res);
 | 
			
		||||
 | 
			
		||||
			return nextTick(() => {
 | 
			
		||||
				if (scrollableElement) {
 | 
			
		||||
					scroll(scrollableElement, { top: oldScroll + (scrollableElement.scrollHeight - oldHeight), behavior: 'instant' });
 | 
			
		||||
				} else {
 | 
			
		||||
					window.scrollY = oldScroll + (getBodyScrollHeight() - oldHeight);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return nextTick();
 | 
			
		||||
			});
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		if (res.length > SECOND_FETCH_LIMIT) {
 | 
			
		||||
			res.pop();
 | 
			
		||||
			items.value = items.value.concat(res);
 | 
			
		||||
			more.value = true;
 | 
			
		||||
 | 
			
		||||
			if (props.pagination.reversed) {
 | 
			
		||||
				reverseConcat(res).then(() => {
 | 
			
		||||
					more.value = true;
 | 
			
		||||
					moreFetching.value = false;
 | 
			
		||||
				});
 | 
			
		||||
			} else {
 | 
			
		||||
				items.value = items.value.concat(res);
 | 
			
		||||
				more.value = true;
 | 
			
		||||
				moreFetching.value = false;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			items.value = items.value.concat(res);
 | 
			
		||||
			more.value = false;
 | 
			
		||||
			if (props.pagination.reversed) {
 | 
			
		||||
				reverseConcat(res).then(() => {
 | 
			
		||||
					more.value = false;
 | 
			
		||||
					moreFetching.value = false;
 | 
			
		||||
				});
 | 
			
		||||
			} else {
 | 
			
		||||
				items.value = items.value.concat(res);
 | 
			
		||||
				more.value = false;
 | 
			
		||||
				moreFetching.value = false;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		offset.value += res.length;
 | 
			
		||||
		moreFetching.value = false;
 | 
			
		||||
	}, e => {
 | 
			
		||||
		moreFetching.value = false;
 | 
			
		||||
	});
 | 
			
		||||
| 
						 | 
				
			
			@ -195,16 +233,13 @@ const fetchMoreAhead = async (): Promise<void> => {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
const prepend = (item: Item, force = false): void => {
 | 
			
		||||
	console.log('prepend', item)
 | 
			
		||||
	// 初回表示時はunshiftだけでOK
 | 
			
		||||
	if (!rootEl) {
 | 
			
		||||
		items.value.unshift(item);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const el = props.pagination.pageEl || rootEl;
 | 
			
		||||
	const isTop = isBackTop.value || (props.pagination.reversed ? isBottom : isTopVisible)(el);
 | 
			
		||||
	console.log(isTop || force)
 | 
			
		||||
	const isTop = isBackTop.value || (props.pagination.reversed ? isBottom : isTopVisible)(contentEl);
 | 
			
		||||
 | 
			
		||||
	if (isTop || force) {
 | 
			
		||||
		// Prepend the item
 | 
			
		||||
| 
						 | 
				
			
			@ -221,7 +256,7 @@ const prepend = (item: Item, force = false): void => {
 | 
			
		|||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		queue.value.push(item);
 | 
			
		||||
		(props.pagination.reversed ? onScrollBottom : onScrollTop)(el, () => {
 | 
			
		||||
		(props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, () => {
 | 
			
		||||
			for (const item of queue.value) {
 | 
			
		||||
				prepend(item, true);
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -258,16 +293,7 @@ onDeactivated(() => {
 | 
			
		|||
	isBackTop.value = props.pagination.reversed ? window.scrollY >= (rootEl ? rootEl?.scrollHeight - window.innerHeight : 0) : window.scrollY === 0;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function getScrollableElement() {
 | 
			
		||||
	if (el) {
 | 
			
		||||
		const container = getScrollContainer(contentEl);
 | 
			
		||||
		return container || el;
 | 
			
		||||
	}
 | 
			
		||||
	return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toBottom() {
 | 
			
		||||
	const scrollableElement = getScrollableElement();
 | 
			
		||||
	if (scrollableElement) scrollToBottom(scrollableElement);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -247,8 +247,8 @@ export default defineComponent({
 | 
			
		|||
 | 
			
		||||
	> .file {
 | 
			
		||||
		padding: 8px;
 | 
			
		||||
		color: #444;
 | 
			
		||||
		background: #eee;
 | 
			
		||||
		color: var(--fg);
 | 
			
		||||
		background: transparent;
 | 
			
		||||
		cursor: pointer;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,8 +14,8 @@
 | 
			
		|||
					</div>
 | 
			
		||||
				</template>
 | 
			
		||||
 | 
			
		||||
				<template #default="{ items: messages }">
 | 
			
		||||
					<XList v-if="messages.length > 0" v-slot="{ item: message }" class="messages" :items="messages" direction="up" reversed>
 | 
			
		||||
				<template #default="{ items: messages, fetching }">
 | 
			
		||||
					<XList v-if="messages.length > 0" v-slot="{ item: message }" :class="{ messages: true, 'deny-move-transition': fetching }" :items="messages" direction="up" reversed>
 | 
			
		||||
						<XMessage :key="message.id" :message="message" :is-group="group != null"/>
 | 
			
		||||
					</XList>
 | 
			
		||||
				</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -30,12 +30,12 @@
 | 
			
		|||
				</I18n>
 | 
			
		||||
				<MkEllipsis/>
 | 
			
		||||
			</div>
 | 
			
		||||
			<transition :name="$store.state.animation ? 'fade' : ''">
 | 
			
		||||
				<div v-show="showIndicator" class="new-message">
 | 
			
		||||
					<button class="_buttonPrimary" @click="onIndicatorClick"><i class="fas fa-arrow-circle-down"></i>{{ i18n.locale.newMessageExists }}</button>
 | 
			
		||||
			<transition :name="animation ? 'fade' : ''">
 | 
			
		||||
				<div class="new-message" v-if="showIndicator">
 | 
			
		||||
					<button class="_buttonPrimary" @click="onIndicatorClick"><i class="fas fa-fw fa-arrow-circle-down"></i>{{ i18n.locale.newMessageExists }}</button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</transition>
 | 
			
		||||
			<XForm v-if="!fetching" ref="form" :user="user" :group="group" class="form"/>
 | 
			
		||||
			<XForm v-if="!fetching" ref="formEl" :user="user" :group="group" class="form"/>
 | 
			
		||||
		</footer>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -46,8 +46,7 @@ import { computed, watch, onMounted, nextTick, onBeforeUnmount } from 'vue';
 | 
			
		|||
import * as Misskey from 'misskey-js';
 | 
			
		||||
import * as Acct from 'misskey-js/built/acct';
 | 
			
		||||
import XList from '@/components/date-separated-list.vue';
 | 
			
		||||
import MkPagination from '@/components/ui/pagination.vue';
 | 
			
		||||
import { Paging } from '@/components/ui/pagination.vue';
 | 
			
		||||
import MkPagination, { Paging } from '@/components/ui/pagination.vue';
 | 
			
		||||
import XMessage from './messaging-room.message.vue';
 | 
			
		||||
import XForm from './messaging-room.form.vue';
 | 
			
		||||
import { isBottom, onScrollBottom, scroll } from '@/scripts/scroll';
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +56,7 @@ import * as sound from '@/scripts/sound';
 | 
			
		|||
import * as symbols from '@/symbols';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import { $i } from '@/account';
 | 
			
		||||
import { defaultStore } from '@/store';
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
	userAcct?: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +64,7 @@ const props = defineProps<{
 | 
			
		|||
}>();
 | 
			
		||||
 | 
			
		||||
let rootEl = $ref<Element>();
 | 
			
		||||
let form = $ref<InstanceType<typeof XForm>>();
 | 
			
		||||
let formEl = $ref<InstanceType<typeof XForm>>();
 | 
			
		||||
let pagingComponent = $ref<InstanceType<typeof MkPagination>>();
 | 
			
		||||
 | 
			
		||||
let fetching = $ref(true);
 | 
			
		||||
| 
						 | 
				
			
			@ -73,7 +73,9 @@ let group: Misskey.entities.UserGroup | null = $ref(null);
 | 
			
		|||
let typers: Misskey.entities.User[] = $ref([]);
 | 
			
		||||
let connection: Misskey.ChannelConnection<Misskey.Channels['messaging']> | null = $ref(null);
 | 
			
		||||
let showIndicator = $ref(false);
 | 
			
		||||
let timer: number | null = $ref(null);
 | 
			
		||||
const {
 | 
			
		||||
	animation
 | 
			
		||||
} = defaultStore.reactiveState;
 | 
			
		||||
 | 
			
		||||
let pagination: Paging | null = $ref(null);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -155,7 +157,7 @@ function onDrop(e: DragEvent): void {
 | 
			
		|||
 | 
			
		||||
	// ファイルだったら
 | 
			
		||||
	if (e.dataTransfer.files.length == 1) {
 | 
			
		||||
		form.upload(e.dataTransfer.files[0]);
 | 
			
		||||
		formEl.upload(e.dataTransfer.files[0]);
 | 
			
		||||
		return;
 | 
			
		||||
	} else if (e.dataTransfer.files.length > 1) {
 | 
			
		||||
		os.alert({
 | 
			
		||||
| 
						 | 
				
			
			@ -169,7 +171,7 @@ function onDrop(e: DragEvent): void {
 | 
			
		|||
	const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
 | 
			
		||||
	if (driveFile != null && driveFile != '') {
 | 
			
		||||
		const file = JSON.parse(driveFile);
 | 
			
		||||
		form.file = file;
 | 
			
		||||
		formEl.file = file;
 | 
			
		||||
	}
 | 
			
		||||
	//#endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -234,6 +236,7 @@ function scrollToBottom() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
function onIndicatorClick() {
 | 
			
		||||
	showIndicator = false;
 | 
			
		||||
	scrollToBottom();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -243,11 +246,6 @@ function notifyNewMessage() {
 | 
			
		|||
	onScrollBottom(rootEl, () => {
 | 
			
		||||
		showIndicator = false;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if (timer) window.clearTimeout(timer);
 | 
			
		||||
	timer = window.setTimeout(() => {
 | 
			
		||||
		showIndicator = false;
 | 
			
		||||
	}, 4000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onVisibilitychange() {
 | 
			
		||||
| 
						 | 
				
			
			@ -323,10 +321,15 @@ defineExpose({
 | 
			
		|||
 | 
			
		||||
	> footer {
 | 
			
		||||
		width: 100%;
 | 
			
		||||
		position: sticky;
 | 
			
		||||
		z-index: 2;
 | 
			
		||||
		bottom: 8px;
 | 
			
		||||
 | 
			
		||||
		@media (max-width: 500px) {
 | 
			
		||||
			bottom: 100px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		> .new-message {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			top: -48px;
 | 
			
		||||
			width: 100%;
 | 
			
		||||
			padding: 8px 0;
 | 
			
		||||
			text-align: center;
 | 
			
		||||
| 
						 | 
				
			
			@ -334,17 +337,14 @@ defineExpose({
 | 
			
		|||
			> button {
 | 
			
		||||
				display: inline-block;
 | 
			
		||||
				margin: 0;
 | 
			
		||||
				padding: 0 12px 0 30px;
 | 
			
		||||
				padding: 0 12px;
 | 
			
		||||
				line-height: 32px;
 | 
			
		||||
				font-size: 12px;
 | 
			
		||||
				border-radius: 16px;
 | 
			
		||||
 | 
			
		||||
				> i {
 | 
			
		||||
					position: absolute;
 | 
			
		||||
					top: 0;
 | 
			
		||||
					left: 10px;
 | 
			
		||||
					line-height: 32px;
 | 
			
		||||
					font-size: 16px;
 | 
			
		||||
					display: inline-block;
 | 
			
		||||
					margin-right: 8px;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,7 +71,7 @@ export class Storage<T extends StateDef> {
 | 
			
		|||
					}
 | 
			
		||||
					localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache));
 | 
			
		||||
				});
 | 
			
		||||
			}, 10);
 | 
			
		||||
			}, 1);
 | 
			
		||||
			// streamingのuser storage updateイベントを監視して更新
 | 
			
		||||
			connection?.on('registryUpdated', ({ scope, key, value }: { scope: string[], key: keyof T, value: T[typeof key]['default'] }) => {
 | 
			
		||||
				if (scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.state[key] === value) return;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,15 +34,16 @@ export function onScrollTop(el: HTMLElement, cb) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
export function onScrollBottom(el: HTMLElement, cb) {
 | 
			
		||||
	const container = getScrollContainer(el) || window;
 | 
			
		||||
	const container = getScrollContainer(el);
 | 
			
		||||
	const containerOrWindow = container || window;
 | 
			
		||||
	const onScroll = ev => {
 | 
			
		||||
		if (!document.body.contains(el)) return;
 | 
			
		||||
		if (isBottom(el)) {
 | 
			
		||||
		if (isScrollBottom(container)) {
 | 
			
		||||
			cb();
 | 
			
		||||
			container.removeEventListener('scroll', onScroll);
 | 
			
		||||
			containerOrWindow.removeEventListener('scroll', onScroll);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	container.addEventListener('scroll', onScroll, { passive: true });
 | 
			
		||||
	containerOrWindow.addEventListener('scroll', onScroll, { passive: true });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function scroll(el: HTMLElement, options: {
 | 
			
		||||
| 
						 | 
				
			
			@ -79,10 +80,19 @@ export function scrollToBottom(el: HTMLElement, options: { behavior?: ScrollBeha
 | 
			
		|||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
 | 
			
		||||
export function isBottom(el: HTMLElement, asobi = 1) {
 | 
			
		||||
	const container = getScrollContainer(el);
 | 
			
		||||
	if (container) return container.scrollHeight - Math.abs(container.scrollTop) <= container.clientHeight - asobi;
 | 
			
		||||
	return isScrollBottom(container, asobi);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://ja.javascript.info/size-and-scroll-window#ref-932
 | 
			
		||||
export function getBodyScrollHeight() {
 | 
			
		||||
	return Math.max(
 | 
			
		||||
		document.body.scrollHeight, document.documentElement.scrollHeight,
 | 
			
		||||
		document.body.offsetHeight, document.documentElement.offsetHeight,
 | 
			
		||||
		document.body.clientHeight, document.documentElement.clientHeight
 | 
			
		||||
	) - window.scrollY <= window.innerHeight - asobi;
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isScrollBottom(container?: HTMLElement | null, asobi = 1) {
 | 
			
		||||
	if (container) return container.scrollHeight - Math.abs(container.scrollTop) <= container.clientHeight + asobi;
 | 
			
		||||
	return getBodyScrollHeight() - window.scrollY <= window.innerHeight + asobi;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue