support pagination
This commit is contained in:
parent
f8529a01b9
commit
041449e962
|
@ -455,7 +455,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
builder.andWhere('emoji.name LIKE :name', { name: `%${q.name}%` });
|
||||
}
|
||||
if (q.hostType === 'local') {
|
||||
builder.andWhere('emoji.host LIKE :host', { host: `%${q.host}%` });
|
||||
builder.andWhere('emoji.host IS NULL');
|
||||
} else {
|
||||
if (q.host) {
|
||||
// noIndexScan
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<div :class="$style.root">
|
||||
<MkButton primary :disabled="min === current" @click="onToPrevButtonClicked"><</MkButton>
|
||||
|
||||
<div :class="$style.buttons">
|
||||
<div v-if="prevDotVisible" :class="$style.headTailButtons">
|
||||
<MkButton @click="onToHeadButtonClicked">{{ min }}</MkButton>
|
||||
<span class="ti ti-dots"/>
|
||||
</div>
|
||||
|
||||
<MkButton
|
||||
v-for="i in buttonRanges" :key="i"
|
||||
:disabled="current === i"
|
||||
@click="onNumberButtonClicked(i)"
|
||||
>
|
||||
{{ i }}
|
||||
</MkButton>
|
||||
|
||||
<div v-if="nextDotVisible" :class="$style.headTailButtons">
|
||||
<span class="ti ti-dots"/>
|
||||
<MkButton @click="onToTailButtonClicked">{{ max }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkButton primary :disabled="max === current" @click="onToNextButtonClicked">></MkButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { computed, toRefs } from 'vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
|
||||
const min = 1;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'pageChanged', pageNumber: number): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
current: number;
|
||||
max: number;
|
||||
buttonCount: number;
|
||||
}>();
|
||||
|
||||
const { current, max, buttonCount } = toRefs(props);
|
||||
|
||||
const buttonCountHalf = computed(() => Math.floor(buttonCount.value / 2));
|
||||
const buttonCountStart = computed(() => Math.min(Math.max(min, current.value - buttonCountHalf.value), max.value - buttonCount.value + 1));
|
||||
const buttonRanges = computed(() => Array.from({ length: buttonCount.value }, (_, i) => buttonCountStart.value + i));
|
||||
|
||||
const prevDotVisible = computed(() => (current.value - 1 > buttonCountHalf.value) || (max.value < buttonCount.value));
|
||||
const nextDotVisible = computed(() => (current.value < max.value - buttonCountHalf.value) || (max.value < buttonCount.value));
|
||||
|
||||
function onNumberButtonClicked(pageNumber: number) {
|
||||
emit('pageChanged', pageNumber);
|
||||
}
|
||||
|
||||
function onToHeadButtonClicked() {
|
||||
emit('pageChanged', min);
|
||||
}
|
||||
|
||||
function onToPrevButtonClicked() {
|
||||
const newPageNumber = current.value <= min ? min : current.value - 1;
|
||||
emit('pageChanged', newPageNumber);
|
||||
}
|
||||
|
||||
function onToNextButtonClicked() {
|
||||
const newPageNumber = current.value >= max.value ? max.value : current.value + 1;
|
||||
emit('pageChanged', newPageNumber);
|
||||
}
|
||||
|
||||
function onToTailButtonClicked() {
|
||||
emit('pageChanged', max.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.root {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
|
||||
button {
|
||||
border-radius: 9999px;
|
||||
min-width: 2.5em;
|
||||
min-height: 2.5em;
|
||||
max-width: 2.5em;
|
||||
max-height: 2.5em;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.headTailButtons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="gridItems.length === 0" style="text-align: center">
|
||||
<div v-if="false" style="text-align: center">
|
||||
登録された絵文字はありません。
|
||||
</div>
|
||||
<div v-else class="_gaps">
|
||||
|
@ -17,12 +17,9 @@
|
|||
<MkGrid :data="gridItems" :gridSetting="gridSetting" :columnSettings="columnSettings" @event="onGridEvent"/>
|
||||
</div>
|
||||
|
||||
<div class="_gaps">
|
||||
<div :class="$style.pages">
|
||||
<button @click="onLatestButtonClicked"><</button>
|
||||
<button @click="onOldestButtonClicked">></button>
|
||||
</div>
|
||||
<MkPagingButtons :current="currentPage" :max="allPages" :buttonCount="5" @pageChanged="onPageChanged"/>
|
||||
|
||||
<div class="_gaps">
|
||||
<div :class="$style.buttons">
|
||||
<MkButton danger style="margin-right: auto" @click="onDeleteClicked">{{ i18n.ts.delete }}</MkButton>
|
||||
<MkButton primary :disabled="updateButtonDisabled" @click="onUpdateClicked">{{ i18n.ts.update }}</MkButton>
|
||||
|
@ -34,7 +31,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, toRefs, watch } from 'vue';
|
||||
import { computed, onMounted, ref, toRefs, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import * as os from '@/os.js';
|
||||
import { fromEmojiDetailedAdmin, GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
|
||||
|
@ -56,6 +53,7 @@ import {
|
|||
import { optInGridUtils } from '@/components/grid/optin-utils.js';
|
||||
import { GridSetting } from '@/components/grid/grid.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import MkPagingButtons from '@/components/MkPagingButtons.vue';
|
||||
|
||||
const gridSetting: GridSetting = {
|
||||
rowNumberVisible: true,
|
||||
|
@ -76,26 +74,16 @@ const columnSettings: ColumnSetting[] = [
|
|||
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 140 },
|
||||
];
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'operation:search', query: string, sinceId?: string, untilId?: string): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
customEmojis: Misskey.entities.EmojiDetailedAdmin[];
|
||||
}>();
|
||||
|
||||
const { customEmojis } = toRefs(props);
|
||||
|
||||
const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
|
||||
const query = ref('');
|
||||
const allPages = ref<number>(0);
|
||||
const currentPage = ref<number>(0);
|
||||
const previousQuery = ref<string | undefined>(undefined);
|
||||
|
||||
const gridItems = ref<GridItem[]>([]);
|
||||
const originGridItems = ref<GridItem[]>([]);
|
||||
const updateButtonDisabled = ref<boolean>(false);
|
||||
|
||||
const latest = computed(() => customEmojis.value.length > 0 ? customEmojis.value[0]?.id : undefined);
|
||||
const oldest = computed(() => customEmojis.value.length > 0 ? customEmojis.value[customEmojis.value.length - 1]?.id : undefined);
|
||||
|
||||
watch(customEmojis, refreshGridItems, { immediate: true });
|
||||
|
||||
async function onUpdateClicked() {
|
||||
const _items = gridItems.value;
|
||||
const _originItems = originGridItems.value;
|
||||
|
@ -144,8 +132,6 @@ async function onUpdateClicked() {
|
|||
() => {},
|
||||
() => {},
|
||||
);
|
||||
|
||||
emit('operation:search', query.value, undefined, undefined);
|
||||
}
|
||||
|
||||
async function onDeleteClicked() {
|
||||
|
@ -183,8 +169,6 @@ async function onDeleteClicked() {
|
|||
() => {},
|
||||
() => {},
|
||||
);
|
||||
|
||||
emit('operation:search', query.value, undefined, undefined);
|
||||
}
|
||||
|
||||
function onResetClicked() {
|
||||
|
@ -192,15 +176,11 @@ function onResetClicked() {
|
|||
}
|
||||
|
||||
function onSearchButtonClicked() {
|
||||
emit('operation:search', query.value, undefined, undefined);
|
||||
}
|
||||
|
||||
async function onLatestButtonClicked() {
|
||||
emit('operation:search', query.value, latest.value, undefined);
|
||||
}
|
||||
|
||||
async function onOldestButtonClicked() {
|
||||
emit('operation:search', query.value, undefined, oldest.value);
|
||||
async function onPageChanged(pageNumber: number) {
|
||||
currentPage.value = pageNumber;
|
||||
await refreshCustomEmojis();
|
||||
}
|
||||
|
||||
function onGridEvent(event: GridEvent, currentState: GridCurrentState) {
|
||||
|
@ -286,11 +266,44 @@ function onGridKeyDown(event: GridKeyDownEvent, currentState: GridCurrentState)
|
|||
optInGridUtils.defaultKeyDownHandler(gridItems, event, currentState);
|
||||
}
|
||||
|
||||
async function refreshCustomEmojis() {
|
||||
const limit = 10;
|
||||
|
||||
const query: Misskey.entities.AdminEmojiV2ListRequest['query'] = {
|
||||
hostType: 'local',
|
||||
};
|
||||
|
||||
if (JSON.stringify(query) !== previousQuery.value) {
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
const result = await os.promiseDialog(
|
||||
misskeyApi('admin/emoji/v2/list', {
|
||||
query: query,
|
||||
limit: limit,
|
||||
page: currentPage.value,
|
||||
}),
|
||||
() => {},
|
||||
() => {},
|
||||
);
|
||||
|
||||
customEmojis.value = result.emojis;
|
||||
allPages.value = result.allPages;
|
||||
|
||||
previousQuery.value = JSON.stringify(query);
|
||||
|
||||
refreshGridItems();
|
||||
}
|
||||
|
||||
function refreshGridItems() {
|
||||
gridItems.value = customEmojis.value.map(it => fromEmojiDetailedAdmin(it));
|
||||
originGridItems.value = JSON.parse(JSON.stringify(gridItems.value));
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await refreshCustomEmojis();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
|
@ -309,20 +322,6 @@ function refreshGridItems() {
|
|||
resize: vertical;
|
||||
}
|
||||
|
||||
.pages {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
background-color: var(--buttonBg);
|
||||
border-radius: 9999px;
|
||||
border: none;
|
||||
margin: 0 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
|
|
@ -6,53 +6,21 @@
|
|||
</MkTab>
|
||||
|
||||
<div>
|
||||
<XListComponent
|
||||
v-if="modeTab === 'list'"
|
||||
:customEmojis="customEmojis"
|
||||
@operation:search="onOperationSearch"
|
||||
/>
|
||||
<XRegisterComponent
|
||||
v-else
|
||||
@operation:registered="onOperationRegistered"
|
||||
/>
|
||||
<XListComponent v-if="modeTab === 'list'"/>
|
||||
<XRegisterComponent v-else/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { ref } from 'vue';
|
||||
import MkTab from '@/components/MkTab.vue';
|
||||
import XListComponent from '@/pages/admin/custom-emojis-grid.local.list.vue';
|
||||
import XRegisterComponent from '@/pages/admin/custom-emojis-grid.local.register.vue';
|
||||
|
||||
type PageMode = 'list' | 'register';
|
||||
|
||||
const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
|
||||
const modeTab = ref<PageMode>('list');
|
||||
const query = ref<string>();
|
||||
|
||||
async function refreshCustomEmojis(query?: string, sinceId?: string, untilId?: string) {
|
||||
const emojis = await misskeyApi('admin/emoji/v2/list', {
|
||||
limit: 100,
|
||||
}).then(it => it.emojis);
|
||||
|
||||
customEmojis.value = emojis;
|
||||
}
|
||||
|
||||
async function onOperationSearch(q: string, sinceId?: string, untilId?: string) {
|
||||
query.value = q;
|
||||
await refreshCustomEmojis(q, sinceId, untilId);
|
||||
}
|
||||
|
||||
async function onOperationRegistered() {
|
||||
await refreshCustomEmojis(query.value);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await refreshCustomEmojis();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
Loading…
Reference in New Issue