This commit is contained in:
syuilo 2025-04-29 12:16:32 +09:00
parent 445e52214a
commit ccfd0ed0ea
4 changed files with 67 additions and 108 deletions

View File

@ -19,6 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div v-else ref="rootEl"> <div v-else ref="rootEl">
<div v-if="notesQueue.length > 0" :class="$style.new" @click="releaseQueue()"><button class="_button" :class="$style.newButton">{{ i18n.ts.newNoteRecived }}</button></div>
<component <component
:is="prefer.s.animation ? TransitionGroup : 'div'" :is="prefer.s.animation ? TransitionGroup : 'div'"
:class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap, [$style.reverse]: paginationQuery.reversed }]" :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap, [$style.reverse]: paginationQuery.reversed }]"
@ -50,9 +51,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup, onMounted } from 'vue'; import { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup, onMounted, shallowRef, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js'; import { useInterval } from '@@/js/use-interval.js';
import { scrollToTop } from '@@/js/scroll.js';
import type { BasicTimelineType } from '@/timelines.js'; import type { BasicTimelineType } from '@/timelines.js';
import type { PagingCtx } from '@/use/use-pagination.js'; import type { PagingCtx } from '@/use/use-pagination.js';
import { usePagination } from '@/use/use-pagination.js'; import { usePagination } from '@/use/use-pagination.js';
@ -64,6 +66,7 @@ import { instance } from '@/instance.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { store } from '@/store.js'; import { store } from '@/store.js';
import MkNote from '@/components/MkNote.vue'; import MkNote from '@/components/MkNote.vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js'; import { infoImageUrl } from '@/instance.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
@ -87,13 +90,14 @@ const props = withDefaults(defineProps<{
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'queue', count: number): void;
}>(); }>();
provide('inTimeline', true); provide('inTimeline', true);
provide('tl_withSensitive', computed(() => props.withSensitive)); provide('tl_withSensitive', computed(() => props.withSensitive));
provide('inChannel', computed(() => props.src === 'channel')); provide('inChannel', computed(() => props.src === 'channel'));
const rootEl = useTemplateRef('rootEl');
type TimelineQueryType = { type TimelineQueryType = {
antennaId?: string, antennaId?: string,
withRenotes?: boolean, withRenotes?: boolean,
@ -105,7 +109,7 @@ type TimelineQueryType = {
roleId?: string roleId?: string
}; };
let tlNotesCount = 0; let noteCounterForAd = 0;
const POLLING_INTERVAL = 1000 * 15; const POLLING_INTERVAL = 1000 * 15;
@ -124,14 +128,28 @@ if (!store.s.realtimeMode) {
}); });
} }
function prepend(note) { const notesQueue = ref<Misskey.entities.Note[]>([]);
tlNotesCount++;
if (instance.notesPerOneAd > 0 && tlNotesCount % instance.notesPerOneAd === 0) { function releaseQueue() {
paginator.unshiftItems(notesQueue.value);
notesQueue.value = [];
scrollToTop(rootEl.value);
}
function prepend(note: Misskey.entities.Note) {
noteCounterForAd++;
if (instance.notesPerOneAd > 0 && noteCounterForAd % instance.notesPerOneAd === 0) {
note._shouldInsertAd_ = true; note._shouldInsertAd_ = true;
} }
const isTop = false;
if (isTop) {
paginator.prepend(note); paginator.prepend(note);
} else {
notesQueue.value.unshift(note);
}
if (props.sound) { if (props.sound) {
sound.playMisskeySfx($i && (note.userId === $i.id) ? 'noteMy' : 'note'); sound.playMisskeySfx($i && (note.userId === $i.id) ? 'noteMy' : 'note');
@ -315,7 +333,7 @@ onUnmounted(() => {
function reloadTimeline() { function reloadTimeline() {
return new Promise<void>((res) => { return new Promise<void>((res) => {
tlNotesCount = 0; noteCounterForAd = 0;
paginator.reload().then(() => { paginator.reload().then(() => {
res(); res();
@ -376,7 +394,33 @@ defineExpose({
} }
} }
.new {
position: sticky;
top: var(--MI-stickyTop, 0px);
z-index: 1000;
width: 100%;
box-sizing: border-box;
padding: 8px 0;
-webkit-backdrop-filter: var(--MI-blur, blur(4px));
backdrop-filter: var(--MI-blur, blur(4px));
}
.newButton {
display: block;
padding: 8px 16px;
border-radius: 999px;
width: max-content;
margin: auto;
background: var(--MI_THEME-accent);
color: var(--MI_THEME-fgOnAccent);
font-size: 90%;
}
.ad:empty { .ad:empty {
display: none; display: none;
} }
.more {
margin: 16px auto;
}
</style> </style>

View File

@ -6,26 +6,21 @@ 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;">
<div ref="rootEl">
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
<div :class="$style.tl"> <div :class="$style.tl">
<MkTimeline <MkTimeline
ref="tlEl" :key="antennaId" ref="tlEl" :key="antennaId"
src="antenna" src="antenna"
:antenna="antennaId" :antenna="antennaId"
:sound="true" :sound="true"
@queue="queueUpdated"
/> />
</div> </div>
</div> </div>
</div>
</PageWithHeader> </PageWithHeader>
</template> </template>
<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 { scrollInContainer } from '@@/js/scroll.js';
import MkTimeline from '@/components/MkTimeline.vue'; import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
@ -40,18 +35,8 @@ const props = defineProps<{
}>(); }>();
const antenna = ref<Misskey.entities.Antenna | null>(null); const antenna = ref<Misskey.entities.Antenna | null>(null);
const queue = ref(0);
const rootEl = useTemplateRef('rootEl');
const tlEl = useTemplateRef('tlEl'); const tlEl = useTemplateRef('tlEl');
function queueUpdated(q) {
queue.value = q;
}
function top() {
scrollInContainer(rootEl.value, { top: 0 });
}
async function timetravel() { async function timetravel() {
const { canceled, result: date } = await os.inputDate({ const { canceled, result: date } = await os.inputDate({
title: i18n.ts.date, title: i18n.ts.date,
@ -94,25 +79,6 @@ definePage(() => ({
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.new {
position: sticky;
top: calc(var(--MI-stickyTop, 0px) + 16px);
z-index: 1000;
width: 100%;
margin: calc(-0.675em - 8px) 0;
&:first-child {
margin-top: calc(-0.675em - 8px - var(--MI-margin));
}
}
.newButton {
display: block;
margin: var(--MI-margin) auto 0 auto;
padding: 8px 16px;
border-radius: 32px;
}
.tl { .tl {
background: var(--MI_THEME-bg); background: var(--MI_THEME-bg);
border-radius: var(--MI-radius); border-radius: var(--MI-radius);

View File

@ -4,13 +4,12 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<PageWithHeader ref="pageComponent" v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :swipable="true" :displayMyAvatar="true"> <PageWithHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :swipable="true" :displayMyAvatar="true">
<div class="_spacer" style="--MI_SPACER-w: 800px;"> <div class="_spacer" style="--MI_SPACER-w: 800px;">
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()"> <MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
{{ i18n.ts._timelineDescription[src] }} {{ i18n.ts._timelineDescription[src] }}
</MkInfo> </MkInfo>
<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);"/>
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
<MkTimeline <MkTimeline
ref="tlComponent" ref="tlComponent"
:key="src + withRenotes + withReplies + onlyFiles + withSensitive" :key="src + withRenotes + withReplies + onlyFiles + withSensitive"
@ -22,7 +21,6 @@ SPDX-License-Identifier: AGPL-3.0-only
:withSensitive="withSensitive" :withSensitive="withSensitive"
:onlyFiles="onlyFiles" :onlyFiles="onlyFiles"
:sound="true" :sound="true"
@queue="queueUpdated"
/> />
</div> </div>
</PageWithHeader> </PageWithHeader>
@ -51,11 +49,9 @@ import { prefer } from '@/preferences.js';
provide('shouldOmitHeaderTitle', true); provide('shouldOmitHeaderTitle', true);
const tlComponent = useTemplateRef('tlComponent'); const tlComponent = useTemplateRef('tlComponent');
const pageComponent = useTemplateRef('pageComponent');
type TimelinePageSrc = BasicTimelineType | `list:${string}`; type TimelinePageSrc = BasicTimelineType | `list:${string}`;
const queue = ref(0);
const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global'); const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global');
const src = computed<TimelinePageSrc>({ const src = computed<TimelinePageSrc>({
get: () => ($i ? store.r.tl.value.src : srcWhenNotSignin.value), get: () => ($i ? store.r.tl.value.src : srcWhenNotSignin.value),
@ -110,18 +106,6 @@ const withSensitive = computed<boolean>({
set: (x) => saveTlFilter('withSensitive', x), set: (x) => saveTlFilter('withSensitive', x),
}); });
watch(src, () => {
queue.value = 0;
});
function queueUpdated(q: number): void {
queue.value = q;
}
function top(): void {
if (pageComponent.value) pageComponent.value.scrollToTop();
}
async function chooseList(ev: MouseEvent): Promise<void> { async function chooseList(ev: MouseEvent): Promise<void> {
const lists = await userListsCache.fetch(); const lists = await userListsCache.fetch();
const items: MenuItem[] = [ const items: MenuItem[] = [

View File

@ -6,26 +6,21 @@ 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;">
<div ref="rootEl">
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
<div :class="$style.tl"> <div :class="$style.tl">
<MkTimeline <MkTimeline
ref="tlEl" :key="listId" ref="tlEl" :key="listId"
src="list" src="list"
:list="listId" :list="listId"
:sound="true" :sound="true"
@queue="queueUpdated"
/> />
</div> </div>
</div> </div>
</div>
</PageWithHeader> </PageWithHeader>
</template> </template>
<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 { scrollInContainer } from '@@/js/scroll.js';
import MkTimeline from '@/components/MkTimeline.vue'; import MkTimeline from '@/components/MkTimeline.vue';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
@ -39,9 +34,6 @@ const props = defineProps<{
}>(); }>();
const list = ref<Misskey.entities.UserList | null>(null); const list = ref<Misskey.entities.UserList | null>(null);
const queue = ref(0);
const tlEl = useTemplateRef('tlEl');
const rootEl = useTemplateRef('rootEl');
watch(() => props.listId, async () => { watch(() => props.listId, async () => {
list.value = await misskeyApi('users/lists/show', { list.value = await misskeyApi('users/lists/show', {
@ -49,14 +41,6 @@ watch(() => props.listId, async () => {
}); });
}, { immediate: true }); }, { immediate: true });
function queueUpdated(q) {
queue.value = q;
}
function top() {
scrollInContainer(rootEl.value, { top: 0 });
}
function settings() { function settings() {
router.push(`/my/lists/${props.listId}`); router.push(`/my/lists/${props.listId}`);
} }
@ -76,25 +60,6 @@ definePage(() => ({
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.new {
position: sticky;
top: calc(var(--MI-stickyTop, 0px) + 16px);
z-index: 1000;
width: 100%;
margin: calc(-0.675em - 8px) 0;
&:first-child {
margin-top: calc(-0.675em - 8px - var(--MI-margin));
}
}
.newButton {
display: block;
margin: var(--MI-margin) auto 0 auto;
padding: 8px 16px;
border-radius: 32px;
}
.tl { .tl {
background: var(--MI_THEME-bg); background: var(--MI_THEME-bg);
border-radius: var(--MI-radius); border-radius: var(--MI-radius);