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}%` });
|
builder.andWhere('emoji.name LIKE :name', { name: `%${q.name}%` });
|
||||||
}
|
}
|
||||||
if (q.hostType === 'local') {
|
if (q.hostType === 'local') {
|
||||||
builder.andWhere('emoji.host LIKE :host', { host: `%${q.host}%` });
|
builder.andWhere('emoji.host IS NULL');
|
||||||
} else {
|
} else {
|
||||||
if (q.host) {
|
if (q.host) {
|
||||||
// noIndexScan
|
// noIndexScan
|
||||||
|
@ -524,7 +524,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
emojis,
|
emojis,
|
||||||
count: (count > limit ? emojis.length : count),
|
count: (count > limit ? emojis.length : count),
|
||||||
allCount: count,
|
allCount: count,
|
||||||
allPages: Math.ceil(count / limit),
|
allPages: Math.ceil(count / limit),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="gridItems.length === 0" style="text-align: center">
|
<div v-if="false" style="text-align: center">
|
||||||
登録された絵文字はありません。
|
登録された絵文字はありません。
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="_gaps">
|
<div v-else class="_gaps">
|
||||||
|
@ -17,12 +17,9 @@
|
||||||
<MkGrid :data="gridItems" :gridSetting="gridSetting" :columnSettings="columnSettings" @event="onGridEvent"/>
|
<MkGrid :data="gridItems" :gridSetting="gridSetting" :columnSettings="columnSettings" @event="onGridEvent"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="_gaps">
|
<MkPagingButtons :current="currentPage" :max="allPages" :buttonCount="5" @pageChanged="onPageChanged"/>
|
||||||
<div :class="$style.pages">
|
|
||||||
<button @click="onLatestButtonClicked"><</button>
|
|
||||||
<button @click="onOldestButtonClicked">></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="_gaps">
|
||||||
<div :class="$style.buttons">
|
<div :class="$style.buttons">
|
||||||
<MkButton danger style="margin-right: auto" @click="onDeleteClicked">{{ i18n.ts.delete }}</MkButton>
|
<MkButton danger style="margin-right: auto" @click="onDeleteClicked">{{ i18n.ts.delete }}</MkButton>
|
||||||
<MkButton primary :disabled="updateButtonDisabled" @click="onUpdateClicked">{{ i18n.ts.update }}</MkButton>
|
<MkButton primary :disabled="updateButtonDisabled" @click="onUpdateClicked">{{ i18n.ts.update }}</MkButton>
|
||||||
|
@ -34,7 +31,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 Misskey from 'misskey-js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { fromEmojiDetailedAdmin, GridItem } from '@/pages/admin/custom-emojis-grid.impl.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 { optInGridUtils } from '@/components/grid/optin-utils.js';
|
||||||
import { GridSetting } from '@/components/grid/grid.js';
|
import { GridSetting } from '@/components/grid/grid.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
import MkPagingButtons from '@/components/MkPagingButtons.vue';
|
||||||
|
|
||||||
const gridSetting: GridSetting = {
|
const gridSetting: GridSetting = {
|
||||||
rowNumberVisible: true,
|
rowNumberVisible: true,
|
||||||
|
@ -76,26 +74,16 @@ const columnSettings: ColumnSetting[] = [
|
||||||
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 140 },
|
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 140 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
|
||||||
(ev: 'operation:search', query: string, sinceId?: string, untilId?: string): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
customEmojis: Misskey.entities.EmojiDetailedAdmin[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const { customEmojis } = toRefs(props);
|
|
||||||
|
|
||||||
const query = ref('');
|
const query = ref('');
|
||||||
|
const allPages = ref<number>(0);
|
||||||
|
const currentPage = ref<number>(0);
|
||||||
|
const previousQuery = ref<string | undefined>(undefined);
|
||||||
|
|
||||||
const gridItems = ref<GridItem[]>([]);
|
const gridItems = ref<GridItem[]>([]);
|
||||||
const originGridItems = ref<GridItem[]>([]);
|
const originGridItems = ref<GridItem[]>([]);
|
||||||
const updateButtonDisabled = ref<boolean>(false);
|
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() {
|
async function onUpdateClicked() {
|
||||||
const _items = gridItems.value;
|
const _items = gridItems.value;
|
||||||
const _originItems = originGridItems.value;
|
const _originItems = originGridItems.value;
|
||||||
|
@ -144,8 +132,6 @@ async function onUpdateClicked() {
|
||||||
() => {},
|
() => {},
|
||||||
() => {},
|
() => {},
|
||||||
);
|
);
|
||||||
|
|
||||||
emit('operation:search', query.value, undefined, undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDeleteClicked() {
|
async function onDeleteClicked() {
|
||||||
|
@ -183,8 +169,6 @@ async function onDeleteClicked() {
|
||||||
() => {},
|
() => {},
|
||||||
() => {},
|
() => {},
|
||||||
);
|
);
|
||||||
|
|
||||||
emit('operation:search', query.value, undefined, undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onResetClicked() {
|
function onResetClicked() {
|
||||||
|
@ -192,15 +176,11 @@ function onResetClicked() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSearchButtonClicked() {
|
function onSearchButtonClicked() {
|
||||||
emit('operation:search', query.value, undefined, undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onLatestButtonClicked() {
|
async function onPageChanged(pageNumber: number) {
|
||||||
emit('operation:search', query.value, latest.value, undefined);
|
currentPage.value = pageNumber;
|
||||||
}
|
await refreshCustomEmojis();
|
||||||
|
|
||||||
async function onOldestButtonClicked() {
|
|
||||||
emit('operation:search', query.value, undefined, oldest.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onGridEvent(event: GridEvent, currentState: GridCurrentState) {
|
function onGridEvent(event: GridEvent, currentState: GridCurrentState) {
|
||||||
|
@ -286,11 +266,44 @@ function onGridKeyDown(event: GridKeyDownEvent, currentState: GridCurrentState)
|
||||||
optInGridUtils.defaultKeyDownHandler(gridItems, event, currentState);
|
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() {
|
function refreshGridItems() {
|
||||||
gridItems.value = customEmojis.value.map(it => fromEmojiDetailedAdmin(it));
|
gridItems.value = customEmojis.value.map(it => fromEmojiDetailedAdmin(it));
|
||||||
originGridItems.value = JSON.parse(JSON.stringify(gridItems.value));
|
originGridItems.value = JSON.parse(JSON.stringify(gridItems.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await refreshCustomEmojis();
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
|
@ -309,20 +322,6 @@ function refreshGridItems() {
|
||||||
resize: vertical;
|
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 {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
|
|
@ -6,53 +6,21 @@
|
||||||
</MkTab>
|
</MkTab>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<XListComponent
|
<XListComponent v-if="modeTab === 'list'"/>
|
||||||
v-if="modeTab === 'list'"
|
<XRegisterComponent v-else/>
|
||||||
:customEmojis="customEmojis"
|
|
||||||
@operation:search="onOperationSearch"
|
|
||||||
/>
|
|
||||||
<XRegisterComponent
|
|
||||||
v-else
|
|
||||||
@operation:registered="onOperationRegistered"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
|
||||||
import MkTab from '@/components/MkTab.vue';
|
import MkTab from '@/components/MkTab.vue';
|
||||||
import XListComponent from '@/pages/admin/custom-emojis-grid.local.list.vue';
|
import XListComponent from '@/pages/admin/custom-emojis-grid.local.list.vue';
|
||||||
import XRegisterComponent from '@/pages/admin/custom-emojis-grid.local.register.vue';
|
import XRegisterComponent from '@/pages/admin/custom-emojis-grid.local.register.vue';
|
||||||
|
|
||||||
type PageMode = 'list' | 'register';
|
type PageMode = 'list' | 'register';
|
||||||
|
|
||||||
const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
|
|
||||||
const modeTab = ref<PageMode>('list');
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
Loading…
Reference in New Issue