feat(frontend): 横スワイプでタブを切り替える機能 (#13011)
* (add) 横スワイプでタブを切り替える機能 * Change Changelog * y方向の移動が一定量を超えたらスワイプを中断するように * Update swipe distance thresholds * Remove console.log * adjust threshold * rename, use v-model * fix * Update MkHorizontalSwipe.vue Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * use css module --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
parent
67a41c09ae
commit
c1019a006b
21 changed files with 685 additions and 414 deletions
|
@ -24,6 +24,7 @@
|
||||||
- Feat: 絵文字の詳細ダイアログを追加
|
- Feat: 絵文字の詳細ダイアログを追加
|
||||||
- Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加
|
- Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加
|
||||||
- デフォルトで枠線からはみ出る部分が隠されるようにしました。初期と同じ挙動にするには`$[border.noclip`が必要です
|
- デフォルトで枠線からはみ出る部分が隠されるようにしました。初期と同じ挙動にするには`$[border.noclip`が必要です
|
||||||
|
- Feat: スワイプでタブを切り替えられるように
|
||||||
- Enhance: MFM等のコードブロックに全文コピー用のボタンを追加
|
- Enhance: MFM等のコードブロックに全文コピー用のボタンを追加
|
||||||
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
||||||
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
|
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
|
||||||
|
|
1
locales/index.d.ts
vendored
1
locales/index.d.ts
vendored
|
@ -1204,6 +1204,7 @@ export interface Locale {
|
||||||
"ranking": string;
|
"ranking": string;
|
||||||
"lastNDays": string;
|
"lastNDays": string;
|
||||||
"backToTitle": string;
|
"backToTitle": string;
|
||||||
|
"enableHorizontalSwipe": string;
|
||||||
"_bubbleGame": {
|
"_bubbleGame": {
|
||||||
"howToPlay": string;
|
"howToPlay": string;
|
||||||
"_howToPlay": {
|
"_howToPlay": {
|
||||||
|
|
|
@ -1201,6 +1201,7 @@ replaying: "リプレイ中"
|
||||||
ranking: "ランキング"
|
ranking: "ランキング"
|
||||||
lastNDays: "直近{n}日"
|
lastNDays: "直近{n}日"
|
||||||
backToTitle: "タイトルへ"
|
backToTitle: "タイトルへ"
|
||||||
|
enableHorizontalSwipe: "スワイプしてタブを切り替える"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
|
|
209
packages/frontend/src/components/MkHorizontalSwipe.vue
Normal file
209
packages/frontend/src/components/MkHorizontalSwipe.vue
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="rootEl"
|
||||||
|
:class="[$style.transitionRoot, (defaultStore.state.animation && $style.enableAnimation)]"
|
||||||
|
@touchstart="touchStart"
|
||||||
|
@touchmove="touchMove"
|
||||||
|
@touchend="touchEnd"
|
||||||
|
>
|
||||||
|
<Transition
|
||||||
|
:class="[$style.transitionChildren, { [$style.swiping]: isSwipingForClass }]"
|
||||||
|
:enterActiveClass="$style.swipeAnimation_enterActive"
|
||||||
|
:leaveActiveClass="$style.swipeAnimation_leaveActive"
|
||||||
|
:enterFromClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_enterFrom : $style.swipeAnimationRight_enterFrom"
|
||||||
|
:leaveToClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_leaveTo : $style.swipeAnimationRight_leaveTo"
|
||||||
|
:style="`--swipe: ${pullDistance}px;`"
|
||||||
|
>
|
||||||
|
<!-- 【注意】slot内の最上位要素に動的にkeyを設定すること -->
|
||||||
|
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
|
||||||
|
<slot></slot>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, shallowRef, computed, nextTick, watch } from 'vue';
|
||||||
|
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
|
import { defaultStore } from '@/store.js';
|
||||||
|
|
||||||
|
const rootEl = shallowRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const tabModel = defineModel<string>('tab');
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
tabs: Tab[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'swiped', newKey: string, direction: 'left' | 'right'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// ▼ しきい値 ▼ //
|
||||||
|
|
||||||
|
// スワイプと判定される最小の距離
|
||||||
|
const MIN_SWIPE_DISTANCE = 50;
|
||||||
|
|
||||||
|
// スワイプ時の動作を発火する最小の距離
|
||||||
|
const SWIPE_DISTANCE_THRESHOLD = 125;
|
||||||
|
|
||||||
|
// スワイプを中断するY方向の移動距離
|
||||||
|
const SWIPE_ABORT_Y_THRESHOLD = 75;
|
||||||
|
|
||||||
|
// スワイプできる最大の距離
|
||||||
|
const MAX_SWIPE_DISTANCE = 150;
|
||||||
|
|
||||||
|
// ▲ しきい値 ▲ //
|
||||||
|
|
||||||
|
let startScreenX: number | null = null;
|
||||||
|
let startScreenY: number | null = null;
|
||||||
|
|
||||||
|
const currentTabIndex = computed(() => props.tabs.findIndex(tab => tab.key === tabModel.value));
|
||||||
|
|
||||||
|
const pullDistance = ref(0);
|
||||||
|
const isSwiping = ref(false);
|
||||||
|
const isSwipingForClass = ref(false);
|
||||||
|
let swipeAborted = false;
|
||||||
|
|
||||||
|
function touchStart(event: TouchEvent) {
|
||||||
|
if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
|
||||||
|
|
||||||
|
if (event.touches.length !== 1) return;
|
||||||
|
|
||||||
|
startScreenX = event.touches[0].screenX;
|
||||||
|
startScreenY = event.touches[0].screenY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchMove(event: TouchEvent) {
|
||||||
|
if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
|
||||||
|
|
||||||
|
if (event.touches.length !== 1) return;
|
||||||
|
|
||||||
|
if (startScreenX == null || startScreenY == null) return;
|
||||||
|
|
||||||
|
if (swipeAborted) return;
|
||||||
|
|
||||||
|
let distanceX = event.touches[0].screenX - startScreenX;
|
||||||
|
let distanceY = event.touches[0].screenY - startScreenY;
|
||||||
|
|
||||||
|
if (Math.abs(distanceY) > SWIPE_ABORT_Y_THRESHOLD) {
|
||||||
|
swipeAborted = true;
|
||||||
|
|
||||||
|
pullDistance.value = 0;
|
||||||
|
isSwiping.value = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
isSwipingForClass.value = false;
|
||||||
|
}, 400);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(distanceX) < MIN_SWIPE_DISTANCE) return;
|
||||||
|
if (Math.abs(distanceX) > MAX_SWIPE_DISTANCE) return;
|
||||||
|
|
||||||
|
if (currentTabIndex.value === 0 || props.tabs[currentTabIndex.value - 1].onClick) {
|
||||||
|
distanceX = Math.min(distanceX, 0);
|
||||||
|
}
|
||||||
|
if (currentTabIndex.value === props.tabs.length - 1 || props.tabs[currentTabIndex.value + 1].onClick) {
|
||||||
|
distanceX = Math.max(distanceX, 0);
|
||||||
|
}
|
||||||
|
if (distanceX === 0) return;
|
||||||
|
|
||||||
|
isSwiping.value = true;
|
||||||
|
isSwipingForClass.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
// グリッチを控えるため、1.5px以上の差がないと更新しない
|
||||||
|
if (Math.abs(distanceX - pullDistance.value) < 1.5) return;
|
||||||
|
pullDistance.value = distanceX;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchEnd(event: TouchEvent) {
|
||||||
|
if (swipeAborted) {
|
||||||
|
swipeAborted = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
|
||||||
|
|
||||||
|
if (event.touches.length !== 0) return;
|
||||||
|
|
||||||
|
if (startScreenX == null) return;
|
||||||
|
|
||||||
|
if (!isSwiping.value) return;
|
||||||
|
|
||||||
|
const distance = event.changedTouches[0].screenX - startScreenX;
|
||||||
|
|
||||||
|
if (Math.abs(distance) > SWIPE_DISTANCE_THRESHOLD) {
|
||||||
|
if (distance > 0) {
|
||||||
|
if (props.tabs[currentTabIndex.value - 1] && !props.tabs[currentTabIndex.value - 1].onClick) {
|
||||||
|
tabModel.value = props.tabs[currentTabIndex.value - 1].key;
|
||||||
|
emit('swiped', props.tabs[currentTabIndex.value - 1].key, 'right');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.tabs[currentTabIndex.value + 1] && !props.tabs[currentTabIndex.value + 1].onClick) {
|
||||||
|
tabModel.value = props.tabs[currentTabIndex.value + 1].key;
|
||||||
|
emit('swiped', props.tabs[currentTabIndex.value + 1].key, 'left');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pullDistance.value = 0;
|
||||||
|
isSwiping.value = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
isSwipingForClass.value = false;
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transitionName = ref<'swipeAnimationLeft' | 'swipeAnimationRight' | undefined>(undefined);
|
||||||
|
|
||||||
|
watch(tabModel, (newTab, oldTab) => {
|
||||||
|
const newIndex = props.tabs.findIndex(tab => tab.key === newTab);
|
||||||
|
const oldIndex = props.tabs.findIndex(tab => tab.key === oldTab);
|
||||||
|
|
||||||
|
if (oldIndex >= 0 && newIndex && oldIndex < newIndex) {
|
||||||
|
transitionName.value = 'swipeAnimationLeft';
|
||||||
|
} else {
|
||||||
|
transitionName.value = 'swipeAnimationRight';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
transitionName.value = undefined;
|
||||||
|
}, 400);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.transitionRoot.enableAnimation {
|
||||||
|
display: grid;
|
||||||
|
overflow: clip;
|
||||||
|
|
||||||
|
.transitionChildren {
|
||||||
|
grid-area: 1 / 1 / 2 / 2;
|
||||||
|
transform: translateX(var(--swipe));
|
||||||
|
|
||||||
|
&.swipeAnimation_enterActive,
|
||||||
|
&.swipeAnimation_leaveActive {
|
||||||
|
transition: transform .3s cubic-bezier(0.65, 0.05, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.swipeAnimationRight_leaveTo,
|
||||||
|
&.swipeAnimationLeft_enterFrom {
|
||||||
|
transform: translateX(calc(100% + 24px));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.swipeAnimationRight_enterFrom,
|
||||||
|
&.swipeAnimationLeft_leaveTo {
|
||||||
|
transform: translateX(calc(-100% - 24px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiping {
|
||||||
|
transition: transform .2s ease-out;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -6,6 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
|
<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
||||||
|
@ -98,6 +99,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20">
|
<MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20">
|
||||||
<MkInstanceStats/>
|
<MkInstanceStats/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -114,6 +116,7 @@ import FormSplit from '@/components/form/split.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
import MkInstanceStats from '@/components/MkInstanceStats.vue';
|
import MkInstanceStats from '@/components/MkInstanceStats.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import number from '@/filters/number.js';
|
import number from '@/filters/number.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -7,7 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<div class="_gaps">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
|
<div :key="tab" class="_gaps">
|
||||||
<MkInfo v-if="$i && $i.hasUnreadAnnouncement && tab === 'current'" warn>{{ i18n.ts.youHaveUnreadAnnouncements }}</MkInfo>
|
<MkInfo v-if="$i && $i.hasUnreadAnnouncement && tab === 'current'" warn>{{ i18n.ts.youHaveUnreadAnnouncements }}</MkInfo>
|
||||||
<MkPagination ref="paginationEl" :key="tab" v-slot="{items}" :pagination="tab === 'current' ? paginationCurrent : paginationPast" class="_gaps">
|
<MkPagination ref="paginationEl" :key="tab" v-slot="{items}" :pagination="tab === 'current' ? paginationCurrent : paginationPast" class="_gaps">
|
||||||
<section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement">
|
<section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement">
|
||||||
|
@ -35,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</section>
|
</section>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -44,6 +46,7 @@ import { ref, computed } from 'vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -7,7 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700" :class="$style.main">
|
<MkSpacer :contentMax="700" :class="$style.main">
|
||||||
<div v-if="channel && tab === 'overview'" class="_gaps">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
|
<div v-if="channel && tab === 'overview'" key="overview" class="_gaps">
|
||||||
<div class="_panel" :class="$style.bannerContainer">
|
<div class="_panel" :class="$style.bannerContainer">
|
||||||
<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
|
<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
|
||||||
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
|
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
|
||||||
|
@ -32,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="channel && tab === 'timeline'" class="_gaps">
|
<div v-if="channel && tab === 'timeline'" key="timeline" class="_gaps">
|
||||||
<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>
|
<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>
|
||||||
|
|
||||||
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
|
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
|
||||||
|
@ -40,10 +41,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
|
<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'featured'">
|
<div v-else-if="tab === 'featured'" key="featured">
|
||||||
<MkNotes :pagination="featuredPagination"/>
|
<MkNotes :pagination="featuredPagination"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'search'">
|
<div v-else-if="tab === 'search'" key="search">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<div>
|
<div>
|
||||||
<MkInput v-model="searchQuery" @enter="search()">
|
<MkInput v-model="searchQuery" @enter="search()">
|
||||||
|
@ -54,6 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
|
<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
|
@ -87,6 +89,7 @@ import { defaultStore } from '@/store.js';
|
||||||
import MkNote from '@/components/MkNote.vue';
|
import MkNote from '@/components/MkNote.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { PageHeaderItem } from '@/types/page-header.js';
|
import { PageHeaderItem } from '@/types/page-header.js';
|
||||||
import { isSupportShare } from '@/scripts/navigator.js';
|
import { isSupportShare } from '@/scripts/navigator.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
|
@ -100,6 +103,7 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tab = ref('overview');
|
const tab = ref('overview');
|
||||||
|
|
||||||
const channel = ref<Misskey.entities.Channel | null>(null);
|
const channel = ref<Misskey.entities.Channel | null>(null);
|
||||||
const favorited = ref(false);
|
const favorited = ref(false);
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
|
|
|
@ -7,7 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div v-if="tab === 'search'">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
|
<div v-if="tab === 'search'" key="search">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
|
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
<template #prefix><i class="ti ti-search"></i></template>
|
||||||
|
@ -24,27 +25,28 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkChannelList :key="key" :pagination="channelPagination"/>
|
<MkChannelList :key="key" :pagination="channelPagination"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tab === 'featured'">
|
<div v-if="tab === 'featured'" key="featured">
|
||||||
<MkPagination v-slot="{items}" :pagination="featuredPagination">
|
<MkPagination v-slot="{items}" :pagination="featuredPagination">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'favorites'">
|
<div v-else-if="tab === 'favorites'" key="favorites">
|
||||||
<MkPagination v-slot="{items}" :pagination="favoritesPagination">
|
<MkPagination v-slot="{items}" :pagination="favoritesPagination">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'following'">
|
<div v-else-if="tab === 'following'" key="following">
|
||||||
<MkPagination v-slot="{items}" :pagination="followingPagination">
|
<MkPagination v-slot="{items}" :pagination="followingPagination">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'owned'">
|
<div v-else-if="tab === 'owned'" key="owned">
|
||||||
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
||||||
<MkPagination v-slot="{items}" :pagination="ownedPagination">
|
<MkPagination v-slot="{items}" :pagination="ownedPagination">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -58,6 +60,7 @@ import MkInput from '@/components/MkInput.vue';
|
||||||
import MkRadios from '@/components/MkRadios.vue';
|
import MkRadios from '@/components/MkRadios.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/global/router/supplier.js';
|
import { useRouter } from '@/global/router/supplier.js';
|
||||||
|
|
|
@ -9,13 +9,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/>
|
<MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<MkSpacer v-if="tab === 'info'" :contentMax="800">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
|
<MkSpacer v-if="tab === 'info'" key="info" :contentMax="800">
|
||||||
<XFileInfo :fileId="fileId"/>
|
<XFileInfo :fileId="fileId"/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
|
||||||
<MkSpacer v-else-if="tab === 'notes'" :contentMax="800">
|
<MkSpacer v-else-if="tab === 'notes'" key="notes" :contentMax="800">
|
||||||
<XNotes :fileId="fileId"/>
|
<XNotes :fileId="fileId"/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -23,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, ref, defineAsyncComponent } from 'vue';
|
import { computed, ref, defineAsyncComponent } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fileId: string;
|
fileId: string;
|
||||||
|
|
|
@ -6,17 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<div>
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div v-if="tab === 'featured'">
|
<div v-if="tab === 'featured'" key="featured">
|
||||||
<XFeatured/>
|
<XFeatured/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'users'">
|
<div v-else-if="tab === 'users'" key="users">
|
||||||
<XUsers/>
|
<XUsers/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'roles'">
|
<div v-else-if="tab === 'roles'" key="roles">
|
||||||
<XRoles/>
|
<XRoles/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</MkHorizontalSwipe>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import XFeatured from './explore.featured.vue';
|
||||||
import XUsers from './explore.users.vue';
|
import XUsers from './explore.users.vue';
|
||||||
import XRoles from './explore.roles.vue';
|
import XRoles from './explore.roles.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div v-if="tab === 'featured'">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
|
<div v-if="tab === 'featured'" key="featured">
|
||||||
<MkPagination v-slot="{items}" :pagination="featuredFlashsPagination">
|
<MkPagination v-slot="{items}" :pagination="featuredFlashsPagination">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
||||||
|
@ -15,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'my'">
|
<div v-else-if="tab === 'my'" key="my">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
<MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
||||||
<MkPagination v-slot="{items}" :pagination="myFlashsPagination">
|
<MkPagination v-slot="{items}" :pagination="myFlashsPagination">
|
||||||
|
@ -26,13 +27,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'liked'">
|
<div v-else-if="tab === 'liked'" key="liked">
|
||||||
<MkPagination v-slot="{items}" :pagination="likedFlashsPagination">
|
<MkPagination v-slot="{items}" :pagination="likedFlashsPagination">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
|
<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -42,6 +44,7 @@ import { computed, ref } from 'vue';
|
||||||
import MkFlashPreview from '@/components/MkFlashPreview.vue';
|
import MkFlashPreview from '@/components/MkFlashPreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { useRouter } from '@/global/router/supplier.js';
|
import { useRouter } from '@/global/router/supplier.js';
|
||||||
|
|
|
@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="1400">
|
<MkSpacer :contentMax="1400">
|
||||||
<div class="_root">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div v-if="tab === 'explore'">
|
<div v-if="tab === 'explore'" key="explore">
|
||||||
<MkFoldableSection class="_margin">
|
<MkFoldableSection class="_margin">
|
||||||
<template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template>
|
<template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template>
|
||||||
<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disableAutoLoad="true">
|
<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disableAutoLoad="true">
|
||||||
|
@ -26,14 +26,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'liked'">
|
<div v-else-if="tab === 'liked'" key="liked">
|
||||||
<MkPagination v-slot="{items}" :pagination="likedPostsPagination">
|
<MkPagination v-slot="{items}" :pagination="likedPostsPagination">
|
||||||
<div :class="$style.items">
|
<div :class="$style.items">
|
||||||
<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
|
<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'my'">
|
<div v-else-if="tab === 'my'" key="my">
|
||||||
<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ti ti-plus"></i> {{ i18n.ts.postToGallery }}</MkA>
|
<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ti ti-plus"></i> {{ i18n.ts.postToGallery }}</MkA>
|
||||||
<MkPagination v-slot="{items}" :pagination="myPostsPagination">
|
<MkPagination v-slot="{items}" :pagination="myPostsPagination">
|
||||||
<div :class="$style.items">
|
<div :class="$style.items">
|
||||||
|
@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -51,6 +51,7 @@ import { watch, ref, computed } from 'vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
|
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/global/router/supplier.js';
|
import { useRouter } from '@/global/router/supplier.js';
|
||||||
|
|
|
@ -7,7 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer v-if="instance" :contentMax="600" :marginMin="16" :marginMax="32">
|
<MkSpacer v-if="instance" :contentMax="600" :marginMin="16" :marginMax="32">
|
||||||
<div v-if="tab === 'overview'" class="_gaps_m">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
|
<div v-if="tab === 'overview'" key="overview" class="_gaps_m">
|
||||||
<div class="fnfelxur">
|
<div class="fnfelxur">
|
||||||
<img :src="faviconUrl" alt="" class="icon"/>
|
<img :src="faviconUrl" alt="" class="icon"/>
|
||||||
<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
|
<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
|
||||||
|
@ -76,7 +77,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink>
|
<FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'chart'" class="_gaps_m">
|
<div v-else-if="tab === 'chart'" key="chart" class="_gaps_m">
|
||||||
<div class="cmhjzshl">
|
<div class="cmhjzshl">
|
||||||
<div class="selects">
|
<div class="selects">
|
||||||
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
|
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
|
||||||
|
@ -101,17 +102,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'users'" class="_gaps_m">
|
<div v-else-if="tab === 'users'" key="users" class="_gaps_m">
|
||||||
<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;">
|
<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;">
|
||||||
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`">
|
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`">
|
||||||
<MkUserCardMini :user="user"/>
|
<MkUserCardMini :user="user"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
<div v-else-if="tab === 'raw'" key="raw" class="_gaps_m">
|
||||||
<MkObjectView tall :value="instance">
|
<MkObjectView tall :value="instance">
|
||||||
</MkObjectView>
|
</MkObjectView>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -136,6 +138,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
|
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
|
||||||
import { dateString } from '@/filters/date.js';
|
import { dateString } from '@/filters/date.js';
|
||||||
|
|
||||||
|
@ -144,6 +147,7 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tab = ref('overview');
|
const tab = ref('overview');
|
||||||
|
|
||||||
const chartSrc = ref('instance-requests');
|
const chartSrc = ref('instance-requests');
|
||||||
const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
|
const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
|
||||||
const instance = ref<Misskey.entities.FederationInstance | null>(null);
|
const instance = ref<Misskey.entities.FederationInstance | null>(null);
|
||||||
|
|
|
@ -7,7 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div v-if="tab === 'my'" class="_gaps">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
|
<div v-if="tab === 'my'" key="my" class="_gaps">
|
||||||
<MkButton primary rounded class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
<MkButton primary rounded class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="_gaps">
|
<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="_gaps">
|
||||||
|
@ -16,11 +17,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkA>
|
</MkA>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'favorites'" class="_gaps">
|
<div v-else-if="tab === 'favorites'" key="favorites" class="_gaps">
|
||||||
<MkA v-for="item in favorites" :key="item.id" :to="`/clips/${item.id}`">
|
<MkA v-for="item in favorites" :key="item.id" :to="`/clips/${item.id}`">
|
||||||
<MkClipPreview :clip="item"/>
|
<MkClipPreview :clip="item"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -36,6 +38,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { clipsCache } from '@/cache.js';
|
import { clipsCache } from '@/cache.js';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
endpoint: 'clips/list' as const,
|
endpoint: 'clips/list' as const,
|
||||||
|
@ -44,6 +47,7 @@ const pagination = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const tab = ref('my');
|
const tab = ref('my');
|
||||||
|
|
||||||
const favorites = ref<Misskey.entities.Clip[] | null>(null);
|
const favorites = ref<Misskey.entities.Clip[] | null>(null);
|
||||||
|
|
||||||
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
||||||
|
|
|
@ -7,15 +7,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<div v-if="tab === 'all'">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<XNotifications class="notifications" :excludeTypes="excludeTypes"/>
|
<div v-if="tab === 'all'" key="all">
|
||||||
|
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'mentions'">
|
<div v-else-if="tab === 'mentions'" key="mention">
|
||||||
<MkNotes :pagination="mentionsPagination"/>
|
<MkNotes :pagination="mentionsPagination"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'directNotes'">
|
<div v-else-if="tab === 'directNotes'" key="directNotes">
|
||||||
<MkNotes :pagination="directNotesPagination"/>
|
<MkNotes :pagination="directNotesPagination"/>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -24,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import XNotifications from '@/components/MkNotifications.vue';
|
import XNotifications from '@/components/MkNotifications.vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotes from '@/components/MkNotes.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
@ -96,3 +99,10 @@ definePageMetadata(computed(() => ({
|
||||||
icon: 'ti ti-bell',
|
icon: 'ti ti-bell',
|
||||||
})));
|
})));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.notifications {
|
||||||
|
border-radius: var(--radius);
|
||||||
|
overflow: clip;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -7,7 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div v-if="tab === 'featured'">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
|
<div v-if="tab === 'featured'" key="featured">
|
||||||
<MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
|
<MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
|
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
|
||||||
|
@ -15,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'my'" class="_gaps">
|
<div v-else-if="tab === 'my'" key="my" class="_gaps">
|
||||||
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
||||||
<MkPagination v-slot="{items}" :pagination="myPagesPagination">
|
<MkPagination v-slot="{items}" :pagination="myPagesPagination">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
|
@ -24,13 +25,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'liked'">
|
<div v-else-if="tab === 'liked'" key="liked">
|
||||||
<MkPagination v-slot="{items}" :pagination="likedPagesPagination">
|
<MkPagination v-slot="{items}" :pagination="likedPagesPagination">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
|
<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -40,6 +42,7 @@ import { computed, ref } from 'vue';
|
||||||
import MkPagePreview from '@/components/MkPagePreview.vue';
|
import MkPagePreview from '@/components/MkPagePreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { useRouter } from '@/global/router/supplier.js';
|
import { useRouter } from '@/global/router/supplier.js';
|
||||||
|
|
|
@ -7,7 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
|
|
||||||
<MkSpacer v-if="tab === 'note'" :contentMax="800">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
|
<MkSpacer v-if="tab === 'note'" key="note" :contentMax="800">
|
||||||
<div v-if="notesSearchAvailable">
|
<div v-if="notesSearchAvailable">
|
||||||
<XNote/>
|
<XNote/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,9 +17,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
|
||||||
<MkSpacer v-else-if="tab === 'user'" :contentMax="800">
|
<MkSpacer v-else-if="tab === 'user'" key="user" :contentMax="800">
|
||||||
<XUser/>
|
<XUser/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -29,6 +31,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
const XNote = defineAsyncComponent(() => import('./search.note.vue'));
|
const XNote = defineAsyncComponent(() => import('./search.note.vue'));
|
||||||
const XUser = defineAsyncComponent(() => import('./search.user.vue'));
|
const XUser = defineAsyncComponent(() => import('./search.user.vue'));
|
||||||
|
|
|
@ -155,6 +155,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
|
<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
|
||||||
<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
|
<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
|
||||||
<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
|
<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
|
||||||
|
<MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
<MkSelect v-model="serverDisconnectedBehavior">
|
<MkSelect v-model="serverDisconnectedBehavior">
|
||||||
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
||||||
|
@ -296,6 +297,7 @@ const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
|
||||||
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
|
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
|
||||||
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
|
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
|
||||||
const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
|
const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
|
||||||
|
const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe'));
|
||||||
|
|
||||||
watch(lang, () => {
|
watch(lang, () => {
|
||||||
miLocalStorage.setItem('lang', lang.value as string);
|
miLocalStorage.setItem('lang', lang.value as string);
|
||||||
|
|
|
@ -7,12 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
|
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<div ref="rootEl" v-hotkey.global="keymap">
|
<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
|
||||||
|
<div :key="src + withRenotes + withReplies + onlyFiles" ref="rootEl" v-hotkey.global="keymap">
|
||||||
<MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()">
|
<MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()">
|
||||||
{{ i18n.ts._timelineDescription[src] }}
|
{{ i18n.ts._timelineDescription[src] }}
|
||||||
</MkInfo>
|
</MkInfo>
|
||||||
<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
|
<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
|
||||||
|
|
||||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||||
<div :class="$style.tl">
|
<div :class="$style.tl">
|
||||||
<MkTimeline
|
<MkTimeline
|
||||||
|
@ -28,6 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -38,6 +39,7 @@ import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkTimeline from '@/components/MkTimeline.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { scroll } from '@/scripts/scroll.js';
|
import { scroll } from '@/scripts/scroll.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
@ -69,7 +71,9 @@ const withRenotes = ref(true);
|
||||||
const withReplies = ref($i ? defaultStore.state.tlWithReplies : false);
|
const withReplies = ref($i ? defaultStore.state.tlWithReplies : false);
|
||||||
const onlyFiles = ref(false);
|
const onlyFiles = ref(false);
|
||||||
|
|
||||||
watch(src, () => queue.value = 0);
|
watch(src, () => {
|
||||||
|
queue.value = 0;
|
||||||
|
});
|
||||||
|
|
||||||
watch(withReplies, (x) => {
|
watch(withReplies, (x) => {
|
||||||
if ($i) defaultStore.set('tlWithReplies', x);
|
if ($i) defaultStore.set('tlWithReplies', x);
|
||||||
|
|
|
@ -8,19 +8,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="user">
|
<div v-if="user">
|
||||||
<XHome v-if="tab === 'home'" :user="user"/>
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<MkSpacer v-else-if="tab === 'notes'" :contentMax="800" style="padding-top: 0">
|
<XHome v-if="tab === 'home'" key="home" :user="user"/>
|
||||||
|
<MkSpacer v-else-if="tab === 'notes'" key="notes" :contentMax="800" style="padding-top: 0">
|
||||||
<XTimeline :user="user"/>
|
<XTimeline :user="user"/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
<XActivity v-else-if="tab === 'activity'" :user="user"/>
|
<XActivity v-else-if="tab === 'activity'" key="activity" :user="user"/>
|
||||||
<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
|
<XAchievements v-else-if="tab === 'achievements'" key="achievements" :user="user"/>
|
||||||
<XReactions v-else-if="tab === 'reactions'" :user="user"/>
|
<XReactions v-else-if="tab === 'reactions'" key="reactions" :user="user"/>
|
||||||
<XClips v-else-if="tab === 'clips'" :user="user"/>
|
<XClips v-else-if="tab === 'clips'" key="clips" :user="user"/>
|
||||||
<XLists v-else-if="tab === 'lists'" :user="user"/>
|
<XLists v-else-if="tab === 'lists'" key="lists" :user="user"/>
|
||||||
<XPages v-else-if="tab === 'pages'" :user="user"/>
|
<XPages v-else-if="tab === 'pages'" key="pages" :user="user"/>
|
||||||
<XFlashs v-else-if="tab === 'flashs'" :user="user"/>
|
<XFlashs v-else-if="tab === 'flashs'" key="flashs" :user="user"/>
|
||||||
<XGallery v-else-if="tab === 'gallery'" :user="user"/>
|
<XGallery v-else-if="tab === 'gallery'" key="gallery" :user="user"/>
|
||||||
<XRaw v-else-if="tab === 'raw'" :user="user"/>
|
<XRaw v-else-if="tab === 'raw'" key="raw" :user="user"/>
|
||||||
|
</MkHorizontalSwipe>
|
||||||
</div>
|
</div>
|
||||||
<MkError v-else-if="error" @retry="fetchUser()"/>
|
<MkError v-else-if="error" @retry="fetchUser()"/>
|
||||||
<MkLoading v-else/>
|
<MkLoading v-else/>
|
||||||
|
@ -36,6 +38,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
const XHome = defineAsyncComponent(() => import('./home.vue'));
|
const XHome = defineAsyncComponent(() => import('./home.vue'));
|
||||||
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
||||||
|
@ -57,6 +60,7 @@ const props = withDefaults(defineProps<{
|
||||||
});
|
});
|
||||||
|
|
||||||
const tab = ref(props.page);
|
const tab = ref(props.page);
|
||||||
|
|
||||||
const user = ref<null | Misskey.entities.UserDetailed>(null);
|
const user = ref<null | Misskey.entities.UserDetailed>(null);
|
||||||
const error = ref<any>(null);
|
const error = ref<any>(null);
|
||||||
|
|
||||||
|
|
|
@ -427,6 +427,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
sfxVolume: 1,
|
sfxVolume: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
enableHorizontalSwipe: {
|
||||||
|
where: 'device',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
|
||||||
sound_masterVolume: {
|
sound_masterVolume: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
|
|
Loading…
Reference in a new issue