This commit is contained in:
samunohito 2024-02-06 23:43:12 +09:00
parent 2a0dca44c3
commit 273e3bd2e4
10 changed files with 179 additions and 60 deletions

View File

@ -53,6 +53,7 @@ const toggle = () => {
display: flex; display: flex;
transition: all 0.2s ease; transition: all 0.2s ease;
user-select: none; user-select: none;
align-items: center;
&:hover { &:hover {
> .button { > .button {

View File

@ -1,6 +1,12 @@
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
export type RegisterLogItem = { export type EmojiOperationResult = {
item: GridItem,
success: boolean,
err?: Error
};
export type RequestLogItem = {
failed: boolean; failed: boolean;
url: string; url: string;
name: string; name: string;
@ -55,3 +61,4 @@ export function fromDriveFile(it: Misskey.entities.DriveFile): GridItem {
roleIdsThatCanBeUsedThisEmojiAsReaction: '', roleIdsThatCanBeUsedThisEmojiAsReaction: '',
}; };
} }

View File

@ -4,14 +4,70 @@
登録された絵文字はありません 登録された絵文字はありません
</div> </div>
<div v-else class="_gaps"> <div v-else class="_gaps">
<div :class="$style.searchArea"> <MkFolder>
<MkInput v-model="queryName" :debounce="true" type="search" autocapitalize="off" style="flex: 1"> <template #icon><i class="ti ti-search"></i></template>
<template #prefix><i class="ti ti-search"></i></template> <template #label>検索設定</template>
</MkInput> <template #caption>
<MkButton primary style="margin-left: auto;" @click="onSearchButtonClicked"> 検索条件を詳細に設定します
{{ i18n.ts.search }} </template>
</MkButton>
</div> <div :class="$style.searchArea">
<MkInput v-model="queryName" :debounce="true" type="search" autocapitalize="off" style="grid-column: 1 / 2; grid-row: 1 / 2">
<template #label>name</template>
</MkInput>
<MkInput v-model="queryCategory" :debounce="true" type="search" autocapitalize="off" style="grid-column: 2 / 3; grid-row: 1 / 2">
<template #label>category</template>
</MkInput>
<MkInput v-model="queryAlias" :debounce="true" type="search" autocapitalize="off" style="grid-column: 3 / 4; grid-row: 1 / 2">
<template #label>alias</template>
</MkInput>
<MkInput v-model="queryType" :debounce="true" type="search" autocapitalize="off" style="grid-column: 1 / 2; grid-row: 2 / 3">
<template #label>type</template>
</MkInput>
<MkInput v-model="queryLicense" :debounce="true" type="search" autocapitalize="off" style="grid-column: 2 / 3; grid-row: 2 / 3">
<template #label>license</template>
</MkInput>
<MkInput v-model="queryUpdatedAtFrom" :debounce="true" type="date" autocapitalize="off" style="grid-column: 1 / 2; grid-row: 3 / 4">
<template #label>updatedAt(from)</template>
</MkInput>
<MkInput v-model="queryUpdatedAtTo" :debounce="true" type="date" autocapitalize="off" style="grid-column: 2 / 3; grid-row: 3 / 4">
<template #label>updatedAt(to)</template>
</MkInput>
<MkSelect v-model="querySensitive" style="grid-column: 1 / 2; grid-row: 4 / 5">
<template #label>sensitive</template>
<option :value="null">-</option>
<option :value="true">true</option>
<option :value="false">false</option>
</MkSelect>
<MkSelect v-model="queryLocalOnly" style="grid-column: 2 / 3; grid-row: 4 / 5">
<template #label>localOnly</template>
<option :value="null">-</option>
<option :value="true">true</option>
<option :value="false">false</option>
</MkSelect>
<div style="display:flex; justify-content: flex-end; align-items: flex-end; gap: 8px; grid-column: 3 / 4; grid-row: 4 / 5">
<MkButton primary @click="onSearchButtonClicked">
{{ i18n.ts.search }}
</MkButton>
<MkButton @click="onQueryResetButtonClicked">
リセット
</MkButton>
</div>
</div>
</MkFolder>
<MkFolder>
<template #icon><i class="ti ti-notes"></i></template>
<template #label>登録ログ</template>
<template #caption>
絵文字更新削除時のログが表示されます更新削除操作を行ったりページをリロードすると消えます
</template>
<XRegisterLogs :logs="requestLogs"/>
</MkFolder>
<div :class="$style.gridArea"> <div :class="$style.gridArea">
<MkGrid :data="gridItems" :gridSetting="gridSetting" :columnSettings="columnSettings" @event="onGridEvent"/> <MkGrid :data="gridItems" :gridSetting="gridSetting" :columnSettings="columnSettings" @event="onGridEvent"/>
@ -21,9 +77,9 @@
<div class="_gaps"> <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="onDeleteButtonClicked">{{ i18n.ts.delete }}</MkButton>
<MkButton primary :disabled="updateButtonDisabled" @click="onUpdateClicked">{{ i18n.ts.update }}</MkButton> <MkButton primary :disabled="updateButtonDisabled" @click="onUpdateButtonClicked">{{ i18n.ts.update }}</MkButton>
<MkButton @click="onResetClicked">リセット</MkButton> <MkButton @click="onGridResetButtonClicked">リセット</MkButton>
</div> </div>
</div> </div>
</div> </div>
@ -34,7 +90,7 @@
import { computed, onMounted, 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, RequestLogItem } from '@/pages/admin/custom-emojis-grid.impl.js';
import MkGrid from '@/components/grid/MkGrid.vue'; import MkGrid from '@/components/grid/MkGrid.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
@ -54,7 +110,12 @@ 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'; import MkPagingButtons from '@/components/MkPagingButtons.vue';
import XRegisterLogs from '@/pages/admin/custom-emojis-grid.local.logs.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkSelect from '@/components/MkSelect.vue';
const emptyStrToUndefined = (value: string | null) => value ? value : undefined;
const emptyStrToNull = (value: string) => value === '' ? null : value; const emptyStrToNull = (value: string) => value === '' ? null : value;
const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim()); const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim());
@ -78,16 +139,27 @@ const columnSettings: ColumnSetting[] = [
]; ];
const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]); const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
const queryName = ref('');
const allPages = ref<number>(0); const allPages = ref<number>(0);
const currentPage = ref<number>(0); const currentPage = ref<number>(0);
const queryName = ref<string | null>(null);
const queryCategory = ref<string | null>(null);
const queryAlias = ref<string | null>(null);
const queryType = ref<string | null>(null);
const queryLicense = ref<string | null>(null);
const queryUpdatedAtFrom = ref<string | null>(null);
const queryUpdatedAtTo = ref<string | null>(null);
const querySensitive = ref<string | null>(null);
const queryLocalOnly = ref<string | null>(null);
const previousQuery = ref<string | undefined>(undefined); const previousQuery = ref<string | undefined>(undefined);
const requestLogs = ref<RequestLogItem[]>([]);
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);
async function onUpdateClicked() { async function onUpdateButtonClicked() {
const _items = gridItems.value; const _items = gridItems.value;
const _originItems = originGridItems.value; const _originItems = originGridItems.value;
if (_items.length !== _originItems.length) { if (_items.length !== _originItems.length) {
@ -114,23 +186,46 @@ async function onUpdateClicked() {
const action = () => { const action = () => {
return updatedItems.map(item => return updatedItems.map(item =>
misskeyApi('admin/emoji/update', { misskeyApi(
id: item.id!, 'admin/emoji/update',
name: item.name, {
category: emptyStrToNull(item.category), // eslint-disable-next-line
aliases: emptyStrToEmptyArray(item.aliases), id: item.id!,
license: emptyStrToNull(item.license), name: item.name,
isSensitive: item.isSensitive, category: emptyStrToNull(item.category),
localOnly: item.localOnly, aliases: emptyStrToEmptyArray(item.aliases),
roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction), license: emptyStrToNull(item.license),
}), isSensitive: item.isSensitive,
localOnly: item.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction),
})
.then(() => ({ item, success: true, err: undefined }))
.catch(err => ({ item, success: false, err })),
); );
}; };
await os.promiseDialog(Promise.all(action())); const result = await os.promiseDialog(Promise.all(action()));
const failedItems = result.filter(it => !it.success);
if (failedItems.length > 0) {
await os.alert({
type: 'error',
title: 'エラー',
text: '絵文字の更新・削除に失敗しました。詳細は登録ログをご確認ください。',
});
}
requestLogs.value = result.map(it => ({
failed: !it.success,
url: it.item.url,
name: it.item.name,
error: it.err ? JSON.stringify(it.err) : undefined,
}));
await refreshCustomEmojis();
} }
async function onDeleteClicked() { async function onDeleteButtonClicked() {
const _items = gridItems.value; const _items = gridItems.value;
const _originItems = originGridItems.value; const _originItems = originGridItems.value;
if (_items.length !== _originItems.length) { if (_items.length !== _originItems.length) {
@ -167,7 +262,7 @@ async function onDeleteClicked() {
); );
} }
function onResetClicked() { function onGridResetButtonClicked() {
refreshGridItems(); refreshGridItems();
} }
@ -175,6 +270,18 @@ async function onSearchButtonClicked() {
await refreshCustomEmojis(); await refreshCustomEmojis();
} }
function onQueryResetButtonClicked() {
queryName.value = null;
queryCategory.value = null;
queryAlias.value = null;
queryType.value = null;
queryLicense.value = null;
queryUpdatedAtFrom.value = null;
queryUpdatedAtTo.value = null;
querySensitive.value = null;
queryLocalOnly.value = null;
}
async function onPageChanged(pageNumber: number) { async function onPageChanged(pageNumber: number) {
currentPage.value = pageNumber; currentPage.value = pageNumber;
await refreshCustomEmojis(); await refreshCustomEmojis();
@ -267,10 +374,20 @@ async function refreshCustomEmojis() {
const limit = 100; const limit = 100;
const query: Misskey.entities.AdminEmojiV2ListRequest['query'] = { const query: Misskey.entities.AdminEmojiV2ListRequest['query'] = {
name: emptyStrToNull(queryName.value) ?? undefined, name: emptyStrToUndefined(queryName.value),
type: emptyStrToUndefined(queryType.value),
aliases: emptyStrToUndefined(queryAlias.value),
category: emptyStrToUndefined(queryCategory.value),
license: emptyStrToUndefined(queryLicense.value),
isSensitive: querySensitive.value ? Boolean(querySensitive.value).valueOf() : undefined,
localOnly: queryLocalOnly.value ? Boolean(queryLocalOnly.value).valueOf() : undefined,
updatedAtFrom: emptyStrToUndefined(queryUpdatedAtFrom.value),
updatedAtTo: emptyStrToUndefined(queryUpdatedAtTo.value),
hostType: 'local', hostType: 'local',
}; };
console.log(queryUpdatedAtTo.value);
if (JSON.stringify(query) !== previousQuery.value) { if (JSON.stringify(query) !== previousQuery.value) {
currentPage.value = 1; currentPage.value = 1;
} }
@ -306,11 +423,9 @@ onMounted(async () => {
<style module lang="scss"> <style module lang="scss">
.searchArea { .searchArea {
display: flex; display: grid;
flex-direction: row; grid-template-columns: 1fr 1fr 1fr;
align-items: center; gap: 16px;
justify-content: stretch;
gap: 8px;
} }
.gridArea { .gridArea {

View File

@ -18,7 +18,7 @@
import { toRefs } from 'vue'; import { toRefs } from 'vue';
import { ColumnSetting } from '@/components/grid/column.js'; import { ColumnSetting } from '@/components/grid/column.js';
import { RegisterLogItem } from '@/pages/admin/custom-emojis-grid.impl.js'; import { RequestLogItem } from '@/pages/admin/custom-emojis-grid.impl.js';
import { import {
GridCellContextMenuEvent, GridCellContextMenuEvent,
GridCurrentState, GridCurrentState,
@ -37,7 +37,7 @@ const columnSettings: ColumnSetting[] = [
]; ];
const props = defineProps<{ const props = defineProps<{
logs: RegisterLogItem[]; logs: RequestLogItem[];
}>(); }>();
const { logs } = toRefs(props); const { logs } = toRefs(props);

View File

@ -32,7 +32,7 @@
絵文字登録時のログが表示されます登録操作を行ったりページをリロードすると消えます 絵文字登録時のログが表示されます登録操作を行ったりページをリロードすると消えます
</template> </template>
<XRegisterLogs :logs="registerLogs"/> <XRegisterLogs :logs="requestLogs"/>
</MkFolder> </MkFolder>
<div <div
@ -75,7 +75,12 @@
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { fromDriveFile, GridItem, RegisterLogItem } from '@/pages/admin/custom-emojis-grid.impl.js'; import {
EmojiOperationResult,
fromDriveFile,
GridItem,
RequestLogItem,
} from '@/pages/admin/custom-emojis-grid.impl.js';
import MkGrid from '@/components/grid/MkGrid.vue'; import MkGrid from '@/components/grid/MkGrid.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
@ -99,7 +104,7 @@ import {
import { ColumnSetting } from '@/components/grid/column.js'; import { ColumnSetting } from '@/components/grid/column.js';
import { DroppedFile, extractDroppedItems, flattenDroppedFiles } from '@/scripts/file-drop.js'; import { DroppedFile, extractDroppedItems, flattenDroppedFiles } from '@/scripts/file-drop.js';
import { optInGridUtils } from '@/components/grid/optin-utils.js'; import { optInGridUtils } from '@/components/grid/optin-utils.js';
import XRegisterLogs from '@/pages/admin/custom-emojis-grid.local.register.logs.vue'; import XRegisterLogs from '@/pages/admin/custom-emojis-grid.local.logs.vue';
const MAXIMUM_EMOJI_COUNT = 100; const MAXIMUM_EMOJI_COUNT = 100;
@ -108,13 +113,6 @@ type FolderItem = {
name: string; name: string;
}; };
type UploadResult = {
key: string,
item: GridItem,
success: boolean,
err?: Error
};
const required = validators.required(); const required = validators.required();
const regex = validators.regex(/^[a-zA-Z0-9_]+$/); const regex = validators.regex(/^[a-zA-Z0-9_]+$/);
const columnSettings: ColumnSetting[] = [ const columnSettings: ColumnSetting[] = [
@ -138,7 +136,7 @@ const selectedFolderId = ref(defaultStore.state.uploadFolder);
const keepOriginalUploading = ref(defaultStore.state.keepOriginalUploading); const keepOriginalUploading = ref(defaultStore.state.keepOriginalUploading);
const directoryToCategory = ref<boolean>(false); const directoryToCategory = ref<boolean>(false);
const registerButtonDisabled = ref<boolean>(false); const registerButtonDisabled = ref<boolean>(false);
const registerLogs = ref<RegisterLogItem[]>([]); const requestLogs = ref<RequestLogItem[]>([]);
const isDragOver = ref<boolean>(false); const isDragOver = ref<boolean>(false);
async function onRegistryClicked() { async function onRegistryClicked() {
@ -153,12 +151,12 @@ async function onRegistryClicked() {
} }
const items = new Map<string, GridItem>(gridItems.value.map(it => [`${it.fileId}|${it.name}`, it])); const items = new Map<string, GridItem>(gridItems.value.map(it => [`${it.fileId}|${it.name}`, it]));
const upload = (): Promise<UploadResult>[] => { const upload = (): Promise<EmojiOperationResult>[] => {
const emptyStrToNull = (value: string) => value === '' ? null : value; const emptyStrToNull = (value: string) => value === '' ? null : value;
const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim()); const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim());
return [...items.entries()].slice(0, MAXIMUM_EMOJI_COUNT) return [...items.values()].slice(0, MAXIMUM_EMOJI_COUNT)
.map(([key, item]) => .map(item =>
misskeyApi( misskeyApi(
'admin/emoji/add', { 'admin/emoji/add', {
name: item.name, name: item.name,
@ -170,8 +168,8 @@ async function onRegistryClicked() {
roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction), roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction),
fileId: item.fileId!, fileId: item.fileId!,
}) })
.then((): UploadResult => ({ key, item, success: true, err: undefined })) .then(() => ({ item, success: true, err: undefined }))
.catch((err: any): UploadResult => ({ key, item, success: false, err })), .catch(err => ({ item, success: false, err })),
); );
}; };
@ -186,7 +184,7 @@ async function onRegistryClicked() {
}); });
} }
registerLogs.value = result.map(it => ({ requestLogs.value = result.map(it => ({
failed: !it.success, failed: !it.success,
url: it.item.url, url: it.item.url,
name: it.item.name, name: it.item.name,

View File

@ -1,6 +1,6 @@
/* /*
* version: 2024.2.0-beta.7 * version: 2024.2.0-beta.7
* generatedAt: 2024-02-05T06:03:40.656Z * generatedAt: 2024-02-06T12:17:59.658Z
*/ */
import type { SwitchCaseResponseType } from '../api.js'; import type { SwitchCaseResponseType } from '../api.js';

View File

@ -1,6 +1,6 @@
/* /*
* version: 2024.2.0-beta.7 * version: 2024.2.0-beta.7
* generatedAt: 2024-02-05T06:03:40.654Z * generatedAt: 2024-02-06T12:17:59.656Z
*/ */
import type { import type {

View File

@ -1,6 +1,6 @@
/* /*
* version: 2024.2.0-beta.7 * version: 2024.2.0-beta.7
* generatedAt: 2024-02-05T06:03:40.652Z * generatedAt: 2024-02-06T12:17:59.655Z
*/ */
import { operations } from './types.js'; import { operations } from './types.js';

View File

@ -1,6 +1,6 @@
/* /*
* version: 2024.2.0-beta.7 * version: 2024.2.0-beta.7
* generatedAt: 2024-02-05T06:03:40.651Z * generatedAt: 2024-02-06T12:17:59.654Z
*/ */
import { components } from './types.js'; import { components } from './types.js';

View File

@ -3,7 +3,7 @@
/* /*
* version: 2024.2.0-beta.7 * version: 2024.2.0-beta.7
* generatedAt: 2024-02-05T06:03:40.574Z * generatedAt: 2024-02-06T12:17:59.576Z
*/ */
/** /**
@ -6877,9 +6877,7 @@ export type operations = {
content: { content: {
'application/json': { 'application/json': {
query?: ({ query?: ({
/** Format: date-time */
updatedAtFrom?: string; updatedAtFrom?: string;
/** Format: date-time */
updatedAtTo?: string; updatedAtTo?: string;
name?: string; name?: string;
host?: string; host?: string;