feat: スクロール時の要素表示(ヘッダー、フローティングボタン、ナビゲーションバー)をカスタマイズできるオプションを追加

This commit is contained in:
NoriDev 2023-08-05 00:30:29 +09:00
parent 93e65570ba
commit 5ad37c9700
13 changed files with 72 additions and 18 deletions

View file

@ -25,6 +25,9 @@
출시일: unreleased<br>
전체 변경 사항을 확인하려면, [CHANGELOG.md#2023xx](CHANGELOG.md#2023xx) 문서를 참고하십시오.
### General
- 스크롤 시 요소 표시(헤더, 플로팅 버튼, 탐색 모음)를 사용자화 할 수 있는 옵션 추가
### Client
- 이모티콘 피커의 검색 건수를 100개로 증가 (misskey-dev/misskey#11371)
- about-misskey 페이지에서 클라이언트 버전을 누르면 변경 사항을 볼 수 있음

View file

@ -1,5 +1,6 @@
---
_lang_: "English"
displayHeaderNavBarWhenScroll: "Show elements when scrolling (header, floating buttons, navigation bar)"
addSingle: "Add just one"
addMultiple: "Add multiple"
showRenoteConfirmPopup: "Show confirmation popup when renote"
@ -1174,6 +1175,13 @@ _cherrypick:
reactableRemoteReaction: "Allow remote custom emoji reactions to react if there is an emoji with the same name on this server."
showFollowingMessageInsteadOfButton: "Do not show the follow button in the notification field if you are already following someone"
mobileTimelineHeaderChange: "Timeline header design change in mobile environment"
_displayHeaderNavBarWhenScroll:
all: "Display all"
hideHeaderOnly: "Hide header only"
hideHeaderFloatBtn: "Hide header and floating buttons"
hideFloatBtnOnly: "Hide floating buttons only"
hideFloatBtnNavBar: "Hide floating buttons and navigation bar"
hide: "Hide All"
_bannerDisplay:
all: "All"
topBottom: "Top and Bottom"

9
locales/index.d.ts vendored
View file

@ -3,6 +3,7 @@
// Do not edit this file directly.
export interface Locale {
"_lang_": string;
"displayHeaderNavBarWhenScroll": string;
"addSingle": string;
"addMultiple": string;
"showRenoteConfirmPopup": string;
@ -1179,6 +1180,14 @@ export interface Locale {
"showFollowingMessageInsteadOfButton": string;
"mobileTimelineHeaderChange": string;
};
"_displayHeaderNavBarWhenScroll": {
"all": string;
"hideHeaderOnly": string;
"hideHeaderFloatBtn": string;
"hideFloatBtnOnly": string;
"hideFloatBtnNavBar": string;
"hide": string;
};
"_bannerDisplay": {
"all": string;
"topBottom": string;

View file

@ -1,5 +1,6 @@
_lang_: "日本語"
displayHeaderNavBarWhenScroll: "スクロール時の要素表示(ヘッダー、フローティングボタン、ナビゲーションバー)"
addSingle: "一つだけ追加"
addMultiple: "複数追加"
showRenoteConfirmPopup: "Renoteするときに確認ポップアップを表示"
@ -1177,6 +1178,14 @@ _cherrypick:
showFollowingMessageInsteadOfButton: "既にフォローしている場合、通知欄にフォローボタンを表示しない"
mobileTimelineHeaderChange: "モバイル環境でタイムラインのヘッダーデザインを変更"
_displayHeaderNavBarWhenScroll:
all: "全て表示"
hideHeaderOnly: "ヘッダーだけを隠す"
hideHeaderFloatBtn: "ヘッダーとフローティングボタンを隠す"
hideFloatBtnOnly: "フローティングボタンだけを隠す"
hideFloatBtnNavBar: "フローティングボタンとナビゲーションバーを隠す"
hide: "全て隠す"
_bannerDisplay:
all: "全て"
topBottom: "上部と下部"

View file

@ -1,5 +1,6 @@
---
_lang_: "한국어"
displayHeaderNavBarWhenScroll: "스크롤 시 요소 표시 (헤더, 플로팅 버튼, 탐색 모음)"
addSingle: "하나만 추가"
addMultiple: "여러 개 추가"
showRenoteConfirmPopup: "리노트할 때 확인 팝업 표시"
@ -1172,6 +1173,13 @@ _cherrypick:
reactableRemoteReaction: "서버에 리모트 이모지와 이름이 같은 이모지가 있으면 리모트 이모지에도 반응할 수 있음"
showFollowingMessageInsteadOfButton: "이미 팔로우한 경우 알림 필드에 팔로우 버튼을 표시하지 않음"
mobileTimelineHeaderChange: "모바일 환경에서 타임라인의 헤더 디자인을 변경"
_displayHeaderNavBarWhenScroll:
all: "모두 표시"
hideHeaderOnly: "헤더만 숨기기"
hideHeaderFloatBtn: "헤더와 플로팅 버튼 숨기기"
hideFloatBtnOnly: "플로팅 버튼만 숨기기"
hideFloatBtnNavBar: "플로팅 버튼과 탐색 모음 숨기기"
hide: "모두 숨기기"
_bannerDisplay:
all: "전부"
topBottom: "상단 및 하단"

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div ref="el" :class="$style.root">
<header :class="[$style.header, { [$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: showEl && isMobile && mainRouter.currentRoute.value.name === 'explore' }]" class="_button" :style="{ background: bg }" @click="showBody = !showBody">
<header :class="[$style.header, { [$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && mainRouter.currentRoute.value.name === 'explore' }]" class="_button" :style="{ background: bg }" @click="showBody = !showBody">
<div :class="$style.title"><div><slot name="header"></slot></div></div>
<div :class="$style.divider"></div>
<button class="_button" :class="$style.button">
@ -36,8 +36,6 @@ import { defaultStore } from '@/store';
import { deviceKind } from '@/scripts/device-kind';
import { eventBus } from '@/scripts/cherrypick/eventBus';
const isFriendly = ref(miLocalStorage.getItem('ui') === 'friendly');
const MOBILE_THRESHOLD = 500;
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);

View file

@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
<div style="display: flex; padding-bottom: 10px;">
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
<MkAvatar v-if="!defaultStore.state.hideAvatarsInNote" :class="[$style.avatar, { [$style.avatarReplyTo]: appearNote.reply, [$style.showEl]: !appearNote.reply && showEl && mainRouter.currentRoute.value.name === 'index', [$style.showElTab]: !appearNote.reply && showEl && mainRouter.currentRoute.value.name !== 'index' }]" :user="appearNote.user" link preview/>
<MkAvatar v-if="!defaultStore.state.hideAvatarsInNote" :class="[$style.avatar, { [$style.avatarReplyTo]: appearNote.reply, [$style.showEl]: !appearNote.reply && (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && mainRouter.currentRoute.value.name === 'index', [$style.showElTab]: !appearNote.reply && (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && mainRouter.currentRoute.value.name !== 'index' }]" :user="appearNote.user" link preview/>
<div :class="$style.main">
<MkNoteHeader :note="appearNote" :mini="true"/>
</div>

View file

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root">
<div style="display: flex; padding-bottom: 10px;">
<MkAvatar v-if="!defaultStore.state.hideAvatarsInNote" :class="[$style.avatar, { [$style.showEl]: showEl && mainRouter.currentRoute.value.name === 'index', [$style.showElTab]: showEl && mainRouter.currentRoute.value.name !== 'index' }]" :user="note.user" link preview/>
<MkAvatar v-if="!defaultStore.state.hideAvatarsInNote" :class="[$style.avatar, { [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && mainRouter.currentRoute.value.name === 'index', [$style.showElTab]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && mainRouter.currentRoute.value.name !== 'index' }]" :user="note.user" link preview/>
<div :class="$style.main">
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
</div>

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div ref="rootEl">
<div ref="headerEl" :class="[$style.root, {[$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: showEl && isMobile && isAllowHideHeader && mainRouter.currentRoute.value.name !== 'index', [$style.showElTl]: showEl && isMobile && isAllowHideHeader && mainRouter.currentRoute.value.name === 'index' }]">
<div ref="headerEl" :class="[$style.root, {[$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && isAllowHideHeader && mainRouter.currentRoute.value.name !== 'index', [$style.showElTl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && isAllowHideHeader && mainRouter.currentRoute.value.name === 'index' }]">
<slot name="header"></slot>
</div>
<div ref="bodyEl" :data-sticky-container-header-height="headerHeight">
@ -26,8 +26,7 @@ import { mainRouter } from '@/router';
import { defaultStore } from '@/store';
import { eventBus } from '@/scripts/cherrypick/eventBus';
const isAllowHideHeader = ref(mainRouter.currentRoute.value.name === 'index' || mainRouter.currentRoute.value.name === 'explore' || mainRouter.currentRoute.value.name === 'my-notifications' || mainRouter.currentRoute.value.name === 'my-favorites');
const isAllowHideHeader = ref(['index', 'explore', 'my-notifications', 'my-favorites'].includes(<string>mainRouter.currentRoute.value.name));
const MOBILE_THRESHOLD = 500;
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);

View file

@ -27,6 +27,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="showRenoteConfirmPopup">
<template #label>{{ i18n.ts.showRenoteConfirmPopup }}</template>
</MkSwitch>
<MkRadios v-model="displayHeaderNavBarWhenScroll">
<template #label>{{ i18n.ts.displayHeaderNavBarWhenScroll }}</template>
<option value="all">{{ i18n.ts._displayHeaderNavBarWhenScroll.all }}</option>
<option value="hideHeaderOnly">{{ i18n.ts._displayHeaderNavBarWhenScroll.hideHeaderOnly }}</option>
<option value="hideHeaderFloatBtn">{{ i18n.ts._displayHeaderNavBarWhenScroll.hideHeaderFloatBtn }}</option>
<option value="hideFloatBtnOnly">{{ i18n.ts._displayHeaderNavBarWhenScroll.hideFloatBtnOnly }}</option>
<option value="hideFloatBtnNavBar">{{ i18n.ts._displayHeaderNavBarWhenScroll.hideFloatBtnNavBar }}</option>
<option value="hide">{{ i18n.ts._displayHeaderNavBarWhenScroll.hide }}</option>
</MkRadios>
</div>
</div>
</FormSection>
@ -51,6 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, watch } from 'vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue';
import FormSection from '@/components/form/section.vue';
import { defaultStore } from '@/store';
import * as os from '@/os';
@ -77,6 +87,7 @@ const reactableRemoteReactionEnabled = computed(defaultStore.makeGetterSetter('r
const rememberPostFormToggleStateEnabled = computed(defaultStore.makeGetterSetter('rememberPostFormToggleStateEnabled'));
const showFollowingMessageInsteadOfButtonEnabled = computed(defaultStore.makeGetterSetter('showFollowingMessageInsteadOfButtonEnabled'));
const mobileTimelineHeaderChange = computed(defaultStore.makeGetterSetter('mobileTimelineHeaderChange'));
const displayHeaderNavBarWhenScroll = computed(defaultStore.makeGetterSetter('displayHeaderNavBarWhenScroll'));
watch([
infoButtonForNoteActionsEnabled,

View file

@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:enterFromClass="defaultStore.state.animation ? $style.transition_new_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_new_leaveTo : ''"
>
<div v-if="queue > 0 && defaultStore.state.newNoteReceivedNotificationBehavior === 'default'" :class="[$style.new, { [$style.showEl]: showEl && isMobile, [$style.reduceAnimation]: !defaultStore.state.animation }]"><button class="_buttonPrimary" :class="$style.newButton" @click="top()"><i class="ti ti-arrow-up"></i>{{ i18n.ts.newNoteRecived }}</button></div>
<div v-if="queue > 0 && defaultStore.state.newNoteReceivedNotificationBehavior === 'default'" :class="[$style.new, { [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile, [$style.reduceAnimation]: !defaultStore.state.animation }]"><button class="_buttonPrimary" :class="$style.newButton" @click="top()"><i class="ti ti-arrow-up"></i>{{ i18n.ts.newNoteRecived }}</button></div>
</transition>
<transition
:enterActiveClass="defaultStore.state.animation ? $style.transition_new_enterActive : ''"
@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:enterFromClass="defaultStore.state.animation ? $style.transition_new_enterFrom : ''"
:leaveToClass="defaultStore.state.animation ? $style.transition_new_leaveTo : ''"
>
<div v-if="queue > 0 && defaultStore.state.newNoteReceivedNotificationBehavior === 'count'" :class="[$style.new, { [$style.showEl]: showEl && isMobile, [$style.reduceAnimation]: !defaultStore.state.animation }]"><button class="_buttonPrimary" :class="$style.newButton" @click="top()"><i class="ti ti-arrow-up"></i><I18n :src="i18n.ts.newNoteRecivedCount" textTag="span"><template #n>{{ queue }}</template></I18n></button></div>
<div v-if="queue > 0 && defaultStore.state.newNoteReceivedNotificationBehavior === 'count'" :class="[$style.new, { [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile, [$style.reduceAnimation]: !defaultStore.state.animation }]"><button class="_buttonPrimary" :class="$style.newButton" @click="top()"><i class="ti ti-arrow-up"></i><I18n :src="i18n.ts.newNoteRecivedCount" textTag="span"><template #n>{{ queue }}</template></I18n></button></div>
</transition>
<div :class="$style.tl">
<MkTimeline

View file

@ -456,6 +456,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: true,
},
displayHeaderNavBarWhenScroll: {
where: 'device',
default: 'hideHeaderFloatBtn' as 'all' | 'hideHeaderOnly' | 'hideHeaderFloatBtn' | 'hideFloatBtnOnly' | 'hideFloatBtnNavBar' | 'hide',
},
infoButtonForNoteActionsEnabled: {
where: 'account',
default: true,

View file

@ -21,13 +21,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<XWidgets/>
</div>
<button v-if="isMobile && enableNavButton.includes(<string>mainRouter.currentRoute.value.name)" :class="[$style.floatNavButton, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect, [$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: showEl }]" class="_button" @click="drawerMenuShowing = true"><CPAvatar :class="$style.floatNavButtonAvatar" :user="$i"/></button>
<button v-if="isMobile && enableNavButton.includes(<string>mainRouter.currentRoute.value.name)" :class="[$style.floatNavButton, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect, [$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: (showEl && ['hideHeaderFloatBtn', 'hideFloatBtnOnly', 'hideFloatBtnNavBar', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) }]" class="_button" @click="drawerMenuShowing = true"><CPAvatar :class="$style.floatNavButtonAvatar" :user="$i"/></button>
<button v-if="isMobile && enablePostButton.includes(<string>mainRouter.currentRoute.value.name)" :class="[$style.floatPostButton, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect, [$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: showEl }]" :style="{ background: PostBg }" class="_button" @click="openMessage"><span :class="[$style.floatPostButtonBg, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect }]"></span><i v-if="mainRouter.currentRoute.value.name === 'messaging' && !(['messaging-room', 'messaging-room-group'].includes(<string>mainRouter.currentRoute.value.name))" class="ti ti-plus"></i><i v-else-if="enablePostButton.includes(<string>mainRouter.currentRoute.value.name)" class="ti ti-pencil"></i></button>
<button v-if="isMobile && enablePostButton.includes(<string>mainRouter.currentRoute.value.name)" :class="[$style.floatPostButton, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect, [$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: (showEl && ['hideHeaderFloatBtn', 'hideFloatBtnOnly', 'hideFloatBtnNavBar', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) }]" :style="{ background: PostBg }" class="_button" @click="openMessage"><span :class="[$style.floatPostButtonBg, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect }]"></span><i v-if="mainRouter.currentRoute.value.name === 'messaging' && !(['messaging-room', 'messaging-room-group'].includes(<string>mainRouter.currentRoute.value.name))" class="ti ti-plus"></i><i v-else-if="enablePostButton.includes(<string>mainRouter.currentRoute.value.name)" class="ti ti-pencil"></i></button>
<button v-if="!isDesktop && !isMobile" :class="[$style.widgetButton, { [$style.showEl]: showEl }]" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
<button v-if="!isDesktop && !isMobile" :class="[$style.widgetButton, { [$style.showEl]: (showEl && ['hideHeaderFloatBtn', 'hideFloatBtnOnly', 'hideFloatBtnNavBar', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) }]" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
<div v-if="isMobile" ref="navFooter" :class="[$style.nav, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect }]" :style="{ background: bg }">
<div v-if="isMobile" ref="navFooter" :class="[$style.nav, { [$style.reduceBlurEffect]: !defaultStore.state.useBlurEffect, [$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: (showEl && ['hideFloatBtnNavBar', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) }]" :style="{ background: bg }">
<!-- <button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button> -->
<button :class="[$style.navButton, { [$style.active]: mainRouter.currentRoute.value.name === 'index' }]" class="_button" @click="mainRouter.currentRoute.value.name === 'index' ? top() : mainRouter.replace('/')" @touchstart="openAccountMenu" @touchend="closeAccountMenu"><i :class="$style.navButtonIcon" class="ti ti-home"></i><span v-if="queue > 0" :class="$style.navButtonIndicatorHome"><i class="_indicatorCircle"></i></span></button>
<button :class="[$style.navButton, { [$style.active]: mainRouter.currentRoute.value.name === 'explore' }]" class="_button" @click="mainRouter.currentRoute.value.name === 'explore' ? top() : mainRouter.replace('/explore')"><i :class="$style.navButtonIcon" class="ti ti-hash"></i></button>
@ -464,10 +464,6 @@ $float-button-size: 65px;
&.reduceAnimation {
transition: opacity 0s, transform 0s;
}
&.showEl {
transform: translateX(-250px);
}
}
.floatNavButton {
@ -564,11 +560,20 @@ $float-button-size: 65px;
backdrop-filter: var(--blur, blur(15px));
border-top: solid 0.5px var(--divider);
padding: 0 10px;
transition: opacity 0.5s, transform 0.5s;
&.reduceBlurEffect {
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
&.reduceAnimation {
transition: opacity 0s, transform 0s;
}
&.showEl {
transform: translateY(50.55px);
}
}
.navButton {