Compare commits

...

14 Commits

Author SHA1 Message Date
syuilo 7e06a6a2b7 Revert "test"
This reverts commit c4f8cda4aa.
2025-05-02 14:02:25 +09:00
syuilo c4f8cda4aa test 2025-05-02 13:46:26 +09:00
syuilo 15c5e46e4f Revert "test"
This reverts commit 42c53c830e.
2025-05-02 13:42:34 +09:00
syuilo 42c53c830e test 2025-05-02 13:29:45 +09:00
syuilo da58d43a7a Update use-pagination.ts 2025-05-02 13:13:00 +09:00
syuilo 6e5b6a3bdb Revert "test"
This reverts commit 3375619396.
2025-05-02 13:02:16 +09:00
syuilo 3375619396 test 2025-05-02 12:26:45 +09:00
syuilo beca66af01 wip 2025-05-02 11:43:25 +09:00
syuilo dee571ccde Update MkTimeline.vue 2025-05-02 11:16:11 +09:00
syuilo 451f0f7bd1 fix 2025-05-02 11:02:34 +09:00
syuilo 295fe859d8 wip 2025-05-02 09:37:51 +09:00
syuilo 287380b2db Update MkTimeline.vue 2025-05-02 08:59:48 +09:00
syuilo 834281c09c wip 2025-05-02 08:56:15 +09:00
syuilo e096841d35 wip 2025-05-02 08:42:26 +09:00
10 changed files with 164 additions and 29 deletions

20
locales/index.d.ts vendored
View File

@ -2322,6 +2322,10 @@ export interface Locale extends ILocale {
*
*/
"newNoteRecived": string;
/**
*
*/
"newNote": string;
/**
*
*/
@ -5717,6 +5721,22 @@ export interface Locale extends ILocale {
*
*/
"enableSyncThemesBetweenDevices": string;
/**
*
*/
"realtimeMode_description": string;
/**
*
*/
"contentsUpdateFrequency": string;
/**
*
*/
"contentsUpdateFrequency_description": string;
/**
*
*/
"contentsUpdateFrequency_description2": string;
"_chat": {
/**
*

View File

@ -576,6 +576,7 @@ showFixedPostForm: "タイムライン上部に投稿フォームを表示する
showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)"
withRepliesByDefaultForNewlyFollowed: "フォローする際、デフォルトで返信をTLに含むようにする"
newNoteRecived: "新しいノートがあります"
newNote: "新しいノート"
sounds: "サウンド"
sound: "サウンド"
listen: "聴く"
@ -1429,6 +1430,10 @@ _settings:
ifOn: "オンのとき"
ifOff: "オフのとき"
enableSyncThemesBetweenDevices: "デバイス間でインストールしたテーマを同期"
realtimeMode_description: "サーバーと接続を確立し、リアルタイムでコンテンツを更新します。通信量とバッテリーの消費が多くなる場合があります。"
contentsUpdateFrequency: "コンテンツの取得頻度"
contentsUpdateFrequency_description: "高いほどリアルタイムにコンテンツが更新されますが、パフォーマンスが低下し、通信量とバッテリーの消費が多くなります。"
contentsUpdateFrequency_description2: "リアルタイムモードがオンのときは、この設定に関わらずリアルタイムでコンテンツが更新されます。"
_chat:
showSenderName: "送信者の名前を表示"

View File

@ -415,11 +415,7 @@ provide(DI.mfmEmojiReactCallback, (reaction) => {
});
});
if (props.mock) {
watch(() => props.note, (to) => {
note = deepClone(to);
}, { deep: true });
} else {
if (!props.mock) {
useNoteCapture({
note: appearNote,
parentNote: note,
@ -540,6 +536,9 @@ function react(): void {
if (props.mock) {
emit('reaction', reaction);
$appearNote.reactions[reaction] = 1;
$appearNote.reactionCount++;
$appearNote.myReaction = reaction;
return;
}

View File

@ -78,7 +78,12 @@ const paginator = usePagination({
},
});
const POLLING_INTERVAL = 1000 * 15;
const MIN_POLLING_INTERVAL = 1000 * 10;
const POLLING_INTERVAL =
prefer.s.pollingInterval === 1 ? MIN_POLLING_INTERVAL * 1.5 * 1.5 :
prefer.s.pollingInterval === 2 ? MIN_POLLING_INTERVAL * 1.5 :
prefer.s.pollingInterval === 3 ? MIN_POLLING_INTERVAL :
MIN_POLLING_INTERVAL;
if (!store.s.realtimeMode) {
useInterval(async () => {

View File

@ -19,7 +19,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div v-else ref="rootEl">
<div v-if="paginator.queue.value.length > 0" :class="$style.new" @click="releaseQueue()"><button class="_button" :class="$style.newButton">{{ i18n.ts.newNoteRecived }}</button></div>
<div v-if="paginator.queuedAheadItemsCount.value > 0" :class="$style.new">
<div :class="$style.newBg1"></div>
<div :class="$style.newBg2"></div>
<button class="_button" :class="$style.newButton" @click="releaseQueue()"><i class="ti ti-circle-arrow-up"></i> {{ i18n.ts.newNote }}</button>
</div>
<component
:is="prefer.s.animation ? TransitionGroup : 'div'"
:class="[$style.notes, { [$style.noGap]: noGap, '_gaps': !noGap }]"
@ -109,7 +113,12 @@ type TimelineQueryType = {
let adInsertionCounter = 0;
const POLLING_INTERVAL = 1000 * 15;
const MIN_POLLING_INTERVAL = 1000 * 10;
const POLLING_INTERVAL =
prefer.s.pollingInterval === 1 ? MIN_POLLING_INTERVAL * 1.5 * 1.5 :
prefer.s.pollingInterval === 2 ? MIN_POLLING_INTERVAL * 1.5 :
prefer.s.pollingInterval === 3 ? MIN_POLLING_INTERVAL :
MIN_POLLING_INTERVAL;
if (!store.s.realtimeMode) {
useInterval(async () => {
@ -125,11 +134,9 @@ if (!store.s.realtimeMode) {
globalEvents.on('notePosted', (note: Misskey.entities.Note) => {
const isTop = rootEl.value == null ? false : isHeadVisible(rootEl.value, 16);
if (isTop) {
paginator.fetchNewer({
toQueue: false,
});
}
paginator.fetchNewer({
toQueue: !isTop,
});
});
function releaseQueue() {
@ -387,25 +394,77 @@ defineExpose({
}
.new {
--gapFill: 0.5px; //
position: sticky;
top: var(--MI-stickyTop, 0px);
top: calc(var(--MI-stickyTop, 0px) - var(--gapFill));
z-index: 1000;
width: 100%;
box-sizing: border-box;
padding: 8px 0;
padding: calc(10px + var(--gapFill)) 0 10px 0;
}
/* 疑似progressive blur */
.newBg1, .newBg2 {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.newBg1 {
height: 100%;
-webkit-backdrop-filter: var(--MI-blur, blur(2px));
backdrop-filter: var(--MI-blur, blur(2px));
mask-image: linear-gradient( /* 疑似Easing Linear Gradients */
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%
);
}
.newBg2 {
height: 75%;
-webkit-backdrop-filter: var(--MI-blur, blur(4px));
backdrop-filter: var(--MI-blur, blur(4px));
mask-image: linear-gradient( /* 疑似Easing Linear Gradients */
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%
);
}
.newButton {
position: relative;
display: block;
padding: 8px 16px;
padding: 6px 12px;
border-radius: 999px;
width: max-content;
margin: auto;
background: var(--MI_THEME-accent);
color: var(--MI_THEME-fgOnAccent);
font-size: 90%;
font-size: 85%;
&:hover {
background: hsl(from var(--MI_THEME-accent) h s calc(l + 5));
}
&:active {
background: hsl(from var(--MI_THEME-accent) h s calc(l - 5));
}
}
.ad:empty {

View File

@ -76,8 +76,6 @@ const onceReacted = ref<boolean>(false);
function addReaction(emoji) {
onceReacted.value = true;
emit('reacted');
exampleNote.reactions[emoji] = 1;
exampleNote.myReaction = emoji;
doNotification(emoji);
}

View File

@ -41,6 +41,24 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkRadios>
</SearchMarker>
<SearchMarker :keywords="['realtimemode']">
<MkSwitch v-model="realtimeMode">
<template #label><SearchLabel>{{ i18n.ts.realtimeMode }}</SearchLabel></template>
<template #caption><SearchKeyword>{{ i18n.ts._settings.realtimeMode_description }}</SearchKeyword></template>
</MkSwitch>
</SearchMarker>
<MkDisableSection :disabled="realtimeMode">
<SearchMarker :keywords="['polling', 'interval']">
<MkPreferenceContainer k="pollingInterval">
<MkRange v-model="pollingInterval" :min="1" :max="3" :step="1" easing :textConverter="(v) => v === 1 ? i18n.ts.low : v === 2 ? i18n.ts.middle : v === 3 ? i18n.ts.high : ''">
<template #label><SearchLabel>{{ i18n.ts._settings.contentsUpdateFrequency }}</SearchLabel></template>
<template #caption><SearchKeyword>{{ i18n.ts._settings.contentsUpdateFrequency_description }}</SearchKeyword><br><SearchKeyword>{{ i18n.ts._settings.contentsUpdateFrequency_description2 }}</SearchKeyword></template>
</MkRange>
</MkPreferenceContainer>
</SearchMarker>
</MkDisableSection>
<div class="_gaps_s">
<SearchMarker :keywords="['titlebar', 'show']">
<MkPreferenceContainer k="showTitlebar">
@ -717,7 +735,7 @@ import MkRadios from '@/components/MkRadios.vue';
import MkRange from '@/components/MkRange.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue';
import MkDisableSection from '@/components/MkDisableSection.vue';
import FormLink from '@/components/form/link.vue';
import MkLink from '@/components/MkLink.vue';
import MkInfo from '@/components/MkInfo.vue';
@ -740,8 +758,10 @@ const $i = ensureSignin();
const lang = ref(miLocalStorage.getItem('lang'));
const dataSaver = ref(prefer.s.dataSaver);
const realtimeMode = computed(store.makeGetterSetter('realtimeMode'));
const overridedDeviceKind = prefer.model('overridedDeviceKind');
const pollingInterval = prefer.model('pollingInterval');
const showTitlebar = prefer.model('showTitlebar');
const keepCw = prefer.model('keepCw');
const serverDisconnectedBehavior = prefer.model('serverDisconnectedBehavior');
@ -824,6 +844,8 @@ watch(useSystemFont, () => {
watch([
hemisphere,
lang,
realtimeMode,
pollingInterval,
enableInfiniteScroll,
showNoteActionsOnlyHover,
overridedDeviceKind,

View File

@ -241,6 +241,12 @@ export const PREF_DEF = {
numberOfPageCache: {
default: 3,
},
pollingInterval: {
// 1 ... 低
// 2 ... 中
// 3 ... 高
default: 2,
},
showNoteActionsOnlyHover: {
default: false,
},

View File

@ -11,6 +11,7 @@ import { useStream } from '@/stream.js';
import { $i } from '@/i.js';
import { store } from '@/store.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { prefer } from '@/preferences.js';
export const noteEvents = new EventEmitter<{
[ev: `reacted:${string}`]: (ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; }; }) => void;
@ -59,7 +60,12 @@ function pollingDequeue(note: Pick<Misskey.entities.Note, 'id' | 'createdAt'>) {
}
const CAPTURE_MAX = 30;
const POLLING_INTERVAL = 1000 * 15;
const MIN_POLLING_INTERVAL = 1000 * 10;
const POLLING_INTERVAL =
prefer.s.pollingInterval === 1 ? MIN_POLLING_INTERVAL * 1.5 * 1.5 :
prefer.s.pollingInterval === 2 ? MIN_POLLING_INTERVAL * 1.5 :
prefer.s.pollingInterval === 3 ? MIN_POLLING_INTERVAL :
MIN_POLLING_INTERVAL;
window.setInterval(() => {
const ids = [...pollingQueue.entries()]

View File

@ -9,6 +9,7 @@ import type { ComputedRef, Ref, ShallowRef } from 'vue';
import { misskeyApi } from '@/utility/misskey-api.js';
const MAX_ITEMS = 20;
const MAX_QUEUE_ITEMS = 100;
const FIRST_FETCH_LIMIT = 15;
const SECOND_FETCH_LIMIT = 30;
@ -43,7 +44,8 @@ export function usePagination<T extends MisskeyEntity>(props: {
useShallowRef?: boolean;
}) {
const items = props.useShallowRef ? shallowRef<T[]>([]) : ref<T[]>([]);
const queue = props.useShallowRef ? shallowRef<T[]>([]) : ref<T[]>([]);
let aheadQueue: T[] = [];
const queuedAheadItemsCount = ref(0);
const fetching = ref(true);
const moreFetching = ref(false);
const canFetchMore = ref(false);
@ -54,6 +56,9 @@ export function usePagination<T extends MisskeyEntity>(props: {
function getNewestId() {
// 様々な要因により並び順は保証されないのでソートが必要
if (aheadQueue.length > 0) {
return aheadQueue.map(x => x.id).sort().at(-1);
}
return items.value.map(x => x.id).sort().at(-1);
}
@ -64,6 +69,8 @@ export function usePagination<T extends MisskeyEntity>(props: {
async function init(): Promise<void> {
items.value = [];
aheadQueue = [];
queuedAheadItemsCount.value = 0;
fetching.value = true;
const params = props.ctx.params ? isRef(props.ctx.params) ? props.ctx.params.value : props.ctx.params : {};
await misskeyApi<T[]>(props.ctx.endpoint, {
@ -120,6 +127,7 @@ export function usePagination<T extends MisskeyEntity>(props: {
moreFetching.value = false;
} else {
items.value.push(...res);
if (props.useShallowRef) triggerRef(items);
canFetchMore.value = true;
moreFetching.value = false;
}
@ -142,8 +150,11 @@ export function usePagination<T extends MisskeyEntity>(props: {
}),
}).then(res => {
if (options.toQueue) {
queue.value.unshift(...res.toReversed());
if (props.useShallowRef) triggerRef(queue);
aheadQueue.unshift(...res.toReversed());
if (aheadQueue.length > MAX_QUEUE_ITEMS) {
aheadQueue = aheadQueue.slice(0, MAX_QUEUE_ITEMS);
}
queuedAheadItemsCount.value = aheadQueue.length;
} else {
items.value.unshift(...res.toReversed());
if (props.useShallowRef) triggerRef(items);
@ -172,13 +183,17 @@ export function usePagination<T extends MisskeyEntity>(props: {
}
function enqueue(item: T) {
queue.value.unshift(item);
if (props.useShallowRef) triggerRef(queue);
aheadQueue.unshift(item);
if (aheadQueue.length > MAX_QUEUE_ITEMS) {
aheadQueue.pop();
}
queuedAheadItemsCount.value = aheadQueue.length;
}
function releaseQueue() {
unshiftItems(queue.value);
queue.value = [];
unshiftItems(aheadQueue);
aheadQueue = [];
queuedAheadItemsCount.value = 0;
}
onMounted(() => {
@ -187,7 +202,7 @@ export function usePagination<T extends MisskeyEntity>(props: {
return {
items,
queue,
queuedAheadItemsCount,
fetching,
moreFetching,
canFetchMore,