Compare commits
23 Commits
6c45aeec32
...
2f817cec8a
Author | SHA1 | Date |
---|---|---|
|
2f817cec8a | |
|
c310f7cef6 | |
|
f4bc78ad3d | |
|
c192d0512e | |
|
77d4acfd32 | |
|
80703d1563 | |
|
41447b6837 | |
|
69edb20465 | |
|
39f718c510 | |
|
8cbd3dcd42 | |
|
dafc6b2d45 | |
|
a2f3947bfc | |
|
55b50663b3 | |
|
5dd17b0061 | |
|
d25af911cf | |
|
fd3bce262a | |
|
e67955f6a1 | |
|
df1a3742dd | |
|
86daa7cba5 | |
|
bfbcc71008 | |
|
84fe933863 | |
|
4ee5811c2f | |
|
53948b5469 |
|
@ -4,7 +4,9 @@
|
||||||
-
|
-
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
-
|
- Feat: マウスでもタイムラインを引っ張って更新できるように
|
||||||
|
- アクセシビリティ設定からオフにすることもできます
|
||||||
|
- Enhance: タイムラインのパフォーマンスを向上
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775`
|
- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775`
|
||||||
|
|
|
@ -5721,6 +5721,10 @@ export interface Locale extends ILocale {
|
||||||
* デバイス間でインストールしたテーマを同期
|
* デバイス間でインストールしたテーマを同期
|
||||||
*/
|
*/
|
||||||
"enableSyncThemesBetweenDevices": string;
|
"enableSyncThemesBetweenDevices": string;
|
||||||
|
/**
|
||||||
|
* ひっぱって更新
|
||||||
|
*/
|
||||||
|
"enablePullToRefresh": string;
|
||||||
/**
|
/**
|
||||||
* サーバーと接続を確立し、リアルタイムでコンテンツを更新します。通信量とバッテリーの消費が多くなる場合があります。
|
* サーバーと接続を確立し、リアルタイムでコンテンツを更新します。通信量とバッテリーの消費が多くなる場合があります。
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1430,6 +1430,7 @@ _settings:
|
||||||
ifOn: "オンのとき"
|
ifOn: "オンのとき"
|
||||||
ifOff: "オフのとき"
|
ifOff: "オフのとき"
|
||||||
enableSyncThemesBetweenDevices: "デバイス間でインストールしたテーマを同期"
|
enableSyncThemesBetweenDevices: "デバイス間でインストールしたテーマを同期"
|
||||||
|
enablePullToRefresh: "ひっぱって更新"
|
||||||
realtimeMode_description: "サーバーと接続を確立し、リアルタイムでコンテンツを更新します。通信量とバッテリーの消費が多くなる場合があります。"
|
realtimeMode_description: "サーバーと接続を確立し、リアルタイムでコンテンツを更新します。通信量とバッテリーの消費が多くなる場合があります。"
|
||||||
contentsUpdateFrequency: "コンテンツの取得頻度"
|
contentsUpdateFrequency: "コンテンツの取得頻度"
|
||||||
contentsUpdateFrequency_description: "高いほどリアルタイムにコンテンツが更新されますが、パフォーマンスが低下し、通信量とバッテリーの消費が多くなります。"
|
contentsUpdateFrequency_description: "高いほどリアルタイムにコンテンツが更新されますが、パフォーマンスが低下し、通信量とバッテリーの消費が多くなります。"
|
||||||
|
|
|
@ -19,14 +19,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
pagination: Paging;
|
pagination: PagingCtx;
|
||||||
noGap?: boolean;
|
noGap?: boolean;
|
||||||
extractor?: (item: any) => any;
|
extractor?: (item: any) => any;
|
||||||
}>(), {
|
}>(), {
|
||||||
|
|
|
@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref, useTemplateRef } from 'vue';
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
@ -53,7 +53,7 @@ const emit = defineEmits<{
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const dialog = ref<InstanceType<typeof MkModalWindow>>();
|
const dialog = useTemplateRef('dialog');
|
||||||
|
|
||||||
const username = ref('');
|
const username = ref('');
|
||||||
const email = ref('');
|
const email = ref('');
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad">
|
<MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad" :pullToRefresh="pullToRefresh">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" draggable="false"/>
|
||||||
|
@ -30,22 +30,29 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useTemplateRef } from 'vue';
|
import { useTemplateRef } from 'vue';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import MkNote from '@/components/MkNote.vue';
|
import MkNote from '@/components/MkNote.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
pagination: Paging;
|
pagination: PagingCtx;
|
||||||
noGap?: boolean;
|
noGap?: boolean;
|
||||||
disableAutoLoad?: boolean;
|
disableAutoLoad?: boolean;
|
||||||
}>();
|
pullToRefresh?: boolean;
|
||||||
|
}>(), {
|
||||||
|
pullToRefresh: true,
|
||||||
|
});
|
||||||
|
|
||||||
const pagingComponent = useTemplateRef('pagingComponent');
|
const pagingComponent = useTemplateRef('pagingComponent');
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
return pagingComponent.value?.paginator.reload();
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
pagingComponent,
|
reload,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,6 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<component :is="prefer.s.enablePullToRefresh && pullToRefresh ? MkPullToRefresh : 'div'" :refresher="() => paginator.reload()">
|
||||||
<Transition
|
<Transition
|
||||||
:enterActiveClass="prefer.s.animation ? $style.transition_fade_enterActive : ''"
|
:enterActiveClass="prefer.s.animation ? $style.transition_fade_enterActive : ''"
|
||||||
:leaveActiveClass="prefer.s.animation ? $style.transition_fade_leaveActive : ''"
|
:leaveActiveClass="prefer.s.animation ? $style.transition_fade_leaveActive : ''"
|
||||||
|
@ -41,6 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -51,13 +53,16 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { usePagination } from '@/use/use-pagination.js';
|
import { usePagination } from '@/use/use-pagination.js';
|
||||||
|
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
pagination: PagingCtx;
|
pagination: PagingCtx;
|
||||||
disableAutoLoad?: boolean;
|
disableAutoLoad?: boolean;
|
||||||
displayLimit?: number;
|
displayLimit?: number;
|
||||||
|
pullToRefresh?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
displayLimit: 20,
|
displayLimit: 20,
|
||||||
|
pullToRefresh: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const paginator = usePagination({
|
const paginator = usePagination({
|
||||||
|
|
|
@ -4,13 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="rootEl">
|
<div ref="rootEl" :class="isPulling ? $style.isPulling : null">
|
||||||
<div v-if="isPullStart" :class="$style.frame" :style="`--frame-min-height: ${pullDistance / (PULL_BRAKE_BASE + (pullDistance / PULL_BRAKE_FACTOR))}px;`">
|
<div v-if="isPulling" :class="$style.frame" :style="`--frame-min-height: ${pullDistance / (PULL_BRAKE_BASE + (pullDistance / PULL_BRAKE_FACTOR))}px;`">
|
||||||
<div :class="$style.frameContent">
|
<div :class="$style.frameContent">
|
||||||
<MkLoading v-if="isRefreshing" :class="$style.loader" :em="true"/>
|
<MkLoading v-if="isRefreshing" :class="$style.loader" :em="true"/>
|
||||||
<i v-else class="ti ti-arrow-bar-to-down" :class="[$style.icon, { [$style.refresh]: isPullEnd }]"></i>
|
<i v-else class="ti ti-arrow-bar-to-down" :class="[$style.icon, { [$style.refresh]: isPulledEnough }]"></i>
|
||||||
<div :class="$style.text">
|
<div :class="$style.text">
|
||||||
<template v-if="isPullEnd">{{ i18n.ts.releaseToRefresh }}</template>
|
<template v-if="isPulledEnough">{{ i18n.ts.releaseToRefresh }}</template>
|
||||||
<template v-else-if="isRefreshing">{{ i18n.ts.refreshing }}</template>
|
<template v-else-if="isRefreshing">{{ i18n.ts.refreshing }}</template>
|
||||||
<template v-else>{{ i18n.ts.pullDownToRefresh }}</template>
|
<template v-else>{{ i18n.ts.pullDownToRefresh }}</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,19 +34,16 @@ const RELEASE_TRANSITION_DURATION = 200;
|
||||||
const PULL_BRAKE_BASE = 1.5;
|
const PULL_BRAKE_BASE = 1.5;
|
||||||
const PULL_BRAKE_FACTOR = 170;
|
const PULL_BRAKE_FACTOR = 170;
|
||||||
|
|
||||||
const isPullStart = ref(false);
|
const isPulling = ref(false);
|
||||||
const isPullEnd = ref(false);
|
const isPulledEnough = ref(false);
|
||||||
const isRefreshing = ref(false);
|
const isRefreshing = ref(false);
|
||||||
const pullDistance = ref(0);
|
const pullDistance = ref(0);
|
||||||
|
|
||||||
let supportPointerDesktop = false;
|
|
||||||
let startScreenY: number | null = null;
|
let startScreenY: number | null = null;
|
||||||
|
|
||||||
const rootEl = useTemplateRef('rootEl');
|
const rootEl = useTemplateRef('rootEl');
|
||||||
let scrollEl: HTMLElement | null = null;
|
let scrollEl: HTMLElement | null = null;
|
||||||
|
|
||||||
let disabled = false;
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
refresher: () => Promise<void>;
|
refresher: () => Promise<void>;
|
||||||
}>(), {
|
}>(), {
|
||||||
|
@ -57,18 +54,51 @@ const emit = defineEmits<{
|
||||||
(ev: 'refresh'): void;
|
(ev: 'refresh'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function getScreenY(event) {
|
function getScreenY(event: TouchEvent | MouseEvent | PointerEvent): number {
|
||||||
if (supportPointerDesktop) {
|
if (event.touches && event.touches[0] && event.touches[0].screenY != null) {
|
||||||
|
return event.touches[0].screenY;
|
||||||
|
} else {
|
||||||
return event.screenY;
|
return event.screenY;
|
||||||
}
|
}
|
||||||
return event.touches[0].screenY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveStart(event) {
|
// When at the top of the page, disable vertical overscroll so passive touch listeners can take over.
|
||||||
if (!isPullStart.value && !isRefreshing.value && !disabled) {
|
function lockDownScroll() {
|
||||||
isPullStart.value = true;
|
scrollEl!.style.touchAction = 'pan-x pan-down pinch-zoom';
|
||||||
|
scrollEl!.style.overscrollBehavior = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function unlockDownScroll() {
|
||||||
|
scrollEl!.style.touchAction = 'auto';
|
||||||
|
scrollEl!.style.overscrollBehavior = 'contain';
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveStart(event: PointerEvent) {
|
||||||
|
const scrollPos = scrollEl!.scrollTop;
|
||||||
|
if (scrollPos === 0) {
|
||||||
|
lockDownScroll();
|
||||||
|
if (!isPulling.value && !isRefreshing.value) {
|
||||||
|
isPulling.value = true;
|
||||||
startScreenY = getScreenY(event);
|
startScreenY = getScreenY(event);
|
||||||
pullDistance.value = 0;
|
pullDistance.value = 0;
|
||||||
|
|
||||||
|
// タッチデバイスでPointerEventを使うとなんか挙動がおかしいので、TouchEventとMouseEventを使い分ける
|
||||||
|
if (event.pointerType === 'mouse') {
|
||||||
|
window.addEventListener('mousemove', moving, { passive: true });
|
||||||
|
window.addEventListener('mouseup', () => {
|
||||||
|
window.removeEventListener('mousemove', moving);
|
||||||
|
onPullRelease();
|
||||||
|
}, { passive: true, once: true });
|
||||||
|
} else {
|
||||||
|
window.addEventListener('touchmove', moving, { passive: true });
|
||||||
|
window.addEventListener('touchend', () => {
|
||||||
|
window.removeEventListener('touchmove', moving);
|
||||||
|
onPullRelease();
|
||||||
|
}, { passive: true, once: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unlockDownScroll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,11 +138,11 @@ async function closeContent() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveEnd() {
|
function onPullRelease() {
|
||||||
if (isPullStart.value && !isRefreshing.value) {
|
window.document.body.removeAttribute('inert');
|
||||||
startScreenY = null;
|
startScreenY = null;
|
||||||
if (isPullEnd.value) {
|
if (isPulledEnough.value) {
|
||||||
isPullEnd.value = false;
|
isPulledEnough.value = false;
|
||||||
isRefreshing.value = true;
|
isRefreshing.value = true;
|
||||||
fixOverContent().then(() => {
|
fixOverContent().then(() => {
|
||||||
emit('refresh');
|
emit('refresh');
|
||||||
|
@ -121,18 +151,26 @@ function moveEnd() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
closeContent().then(() => isPullStart.value = false);
|
closeContent().then(() => isPulling.value = false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function moving(event: TouchEvent | PointerEvent) {
|
function toggleScrollLockOnTouchEnd() {
|
||||||
if (!isPullStart.value || isRefreshing.value || disabled) return;
|
const scrollPos = scrollEl!.scrollTop;
|
||||||
|
if (scrollPos === 0) {
|
||||||
|
lockDownScroll();
|
||||||
|
} else {
|
||||||
|
unlockDownScroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value) || isHorizontalSwipeSwiping.value) {
|
function moving(event: MouseEvent | TouchEvent) {
|
||||||
|
if (!isPulling.value || isRefreshing.value) return;
|
||||||
|
|
||||||
|
if ((scrollEl?.scrollTop ?? 0) > SCROLL_STOP + pullDistance.value || isHorizontalSwipeSwiping.value) {
|
||||||
pullDistance.value = 0;
|
pullDistance.value = 0;
|
||||||
isPullEnd.value = false;
|
isPulledEnough.value = false;
|
||||||
moveEnd();
|
onPullRelease();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,15 +182,12 @@ function moving(event: TouchEvent | PointerEvent) {
|
||||||
const moveHeight = moveScreenY - startScreenY!;
|
const moveHeight = moveScreenY - startScreenY!;
|
||||||
pullDistance.value = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE);
|
pullDistance.value = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE);
|
||||||
|
|
||||||
if (pullDistance.value > 0) {
|
// マウスでのpull時、画面上のテキスト選択が発生して画面がスクロールされたりするのを防ぐ
|
||||||
if (event.cancelable) event.preventDefault();
|
if (pullDistance.value > 3) { // ある程度遊びを持たせないと通常のクリックでも発火しクリックできなくなる
|
||||||
|
window.document.body.setAttribute('inert', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pullDistance.value > SCROLL_STOP) {
|
isPulledEnough.value = pullDistance.value >= FIRE_THRESHOLD;
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
isPullEnd.value = pullDistance.value >= FIRE_THRESHOLD;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -162,65 +197,29 @@ function moving(event: TouchEvent | PointerEvent) {
|
||||||
*/
|
*/
|
||||||
function refreshFinished() {
|
function refreshFinished() {
|
||||||
closeContent().then(() => {
|
closeContent().then(() => {
|
||||||
isPullStart.value = false;
|
isPulling.value = false;
|
||||||
isRefreshing.value = false;
|
isRefreshing.value = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDisabled(value) {
|
|
||||||
disabled = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onScrollContainerScroll() {
|
|
||||||
const scrollPos = scrollEl!.scrollTop;
|
|
||||||
|
|
||||||
// When at the top of the page, disable vertical overscroll so passive touch listeners can take over.
|
|
||||||
if (scrollPos === 0) {
|
|
||||||
scrollEl!.style.touchAction = 'pan-x pan-down pinch-zoom';
|
|
||||||
registerEventListenersForReadyToPull();
|
|
||||||
} else {
|
|
||||||
scrollEl!.style.touchAction = 'auto';
|
|
||||||
unregisterEventListenersForReadyToPull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerEventListenersForReadyToPull() {
|
|
||||||
if (rootEl.value == null) return;
|
|
||||||
rootEl.value.addEventListener('touchstart', moveStart, { passive: true });
|
|
||||||
rootEl.value.addEventListener('touchmove', moving, { passive: false }); // passive: falseにしないとpreventDefaultが使えない
|
|
||||||
}
|
|
||||||
|
|
||||||
function unregisterEventListenersForReadyToPull() {
|
|
||||||
if (rootEl.value == null) return;
|
|
||||||
rootEl.value.removeEventListener('touchstart', moveStart);
|
|
||||||
rootEl.value.removeEventListener('touchmove', moving);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (rootEl.value == null) return;
|
if (rootEl.value == null) return;
|
||||||
|
|
||||||
scrollEl = getScrollContainer(rootEl.value);
|
scrollEl = getScrollContainer(rootEl.value);
|
||||||
if (scrollEl == null) return;
|
rootEl.value.addEventListener('pointerdown', moveStart, { passive: true });
|
||||||
|
rootEl.value.addEventListener('touchend', toggleScrollLockOnTouchEnd, { passive: true });
|
||||||
scrollEl.addEventListener('scroll', onScrollContainerScroll, { passive: true });
|
|
||||||
|
|
||||||
rootEl.value.addEventListener('touchend', moveEnd, { passive: true });
|
|
||||||
|
|
||||||
registerEventListenersForReadyToPull();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (scrollEl) scrollEl.removeEventListener('scroll', onScrollContainerScroll);
|
if (rootEl.value) rootEl.value.removeEventListener('pointerdown', moveStart);
|
||||||
|
if (rootEl.value) rootEl.value.removeEventListener('touchend', toggleScrollLockOnTouchEnd);
|
||||||
unregisterEventListenersForReadyToPull();
|
|
||||||
});
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
setDisabled,
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
.isPulling {
|
||||||
|
will-change: contents;
|
||||||
|
}
|
||||||
|
|
||||||
.frame {
|
.frame {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
|
|
|
@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, useTemplateRef } from 'vue';
|
||||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
@ -81,7 +81,7 @@ const emit = defineEmits<{
|
||||||
(ev: 'closed'): void
|
(ev: 'closed'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
|
const windowEl = useTemplateRef('windowEl');
|
||||||
|
|
||||||
const name = computed(() => props.emoji.name);
|
const name = computed(() => props.emoji.name);
|
||||||
const host = computed(() => props.emoji.host);
|
const host = computed(() => props.emoji.host);
|
||||||
|
|
|
@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, toRefs } from 'vue';
|
import { computed, ref, toRefs, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -74,7 +74,7 @@ const props = withDefaults(defineProps<{
|
||||||
|
|
||||||
const { initialRoleIds, infoMessage, title, publicOnly } = toRefs(props);
|
const { initialRoleIds, infoMessage, title, publicOnly } = toRefs(props);
|
||||||
|
|
||||||
const windowEl = ref<InstanceType<typeof MkModalWindow>>();
|
const windowEl = useTemplateRef('windowEl');
|
||||||
const roles = ref<Misskey.entities.Role[]>([]);
|
const roles = ref<Misskey.entities.Role[]>([]);
|
||||||
const selectedRoleIds = ref<string[]>(initialRoleIds.value ?? []);
|
const selectedRoleIds = ref<string[]>(initialRoleIds.value ?? []);
|
||||||
const fetching = ref(false);
|
const fetching = ref(false);
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkPullToRefresh :refresher="() => reloadTimeline()">
|
<component :is="prefer.s.enablePullToRefresh ? MkPullToRefresh : 'div'" :refresher="() => reloadTimeline()">
|
||||||
<MkLoading v-if="paginator.fetching.value"/>
|
<MkLoading v-if="paginator.fetching.value"/>
|
||||||
|
|
||||||
<MkError v-else-if="paginator.error.value" @retry="paginator.init()"/>
|
<MkError v-else-if="paginator.error.value" @retry="paginator.init()"/>
|
||||||
|
@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkLoading v-else :inline="true"/>
|
<MkLoading v-else :inline="true"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</MkPullToRefresh>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -96,8 +96,8 @@ provide('tl_withSensitive', computed(() => props.withSensitive));
|
||||||
provide('inChannel', computed(() => props.src === 'channel'));
|
provide('inChannel', computed(() => props.src === 'channel'));
|
||||||
|
|
||||||
function isTop() {
|
function isTop() {
|
||||||
if (scrollContainer == null) return false;
|
if (scrollContainer == null) return true;
|
||||||
if (rootEl.value == null) return false;
|
if (rootEl.value == null) return true;
|
||||||
const scrollTop = scrollContainer.scrollTop;
|
const scrollTop = scrollContainer.scrollTop;
|
||||||
const tlTop = rootEl.value.offsetTop - scrollContainer.offsetTop;
|
const tlTop = rootEl.value.offsetTop - scrollContainer.offsetTop;
|
||||||
return scrollTop <= tlTop;
|
return scrollTop <= tlTop;
|
||||||
|
@ -155,13 +155,13 @@ if (!store.s.realtimeMode) {
|
||||||
immediate: false,
|
immediate: false,
|
||||||
afterMounted: true,
|
afterMounted: true,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
globalEvents.on('notePosted', (note: Misskey.entities.Note) => {
|
globalEvents.on('notePosted', (note: Misskey.entities.Note) => {
|
||||||
paginator.fetchNewer({
|
paginator.fetchNewer({
|
||||||
toQueue: !isTop(),
|
toQueue: !isTop(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function releaseQueue() {
|
function releaseQueue() {
|
||||||
paginator.releaseQueue();
|
paginator.releaseQueue();
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkPullToRefresh :refresher="() => reload()">
|
<component :is="prefer.s.enablePullToRefresh ? MkPullToRefresh : 'div'" :refresher="() => reload()">
|
||||||
<MkLoading v-if="paginator.fetching.value"/>
|
<MkLoading v-if="paginator.fetching.value"/>
|
||||||
|
|
||||||
<MkError v-else-if="paginator.error.value" @retry="paginator.init()"/>
|
<MkError v-else-if="paginator.error.value" @retry="paginator.init()"/>
|
||||||
|
@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkLoading v-else/>
|
<MkLoading v-else/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</MkPullToRefresh>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
|
@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -74,7 +74,7 @@ const emit = defineEmits<{
|
||||||
(ev: 'closed'): void
|
(ev: 'closed'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
|
const dialog = useTemplateRef('dialog');
|
||||||
const title = ref(props.announcement ? props.announcement.title : '');
|
const title = ref(props.announcement ? props.announcement.title : '');
|
||||||
const text = ref(props.announcement ? props.announcement.text : '');
|
const text = ref(props.announcement ? props.announcement.text : '');
|
||||||
const icon = ref(props.announcement ? props.announcement.icon : 'info');
|
const icon = ref(props.announcement ? props.announcement.icon : 'info');
|
||||||
|
|
|
@ -21,14 +21,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import MkUserInfo from '@/components/MkUserInfo.vue';
|
import MkUserInfo from '@/components/MkUserInfo.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
pagination: Paging;
|
pagination: PagingCtx;
|
||||||
noGap?: boolean;
|
noGap?: boolean;
|
||||||
extractor?: (item: any) => any;
|
extractor?: (item: any) => any;
|
||||||
}>(), {
|
}>(), {
|
||||||
|
|
|
@ -39,15 +39,15 @@ import { i18n } from '@/i18n.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import XUser from '@/components/MkUserSetupDialog.User.vue';
|
import XUser from '@/components/MkUserSetupDialog.User.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
|
|
||||||
const pinnedUsers: Paging = {
|
const pinnedUsers: PagingCtx = {
|
||||||
endpoint: 'pinned-users',
|
endpoint: 'pinned-users',
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
const popularUsers: Paging = {
|
const popularUsers: PagingCtx = {
|
||||||
endpoint: 'users',
|
endpoint: 'users',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
|
|
|
@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-if="instance.policies.ltlAvailable" :class="[$style.tl, $style.panel]">
|
<div v-if="instance.policies.ltlAvailable" :class="[$style.tl, $style.panel]">
|
||||||
<div :class="$style.tlHeader">{{ i18n.ts.letsLookAtTimeline }}</div>
|
<div :class="$style.tlHeader">{{ i18n.ts.letsLookAtTimeline }}</div>
|
||||||
<div :class="$style.tlBody">
|
<div :class="$style.tlBody">
|
||||||
<MkTimeline src="local"/>
|
<MkStreamingNotesTimeline src="local"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.panel">
|
<div :class="$style.panel">
|
||||||
|
@ -58,7 +58,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import XSigninDialog from '@/components/MkSigninDialog.vue';
|
import XSigninDialog from '@/components/MkSigninDialog.vue';
|
||||||
import XSignupDialog from '@/components/MkSignupDialog.vue';
|
import XSignupDialog from '@/components/MkSignupDialog.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import { instanceName } from '@@/js/config.js';
|
import { instanceName } from '@@/js/config.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
|
@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, toRefs, watch } from 'vue';
|
import { computed, onMounted, ref, toRefs, useTemplateRef, watch } from 'vue';
|
||||||
import type { DataSource, GridSetting, GridState, Size } from '@/components/grid/grid.js';
|
import type { DataSource, GridSetting, GridState, Size } from '@/components/grid/grid.js';
|
||||||
import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
|
import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
|
||||||
import type { GridContext, GridEvent } from '@/components/grid/grid-event.js';
|
import type { GridContext, GridEvent } from '@/components/grid/grid-event.js';
|
||||||
|
@ -130,7 +130,7 @@ const bus = new GridEventEmitter();
|
||||||
*/
|
*/
|
||||||
const resizeObserver = new ResizeObserver((entries) => window.setTimeout(() => onResize(entries)));
|
const resizeObserver = new ResizeObserver((entries) => window.setTimeout(() => onResize(entries)));
|
||||||
|
|
||||||
const rootEl = ref<InstanceType<typeof HTMLTableElement>>();
|
const rootEl = useTemplateRef('rootEl');
|
||||||
/**
|
/**
|
||||||
* グリッドの最も上位にある状態。
|
* グリッドの最も上位にある状態。
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -31,10 +31,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, onMounted, onUnmounted, ref, toRefs, watch } from 'vue';
|
import { computed, nextTick, onMounted, onUnmounted, ref, toRefs, useTemplateRef, watch } from 'vue';
|
||||||
import { GridEventEmitter } from '@/components/grid/grid.js';
|
|
||||||
import type { Size } from '@/components/grid/grid.js';
|
import type { Size } from '@/components/grid/grid.js';
|
||||||
import type { GridColumn } from '@/components/grid/column.js';
|
import type { GridColumn } from '@/components/grid/column.js';
|
||||||
|
import { GridEventEmitter } from '@/components/grid/grid.js';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'operation:beginWidthChange', sender: GridColumn): void;
|
(ev: 'operation:beginWidthChange', sender: GridColumn): void;
|
||||||
|
@ -50,8 +50,8 @@ const props = defineProps<{
|
||||||
|
|
||||||
const { column, bus } = toRefs(props);
|
const { column, bus } = toRefs(props);
|
||||||
|
|
||||||
const rootEl = ref<InstanceType<typeof HTMLTableCellElement>>();
|
const rootEl = useTemplateRef('rootEl');
|
||||||
const contentEl = ref<InstanceType<typeof HTMLDivElement>>();
|
const contentEl = useTemplateRef('contentEl');
|
||||||
|
|
||||||
const resizing = ref<boolean>(false);
|
const resizing = ref<boolean>(false);
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ import { computed, ref } from 'vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
|
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
|
||||||
import FormSplit from '@/components/form/split.vue';
|
import FormSplit from '@/components/form/split.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -81,7 +81,7 @@ const pagination = {
|
||||||
state.value === 'notResponding' ? { notResponding: true } :
|
state.value === 'notResponding' ? { notResponding: true } :
|
||||||
{}),
|
{}),
|
||||||
})),
|
})),
|
||||||
} as Paging;
|
} as PagingCtx;
|
||||||
|
|
||||||
function getStatus(instance) {
|
function getStatus(instance) {
|
||||||
if (instance.isSuspended) return 'Suspended';
|
if (instance.isSuspended) return 'Suspended';
|
||||||
|
|
|
@ -87,7 +87,7 @@ const pagination = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function resolved(reportId) {
|
function resolved(reportId) {
|
||||||
reports.value?.removeItem(reportId);
|
reports.value?.paginator.removeItem(reportId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeTutorial() {
|
function closeTutorial() {
|
||||||
|
|
|
@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, useTemplateRef } from 'vue';
|
import { computed, ref, useTemplateRef } from 'vue';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
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';
|
||||||
|
@ -73,7 +73,7 @@ const pagingComponent = useTemplateRef('pagingComponent');
|
||||||
const type = ref('all');
|
const type = ref('all');
|
||||||
const sort = ref('+createdAt');
|
const sort = ref('+createdAt');
|
||||||
|
|
||||||
const pagination: Paging = {
|
const pagination: PagingCtx = {
|
||||||
endpoint: 'admin/invite/list' as const,
|
endpoint: 'admin/invite/list' as const,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
params: computed(() => ({
|
||||||
|
@ -100,12 +100,12 @@ async function createWithOptions() {
|
||||||
text: tickets.map(x => x.code).join('\n'),
|
text: tickets.map(x => x.code).join('\n'),
|
||||||
});
|
});
|
||||||
|
|
||||||
tickets.forEach(ticket => pagingComponent.value?.prepend(ticket));
|
tickets.forEach(ticket => pagingComponent.value?.paginator.prepend(ticket));
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleted(id: string) {
|
function deleted(id: string) {
|
||||||
if (pagingComponent.value) {
|
if (pagingComponent.value) {
|
||||||
pagingComponent.value.items.delete(id);
|
pagingComponent.value.paginator.removeItem(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,7 +121,7 @@ async function addUser() {
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
paginationComponent.value?.reload();
|
paginationComponent.value?.paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, useTemplateRef } 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';
|
||||||
|
@ -71,7 +71,7 @@ const paginationPast = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const paginationEl = ref<InstanceType<typeof MkPagination>>();
|
const paginationEl = useTemplateRef('paginationEl');
|
||||||
|
|
||||||
const tab = ref('current');
|
const tab = ref('current');
|
||||||
|
|
||||||
|
@ -86,10 +86,10 @@ async function read(target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!paginationEl.value) return;
|
if (!paginationEl.value) return;
|
||||||
paginationEl.value.updateItem(target.id, a => {
|
paginationEl.value.paginator.updateItem(target.id, a => ({
|
||||||
a.isRead = true;
|
...a,
|
||||||
return a;
|
isRead: true,
|
||||||
});
|
}));
|
||||||
misskeyApi('i/read-announcement', { announcementId: target.id });
|
misskeyApi('i/read-announcement', { announcementId: target.id });
|
||||||
updateCurrentAccountPartial({
|
updateCurrentAccountPartial({
|
||||||
unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id),
|
unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id),
|
||||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||||
<div :class="$style.tl">
|
<div :class="$style.tl">
|
||||||
<MkTimeline
|
<MkStreamingNotesTimeline
|
||||||
ref="tlEl" :key="antennaId"
|
ref="tlEl" :key="antennaId"
|
||||||
src="antenna"
|
src="antenna"
|
||||||
:antenna="antennaId"
|
:antenna="antennaId"
|
||||||
|
@ -21,7 +21,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 MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.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';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
|
|
@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, ref } from 'vue';
|
import { computed, watch, ref, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkWindow from '@/components/MkWindow.vue';
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -86,7 +86,7 @@ const emit = defineEmits<{
|
||||||
(ev: 'closed'): void
|
(ev: 'closed'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
|
const windowEl = useTemplateRef('windowEl');
|
||||||
const url = ref<string>(props.avatarDecoration ? props.avatarDecoration.url : '');
|
const url = ref<string>(props.avatarDecoration ? props.avatarDecoration.url : '');
|
||||||
const name = ref<string>(props.avatarDecoration ? props.avatarDecoration.name : '');
|
const name = ref<string>(props.avatarDecoration ? props.avatarDecoration.name : '');
|
||||||
const description = ref<string>(props.avatarDecoration ? props.avatarDecoration.description : '');
|
const description = ref<string>(props.avatarDecoration ? props.avatarDecoration.description : '');
|
||||||
|
|
|
@ -37,10 +37,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
|
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
|
||||||
<MkPostForm v-if="$i && prefer.r.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
|
<MkPostForm v-if="$i && prefer.r.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
|
||||||
|
|
||||||
<MkTimeline :key="channelId" src="channel" :channel="channelId"/>
|
<MkStreamingNotesTimeline :key="channelId" src="channel" :channel="channelId"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'featured'">
|
<div v-else-if="tab === 'featured'">
|
||||||
<MkNotes :pagination="featuredPagination"/>
|
<MkNotesTimeline :pagination="featuredPagination"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'search'">
|
<div v-else-if="tab === 'search'">
|
||||||
<div v-if="notesSearchAvailable" class="_gaps">
|
<div v-if="notesSearchAvailable" class="_gaps">
|
||||||
|
@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
|
<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
|
<MkNotesTimeline v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
|
<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
|
||||||
|
@ -76,7 +76,7 @@ import { url } from '@@/js/config.js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import type { PageHeaderItem } from '@/types/page-header.js';
|
import type { PageHeaderItem } from '@/types/page-header.js';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||||
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
|
import XChannelFollowButton from '@/components/MkChannelFollowButton.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';
|
||||||
|
@ -84,7 +84,7 @@ import { $i, iAmModerator } from '@/i.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { deviceKind } from '@/utility/device-kind.js';
|
import { deviceKind } from '@/utility/device-kind.js';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import { favoritedChannelsCache } from '@/cache.js';
|
import { favoritedChannelsCache } from '@/cache.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
|
|
@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkNotes :pagination="pagination" :detail="true"/>
|
<MkNotesTimeline :pagination="pagination" :detail="true"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
|
@ -34,7 +34,7 @@ import { computed, watch, provide, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
|
@ -115,7 +115,7 @@ const selectAll = () => {
|
||||||
if (selectedEmojis.value.length > 0) {
|
if (selectedEmojis.value.length > 0) {
|
||||||
selectedEmojis.value = [];
|
selectedEmojis.value = [];
|
||||||
} else {
|
} else {
|
||||||
selectedEmojis.value = Array.from(emojisPaginationComponent.value?.items.values(), item => item.id);
|
selectedEmojis.value = emojisPaginationComponent.value?.paginator.items.value.map(item => item.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ const add = async (ev: MouseEvent) => {
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
if (result.created) {
|
if (result.created) {
|
||||||
emojisPaginationComponent.value?.prepend(result.created);
|
emojisPaginationComponent.value?.paginator.prepend(result.created);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
|
@ -145,12 +145,12 @@ const edit = (emoji) => {
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
if (result.updated) {
|
if (result.updated) {
|
||||||
emojisPaginationComponent.value?.updateItem(result.updated.id, (oldEmoji) => ({
|
emojisPaginationComponent.value?.paginator.updateItem(result.updated.id, (oldEmoji) => ({
|
||||||
...oldEmoji,
|
...oldEmoji,
|
||||||
...result.updated,
|
...result.updated,
|
||||||
}));
|
}));
|
||||||
} else if (result.deleted) {
|
} else if (result.deleted) {
|
||||||
emojisPaginationComponent.value?.removeItem(emoji.id);
|
emojisPaginationComponent.value?.paginator.removeItem(emoji.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
|
@ -242,7 +242,7 @@ const setCategoryBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
category: result,
|
category: result,
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.reload();
|
emojisPaginationComponent.value?.paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setLicenseBulk = async () => {
|
const setLicenseBulk = async () => {
|
||||||
|
@ -254,7 +254,7 @@ const setLicenseBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
license: result,
|
license: result,
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.reload();
|
emojisPaginationComponent.value?.paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const addTagBulk = async () => {
|
const addTagBulk = async () => {
|
||||||
|
@ -266,7 +266,7 @@ const addTagBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
aliases: result.split(' '),
|
aliases: result.split(' '),
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.reload();
|
emojisPaginationComponent.value?.paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeTagBulk = async () => {
|
const removeTagBulk = async () => {
|
||||||
|
@ -278,7 +278,7 @@ const removeTagBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
aliases: result.split(' '),
|
aliases: result.split(' '),
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.reload();
|
emojisPaginationComponent.value?.paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setTagBulk = async () => {
|
const setTagBulk = async () => {
|
||||||
|
@ -290,7 +290,7 @@ const setTagBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
aliases: result.split(' '),
|
aliases: result.split(' '),
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.reload();
|
emojisPaginationComponent.value?.paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const delBulk = async () => {
|
const delBulk = async () => {
|
||||||
|
@ -302,7 +302,7 @@ const delBulk = async () => {
|
||||||
await os.apiWithDialog('admin/emoji/delete-bulk', {
|
await os.apiWithDialog('admin/emoji/delete-bulk', {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.reload();
|
emojisPaginationComponent.value?.paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed(() => [{
|
||||||
|
|
|
@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInfo>{{ i18n.ts._fileViewer.thisPageCanBeSeenFromTheAuthor }}</MkInfo>
|
<MkInfo>{{ i18n.ts._fileViewer.thisPageCanBeSeenFromTheAuthor }}</MkInfo>
|
||||||
<MkNotes ref="tlComponent" :pagination="pagination"/>
|
<MkNotesTimeline ref="tlComponent" :pagination="pagination"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fileId: string;
|
fileId: string;
|
||||||
|
@ -23,7 +23,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const realFileId = computed(() => props.fileId);
|
const realFileId = computed(() => props.fileId);
|
||||||
|
|
||||||
const pagination = ref<Paging>({
|
const pagination = ref<PagingCtx>({
|
||||||
endpoint: 'drive/files/attached-notes',
|
endpoint: 'drive/files/attached-notes',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
|
|
|
@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, ref } from 'vue';
|
import { computed, watch, ref, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkWindow from '@/components/MkWindow.vue';
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -103,7 +103,7 @@ const emit = defineEmits<{
|
||||||
(ev: 'closed'): void
|
(ev: 'closed'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
|
const windowEl = useTemplateRef('windowEl');
|
||||||
const name = ref<string>(props.emoji ? props.emoji.name : '');
|
const name = ref<string>(props.emoji ? props.emoji.name : '');
|
||||||
const category = ref<string>(props.emoji?.category ? props.emoji.category : '');
|
const category = ref<string>(props.emoji?.category ? props.emoji.category : '');
|
||||||
const aliases = ref<string>(props.emoji ? props.emoji.aliases.join(' ') : '');
|
const aliases = ref<string>(props.emoji ? props.emoji.aliases.join(' ') : '');
|
||||||
|
|
|
@ -9,14 +9,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="notes">{{ i18n.ts.notes }}</option>
|
<option value="notes">{{ i18n.ts.notes }}</option>
|
||||||
<option value="polls">{{ i18n.ts.poll }}</option>
|
<option value="polls">{{ i18n.ts.poll }}</option>
|
||||||
</MkTab>
|
</MkTab>
|
||||||
<MkNotes v-if="tab === 'notes'" :pagination="paginationForNotes"/>
|
<MkNotesTimeline v-if="tab === 'notes'" :pagination="paginationForNotes"/>
|
||||||
<MkNotes v-else-if="tab === 'polls'" :pagination="paginationForPolls"/>
|
<MkNotesTimeline v-else-if="tab === 'polls'" :pagination="paginationForPolls"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import MkTab from '@/components/MkTab.vue';
|
import MkTab from '@/components/MkTab.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { useTemplateRef, computed, ref } from 'vue';
|
import { useTemplateRef, computed, ref } from 'vue';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
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 { userPage, acct } from '@/filters/user.js';
|
import { userPage, acct } from '@/filters/user.js';
|
||||||
|
@ -53,7 +53,7 @@ import { $i } from '@/i.js';
|
||||||
|
|
||||||
const paginationComponent = useTemplateRef('paginationComponent');
|
const paginationComponent = useTemplateRef('paginationComponent');
|
||||||
|
|
||||||
const pagination = computed<Paging>(() => tab.value === 'list' ? {
|
const pagination = computed<PagingCtx>(() => tab.value === 'list' ? {
|
||||||
endpoint: 'following/requests/list',
|
endpoint: 'following/requests/list',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
} : {
|
} : {
|
||||||
|
@ -63,19 +63,19 @@ const pagination = computed<Paging>(() => tab.value === 'list' ? {
|
||||||
|
|
||||||
function accept(user: Misskey.entities.UserLite) {
|
function accept(user: Misskey.entities.UserLite) {
|
||||||
os.apiWithDialog('following/requests/accept', { userId: user.id }).then(() => {
|
os.apiWithDialog('following/requests/accept', { userId: user.id }).then(() => {
|
||||||
paginationComponent.value?.reload();
|
paginationComponent.value?.paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function reject(user: Misskey.entities.UserLite) {
|
function reject(user: Misskey.entities.UserLite) {
|
||||||
os.apiWithDialog('following/requests/reject', { userId: user.id }).then(() => {
|
os.apiWithDialog('following/requests/reject', { userId: user.id }).then(() => {
|
||||||
paginationComponent.value?.reload();
|
paginationComponent.value?.paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel(user: Misskey.entities.UserLite) {
|
function cancel(user: Misskey.entities.UserLite) {
|
||||||
os.apiWithDialog('following/requests/cancel', { userId: user.id }).then(() => {
|
os.apiWithDialog('following/requests/cancel', { userId: user.id }).then(() => {
|
||||||
paginationComponent.value?.reload();
|
paginationComponent.value?.paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { ref, computed, watch } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { ChartSrc } from '@/components/MkChart.vue';
|
import type { ChartSrc } from '@/components/MkChart.vue';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import MkChart from '@/components/MkChart.vue';
|
import MkChart from '@/components/MkChart.vue';
|
||||||
import MkObjectView from '@/components/MkObjectView.vue';
|
import MkObjectView from '@/components/MkObjectView.vue';
|
||||||
import FormLink from '@/components/form/link.vue';
|
import FormLink from '@/components/form/link.vue';
|
||||||
|
@ -180,7 +180,7 @@ const usersPagination = {
|
||||||
hostname: props.host,
|
hostname: props.host,
|
||||||
},
|
},
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
} satisfies Paging;
|
} satisfies PagingCtx;
|
||||||
|
|
||||||
if (iAmModerator) {
|
if (iAmModerator) {
|
||||||
watch(moderationNote, async () => {
|
watch(moderationNote, async () => {
|
||||||
|
|
|
@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, useTemplateRef } from 'vue';
|
import { computed, ref, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
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';
|
||||||
|
@ -51,7 +51,7 @@ const currentInviteLimit = ref<null | number>(null);
|
||||||
const inviteLimit = (($i != null && $i.policies.inviteLimit) || (($i == null && instance.policies.inviteLimit))) as number;
|
const inviteLimit = (($i != null && $i.policies.inviteLimit) || (($i == null && instance.policies.inviteLimit))) as number;
|
||||||
const inviteLimitCycle = (($i != null && $i.policies.inviteLimitCycle) || ($i == null && instance.policies.inviteLimitCycle)) as number;
|
const inviteLimitCycle = (($i != null && $i.policies.inviteLimitCycle) || ($i == null && instance.policies.inviteLimitCycle)) as number;
|
||||||
|
|
||||||
const pagination: Paging = {
|
const pagination: PagingCtx = {
|
||||||
endpoint: 'invite/list' as const,
|
endpoint: 'invite/list' as const,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
};
|
||||||
|
@ -74,13 +74,13 @@ async function create() {
|
||||||
text: ticket.code,
|
text: ticket.code,
|
||||||
});
|
});
|
||||||
|
|
||||||
pagingComponent.value?.prepend(ticket);
|
pagingComponent.value?.paginator.prepend(ticket);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleted(id: string) {
|
function deleted(id: string) {
|
||||||
if (pagingComponent.value) {
|
if (pagingComponent.value) {
|
||||||
pagingComponent.value.items.delete(id);
|
pagingComponent.value.paginator.removeItem(id);
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,15 +73,15 @@ async function create() {
|
||||||
|
|
||||||
clipsCache.delete();
|
clipsCache.delete();
|
||||||
|
|
||||||
pagingComponent.value?.reload();
|
pagingComponent.value?.paginator.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClipCreated() {
|
function onClipCreated() {
|
||||||
pagingComponent.value?.reload();
|
pagingComponent.value?.paginator.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClipDeleted() {
|
function onClipDeleted() {
|
||||||
pagingComponent.value?.reload();
|
pagingComponent.value?.paginator.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, useTemplateRef, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -80,7 +80,7 @@ const props = defineProps<{
|
||||||
listId: string;
|
listId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const paginationEl = ref<InstanceType<typeof MkPagination>>();
|
const paginationEl = useTemplateRef('paginationEl');
|
||||||
const list = ref<Misskey.entities.UserList | null>(null);
|
const list = ref<Misskey.entities.UserList | null>(null);
|
||||||
const isPublic = ref(false);
|
const isPublic = ref(false);
|
||||||
const name = ref('');
|
const name = ref('');
|
||||||
|
@ -109,7 +109,7 @@ function addUser() {
|
||||||
listId: list.value.id,
|
listId: list.value.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
paginationEl.value?.reload();
|
paginationEl.value?.paginator.reload();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ async function removeUser(item, ev) {
|
||||||
listId: list.value.id,
|
listId: list.value.id,
|
||||||
userId: item.userId,
|
userId: item.userId,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
paginationEl.value?.removeItem(item.id);
|
paginationEl.value?.paginator.removeItem(item.id);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
@ -147,7 +147,7 @@ async function showMembershipMenu(item, ev) {
|
||||||
userId: item.userId,
|
userId: item.userId,
|
||||||
withReplies,
|
withReplies,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
paginationEl.value!.updateItem(item.id, (old) => ({
|
paginationEl.value!.paginator.updateItem(item.id, (old) => ({
|
||||||
...old,
|
...old,
|
||||||
withReplies,
|
withReplies,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
|
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
|
||||||
<div v-if="note">
|
<div v-if="note">
|
||||||
<div v-if="showNext" class="_margin">
|
<div v-if="showNext" class="_margin">
|
||||||
<MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
|
<MkNotesTimeline class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="_margin">
|
<div class="_margin">
|
||||||
|
@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showPrev" class="_margin">
|
<div v-if="showPrev" class="_margin">
|
||||||
<MkNotes class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
|
<MkNotesTimeline class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkError v-else-if="error" @retry="fetchNote()"/>
|
<MkError v-else-if="error" @retry="fetchNote()"/>
|
||||||
|
@ -50,9 +50,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { computed, watch, ref } from 'vue';
|
import { computed, watch, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { host } from '@@/js/config.js';
|
import { host } from '@@/js/config.js';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
|
import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
|
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
@ -80,7 +80,7 @@ const showPrev = ref<'user' | 'channel' | false>(false);
|
||||||
const showNext = ref<'user' | 'channel' | false>(false);
|
const showNext = ref<'user' | 'channel' | false>(false);
|
||||||
const error = ref();
|
const error = ref();
|
||||||
|
|
||||||
const prevUserPagination: Paging = {
|
const prevUserPagination: PagingCtx = {
|
||||||
endpoint: 'users/notes',
|
endpoint: 'users/notes',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => note.value ? ({
|
params: computed(() => note.value ? ({
|
||||||
|
@ -89,7 +89,7 @@ const prevUserPagination: Paging = {
|
||||||
}) : undefined),
|
}) : undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextUserPagination: Paging = {
|
const nextUserPagination: PagingCtx = {
|
||||||
reversed: true,
|
reversed: true,
|
||||||
endpoint: 'users/notes',
|
endpoint: 'users/notes',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
@ -99,7 +99,7 @@ const nextUserPagination: Paging = {
|
||||||
}) : undefined),
|
}) : undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
const prevChannelPagination: Paging = {
|
const prevChannelPagination: PagingCtx = {
|
||||||
endpoint: 'channels/timeline',
|
endpoint: 'channels/timeline',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => note.value ? ({
|
params: computed(() => note.value ? ({
|
||||||
|
@ -108,7 +108,7 @@ const prevChannelPagination: Paging = {
|
||||||
}) : undefined),
|
}) : undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextChannelPagination: Paging = {
|
const nextChannelPagination: PagingCtx = {
|
||||||
reversed: true,
|
reversed: true,
|
||||||
endpoint: 'channels/timeline',
|
endpoint: 'channels/timeline',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
|
|
@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true">
|
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true">
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||||
<div v-if="tab === 'all'">
|
<div v-if="tab === 'all'">
|
||||||
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
<MkStreamingNotificationsTimeline :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'mentions'">
|
<div v-else-if="tab === 'mentions'">
|
||||||
<MkNotes :pagination="mentionsPagination"/>
|
<MkNotesTimeline :pagination="mentionsPagination"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'directNotes'">
|
<div v-else-if="tab === 'directNotes'">
|
||||||
<MkNotes :pagination="directNotesPagination"/>
|
<MkNotesTimeline :pagination="directNotesPagination"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
|
@ -22,8 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { notificationTypes } from '@@/js/const.js';
|
import { notificationTypes } from '@@/js/const.js';
|
||||||
import XNotifications from '@/components/MkNotifications.vue';
|
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'timeline'" class="_spacer" style="--MI_SPACER-w: 700px;">
|
<div v-else-if="tab === 'timeline'" class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<MkTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/>
|
<MkStreamingNotesTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/>
|
||||||
<div v-else-if="!visible" class="_fullinfo">
|
<div v-else-if="!visible" class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" draggable="false"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
|
@ -42,7 +42,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import MkUserList from '@/components/MkUserList.vue';
|
import MkUserList from '@/components/MkUserList.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||||
import { serverErrorImageUrl, infoImageUrl } from '@/instance.js';
|
import { serverErrorImageUrl, infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
|
|
|
@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<MkFoldableSection v-if="notePagination">
|
<MkFoldableSection v-if="notePagination">
|
||||||
<template #header>{{ i18n.ts.searchResult }}</template>
|
<template #header>{{ i18n.ts.searchResult }}</template>
|
||||||
<MkNotes :key="`searchNotes:${key}`" :pagination="notePagination"/>
|
<MkNotesTimeline :key="`searchNotes:${key}`" :pagination="notePagination"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -113,7 +113,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, shallowRef, toRef } from 'vue';
|
import { computed, ref, shallowRef, toRef } from 'vue';
|
||||||
import type * as Misskey from 'misskey-js';
|
import type * as Misskey from 'misskey-js';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { host as localHost } from '@@/js/config.js';
|
import { host as localHost } from '@@/js/config.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -125,7 +125,7 @@ import { useRouter } from '@/router.js';
|
||||||
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 MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import MkRadios from '@/components/MkRadios.vue';
|
import MkRadios from '@/components/MkRadios.vue';
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ const props = withDefaults(defineProps<{
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const key = ref(0);
|
const key = ref(0);
|
||||||
const notePagination = ref<Paging<'notes/search'>>();
|
const notePagination = ref<PagingCtx<'notes/search'>>();
|
||||||
|
|
||||||
const searchQuery = ref(toRef(props, 'query').value);
|
const searchQuery = ref(toRef(props, 'query').value);
|
||||||
const hostInput = ref(toRef(props, 'host').value);
|
const hostInput = ref(toRef(props, 'host').value);
|
||||||
|
|
|
@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, toRef } from 'vue';
|
import { ref, toRef } from 'vue';
|
||||||
import type { Endpoints } from 'misskey-js';
|
import type { Endpoints } from 'misskey-js';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||||
import MkUserList from '@/components/MkUserList.vue';
|
import MkUserList from '@/components/MkUserList.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkRadios from '@/components/MkRadios.vue';
|
import MkRadios from '@/components/MkRadios.vue';
|
||||||
|
@ -50,7 +50,7 @@ const props = withDefaults(defineProps<{
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const key = ref(0);
|
const key = ref(0);
|
||||||
const userPagination = ref<Paging<'users/search'>>();
|
const userPagination = ref<PagingCtx<'users/search'>>();
|
||||||
|
|
||||||
const searchQuery = ref(toRef(props, 'query').value);
|
const searchQuery = ref(toRef(props, 'query').value);
|
||||||
const searchOrigin = ref(toRef(props, 'origin').value);
|
const searchOrigin = ref(toRef(props, 'origin').value);
|
||||||
|
|
|
@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, useTemplateRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import FormPagination from '@/components/MkPagination.vue';
|
import FormPagination from '@/components/MkPagination.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
@ -65,7 +65,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
const list = ref<InstanceType<typeof FormPagination>>();
|
const list = useTemplateRef('list');
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
endpoint: 'i/apps' as const,
|
endpoint: 'i/apps' as const,
|
||||||
|
@ -78,7 +78,7 @@ const pagination = {
|
||||||
|
|
||||||
function revoke(token) {
|
function revoke(token) {
|
||||||
misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => {
|
misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => {
|
||||||
list.value?.reload();
|
list.value?.paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -483,6 +483,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkPreferenceContainer>
|
</MkPreferenceContainer>
|
||||||
</SearchMarker>
|
</SearchMarker>
|
||||||
|
|
||||||
|
<SearchMarker :keywords="['swipe', 'pull', 'refresh']">
|
||||||
|
<MkPreferenceContainer k="enablePullToRefresh">
|
||||||
|
<MkSwitch v-model="enablePullToRefresh">
|
||||||
|
<template #label><SearchLabel>{{ i18n.ts._settings.enablePullToRefresh }}</SearchLabel></template>
|
||||||
|
</MkSwitch>
|
||||||
|
</MkPreferenceContainer>
|
||||||
|
</SearchMarker>
|
||||||
|
|
||||||
<SearchMarker :keywords="['keep', 'screen', 'display', 'on']">
|
<SearchMarker :keywords="['keep', 'screen', 'display', 'on']">
|
||||||
<MkPreferenceContainer k="keepScreenOn">
|
<MkPreferenceContainer k="keepScreenOn">
|
||||||
<MkSwitch v-model="keepScreenOn">
|
<MkSwitch v-model="keepScreenOn">
|
||||||
|
@ -813,6 +821,7 @@ const animatedMfm = prefer.model('animatedMfm');
|
||||||
const disableShowingAnimatedImages = prefer.model('disableShowingAnimatedImages');
|
const disableShowingAnimatedImages = prefer.model('disableShowingAnimatedImages');
|
||||||
const keepScreenOn = prefer.model('keepScreenOn');
|
const keepScreenOn = prefer.model('keepScreenOn');
|
||||||
const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe');
|
const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe');
|
||||||
|
const enablePullToRefresh = prefer.model('enablePullToRefresh');
|
||||||
const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer');
|
const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer');
|
||||||
const contextMenu = prefer.model('contextMenu');
|
const contextMenu = prefer.model('contextMenu');
|
||||||
const menuStyle = prefer.model('menuStyle');
|
const menuStyle = prefer.model('menuStyle');
|
||||||
|
@ -871,6 +880,8 @@ watch([
|
||||||
fontSize,
|
fontSize,
|
||||||
useSystemFont,
|
useSystemFont,
|
||||||
makeEveryTextElementsSelectable,
|
makeEveryTextElementsSelectable,
|
||||||
|
enableHorizontalSwipe,
|
||||||
|
enablePullToRefresh,
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||||
<MkNotes ref="notes" class="" :pagination="pagination"/>
|
<MkNotesTimeline ref="tlComponent" class="" :pagination="pagination"/>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="$i" #footer>
|
<template v-if="$i" #footer>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
|
@ -19,8 +19,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, useTemplateRef } from 'vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -40,7 +40,8 @@ const pagination = {
|
||||||
tag: props.tag,
|
tag: props.tag,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
const notes = ref<InstanceType<typeof MkNotes>>();
|
|
||||||
|
const tlComponent = useTemplateRef('tlComponent');
|
||||||
|
|
||||||
async function post() {
|
async function post() {
|
||||||
store.set('postFormHashtags', props.tag);
|
store.set('postFormHashtags', props.tag);
|
||||||
|
@ -48,7 +49,7 @@ async function post() {
|
||||||
await os.post();
|
await os.post();
|
||||||
store.set('postFormHashtags', '');
|
store.set('postFormHashtags', '');
|
||||||
store.set('postFormWithHashtags', false);
|
store.set('postFormWithHashtags', false);
|
||||||
notes.value?.pagingComponent?.reload();
|
tlComponent.value?.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed(() => [{
|
||||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
{{ i18n.ts._timelineDescription[src] }}
|
{{ i18n.ts._timelineDescription[src] }}
|
||||||
</MkInfo>
|
</MkInfo>
|
||||||
<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="_panel" fixed style="margin-bottom: var(--MI-margin);"/>
|
<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="_panel" fixed style="margin-bottom: var(--MI-margin);"/>
|
||||||
<MkTimeline
|
<MkStreamingNotesTimeline
|
||||||
ref="tlComponent"
|
ref="tlComponent"
|
||||||
:key="src + withRenotes + withReplies + onlyFiles + withSensitive"
|
:key="src + withRenotes + withReplies + onlyFiles + withSensitive"
|
||||||
:class="$style.tl"
|
:class="$style.tl"
|
||||||
|
@ -31,7 +31,7 @@ import { computed, watch, provide, useTemplateRef, ref, onMounted, onActivated }
|
||||||
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';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.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 * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||||
<div :class="$style.tl">
|
<div :class="$style.tl">
|
||||||
<MkTimeline
|
<MkStreamingNotesTimeline
|
||||||
ref="tlEl" :key="listId"
|
ref="tlEl" :key="listId"
|
||||||
src="list"
|
src="list"
|
||||||
:list="listId"
|
:list="listId"
|
||||||
|
@ -21,7 +21,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 MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.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';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -4,6 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<component :is="prefer.s.enablePullToRefresh ? MkPullToRefresh : 'div'" :refresher="() => reload()">
|
||||||
<div class="_spacer" :style="{ '--MI_SPACER-w': narrow ? '800px' : '1100px' }">
|
<div class="_spacer" :style="{ '--MI_SPACER-w': narrow ? '800px' : '1100px' }">
|
||||||
<div ref="rootEl" class="ftskorzw" :class="{ wide: !narrow }" style="container-type: inline-size;">
|
<div ref="rootEl" class="ftskorzw" :class="{ wide: !narrow }" style="container-type: inline-size;">
|
||||||
<div class="main _gaps">
|
<div class="main _gaps">
|
||||||
|
@ -156,6 +157,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -185,6 +187,7 @@ import { useRouter } from '@/router.js';
|
||||||
import { getStaticImageUrl } from '@/utility/media-proxy.js';
|
import { getStaticImageUrl } from '@/utility/media-proxy.js';
|
||||||
import MkSparkle from '@/components/MkSparkle.vue';
|
import MkSparkle from '@/components/MkSparkle.vue';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||||
|
|
||||||
function calcAge(birthdate: string): number {
|
function calcAge(birthdate: string): number {
|
||||||
const date = new Date(birthdate);
|
const date = new Date(birthdate);
|
||||||
|
@ -207,7 +210,7 @@ const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
user: Misskey.entities.UserDetailed;
|
user: Misskey.entities.UserDetailed;
|
||||||
/** Test only; MkNotes currently causes problems in vitest */
|
/** Test only; MkNotesTimeline currently causes problems in vitest */
|
||||||
disableNotes: boolean;
|
disableNotes: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
disableNotes: false,
|
disableNotes: false,
|
||||||
|
@ -299,6 +302,10 @@ watch([props.user], () => {
|
||||||
memoDraft.value = props.user.memo;
|
memoDraft.value = props.user.memo;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function reload() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.requestAnimationFrame(parallaxLoop);
|
window.requestAnimationFrame(parallaxLoop);
|
||||||
narrow.value = rootEl.value!.clientWidth < 1000;
|
narrow.value = rootEl.value!.clientWidth < 1000;
|
||||||
|
|
|
@ -8,19 +8,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #header>
|
<template #header>
|
||||||
<MkTab v-model="tab" :class="$style.tab">
|
<MkTab v-model="tab" :class="$style.tab">
|
||||||
<option value="featured">{{ i18n.ts.featured }}</option>
|
<option value="featured">{{ i18n.ts.featured }}</option>
|
||||||
<option :value="null">{{ i18n.ts.notes }}</option>
|
<option value="notes">{{ i18n.ts.notes }}</option>
|
||||||
<option value="all">{{ i18n.ts.all }}</option>
|
<option value="all">{{ i18n.ts.all }}</option>
|
||||||
<option value="files">{{ i18n.ts.withFiles }}</option>
|
<option value="files">{{ i18n.ts.withFiles }}</option>
|
||||||
</MkTab>
|
</MkTab>
|
||||||
</template>
|
</template>
|
||||||
<MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
<MkNotesTimeline :key="tab" :noGap="true" :pagination="pagination" :pullToRefresh="false" :class="$style.tl"/>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import MkTab from '@/components/MkTab.vue';
|
import MkTab from '@/components/MkTab.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ const props = defineProps<{
|
||||||
user: Misskey.entities.UserDetailed;
|
user: Misskey.entities.UserDetailed;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tab = ref<string | null>('all');
|
const tab = ref<string>('all');
|
||||||
|
|
||||||
const pagination = computed(() => tab.value === 'featured' ? {
|
const pagination = computed(() => tab.value === 'featured' ? {
|
||||||
endpoint: 'users/featured-notes' as const,
|
endpoint: 'users/featured-notes' as const,
|
||||||
|
|
|
@ -7,9 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<PageWithHeader v-model:tab="tab" :tabs="headerTabs" :actions="headerActions" :swipable="true">
|
<PageWithHeader v-model:tab="tab" :tabs="headerTabs" :actions="headerActions" :swipable="true">
|
||||||
<div v-if="user">
|
<div v-if="user">
|
||||||
<XHome v-if="tab === 'home'" :user="user" @unfoldFiles="() => { tab = 'files'; }"/>
|
<XHome v-if="tab === 'home'" :user="user" @unfoldFiles="() => { tab = 'files'; }"/>
|
||||||
<div v-else-if="tab === 'notes'" class="_spacer" style="--MI_SPACER-w: 800px;">
|
<XNotes v-else-if="tab === 'notes'" :user="user"/>
|
||||||
<XTimeline :user="user"/>
|
|
||||||
</div>
|
|
||||||
<XFiles v-else-if="tab === 'files'" :user="user"/>
|
<XFiles v-else-if="tab === 'files'" :user="user"/>
|
||||||
<XActivity v-else-if="tab === 'activity'" :user="user"/>
|
<XActivity v-else-if="tab === 'activity'" :user="user"/>
|
||||||
<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
|
<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
|
||||||
|
@ -37,7 +35,7 @@ import { $i } from '@/i.js';
|
||||||
import { serverContext, assertServerContext } from '@/server-context.js';
|
import { serverContext, assertServerContext } from '@/server-context.js';
|
||||||
|
|
||||||
const XHome = defineAsyncComponent(() => import('./home.vue'));
|
const XHome = defineAsyncComponent(() => import('./home.vue'));
|
||||||
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
const XNotes = defineAsyncComponent(() => import('./notes.vue'));
|
||||||
const XFiles = defineAsyncComponent(() => import('./files.vue'));
|
const XFiles = defineAsyncComponent(() => import('./files.vue'));
|
||||||
const XActivity = defineAsyncComponent(() => import('./activity.vue'));
|
const XActivity = defineAsyncComponent(() => import('./activity.vue'));
|
||||||
const XAchievements = defineAsyncComponent(() => import('./achievements.vue'));
|
const XAchievements = defineAsyncComponent(() => import('./achievements.vue'));
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||||
|
<div :class="$style.root">
|
||||||
|
<MkStickyContainer>
|
||||||
|
<template #header>
|
||||||
|
<MkTab v-model="tab" :class="$style.tab">
|
||||||
|
<option value="featured">{{ i18n.ts.featured }}</option>
|
||||||
|
<option value="notes">{{ i18n.ts.notes }}</option>
|
||||||
|
<option value="all">{{ i18n.ts.all }}</option>
|
||||||
|
<option value="files">{{ i18n.ts.withFiles }}</option>
|
||||||
|
</MkTab>
|
||||||
|
</template>
|
||||||
|
<MkNotesTimeline :key="tab" :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
||||||
|
</MkStickyContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
|
import MkTab from '@/components/MkTab.vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
user: Misskey.entities.UserDetailed;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const tab = ref<string>('all');
|
||||||
|
|
||||||
|
const pagination = computed(() => tab.value === 'featured' ? {
|
||||||
|
endpoint: 'users/featured-notes' as const,
|
||||||
|
limit: 10,
|
||||||
|
params: {
|
||||||
|
userId: props.user.id,
|
||||||
|
},
|
||||||
|
} : {
|
||||||
|
endpoint: 'users/notes' as const,
|
||||||
|
limit: 10,
|
||||||
|
params: {
|
||||||
|
userId: props.user.id,
|
||||||
|
withRenotes: tab.value === 'all',
|
||||||
|
withReplies: tab.value === 'all',
|
||||||
|
withChannelNotes: tab.value === 'all',
|
||||||
|
withFiles: tab.value === 'files',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.tab {
|
||||||
|
padding: calc(var(--MI-margin) / 2) 0;
|
||||||
|
background: var(--MI_THEME-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tl {
|
||||||
|
background: var(--MI_THEME-bg);
|
||||||
|
border-radius: var(--MI-radius);
|
||||||
|
overflow: clip;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -303,6 +303,9 @@ export const PREF_DEF = {
|
||||||
enableHorizontalSwipe: {
|
enableHorizontalSwipe: {
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
enablePullToRefresh: {
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
useNativeUiForVideoAudioPlayer: {
|
useNativeUiForVideoAudioPlayer: {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:leaveToClass="prefer.s.animation ? $style.transition_menuDrawer_leaveTo : ''"
|
:leaveToClass="prefer.s.animation ? $style.transition_menuDrawer_leaveTo : ''"
|
||||||
>
|
>
|
||||||
<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
|
<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
|
||||||
<XDrawerMenu/>
|
<XNavbar style="height: 100%;" :asDrawer="true" :showWidgetButton="false"/>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
|
import XNavbar from '@/ui/_common_/navbar.vue';
|
||||||
|
|
||||||
const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
|
const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
|
||||||
const XUpload = defineAsyncComponent(() => import('./upload.vue'));
|
const XUpload = defineAsyncComponent(() => import('./upload.vue'));
|
||||||
|
@ -230,12 +230,6 @@ if ($i) {
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
height: 100dvh;
|
height: 100dvh;
|
||||||
width: 240px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
contain: strict;
|
|
||||||
overflow: auto;
|
|
||||||
overscroll-behavior: contain;
|
|
||||||
background: var(--MI_THEME-navBg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.widgetsDrawerBg {
|
.widgetsDrawerBg {
|
||||||
|
|
|
@ -1,273 +0,0 @@
|
||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="$style.root">
|
|
||||||
<div :class="$style.top">
|
|
||||||
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
|
|
||||||
<button class="_button" :class="$style.instance" @click="openInstanceMenu">
|
|
||||||
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div :class="$style.middle">
|
|
||||||
<MkA :class="$style.item" :activeClass="$style.active" to="/" exact>
|
|
||||||
<i :class="$style.itemIcon" class="ti ti-home ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span>
|
|
||||||
</MkA>
|
|
||||||
<template v-for="item in prefer.r.menu.value">
|
|
||||||
<div v-if="item === '-'" :class="$style.divider"></div>
|
|
||||||
<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" class="_button" :class="[$style.item, { [$style.active]: navbarItemDef[item].active }]" :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
|
|
||||||
<i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span>
|
|
||||||
<span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator" class="_blink">
|
|
||||||
<span v-if="navbarItemDef[item].indicateValue" class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ navbarItemDef[item].indicateValue }}</span>
|
|
||||||
<i v-else class="_indicatorCircle"></i>
|
|
||||||
</span>
|
|
||||||
</component>
|
|
||||||
</template>
|
|
||||||
<div :class="$style.divider"></div>
|
|
||||||
<MkA v-if="$i.isAdmin || $i.isModerator" :class="$style.item" :activeClass="$style.active" to="/admin">
|
|
||||||
<i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.controlPanel }}</span>
|
|
||||||
</MkA>
|
|
||||||
<button :class="$style.item" class="_button" @click="more">
|
|
||||||
<i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.more }}</span>
|
|
||||||
<span v-if="otherMenuItemIndicated" :class="$style.itemIndicator" class="_blink"><i class="_indicatorCircle"></i></span>
|
|
||||||
</button>
|
|
||||||
<MkA :class="$style.item" :activeClass="$style.active" to="/settings">
|
|
||||||
<i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span>
|
|
||||||
</MkA>
|
|
||||||
</div>
|
|
||||||
<div :class="$style.bottom">
|
|
||||||
<button class="_button" :class="$style.post" data-cy-open-post-form @click="os.post">
|
|
||||||
<i :class="$style.postIcon" class="ti ti-pencil ti-fw"></i><span style="position: relative;">{{ i18n.ts.note }}</span>
|
|
||||||
</button>
|
|
||||||
<button class="_button" :class="$style.account" @click="openAccountMenu">
|
|
||||||
<MkAvatar :user="$i" :class="$style.avatar"/><MkAcct :class="$style.acct" class="_nowrap" :user="$i"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, defineAsyncComponent } from 'vue';
|
|
||||||
import { openInstanceMenu } from './common.js';
|
|
||||||
import * as os from '@/os.js';
|
|
||||||
import { navbarItemDef } from '@/navbar.js';
|
|
||||||
import { prefer } from '@/preferences.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
|
||||||
import { instance } from '@/instance.js';
|
|
||||||
import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
|
|
||||||
import { $i } from '@/i.js';
|
|
||||||
|
|
||||||
const otherMenuItemIndicated = computed(() => {
|
|
||||||
for (const def in navbarItemDef) {
|
|
||||||
if (prefer.r.menu.value.includes(def)) continue;
|
|
||||||
if (navbarItemDef[def].indicated) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
function openAccountMenu(ev: MouseEvent) {
|
|
||||||
openAccountMenu_({
|
|
||||||
withExtraOperation: true,
|
|
||||||
}, ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
function more() {
|
|
||||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {}, {
|
|
||||||
closed: () => dispose(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
.root {
|
|
||||||
--nav-bg-transparent: color(from var(--MI_THEME-navBg) srgb r g b / 0.5);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1;
|
|
||||||
padding: 20px 0;
|
|
||||||
background: var(--nav-bg-transparent);
|
|
||||||
-webkit-backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center center;
|
|
||||||
-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
|
|
||||||
mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.instance {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instanceIcon {
|
|
||||||
display: inline-block;
|
|
||||||
width: 38px;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom {
|
|
||||||
position: sticky;
|
|
||||||
bottom: 0;
|
|
||||||
padding: 20px 0;
|
|
||||||
background: var(--nav-bg-transparent);
|
|
||||||
-webkit-backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
}
|
|
||||||
|
|
||||||
.post {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
color: var(--MI_THEME-fgOnAccent);
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
width: calc(100% - 38px);
|
|
||||||
height: 100%;
|
|
||||||
margin: auto;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover, &.active {
|
|
||||||
&::before {
|
|
||||||
background: hsl(from var(--MI_THEME-accent) h s calc(l + 10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.postIcon {
|
|
||||||
position: relative;
|
|
||||||
margin-left: 30px;
|
|
||||||
margin-right: 8px;
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: 30px;
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
display: block;
|
|
||||||
flex-shrink: 0;
|
|
||||||
position: relative;
|
|
||||||
width: 32px;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acct {
|
|
||||||
display: block;
|
|
||||||
flex-shrink: 1;
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.middle {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
margin: 16px 16px;
|
|
||||||
border-top: solid 0.5px var(--MI_THEME-divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
padding-left: 24px;
|
|
||||||
line-height: 2.85rem;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: var(--MI_THEME-navFg);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
color: light-dark(hsl(from var(--MI_THEME-navFg) h s calc(l - 17)), hsl(from var(--MI_THEME-navFg) h s calc(l + 17)));
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--MI_THEME-navActive);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover, &.active {
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
width: calc(100% - 24px);
|
|
||||||
height: 100%;
|
|
||||||
margin: auto;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: var(--MI_THEME-accentedBg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemIcon {
|
|
||||||
position: relative;
|
|
||||||
width: 32px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemIndicator {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 20px;
|
|
||||||
color: var(--MI_THEME-navIndicator);
|
|
||||||
font-size: 8px;
|
|
||||||
|
|
||||||
&:has(.itemIndicateValueIcon) {
|
|
||||||
animation: none;
|
|
||||||
left: auto;
|
|
||||||
right: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemText {
|
|
||||||
position: relative;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -10,6 +10,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu">
|
<button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu">
|
||||||
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
|
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
|
||||||
</button>
|
</button>
|
||||||
|
<button v-if="!iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode">
|
||||||
|
<i class="ti ti-bolt ti-fw"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.middle">
|
<div :class="$style.middle">
|
||||||
<MkA v-tooltip.noDelay.right="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact>
|
<MkA v-tooltip.noDelay.right="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact>
|
||||||
|
@ -50,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-if="showWidgetButton" class="_button" :class="[$style.widget]" @click="() => emit('widgetButtonClick')">
|
<button v-if="showWidgetButton" class="_button" :class="[$style.widget]" @click="() => emit('widgetButtonClick')">
|
||||||
<i class="ti ti-apps ti-fw"></i>
|
<i class="ti ti-apps ti-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode">
|
<button v-if="iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode">
|
||||||
<i class="ti ti-bolt ti-fw"></i>
|
<i class="ti ti-bolt ti-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="() => { os.post(); }">
|
<button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="() => { os.post(); }">
|
||||||
|
@ -79,6 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</svg>
|
</svg>
|
||||||
<button class="_button" :class="$style.subButtonClickable" @click="menuEdit"><i :class="$style.subButtonIcon" class="ti ti-settings-2"></i></button>
|
<button class="_button" :class="$style.subButtonClickable" @click="menuEdit"><i :class="$style.subButtonIcon" class="ti ti-settings-2"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="!props.asDrawer">
|
||||||
<div :class="$style.subButtonGapFill"></div>
|
<div :class="$style.subButtonGapFill"></div>
|
||||||
<div :class="$style.subButtonGapFillDivider"></div>
|
<div :class="$style.subButtonGapFillDivider"></div>
|
||||||
<div :class="[$style.subButton, $style.toggleButton]">
|
<div :class="[$style.subButton, $style.toggleButton]">
|
||||||
|
@ -89,6 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</svg>
|
</svg>
|
||||||
<button class="_button" :class="$style.subButtonClickable" @click="toggleIconOnly"><i v-if="iconOnly" class="ti ti-chevron-right" :class="$style.subButtonIcon"></i><i v-else class="ti ti-chevron-left" :class="$style.subButtonIcon"></i></button>
|
<button class="_button" :class="$style.subButtonClickable" @click="toggleIconOnly"><i v-if="iconOnly" class="ti ti-chevron-right" :class="$style.subButtonIcon"></i><i v-else class="ti ti-chevron-left" :class="$style.subButtonIcon"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -111,15 +116,16 @@ const router = useRouter();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
showWidgetButton?: boolean;
|
showWidgetButton?: boolean;
|
||||||
|
asDrawer?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'widgetButtonClick'): void;
|
(ev: 'widgetButtonClick'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const forceIconOnly = ref(window.innerWidth <= 1279);
|
const forceIconOnly = ref(!props.asDrawer && window.innerWidth <= 1279);
|
||||||
const iconOnly = computed(() => {
|
const iconOnly = computed(() => {
|
||||||
return forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon');
|
return !props.asDrawer && (forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon'));
|
||||||
});
|
});
|
||||||
|
|
||||||
const otherMenuItemIndicated = computed(() => {
|
const otherMenuItemIndicated = computed(() => {
|
||||||
|
@ -208,13 +214,51 @@ function menuEdit() {
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
background: var(--MI_THEME-navBg);
|
background: var(--MI_THEME-navBg);
|
||||||
contain: strict;
|
contain: strict;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
direction: rtl; // スクロールバーを左に表示したいため
|
direction: rtl; // スクロールバーを左に表示したいため
|
||||||
}
|
}
|
||||||
|
|
||||||
.top {
|
.top {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
|
|
||||||
|
/* 疑似progressive blur */
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
inset: 0;
|
||||||
|
content: "";
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to top,
|
||||||
|
rgb(0 0 0 / 0%) 0%,
|
||||||
|
rgb(0 0 0 / 4.9%) 7.75%,
|
||||||
|
rgb(0 0 0 / 10.4%) 11.25%,
|
||||||
|
rgb(0 0 0 / 45%) 23.55%,
|
||||||
|
rgb(0 0 0 / 55%) 26.45%,
|
||||||
|
rgb(0 0 0 / 89.6%) 38.75%,
|
||||||
|
rgb(0 0 0 / 95.1%) 42.25%,
|
||||||
|
rgb(0 0 0 / 100%) 50%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
inset: 0;
|
||||||
|
bottom: 25%;
|
||||||
|
content: "";
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to top,
|
||||||
|
rgb(0 0 0 / 0%) 0%,
|
||||||
|
rgb(0 0 0 / 4.9%) 15.5%,
|
||||||
|
rgb(0 0 0 / 10.4%) 22.5%,
|
||||||
|
rgb(0 0 0 / 45%) 47.1%,
|
||||||
|
rgb(0 0 0 / 55%) 52.9%,
|
||||||
|
rgb(0 0 0 / 89.6%) 77.5%,
|
||||||
|
rgb(0 0 0 / 95.1%) 91.9%,
|
||||||
|
rgb(0 0 0 / 100%) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.middle {
|
.middle {
|
||||||
|
@ -223,6 +267,47 @@ function menuEdit() {
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
|
|
||||||
|
/* 疑似progressive blur */
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
inset: -30px 0 0 0;
|
||||||
|
content: "";
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgb(0 0 0 / 0%) 0%,
|
||||||
|
rgb(0 0 0 / 4.9%) 7.75%,
|
||||||
|
rgb(0 0 0 / 10.4%) 11.25%,
|
||||||
|
rgb(0 0 0 / 45%) 23.55%,
|
||||||
|
rgb(0 0 0 / 55%) 26.45%,
|
||||||
|
rgb(0 0 0 / 89.6%) 38.75%,
|
||||||
|
rgb(0 0 0 / 95.1%) 42.25%,
|
||||||
|
rgb(0 0 0 / 100%) 50%
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
inset: 0;
|
||||||
|
top: 25%;
|
||||||
|
content: "";
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgb(0 0 0 / 0%) 0%,
|
||||||
|
rgb(0 0 0 / 4.9%) 15.5%,
|
||||||
|
rgb(0 0 0 / 10.4%) 22.5%,
|
||||||
|
rgb(0 0 0 / 45%) 47.1%,
|
||||||
|
rgb(0 0 0 / 55%) 52.9%,
|
||||||
|
rgb(0 0 0 / 89.6%) 77.5%,
|
||||||
|
rgb(0 0 0 / 95.1%) 91.9%,
|
||||||
|
rgb(0 0 0 / 100%) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.subButtons {
|
.subButtons {
|
||||||
|
@ -307,29 +392,18 @@ function menuEdit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
.top {
|
.top {
|
||||||
|
--top-height: 80px;
|
||||||
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding: 20px 0;
|
display: flex;
|
||||||
background: var(--nav-bg-transparent);
|
height: var(--top-height);
|
||||||
-webkit-backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.instance {
|
.instance {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
width: var(--top-height);
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
> .instanceIcon {
|
|
||||||
outline: 2px solid var(--MI_THEME-focus);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.instanceIcon {
|
.instanceIcon {
|
||||||
|
@ -339,27 +413,22 @@ function menuEdit() {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom {
|
|
||||||
position: sticky;
|
|
||||||
bottom: 0;
|
|
||||||
padding-top: 20px;
|
|
||||||
background: var(--nav-bg-transparent);
|
|
||||||
-webkit-backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
}
|
|
||||||
|
|
||||||
.realtimeMode {
|
.realtimeMode {
|
||||||
display: block;
|
display: inline-block;
|
||||||
position: relative;
|
width: var(--top-height);
|
||||||
width: 100%;
|
margin-left: auto;
|
||||||
height: 52px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&.on {
|
&.on {
|
||||||
color: var(--MI_THEME-accent);
|
color: var(--MI_THEME-accent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.post {
|
.post {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -549,9 +618,6 @@ function menuEdit() {
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
background: var(--nav-bg-transparent);
|
|
||||||
-webkit-backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.instance {
|
.instance {
|
||||||
|
@ -580,9 +646,6 @@ function menuEdit() {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
background: var(--nav-bg-transparent);
|
|
||||||
-webkit-backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
backdrop-filter: var(--MI-blur, blur(8px));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget {
|
.widget {
|
||||||
|
@ -691,7 +754,7 @@ function menuEdit() {
|
||||||
.item {
|
.item {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 18px 0;
|
padding: 16px 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name || antennaName || i18n.ts._deck._columns.antenna }}</span>
|
<i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name || antennaName || i18n.ts._deck._columns.antenna }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId"/>
|
<MkStreamingNotesTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId"/>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import type { Column } from '@/deck.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import type { SoundStore } from '@/preferences/def.js';
|
import type { SoundStore } from '@/preferences/def.js';
|
||||||
import { updateColumn } from '@/deck.js';
|
import { updateColumn } from '@/deck.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.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';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div style="padding: 8px; text-align: center;">
|
<div style="padding: 8px; text-align: center;">
|
||||||
<MkButton primary gradate rounded inline small @click="post"><i class="ti ti-pencil"></i></MkButton>
|
<MkButton primary gradate rounded inline small @click="post"><i class="ti ti-pencil"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
<MkTimeline ref="timeline" src="channel" :channel="column.channelId"/>
|
<MkStreamingNotesTimeline ref="timeline" src="channel" :channel="column.channelId"/>
|
||||||
</template>
|
</template>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
@ -26,7 +26,7 @@ import type { Column } from '@/deck.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import type { SoundStore } from '@/preferences/def.js';
|
import type { SoundStore } from '@/preferences/def.js';
|
||||||
import { updateColumn } from '@/deck.js';
|
import { updateColumn } from '@/deck.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { favoritedChannelsCache } from '@/cache.js';
|
import { favoritedChannelsCache } from '@/cache.js';
|
||||||
|
|
|
@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<XColumn :column="column" :isStacked="isStacked" :refresher="() => reloadTimeline()">
|
<XColumn :column="column" :isStacked="isStacked" :refresher="() => reloadTimeline()">
|
||||||
<template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.direct }}</template>
|
<template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.direct }}</template>
|
||||||
|
|
||||||
<MkNotes ref="tlComponent" :pagination="pagination"/>
|
<MkNotesTimeline ref="tlComponent" :pagination="pagination"/>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref, useTemplateRef } from 'vue';
|
||||||
import XColumn from './column.vue';
|
import XColumn from './column.vue';
|
||||||
import type { Column } from '@/deck.js';
|
import type { Column } from '@/deck.js';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -31,11 +31,11 @@ const pagination = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const tlComponent = ref<InstanceType<typeof MkNotes>>();
|
const tlComponent = useTemplateRef('tlComponent');
|
||||||
|
|
||||||
function reloadTimeline() {
|
function reloadTimeline() {
|
||||||
return new Promise<void>((res) => {
|
return new Promise<void>((res) => {
|
||||||
tlComponent.value?.pagingComponent?.reload().then(() => {
|
tlComponent.value?.reload().then(() => {
|
||||||
res();
|
res();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i class="ti ti-list"></i><span style="margin-left: 8px;">{{ (column.name || listName) ?? i18n.ts._deck._columns.list }}</span>
|
<i class="ti ti-list"></i><span style="margin-left: 8px;">{{ (column.name || listName) ?? i18n.ts._deck._columns.list }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :withRenotes="withRenotes"/>
|
<MkStreamingNotesTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :withRenotes="withRenotes"/>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import type { Column } from '@/deck.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import type { SoundStore } from '@/preferences/def.js';
|
import type { SoundStore } from '@/preferences/def.js';
|
||||||
import { updateColumn } from '@/deck.js';
|
import { updateColumn } from '@/deck.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.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';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -7,27 +7,27 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<XColumn :column="column" :isStacked="isStacked" :refresher="() => reloadTimeline()">
|
<XColumn :column="column" :isStacked="isStacked" :refresher="() => reloadTimeline()">
|
||||||
<template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.mentions }}</template>
|
<template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.mentions }}</template>
|
||||||
|
|
||||||
<MkNotes ref="tlComponent" :pagination="pagination"/>
|
<MkNotesTimeline ref="tlComponent" :pagination="pagination"/>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { ref, useTemplateRef } from 'vue';
|
||||||
import XColumn from './column.vue';
|
import XColumn from './column.vue';
|
||||||
import type { Column } from '@/deck.js';
|
import type { Column } from '@/deck.js';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import { i18n } from '@/i18n.js';
|
||||||
import { i18n } from '../../i18n.js';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
column: Column;
|
column: Column;
|
||||||
isStacked: boolean;
|
isStacked: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tlComponent = ref<InstanceType<typeof MkNotes>>();
|
const tlComponent = useTemplateRef('tlComponent');
|
||||||
|
|
||||||
function reloadTimeline() {
|
function reloadTimeline() {
|
||||||
return new Promise<void>((res) => {
|
return new Promise<void>((res) => {
|
||||||
tlComponent.value?.pagingComponent?.reload().then(() => {
|
tlComponent.value?.reload().then(() => {
|
||||||
res();
|
res();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<XColumn :column="column" :isStacked="isStacked" :menu="menu" :refresher="async () => { await notificationsComponent?.reload() }">
|
<XColumn :column="column" :isStacked="isStacked" :menu="menu" :refresher="async () => { await notificationsComponent?.reload() }">
|
||||||
<template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.notifications }}</template>
|
<template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.notifications }}</template>
|
||||||
|
|
||||||
<XNotifications ref="notificationsComponent" :excludeTypes="props.column.excludeTypes"/>
|
<MkStreamingNotificationsTimeline ref="notificationsComponent" :excludeTypes="props.column.excludeTypes"/>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import { defineAsyncComponent, useTemplateRef } from 'vue';
|
||||||
import XColumn from './column.vue';
|
import XColumn from './column.vue';
|
||||||
import type { Column } from '@/deck.js';
|
import type { Column } from '@/deck.js';
|
||||||
import { updateColumn } from '@/deck.js';
|
import { updateColumn } from '@/deck.js';
|
||||||
import XNotifications from '@/components/MkNotifications.vue';
|
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i class="ti ti-badge"></i><span style="margin-left: 8px;">{{ column.name || roleName || i18n.ts._deck._columns.roleTimeline }}</span>
|
<i class="ti ti-badge"></i><span style="margin-left: 8px;">{{ column.name || roleName || i18n.ts._deck._columns.roleTimeline }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<MkTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId"/>
|
<MkStreamingNotesTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId"/>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import type { Column } from '@/deck.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import type { SoundStore } from '@/preferences/def.js';
|
import type { SoundStore } from '@/preferences/def.js';
|
||||||
import { updateColumn } from '@/deck.js';
|
import { updateColumn } from '@/deck.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.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';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</p>
|
</p>
|
||||||
<p :class="$style.disabledDescription">{{ i18n.ts._disabledTimeline.description }}</p>
|
<p :class="$style.disabledDescription">{{ i18n.ts._disabledTimeline.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
<MkTimeline
|
<MkStreamingNotesTimeline
|
||||||
v-else-if="column.tl"
|
v-else-if="column.tl"
|
||||||
ref="timeline"
|
ref="timeline"
|
||||||
:key="column.tl + withRenotes + withReplies + onlyFiles"
|
:key="column.tl + withRenotes + withReplies + onlyFiles"
|
||||||
|
@ -37,7 +37,7 @@ import type { Column } from '@/deck.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import type { SoundStore } from '@/preferences/def.js';
|
import type { SoundStore } from '@/preferences/def.js';
|
||||||
import { removeColumn, updateColumn } from '@/deck.js';
|
import { removeColumn, updateColumn } from '@/deck.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { hasWithReplies, isAvailableBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
|
import { hasWithReplies, isAvailableBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
|
||||||
|
|
|
@ -39,6 +39,7 @@ import type { PageMetadata } from '@/page.js';
|
||||||
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
|
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
|
||||||
import XPreferenceRestore from '@/ui/_common_/PreferenceRestore.vue';
|
import XPreferenceRestore from '@/ui/_common_/PreferenceRestore.vue';
|
||||||
import XTitlebar from '@/ui/_common_/titlebar.vue';
|
import XTitlebar from '@/ui/_common_/titlebar.vue';
|
||||||
|
import XSidebar from '@/ui/_common_/navbar.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
@ -51,7 +52,6 @@ import { shouldSuggestRestoreBackup } from '@/preferences/utility.js';
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
|
||||||
const XWidgets = defineAsyncComponent(() => import('./_common_/widgets.vue'));
|
const XWidgets = defineAsyncComponent(() => import('./_common_/widgets.vue'));
|
||||||
const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
|
|
||||||
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
|
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
|
||||||
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
|
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
|
||||||
|
|
||||||
|
|
|
@ -196,6 +196,23 @@ export function usePagination<T extends MisskeyEntity>(props: {
|
||||||
queuedAheadItemsCount.value = 0;
|
queuedAheadItemsCount.value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeItem(id: string) {
|
||||||
|
const index = items.value.findIndex(x => x.id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
items.value.splice(index, 1);
|
||||||
|
if (props.useShallowRef) triggerRef(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateItem(id: string, updator: (item: T) => T) {
|
||||||
|
const index = items.value.findIndex(x => x.id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
const item = items.value[index]!;
|
||||||
|
items.value[index] = updator(item);
|
||||||
|
if (props.useShallowRef) triggerRef(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
|
@ -213,6 +230,8 @@ export function usePagination<T extends MisskeyEntity>(props: {
|
||||||
unshiftItems,
|
unshiftItems,
|
||||||
prepend,
|
prepend,
|
||||||
trim,
|
trim,
|
||||||
|
removeItem,
|
||||||
|
updateItem,
|
||||||
enqueue,
|
enqueue,
|
||||||
releaseQueue,
|
releaseQueue,
|
||||||
error,
|
error,
|
||||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configureNotification()"><i class="ti ti-settings"></i></button></template>
|
<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configureNotification()"><i class="ti ti-settings"></i></button></template>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<XNotifications :excludeTypes="widgetProps.excludeTypes"/>
|
<MkStreamingNotificationsTimeline :excludeTypes="widgetProps.excludeTypes"/>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -21,7 +21,7 @@ import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import XNotifications from '@/components/MkNotifications.vue';
|
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<p :class="$style.disabledDescription">{{ i18n.ts._disabledTimeline.description }}</p>
|
<p :class="$style.disabledDescription">{{ i18n.ts._disabledTimeline.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<MkTimeline :key="widgetProps.src === 'list' ? `list:${widgetProps.list.id}` : widgetProps.src === 'antenna' ? `antenna:${widgetProps.antenna.id}` : widgetProps.src" :src="widgetProps.src" :list="widgetProps.list ? widgetProps.list.id : null" :antenna="widgetProps.antenna ? widgetProps.antenna.id : null"/>
|
<MkStreamingNotesTimeline :key="widgetProps.src === 'list' ? `list:${widgetProps.list.id}` : widgetProps.src === 'antenna' ? `antenna:${widgetProps.antenna.id}` : widgetProps.src" :src="widgetProps.src" :list="widgetProps.list ? widgetProps.list.id : null" :antenna="widgetProps.antenna ? widgetProps.antenna.id : null"/>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -38,7 +38,7 @@ import type { GetFormResultType } from '@/utility/form.js';
|
||||||
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';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { availableBasicTimelines, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
|
import { availableBasicTimelines, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
|
Loading…
Reference in New Issue