mirror of
https://github.com/misskey-dev/misskey
synced 2025-06-29 16:22:50 +02:00
Compare commits
6 commits
2025.6.4-a
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
c48acad04b | ||
|
5d3bb02f4b | ||
|
933e252687 | ||
|
f1deb89e34 | ||
|
8bc822d829 | ||
|
c215cccf1d |
75 changed files with 1104 additions and 1140 deletions
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- Feat: ノートの下書き機能
|
- Feat: ノートの下書き機能
|
||||||
|
- Feat: クリップ内でノートを検索できるように
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: モデログを検索できるように
|
- Feat: モデログを検索できるように
|
||||||
- Enhance: 設定の自動バックアップをオンにした直後に自動バックアップするように
|
- Enhance: 設定の自動バックアップをオンにした直後に自動バックアップするように
|
||||||
- Enhance: ファイルアップロード前にキャプション設定を行えるように
|
- Enhance: ファイルアップロード前にキャプション設定を行えるように
|
||||||
|
- Enhance: ファイルアップロード時にセンシティブ設定されているか表示するように
|
||||||
- Enhance: ページネーション(一覧表示)の並び順を逆にできるように
|
- Enhance: ページネーション(一覧表示)の並び順を逆にできるように
|
||||||
- Enhance: ページネーション(一覧表示)の基準日時を指定できるように
|
- Enhance: ページネーション(一覧表示)の基準日時を指定できるように
|
||||||
- Fix: ファイルがドライブの既定アップロード先に指定したフォルダにアップロードされない問題を修正
|
- Fix: ファイルがドライブの既定アップロード先に指定したフォルダにアップロードされない問題を修正
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.6.4-alpha.2",
|
"version": "2025.6.4-alpha.3",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Brackets } from 'typeorm';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/_.js';
|
import type { NotesRepository, ClipsRepository, ClipNotesRepository } from '@/models/_.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -46,6 +48,7 @@ export const paramDef = {
|
||||||
untilId: { type: 'string', format: 'misskey:id' },
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
sinceDate: { type: 'integer' },
|
sinceDate: { type: 'integer' },
|
||||||
untilDate: { type: 'integer' },
|
untilDate: { type: 'integer' },
|
||||||
|
search: { type: 'string', minLength: 1, maxLength: 100, nullable: true },
|
||||||
},
|
},
|
||||||
required: ['clipId'],
|
required: ['clipId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -97,6 +100,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
this.queryService.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' });
|
this.queryService.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.search != null) {
|
||||||
|
for (const word of ps.search!.trim().split(' ')) {
|
||||||
|
query.andWhere(new Brackets(qb => {
|
||||||
|
qb.orWhere('note.text ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
||||||
|
qb.orWhere('note.cw ILIKE :search', { search: `%${sqlLikeEscape(word)}%` });
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const notes = await query
|
const notes = await query
|
||||||
.limit(ps.limit)
|
.limit(ps.limit)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
|
@ -153,6 +153,9 @@ export default [
|
||||||
autofix: true,
|
autofix: true,
|
||||||
}],
|
}],
|
||||||
'vue/attribute-hyphenation': ['error', 'never'],
|
'vue/attribute-hyphenation': ['error', 'never'],
|
||||||
|
'vue/no-mutating-props': ['error', {
|
||||||
|
shallowOnly: true,
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkPagination :pagination="pagination">
|
<MkPagination :paginator="paginator">
|
||||||
<template #empty><MkResult type="empty"/></template>
|
<template #empty><MkResult type="empty"/></template>
|
||||||
|
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
|
@ -14,13 +14,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
import type { Paginator } from '@/utility/paginator.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';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
pagination: PagingCtx;
|
paginator: Paginator;
|
||||||
noGap?: boolean;
|
noGap?: boolean;
|
||||||
extractor?: (item: any) => any;
|
extractor?: (item: any) => any;
|
||||||
}>(), {
|
}>(), {
|
||||||
|
|
|
@ -130,7 +130,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, useTemplateRef, watch, computed, TransitionGroup } from 'vue';
|
import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, useTemplateRef, watch, computed, TransitionGroup, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkButton from './MkButton.vue';
|
import MkButton from './MkButton.vue';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
@ -146,10 +146,10 @@ import { prefer } from '@/preferences.js';
|
||||||
import { chooseFileFromPcAndUpload, selectDriveFolder } from '@/utility/drive.js';
|
import { chooseFileFromPcAndUpload, selectDriveFolder } from '@/utility/drive.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import { isSeparatorNeeded, getSeparatorInfo, makeDateGroupedTimelineComputedRef } from '@/utility/timeline-date-separate.js';
|
import { isSeparatorNeeded, getSeparatorInfo, makeDateGroupedTimelineComputedRef } from '@/utility/timeline-date-separate.js';
|
||||||
import { usePagination } from '@/composables/use-pagination.js';
|
|
||||||
import { globalEvents, useGlobalEvent } from '@/events.js';
|
import { globalEvents, useGlobalEvent } from '@/events.js';
|
||||||
import { checkDragDataType, getDragData, setDragData } from '@/drag-and-drop.js';
|
import { checkDragDataType, getDragData, setDragData } from '@/drag-and-drop.js';
|
||||||
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
|
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
initialFolder?: Misskey.entities.DriveFolder['id'] | null;
|
initialFolder?: Misskey.entities.DriveFolder['id'] | null;
|
||||||
|
@ -195,33 +195,23 @@ const fetching = ref(true);
|
||||||
|
|
||||||
const sortModeSelect = ref<NonNullable<Misskey.entities.DriveFilesRequest['sort']>>('+createdAt');
|
const sortModeSelect = ref<NonNullable<Misskey.entities.DriveFilesRequest['sort']>>('+createdAt');
|
||||||
|
|
||||||
const filesPaginator = usePagination({
|
const filesPaginator = markRaw(new Paginator('drive/files', {
|
||||||
ctx: {
|
|
||||||
endpoint: 'drive/files',
|
|
||||||
limit: 30,
|
limit: 30,
|
||||||
canFetchDetection: 'limit',
|
canFetchDetection: 'limit',
|
||||||
params: computed(() => ({
|
params: () => ({ // 自動でリロードしたくないためcomputedParamsは使わない
|
||||||
folderId: folder.value ? folder.value.id : null,
|
folderId: folder.value ? folder.value.id : null,
|
||||||
type: props.type,
|
type: props.type,
|
||||||
sort: sortModeSelect.value,
|
sort: sortModeSelect.value,
|
||||||
})),
|
}),
|
||||||
},
|
}));
|
||||||
autoInit: false,
|
|
||||||
autoReInit: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const foldersPaginator = usePagination({
|
const foldersPaginator = markRaw(new Paginator('drive/folders', {
|
||||||
ctx: {
|
|
||||||
endpoint: 'drive/folders',
|
|
||||||
limit: 30,
|
limit: 30,
|
||||||
canFetchDetection: 'limit',
|
canFetchDetection: 'limit',
|
||||||
params: computed(() => ({
|
params: () => ({ // 自動でリロードしたくないためcomputedParamsは使わない
|
||||||
folderId: folder.value ? folder.value.id : null,
|
folderId: folder.value ? folder.value.id : null,
|
||||||
})),
|
}),
|
||||||
},
|
}));
|
||||||
autoInit: false,
|
|
||||||
autoReInit: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const filesTimeline = makeDateGroupedTimelineComputedRef(filesPaginator.items, 'month');
|
const filesTimeline = makeDateGroupedTimelineComputedRef(filesPaginator.items, 'month');
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<MkPagination v-slot="{ items }" :pagination="pagination">
|
<MkPagination v-slot="{ items }" :paginator="paginator">
|
||||||
<div :class="[$style.fileList, { [$style.grid]: viewMode === 'grid', [$style.list]: viewMode === 'list', '_gaps_s': viewMode === 'list' }]">
|
<div :class="[$style.fileList, { [$style.grid]: viewMode === 'grid', [$style.list]: viewMode === 'list', '_gaps_s': viewMode === 'list' }]">
|
||||||
<MkA
|
<MkA
|
||||||
v-for="file in items"
|
v-for="file in items"
|
||||||
|
@ -40,15 +40,15 @@ 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 type { Paginator } from '@/utility/paginator.js';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
||||||
import bytes from '@/filters/bytes.js';
|
import bytes from '@/filters/bytes.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { dateString } from '@/filters/date.js';
|
import { dateString } from '@/filters/date.js';
|
||||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
pagination: PagingCtx<'admin/drive/files'>;
|
paginator: Paginator<'admin/drive/files'>;
|
||||||
viewMode: 'grid' | 'list';
|
viewMode: 'grid' | 'list';
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -187,7 +187,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/>
|
<MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'renotes'" :class="$style.tab_renotes">
|
<div v-else-if="tab === 'renotes'" :class="$style.tab_renotes">
|
||||||
<MkPagination :pagination="renotesPagination" :disableAutoLoad="true">
|
<MkPagination :paginator="renotesPaginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
|
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
|
||||||
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
|
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
|
||||||
|
@ -204,7 +204,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span style="margin-left: 4px;">{{ $appearNote.reactions[reaction] }}</span>
|
<span style="margin-left: 4px;">{{ $appearNote.reactions[reaction] }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<MkPagination v-if="reactionTabType" :key="reactionTabType" :pagination="reactionsPagination" :disableAutoLoad="true">
|
<MkPagination v-if="reactionTabType" :key="reactionTabType" :paginator="reactionsPaginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
|
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
|
||||||
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
|
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
|
||||||
|
@ -228,7 +228,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, onMounted, provide, ref, useTemplateRef } from 'vue';
|
import { computed, inject, markRaw, onMounted, provide, ref, useTemplateRef } from 'vue';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { isLink } from '@@/js/is-link.js';
|
import { isLink } from '@@/js/is-link.js';
|
||||||
|
@ -274,6 +274,7 @@ import { prefer } from '@/preferences.js';
|
||||||
import { getPluginHandlers } from '@/plugin.js';
|
import { getPluginHandlers } from '@/plugin.js';
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
import { globalEvents, useGlobalEvent } from '@/events.js';
|
import { globalEvents, useGlobalEvent } from '@/events.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
|
@ -376,21 +377,19 @@ provide(DI.mfmEmojiReactCallback, (reaction) => {
|
||||||
const tab = ref(props.initialTab);
|
const tab = ref(props.initialTab);
|
||||||
const reactionTabType = ref<string | null>(null);
|
const reactionTabType = ref<string | null>(null);
|
||||||
|
|
||||||
const renotesPagination = computed(() => ({
|
const renotesPaginator = markRaw(new Paginator('notes/renotes', {
|
||||||
endpoint: 'notes/renotes',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const reactionsPagination = computed(() => ({
|
const reactionsPaginator = markRaw(new Paginator('notes/reactions', {
|
||||||
endpoint: 'notes/reactions',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
computedParams: computed(() => ({
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
type: reactionTabType.value,
|
type: reactionTabType.value,
|
||||||
},
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useTooltip(renoteButton, async (showing) => {
|
useTooltip(renoteButton, async (showing) => {
|
||||||
|
|
|
@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
{{ i18n.ts.drafts }} ({{ currentDraftsCount }}/{{ $i?.policies.noteDraftLimit }})
|
{{ i18n.ts.drafts }} ({{ currentDraftsCount }}/{{ $i?.policies.noteDraftLimit }})
|
||||||
</template>
|
</template>
|
||||||
<div class="_spacer">
|
<div class="_spacer">
|
||||||
<MkPagination ref="pagingEl" :pagination="paging" withControl>
|
<MkPagination :paginator="paginator" withControl>
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<MkResult type="empty" :text="i18n.ts._drafts.noDrafts"/>
|
<MkResult type="empty" :text="i18n.ts._drafts.noDrafts"/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -100,9 +100,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, shallowRef, useTemplateRef } from 'vue';
|
import { ref, shallowRef, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
|
@ -111,6 +110,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api';
|
import { misskeyApi } from '@/utility/misskey-api';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'restore', draft: Misskey.entities.NoteDraft): void;
|
(ev: 'restore', draft: Misskey.entities.NoteDraft): void;
|
||||||
|
@ -118,12 +118,9 @@ const emit = defineEmits<{
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const paging = {
|
const paginator = markRaw(new Paginator('notes/drafts/list', {
|
||||||
endpoint: 'notes/drafts/list',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
} satisfies PagingCtx;
|
}));
|
||||||
|
|
||||||
const pagingComponent = useTemplateRef('pagingEl');
|
|
||||||
|
|
||||||
const currentDraftsCount = ref(0);
|
const currentDraftsCount = ref(0);
|
||||||
misskeyApi('notes/drafts/count').then((count) => {
|
misskeyApi('notes/drafts/count').then((count) => {
|
||||||
|
@ -151,7 +148,7 @@ async function deleteDraft(draft: Misskey.entities.NoteDraft) {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
os.apiWithDialog('notes/drafts/delete', { draftId: draft.id }).then(() => {
|
os.apiWithDialog('notes/drafts/delete', { draftId: draft.id }).then(() => {
|
||||||
pagingComponent.value?.paginator.reload();
|
paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,17 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad" :pullToRefresh="pullToRefresh" :withControl="withControl">
|
<MkPagination :paginator="paginator" :autoLoad="autoLoad" :pullToRefresh="pullToRefresh" :withControl="withControl">
|
||||||
<template #empty><MkResult type="empty" :text="i18n.ts.noNotes"/></template>
|
<template #empty><MkResult type="empty" :text="i18n.ts.noNotes"/></template>
|
||||||
|
|
||||||
<template #default="{ items: notes }">
|
<template #default="{ items: notes }">
|
||||||
<div :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap }]">
|
<div :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap }]">
|
||||||
<template v-for="(note, i) in notes" :key="note.id">
|
<template v-for="(note, i) in notes" :key="note.id">
|
||||||
<div v-if="i > 0 && isSeparatorNeeded(pagingComponent.paginator.items.value[i -1].createdAt, note.createdAt)" :data-scroll-anchor="note.id">
|
<div v-if="i > 0 && isSeparatorNeeded(paginator.items.value[i -1].createdAt, note.createdAt)" :data-scroll-anchor="note.id">
|
||||||
<div :class="$style.date">
|
<div :class="$style.date">
|
||||||
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(pagingComponent.paginator.items.value[i -1].createdAt, note.createdAt).prevText }}</span>
|
<span><i class="ti ti-chevron-up"></i> {{ getSeparatorInfo(paginator.items.value[i -1].createdAt, note.createdAt).prevText }}</span>
|
||||||
<span style="height: 1em; width: 1px; background: var(--MI_THEME-divider);"></span>
|
<span style="height: 1em; width: 1px; background: var(--MI_THEME-divider);"></span>
|
||||||
<span>{{ getSeparatorInfo(pagingComponent.paginator.items.value[i -1].createdAt, note.createdAt).nextText }} <i class="ti ti-chevron-down"></i></span>
|
<span>{{ getSeparatorInfo(paginator.items.value[i -1].createdAt, note.createdAt).nextText }} <i class="ti ti-chevron-down"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,9 +31,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup generic="T extends PagingCtx">
|
<script lang="ts" setup generic="T extends Paginator">
|
||||||
import { useTemplateRef } from 'vue';
|
import type { Paginator } from '@/utility/paginator.js';
|
||||||
import type { PagingCtx } from '@/composables/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';
|
||||||
|
@ -41,24 +40,23 @@ import { globalEvents, useGlobalEvent } from '@/events.js';
|
||||||
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
pagination: T;
|
paginator: T;
|
||||||
noGap?: boolean;
|
noGap?: boolean;
|
||||||
disableAutoLoad?: boolean;
|
autoLoad?: boolean;
|
||||||
pullToRefresh?: boolean;
|
pullToRefresh?: boolean;
|
||||||
withControl?: boolean;
|
withControl?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
|
autoLoad: true,
|
||||||
pullToRefresh: true,
|
pullToRefresh: true,
|
||||||
withControl: true,
|
withControl: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const pagingComponent = useTemplateRef('pagingComponent');
|
|
||||||
|
|
||||||
useGlobalEvent('noteDeleted', (noteId) => {
|
useGlobalEvent('noteDeleted', (noteId) => {
|
||||||
pagingComponent.value?.paginator.removeItem(noteId);
|
props.paginator.removeItem(noteId);
|
||||||
});
|
});
|
||||||
|
|
||||||
function reload() {
|
function reload() {
|
||||||
return pagingComponent.value?.paginator.reload();
|
return props.paginator.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<component :is="prefer.s.enablePullToRefresh && pullToRefresh ? MkPullToRefresh : 'div'" :refresher="() => paginator.reload()" @contextmenu.prevent.stop="onContextmenu">
|
<component :is="prefer.s.enablePullToRefresh && pullToRefresh ? MkPullToRefresh : 'div'" :refresher="() => paginator.reload()" @contextmenu.prevent.stop="onContextmenu">
|
||||||
<div>
|
<div>
|
||||||
<MkPaginationControl v-if="props.withControl" v-model:order="order" v-model:date="date" style="margin-bottom: 10px" @reload="paginator.reload()"/>
|
<MkPaginationControl v-if="props.withControl" :paginator="paginator" style="margin-bottom: 10px"/>
|
||||||
|
|
||||||
<!-- :css="prefer.s.animation" にしたいけどバグる(おそらくvueのバグ) https://github.com/misskey-dev/misskey/issues/16078 -->
|
<!-- :css="prefer.s.animation" にしたいけどバグる(おそらくvueのバグ) https://github.com/misskey-dev/misskey/issues/16078 -->
|
||||||
<Transition
|
<Transition
|
||||||
|
@ -26,14 +26,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<div v-else key="_root_" class="_gaps">
|
<div v-else key="_root_" class="_gaps">
|
||||||
<slot :items="paginator.items.value" :fetching="paginator.fetching.value || paginator.fetchingOlder.value"></slot>
|
<slot :items="paginator.items.value" :fetching="paginator.fetching.value || paginator.fetchingOlder.value"></slot>
|
||||||
<div v-if="order === 'oldest'">
|
<div v-if="paginator.order.value === 'oldest'">
|
||||||
<MkButton v-if="!paginator.fetchingNewer.value" :class="$style.more" :wait="paginator.fetchingNewer.value" primary rounded @click="paginator.fetchNewer">
|
<MkButton v-if="!paginator.fetchingNewer.value" :class="$style.more" :wait="paginator.fetchingNewer.value" primary rounded @click="paginator.fetchNewer()">
|
||||||
{{ i18n.ts.loadMore }}
|
{{ i18n.ts.loadMore }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
<MkLoading v-else/>
|
<MkLoading v-else/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else v-show="paginator.canFetchOlder.value">
|
<div v-else v-show="paginator.canFetchOlder.value">
|
||||||
<MkButton v-if="!paginator.fetchingOlder.value" :class="$style.more" :wait="paginator.fetchingOlder.value" primary rounded @click="paginator.fetchOlder">
|
<MkButton v-if="!paginator.fetchingOlder.value" :class="$style.more" :wait="paginator.fetchingOlder.value" primary rounded @click="paginator.fetchOlder()">
|
||||||
{{ i18n.ts.loadMore }}
|
{{ i18n.ts.loadMore }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
<MkLoading v-else/>
|
<MkLoading v-else/>
|
||||||
|
@ -44,49 +44,29 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup generic="T extends PagingCtx">
|
<script lang="ts" setup generic="T extends Paginator, I = UnwrapRef<T['items']>">
|
||||||
import { isLink } from '@@/js/is-link.js';
|
import { isLink } from '@@/js/is-link.js';
|
||||||
import { ref, watch } from 'vue';
|
import { onMounted, watch } from 'vue';
|
||||||
import type { UnwrapRef } from 'vue';
|
import type { UnwrapRef } from 'vue';
|
||||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
import type { Paginator } from '@/utility/paginator.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
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 '@/composables/use-pagination.js';
|
|
||||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||||
import MkPaginationControl from '@/components/MkPaginationControl.vue';
|
import MkPaginationControl from '@/components/MkPaginationControl.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
||||||
type Paginator = ReturnType<typeof usePagination<T['endpoint']>>;
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
pagination: T;
|
paginator: T;
|
||||||
disableAutoLoad?: boolean;
|
autoLoad?: boolean;
|
||||||
displayLimit?: number;
|
|
||||||
pullToRefresh?: boolean;
|
pullToRefresh?: boolean;
|
||||||
withControl?: boolean;
|
withControl?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
displayLimit: 20,
|
autoLoad: true,
|
||||||
pullToRefresh: true,
|
pullToRefresh: true,
|
||||||
withControl: false,
|
withControl: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const order = ref<'newest' | 'oldest'>(props.pagination.order ?? 'newest');
|
|
||||||
const date = ref<number | null>(null);
|
|
||||||
|
|
||||||
const paginator: Paginator = usePagination({
|
|
||||||
ctx: props.pagination,
|
|
||||||
});
|
|
||||||
|
|
||||||
watch([order, date], () => {
|
|
||||||
paginator.updateCtx({
|
|
||||||
...props.pagination,
|
|
||||||
order: order.value,
|
|
||||||
initialDirection: order.value === 'oldest' ? 'newer' : 'older',
|
|
||||||
initialDate: date.value,
|
|
||||||
});
|
|
||||||
}, { immediate: false });
|
|
||||||
|
|
||||||
function onContextmenu(ev: MouseEvent) {
|
function onContextmenu(ev: MouseEvent) {
|
||||||
if (ev.target && isLink(ev.target as HTMLElement)) return;
|
if (ev.target && isLink(ev.target as HTMLElement)) return;
|
||||||
if (window.getSelection()?.toString() !== '') return;
|
if (window.getSelection()?.toString() !== '') return;
|
||||||
|
@ -96,19 +76,27 @@ function onContextmenu(ev: MouseEvent) {
|
||||||
icon: 'ti ti-refresh',
|
icon: 'ti ti-refresh',
|
||||||
text: i18n.ts.reload,
|
text: i18n.ts.reload,
|
||||||
action: () => {
|
action: () => {
|
||||||
paginator.reload();
|
props.paginator.reload();
|
||||||
},
|
},
|
||||||
}], ev);
|
}], ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.autoLoad) {
|
||||||
|
onMounted(() => {
|
||||||
|
props.paginator.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.paginator.computedParams) {
|
||||||
|
watch(props.paginator.computedParams, () => {
|
||||||
|
props.paginator.reload();
|
||||||
|
}, { immediate: false, deep: true });
|
||||||
|
}
|
||||||
|
|
||||||
defineSlots<{
|
defineSlots<{
|
||||||
empty: () => void;
|
empty: () => void;
|
||||||
default: (props: { items: UnwrapRef<Paginator['items']> }) => void;
|
default: (props: { items: I }) => void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
paginator: paginator,
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -9,10 +9,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSelect v-model="order" :class="$style.order" :items="[{ label: i18n.ts._order.newest, value: 'newest' }, { label: i18n.ts._order.oldest, value: 'oldest' }]">
|
<MkSelect v-model="order" :class="$style.order" :items="[{ label: i18n.ts._order.newest, value: 'newest' }, { label: i18n.ts._order.oldest, value: 'oldest' }]">
|
||||||
<template #prefix><i class="ti ti-arrows-sort"></i></template>
|
<template #prefix><i class="ti ti-arrows-sort"></i></template>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<MkButton v-if="canSearch" v-tooltip="i18n.ts.search" iconOnly transparent rounded :active="searchOpened" @click="searchOpened = !searchOpened"><i class="ti ti-search"></i></MkButton>
|
<MkButton v-if="paginator.canSearch" v-tooltip="i18n.ts.search" iconOnly transparent rounded :active="searchOpened" @click="searchOpened = !searchOpened"><i class="ti ti-search"></i></MkButton>
|
||||||
<MkButton v-if="canFilter" v-tooltip="i18n.ts.filter" iconOnly transparent rounded :active="filterOpened" @click="filterOpened = !filterOpened"><i class="ti ti-filter"></i></MkButton>
|
<MkButton v-if="canFilter" v-tooltip="i18n.ts.filter" iconOnly transparent rounded :active="filterOpened" @click="filterOpened = !filterOpened"><i class="ti ti-filter"></i></MkButton>
|
||||||
<MkButton v-tooltip="i18n.ts.dateAndTime" iconOnly transparent rounded :active="date != null" @click="date = date == null ? Date.now() : null"><i class="ti ti-calendar-clock"></i></MkButton>
|
<MkButton v-tooltip="i18n.ts.dateAndTime" iconOnly transparent rounded :active="date != null" @click="date = date == null ? Date.now() : null"><i class="ti ti-calendar-clock"></i></MkButton>
|
||||||
<MkButton v-tooltip="i18n.ts.reload" iconOnly transparent rounded @click="emit('reload')"><i class="ti ti-refresh"></i></MkButton>
|
<MkButton v-tooltip="i18n.ts.reload" iconOnly transparent rounded @click="paginator.reload()"><i class="ti ti-refresh"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkInput
|
<MkInput
|
||||||
|
@ -37,9 +37,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup generic="T extends PagingCtx">
|
<script lang="ts" setup generic="T extends Paginator">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
import type { Paginator } from '@/utility/paginator.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
@ -47,32 +47,35 @@ import MkInput from '@/components/MkInput.vue';
|
||||||
import { formatDateTimeString } from '@/utility/format-time-string.js';
|
import { formatDateTimeString } from '@/utility/format-time-string.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
canSearch?: boolean;
|
paginator: T;
|
||||||
canFilter?: boolean;
|
canFilter?: boolean;
|
||||||
filterOpened?: boolean;
|
filterOpened?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
canSearch: false,
|
|
||||||
canFilter: false,
|
canFilter: false,
|
||||||
filterOpened: false,
|
filterOpened: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'reload'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const searchOpened = ref(false);
|
const searchOpened = ref(false);
|
||||||
const filterOpened = ref(props.filterOpened);
|
const filterOpened = ref(props.filterOpened);
|
||||||
|
|
||||||
const order = defineModel<'newest' | 'oldest'>('order', {
|
const order = ref<'newest' | 'oldest'>('newest');
|
||||||
default: 'newest',
|
const date = ref<number | null>(null);
|
||||||
|
const q = ref<string | null>(null);
|
||||||
|
|
||||||
|
watch(order, () => {
|
||||||
|
props.paginator.order.value = order.value;
|
||||||
|
props.paginator.initialDirection = order.value === 'oldest' ? 'newer' : 'older';
|
||||||
|
props.paginator.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
const date = defineModel<number | null>('date', {
|
watch(date, () => {
|
||||||
default: null,
|
props.paginator.initialDate = date.value;
|
||||||
|
props.paginator.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
const q = defineModel<string | null>('q', {
|
watch(q, () => {
|
||||||
default: null,
|
props.paginator.searchQuery.value = q.value;
|
||||||
|
props.paginator.reload();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -56,14 +56,12 @@ 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, shallowRef, ref } from 'vue';
|
import { computed, watch, onUnmounted, provide, useTemplateRef, TransitionGroup, onMounted, shallowRef, ref, markRaw } 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 { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
|
import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
|
||||||
import type { BasicTimelineType } from '@/timelines.js';
|
import type { BasicTimelineType } from '@/timelines.js';
|
||||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
|
||||||
import type { SoundStore } from '@/preferences/def.js';
|
import type { SoundStore } from '@/preferences/def.js';
|
||||||
import { usePagination } from '@/composables/use-pagination.js';
|
|
||||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import * as sound from '@/utility/sound.js';
|
import * as sound from '@/utility/sound.js';
|
||||||
|
@ -76,6 +74,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { globalEvents, useGlobalEvent } from '@/events.js';
|
import { globalEvents, useGlobalEvent } from '@/events.js';
|
||||||
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
|
src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
|
||||||
|
@ -102,6 +101,97 @@ 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'));
|
||||||
|
|
||||||
|
let paginator: Paginator;
|
||||||
|
|
||||||
|
if (props.src === 'antenna') {
|
||||||
|
paginator = markRaw(new Paginator('antennas/notes', {
|
||||||
|
computedParams: computed(() => ({
|
||||||
|
antennaId: props.antenna,
|
||||||
|
})),
|
||||||
|
useShallowRef: true,
|
||||||
|
}));
|
||||||
|
} else if (props.src === 'home') {
|
||||||
|
paginator = markRaw(new Paginator('notes/timeline', {
|
||||||
|
computedParams: computed(() => ({
|
||||||
|
withRenotes: props.withRenotes,
|
||||||
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
|
})),
|
||||||
|
useShallowRef: true,
|
||||||
|
}));
|
||||||
|
} else if (props.src === 'local') {
|
||||||
|
paginator = markRaw(new Paginator('notes/local-timeline', {
|
||||||
|
computedParams: computed(() => ({
|
||||||
|
withRenotes: props.withRenotes,
|
||||||
|
withReplies: props.withReplies,
|
||||||
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
|
})),
|
||||||
|
useShallowRef: true,
|
||||||
|
}));
|
||||||
|
} else if (props.src === 'social') {
|
||||||
|
paginator = markRaw(new Paginator('notes/hybrid-timeline', {
|
||||||
|
computedParams: computed(() => ({
|
||||||
|
withRenotes: props.withRenotes,
|
||||||
|
withReplies: props.withReplies,
|
||||||
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
|
})),
|
||||||
|
useShallowRef: true,
|
||||||
|
}));
|
||||||
|
} else if (props.src === 'global') {
|
||||||
|
paginator = markRaw(new Paginator('notes/global-timeline', {
|
||||||
|
computedParams: computed(() => ({
|
||||||
|
withRenotes: props.withRenotes,
|
||||||
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
|
})),
|
||||||
|
useShallowRef: true,
|
||||||
|
}));
|
||||||
|
} else if (props.src === 'mentions') {
|
||||||
|
paginator = markRaw(new Paginator('notes/mentions', {
|
||||||
|
useShallowRef: true,
|
||||||
|
}));
|
||||||
|
} else if (props.src === 'directs') {
|
||||||
|
paginator = markRaw(new Paginator('notes/mentions', {
|
||||||
|
params: {
|
||||||
|
visibility: 'specified',
|
||||||
|
},
|
||||||
|
useShallowRef: true,
|
||||||
|
}));
|
||||||
|
} else if (props.src === 'list') {
|
||||||
|
paginator = markRaw(new Paginator('notes/user-list-timeline', {
|
||||||
|
computedParams: computed(() => ({
|
||||||
|
withRenotes: props.withRenotes,
|
||||||
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
|
listId: props.list,
|
||||||
|
})),
|
||||||
|
useShallowRef: true,
|
||||||
|
}));
|
||||||
|
} else if (props.src === 'channel') {
|
||||||
|
paginator = markRaw(new Paginator('channels/timeline', {
|
||||||
|
computedParams: computed(() => ({
|
||||||
|
channelId: props.channel,
|
||||||
|
})),
|
||||||
|
useShallowRef: true,
|
||||||
|
}));
|
||||||
|
} else if (props.src === 'role') {
|
||||||
|
paginator = markRaw(new Paginator('roles/notes', {
|
||||||
|
computedParams: computed(() => ({
|
||||||
|
roleId: props.role,
|
||||||
|
})),
|
||||||
|
useShallowRef: true,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
throw new Error('Unrecognized timeline type: ' + props.src);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
paginator.init();
|
||||||
|
|
||||||
|
if (paginator.computedParams) {
|
||||||
|
watch(paginator.computedParams, () => {
|
||||||
|
paginator.reload();
|
||||||
|
}, { immediate: false, deep: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function isTop() {
|
function isTop() {
|
||||||
if (scrollContainer == null) return true;
|
if (scrollContainer == null) return true;
|
||||||
if (rootEl.value == null) return true;
|
if (rootEl.value == null) return true;
|
||||||
|
@ -133,17 +223,6 @@ onUnmounted(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
type TimelineQueryType = {
|
|
||||||
antennaId?: string,
|
|
||||||
withRenotes?: boolean,
|
|
||||||
withReplies?: boolean,
|
|
||||||
withFiles?: boolean,
|
|
||||||
visibility?: string,
|
|
||||||
listId?: string,
|
|
||||||
channelId?: string,
|
|
||||||
roleId?: string
|
|
||||||
};
|
|
||||||
|
|
||||||
let adInsertionCounter = 0;
|
let adInsertionCounter = 0;
|
||||||
|
|
||||||
const MIN_POLLING_INTERVAL = 1000 * 10;
|
const MIN_POLLING_INTERVAL = 1000 * 10;
|
||||||
|
@ -204,7 +283,6 @@ function prepend(note: Misskey.entities.Note) {
|
||||||
|
|
||||||
let connection: Misskey.ChannelConnection | null = null;
|
let connection: Misskey.ChannelConnection | null = null;
|
||||||
let connection2: Misskey.ChannelConnection | null = null;
|
let connection2: Misskey.ChannelConnection | null = null;
|
||||||
let paginationQuery: PagingCtx;
|
|
||||||
|
|
||||||
const stream = store.s.realtimeMode ? useStream() : null;
|
const stream = store.s.realtimeMode ? useStream() : null;
|
||||||
|
|
||||||
|
@ -274,100 +352,17 @@ function disconnectChannel() {
|
||||||
if (connection2) connection2.dispose();
|
if (connection2) connection2.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePaginationQuery() {
|
if (store.s.realtimeMode) {
|
||||||
let endpoint: keyof Misskey.Endpoints | null;
|
connectChannel();
|
||||||
let query: TimelineQueryType | null;
|
|
||||||
|
|
||||||
if (props.src === 'antenna') {
|
|
||||||
endpoint = 'antennas/notes';
|
|
||||||
query = {
|
|
||||||
antennaId: props.antenna,
|
|
||||||
};
|
|
||||||
} else if (props.src === 'home') {
|
|
||||||
endpoint = 'notes/timeline';
|
|
||||||
query = {
|
|
||||||
withRenotes: props.withRenotes,
|
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
|
||||||
};
|
|
||||||
} else if (props.src === 'local') {
|
|
||||||
endpoint = 'notes/local-timeline';
|
|
||||||
query = {
|
|
||||||
withRenotes: props.withRenotes,
|
|
||||||
withReplies: props.withReplies,
|
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
|
||||||
};
|
|
||||||
} else if (props.src === 'social') {
|
|
||||||
endpoint = 'notes/hybrid-timeline';
|
|
||||||
query = {
|
|
||||||
withRenotes: props.withRenotes,
|
|
||||||
withReplies: props.withReplies,
|
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
|
||||||
};
|
|
||||||
} else if (props.src === 'global') {
|
|
||||||
endpoint = 'notes/global-timeline';
|
|
||||||
query = {
|
|
||||||
withRenotes: props.withRenotes,
|
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
|
||||||
};
|
|
||||||
} else if (props.src === 'mentions') {
|
|
||||||
endpoint = 'notes/mentions';
|
|
||||||
query = null;
|
|
||||||
} else if (props.src === 'directs') {
|
|
||||||
endpoint = 'notes/mentions';
|
|
||||||
query = {
|
|
||||||
visibility: 'specified',
|
|
||||||
};
|
|
||||||
} else if (props.src === 'list') {
|
|
||||||
endpoint = 'notes/user-list-timeline';
|
|
||||||
query = {
|
|
||||||
withRenotes: props.withRenotes,
|
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
|
||||||
listId: props.list,
|
|
||||||
};
|
|
||||||
} else if (props.src === 'channel') {
|
|
||||||
endpoint = 'channels/timeline';
|
|
||||||
query = {
|
|
||||||
channelId: props.channel,
|
|
||||||
};
|
|
||||||
} else if (props.src === 'role') {
|
|
||||||
endpoint = 'roles/notes';
|
|
||||||
query = {
|
|
||||||
roleId: props.role,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new Error('Unrecognized timeline type: ' + props.src);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
paginationQuery = {
|
watch(() => [props.list, props.antenna, props.channel, props.role, props.withRenotes], () => {
|
||||||
endpoint: endpoint,
|
|
||||||
limit: 10,
|
|
||||||
params: query,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshEndpointAndChannel() {
|
|
||||||
if (store.s.realtimeMode) {
|
if (store.s.realtimeMode) {
|
||||||
disconnectChannel();
|
disconnectChannel();
|
||||||
connectChannel();
|
connectChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePaginationQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
// デッキのリストカラムでwithRenotesを変更した場合に自動的に更新されるようにさせる
|
|
||||||
// IDが切り替わったら切り替え先のTLを表示させたい
|
|
||||||
watch(() => [props.list, props.antenna, props.channel, props.role, props.withRenotes], refreshEndpointAndChannel);
|
|
||||||
|
|
||||||
// withSensitiveはクライアントで完結する処理のため、単にリロードするだけでOK
|
|
||||||
watch(() => props.withSensitive, reloadTimeline);
|
|
||||||
|
|
||||||
// 初回表示用
|
|
||||||
refreshEndpointAndChannel();
|
|
||||||
|
|
||||||
const paginator = usePagination({
|
|
||||||
ctx: paginationQuery,
|
|
||||||
useShallowRef: true,
|
|
||||||
});
|
});
|
||||||
|
watch(() => props.withSensitive, reloadTimeline);
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
disconnectChannel();
|
disconnectChannel();
|
||||||
|
|
|
@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup } from 'vue';
|
import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } 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 type { notificationTypes } from '@@/js/const.js';
|
import type { notificationTypes } from '@@/js/const.js';
|
||||||
|
@ -53,8 +53,8 @@ import { i18n } from '@/i18n.js';
|
||||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import { usePagination } from '@/composables/use-pagination.js';
|
|
||||||
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-separate.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
excludeTypes?: typeof notificationTypes[number][];
|
excludeTypes?: typeof notificationTypes[number][];
|
||||||
|
@ -62,21 +62,17 @@ const props = defineProps<{
|
||||||
|
|
||||||
const rootEl = useTemplateRef('rootEl');
|
const rootEl = useTemplateRef('rootEl');
|
||||||
|
|
||||||
const paginator = usePagination({
|
const paginator = prefer.s.useGroupedNotifications ? markRaw(new Paginator('i/notifications-grouped', {
|
||||||
ctx: prefer.s.useGroupedNotifications ? {
|
|
||||||
endpoint: 'i/notifications-grouped' as const,
|
|
||||||
limit: 20,
|
limit: 20,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
excludeTypes: props.excludeTypes ?? undefined,
|
excludeTypes: props.excludeTypes ?? undefined,
|
||||||
})),
|
})),
|
||||||
} : {
|
})) : markRaw(new Paginator('i/notifications', {
|
||||||
endpoint: 'i/notifications' as const,
|
|
||||||
limit: 20,
|
limit: 20,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
excludeTypes: props.excludeTypes ?? undefined,
|
excludeTypes: props.excludeTypes ?? undefined,
|
||||||
})),
|
})),
|
||||||
},
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
const MIN_POLLING_INTERVAL = 1000 * 10;
|
const MIN_POLLING_INTERVAL = 1000 * 10;
|
||||||
const POLLING_INTERVAL =
|
const POLLING_INTERVAL =
|
||||||
|
@ -116,6 +112,14 @@ function reload() {
|
||||||
let connection: Misskey.ChannelConnection<Misskey.Channels['main']> | null = null;
|
let connection: Misskey.ChannelConnection<Misskey.Channels['main']> | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
paginator.init();
|
||||||
|
|
||||||
|
if (paginator.computedParams) {
|
||||||
|
watch(paginator.computedParams, () => {
|
||||||
|
paginator.reload();
|
||||||
|
}, { immediate: false, deep: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (store.s.realtimeMode) {
|
if (store.s.realtimeMode) {
|
||||||
connection = useStream().useChannel('main');
|
connection = useStream().useChannel('main');
|
||||||
connection.on('notification', onNotification);
|
connection.on('notification', onNotification);
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.left">
|
<div :class="$style.left">
|
||||||
<slot v-if="item.type === 'event'" name="left" :event="item.data" :timestamp="item.timestamp" :delta="item.delta"></slot>
|
<slot v-if="item.type === 'event'" name="left" :event="item.data" :timestamp="item.timestamp" :delta="item.delta"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div :class="[$style.center, item.type === 'date' ? $style.date : '']">
|
<div :class="[$style.center, item.type === 'date' ? $style.date : '', i === 0 ? $style.first : '', i === items.length - 1 ? $style.last : '']">
|
||||||
<div :class="$style.centerLine"></div>
|
<div :class="$style.centerLine"></div>
|
||||||
<div :class="$style.centerPoint"></div>
|
<div :class="$style.centerPoint"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -143,6 +143,22 @@ const items = computed<TlItem<T>[]>(() => {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.first {
|
||||||
|
.centerLine {
|
||||||
|
height: 50%;
|
||||||
|
top: auto;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.last {
|
||||||
|
.centerLine {
|
||||||
|
height: 50%;
|
||||||
|
top: 0;
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.centerLine {
|
.centerLine {
|
||||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.itemThumbnail" :style="{ backgroundImage: `url(${ item.thumbnail })` }" @click="onThumbnailClick(item, $event)"></div>
|
<div :class="$style.itemThumbnail" :style="{ backgroundImage: `url(${ item.thumbnail })` }" @click="onThumbnailClick(item, $event)"></div>
|
||||||
<div :class="$style.itemBody">
|
<div :class="$style.itemBody">
|
||||||
<div><MkCondensedLine :minScale="2 / 3">{{ item.name }}</MkCondensedLine></div>
|
<div><i v-if="item.isSensitive" style="color: var(--MI_THEME-warn); margin-right: 0.5em;" class="ti ti-eye-exclamation"></i><MkCondensedLine :minScale="2 / 3">{{ item.name }}</MkCondensedLine></div>
|
||||||
<div :class="$style.itemInfo">
|
<div :class="$style.itemInfo">
|
||||||
<span>{{ item.file.type }}</span>
|
<span>{{ item.file.type }}</span>
|
||||||
<span v-if="item.compressedSize">({{ i18n.tsx._uploader.compressedToX({ x: bytes(item.compressedSize) }) }} = {{ i18n.tsx._uploader.savedXPercent({ x: Math.round((1 - item.compressedSize / item.file.size) * 100) }) }})</span>
|
<span v-if="item.compressedSize">({{ i18n.tsx._uploader.compressedToX({ x: bytes(item.compressedSize) }) }} = {{ i18n.tsx._uploader.savedXPercent({ x: Math.round((1 - item.compressedSize / item.file.size) * 100) }) }})</span>
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkPagination :pagination="pagination">
|
<MkPagination :paginator="paginator">
|
||||||
<template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template>
|
<template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template>
|
||||||
|
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
|
@ -16,13 +16,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
import type { Paginator } from '@/utility/paginator.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';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
pagination: PagingCtx;
|
paginator: Paginator;
|
||||||
noGap?: boolean;
|
noGap?: boolean;
|
||||||
extractor?: (item: any) => any;
|
extractor?: (item: any) => any;
|
||||||
}>(), {
|
}>(), {
|
||||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkFolder :defaultOpen="true">
|
<MkFolder :defaultOpen="true">
|
||||||
<template #label>{{ i18n.ts.recommended }}</template>
|
<template #label>{{ i18n.ts.recommended }}</template>
|
||||||
|
|
||||||
<MkPagination :pagination="pinnedUsers">
|
<MkPagination :paginator="pinnedUsersPaginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div :class="$style.users">
|
<div :class="$style.users">
|
||||||
<XUser v-for="item in (items as Misskey.entities.UserDetailed[])" :key="item.id" :user="item"/>
|
<XUser v-for="item in (items as Misskey.entities.UserDetailed[])" :key="item.id" :user="item"/>
|
||||||
|
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkFolder :defaultOpen="true">
|
<MkFolder :defaultOpen="true">
|
||||||
<template #label>{{ i18n.ts.popularUsers }}</template>
|
<template #label>{{ i18n.ts.popularUsers }}</template>
|
||||||
|
|
||||||
<MkPagination :pagination="popularUsers">
|
<MkPagination :paginator="popularUsersPaginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div :class="$style.users">
|
<div :class="$style.users">
|
||||||
<XUser v-for="item in (items as Misskey.entities.UserDetailed[])" :key="item.id" :user="item"/>
|
<XUser v-for="item in (items as Misskey.entities.UserDetailed[])" :key="item.id" :user="item"/>
|
||||||
|
@ -35,20 +35,19 @@ 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 { markRaw } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
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 { PagingCtx } from '@/composables/use-pagination.js';
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const pinnedUsers: PagingCtx = {
|
const pinnedUsersPaginator = markRaw(new Paginator('pinned-users', {
|
||||||
endpoint: 'pinned-users',
|
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
|
|
||||||
const popularUsers: PagingCtx = {
|
const popularUsersPaginator = markRaw(new Paginator('users', {
|
||||||
endpoint: 'users',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
params: {
|
params: {
|
||||||
|
@ -56,7 +55,7 @@ const popularUsers: PagingCtx = {
|
||||||
origin: 'local',
|
origin: 'local',
|
||||||
sort: '+follower',
|
sort: '+follower',
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -1,316 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { computed, isRef, onMounted, ref, shallowRef, triggerRef, watch } from 'vue';
|
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import type { ComputedRef, DeepReadonly, Ref, ShallowRef } from 'vue';
|
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
|
||||||
|
|
||||||
const MAX_ITEMS = 30;
|
|
||||||
const MAX_QUEUE_ITEMS = 100;
|
|
||||||
const FIRST_FETCH_LIMIT = 15;
|
|
||||||
const SECOND_FETCH_LIMIT = 30;
|
|
||||||
|
|
||||||
export type MisskeyEntity = {
|
|
||||||
id: string;
|
|
||||||
createdAt: string;
|
|
||||||
_shouldInsertAd_?: boolean;
|
|
||||||
[x: string]: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PagingCtx<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints> = {
|
|
||||||
endpoint: E;
|
|
||||||
limit?: number;
|
|
||||||
params?: Misskey.Endpoints[E]['req'] | ComputedRef<Misskey.Endpoints[E]['req']>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 検索APIのような、ページング不可なエンドポイントを利用する場合
|
|
||||||
* (そのようなAPIをこの関数で使うのは若干矛盾してるけど)
|
|
||||||
*/
|
|
||||||
noPaging?: boolean;
|
|
||||||
|
|
||||||
offsetMode?: boolean;
|
|
||||||
|
|
||||||
initialId?: MisskeyEntity['id'];
|
|
||||||
initialDate?: number | null;
|
|
||||||
initialDirection?: 'newer' | 'older';
|
|
||||||
|
|
||||||
// 配列内の要素をどのような順序で並べるか
|
|
||||||
// newest: 新しいものが先頭 (default)
|
|
||||||
// oldest: 古いものが先頭
|
|
||||||
// NOTE: このようなプロパティを用意してこっち側で並びを管理せずに、Setで持っておき参照者側が好きに並び変えるような設計の方がすっきりしそうなものの、Vueのレンダリングのたびに並び替え処理が発生することになったりしそうでパフォーマンス上の懸念がある
|
|
||||||
order?: 'newest' | 'oldest';
|
|
||||||
|
|
||||||
// 一部のAPIはさらに遡れる場合でもパフォーマンス上の理由でlimit以下の結果を返す場合があり、その場合はsafe、それ以外はlimitにすることを推奨
|
|
||||||
canFetchDetection?: 'safe' | 'limit';
|
|
||||||
};
|
|
||||||
|
|
||||||
export function usePagination<Endpoint extends keyof Misskey.Endpoints, T extends { id: string; } = (Misskey.Endpoints[Endpoint]['res'] extends (infer I)[] ? I extends { id: string } ? I : { id: string } : { id: string })>(props: {
|
|
||||||
ctx: PagingCtx<Endpoint>;
|
|
||||||
autoInit?: boolean;
|
|
||||||
autoReInit?: boolean;
|
|
||||||
useShallowRef?: boolean;
|
|
||||||
}) {
|
|
||||||
const items = props.useShallowRef ? shallowRef<T[]>([]) : ref<T[]>([]);
|
|
||||||
let aheadQueue: T[] = [];
|
|
||||||
const queuedAheadItemsCount = ref(0);
|
|
||||||
const fetching = ref(true);
|
|
||||||
const fetchingOlder = ref(false);
|
|
||||||
const fetchingNewer = ref(false);
|
|
||||||
const canFetchOlder = ref(false);
|
|
||||||
const error = ref(false);
|
|
||||||
|
|
||||||
if (props.autoReInit !== false) {
|
|
||||||
watch(() => [props.ctx.endpoint, props.ctx.params], init, { deep: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNewestId(): string | null | undefined {
|
|
||||||
// 様々な要因により並び順は保証されないのでソートが必要
|
|
||||||
if (aheadQueue.length > 0) {
|
|
||||||
return aheadQueue.map(x => x.id).sort().at(-1);
|
|
||||||
}
|
|
||||||
return items.value.map(x => x.id).sort().at(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOldestId(): string | null | undefined {
|
|
||||||
// 様々な要因により並び順は保証されないのでソートが必要
|
|
||||||
return items.value.map(x => x.id).sort().at(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, {
|
|
||||||
...params,
|
|
||||||
limit: props.ctx.limit ?? FIRST_FETCH_LIMIT,
|
|
||||||
allowPartial: true,
|
|
||||||
...((props.ctx.initialId == null && props.ctx.initialDate == null) && props.ctx.initialDirection === 'newer' ? {
|
|
||||||
sinceId: '0',
|
|
||||||
} : props.ctx.initialDirection === 'newer' ? {
|
|
||||||
sinceId: props.ctx.initialId,
|
|
||||||
sinceDate: props.ctx.initialDate,
|
|
||||||
} : (props.ctx.initialId || props.ctx.initialDate) && props.ctx.initialDirection === 'older' ? {
|
|
||||||
untilId: props.ctx.initialId,
|
|
||||||
untilDate: props.ctx.initialDate,
|
|
||||||
} : {}),
|
|
||||||
}).then(res => {
|
|
||||||
// 逆順で返ってくるので
|
|
||||||
if ((props.ctx.initialId || props.ctx.initialDate) && props.ctx.initialDirection === 'newer') {
|
|
||||||
res.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < res.length; i++) {
|
|
||||||
const item = res[i];
|
|
||||||
if (i === 3) item._shouldInsertAd_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pushItems(res);
|
|
||||||
|
|
||||||
if (props.ctx.canFetchDetection === 'limit') {
|
|
||||||
if (res.length < FIRST_FETCH_LIMIT) {
|
|
||||||
canFetchOlder.value = false;
|
|
||||||
} else {
|
|
||||||
canFetchOlder.value = true;
|
|
||||||
}
|
|
||||||
} else if (props.ctx.canFetchDetection === 'safe' || props.ctx.canFetchDetection == null) {
|
|
||||||
if (res.length === 0 || props.ctx.noPaging) {
|
|
||||||
canFetchOlder.value = false;
|
|
||||||
} else {
|
|
||||||
canFetchOlder.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error.value = false;
|
|
||||||
fetching.value = false;
|
|
||||||
}, err => {
|
|
||||||
error.value = true;
|
|
||||||
fetching.value = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function reload(): Promise<void> {
|
|
||||||
return init();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchOlder(): Promise<void> {
|
|
||||||
if (!canFetchOlder.value || fetching.value || fetchingOlder.value || items.value.length === 0) return;
|
|
||||||
fetchingOlder.value = true;
|
|
||||||
const params = props.ctx.params ? isRef(props.ctx.params) ? props.ctx.params.value : props.ctx.params : {};
|
|
||||||
await misskeyApi<T[]>(props.ctx.endpoint, {
|
|
||||||
...params,
|
|
||||||
limit: SECOND_FETCH_LIMIT,
|
|
||||||
...(props.ctx.offsetMode ? {
|
|
||||||
offset: items.value.length,
|
|
||||||
} : {
|
|
||||||
untilId: getOldestId(),
|
|
||||||
}),
|
|
||||||
}).then(res => {
|
|
||||||
for (let i = 0; i < res.length; i++) {
|
|
||||||
const item = res[i];
|
|
||||||
if (i === 10) item._shouldInsertAd_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pushItems(res);
|
|
||||||
|
|
||||||
if (props.ctx.canFetchDetection === 'limit') {
|
|
||||||
if (res.length < FIRST_FETCH_LIMIT) {
|
|
||||||
canFetchOlder.value = false;
|
|
||||||
} else {
|
|
||||||
canFetchOlder.value = true;
|
|
||||||
}
|
|
||||||
} else if (props.ctx.canFetchDetection === 'safe' || props.ctx.canFetchDetection == null) {
|
|
||||||
if (res.length === 0) {
|
|
||||||
canFetchOlder.value = false;
|
|
||||||
} else {
|
|
||||||
canFetchOlder.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).finally(() => {
|
|
||||||
fetchingOlder.value = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchNewer(options: {
|
|
||||||
toQueue?: boolean;
|
|
||||||
} = {}): Promise<void> {
|
|
||||||
fetchingNewer.value = true;
|
|
||||||
const params = props.ctx.params ? isRef(props.ctx.params) ? props.ctx.params.value : props.ctx.params : {};
|
|
||||||
await misskeyApi<T[]>(props.ctx.endpoint, {
|
|
||||||
...params,
|
|
||||||
limit: SECOND_FETCH_LIMIT,
|
|
||||||
...(props.ctx.offsetMode ? {
|
|
||||||
offset: items.value.length,
|
|
||||||
} : {
|
|
||||||
sinceId: getNewestId(),
|
|
||||||
}),
|
|
||||||
}).then(res => {
|
|
||||||
if (res.length === 0) return; // これやらないと余計なre-renderが走る
|
|
||||||
|
|
||||||
if (options.toQueue) {
|
|
||||||
aheadQueue.unshift(...res.toReversed());
|
|
||||||
if (aheadQueue.length > MAX_QUEUE_ITEMS) {
|
|
||||||
aheadQueue = aheadQueue.slice(0, MAX_QUEUE_ITEMS);
|
|
||||||
}
|
|
||||||
queuedAheadItemsCount.value = aheadQueue.length;
|
|
||||||
} else {
|
|
||||||
if (props.ctx.order === 'oldest') {
|
|
||||||
pushItems(res);
|
|
||||||
} else {
|
|
||||||
unshiftItems(res.toReversed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).finally(() => {
|
|
||||||
fetchingNewer.value = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function trim(trigger = true) {
|
|
||||||
if (items.value.length >= MAX_ITEMS) canFetchOlder.value = true;
|
|
||||||
items.value = items.value.slice(0, MAX_ITEMS);
|
|
||||||
if (props.useShallowRef && trigger) triggerRef(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
function unshiftItems(newItems: T[]) {
|
|
||||||
if (newItems.length === 0) return; // これやらないと余計なre-renderが走る
|
|
||||||
items.value.unshift(...newItems.filter(x => !items.value.some(y => y.id === x.id))); // ストリーミングやポーリングのタイミングによっては重複することがあるため
|
|
||||||
trim(false);
|
|
||||||
if (props.useShallowRef) triggerRef(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
function pushItems(oldItems: T[]) {
|
|
||||||
if (oldItems.length === 0) return; // これやらないと余計なre-renderが走る
|
|
||||||
items.value.push(...oldItems);
|
|
||||||
if (props.useShallowRef) triggerRef(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepend(item: T) {
|
|
||||||
if (items.value.some(x => x.id === item.id)) return;
|
|
||||||
items.value.unshift(item);
|
|
||||||
trim(false);
|
|
||||||
if (props.useShallowRef) triggerRef(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
function enqueue(item: T) {
|
|
||||||
aheadQueue.unshift(item);
|
|
||||||
if (aheadQueue.length > MAX_QUEUE_ITEMS) {
|
|
||||||
aheadQueue.pop();
|
|
||||||
}
|
|
||||||
queuedAheadItemsCount.value = aheadQueue.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function releaseQueue() {
|
|
||||||
if (aheadQueue.length === 0) return; // これやらないと余計なre-renderが走る
|
|
||||||
unshiftItems(aheadQueue);
|
|
||||||
aheadQueue = [];
|
|
||||||
queuedAheadItemsCount.value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeItem(id: string) {
|
|
||||||
// TODO: queueからも消す
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// TODO: queueのも更新
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCtx(ctx: PagingCtx<Endpoint>) {
|
|
||||||
props.ctx = ctx;
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCtxPartial(ctx: Partial<PagingCtx<Endpoint>>) {
|
|
||||||
props.ctx = {
|
|
||||||
...props.ctx,
|
|
||||||
...ctx,
|
|
||||||
};
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.autoInit !== false) {
|
|
||||||
onMounted(() => {
|
|
||||||
init();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
items: items as DeepReadonly<ShallowRef<T[]>>,
|
|
||||||
queuedAheadItemsCount,
|
|
||||||
fetching,
|
|
||||||
fetchingOlder,
|
|
||||||
fetchingNewer,
|
|
||||||
canFetchOlder,
|
|
||||||
init,
|
|
||||||
reload,
|
|
||||||
fetchOlder,
|
|
||||||
fetchNewer,
|
|
||||||
unshiftItems,
|
|
||||||
prepend,
|
|
||||||
trim,
|
|
||||||
removeItem,
|
|
||||||
updateItem,
|
|
||||||
enqueue,
|
|
||||||
releaseQueue,
|
|
||||||
error,
|
|
||||||
updateCtx,
|
|
||||||
updateCtxPartial,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</FormSplit>
|
</FormSplit>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
|
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :paginator="paginator">
|
||||||
<div :class="$style.items">
|
<div :class="$style.items">
|
||||||
<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.item" :to="`/instance-info/${instance.host}`">
|
<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.item" :to="`/instance-info/${instance.host}`">
|
||||||
<MkInstanceCardMini :instance="instance"/>
|
<MkInstanceCardMini :instance="instance"/>
|
||||||
|
@ -51,24 +51,22 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, markRaw, 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 { PagingCtx } from '@/composables/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';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const host = ref('');
|
const host = ref('');
|
||||||
const state = ref('federating');
|
const state = ref('federating');
|
||||||
const sort = ref('+pubSub');
|
const sort = ref('+pubSub');
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('federation/instances', {
|
||||||
endpoint: 'federation/instances' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
displayLimit: 50,
|
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
sort: sort.value,
|
sort: sort.value,
|
||||||
host: host.value !== '' ? host.value : null,
|
host: host.value !== '' ? host.value : null,
|
||||||
...(
|
...(
|
||||||
|
@ -81,7 +79,7 @@ const pagination = {
|
||||||
state.value === 'notResponding' ? { notResponding: true } :
|
state.value === 'notResponding' ? { notResponding: true } :
|
||||||
{}),
|
{}),
|
||||||
})),
|
})),
|
||||||
} as PagingCtx;
|
}));
|
||||||
|
|
||||||
function getStatus(instance) {
|
function getStatus(instance) {
|
||||||
if (instance.isSuspended) return 'Suspended';
|
if (instance.isSuspended) return 'Suspended';
|
||||||
|
|
|
@ -160,7 +160,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="archived">{{ i18n.ts.archived }}</option>
|
<option value="archived">{{ i18n.ts.archived }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
|
|
||||||
<MkPagination :pagination="announcementsPagination">
|
<MkPagination :paginator="announcementsPaginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<div v-for="announcement in items" :key="announcement.id" v-panel :class="$style.announcementItem" @click="editAnnouncement(announcement)">
|
<div v-for="announcement in items" :key="announcement.id" v-panel :class="$style.announcementItem" @click="editAnnouncement(announcement)">
|
||||||
|
@ -179,7 +179,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'drive'" class="_gaps">
|
<div v-else-if="tab === 'drive'" class="_gaps">
|
||||||
<MkFileListForAdmin :pagination="filesPagination" viewMode="grid"/>
|
<MkFileListForAdmin :paginator="filesPaginator" viewMode="grid"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'chart'" class="_gaps_m">
|
<div v-else-if="tab === 'chart'" class="_gaps_m">
|
||||||
|
@ -211,7 +211,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, defineAsyncComponent, watch, ref } from 'vue';
|
import { computed, defineAsyncComponent, watch, ref, markRaw } 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 MkChart from '@/components/MkChart.vue';
|
import MkChart from '@/components/MkChart.vue';
|
||||||
|
@ -235,6 +235,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import { iAmAdmin, $i, iAmModerator } from '@/i.js';
|
import { iAmAdmin, $i, iAmModerator } from '@/i.js';
|
||||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
userId: string;
|
userId: string;
|
||||||
|
@ -255,24 +256,22 @@ const silenced = ref(false);
|
||||||
const suspended = ref(false);
|
const suspended = ref(false);
|
||||||
const isSystem = ref(false);
|
const isSystem = ref(false);
|
||||||
const moderationNote = ref('');
|
const moderationNote = ref('');
|
||||||
const filesPagination = {
|
const filesPaginator = markRaw(new Paginator('admin/drive/files', {
|
||||||
endpoint: 'admin/drive/files' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: props.userId,
|
userId: props.userId,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const announcementsStatus = ref<'active' | 'archived'>('active');
|
const announcementsStatus = ref<'active' | 'archived'>('active');
|
||||||
|
|
||||||
const announcementsPagination = {
|
const announcementsPaginator = markRaw(new Paginator('admin/announcements/list', {
|
||||||
endpoint: 'admin/announcements/list' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: props.userId,
|
userId: props.userId,
|
||||||
status: announcementsStatus.value,
|
status: announcementsStatus.value,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
const expandedRoles = ref([]);
|
const expandedRoles = ref([]);
|
||||||
|
|
||||||
function createFetcher() {
|
function createFetcher() {
|
||||||
|
|
|
@ -41,13 +41,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" :spellcheck="false">
|
<MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" :spellcheck="false">
|
||||||
<span>{{ i18n.ts.username }}</span>
|
<span>{{ i18n.ts.username }}</span>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params().origin === 'local'">
|
<MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" :spellcheck="false" :disabled="paginator.computedParams.value.origin === 'local'">
|
||||||
<span>{{ i18n.ts.host }}</span>
|
<span>{{ i18n.ts.host }}</span>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="reports" :pagination="pagination">
|
<MkPagination v-slot="{items}" :paginator="paginator">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/>
|
<XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, useTemplateRef, ref } from 'vue';
|
import { computed, ref, markRaw } from '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 XAbuseReport from '@/components/MkAbuseReport.vue';
|
import XAbuseReport from '@/components/MkAbuseReport.vue';
|
||||||
|
@ -66,8 +66,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
const reports = useTemplateRef('reports');
|
|
||||||
|
|
||||||
const state = ref('unresolved');
|
const state = ref('unresolved');
|
||||||
const reporterOrigin = ref('combined');
|
const reporterOrigin = ref('combined');
|
||||||
|
@ -75,18 +74,17 @@ const targetUserOrigin = ref('combined');
|
||||||
const searchUsername = ref('');
|
const searchUsername = ref('');
|
||||||
const searchHost = ref('');
|
const searchHost = ref('');
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('admin/abuse-user-reports', {
|
||||||
endpoint: 'admin/abuse-user-reports' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
state: state.value,
|
state: state.value,
|
||||||
reporterOrigin: reporterOrigin.value,
|
reporterOrigin: reporterOrigin.value,
|
||||||
targetUserOrigin: targetUserOrigin.value,
|
targetUserOrigin: targetUserOrigin.value,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
function resolved(reportId) {
|
function resolved(reportId) {
|
||||||
reports.value?.paginator.removeItem(reportId);
|
paginator.removeItem(reportId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</FormSplit>
|
</FormSplit>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
|
<MkPagination v-slot="{items}" :key="host + state" :paginator="paginator">
|
||||||
<div :class="$style.instances">
|
<div :class="$style.instances">
|
||||||
<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.instance" :to="`/instance-info/${instance.host}`">
|
<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.instance" :to="`/instance-info/${instance.host}`">
|
||||||
<MkInstanceCardMini :instance="instance"/>
|
<MkInstanceCardMini :instance="instance"/>
|
||||||
|
@ -56,7 +56,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 { computed, ref } from 'vue';
|
import { computed, markRaw, 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';
|
||||||
|
@ -64,15 +64,15 @@ 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';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const host = ref('');
|
const host = ref('');
|
||||||
const state = ref('federating');
|
const state = ref('federating');
|
||||||
const sort = ref('+pubSub');
|
const sort = ref('+pubSub');
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('federation/instances', {
|
||||||
endpoint: 'federation/instances' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
sort: sort.value,
|
sort: sort.value,
|
||||||
host: host.value !== '' ? host.value : null,
|
host: host.value !== '' ? host.value : null,
|
||||||
...(
|
...(
|
||||||
|
@ -85,7 +85,7 @@ const pagination = {
|
||||||
state.value === 'notResponding' ? { notResponding: true } :
|
state.value === 'notResponding' ? { notResponding: true } :
|
||||||
{}),
|
{}),
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
function getStatus(instance: Misskey.entities.FederationInstance) {
|
function getStatus(instance: Misskey.entities.FederationInstance) {
|
||||||
switch (instance.suspensionState) {
|
switch (instance.suspensionState) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="local">{{ i18n.ts.local }}</option>
|
<option value="local">{{ i18n.ts.local }}</option>
|
||||||
<option value="remote">{{ i18n.ts.remote }}</option>
|
<option value="remote">{{ i18n.ts.remote }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'">
|
<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="paginator.computedParams.value.origin === 'local'">
|
||||||
<template #label>{{ i18n.ts.host }}</template>
|
<template #label>{{ i18n.ts.host }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,14 +26,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>MIME type</template>
|
<template #label>MIME type</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</div>
|
</div>
|
||||||
<MkFileListForAdmin :pagination="pagination" :viewMode="viewMode"/>
|
<MkFileListForAdmin :paginator="paginator" :viewMode="viewMode"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, markRaw, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
@ -42,23 +42,22 @@ import * as os from '@/os.js';
|
||||||
import { lookupFile } from '@/utility/admin-lookup.js';
|
import { lookupFile } from '@/utility/admin-lookup.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const origin = ref<Misskey.entities.AdminDriveFilesRequest['origin']>('local');
|
const origin = ref<Misskey.entities.AdminDriveFilesRequest['origin']>('local');
|
||||||
const type = ref<string | null>(null);
|
const type = ref<string | null>(null);
|
||||||
const searchHost = ref('');
|
const searchHost = ref('');
|
||||||
const userId = ref('');
|
const userId = ref('');
|
||||||
const viewMode = ref<'grid' | 'list'>('grid');
|
const viewMode = ref<'grid' | 'list'>('grid');
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('admin/drive/files', {
|
||||||
endpoint: 'admin/drive/files' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
type: (type.value && type.value !== '') ? type.value : null,
|
type: (type.value && type.value !== '') ? type.value : null,
|
||||||
userId: (userId.value && userId.value !== '') ? userId.value : null,
|
userId: (userId.value && userId.value !== '') ? userId.value : null,
|
||||||
origin: origin.value,
|
origin: origin.value,
|
||||||
hostname: (searchHost.value && searchHost.value !== '') ? searchHost.value : null,
|
hostname: (searchHost.value && searchHost.value !== '') ? searchHost.value : null,
|
||||||
})),
|
})),
|
||||||
} satisfies PagingCtx<'admin/drive/files'>;
|
}));
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
os.confirm({
|
os.confirm({
|
||||||
|
|
|
@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkInput v-if="!noExpirationDate" v-model="expiresAt" type="datetime-local">
|
<MkInput v-if="!noExpirationDate" v-model="expiresAt" type="datetime-local">
|
||||||
<template #label>{{ i18n.ts.expirationDate }}</template>
|
<template #label>{{ i18n.ts.expirationDate }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="createCount" type="number" min="1">
|
<MkInput v-model="createCount" type="number" :min="1">
|
||||||
<template #label>{{ i18n.ts.createCount }}</template>
|
<template #label>{{ i18n.ts.createCount }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkButton primary rounded @click="createWithOptions">{{ i18n.ts.create }}</MkButton>
|
<MkButton primary rounded @click="createWithOptions">{{ i18n.ts.create }}</MkButton>
|
||||||
|
@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="-usedAt">{{ i18n.ts.usedAt }} ({{ i18n.ts.descendingOrder }})</option>
|
<option value="-usedAt">{{ i18n.ts.usedAt }} ({{ i18n.ts.descendingOrder }})</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
</div>
|
</div>
|
||||||
<MkPagination ref="pagingComponent" :pagination="pagination">
|
<MkPagination :paginator="paginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkInviteCode v-for="item in items" :key="item.id" :invite="(item as any)" :onDeleted="deleted" moderator/>
|
<MkInviteCode v-for="item in items" :key="item.id" :invite="(item as any)" :onDeleted="deleted" moderator/>
|
||||||
|
@ -54,8 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, useTemplateRef } from 'vue';
|
import { computed, markRaw, ref, useTemplateRef } from 'vue';
|
||||||
import type { PagingCtx } from '@/composables/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';
|
||||||
|
@ -67,21 +66,19 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkInviteCode from '@/components/MkInviteCode.vue';
|
import MkInviteCode from '@/components/MkInviteCode.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
const pagingComponent = useTemplateRef('pagingComponent');
|
|
||||||
|
|
||||||
const type = ref('all');
|
const type = ref('all');
|
||||||
const sort = ref('+createdAt');
|
const sort = ref('+createdAt');
|
||||||
|
|
||||||
const pagination: PagingCtx = {
|
const paginator = markRaw(new Paginator('admin/invite/list', {
|
||||||
endpoint: 'admin/invite/list' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
type: type.value,
|
type: type.value,
|
||||||
sort: sort.value,
|
sort: sort.value,
|
||||||
})),
|
})),
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
};
|
}));
|
||||||
|
|
||||||
const expiresAt = ref('');
|
const expiresAt = ref('');
|
||||||
const noExpirationDate = ref(true);
|
const noExpirationDate = ref(true);
|
||||||
|
@ -100,13 +97,11 @@ async function createWithOptions() {
|
||||||
text: tickets.map(x => x.code).join('\n'),
|
text: tickets.map(x => x.code).join('\n'),
|
||||||
});
|
});
|
||||||
|
|
||||||
tickets.forEach(ticket => pagingComponent.value?.paginator.prepend(ticket));
|
tickets.forEach(ticket => paginator.prepend(ticket));
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleted(id: string) {
|
function deleted(id: string) {
|
||||||
if (pagingComponent.value) {
|
paginator.removeItem(id);
|
||||||
pagingComponent.value.paginator.removeItem(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -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: 900px;">
|
<div class="_spacer" style="--MI_SPACER-w: 900px;">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkPaginationControl v-model:order="order" v-model:date="date" v-model:q="q" canSearch canFilter @reload="paginator.reload()">
|
<MkPaginationControl :paginator="paginator" canFilter>
|
||||||
<MkSelect v-model="type" style="margin: 0; flex: 1;">
|
<MkSelect v-model="type" style="margin: 0; flex: 1;">
|
||||||
<template #label>{{ i18n.ts.type }}</template>
|
<template #label>{{ i18n.ts.type }}</template>
|
||||||
<option :value="null">{{ i18n.ts.all }}</option>
|
<option :value="null">{{ i18n.ts.all }}</option>
|
||||||
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, useTemplateRef, ref, watch } from 'vue';
|
import { computed, ref, markRaw, onMounted } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XModLog from './modlog.ModLog.vue';
|
import XModLog from './modlog.ModLog.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
@ -54,37 +54,25 @@ import MkTl from '@/components/MkTl.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { usePagination } from '@/composables/use-pagination.js';
|
|
||||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkPaginationControl from '@/components/MkPaginationControl.vue';
|
import MkPaginationControl from '@/components/MkPaginationControl.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const order = ref<'newest' | 'oldest'>('newest');
|
|
||||||
const date = ref<number | null>(null);
|
|
||||||
const type = ref<string | null>(null);
|
const type = ref<string | null>(null);
|
||||||
const moderatorId = ref('');
|
const moderatorId = ref('');
|
||||||
const q = ref<string | null>(null);
|
|
||||||
|
|
||||||
const paginator = usePagination({
|
const paginator = markRaw(new Paginator('admin/show-moderation-logs', {
|
||||||
ctx: {
|
|
||||||
endpoint: 'admin/show-moderation-logs',
|
|
||||||
limit: 20,
|
limit: 20,
|
||||||
canFetchDetection: 'limit',
|
canFetchDetection: 'limit',
|
||||||
params: computed(() => ({
|
canSearch: true,
|
||||||
|
computedParams: computed(() => ({
|
||||||
type: type.value,
|
type: type.value,
|
||||||
userId: moderatorId.value === '' ? null : moderatorId.value,
|
userId: moderatorId.value === '' ? null : moderatorId.value,
|
||||||
search: q.value,
|
|
||||||
})),
|
})),
|
||||||
},
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
watch([order, date], () => {
|
paginator.init();
|
||||||
paginator.updateCtxPartial({
|
|
||||||
order: order.value,
|
|
||||||
initialDirection: order.value === 'oldest' ? 'newer' : 'older',
|
|
||||||
initialDate: date.value,
|
|
||||||
});
|
|
||||||
}, { immediate: false });
|
|
||||||
|
|
||||||
const timeline = computed(() => {
|
const timeline = computed(() => {
|
||||||
return paginator.items.value.map(x => ({
|
return paginator.items.value.map(x => ({
|
||||||
|
@ -95,7 +83,7 @@ const timeline = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function fetchMore() {
|
function fetchMore() {
|
||||||
if (order.value === 'oldest') {
|
if (paginator.order.value === 'oldest') {
|
||||||
paginator.fetchNewer();
|
paginator.fetchNewer();
|
||||||
} else {
|
} else {
|
||||||
paginator.fetchOlder();
|
paginator.fetchOlder();
|
||||||
|
|
|
@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkButton primary rounded @click="assign"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton>
|
<MkButton primary rounded @click="assign"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton>
|
||||||
|
|
||||||
<MkPagination :pagination="usersPagination">
|
<MkPagination :paginator="usersPaginator">
|
||||||
<template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template>
|
<template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template>
|
||||||
|
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
|
@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, reactive, ref } from 'vue';
|
import { computed, markRaw, reactive, ref } from 'vue';
|
||||||
import XEditor from './roles.editor.vue';
|
import XEditor from './roles.editor.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -66,6 +66,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -73,13 +74,12 @@ const props = defineProps<{
|
||||||
id?: string;
|
id?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const usersPagination = {
|
const usersPaginator = markRaw(new Paginator('admin/roles/users', {
|
||||||
endpoint: 'admin/roles/users' as const,
|
|
||||||
limit: 20,
|
limit: 20,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
roleId: props.id,
|
roleId: props.id,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const expandedItems = ref([]);
|
const expandedItems = ref([]);
|
||||||
|
|
||||||
|
|
|
@ -38,13 +38,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #prefix>@</template>
|
<template #prefix>@</template>
|
||||||
<template #label>{{ i18n.ts.username }}</template>
|
<template #label>{{ i18n.ts.username }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params.origin === 'local'">
|
<MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="paginator.computedParams.value.origin === 'local'">
|
||||||
<template #prefix>@</template>
|
<template #prefix>@</template>
|
||||||
<template #label>{{ i18n.ts.host }}</template>
|
<template #label>{{ i18n.ts.host }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination">
|
<MkPagination v-slot="{items}" :paginator="paginator">
|
||||||
<div :class="$style.users">
|
<div :class="$style.users">
|
||||||
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" :class="$style.user" :to="`/admin/user/${user.id}`">
|
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" :class="$style.user" :to="`/admin/user/${user.id}`">
|
||||||
<MkUserCardMini :user="user"/>
|
<MkUserCardMini :user="user"/>
|
||||||
|
@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, useTemplateRef, ref, watchEffect } from 'vue';
|
import { computed, markRaw, ref, watchEffect } from 'vue';
|
||||||
import { defaultMemoryStorage } from '@/memory-storage';
|
import { defaultMemoryStorage } from '@/memory-storage';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
@ -69,6 +69,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import { dateString } from '@/filters/date.js';
|
import { dateString } from '@/filters/date.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
type SearchQuery = {
|
type SearchQuery = {
|
||||||
sort?: string;
|
sort?: string;
|
||||||
|
@ -78,7 +79,6 @@ type SearchQuery = {
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const paginationComponent = useTemplateRef('paginationComponent');
|
|
||||||
const storedQuery = JSON.parse(defaultMemoryStorage.getItem('admin-users-query') ?? '{}') as SearchQuery;
|
const storedQuery = JSON.parse(defaultMemoryStorage.getItem('admin-users-query') ?? '{}') as SearchQuery;
|
||||||
|
|
||||||
const sort = ref(storedQuery.sort ?? '+createdAt');
|
const sort = ref(storedQuery.sort ?? '+createdAt');
|
||||||
|
@ -86,10 +86,9 @@ const state = ref(storedQuery.state ?? 'all');
|
||||||
const origin = ref(storedQuery.origin ?? 'local');
|
const origin = ref(storedQuery.origin ?? 'local');
|
||||||
const searchUsername = ref(storedQuery.username ?? '');
|
const searchUsername = ref(storedQuery.username ?? '');
|
||||||
const searchHost = ref(storedQuery.hostname ?? '');
|
const searchHost = ref(storedQuery.hostname ?? '');
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('admin/show-users', {
|
||||||
endpoint: 'admin/show-users' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
sort: sort.value,
|
sort: sort.value,
|
||||||
state: state.value,
|
state: state.value,
|
||||||
origin: origin.value,
|
origin: origin.value,
|
||||||
|
@ -97,7 +96,7 @@ const pagination = {
|
||||||
hostname: searchHost.value,
|
hostname: searchHost.value,
|
||||||
})),
|
})),
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
};
|
}));
|
||||||
|
|
||||||
function searchUser() {
|
function searchUser() {
|
||||||
os.selectUser({ includeSelf: true }).then(user => {
|
os.selectUser({ includeSelf: true }).then(user => {
|
||||||
|
@ -121,7 +120,7 @@ async function addUser() {
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
paginationComponent.value?.paginator.reload();
|
paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInfo v-if="$i && $i.hasUnreadAnnouncement && tab === 'current'" warn>{{ i18n.ts.youHaveUnreadAnnouncements }}</MkInfo>
|
<MkInfo v-if="$i && $i.hasUnreadAnnouncement && tab === 'current'" warn>{{ i18n.ts.youHaveUnreadAnnouncements }}</MkInfo>
|
||||||
<MkPagination ref="paginationEl" :key="tab" v-slot="{items}" :pagination="tab === 'current' ? paginationCurrent : paginationPast" class="_gaps">
|
<MkPagination v-slot="{items}" :paginator="paginator" class="_gaps">
|
||||||
<section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement">
|
<section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement">
|
||||||
<div v-if="announcement.forYou" :class="$style.forYou"><i class="ti ti-pin"></i> {{ i18n.ts.forYou }}</div>
|
<div v-if="announcement.forYou" :class="$style.forYou"><i class="ti ti-pin"></i> {{ i18n.ts.forYou }}</div>
|
||||||
<div :class="$style.header">
|
<div :class="$style.header">
|
||||||
|
@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, useTemplateRef } from 'vue';
|
import { ref, computed, markRaw } 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';
|
||||||
|
@ -54,24 +54,14 @@ import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { updateCurrentAccountPartial } from '@/accounts.js';
|
import { updateCurrentAccountPartial } from '@/accounts.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const paginationCurrent = {
|
const paginator = markRaw(new Paginator('announcements', {
|
||||||
endpoint: 'announcements' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
computedParams: computed(() => ({
|
||||||
isActive: true,
|
isActive: tab.value === 'current',
|
||||||
},
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const paginationPast = {
|
|
||||||
endpoint: 'announcements' as const,
|
|
||||||
limit: 10,
|
|
||||||
params: {
|
|
||||||
isActive: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const paginationEl = useTemplateRef('paginationEl');
|
|
||||||
|
|
||||||
const tab = ref('current');
|
const tab = ref('current');
|
||||||
|
|
||||||
|
@ -85,8 +75,7 @@ async function read(target) {
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!paginationEl.value) return;
|
paginator.updateItem(target.id, a => ({
|
||||||
paginationEl.value.paginator.updateItem(target.id, a => ({
|
|
||||||
...a,
|
...a,
|
||||||
isRead: true,
|
isRead: true,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStreamingNotesTimeline :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'">
|
||||||
<MkNotesTimeline :pagination="featuredPagination"/>
|
<MkNotesTimeline :paginator="featuredPaginator"/>
|
||||||
</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>
|
||||||
<MkNotesTimeline v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
|
<MkNotesTimeline v-if="searchPaginator" :key="searchKey" :paginator="searchPaginator"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
|
<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
|
||||||
|
@ -70,7 +70,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, markRaw, shallowRef } 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 { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
|
@ -97,6 +97,7 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
import { notesSearchAvailable } from '@/utility/check-permissions.js';
|
import { notesSearchAvailable } from '@/utility/check-permissions.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -109,14 +110,13 @@ const tab = ref('overview');
|
||||||
const channel = ref<Misskey.entities.Channel | null>(null);
|
const channel = ref<Misskey.entities.Channel | null>(null);
|
||||||
const favorited = ref(false);
|
const favorited = ref(false);
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
const searchPagination = ref();
|
const searchPaginator = shallowRef();
|
||||||
const searchKey = ref('');
|
const searchKey = ref('');
|
||||||
const featuredPagination = computed(() => ({
|
const featuredPaginator = markRaw(new Paginator('channels/featured', {
|
||||||
endpoint: 'notes/featured' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
computedParams: computed(() => ({
|
||||||
channelId: props.channelId,
|
channelId: props.channelId,
|
||||||
},
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
|
@ -190,14 +190,13 @@ async function search() {
|
||||||
|
|
||||||
if (query == null) return;
|
if (query == null) return;
|
||||||
|
|
||||||
searchPagination.value = {
|
searchPaginator.value = markRaw(new Paginator('notes/search', {
|
||||||
endpoint: 'notes/search',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
query: query,
|
query: query,
|
||||||
channelId: channel.value.id,
|
channelId: channel.value.id,
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
|
|
||||||
searchKey.value = query;
|
searchKey.value = query;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,27 +18,27 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
|
<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkFoldableSection v-if="channelPagination">
|
<MkFoldableSection v-if="channelPaginator">
|
||||||
<template #header>{{ i18n.ts.searchResult }}</template>
|
<template #header>{{ i18n.ts.searchResult }}</template>
|
||||||
<MkChannelList :key="key" :pagination="channelPagination"/>
|
<MkChannelList :key="key" :paginator="channelPaginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tab === 'featured'">
|
<div v-if="tab === 'featured'">
|
||||||
<MkPagination v-slot="{items}" :pagination="featuredPagination">
|
<MkPagination v-slot="{items}" :paginator="featuredPaginator">
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'favorites'">
|
<div v-else-if="tab === 'favorites'">
|
||||||
<MkPagination v-slot="{items}" :pagination="favoritesPagination">
|
<MkPagination v-slot="{items}" :paginator="favoritesPaginator">
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'following'">
|
<div v-else-if="tab === 'following'">
|
||||||
<MkPagination v-slot="{items}" :pagination="followingPagination">
|
<MkPagination v-slot="{items}" :paginator="followingPaginator">
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'owned'">
|
<div v-else-if="tab === 'owned'">
|
||||||
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
||||||
<MkPagination v-slot="{items}" :pagination="ownedPagination">
|
<MkPagination v-slot="{items}" :paginator="ownedPaginator">
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, markRaw, onMounted, ref, shallowRef } from 'vue';
|
||||||
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
||||||
import MkChannelList from '@/components/MkChannelList.vue';
|
import MkChannelList from '@/components/MkChannelList.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
@ -68,6 +68,7 @@ import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -80,31 +81,27 @@ const key = ref('');
|
||||||
const tab = ref('featured');
|
const tab = ref('featured');
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
const searchType = ref('nameAndDescription');
|
const searchType = ref('nameAndDescription');
|
||||||
const channelPagination = ref();
|
const channelPaginator = shallowRef();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
searchQuery.value = props.query ?? '';
|
searchQuery.value = props.query ?? '';
|
||||||
searchType.value = props.type ?? 'nameAndDescription';
|
searchType.value = props.type ?? 'nameAndDescription';
|
||||||
});
|
});
|
||||||
|
|
||||||
const featuredPagination = {
|
const featuredPaginator = markRaw(new Paginator('channels/featured', {
|
||||||
endpoint: 'channels/featured' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
};
|
}));
|
||||||
const favoritesPagination = {
|
const favoritesPaginator = markRaw(new Paginator('channels/my-favorites', {
|
||||||
endpoint: 'channels/my-favorites' as const,
|
|
||||||
limit: 100,
|
limit: 100,
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
};
|
}));
|
||||||
const followingPagination = {
|
const followingPaginator = markRaw(new Paginator('channels/followed', {
|
||||||
endpoint: 'channels/followed' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
const ownedPagination = {
|
const ownedPaginator = markRaw(new Paginator('channels/owned', {
|
||||||
endpoint: 'channels/owned' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
|
|
||||||
async function search() {
|
async function search() {
|
||||||
const query = searchQuery.value.toString().trim();
|
const query = searchQuery.value.toString().trim();
|
||||||
|
@ -113,14 +110,13 @@ async function search() {
|
||||||
|
|
||||||
const type = searchType.value.toString().trim();
|
const type = searchType.value.toString().trim();
|
||||||
|
|
||||||
channelPagination.value = {
|
channelPaginator.value = markRaw(new Paginator('channels/search', {
|
||||||
endpoint: 'channels/search',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
query: searchQuery.value,
|
query: searchQuery.value,
|
||||||
type: type,
|
type: type,
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
|
|
||||||
key.value = query + type;
|
key.value = query + type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkNotesTimeline :pagination="pagination" :detail="true"/>
|
<MkNotesTimeline :paginator="paginator" :detail="true"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, provide, ref } from 'vue';
|
import { computed, watch, provide, ref, markRaw } 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';
|
||||||
|
@ -46,6 +46,7 @@ import { isSupportShare } from '@/utility/navigator.js';
|
||||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
import { genEmbedCode } from '@/utility/get-embed-code.js';
|
import { genEmbedCode } from '@/utility/get-embed-code.js';
|
||||||
import { assertServerContext, serverContext } from '@/server-context.js';
|
import { assertServerContext, serverContext } from '@/server-context.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
// contextは非ログイン状態の情報しかないためログイン時は利用できない
|
// contextは非ログイン状態の情報しかないためログイン時は利用できない
|
||||||
const CTX_CLIP = !$i && assertServerContext(serverContext, 'clip') ? serverContext.clip : null;
|
const CTX_CLIP = !$i && assertServerContext(serverContext, 'clip') ? serverContext.clip : null;
|
||||||
|
@ -56,13 +57,13 @@ const props = defineProps<{
|
||||||
|
|
||||||
const clip = ref<Misskey.entities.Clip | null>(CTX_CLIP);
|
const clip = ref<Misskey.entities.Clip | null>(CTX_CLIP);
|
||||||
const favorited = ref(false);
|
const favorited = ref(false);
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('clips/notes', {
|
||||||
endpoint: 'clips/notes' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
canSearch: true,
|
||||||
|
computedParams: computed(() => ({
|
||||||
clipId: props.clipId,
|
clipId: props.clipId,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const isOwned = computed<boolean | null>(() => $i && clip.value && ($i.id === clip.value.userId));
|
const isOwned = computed<boolean | null>(() => $i && clip.value && ($i.id === clip.value.userId));
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton inline @click="setLicenseBulk">Set License</MkButton>
|
<MkButton inline @click="setLicenseBulk">Set License</MkButton>
|
||||||
<MkButton inline danger @click="delBulk">Delete</MkButton>
|
<MkButton inline danger @click="delBulk">Delete</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<MkPagination ref="emojisPaginationComponent" :pagination="pagination">
|
<MkPagination ref="emojisPaginationComponent" :paginator="paginator">
|
||||||
<template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template>
|
<template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template>
|
||||||
<template #default="{items}">
|
<template #default="{items}">
|
||||||
<div class="ldhfsamy">
|
<div class="ldhfsamy">
|
||||||
|
@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts.host }}</template>
|
<template #label>{{ i18n.ts.host }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</FormSplit>
|
</FormSplit>
|
||||||
<MkPagination :pagination="remotePagination">
|
<MkPagination :paginator="remotePaginator">
|
||||||
<template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template>
|
<template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template>
|
||||||
<template #default="{items}">
|
<template #default="{items}">
|
||||||
<div class="ldhfsamy">
|
<div class="ldhfsamy">
|
||||||
|
@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, defineAsyncComponent, ref, useTemplateRef } from 'vue';
|
import { computed, defineAsyncComponent, markRaw, ref } from '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';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
@ -84,8 +84,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
|
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
const emojisPaginationComponent = useTemplateRef('emojisPaginationComponent');
|
|
||||||
|
|
||||||
const tab = ref('local');
|
const tab = ref('local');
|
||||||
const query = ref<string | null>(null);
|
const query = ref<string | null>(null);
|
||||||
|
@ -94,28 +93,26 @@ const host = ref<string | null>(null);
|
||||||
const selectMode = ref(false);
|
const selectMode = ref(false);
|
||||||
const selectedEmojis = ref<string[]>([]);
|
const selectedEmojis = ref<string[]>([]);
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('admin/emoji/list', {
|
||||||
endpoint: 'admin/emoji/list' as const,
|
|
||||||
limit: 30,
|
limit: 30,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
query: (query.value && query.value !== '') ? query.value : null,
|
query: (query.value && query.value !== '') ? query.value : null,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const remotePagination = {
|
const remotePaginator = markRaw(new Paginator('admin/emoji/list-remote', {
|
||||||
endpoint: 'admin/emoji/list-remote' as const,
|
|
||||||
limit: 30,
|
limit: 30,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null,
|
query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null,
|
||||||
host: (host.value && host.value !== '') ? host.value : null,
|
host: (host.value && host.value !== '') ? host.value : null,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const selectAll = () => {
|
const selectAll = () => {
|
||||||
if (selectedEmojis.value.length > 0) {
|
if (selectedEmojis.value.length > 0) {
|
||||||
selectedEmojis.value = [];
|
selectedEmojis.value = [];
|
||||||
} else {
|
} else {
|
||||||
selectedEmojis.value = emojisPaginationComponent.value?.paginator.items.value.map(item => item.id);
|
selectedEmojis.value = paginator.items.value.map(item => item.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -132,7 +129,7 @@ const add = async (ev: MouseEvent) => {
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
if (result.created) {
|
if (result.created) {
|
||||||
emojisPaginationComponent.value?.paginator.prepend(result.created);
|
paginator.prepend(result.created);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
|
@ -145,12 +142,12 @@ const edit = async (emoji) => {
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
if (result.updated) {
|
if (result.updated) {
|
||||||
emojisPaginationComponent.value?.paginator.updateItem(result.updated.id, (oldEmoji) => ({
|
paginator.updateItem(result.updated.id, (oldEmoji) => ({
|
||||||
...oldEmoji,
|
...oldEmoji,
|
||||||
...result.updated,
|
...result.updated,
|
||||||
}));
|
}));
|
||||||
} else if (result.deleted) {
|
} else if (result.deleted) {
|
||||||
emojisPaginationComponent.value?.paginator.removeItem(emoji.id);
|
paginator.removeItem(emoji.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
|
@ -245,7 +242,7 @@ const setCategoryBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
category: result,
|
category: result,
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.paginator.reload();
|
paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setLicenseBulk = async () => {
|
const setLicenseBulk = async () => {
|
||||||
|
@ -257,7 +254,7 @@ const setLicenseBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
license: result,
|
license: result,
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.paginator.reload();
|
paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const addTagBulk = async () => {
|
const addTagBulk = async () => {
|
||||||
|
@ -269,7 +266,7 @@ const addTagBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
aliases: result.split(' '),
|
aliases: result.split(' '),
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.paginator.reload();
|
paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeTagBulk = async () => {
|
const removeTagBulk = async () => {
|
||||||
|
@ -281,7 +278,7 @@ const removeTagBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
aliases: result.split(' '),
|
aliases: result.split(' '),
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.paginator.reload();
|
paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setTagBulk = async () => {
|
const setTagBulk = async () => {
|
||||||
|
@ -293,7 +290,7 @@ const setTagBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
aliases: result.split(' '),
|
aliases: result.split(' '),
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value?.paginator.reload();
|
paginator.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const delBulk = async () => {
|
const delBulk = async () => {
|
||||||
|
@ -305,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?.paginator.reload();
|
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>
|
||||||
<MkNotesTimeline ref="tlComponent" :pagination="pagination"/>
|
<MkNotesTimeline :paginator="paginator"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, markRaw } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fileId: string;
|
fileId: string;
|
||||||
|
@ -23,11 +23,10 @@ const props = defineProps<{
|
||||||
|
|
||||||
const realFileId = computed(() => props.fileId);
|
const realFileId = computed(() => props.fileId);
|
||||||
|
|
||||||
const pagination = ref<PagingCtx>({
|
const paginator = markRaw(new Paginator('drive/files/attached-notes', {
|
||||||
endpoint: 'drive/files/attached-notes',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
fileId: realFileId.value,
|
fileId: realFileId.value,
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,30 +9,29 @@ 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>
|
||||||
<MkNotesTimeline v-if="tab === 'notes'" :pagination="paginationForNotes"/>
|
<MkNotesTimeline v-if="tab === 'notes'" :paginator="paginatorForNotes"/>
|
||||||
<MkNotesTimeline v-else-if="tab === 'polls'" :pagination="paginationForPolls"/>
|
<MkNotesTimeline v-else-if="tab === 'polls'" :paginator="paginatorForPolls"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { markRaw, ref } from 'vue';
|
||||||
import MkNotesTimeline from '@/components/MkNotesTimeline.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';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const paginationForNotes = {
|
const paginatorForNotes = markRaw(new Paginator('notes/featured', {
|
||||||
endpoint: 'notes/featured' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
|
|
||||||
const paginationForPolls = {
|
const paginatorForPolls = markRaw(new Paginator('notes/polls/recommendation', {
|
||||||
endpoint: 'notes/polls/recommendation' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
params: {
|
params: {
|
||||||
excludeChannels: true,
|
excludeChannels: true,
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
|
|
||||||
const tab = ref('notes');
|
const tab = ref('notes');
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -13,19 +13,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template v-if="tag == null">
|
<template v-if="tag == null">
|
||||||
<MkFoldableSection class="_margin" persistKey="explore-pinned-users">
|
<MkFoldableSection class="_margin" persistKey="explore-pinned-users">
|
||||||
<template #header><i class="ti ti-bookmark ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedUsers }}</template>
|
<template #header><i class="ti ti-bookmark ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedUsers }}</template>
|
||||||
<MkUserList :pagination="pinnedUsers"/>
|
<MkUserList :paginator="pinnedUsersPaginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
<MkFoldableSection class="_margin" persistKey="explore-popular-users">
|
<MkFoldableSection class="_margin" persistKey="explore-popular-users">
|
||||||
<template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
|
<template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
|
||||||
<MkUserList :pagination="popularUsers"/>
|
<MkUserList :paginator="popularUsersPaginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
<MkFoldableSection class="_margin" persistKey="explore-recently-updated-users">
|
<MkFoldableSection class="_margin" persistKey="explore-recently-updated-users">
|
||||||
<template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
|
<template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
|
||||||
<MkUserList :pagination="recentlyUpdatedUsers"/>
|
<MkUserList :paginator="recentlyUpdatedUsersPaginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
<MkFoldableSection class="_margin" persistKey="explore-recently-registered-users">
|
<MkFoldableSection class="_margin" persistKey="explore-recently-registered-users">
|
||||||
<template #header><i class="ti ti-plus ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyRegisteredUsers }}</template>
|
<template #header><i class="ti ti-plus ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyRegisteredUsers }}</template>
|
||||||
<MkUserList :pagination="recentlyRegisteredUsers"/>
|
<MkUserList :paginator="recentlyRegisteredUsersPaginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,21 +41,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<MkFoldableSection v-if="tag != null" :key="`${tag}`" class="_margin">
|
<MkFoldableSection v-if="tag != null" :key="`${tag}`" class="_margin">
|
||||||
<template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template>
|
<template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template>
|
||||||
<MkUserList :pagination="tagUsers"/>
|
<MkUserList :paginator="tagUsersPaginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
|
|
||||||
<template v-if="tag == null">
|
<template v-if="tag == null">
|
||||||
<MkFoldableSection class="_margin">
|
<MkFoldableSection class="_margin">
|
||||||
<template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
|
<template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
|
||||||
<MkUserList :pagination="popularUsersF"/>
|
<MkUserList :paginator="popularUsersFPaginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
<MkFoldableSection class="_margin">
|
<MkFoldableSection class="_margin">
|
||||||
<template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
|
<template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
|
||||||
<MkUserList :pagination="recentlyUpdatedUsersF"/>
|
<MkUserList :paginator="recentlyUpdatedUsersFPaginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
<MkFoldableSection class="_margin">
|
<MkFoldableSection class="_margin">
|
||||||
<template #header><i class="ti ti-rocket ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyDiscoveredUsers }}</template>
|
<template #header><i class="ti ti-rocket ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyDiscoveredUsers }}</template>
|
||||||
<MkUserList :pagination="recentlyRegisteredUsersF"/>
|
<MkUserList :paginator="recentlyRegisteredUsersFPaginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watch, ref, useTemplateRef, computed } from 'vue';
|
import { watch, ref, useTemplateRef, computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkUserList from '@/components/MkUserList.vue';
|
import MkUserList from '@/components/MkUserList.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
|
@ -71,6 +71,7 @@ import MkTab from '@/components/MkTab.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tag?: string;
|
tag?: string;
|
||||||
|
@ -85,8 +86,7 @@ watch(() => props.tag, () => {
|
||||||
if (tagsEl.value) tagsEl.value.toggleContent(props.tag == null);
|
if (tagsEl.value) tagsEl.value.toggleContent(props.tag == null);
|
||||||
});
|
});
|
||||||
|
|
||||||
const tagUsers = computed(() => ({
|
const tagUsersPaginator = markRaw(new Paginator('hashtags/users', {
|
||||||
endpoint: 'hashtags/users' as const,
|
|
||||||
limit: 30,
|
limit: 30,
|
||||||
params: {
|
params: {
|
||||||
tag: props.tag,
|
tag: props.tag,
|
||||||
|
@ -95,34 +95,66 @@ const tagUsers = computed(() => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };
|
const pinnedUsersPaginator = markRaw(new Paginator('pinned-users', {
|
||||||
const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
noPaging: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const popularUsersPaginator = markRaw(new Paginator('users', {
|
||||||
|
limit: 10,
|
||||||
|
noPaging: true,
|
||||||
|
params: {
|
||||||
state: 'alive',
|
state: 'alive',
|
||||||
origin: 'local',
|
origin: 'local',
|
||||||
sort: '+follower',
|
sort: '+follower',
|
||||||
} };
|
},
|
||||||
const recentlyUpdatedUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
}));
|
||||||
|
|
||||||
|
const recentlyUpdatedUsersPaginator = markRaw(new Paginator('users', {
|
||||||
|
limit: 10,
|
||||||
|
noPaging: true,
|
||||||
|
params: {
|
||||||
origin: 'local',
|
origin: 'local',
|
||||||
sort: '+updatedAt',
|
sort: '+updatedAt',
|
||||||
} };
|
},
|
||||||
const recentlyRegisteredUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
}));
|
||||||
|
|
||||||
|
const recentlyRegisteredUsersPaginator = markRaw(new Paginator('users', {
|
||||||
|
limit: 10,
|
||||||
|
noPaging: true,
|
||||||
|
params: {
|
||||||
origin: 'local',
|
origin: 'local',
|
||||||
state: 'alive',
|
state: 'alive',
|
||||||
sort: '+createdAt',
|
sort: '+createdAt',
|
||||||
} };
|
},
|
||||||
const popularUsersF = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
}));
|
||||||
|
|
||||||
|
const popularUsersFPaginator = markRaw(new Paginator('users', {
|
||||||
|
limit: 10,
|
||||||
|
noPaging: true,
|
||||||
|
params: {
|
||||||
state: 'alive',
|
state: 'alive',
|
||||||
origin: 'remote',
|
origin: 'remote',
|
||||||
sort: '+follower',
|
sort: '+follower',
|
||||||
} };
|
},
|
||||||
const recentlyUpdatedUsersF = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
}));
|
||||||
|
|
||||||
|
const recentlyUpdatedUsersFPaginator = markRaw(new Paginator('users', {
|
||||||
|
limit: 10,
|
||||||
|
noPaging: true,
|
||||||
|
params: {
|
||||||
origin: 'combined',
|
origin: 'combined',
|
||||||
sort: '+updatedAt',
|
sort: '+updatedAt',
|
||||||
} };
|
},
|
||||||
const recentlyRegisteredUsersF = { endpoint: 'users', limit: 10, noPaging: true, params: {
|
}));
|
||||||
|
|
||||||
|
const recentlyRegisteredUsersFPaginator = markRaw(new Paginator('users', {
|
||||||
|
limit: 10,
|
||||||
|
noPaging: true,
|
||||||
|
params: {
|
||||||
origin: 'combined',
|
origin: 'combined',
|
||||||
sort: '+createdAt',
|
sort: '+createdAt',
|
||||||
} };
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
misskeyApi('hashtags/list', {
|
misskeyApi('hashtags/list', {
|
||||||
sort: '+attachedLocalUsers',
|
sort: '+attachedLocalUsers',
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<PageWithHeader>
|
<PageWithHeader>
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||||
<MkPagination :pagination="pagination">
|
<MkPagination :paginator="paginator">
|
||||||
<template #empty><MkResult type="empty" :text="i18n.ts.noNotes"/></template>
|
<template #empty><MkResult type="empty" :text="i18n.ts.noNotes"/></template>
|
||||||
|
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
|
@ -20,16 +20,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { markRaw } from 'vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkNote from '@/components/MkNote.vue';
|
import MkNote from '@/components/MkNote.vue';
|
||||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('i/favorites', {
|
||||||
endpoint: 'i/favorites' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
|
|
||||||
definePage(() => ({
|
definePage(() => ({
|
||||||
title: i18n.ts.favorites,
|
title: i18n.ts.favorites,
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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: 700px;">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<div v-if="tab === 'featured'">
|
<div v-if="tab === 'featured'">
|
||||||
<MkPagination v-slot="{items}" :pagination="featuredFlashsPagination">
|
<MkPagination v-slot="{items}" :paginator="featuredFlashsPaginator">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-else-if="tab === 'my'">
|
<div v-else-if="tab === 'my'">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
<MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
||||||
<MkPagination v-slot="{items}" :pagination="myFlashsPagination">
|
<MkPagination v-slot="{items}" :paginator="myFlashsPaginator">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'liked'">
|
<div v-else-if="tab === 'liked'">
|
||||||
<MkPagination v-slot="{items}" :pagination="likedFlashsPagination">
|
<MkPagination v-slot="{items}" :paginator="likedFlashsPaginator">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
|
<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,31 +37,29 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, markRaw, ref } from 'vue';
|
||||||
import MkFlashPreview from '@/components/MkFlashPreview.vue';
|
import MkFlashPreview from '@/components/MkFlashPreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const tab = ref('featured');
|
const tab = ref('featured');
|
||||||
|
|
||||||
const featuredFlashsPagination = {
|
const featuredFlashsPaginator = markRaw(new Paginator('flash/featured', {
|
||||||
endpoint: 'flash/featured' as const,
|
|
||||||
limit: 5,
|
limit: 5,
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
};
|
}));
|
||||||
const myFlashsPagination = {
|
const myFlashsPaginator = markRaw(new Paginator('flash/my', {
|
||||||
endpoint: 'flash/my' as const,
|
|
||||||
limit: 5,
|
limit: 5,
|
||||||
};
|
}));
|
||||||
const likedFlashsPagination = {
|
const likedFlashsPaginator = markRaw(new Paginator('flash/my-likes', {
|
||||||
endpoint: 'flash/my-likes' as const,
|
|
||||||
limit: 5,
|
limit: 5,
|
||||||
};
|
}));
|
||||||
|
|
||||||
function create() {
|
function create() {
|
||||||
router.push('/play/new');
|
router.push('/play/new');
|
||||||
|
|
|
@ -5,8 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<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 :key="tab" class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||||
<MkPagination ref="paginationComponent" :pagination="pagination">
|
<MkPagination :paginator="paginator">
|
||||||
<template #empty><MkResult type="empty" :text="i18n.ts.noFollowRequests"/></template>
|
<template #empty><MkResult type="empty" :text="i18n.ts.noFollowRequests"/></template>
|
||||||
<template #default="{items}">
|
<template #default="{items}">
|
||||||
<div class="mk-follow-requests _gaps">
|
<div class="mk-follow-requests _gaps">
|
||||||
|
@ -35,8 +35,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 { computed, markRaw, ref, watch } from 'vue';
|
||||||
import type { PagingCtx } from '@/composables/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';
|
||||||
|
@ -44,32 +43,35 @@ 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';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const paginationComponent = useTemplateRef('paginationComponent');
|
const tab = ref($i?.isLocked ? 'list' : 'sent');
|
||||||
|
|
||||||
const pagination = computed<PagingCtx>(() => tab.value === 'list' ? {
|
let paginator: Paginator<'following/requests/list' | 'following/requests/sent'>;
|
||||||
endpoint: 'following/requests/list',
|
|
||||||
limit: 10,
|
watch(tab, (newTab) => {
|
||||||
} : {
|
if (newTab === 'list') {
|
||||||
endpoint: 'following/requests/sent',
|
paginator = markRaw(new Paginator('following/requests/list', { limit: 10 }));
|
||||||
limit: 10,
|
} else {
|
||||||
});
|
paginator = markRaw(new Paginator('following/requests/sent', { limit: 10 }));
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
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?.paginator.reload();
|
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?.paginator.reload();
|
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?.paginator.reload();
|
paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,8 +93,6 @@ const headerTabs = computed(() => [
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const tab = ref($i?.isLocked ? 'list' : 'sent');
|
|
||||||
|
|
||||||
definePage(() => ({
|
definePage(() => ({
|
||||||
title: i18n.ts.followRequests,
|
title: i18n.ts.followRequests,
|
||||||
icon: 'ti ti-user-plus',
|
icon: 'ti ti-user-plus',
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-if="tab === 'explore'">
|
<div v-if="tab === 'explore'">
|
||||||
<MkFoldableSection class="_margin">
|
<MkFoldableSection class="_margin">
|
||||||
<template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template>
|
<template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template>
|
||||||
<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disableAutoLoad="true">
|
<MkPagination v-slot="{items}" :paginator="recentPostsPaginator">
|
||||||
<div :class="$style.items">
|
<div :class="$style.items">
|
||||||
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
<MkFoldableSection class="_margin">
|
<MkFoldableSection class="_margin">
|
||||||
<template #header><i class="ti ti-comet"></i>{{ i18n.ts.popularPosts }}</template>
|
<template #header><i class="ti ti-comet"></i>{{ i18n.ts.popularPosts }}</template>
|
||||||
<MkPagination v-slot="{items}" :pagination="popularPostsPagination" :disableAutoLoad="true">
|
<MkPagination v-slot="{items}" :paginator="popularPostsPaginator">
|
||||||
<div :class="$style.items">
|
<div :class="$style.items">
|
||||||
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'liked'">
|
<div v-else-if="tab === 'liked'">
|
||||||
<MkPagination v-slot="{items}" :pagination="likedPostsPagination">
|
<MkPagination v-slot="{items}" :paginator="likedPostsPaginator">
|
||||||
<div :class="$style.items">
|
<div :class="$style.items">
|
||||||
<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
|
<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'my'">
|
<div v-else-if="tab === 'my'">
|
||||||
<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ti ti-plus"></i> {{ i18n.ts.postToGallery }}</MkA>
|
<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ti ti-plus"></i> {{ i18n.ts.postToGallery }}</MkA>
|
||||||
<MkPagination v-slot="{items}" :pagination="myPostsPagination">
|
<MkPagination v-slot="{items}" :paginator="myPostsPaginator">
|
||||||
<div :class="$style.items">
|
<div :class="$style.items">
|
||||||
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,13 +44,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watch, ref, computed } from 'vue';
|
import { watch, ref, computed, markRaw } from 'vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
|
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -59,34 +60,19 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tab = ref('explore');
|
const tab = ref('explore');
|
||||||
const tags = ref([]);
|
|
||||||
const tagsRef = ref();
|
const tagsRef = ref();
|
||||||
|
|
||||||
const recentPostsPagination = {
|
const recentPostsPaginator = markRaw(new Paginator('gallery/posts', {
|
||||||
endpoint: 'gallery/posts' as const,
|
|
||||||
limit: 6,
|
limit: 6,
|
||||||
};
|
}));
|
||||||
const popularPostsPagination = {
|
const popularPostsPaginator = markRaw(new Paginator('gallery/featured', {
|
||||||
endpoint: 'gallery/featured' as const,
|
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
};
|
}));
|
||||||
const myPostsPagination = {
|
const myPostsPaginator = markRaw(new Paginator('i/gallery/posts', {
|
||||||
endpoint: 'i/gallery/posts' as const,
|
|
||||||
limit: 5,
|
limit: 5,
|
||||||
};
|
}));
|
||||||
const likedPostsPagination = {
|
const likedPostsPaginator = markRaw(new Paginator('i/gallery/likes', {
|
||||||
endpoint: 'i/gallery/likes' as const,
|
|
||||||
limit: 5,
|
limit: 5,
|
||||||
};
|
|
||||||
|
|
||||||
const tagUsersPagination = computed(() => ({
|
|
||||||
endpoint: 'hashtags/users' as const,
|
|
||||||
limit: 30,
|
|
||||||
params: {
|
|
||||||
tag: props.tag,
|
|
||||||
origin: 'combined',
|
|
||||||
sort: '+follower',
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
watch(() => props.tag, () => {
|
watch(() => props.tag, () => {
|
||||||
|
|
|
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkContainer :max-height="300" :foldable="true" class="other">
|
<MkContainer :max-height="300" :foldable="true" class="other">
|
||||||
<template #icon><i class="ti ti-clock"></i></template>
|
<template #icon><i class="ti ti-clock"></i></template>
|
||||||
<template #header>{{ i18n.ts.recentPosts }}</template>
|
<template #header>{{ i18n.ts.recentPosts }}</template>
|
||||||
<MkPagination v-slot="{items}" :pagination="otherPostsPagination">
|
<MkPagination v-slot="{items}" :paginator="otherPostsPaginator">
|
||||||
<div class="sdrarzaf">
|
<div class="sdrarzaf">
|
||||||
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, ref, defineAsyncComponent } from 'vue';
|
import { computed, watch, ref, defineAsyncComponent, markRaw } 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';
|
||||||
|
@ -80,6 +80,7 @@ import { $i } from '@/i.js';
|
||||||
import { isSupportShare } from '@/utility/navigator.js';
|
import { isSupportShare } from '@/utility/navigator.js';
|
||||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -89,13 +90,12 @@ const props = defineProps<{
|
||||||
|
|
||||||
const post = ref<Misskey.entities.GalleryPost | null>(null);
|
const post = ref<Misskey.entities.GalleryPost | null>(null);
|
||||||
const error = ref<any>(null);
|
const error = ref<any>(null);
|
||||||
const otherPostsPagination = {
|
const otherPostsPaginator = markRaw(new Paginator('users/gallery/posts', {
|
||||||
endpoint: 'users/gallery/posts' as const,
|
|
||||||
limit: 6,
|
limit: 6,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: post.value.user.id,
|
userId: post.value.user.id,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
function fetchPost() {
|
function fetchPost() {
|
||||||
post.value = null;
|
post.value = null;
|
||||||
|
|
|
@ -115,7 +115,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'users'" class="_gaps_m">
|
<div v-else-if="tab === 'users'" class="_gaps_m">
|
||||||
<MkPagination v-slot="{ items }" :pagination="usersPagination">
|
<MkPagination v-slot="{ items }" :paginator="usersPaginator">
|
||||||
<div :class="$style.users">
|
<div :class="$style.users">
|
||||||
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${user.updatedAt ? dateString(user.updatedAt) : 'unknown'}`" :to="`/admin/user/${user.id}`">
|
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${user.updatedAt ? dateString(user.updatedAt) : 'unknown'}`" :to="`/admin/user/${user.id}`">
|
||||||
<MkUserCardMini :user="user"/>
|
<MkUserCardMini :user="user"/>
|
||||||
|
@ -132,10 +132,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, watch } from 'vue';
|
import { ref, computed, watch, markRaw } 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 { PagingCtx } from '@/composables/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';
|
||||||
|
@ -156,6 +155,7 @@ import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
|
import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
|
||||||
import { dateString } from '@/filters/date.js';
|
import { dateString } from '@/filters/date.js';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
host: string;
|
host: string;
|
||||||
|
@ -173,8 +173,7 @@ const isMediaSilenced = ref(false);
|
||||||
const faviconUrl = ref<string | null>(null);
|
const faviconUrl = ref<string | null>(null);
|
||||||
const moderationNote = ref('');
|
const moderationNote = ref('');
|
||||||
|
|
||||||
const usersPagination = {
|
const usersPaginator = iAmModerator ? markRaw(new Paginator('admin/show-users', {
|
||||||
endpoint: iAmModerator ? 'admin/show-users' : 'users',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
sort: '+updatedAt',
|
sort: '+updatedAt',
|
||||||
|
@ -182,7 +181,15 @@ const usersPagination = {
|
||||||
hostname: props.host,
|
hostname: props.host,
|
||||||
},
|
},
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
} satisfies PagingCtx<'admin/show-users' | 'users'>;
|
})) : markRaw(new Paginator('users', {
|
||||||
|
limit: 10,
|
||||||
|
params: {
|
||||||
|
sort: '+updatedAt',
|
||||||
|
state: 'all',
|
||||||
|
hostname: props.host,
|
||||||
|
},
|
||||||
|
offsetMode: true,
|
||||||
|
}));
|
||||||
|
|
||||||
if (iAmModerator) {
|
if (iAmModerator) {
|
||||||
watch(moderationNote, async () => {
|
watch(moderationNote, async () => {
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton inline primary rounded :disabled="currentInviteLimit !== null && currentInviteLimit <= 0" @click="create"><i class="ti ti-user-plus"></i> {{ i18n.ts.createInviteCode }}</MkButton>
|
<MkButton inline primary rounded :disabled="currentInviteLimit !== null && currentInviteLimit <= 0" @click="create"><i class="ti ti-user-plus"></i> {{ i18n.ts.createInviteCode }}</MkButton>
|
||||||
<div v-if="currentInviteLimit !== null">{{ i18n.tsx.createLimitRemaining({ limit: currentInviteLimit }) }}</div>
|
<div v-if="currentInviteLimit !== null">{{ i18n.tsx.createLimitRemaining({ limit: currentInviteLimit }) }}</div>
|
||||||
|
|
||||||
<MkPagination ref="pagingComponent" :pagination="pagination">
|
<MkPagination :paginator="paginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkInviteCode v-for="item in (items as Misskey.entities.InviteCode[])" :key="item.id" :invite="item" :onDeleted="deleted"/>
|
<MkInviteCode v-for="item in (items as Misskey.entities.InviteCode[])" :key="item.id" :invite="item" :onDeleted="deleted"/>
|
||||||
|
@ -27,9 +27,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, useTemplateRef } from 'vue';
|
import { computed, markRaw, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { PagingCtx } from '@/composables/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';
|
||||||
|
@ -39,16 +38,15 @@ import MkInviteCode from '@/components/MkInviteCode.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const pagingComponent = useTemplateRef('pagingComponent');
|
|
||||||
const currentInviteLimit = ref<null | number>(null);
|
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: PagingCtx = {
|
const paginator = markRaw(new Paginator('invite/list', {
|
||||||
endpoint: 'invite/list' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
|
|
||||||
const resetCycle = computed<null | string>(() => {
|
const resetCycle = computed<null | string>(() => {
|
||||||
if (!inviteLimitCycle) return null;
|
if (!inviteLimitCycle) return null;
|
||||||
|
@ -68,14 +66,12 @@ async function create() {
|
||||||
text: ticket.code,
|
text: ticket.code,
|
||||||
});
|
});
|
||||||
|
|
||||||
pagingComponent.value?.paginator.prepend(ticket);
|
paginator.prepend(ticket);
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleted(id: string) {
|
function deleted(id: string) {
|
||||||
if (pagingComponent.value) {
|
paginator.removeItem(id);
|
||||||
pagingComponent.value.paginator.removeItem(id);
|
|
||||||
}
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,44 +12,38 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-if="tab === 'my'" class="_gaps">
|
<div v-if="tab === 'my'" class="_gaps">
|
||||||
<MkButton primary rounded class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
<MkButton primary rounded class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
|
|
||||||
<MkPagination v-slot="{ items }" ref="pagingComponent" :pagination="pagination" class="_gaps" withControl>
|
<MkPagination v-slot="{ items }" :paginator="paginator" class="_gaps" withControl>
|
||||||
<MkClipPreview v-for="item in items" :key="item.id" :clip="item" :noUserInfo="true"/>
|
<MkClipPreview v-for="item in items" :key="item.id" :clip="item" :noUserInfo="true"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'favorites'" class="_gaps">
|
<div v-else-if="tab === 'favorites'">
|
||||||
<MkClipPreview v-for="item in favorites" :key="item.id" :clip="item"/>
|
<MkPagination v-slot="{ items }" :paginator="favoritesPaginator" class="_gaps" withControl>
|
||||||
|
<MkClipPreview v-for="item in items" :key="item.id" :clip="item" :noUserInfo="true"/>
|
||||||
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watch, ref, useTemplateRef, computed } from 'vue';
|
import { watch, ref, computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-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 MkClipPreview from '@/components/MkClipPreview.vue';
|
import MkClipPreview from '@/components/MkClipPreview.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { clipsCache } from '@/cache.js';
|
import { clipsCache } from '@/cache.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
const pagination = {
|
|
||||||
endpoint: 'clips/list' as const,
|
|
||||||
noPaging: true,
|
|
||||||
limit: 10,
|
|
||||||
};
|
|
||||||
|
|
||||||
const tab = ref('my');
|
const tab = ref('my');
|
||||||
|
|
||||||
const favorites = ref<Misskey.entities.Clip[] | null>(null);
|
const paginator = markRaw(new Paginator('clips/list', {
|
||||||
|
}));
|
||||||
|
|
||||||
const pagingComponent = useTemplateRef('pagingComponent');
|
const favoritesPaginator = markRaw(new Paginator('clips/my-favorites', {
|
||||||
|
}));
|
||||||
watch(tab, async () => {
|
|
||||||
favorites.value = await misskeyApi('clips/my-favorites');
|
|
||||||
});
|
|
||||||
|
|
||||||
async function create() {
|
async function create() {
|
||||||
const { canceled, result } = await os.form(i18n.ts.createNewClip, {
|
const { canceled, result } = await os.form(i18n.ts.createNewClip, {
|
||||||
|
@ -76,15 +70,15 @@ async function create() {
|
||||||
|
|
||||||
clipsCache.delete();
|
clipsCache.delete();
|
||||||
|
|
||||||
pagingComponent.value?.paginator.reload();
|
paginator.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClipCreated() {
|
function onClipCreated() {
|
||||||
pagingComponent.value?.paginator.reload();
|
paginator.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClipDeleted() {
|
function onClipDeleted() {
|
||||||
pagingComponent.value?.paginator.reload();
|
paginator.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
|
<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
|
||||||
|
|
||||||
<MkPagination ref="paginationEl" :pagination="membershipsPagination" withControl>
|
<MkPagination :paginator="membershipsPaginator" withControl>
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<div v-for="item in items" :key="item.id">
|
<div v-for="item in items" :key="item.id">
|
||||||
|
@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, useTemplateRef, watch } from 'vue';
|
import { computed, markRaw, ref, 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';
|
||||||
|
@ -69,6 +69,7 @@ import { ensureSignin } from '@/i.js';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { mainRouter } from '@/router.js';
|
import { mainRouter } from '@/router.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
@ -80,17 +81,15 @@ const props = defineProps<{
|
||||||
listId: string;
|
listId: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
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('');
|
||||||
const membershipsPagination = {
|
const membershipsPaginator = markRaw(new Paginator('users/lists/get-memberships', {
|
||||||
endpoint: 'users/lists/get-memberships' as const,
|
|
||||||
limit: 30,
|
limit: 30,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
listId: props.listId,
|
listId: props.listId,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
function fetchList() {
|
function fetchList() {
|
||||||
misskeyApi('users/lists/show', {
|
misskeyApi('users/lists/show', {
|
||||||
|
@ -109,7 +108,7 @@ function addUser() {
|
||||||
listId: list.value.id,
|
listId: list.value.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
paginationEl.value?.paginator.reload();
|
membershipsPaginator.reload();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -125,7 +124,7 @@ async function removeUser(item, ev) {
|
||||||
listId: list.value.id,
|
listId: list.value.id,
|
||||||
userId: item.userId,
|
userId: item.userId,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
paginationEl.value?.paginator.removeItem(item.id);
|
membershipsPaginator.removeItem(item.id);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
@ -147,7 +146,7 @@ async function showMembershipMenu(item, ev) {
|
||||||
userId: item.userId,
|
userId: item.userId,
|
||||||
withReplies,
|
withReplies,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
paginationEl.value!.paginator.updateItem(item.id, (old) => ({
|
membershipsPaginator.updateItem(item.id, (old) => ({
|
||||||
...old,
|
...old,
|
||||||
withReplies,
|
withReplies,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -9,7 +9,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">
|
||||||
<MkNotesTimeline :withControl="false" :pullToRefresh="false" class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
|
<MkNotesTimeline :withControl="false" :pullToRefresh="false" class="" :paginator="showNext === 'channel' ? nextChannelPaginator : nextUserPaginator" :noGap="true"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="_margin">
|
<div class="_margin">
|
||||||
|
@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showPrev" class="_margin">
|
<div v-if="showPrev" class="_margin">
|
||||||
<MkNotesTimeline :withControl="false" :pullToRefresh="false" class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
|
<MkNotesTimeline :withControl="false" :pullToRefresh="false" class="" :paginator="showPrev === 'channel' ? prevChannelPaginator : prevUserPaginator" :noGap="true"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkError v-else-if="error" @retry="fetchNote()"/>
|
<MkError v-else-if="error" @retry="fetchNote()"/>
|
||||||
|
@ -45,10 +45,9 @@ 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, markRaw } 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 { PagingCtx } from '@/composables/use-pagination.js';
|
|
||||||
import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
|
import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
|
||||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
|
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
|
||||||
|
@ -63,6 +62,7 @@ import { pleaseLogin } from '@/utility/please-login.js';
|
||||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||||
import { serverContext, assertServerContext } from '@/server-context.js';
|
import { serverContext, assertServerContext } from '@/server-context.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
// contextは非ログイン状態の情報しかないためログイン時は利用できない
|
// contextは非ログイン状態の情報しかないためログイン時は利用できない
|
||||||
const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
|
const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
|
||||||
|
@ -78,45 +78,41 @@ 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: PagingCtx = {
|
const prevUserPaginator = markRaw(new Paginator('users/notes', {
|
||||||
endpoint: 'users/notes',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
initialId: props.noteId,
|
initialId: props.noteId,
|
||||||
initialDirection: 'older',
|
initialDirection: 'older',
|
||||||
params: computed(() => note.value ? ({
|
computedParams: computed(() => note.value ? ({
|
||||||
userId: note.value.userId,
|
userId: note.value.userId,
|
||||||
}) : undefined),
|
}) : undefined),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const nextUserPagination: PagingCtx = {
|
const nextUserPaginator = markRaw(new Paginator('users/notes', {
|
||||||
endpoint: 'users/notes',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
initialId: props.noteId,
|
initialId: props.noteId,
|
||||||
initialDirection: 'newer',
|
initialDirection: 'newer',
|
||||||
params: computed(() => note.value ? ({
|
computedParams: computed(() => note.value ? ({
|
||||||
userId: note.value.userId,
|
userId: note.value.userId,
|
||||||
}) : undefined),
|
}) : undefined),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const prevChannelPagination: PagingCtx = {
|
const prevChannelPaginator = markRaw(new Paginator('channels/timeline', {
|
||||||
endpoint: 'channels/timeline',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
initialId: props.noteId,
|
initialId: props.noteId,
|
||||||
initialDirection: 'older',
|
initialDirection: 'older',
|
||||||
params: computed(() => note.value ? ({
|
computedParams: computed(() => note.value ? ({
|
||||||
channelId: note.value.channelId,
|
channelId: note.value.channelId,
|
||||||
}) : undefined),
|
}) : undefined),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const nextChannelPagination: PagingCtx = {
|
const nextChannelPaginator = markRaw(new Paginator('channels/timeline', {
|
||||||
endpoint: 'channels/timeline',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
initialId: props.noteId,
|
initialId: props.noteId,
|
||||||
initialDirection: 'newer',
|
initialDirection: 'newer',
|
||||||
params: computed(() => note.value ? ({
|
computedParams: computed(() => note.value ? ({
|
||||||
channelId: note.value.channelId,
|
channelId: note.value.channelId,
|
||||||
}) : undefined),
|
}) : undefined),
|
||||||
};
|
}));
|
||||||
|
|
||||||
function fetchNote() {
|
function fetchNote() {
|
||||||
showPrev.value = false;
|
showPrev.value = false;
|
||||||
|
|
|
@ -10,40 +10,39 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStreamingNotificationsTimeline :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'">
|
||||||
<MkNotesTimeline :pagination="mentionsPagination"/>
|
<MkNotesTimeline :paginator="mentionsPaginator"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'directNotes'">
|
<div v-else-if="tab === 'directNotes'">
|
||||||
<MkNotesTimeline :pagination="directNotesPagination"/>
|
<MkNotesTimeline :paginator="directNotesPaginator"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, markRaw, ref } from 'vue';
|
||||||
import { notificationTypes } from '@@/js/const.js';
|
import { notificationTypes } from '@@/js/const.js';
|
||||||
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
|
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
|
||||||
import MkNotesTimeline from '@/components/MkNotesTimeline.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';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const tab = ref('all');
|
const tab = ref('all');
|
||||||
const includeTypes = ref<string[] | null>(null);
|
const includeTypes = ref<string[] | null>(null);
|
||||||
const excludeTypes = computed(() => includeTypes.value ? notificationTypes.filter(t => !includeTypes.value.includes(t)) : null);
|
const excludeTypes = computed(() => includeTypes.value ? notificationTypes.filter(t => !includeTypes.value.includes(t)) : null);
|
||||||
|
|
||||||
const mentionsPagination = {
|
const mentionsPaginator = markRaw(new Paginator('notes/mentions', {
|
||||||
endpoint: 'notes/mentions' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
|
|
||||||
const directNotesPagination = {
|
const directNotesPaginator = markRaw(new Paginator('notes/mentions', {
|
||||||
endpoint: 'notes/mentions' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
visibility: 'specified',
|
visibility: 'specified',
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
|
|
||||||
function setFilter(ev) {
|
function setFilter(ev) {
|
||||||
const typeItems = notificationTypes.map(t => ({
|
const typeItems = notificationTypes.map(t => ({
|
||||||
|
|
|
@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkContainer :max-height="300" :foldable="true" class="other">
|
<MkContainer :max-height="300" :foldable="true" class="other">
|
||||||
<template #icon><i class="ti ti-clock"></i></template>
|
<template #icon><i class="ti ti-clock"></i></template>
|
||||||
<template #header>{{ i18n.ts.recentPosts }}</template>
|
<template #header>{{ i18n.ts.recentPosts }}</template>
|
||||||
<MkPagination v-slot="{items}" :pagination="otherPostsPagination" :class="$style.relatedPagesRoot" class="_gaps">
|
<MkPagination v-slot="{items}" :paginator="otherPostsPaginator" :class="$style.relatedPagesRoot" class="_gaps">
|
||||||
<MkPagePreview v-for="page in items" :key="page.id" :page="page" :class="$style.relatedPagesItem"/>
|
<MkPagePreview v-for="page in items" :key="page.id" :page="page" :class="$style.relatedPagesItem"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
|
@ -97,7 +97,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, ref, defineAsyncComponent } from 'vue';
|
import { computed, watch, ref, defineAsyncComponent, markRaw } 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';
|
||||||
|
@ -122,6 +122,7 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { getPluginHandlers } from '@/plugin.js';
|
import { getPluginHandlers } from '@/plugin.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -132,13 +133,12 @@ const props = defineProps<{
|
||||||
|
|
||||||
const page = ref<Misskey.entities.Page | null>(null);
|
const page = ref<Misskey.entities.Page | null>(null);
|
||||||
const error = ref<any>(null);
|
const error = ref<any>(null);
|
||||||
const otherPostsPagination = {
|
const otherPostsPaginator = markRaw(new Paginator('users/pages', {
|
||||||
endpoint: 'users/pages' as const,
|
|
||||||
limit: 6,
|
limit: 6,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: page.value.user.id,
|
userId: page.value.user.id,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
const path = computed(() => props.username + '/' + props.pageName);
|
const path = computed(() => props.username + '/' + props.pageName);
|
||||||
|
|
||||||
function fetchPage() {
|
function fetchPage() {
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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: 700px;">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<div v-if="tab === 'featured'">
|
<div v-if="tab === 'featured'">
|
||||||
<MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
|
<MkPagination v-slot="{items}" :paginator="featuredPagesPaginator">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
|
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<div v-else-if="tab === 'my'" class="_gaps">
|
<div v-else-if="tab === 'my'" class="_gaps">
|
||||||
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
||||||
<MkPagination v-slot="{items}" :pagination="myPagesPagination">
|
<MkPagination v-slot="{items}" :paginator="myPagesPaginator">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
|
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'liked'">
|
<div v-else-if="tab === 'liked'">
|
||||||
<MkPagination v-slot="{items}" :pagination="likedPagesPagination">
|
<MkPagination v-slot="{items}" :paginator="likedPagesPaginator">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
|
<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,30 +35,28 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, markRaw, ref } from 'vue';
|
||||||
import MkPagePreview from '@/components/MkPagePreview.vue';
|
import MkPagePreview from '@/components/MkPagePreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const tab = ref('featured');
|
const tab = ref('featured');
|
||||||
|
|
||||||
const featuredPagesPagination = {
|
const featuredPagesPaginator = markRaw(new Paginator('pages/featured', {
|
||||||
endpoint: 'pages/featured' as const,
|
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
};
|
}));
|
||||||
const myPagesPagination = {
|
const myPagesPaginator = markRaw(new Paginator('i/pages', {
|
||||||
endpoint: 'i/pages' as const,
|
|
||||||
limit: 5,
|
limit: 5,
|
||||||
};
|
}));
|
||||||
const likedPagesPagination = {
|
const likedPagesPaginator = markRaw(new Paginator('i/page-likes', {
|
||||||
endpoint: 'i/page-likes' as const,
|
|
||||||
limit: 5,
|
limit: 5,
|
||||||
};
|
}));
|
||||||
|
|
||||||
function create() {
|
function create() {
|
||||||
router.push('/pages/new');
|
router.push('/pages/new');
|
||||||
|
|
|
@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<MkFolder v-if="$i" :defaultOpen="true">
|
<MkFolder v-if="$i" :defaultOpen="true">
|
||||||
<template #label>{{ i18n.ts._reversi.myGames }}</template>
|
<template #label>{{ i18n.ts._reversi.myGames }}</template>
|
||||||
<MkPagination :pagination="myGamesPagination" :disableAutoLoad="true">
|
<MkPagination :paginator="myGamesPaginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div :class="$style.gamePreviews">
|
<div :class="$style.gamePreviews">
|
||||||
<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`">
|
<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`">
|
||||||
|
@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<MkFolder :defaultOpen="true">
|
<MkFolder :defaultOpen="true">
|
||||||
<template #label>{{ i18n.ts._reversi.allGames }}</template>
|
<template #label>{{ i18n.ts._reversi.allGames }}</template>
|
||||||
<MkPagination :pagination="gamesPagination" :disableAutoLoad="true">
|
<MkPagination :paginator="gamesPaginator">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
<div :class="$style.gamePreviews">
|
<div :class="$style.gamePreviews">
|
||||||
<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`">
|
<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`">
|
||||||
|
@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onDeactivated, onMounted, onUnmounted, ref } from 'vue';
|
import { markRaw, onDeactivated, onMounted, onUnmounted, 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 { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
@ -120,19 +120,18 @@ import { useRouter } from '@/router.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { pleaseLogin } from '@/utility/please-login.js';
|
import { pleaseLogin } from '@/utility/please-login.js';
|
||||||
import * as sound from '@/utility/sound.js';
|
import * as sound from '@/utility/sound.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const myGamesPagination = {
|
const myGamesPaginator = markRaw(new Paginator('reversi/games', {
|
||||||
endpoint: 'reversi/games' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
my: true,
|
my: true,
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
|
|
||||||
const gamesPagination = {
|
const gamesPaginator = markRaw(new Paginator('reversi/games', {
|
||||||
endpoint: 'reversi/games' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-else-if="tab === 'users'" class="_spacer" style="--MI_SPACER-w: 1200px;">
|
<div v-else-if="tab === 'users'" class="_spacer" style="--MI_SPACER-w: 1200px;">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<div v-if="role">{{ role.description }}</div>
|
<div v-if="role">{{ role.description }}</div>
|
||||||
<MkUserList v-if="visible" :pagination="users" :extractor="(item) => item.user"/>
|
<MkUserList v-if="visible" :paginator="usersPaginator" :extractor="(item) => item.user"/>
|
||||||
<MkResult v-else-if="!visible" type="empty" :text="i18n.ts.nothing"/>
|
<MkResult v-else-if="!visible" type="empty" :text="i18n.ts.nothing"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,13 +23,14 @@ 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, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
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 MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
roleId: string;
|
roleId: string;
|
||||||
|
@ -60,12 +61,11 @@ watch(() => props.roleId, () => {
|
||||||
});
|
});
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
const users = computed(() => ({
|
const usersPaginator = markRaw(new Paginator('roles/users', {
|
||||||
endpoint: 'roles/users' as const,
|
|
||||||
limit: 30,
|
limit: 30,
|
||||||
params: {
|
computedParams: computed(() => ({
|
||||||
roleId: props.roleId,
|
roleId: props.roleId,
|
||||||
},
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const headerTabs = computed(() => [{
|
const headerTabs = computed(() => [{
|
||||||
|
|
|
@ -103,19 +103,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkFoldableSection v-if="notePagination">
|
<MkFoldableSection v-if="paginator">
|
||||||
<template #header>{{ i18n.ts.searchResult }}</template>
|
<template #header>{{ i18n.ts.searchResult }}</template>
|
||||||
<MkNotesTimeline :key="`searchNotes:${key}`" :pagination="notePagination"/>
|
<MkNotesTimeline :key="`searchNotes:${key}`" :paginator="paginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, shallowRef, toRef } from 'vue';
|
import { computed, markRaw, ref, shallowRef, toRef } from 'vue';
|
||||||
import type * as Misskey from 'misskey-js';
|
|
||||||
import type { PagingCtx } from '@/composables/use-pagination.js';
|
|
||||||
import { $i } from '@/i.js';
|
|
||||||
import { host as localHost } from '@@/js/config.js';
|
import { host as localHost } from '@@/js/config.js';
|
||||||
|
import type * as Misskey from 'misskey-js';
|
||||||
|
import { $i } from '@/i.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -128,6 +127,7 @@ import MkInput from '@/components/MkInput.vue';
|
||||||
import MkNotesTimeline from '@/components/MkNotesTimeline.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';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
query?: string;
|
query?: string;
|
||||||
|
@ -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<PagingCtx<'notes/search'>>();
|
const paginator = shallowRef<Paginator<'notes/search'> | null>(null);
|
||||||
|
|
||||||
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);
|
||||||
|
@ -299,13 +299,12 @@ async function search() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notePagination.value = {
|
paginator.value = markRaw(new Paginator('notes/search', {
|
||||||
endpoint: 'notes/search',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
...searchParams.value,
|
...searchParams.value,
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
|
|
||||||
key.value++;
|
key.value++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,17 +17,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
|
<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkFoldableSection v-if="userPagination">
|
<MkFoldableSection v-if="paginator">
|
||||||
<template #header>{{ i18n.ts.searchResult }}</template>
|
<template #header>{{ i18n.ts.searchResult }}</template>
|
||||||
<MkUserList :key="`searchUsers:${key}`" :pagination="userPagination"/>
|
<MkUserList :key="`searchUsers:${key}`" :paginator="paginator"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, toRef } from 'vue';
|
import { markRaw, ref, shallowRef, toRef } from 'vue';
|
||||||
import type { Endpoints } from 'misskey-js';
|
import type { Endpoints } from 'misskey-js';
|
||||||
import type { PagingCtx } from '@/composables/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';
|
||||||
|
@ -38,6 +37,7 @@ import * as os from '@/os.js';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
query?: string,
|
query?: string,
|
||||||
|
@ -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<PagingCtx<'users/search'>>();
|
const paginator = shallowRef<Paginator<'users/search'> | null>(null);
|
||||||
|
|
||||||
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);
|
||||||
|
@ -112,15 +112,14 @@ async function search() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userPagination.value = {
|
paginator.value = markRaw(new Paginator('users/search', {
|
||||||
endpoint: 'users/search',
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
params: {
|
params: {
|
||||||
query: query,
|
query: query,
|
||||||
origin: instance.federation === 'none' ? 'local' : searchOrigin.value,
|
origin: instance.federation === 'none' ? 'local' : searchOrigin.value,
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
|
|
||||||
key.value++;
|
key.value++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<FormPagination ref="list" :pagination="pagination">
|
<MkPagination :paginator="paginator">
|
||||||
<template #empty><MkResult type="empty"/></template>
|
<template #empty><MkResult type="empty"/></template>
|
||||||
<template #default="{items}">
|
<template #default="{items}">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
|
@ -44,35 +44,33 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</FormPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, useTemplateRef } from 'vue';
|
import { ref, computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import FormPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
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 MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const list = useTemplateRef('list');
|
const paginator = markRaw(new Paginator('i/apps', {
|
||||||
|
|
||||||
const pagination = {
|
|
||||||
endpoint: 'i/apps' as const,
|
|
||||||
limit: 100,
|
limit: 100,
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
params: {
|
params: {
|
||||||
sort: '+lastUsedAt',
|
sort: '+lastUsedAt',
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
|
|
||||||
function revoke(token) {
|
function revoke(token) {
|
||||||
misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => {
|
misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => {
|
||||||
list.value?.paginator.reload();
|
paginator.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkFolder :defaultOpen="true">
|
<MkFolder :defaultOpen="true">
|
||||||
<template #label>{{ i18n.ts.manage }}</template>
|
<template #label>{{ i18n.ts.manage }}</template>
|
||||||
|
|
||||||
<MkPagination :pagination="pagination" withControl>
|
<MkPagination :paginator="paginator" withControl>
|
||||||
<template #default="{items}">
|
<template #default="{items}">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`">
|
<FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`">
|
||||||
|
@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, defineAsyncComponent } from 'vue';
|
import { computed, ref, defineAsyncComponent, markRaw } from 'vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
import FormLink from '@/components/form/link.vue';
|
import FormLink from '@/components/form/link.vue';
|
||||||
|
@ -72,14 +72,14 @@ import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const isDesktop = ref(window.innerWidth >= 1100);
|
const isDesktop = ref(window.innerWidth >= 1100);
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('i/webhooks/list', {
|
||||||
endpoint: 'i/webhooks/list' as const,
|
|
||||||
limit: 100,
|
limit: 100,
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
};
|
}));
|
||||||
|
|
||||||
async function generateToken() {
|
async function generateToken() {
|
||||||
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkTokenGenerateWindow.vue').then(x => x.default), {}, {
|
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkTokenGenerateWindow.vue').then(x => x.default), {}, {
|
||||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option v-for="x in sortOptions" :key="x.value" :value="x.value">{{ x.displayName }}</option>
|
<option v-for="x in sortOptions" :key="x.value" :value="x.value">{{ x.displayName }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<div v-if="!fetching">
|
<div v-if="!fetching">
|
||||||
<MkPagination v-slot="{items}" :pagination="pagination">
|
<MkPagination v-slot="{items}" :paginator="paginator">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<div
|
<div
|
||||||
v-for="file in items" :key="file.id"
|
v-for="file in items" :key="file.id"
|
||||||
|
@ -48,9 +48,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, markRaw, ref, watch } from 'vue';
|
||||||
import type { StyleValue } from 'vue';
|
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
|
import type { StyleValue } from '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 MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
@ -60,13 +60,13 @@ import bytes from '@/filters/bytes.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
|
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const sortMode = ref('+size');
|
const sortMode = ref('+size');
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('drive/files', {
|
||||||
endpoint: 'drive/files' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({ sort: sortMode.value })),
|
computedParams: computed(() => ({ sort: sortMode.value })),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const sortOptions = [
|
const sortOptions = [
|
||||||
{ value: 'sizeDesc', displayName: i18n.ts._drivecleaner.orderBySizeDesc },
|
{ value: 'sizeDesc', displayName: i18n.ts._drivecleaner.orderBySizeDesc },
|
||||||
|
|
|
@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #icon><i class="ti ti-repeat-off"></i></template>
|
<template #icon><i class="ti ti-repeat-off"></i></template>
|
||||||
<template #label><SearchLabel>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</SearchLabel></template>
|
<template #label><SearchLabel>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</SearchLabel></template>
|
||||||
|
|
||||||
<MkPagination :pagination="renoteMutingPagination" withControl>
|
<MkPagination :paginator="renoteMutingPaginator" withControl>
|
||||||
<template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template>
|
<template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template>
|
||||||
|
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
|
@ -111,7 +111,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #icon><i class="ti ti-eye-off"></i></template>
|
<template #icon><i class="ti ti-eye-off"></i></template>
|
||||||
<template #label>{{ i18n.ts.mutedUsers }}</template>
|
<template #label>{{ i18n.ts.mutedUsers }}</template>
|
||||||
|
|
||||||
<MkPagination :pagination="mutingPagination" withControl>
|
<MkPagination :paginator="mutingPaginator" withControl>
|
||||||
<template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template>
|
<template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template>
|
||||||
|
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
|
@ -144,7 +144,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #icon><i class="ti ti-ban"></i></template>
|
<template #icon><i class="ti ti-ban"></i></template>
|
||||||
<template #label>{{ i18n.ts.blockedUsers }}</template>
|
<template #label>{{ i18n.ts.blockedUsers }}</template>
|
||||||
|
|
||||||
<MkPagination :pagination="blockingPagination" withControl>
|
<MkPagination :paginator="blockingPaginator" withControl>
|
||||||
<template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template>
|
<template #empty><MkResult type="empty" :text="i18n.ts.noUsers"/></template>
|
||||||
|
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
|
@ -174,7 +174,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, watch } from 'vue';
|
import { ref, computed, watch, markRaw } from 'vue';
|
||||||
import XEmojiMute from './mute-block.emoji-mute.vue';
|
import XEmojiMute from './mute-block.emoji-mute.vue';
|
||||||
import XInstanceMute from './mute-block.instance-mute.vue';
|
import XInstanceMute from './mute-block.instance-mute.vue';
|
||||||
import XWordMute from './mute-block.word-mute.vue';
|
import XWordMute from './mute-block.word-mute.vue';
|
||||||
|
@ -192,23 +192,21 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import { reloadAsk } from '@/utility/reload-ask.js';
|
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
const renoteMutingPagination = {
|
const renoteMutingPaginator = markRaw(new Paginator('renote-mute/list', {
|
||||||
endpoint: 'renote-mute/list' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
|
|
||||||
const mutingPagination = {
|
const mutingPaginator = markRaw(new Paginator('mute/list', {
|
||||||
endpoint: 'mute/list' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
|
|
||||||
const blockingPagination = {
|
const blockingPaginator = markRaw(new Paginator('blocking/list', {
|
||||||
endpoint: 'blocking/list' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
}));
|
||||||
|
|
||||||
const expandedRenoteMuteItems = ref([]);
|
const expandedRenoteMuteItems = ref([]);
|
||||||
const expandedMuteItems = ref([]);
|
const expandedMuteItems = ref([]);
|
||||||
|
|
|
@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ i18n.ts.signinHistory }}</template>
|
<template #label>{{ i18n.ts.signinHistory }}</template>
|
||||||
<MkPagination :pagination="pagination" disableAutoLoad withControl>
|
<MkPagination :paginator="paginator" withControl>
|
||||||
<template #default="{items}">
|
<template #default="{items}">
|
||||||
<div>
|
<div>
|
||||||
<div v-for="item in items" :key="item.id" v-panel class="timnmucd">
|
<div v-for="item in items" :key="item.id" v-panel class="timnmucd">
|
||||||
|
@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed, markRaw } from 'vue';
|
||||||
import X2fa from './2fa.vue';
|
import X2fa from './2fa.vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
import FormSlot from '@/components/form/slot.vue';
|
import FormSlot from '@/components/form/slot.vue';
|
||||||
|
@ -64,11 +64,11 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('i/signin-history', {
|
||||||
endpoint: 'i/signin-history' as const,
|
|
||||||
limit: 5,
|
limit: 5,
|
||||||
};
|
}));
|
||||||
|
|
||||||
async function change() {
|
async function change() {
|
||||||
const { canceled: canceled2, result: newPassword } = await os.inputText({
|
const { canceled: canceled2, result: newPassword } = await os.inputText({
|
||||||
|
|
|
@ -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;">
|
||||||
<MkNotesTimeline ref="tlComponent" class="" :pagination="pagination"/>
|
<MkNotesTimeline :paginator="paginator"/>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="$i" #footer>
|
<template v-if="$i" #footer>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, useTemplateRef } from 'vue';
|
import { computed, markRaw, ref } from 'vue';
|
||||||
import MkNotesTimeline from '@/components/MkNotesTimeline.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';
|
||||||
|
@ -28,20 +28,18 @@ import { $i } from '@/i.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { genEmbedCode } from '@/utility/get-embed-code.js';
|
import { genEmbedCode } from '@/utility/get-embed-code.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tag: string;
|
tag: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('notes/search-by-tag', {
|
||||||
endpoint: 'notes/search-by-tag' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
tag: props.tag,
|
tag: props.tag,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const tlComponent = useTemplateRef('tlComponent');
|
|
||||||
|
|
||||||
async function post() {
|
async function post() {
|
||||||
store.set('postFormHashtags', props.tag);
|
store.set('postFormHashtags', props.tag);
|
||||||
|
@ -49,7 +47,7 @@ async function post() {
|
||||||
await os.post();
|
await os.post();
|
||||||
store.set('postFormHashtags', '');
|
store.set('postFormHashtags', '');
|
||||||
store.set('postFormWithHashtags', false);
|
store.set('postFormWithHashtags', false);
|
||||||
tlComponent.value?.reload();
|
paginator.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed(() => [{
|
||||||
|
|
|
@ -7,29 +7,29 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<PageWithHeader>
|
<PageWithHeader>
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 1200px;">
|
<div class="_spacer" style="--MI_SPACER-w: 1200px;">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkUserList :pagination="tagUsers"/>
|
<MkUserList :paginator="paginator"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PageWithHeader>
|
</PageWithHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed, markRaw } from 'vue';
|
||||||
import MkUserList from '@/components/MkUserList.vue';
|
import MkUserList from '@/components/MkUserList.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tag: string;
|
tag: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tagUsers = computed(() => ({
|
const paginator = markRaw(new Paginator('hashtags/users', {
|
||||||
endpoint: 'hashtags/users' as const,
|
|
||||||
limit: 30,
|
limit: 30,
|
||||||
params: {
|
computedParams: computed(() => ({
|
||||||
tag: props.tag,
|
tag: props.tag,
|
||||||
origin: 'combined',
|
origin: 'combined',
|
||||||
sort: '+follower',
|
sort: '+follower',
|
||||||
},
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
definePage(() => ({
|
definePage(() => ({
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<div>
|
<div>
|
||||||
<MkPagination v-slot="{items}" :pagination="pagination" withControl>
|
<MkPagination v-slot="{items}" :paginator="paginator" withControl>
|
||||||
<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" :class="$style.item" class="_panel _margin">
|
<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" :class="$style.item" class="_panel _margin">
|
||||||
<b>{{ item.name }}</b>
|
<b>{{ item.name }}</b>
|
||||||
<div v-if="item.description" :class="$style.description">{{ item.description }}</div>
|
<div v-if="item.description" :class="$style.description">{{ item.description }}</div>
|
||||||
|
@ -17,21 +17,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.User;
|
user: Misskey.entities.User;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('users/clips', {
|
||||||
endpoint: 'users/clips' as const,
|
|
||||||
limit: 20,
|
limit: 20,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 1100px;">
|
<div class="_spacer" style="--MI_SPACER-w: 1100px;">
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<MkPagination v-slot="{items}" :pagination="pagination" withControl>
|
<MkPagination v-slot="{items}" :paginator="paginator" withControl>
|
||||||
<div :class="$style.stream">
|
<div :class="$style.stream">
|
||||||
<MkNoteMediaGrid v-for="note in items" :note="note" square/>
|
<MkNoteMediaGrid v-for="note in items" :note="note" square/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,24 +16,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
||||||
import MkNoteMediaGrid from '@/components/MkNoteMediaGrid.vue';
|
import MkNoteMediaGrid from '@/components/MkNoteMediaGrid.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.UserDetailed;
|
user: Misskey.entities.UserDetailed;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('users/notes', {
|
||||||
endpoint: 'users/notes' as const,
|
|
||||||
limit: 15,
|
limit: 15,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
withFiles: true,
|
withFiles: true,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -5,27 +5,27 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<MkPagination v-slot="{items}" :pagination="pagination" withControl>
|
<MkPagination v-slot="{items}" :paginator="paginator" withControl>
|
||||||
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash" class="_margin"/>
|
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash" class="_margin"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkFlashPreview from '@/components/MkFlashPreview.vue';
|
import MkFlashPreview from '@/components/MkFlashPreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.User;
|
user: Misskey.entities.User;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('users/flashs', {
|
||||||
endpoint: 'users/flashs' as const,
|
|
||||||
limit: 20,
|
limit: 20,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<MkPagination v-slot="{items}" :pagination="type === 'following' ? followingPagination : followersPagination" withControl>
|
<MkPagination v-slot="{items}" :paginator="type === 'following' ? followingPaginator : followersPaginator" withControl>
|
||||||
<div :class="$style.users">
|
<div :class="$style.users">
|
||||||
<MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" :user="user"/>
|
<MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" :user="user"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,31 +14,30 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-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 { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.User;
|
user: Misskey.entities.User;
|
||||||
type: 'following' | 'followers';
|
type: 'following' | 'followers';
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const followingPagination = {
|
const followingPaginator = markRaw(new Paginator('users/following', {
|
||||||
endpoint: 'users/following' as const,
|
|
||||||
limit: 20,
|
limit: 20,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
|
|
||||||
const followersPagination = {
|
const followersPaginator = markRaw(new Paginator('users/followers', {
|
||||||
endpoint: 'users/followers' as const,
|
|
||||||
limit: 20,
|
limit: 20,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<MkPagination v-slot="{items}" :pagination="pagination" withControl>
|
<MkPagination v-slot="{items}" :paginator="paginator" withControl>
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,23 +14,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
|
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
user: Misskey.entities.User;
|
user: Misskey.entities.User;
|
||||||
}>(), {
|
}>(), {
|
||||||
});
|
});
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('users/gallery/posts', {
|
||||||
endpoint: 'users/gallery/posts' as const,
|
|
||||||
limit: 6,
|
limit: 6,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -13,16 +13,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="files">{{ i18n.ts.withFiles }}</option>
|
<option value="files">{{ i18n.ts.withFiles }}</option>
|
||||||
</MkTab>
|
</MkTab>
|
||||||
</template>
|
</template>
|
||||||
<MkNotesTimeline :key="tab" :noGap="true" :pagination="pagination" :pullToRefresh="false" :class="$style.tl"/>
|
<MkNotesTimeline v-if="tab === 'featured'" :noGap="true" :paginator="featuredPaginator" :pullToRefresh="false" :class="$style.tl"/>
|
||||||
|
<MkNotesTimeline v-else :noGap="true" :paginator="notesPaginator" :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, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkNotesTimeline from '@/components/MkNotesTimeline.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';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.UserDetailed;
|
user: Misskey.entities.UserDetailed;
|
||||||
|
@ -30,23 +32,23 @@ const props = defineProps<{
|
||||||
|
|
||||||
const tab = ref<string>('all');
|
const tab = ref<string>('all');
|
||||||
|
|
||||||
const pagination = computed(() => tab.value === 'featured' ? {
|
const featuredPaginator = markRaw(new Paginator('users/featured-notes', {
|
||||||
endpoint: 'users/featured-notes' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
},
|
},
|
||||||
} : {
|
}));
|
||||||
endpoint: 'users/notes' as const,
|
|
||||||
|
const notesPaginator = markRaw(new Paginator('users/notes', {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
computedParams: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
withRenotes: tab.value === 'all',
|
withRenotes: tab.value === 'all',
|
||||||
withReplies: tab.value === 'all',
|
withReplies: tab.value === 'all',
|
||||||
withChannelNotes: tab.value === 'all',
|
withChannelNotes: tab.value === 'all',
|
||||||
withFiles: tab.value === 'files',
|
withFiles: tab.value === 'files',
|
||||||
},
|
})),
|
||||||
});
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<div>
|
<div>
|
||||||
<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" withControl>
|
<MkPagination v-slot="{items}" :paginator="paginator" withControl>
|
||||||
<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/list/${ list.id }`">
|
<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/list/${ list.id }`">
|
||||||
<div>{{ list.name }}</div>
|
<div>{{ list.name }}</div>
|
||||||
<MkAvatars :userIds="list.userIds"/>
|
<MkAvatars :userIds="list.userIds"/>
|
||||||
|
@ -19,24 +19,24 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from 'vue';
|
import { markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkStickyContainer from '@/components/global/MkStickyContainer.vue';
|
import MkStickyContainer from '@/components/global/MkStickyContainer.vue';
|
||||||
import MkAvatars from '@/components/MkAvatars.vue';
|
import MkAvatars from '@/components/MkAvatars.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.UserDetailed;
|
user: Misskey.entities.UserDetailed;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('users/lists/list', {
|
||||||
endpoint: 'users/lists/list' as const,
|
|
||||||
noPaging: true,
|
noPaging: true,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -15,18 +15,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="files">{{ i18n.ts.withFiles }}</option>
|
<option value="files">{{ i18n.ts.withFiles }}</option>
|
||||||
</MkTab>
|
</MkTab>
|
||||||
</template>
|
</template>
|
||||||
<MkNotesTimeline :key="tab" :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
<MkNotesTimeline v-if="tab === 'featured'" :noGap="true" :paginator="featuredPaginator" :class="$style.tl"/>
|
||||||
|
<MkNotesTimeline v-else :noGap="true" :paginator="notesPaginator" :class="$style.tl"/>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkNotesTimeline from '@/components/MkNotesTimeline.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';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.UserDetailed;
|
user: Misskey.entities.UserDetailed;
|
||||||
|
@ -34,23 +36,23 @@ const props = defineProps<{
|
||||||
|
|
||||||
const tab = ref<string>('all');
|
const tab = ref<string>('all');
|
||||||
|
|
||||||
const pagination = computed(() => tab.value === 'featured' ? {
|
const featuredPaginator = markRaw(new Paginator('users/featured-notes', {
|
||||||
endpoint: 'users/featured-notes' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
},
|
},
|
||||||
} : {
|
}));
|
||||||
endpoint: 'users/notes' as const,
|
|
||||||
|
const notesPaginator = markRaw(new Paginator('users/notes', {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
computedParams: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
withRenotes: tab.value === 'all',
|
withRenotes: tab.value === 'all',
|
||||||
withReplies: tab.value === 'all',
|
withReplies: tab.value === 'all',
|
||||||
withChannelNotes: tab.value === 'all',
|
withChannelNotes: tab.value === 'all',
|
||||||
withFiles: tab.value === 'files',
|
withFiles: tab.value === 'files',
|
||||||
},
|
})),
|
||||||
});
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -5,27 +5,27 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<MkPagination v-slot="{items}" :pagination="pagination" withControl>
|
<MkPagination v-slot="{items}" :paginator="paginator" withControl>
|
||||||
<MkPagePreview v-for="page in items" :key="page.id" :page="page" class="_margin"/>
|
<MkPagePreview v-for="page in items" :key="page.id" :page="page" class="_margin"/>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkPagePreview from '@/components/MkPagePreview.vue';
|
import MkPagePreview from '@/components/MkPagePreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.User;
|
user: Misskey.entities.User;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('users/pages', {
|
||||||
endpoint: 'users/pages' as const,
|
|
||||||
limit: 20,
|
limit: 20,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||||
<MkPagination v-slot="{items}" :pagination="pagination">
|
<MkPagination v-slot="{items}" :paginator="paginator">
|
||||||
<div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="_panel _margin">
|
<div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="_panel _margin">
|
||||||
<div :class="$style.header">
|
<div :class="$style.header">
|
||||||
<MkAvatar :class="$style.avatar" :user="user"/>
|
<MkAvatar :class="$style.avatar" :user="user"/>
|
||||||
|
@ -19,23 +19,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed, markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkNote from '@/components/MkNote.vue';
|
import MkNote from '@/components/MkNote.vue';
|
||||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.User;
|
user: Misskey.entities.User;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('users/reactions', {
|
||||||
endpoint: 'users/reactions' as const,
|
|
||||||
limit: 20,
|
limit: 20,
|
||||||
params: computed(() => ({
|
computedParams: computed(() => ({
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
})),
|
})),
|
||||||
};
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -7,35 +7,33 @@ 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>
|
||||||
|
|
||||||
<MkNotesTimeline ref="tlComponent" :pagination="pagination"/>
|
<MkNotesTimeline :paginator="paginator"/>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, useTemplateRef } from 'vue';
|
import { markRaw, ref } 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 MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
column: Column;
|
column: Column;
|
||||||
isStacked: boolean;
|
isStacked: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const pagination = {
|
const paginator = markRaw(new Paginator('notes/mentions', {
|
||||||
endpoint: 'notes/mentions' as const,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
visibility: 'specified',
|
visibility: 'specified',
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
|
|
||||||
const tlComponent = useTemplateRef('tlComponent');
|
|
||||||
|
|
||||||
function reloadTimeline() {
|
function reloadTimeline() {
|
||||||
return new Promise<void>((res) => {
|
return new Promise<void>((res) => {
|
||||||
tlComponent.value?.reload().then(() => {
|
paginator.reload().then(() => {
|
||||||
res();
|
res();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,34 +7,32 @@ 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>
|
||||||
|
|
||||||
<MkNotesTimeline ref="tlComponent" :pagination="pagination"/>
|
<MkNotesTimeline :paginator="paginator"/>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, useTemplateRef } from 'vue';
|
import { markRaw, ref } 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 { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||||
|
import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
column: Column;
|
column: Column;
|
||||||
isStacked: boolean;
|
isStacked: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tlComponent = useTemplateRef('tlComponent');
|
const paginator = markRaw(new Paginator('notes/mentions', {
|
||||||
|
limit: 10,
|
||||||
|
}));
|
||||||
|
|
||||||
function reloadTimeline() {
|
function reloadTimeline() {
|
||||||
return new Promise<void>((res) => {
|
return new Promise<void>((res) => {
|
||||||
tlComponent.value?.reload().then(() => {
|
paginator.reload().then(() => {
|
||||||
res();
|
res();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const pagination = {
|
|
||||||
endpoint: 'notes/mentions' as const,
|
|
||||||
limit: 10,
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
322
packages/frontend/src/utility/paginator.ts
Normal file
322
packages/frontend/src/utility/paginator.ts
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ref, shallowRef, triggerRef } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import type { ComputedRef, DeepReadonly, Ref, ShallowRef } from 'vue';
|
||||||
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
|
||||||
|
const MAX_ITEMS = 30;
|
||||||
|
const MAX_QUEUE_ITEMS = 100;
|
||||||
|
const FIRST_FETCH_LIMIT = 15;
|
||||||
|
const SECOND_FETCH_LIMIT = 30;
|
||||||
|
|
||||||
|
export type MisskeyEntity = {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
_shouldInsertAd_?: boolean;
|
||||||
|
[x: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Paginator<Endpoint extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, T extends { id: string; } = (Misskey.Endpoints[Endpoint]['res'] extends (infer I)[] ? I extends { id: string } ? I : { id: string } : { id: string })> {
|
||||||
|
/**
|
||||||
|
* 外部から直接操作しないでください
|
||||||
|
*/
|
||||||
|
public items: ShallowRef<T[]> | Ref<T[]>;
|
||||||
|
|
||||||
|
public queuedAheadItemsCount = ref(0);
|
||||||
|
public fetching = ref(true);
|
||||||
|
public fetchingOlder = ref(false);
|
||||||
|
public fetchingNewer = ref(false);
|
||||||
|
public canFetchOlder = ref(false);
|
||||||
|
public canSearch = false;
|
||||||
|
public error = ref(false);
|
||||||
|
private endpoint: Endpoint;
|
||||||
|
private limit: number;
|
||||||
|
private params: Misskey.Endpoints[Endpoint]['req'] | (() => Misskey.Endpoints[Endpoint]['req']);
|
||||||
|
public computedParams: ComputedRef<Misskey.Endpoints[Endpoint]['req']> | null;
|
||||||
|
public initialId: MisskeyEntity['id'] | null = null;
|
||||||
|
public initialDate: number | null = null;
|
||||||
|
public initialDirection: 'newer' | 'older';
|
||||||
|
private offsetMode: boolean;
|
||||||
|
public noPaging: boolean;
|
||||||
|
public searchQuery = ref<null | string>('');
|
||||||
|
private searchParamName: string;
|
||||||
|
private canFetchDetection: 'safe' | 'limit' | null = null;
|
||||||
|
private aheadQueue: T[] = [];
|
||||||
|
private useShallowRef: boolean;
|
||||||
|
|
||||||
|
// 配列内の要素をどのような順序で並べるか
|
||||||
|
// newest: 新しいものが先頭 (default)
|
||||||
|
// oldest: 古いものが先頭
|
||||||
|
// NOTE: このようなプロパティを用意してこっち側で並びを管理せずに、Setで持っておき参照者側が好きに並び変えるような設計の方がすっきりしそうなものの、Vueのレンダリングのたびに並び替え処理が発生することになったりしそうでパフォーマンス上の懸念がある
|
||||||
|
public order: Ref<'newest' | 'oldest'>;
|
||||||
|
|
||||||
|
constructor(endpoint: Endpoint, props: {
|
||||||
|
limit?: number;
|
||||||
|
params?: Misskey.Endpoints[Endpoint]['req'] | (() => Misskey.Endpoints[Endpoint]['req']);
|
||||||
|
computedParams?: ComputedRef<Misskey.Endpoints[Endpoint]['req']>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 検索APIのような、ページング不可なエンドポイントを利用する場合
|
||||||
|
* (そのようなAPIをこの関数で使うのは若干矛盾してるけど)
|
||||||
|
*/
|
||||||
|
noPaging?: boolean;
|
||||||
|
|
||||||
|
offsetMode?: boolean;
|
||||||
|
|
||||||
|
initialId?: MisskeyEntity['id'];
|
||||||
|
initialDate?: number | null;
|
||||||
|
initialDirection?: 'newer' | 'older';
|
||||||
|
order?: 'newest' | 'oldest';
|
||||||
|
|
||||||
|
// 一部のAPIはさらに遡れる場合でもパフォーマンス上の理由でlimit以下の結果を返す場合があり、その場合はsafe、それ以外はlimitにすることを推奨
|
||||||
|
canFetchDetection?: 'safe' | 'limit';
|
||||||
|
|
||||||
|
useShallowRef?: boolean;
|
||||||
|
|
||||||
|
canSearch?: boolean;
|
||||||
|
searchParamName?: keyof Misskey.Endpoints[Endpoint]['req'];
|
||||||
|
}) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
this.useShallowRef = props.useShallowRef ?? false;
|
||||||
|
this.items = this.useShallowRef ? shallowRef([] as T[]) : ref([] as T[]);
|
||||||
|
this.limit = props.limit ?? FIRST_FETCH_LIMIT;
|
||||||
|
this.params = props.params ?? {};
|
||||||
|
this.computedParams = props.computedParams ?? null;
|
||||||
|
this.order = ref(props.order ?? 'newest');
|
||||||
|
this.initialId = props.initialId ?? null;
|
||||||
|
this.initialDate = props.initialDate ?? null;
|
||||||
|
this.initialDirection = props.initialDirection ?? 'older';
|
||||||
|
this.canFetchDetection = props.canFetchDetection ?? null;
|
||||||
|
this.noPaging = props.noPaging ?? false;
|
||||||
|
this.offsetMode = props.offsetMode ?? false;
|
||||||
|
this.canSearch = props.canSearch ?? false;
|
||||||
|
this.searchParamName = props.searchParamName ?? 'search';
|
||||||
|
|
||||||
|
this.getNewestId = this.getNewestId.bind(this);
|
||||||
|
this.getOldestId = this.getOldestId.bind(this);
|
||||||
|
this.init = this.init.bind(this);
|
||||||
|
this.reload = this.reload.bind(this);
|
||||||
|
this.fetchOlder = this.fetchOlder.bind(this);
|
||||||
|
this.fetchNewer = this.fetchNewer.bind(this);
|
||||||
|
this.unshiftItems = this.unshiftItems.bind(this);
|
||||||
|
this.pushItems = this.pushItems.bind(this);
|
||||||
|
this.prepend = this.prepend.bind(this);
|
||||||
|
this.enqueue = this.enqueue.bind(this);
|
||||||
|
this.releaseQueue = this.releaseQueue.bind(this);
|
||||||
|
this.removeItem = this.removeItem.bind(this);
|
||||||
|
this.updateItem = this.updateItem.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getNewestId(): string | null | undefined {
|
||||||
|
// 様々な要因により並び順は保証されないのでソートが必要
|
||||||
|
if (this.aheadQueue.length > 0) {
|
||||||
|
return this.aheadQueue.map(x => x.id).sort().at(-1);
|
||||||
|
}
|
||||||
|
return this.items.value.map(x => x.id).sort().at(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOldestId(): string | null | undefined {
|
||||||
|
// 様々な要因により並び順は保証されないのでソートが必要
|
||||||
|
return this.items.value.map(x => x.id).sort().at(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async init(): Promise<void> {
|
||||||
|
this.items.value = [];
|
||||||
|
this.aheadQueue = [];
|
||||||
|
this.queuedAheadItemsCount.value = 0;
|
||||||
|
this.fetching.value = true;
|
||||||
|
|
||||||
|
await misskeyApi<T[]>(this.endpoint, {
|
||||||
|
...(typeof this.params === 'function' ? this.params() : this.params),
|
||||||
|
...(this.computedParams ? this.computedParams.value : {}),
|
||||||
|
...(this.searchQuery.value != null && this.searchQuery.value.trim() !== '' ? { [this.searchParamName]: this.searchQuery.value } : {}),
|
||||||
|
limit: this.limit ?? FIRST_FETCH_LIMIT,
|
||||||
|
allowPartial: true,
|
||||||
|
...((this.initialId == null && this.initialDate == null) && this.initialDirection === 'newer' ? {
|
||||||
|
sinceId: '0',
|
||||||
|
} : this.initialDirection === 'newer' ? {
|
||||||
|
sinceId: this.initialId ?? undefined,
|
||||||
|
sinceDate: this.initialDate ?? undefined,
|
||||||
|
} : (this.initialId || this.initialDate) && this.initialDirection === 'older' ? {
|
||||||
|
untilId: this.initialId ?? undefined,
|
||||||
|
untilDate: this.initialDate ?? undefined,
|
||||||
|
} : {}),
|
||||||
|
}).then(res => {
|
||||||
|
// 逆順で返ってくるので
|
||||||
|
if ((this.initialId || this.initialDate) && this.initialDirection === 'newer') {
|
||||||
|
res.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
const item = res[i];
|
||||||
|
if (i === 3) item._shouldInsertAd_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushItems(res);
|
||||||
|
|
||||||
|
if (this.canFetchDetection === 'limit') {
|
||||||
|
if (res.length < FIRST_FETCH_LIMIT) {
|
||||||
|
this.canFetchOlder.value = false;
|
||||||
|
} else {
|
||||||
|
this.canFetchOlder.value = true;
|
||||||
|
}
|
||||||
|
} else if (this.canFetchDetection === 'safe' || this.canFetchDetection == null) {
|
||||||
|
if (res.length === 0 || this.noPaging) {
|
||||||
|
this.canFetchOlder.value = false;
|
||||||
|
} else {
|
||||||
|
this.canFetchOlder.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.error.value = false;
|
||||||
|
this.fetching.value = false;
|
||||||
|
}, err => {
|
||||||
|
this.error.value = true;
|
||||||
|
this.fetching.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public reload(): Promise<void> {
|
||||||
|
return this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchOlder(): Promise<void> {
|
||||||
|
if (!this.canFetchOlder.value || this.fetching.value || this.fetchingOlder.value || this.items.value.length === 0) return;
|
||||||
|
this.fetchingOlder.value = true;
|
||||||
|
await misskeyApi<T[]>(this.endpoint, {
|
||||||
|
...(typeof this.params === 'function' ? this.params() : this.params),
|
||||||
|
...(this.computedParams ? this.computedParams.value : {}),
|
||||||
|
...(this.searchQuery.value != null && this.searchQuery.value.trim() !== '' ? { [this.searchParamName]: this.searchQuery.value } : {}),
|
||||||
|
limit: SECOND_FETCH_LIMIT,
|
||||||
|
...(this.offsetMode ? {
|
||||||
|
offset: this.items.value.length,
|
||||||
|
} : {
|
||||||
|
untilId: this.getOldestId(),
|
||||||
|
}),
|
||||||
|
}).then(res => {
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
const item = res[i];
|
||||||
|
if (i === 10) item._shouldInsertAd_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushItems(res);
|
||||||
|
|
||||||
|
if (this.canFetchDetection === 'limit') {
|
||||||
|
if (res.length < FIRST_FETCH_LIMIT) {
|
||||||
|
this.canFetchOlder.value = false;
|
||||||
|
} else {
|
||||||
|
this.canFetchOlder.value = true;
|
||||||
|
}
|
||||||
|
} else if (this.canFetchDetection === 'safe' || this.canFetchDetection == null) {
|
||||||
|
if (res.length === 0) {
|
||||||
|
this.canFetchOlder.value = false;
|
||||||
|
} else {
|
||||||
|
this.canFetchOlder.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.fetchingOlder.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchNewer(options: {
|
||||||
|
toQueue?: boolean;
|
||||||
|
} = {}): Promise<void> {
|
||||||
|
this.fetchingNewer.value = true;
|
||||||
|
await misskeyApi<T[]>(this.endpoint, {
|
||||||
|
...(typeof this.params === 'function' ? this.params() : this.params),
|
||||||
|
...(this.computedParams ? this.computedParams.value : {}),
|
||||||
|
...(this.searchQuery.value != null && this.searchQuery.value.trim() !== '' ? { [this.searchParamName]: this.searchQuery.value } : {}),
|
||||||
|
limit: SECOND_FETCH_LIMIT,
|
||||||
|
...(this.offsetMode ? {
|
||||||
|
offset: this.items.value.length,
|
||||||
|
} : {
|
||||||
|
sinceId: this.getNewestId(),
|
||||||
|
}),
|
||||||
|
}).then(res => {
|
||||||
|
if (res.length === 0) return; // これやらないと余計なre-renderが走る
|
||||||
|
|
||||||
|
if (options.toQueue) {
|
||||||
|
this.aheadQueue.unshift(...res.toReversed());
|
||||||
|
if (this.aheadQueue.length > MAX_QUEUE_ITEMS) {
|
||||||
|
this.aheadQueue = this.aheadQueue.slice(0, MAX_QUEUE_ITEMS);
|
||||||
|
}
|
||||||
|
this.queuedAheadItemsCount.value = this.aheadQueue.length;
|
||||||
|
} else {
|
||||||
|
if (this.order.value === 'oldest') {
|
||||||
|
this.pushItems(res);
|
||||||
|
} else {
|
||||||
|
this.unshiftItems(res.toReversed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.fetchingNewer.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public trim(trigger = true): void {
|
||||||
|
if (this.items.value.length >= MAX_ITEMS) this.canFetchOlder.value = true;
|
||||||
|
this.items.value = this.items.value.slice(0, MAX_ITEMS);
|
||||||
|
if (this.useShallowRef && trigger) triggerRef(this.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unshiftItems(newItems: T[]): void {
|
||||||
|
if (newItems.length === 0) return; // これやらないと余計なre-renderが走る
|
||||||
|
this.items.value.unshift(...newItems.filter(x => !this.items.value.some(y => y.id === x.id))); // ストリーミングやポーリングのタイミングによっては重複することがあるため
|
||||||
|
this.trim(false);
|
||||||
|
if (this.useShallowRef) triggerRef(this.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public pushItems(oldItems: T[]): void {
|
||||||
|
if (oldItems.length === 0) return; // これやらないと余計なre-renderが走る
|
||||||
|
this.items.value.push(...oldItems);
|
||||||
|
if (this.useShallowRef) triggerRef(this.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public prepend(item: T): void {
|
||||||
|
if (this.items.value.some(x => x.id === item.id)) return;
|
||||||
|
this.items.value.unshift(item);
|
||||||
|
this.trim(false);
|
||||||
|
if (this.useShallowRef) triggerRef(this.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enqueue(item: T): void {
|
||||||
|
this.aheadQueue.unshift(item);
|
||||||
|
if (this.aheadQueue.length > MAX_QUEUE_ITEMS) {
|
||||||
|
this.aheadQueue.pop();
|
||||||
|
}
|
||||||
|
this.queuedAheadItemsCount.value = this.aheadQueue.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public releaseQueue(): void {
|
||||||
|
if (this.aheadQueue.length === 0) return; // これやらないと余計なre-renderが走る
|
||||||
|
this.unshiftItems(this.aheadQueue);
|
||||||
|
this.aheadQueue = [];
|
||||||
|
this.queuedAheadItemsCount.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeItem(id: string): void {
|
||||||
|
// TODO: queueからも消す
|
||||||
|
|
||||||
|
const index = this.items.value.findIndex(x => x.id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.items.value.splice(index, 1);
|
||||||
|
if (this.useShallowRef) triggerRef(this.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateItem(id: string, updator: (item: T) => T): void {
|
||||||
|
// TODO: queueのも更新
|
||||||
|
|
||||||
|
const index = this.items.value.findIndex(x => x.id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
const item = this.items.value[index]!;
|
||||||
|
this.items.value[index] = updator(item);
|
||||||
|
if (this.useShallowRef) triggerRef(this.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.6.4-alpha.2",
|
"version": "2025.6.4-alpha.3",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
|
|
@ -18274,6 +18274,7 @@ export interface operations {
|
||||||
untilId?: string;
|
untilId?: string;
|
||||||
sinceDate?: number;
|
sinceDate?: number;
|
||||||
untilDate?: number;
|
untilDate?: number;
|
||||||
|
search?: string | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue