enhance(frontend): disable horizontal swipe for timeline/notifications to improve ux
This commit is contained in:
parent
33e76f9dfc
commit
1af4081090
|
@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
|
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
|
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
|
||||||
import { onScrollTop, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible, isHeadVisible } from '@@/js/scroll.js';
|
import { onScrollTop, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scrollInContainer, isTailVisible, isHeadVisible } from '@@/js/scroll.js';
|
||||||
import type { ComputedRef } from 'vue';
|
import type { ComputedRef } from 'vue';
|
||||||
import { misskeyApi } from '@/misskey-api.js';
|
import { misskeyApi } from '@/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -252,7 +252,7 @@ const fetchMore = async (): Promise<void> => {
|
||||||
|
|
||||||
return nextTick(() => {
|
return nextTick(() => {
|
||||||
if (scrollableElement.value) {
|
if (scrollableElement.value) {
|
||||||
scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
|
scrollInContainer(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
|
||||||
} else {
|
} else {
|
||||||
window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
|
window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1
|
||||||
return removeListener;
|
return removeListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scroll(el: HTMLElement, options: ScrollToOptions | undefined) {
|
export function scrollInContainer(el: HTMLElement, options: ScrollToOptions | undefined) {
|
||||||
const container = getScrollContainer(el);
|
const container = getScrollContainer(el);
|
||||||
if (container == null) {
|
if (container == null) {
|
||||||
window.scroll(options);
|
window.scroll(options);
|
||||||
|
@ -108,7 +108,7 @@ export function scroll(el: HTMLElement, options: ScrollToOptions | undefined) {
|
||||||
* @param options Scroll options
|
* @param options Scroll options
|
||||||
*/
|
*/
|
||||||
export function scrollToTop(el: HTMLElement, options: { behavior?: ScrollBehavior; } = {}) {
|
export function scrollToTop(el: HTMLElement, options: { behavior?: ScrollBehavior; } = {}) {
|
||||||
scroll(el, { top: 0, ...options });
|
scrollInContainer(el, { top: 0, ...options });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -58,7 +58,8 @@ export default [
|
||||||
// location ... window.locationと衝突 or 紛らわしい
|
// location ... window.locationと衝突 or 紛らわしい
|
||||||
// document ... window.documentと衝突 or 紛らわしい
|
// document ... window.documentと衝突 or 紛らわしい
|
||||||
// history ... window.historyと衝突 or 紛らわしい
|
// history ... window.historyと衝突 or 紛らわしい
|
||||||
'id-denylist': ['warn', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history'],
|
// scroll ... window.scrollと衝突 or 紛らわしい
|
||||||
|
'id-denylist': ['warn', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history', 'scroll'],
|
||||||
'no-restricted-globals': [
|
'no-restricted-globals': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
@ -85,6 +86,10 @@ export default [
|
||||||
'name': 'history',
|
'name': 'history',
|
||||||
'message': 'Use `window.history`.',
|
'message': 'Use `window.history`.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'scroll',
|
||||||
|
'message': 'Use `window.scroll`.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': 'name',
|
'name': 'name',
|
||||||
'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',
|
'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',
|
||||||
|
|
|
@ -14,8 +14,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
>
|
>
|
||||||
<MkNoteSub v-if="appearNote.reply && !renoteCollapsed" :note="appearNote.reply" :class="$style.replyTo"/>
|
<MkNoteSub v-if="appearNote.reply && !renoteCollapsed" :note="appearNote.reply" :class="$style.replyTo"/>
|
||||||
<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
|
<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
|
||||||
<!--<div v-if="appearNote._prId_" class="tip"><i class="ti ti-speakerphone"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
|
|
||||||
<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
|
|
||||||
<div v-if="isRenote" :class="$style.renote">
|
<div v-if="isRenote" :class="$style.renote">
|
||||||
<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
|
<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
|
||||||
<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
|
<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
|
||||||
|
|
|
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch } from 'vue';
|
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
|
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
|
||||||
import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible } from '@@/js/scroll.js';
|
import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scrollInContainer, isTailVisible } from '@@/js/scroll.js';
|
||||||
import type { ComputedRef } from 'vue';
|
import type { ComputedRef } from 'vue';
|
||||||
import type { MisskeyEntity } from '@/types/date-separated-list.js';
|
import type { MisskeyEntity } from '@/types/date-separated-list.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
@ -258,7 +258,7 @@ const fetchMore = async (): Promise<void> => {
|
||||||
|
|
||||||
return nextTick(() => {
|
return nextTick(() => {
|
||||||
if (scrollableElement.value) {
|
if (scrollableElement.value) {
|
||||||
scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
|
scrollInContainer(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
|
||||||
} else {
|
} else {
|
||||||
window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
|
window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']">
|
<div ref="rootEl" :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']">
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="actions" :tabs="tabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="actions" :tabs="tabs"/></template>
|
||||||
<div :class="$style.body">
|
<div :class="$style.body">
|
||||||
|
@ -16,6 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useTemplateRef } from 'vue';
|
||||||
|
import { scrollInContainer } from '@@/js/scroll.js';
|
||||||
import type { PageHeaderItem } from '@/types/page-header.js';
|
import type { PageHeaderItem } from '@/types/page-header.js';
|
||||||
import type { Tab } from './MkPageHeader.tabs.vue';
|
import type { Tab } from './MkPageHeader.tabs.vue';
|
||||||
|
|
||||||
|
@ -31,6 +33,13 @@ const props = withDefaults(defineProps<{
|
||||||
});
|
});
|
||||||
|
|
||||||
const tab = defineModel<string>('tab');
|
const tab = defineModel<string>('tab');
|
||||||
|
const rootEl = useTemplateRef('rootEl');
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
scrollToTop: () => {
|
||||||
|
if (rootEl.value) scrollInContainer(rootEl.value, { top: 0, behavior: 'smooth' });
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, ref, useTemplateRef } from 'vue';
|
import { computed, watch, ref, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { scroll } from '@@/js/scroll.js';
|
import { scrollInContainer } from '@@/js/scroll.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkTimeline from '@/components/MkTimeline.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
@ -49,7 +49,7 @@ function queueUpdated(q) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function top() {
|
function top() {
|
||||||
scroll(rootEl.value, { top: 0 });
|
scrollInContainer(rootEl.value, { top: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function timetravel() {
|
async function timetravel() {
|
||||||
|
|
|
@ -6,7 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
|
||||||
<div v-if="tab === 'all'">
|
<div v-if="tab === 'all'">
|
||||||
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,7 +15,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-else-if="tab === 'directNotes'">
|
<div v-else-if="tab === 'directNotes'">
|
||||||
<MkNotes :pagination="directNotesPagination"/>
|
<MkNotes :pagination="directNotesPagination"/>
|
||||||
</div>
|
</div>
|
||||||
</MkHorizontalSwipe>
|
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,10 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PageWithHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true">
|
<PageWithHeader ref="rootEl" v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true">
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
|
|
||||||
<div ref="rootEl">
|
|
||||||
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
|
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
|
||||||
{{ i18n.ts._timelineDescription[src] }}
|
{{ i18n.ts._timelineDescription[src] }}
|
||||||
</MkInfo>
|
</MkInfo>
|
||||||
|
@ -27,15 +25,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@queue="queueUpdated"
|
@queue="queueUpdated"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</MkHorizontalSwipe>
|
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, provide, useTemplateRef, ref, onMounted, onActivated } from 'vue';
|
import { computed, watch, provide, useTemplateRef, ref, onMounted, onActivated } from 'vue';
|
||||||
import { scroll } from '@@/js/scroll.js';
|
|
||||||
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import type { BasicTimelineType } from '@/timelines.js';
|
import type { BasicTimelineType } from '@/timelines.js';
|
||||||
|
@ -133,7 +128,7 @@ function queueUpdated(q: number): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function top(): void {
|
function top(): void {
|
||||||
if (rootEl.value) scroll(rootEl.value, { top: 0, behavior: 'smooth' });
|
if (rootEl.value) rootEl.value.scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function chooseList(ev: MouseEvent): Promise<void> {
|
async function chooseList(ev: MouseEvent): Promise<void> {
|
||||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, ref, useTemplateRef } from 'vue';
|
import { computed, watch, ref, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { scroll } from '@@/js/scroll.js';
|
import { scrollInContainer } from '@@/js/scroll.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkTimeline from '@/components/MkTimeline.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
@ -54,7 +54,7 @@ function queueUpdated(q) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function top() {
|
function top() {
|
||||||
scroll(rootEl.value, { top: 0 });
|
scrollInContainer(rootEl.value, { top: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function settings() {
|
function settings() {
|
||||||
|
|
Loading…
Reference in New Issue