1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-14 00:35:52 +02:00
Files
misskey/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
かっこかり 16ffd88ecc enhance: 誕生日のユーザーウィジェットで、今日だけに限らず、直近の誕生日ユーザーを表示できるように (#13637)
* enhance(frontend): 「今日誕生日のフォロー中ユーザー」ウィジェットをリファクタリング

(cherry picked from commit 24652b9364)

* fix(backend): 年越しの時期で誕生日検索クエリーが誤動作する問題を修正 (MisskeyIO#577)

(cherry picked from commit 38581006be)

* fix

* spdx

* delete birthday param on users/following api

* 名称を一本化

* Update Changelog

* Update Changelog

* fix(frontend/WidgetBirthdayFollowings): ユーザーの名前が長いと投稿ボタンがはみ出てしまう問題を修正 (MisskeyIO#582)

(cherry picked from commit fa47a545b1)

* use module css

* default 3day

* Revert "delete birthday param on users/following api"

This reverts commit a47456c1c4.

* Update Changelog

* 日付が1ヶ月ズレている問題を修正?

* fix: 日付関連のバグを修正

Co-authored-by: taiy <53635909+taiyme@users.noreply.github.com>

* build misskey-js types

* add comment

* Update CHANGELOG.md

* migrate

* change migration

* UPdate Changelog

* fix: revert unnecessary changes

* 🎨

* i18n

* fix

* update changelog

* 🎨

* fix lint

* refactor: remove unnecessary classes

* fix

* fix

---------

Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
Co-authored-by: taiy <53635909+taiyme@users.noreply.github.com>
2025-12-31 22:33:26 +09:00

191 lines
5.2 KiB
Vue

<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkContainer :style="`height: ${widgetProps.height}px;`" :showHeader="widgetProps.showHeader" :scrollable="true" class="mkw-bdayfollowings">
<template #icon><i class="ti ti-cake"></i></template>
<template #header>{{ i18n.ts._widgets.birthdayFollowings }}</template>
<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="fetch"><i class="ti ti-refresh"></i></button></template>
<MkPagination v-slot="{ items }" :paginator="birthdayUsersPaginator">
<div>
<template v-for="(user, i) in items" :key="user.id">
<div
v-if="i > 0 && isSeparatorNeeded(birthdayUsersPaginator.items.value[i - 1].birthday, user.birthday)"
>
<div :class="$style.date">
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(birthdayUsersPaginator.items.value[i - 1].birthday, user.birthday)?.prevText }}</span>
<span style="height: 1em; width: 1px; background: var(--MI_THEME-divider);"></span>
<span>{{ getSeparatorInfo(birthdayUsersPaginator.items.value[i - 1].birthday, user.birthday)?.nextText }} <i class="ti ti-chevron-down"></i></span>
</div>
<XUser :class="$style.user" :item="user" />
</div>
<XUser v-else :class="$style.user" :item="user" />
</template>
</div>
</MkPagination>
</MkContainer>
</template>
<script lang="ts" setup>
import { computed, markRaw, ref, watch } from 'vue';
import { useLowresTime } from '@/composables/use-lowres-time.js';
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
import { useWidgetPropsManager } from './widget.js';
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkPagination from '@/components/MkPagination.vue';
import XUser from './WidgetBirthdayFollowings.user.vue';
import { i18n } from '@/i18n.js';
import { Paginator } from '@/utility/paginator.js';
const name = 'birthdayFollowings';
const widgetPropsDef = {
showHeader: {
type: 'boolean',
label: i18n.ts._widgetOptions.showHeader,
default: true,
},
height: {
type: 'number' as const,
label: i18n.ts._widgetOptions.height,
default: 300,
},
period: {
type: 'radio' as const,
label: i18n.ts._widgetOptions._birthdayFollowings.period,
default: '3day',
options: [{
value: 'today' as const,
label: i18n.ts.today,
}, {
value: '3day' as const,
label: i18n.tsx.dayX({ day: 3 }),
}, {
value: 'week' as const,
label: i18n.ts.oneWeek,
}, {
value: 'month' as const,
label: i18n.ts.oneMonth,
}],
},
} satisfies FormWithDefault;
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
const props = defineProps<WidgetComponentProps<WidgetProps>>();
const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
const { widgetProps, configure } = useWidgetPropsManager(
name,
widgetPropsDef,
props,
emit,
);
const now = useLowresTime();
const nextDay = new Date();
nextDay.setHours(24, 0, 0, 0);
let nextDayMidnightTime = nextDay.getTime();
const begin = ref<Date>(new Date());
const end = computed(() => {
switch (widgetProps.period) {
case '3day':
return new Date(begin.value.getTime() + 1000 * 60 * 60 * 24 * 3);
case 'week':
return new Date(begin.value.getTime() + 1000 * 60 * 60 * 24 * 7);
case 'month':
return new Date(begin.value.getTime() + 1000 * 60 * 60 * 24 * 30);
default:
return begin.value;
}
});
const birthdayUsersPaginator = markRaw(new Paginator('users/get-following-birthday-users', {
limit: 18,
offsetMode: true,
computedParams: computed(() => {
if (widgetProps.period === 'today') {
return {
birthday: {
month: begin.value.getMonth() + 1,
day: begin.value.getDate(),
},
};
} else {
return {
birthday: {
begin: {
month: begin.value.getMonth() + 1,
day: begin.value.getDate(),
},
end: {
month: end.value.getMonth() + 1,
day: end.value.getDate(),
},
},
};
}
}),
}));
function fetch() {
const now = new Date();
begin.value = now;
}
const UPDATE_INTERVAL = 1000 * 60;
let nextDayTimer: number | null = null;
watch(now, (to) => {
// 次回更新までに日付が変わる場合、日付が変わった直後に強制的に更新するタイマーをセットする
if (nextDayMidnightTime - to <= UPDATE_INTERVAL) {
if (nextDayTimer != null) {
window.clearTimeout(nextDayTimer);
nextDayTimer = null;
}
nextDayTimer = window.setTimeout(() => {
fetch();
nextDay.setHours(24, 0, 0, 0);
nextDayMidnightTime = nextDay.getTime();
nextDayTimer = null;
}, nextDayMidnightTime - to);
}
}, { immediate: true });
defineExpose<WidgetComponentExpose>({
name,
configure,
id: props.widget ? props.widget.id : null,
});
</script>
<style lang="scss" module>
.root {
container-type: inline-size;
background: var(--MI_THEME-panel);
}
.user {
border-bottom: solid 0.5px var(--MI_THEME-divider);
}
.date {
display: flex;
font-size: 85%;
align-items: center;
justify-content: center;
gap: 1em;
opacity: 0.75;
padding: 8px 8px;
margin: 0 auto;
border-bottom: solid 0.5px var(--MI_THEME-divider);
}
</style>