support role select
This commit is contained in:
parent
c0f941689b
commit
07b9757b36
|
@ -444,12 +444,12 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
function multipleWordsToQuery(
|
function multipleWordsToQuery(
|
||||||
query: string,
|
query: string,
|
||||||
builder: SelectQueryBuilder<MiEmoji>,
|
builder: SelectQueryBuilder<MiEmoji>,
|
||||||
action: (qb: WhereExpressionBuilder, word: string) => void,
|
action: (qb: WhereExpressionBuilder, idx: number, word: string) => void,
|
||||||
) {
|
) {
|
||||||
const words = query.split(/\s/);
|
const words = query.split(/\s/);
|
||||||
builder.andWhere(new Brackets((qb => {
|
builder.andWhere(new Brackets((qb => {
|
||||||
for (const word of words) {
|
for (const [idx, word] of words.entries()) {
|
||||||
action(qb, word);
|
action(qb, idx, word);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
@ -466,8 +466,8 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
builder.andWhere('emoji.updatedAt <= :updateAtTo', { updateAtTo: q.updatedAtTo });
|
builder.andWhere('emoji.updatedAt <= :updateAtTo', { updateAtTo: q.updatedAtTo });
|
||||||
}
|
}
|
||||||
if (q.name) {
|
if (q.name) {
|
||||||
multipleWordsToQuery(q.name, builder, (qb, word) => {
|
multipleWordsToQuery(q.name, builder, (qb, idx, word) => {
|
||||||
qb.orWhere('emoji.name LIKE :name', { name: `%${word}%` });
|
qb.orWhere(`emoji.name LIKE :name${idx}`, Object.fromEntries([[`name${idx}`, `%${word}%`]]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,8 +479,8 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
case q.hostType === 'remote': {
|
case q.hostType === 'remote': {
|
||||||
if (q.host) {
|
if (q.host) {
|
||||||
// noIndexScan
|
// noIndexScan
|
||||||
multipleWordsToQuery(q.host, builder, (qb, word) => {
|
multipleWordsToQuery(q.host, builder, (qb, idx, word) => {
|
||||||
qb.orWhere('emoji.host LIKE :host', { host: `%${word}%` });
|
qb.orWhere(`emoji.host LIKE :host${idx}`, Object.fromEntries([[`host${idx}`, `%${word}%`]]));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
builder.andWhere('emoji.host IS NOT NULL');
|
builder.andWhere('emoji.host IS NOT NULL');
|
||||||
|
@ -491,38 +491,38 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
|
|
||||||
if (q.uri) {
|
if (q.uri) {
|
||||||
// noIndexScan
|
// noIndexScan
|
||||||
multipleWordsToQuery(q.uri, builder, (qb, word) => {
|
multipleWordsToQuery(q.uri, builder, (qb, idx, word) => {
|
||||||
qb.orWhere('emoji.uri LIKE :uri', { uri: `%${word}%` });
|
qb.orWhere(`emoji.uri LIKE :uri${idx}`, Object.fromEntries([[`uri${idx}`, `%${word}%`]]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (q.publicUrl) {
|
if (q.publicUrl) {
|
||||||
// noIndexScan
|
// noIndexScan
|
||||||
multipleWordsToQuery(q.publicUrl, builder, (qb, word) => {
|
multipleWordsToQuery(q.publicUrl, builder, (qb, idx, word) => {
|
||||||
qb.orWhere('emoji.publicUrl LIKE :publicUrl', { publicUrl: `%${word}%` });
|
qb.orWhere(`emoji.publicUrl LIKE :publicUrl${idx}`, Object.fromEntries([[`publicUrl${idx}`, `%${word}%`]]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (q.type) {
|
if (q.type) {
|
||||||
// noIndexScan
|
// noIndexScan
|
||||||
multipleWordsToQuery(q.type, builder, (qb, word) => {
|
multipleWordsToQuery(q.type, builder, (qb, idx, word) => {
|
||||||
qb.orWhere('emoji.type LIKE :type', { type: `%${word}%` });
|
qb.orWhere(`emoji.type LIKE :type${idx}`, Object.fromEntries([[`type${idx}`, `%${word}%`]]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (q.aliases) {
|
if (q.aliases) {
|
||||||
// noIndexScan
|
// noIndexScan
|
||||||
multipleWordsToQuery(q.aliases, builder, (qb, word) => {
|
multipleWordsToQuery(q.aliases, builder, (qb, idx, word) => {
|
||||||
qb.orWhere('emoji.aliases LIKE :aliases', { aliases: `%${word}%` });
|
qb.orWhere(`emoji.aliases LIKE :aliases${idx}`, Object.fromEntries([[`aliases${idx}`, `%${word}%`]]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (q.category) {
|
if (q.category) {
|
||||||
// noIndexScan
|
// noIndexScan
|
||||||
multipleWordsToQuery(q.category, builder, (qb, word) => {
|
multipleWordsToQuery(q.category, builder, (qb, idx, word) => {
|
||||||
qb.orWhere('emoji.category LIKE :category', { category: `%${word}%` });
|
qb.orWhere(`emoji.category LIKE :category${idx}`, Object.fromEntries([[`category${idx}`, `%${word}%`]]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (q.license) {
|
if (q.license) {
|
||||||
// noIndexScan
|
// noIndexScan
|
||||||
multipleWordsToQuery(q.license, builder, (qb, word) => {
|
multipleWordsToQuery(q.license, builder, (qb, idx, word) => {
|
||||||
qb.orWhere('emoji.license LIKE :license', { license: `%${word}%` });
|
qb.orWhere(`emoji.license LIKE :license${idx}`, Object.fromEntries([[`license${idx}`, `%${word}%`]]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (q.isSensitive != null) {
|
if (q.isSensitive != null) {
|
||||||
|
@ -533,6 +533,9 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
// noIndexScan
|
// noIndexScan
|
||||||
builder.andWhere('emoji.localOnly = :localOnly', { localOnly: q.localOnly });
|
builder.andWhere('emoji.localOnly = :localOnly', { localOnly: q.localOnly });
|
||||||
}
|
}
|
||||||
|
if (q.roleIds && q.roleIds.length > 0) {
|
||||||
|
builder.andWhere('emoji.roleIdsThatCanBeUsedThisEmojiAsReaction @> :roleIds', { roleIds: q.roleIds });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params?.sinceId) {
|
if (params?.sinceId) {
|
||||||
|
@ -542,7 +545,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||||
builder.andWhere('emoji.id < :untilId', { untilId: params.untilId });
|
builder.andWhere('emoji.id < :untilId', { untilId: params.untilId });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params?.sort) {
|
if (params?.sort && params.sort.length > 0) {
|
||||||
for (const sort of params.sort) {
|
for (const sort of params.sort) {
|
||||||
builder.addOrderBy(`emoji.${sort.key}`, sort.direction);
|
builder.addOrderBy(`emoji.${sort.key}`, sort.direction);
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,14 @@ export class EmojiEntityService {
|
||||||
...await this.rolesRepository.findBy({ id: In(emoji.roleIdsThatCanBeUsedThisEmojiAsReaction) }),
|
...await this.rolesRepository.findBy({ id: In(emoji.roleIdsThatCanBeUsedThisEmojiAsReaction) }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
roles.sort((a, b) => {
|
||||||
|
if (a.displayOrder !== b.displayOrder) {
|
||||||
|
return b.displayOrder - a.displayOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.id.localeCompare(b.id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
<template>
|
||||||
|
<MkWindow
|
||||||
|
ref="windowEl"
|
||||||
|
:initialWidth="400"
|
||||||
|
:initialHeight="500"
|
||||||
|
:canResize="false"
|
||||||
|
@close="windowEl?.close()"
|
||||||
|
@closed="$emit('closed')"
|
||||||
|
>
|
||||||
|
<template #header>{{ title }}</template>
|
||||||
|
<MkSpacer :marginMin="20" :marginMax="28">
|
||||||
|
<div class="_gaps">
|
||||||
|
<div :class="$style.header">
|
||||||
|
<MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div v-if="selectedRoles.length > 0" class="_gaps" :class="$style.roleItemArea">
|
||||||
|
<div v-for="role in selectedRoles" :key="role.id" :class="$style.roleItem">
|
||||||
|
<MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/>
|
||||||
|
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnAssign" @click="removeRole(role.id)"><i class="ti ti-x"></i></button>
|
||||||
|
<button v-else class="_button" :class="$style.roleUnAssign" disabled><i class="ti ti-ban"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else :class="$style.roleItemArea" style="text-align: center">
|
||||||
|
何も選択されていません
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MkInfo v-if="infoMessage">{{ infoMessage }}</MkInfo>
|
||||||
|
|
||||||
|
<div :class="$style.buttons">
|
||||||
|
<MkButton primary @click="onOkClicked">{{ i18n.ts.ok }}</MkButton>
|
||||||
|
<MkButton @click="onCancelClicked">{{ i18n.ts.cancel }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
</MkWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, defineProps, ref, toRefs } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
import Section from '@/components/form/section.vue';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
|
import MkSpacer from '@/components/global/MkSpacer.vue';
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'done', value: Misskey.entities.Role[]),
|
||||||
|
(ev: 'closed'),
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
initialRoleIds?: string[],
|
||||||
|
infoMessage?: string,
|
||||||
|
title?: string,
|
||||||
|
publicOnly: boolean,
|
||||||
|
}>(), {
|
||||||
|
initialRoleIds: undefined,
|
||||||
|
infoMessage: undefined,
|
||||||
|
title: undefined,
|
||||||
|
publicOnly: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { initialRoleIds, infoMessage, title, publicOnly } = toRefs(props);
|
||||||
|
|
||||||
|
const windowEl = ref<InstanceType<typeof MkWindow>>();
|
||||||
|
const roles = ref<Misskey.entities.Role[]>([]);
|
||||||
|
const selectedRoleIds = ref<string[]>(initialRoleIds.value ?? []);
|
||||||
|
|
||||||
|
const selectedRoles = computed(() => {
|
||||||
|
const r = roles.value.filter(role => selectedRoleIds.value.includes(role.id));
|
||||||
|
r.sort((a, b) => {
|
||||||
|
if (a.displayOrder !== b.displayOrder) {
|
||||||
|
return b.displayOrder - a.displayOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.id.localeCompare(b.id);
|
||||||
|
});
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetchRoles() {
|
||||||
|
const result = await misskeyApi('admin/roles/list', {});
|
||||||
|
roles.value = result.filter(it => publicOnly.value ? it.isPublic : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addRole() {
|
||||||
|
const items = roles.value
|
||||||
|
.filter(r => r.isPublic)
|
||||||
|
.filter(r => !selectedRoleIds.value.includes(r.id))
|
||||||
|
.map(r => ({ text: r.name, value: r }));
|
||||||
|
|
||||||
|
const { canceled, result: role } = await os.select({ items });
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedRoleIds.value.push(role.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeRole(roleId: string) {
|
||||||
|
selectedRoleIds.value = selectedRoleIds.value.filter(x => x !== roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOkClicked() {
|
||||||
|
emit('done', selectedRoles.value);
|
||||||
|
windowEl.value?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCancelClicked() {
|
||||||
|
emit('closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchRoles();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.roleItemArea {
|
||||||
|
background-color: var(--acrylicBg);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roleItem {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roleUnAssign {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin-left: 8px;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addRoleButton {
|
||||||
|
min-width: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
max-width: 32px;
|
||||||
|
max-height: 32px;
|
||||||
|
margin-left: 8px;
|
||||||
|
align-self: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
border-top: solid 0.5px var(--divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -59,7 +59,6 @@ import * as os from '@/os.js';
|
||||||
import { CellValue, GridCell } from '@/components/grid/cell.js';
|
import { CellValue, GridCell } from '@/components/grid/cell.js';
|
||||||
import { equalCellAddress, getCellAddress } from '@/components/grid/grid-utils.js';
|
import { equalCellAddress, getCellAddress } from '@/components/grid/grid-utils.js';
|
||||||
import { GridRowSetting } from '@/components/grid/row.js';
|
import { GridRowSetting } from '@/components/grid/row.js';
|
||||||
import { selectFile } from '@/scripts/select-file.js';
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'operation:beginEdit', sender: GridCell): void;
|
(ev: 'operation:beginEdit', sender: GridCell): void;
|
||||||
|
@ -107,7 +106,7 @@ watch(() => cell.value.selected, () => {
|
||||||
function onCellDoubleClick(ev: MouseEvent) {
|
function onCellDoubleClick(ev: MouseEvent) {
|
||||||
switch (ev.type) {
|
switch (ev.type) {
|
||||||
case 'dblclick': {
|
case 'dblclick': {
|
||||||
beginEditing();
|
beginEditing(ev.target as HTMLElement);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +126,7 @@ function onCellKeyDown(ev: KeyboardEvent) {
|
||||||
case 'NumpadEnter':
|
case 'NumpadEnter':
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'F2': {
|
case 'F2': {
|
||||||
beginEditing();
|
beginEditing(ev.target as HTMLElement);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,11 +163,27 @@ function unregisterOutsideMouseDown() {
|
||||||
removeEventListener('mousedown', onOutsideMouseDown);
|
removeEventListener('mousedown', onOutsideMouseDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function beginEditing() {
|
async function beginEditing(target: HTMLElement) {
|
||||||
if (editing.value || !cell.value.column.setting.editable) {
|
if (editing.value || !cell.value.column.setting.editable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cell.value.column.setting.customValueEditor) {
|
||||||
|
emit('operation:beginEdit', cell.value);
|
||||||
|
const newValue = await cell.value.column.setting.customValueEditor(
|
||||||
|
cell.value.row,
|
||||||
|
cell.value.column,
|
||||||
|
cell.value.value,
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
emit('operation:endEdit', cell.value);
|
||||||
|
|
||||||
|
if (newValue !== cell.value.value) {
|
||||||
|
emitValueChange(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
rootEl.value?.focus();
|
||||||
|
} else {
|
||||||
switch (cellType.value) {
|
switch (cellType.value) {
|
||||||
case 'text': {
|
case 'text': {
|
||||||
editingValue.value = cell.value.value;
|
editingValue.value = cell.value.value;
|
||||||
|
@ -189,12 +204,6 @@ async function beginEditing() {
|
||||||
emitValueChange(!cell.value.value);
|
emitValueChange(!cell.value.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'image': {
|
|
||||||
const file = await selectFile(rootEl.value);
|
|
||||||
if (file) {
|
|
||||||
emitValueChange(JSON.stringify(file));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1170,7 +1170,9 @@ function patchData(newItems: DataSource[]) {
|
||||||
const newValue = newItem[_col.setting.bindTo];
|
const newValue = newItem[_col.setting.bindTo];
|
||||||
if (oldCell.value !== newValue) {
|
if (oldCell.value !== newValue) {
|
||||||
oldCell.violation = cellValidation(oldCell, newValue);
|
oldCell.violation = cellValidation(oldCell, newValue);
|
||||||
oldCell.value = newValue;
|
oldCell.value = _col.setting.valueTransformer
|
||||||
|
? _col.setting.valueTransformer(holder.row, _col, newValue)
|
||||||
|
: newValue;
|
||||||
changedCells.push(oldCell);
|
changedCells.push(oldCell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1199,6 +1201,8 @@ function patchData(newItems: DataSource[]) {
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
state.value = 'normal';
|
||||||
|
|
||||||
const bindToList = columnSettings.map(it => it.bindTo);
|
const bindToList = columnSettings.map(it => it.bindTo);
|
||||||
if (new Set(bindToList).size !== columnSettings.length) {
|
if (new Set(bindToList).size !== columnSettings.length) {
|
||||||
// 取得元のプロパティ名重複は許容したくない
|
// 取得元のプロパティ名重複は許容したくない
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { GridRow } from '@/components/grid/row.js';
|
||||||
import { MenuItem } from '@/types/menu.js';
|
import { MenuItem } from '@/types/menu.js';
|
||||||
import { GridContext } from '@/components/grid/grid-event.js';
|
import { GridContext } from '@/components/grid/grid-event.js';
|
||||||
|
|
||||||
export type CellValue = string | boolean | number | undefined | null
|
export type CellValue = string | boolean | number | undefined | null | Array<unknown> | Object;
|
||||||
|
|
||||||
export type CellAddress = {
|
export type CellAddress = {
|
||||||
row: number;
|
row: number;
|
||||||
|
@ -41,9 +41,13 @@ export function createCell(
|
||||||
value: CellValue,
|
value: CellValue,
|
||||||
setting: GridCellSetting,
|
setting: GridCellSetting,
|
||||||
): GridCell {
|
): GridCell {
|
||||||
|
const newValue = (row.using && column.setting.valueTransformer)
|
||||||
|
? column.setting.valueTransformer(row, column, value)
|
||||||
|
: value;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
address: { row: row.index, col: column.index },
|
address: { row: row.index, col: column.index },
|
||||||
value,
|
value: newValue,
|
||||||
column,
|
column,
|
||||||
row,
|
row,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
|
|
@ -8,7 +8,8 @@ import { GridContext } from '@/components/grid/grid-event.js';
|
||||||
|
|
||||||
export type ColumnType = 'text' | 'number' | 'date' | 'boolean' | 'image' | 'hidden';
|
export type ColumnType = 'text' | 'number' | 'date' | 'boolean' | 'image' | 'hidden';
|
||||||
|
|
||||||
export type CellValueConverter = (row: GridRow, col: GridColumn, value: CellValue) => CellValue;
|
export type CustomValueEditor = (row: GridRow, col: GridColumn, value: CellValue, cellElement: HTMLElement) => Promise<CellValue>;
|
||||||
|
export type CellValueTransformer = (row: GridRow, col: GridColumn, value: CellValue) => CellValue;
|
||||||
export type GridColumnContextMenuFactory = (col: GridColumn, context: GridContext) => MenuItem[];
|
export type GridColumnContextMenuFactory = (col: GridColumn, context: GridContext) => MenuItem[];
|
||||||
|
|
||||||
export type GridColumnSetting = {
|
export type GridColumnSetting = {
|
||||||
|
@ -19,7 +20,8 @@ export type GridColumnSetting = {
|
||||||
width: SizeStyle;
|
width: SizeStyle;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
validators?: CellValidator[];
|
validators?: CellValidator[];
|
||||||
valueConverter?: CellValueConverter;
|
customValueEditor?: CustomValueEditor;
|
||||||
|
valueTransformer?: CellValueTransformer;
|
||||||
contextMenuFactory?: GridColumnContextMenuFactory;
|
contextMenuFactory?: GridColumnContextMenuFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,18 +41,22 @@ class OptInGridUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
copyToClipboard(gridItems: Ref<DataSource[]>, context: GridContext) {
|
copyToClipboard(gridItems: Ref<DataSource[]> | DataSource[], context: GridContext) {
|
||||||
|
const items = typeof gridItems === 'object' ? (gridItems as Ref<DataSource[]>).value : gridItems;
|
||||||
const lines = Array.of<string>();
|
const lines = Array.of<string>();
|
||||||
const bounds = context.randedBounds;
|
const bounds = context.randedBounds;
|
||||||
|
|
||||||
for (let row = bounds.leftTop.row; row <= bounds.rightBottom.row; row++) {
|
for (let row = bounds.leftTop.row; row <= bounds.rightBottom.row; row++) {
|
||||||
const items = Array.of<string>();
|
const rowItems = Array.of<string>();
|
||||||
for (let col = bounds.leftTop.col; col <= bounds.rightBottom.col; col++) {
|
for (let col = bounds.leftTop.col; col <= bounds.rightBottom.col; col++) {
|
||||||
const bindTo = context.columns[col].setting.bindTo;
|
const bindTo = context.columns[col].setting.bindTo;
|
||||||
const cell = gridItems.value[row][bindTo];
|
const cell = items[row][bindTo];
|
||||||
items.push(cell?.toString() ?? '');
|
const value = typeof cell === 'object' || Array.isArray(cell)
|
||||||
|
? JSON.stringify(cell)
|
||||||
|
: cell?.toString() ?? '';
|
||||||
|
rowItems.push(value);
|
||||||
}
|
}
|
||||||
lines.push(items.join('\t'));
|
lines.push(rowItems.join('\t'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = lines.join('\n');
|
const text = lines.join('\n');
|
||||||
|
@ -66,9 +70,12 @@ class OptInGridUtils {
|
||||||
async pasteFromClipboard(
|
async pasteFromClipboard(
|
||||||
gridItems: Ref<DataSource[]>,
|
gridItems: Ref<DataSource[]>,
|
||||||
context: GridContext,
|
context: GridContext,
|
||||||
|
valueConverters?: { bindTo: string, converter: (value: string) => CellValue }[],
|
||||||
) {
|
) {
|
||||||
function parseValue(value: string, type: GridColumnSetting['type']): CellValue {
|
const converterMap = new Map<string, (value: string) => CellValue>(valueConverters?.map(it => [it.bindTo, it.converter]) ?? []);
|
||||||
switch (type) {
|
|
||||||
|
function parseValue(value: string, setting: GridColumnSetting): CellValue {
|
||||||
|
switch (setting.type) {
|
||||||
case 'number': {
|
case 'number': {
|
||||||
return Number(value);
|
return Number(value);
|
||||||
}
|
}
|
||||||
|
@ -76,7 +83,9 @@ class OptInGridUtils {
|
||||||
return value === 'true';
|
return value === 'true';
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return value;
|
return converterMap.has(setting.bindTo)
|
||||||
|
? converterMap.get(setting.bindTo)!(value)
|
||||||
|
: value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +104,7 @@ class OptInGridUtils {
|
||||||
// 単独文字列の場合は選択範囲全体に同じテキストを貼り付ける
|
// 単独文字列の場合は選択範囲全体に同じテキストを貼り付ける
|
||||||
const ranges = context.rangedCells;
|
const ranges = context.rangedCells;
|
||||||
for (const cell of ranges) {
|
for (const cell of ranges) {
|
||||||
gridItems.value[cell.row.index][cell.column.setting.bindTo] = parseValue(lines[0][0], cell.column.setting.type);
|
gridItems.value[cell.row.index][cell.column.setting.bindTo] = parseValue(lines[0][0], cell.column.setting);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 表形式文字列の場合は表形式にパースし、選択範囲に合うように貼り付ける
|
// 表形式文字列の場合は表形式にパースし、選択範囲に合うように貼り付ける
|
||||||
|
@ -117,13 +126,13 @@ class OptInGridUtils {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
gridItems.value[row][columns[col].setting.bindTo] = parseValue(items[colIdx], columns[col].setting.type);
|
gridItems.value[row][columns[col].setting.bindTo] = parseValue(items[colIdx], columns[col].setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteSelectionRange(gridItems: Ref<DataSource[]>, context: GridContext) {
|
deleteSelectionRange(gridItems: Ref<Record<string, any>[]>, context: GridContext) {
|
||||||
if (context.rangedRows.length > 0) {
|
if (context.rangedRows.length > 0) {
|
||||||
const deletedIndexes = context.rangedRows.map(it => it.index);
|
const deletedIndexes = context.rangedRows.map(it => it.index);
|
||||||
gridItems.value = gridItems.value.filter((_, index) => !deletedIndexes.includes(index));
|
gridItems.value = gridItems.value.filter((_, index) => !deletedIndexes.includes(index));
|
||||||
|
|
|
@ -475,6 +475,27 @@ export async function selectDriveFolder(multiple: boolean) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function selectRole(params: {
|
||||||
|
initialRoleIds?: string[],
|
||||||
|
title?: string,
|
||||||
|
infoMessage?: string,
|
||||||
|
publicOnly?: boolean,
|
||||||
|
}): Promise<
|
||||||
|
{ canceled: true; result: undefined; } |
|
||||||
|
{ canceled: false; result: Misskey.entities.Role[] }
|
||||||
|
> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, {
|
||||||
|
done: roles => {
|
||||||
|
resolve({ canceled: false, result: roles });
|
||||||
|
},
|
||||||
|
closed: () => {
|
||||||
|
resolve({ canceled: true, result: undefined });
|
||||||
|
},
|
||||||
|
}, 'closed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function pickEmoji(src: HTMLElement | null, opts) {
|
export async function pickEmoji(src: HTMLElement | null, opts) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
popup(MkEmojiPickerDialog, {
|
popup(MkEmojiPickerDialog, {
|
||||||
|
|
|
@ -56,6 +56,18 @@
|
||||||
<MkInput v-model="queryUpdatedAtTo" :debounce="true" type="date" autocapitalize="off" class="col3 row3">
|
<MkInput v-model="queryUpdatedAtTo" :debounce="true" type="date" autocapitalize="off" class="col3 row3">
|
||||||
<template #label>updatedAt(to)</template>
|
<template #label>updatedAt(to)</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
<MkInput
|
||||||
|
v-model="queryRolesText"
|
||||||
|
:debounce="true"
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
autocapitalize="off"
|
||||||
|
class="col1 row4"
|
||||||
|
@click="onQueryRolesEditClicked"
|
||||||
|
>
|
||||||
|
<template #label>role</template>
|
||||||
|
<template #suffix><span class="ti ti-pencil"/></template>
|
||||||
|
</MkInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkFolder :spacerMax="8" :spacerMin="8">
|
<MkFolder :spacerMax="8" :spacerMin="8">
|
||||||
|
@ -153,6 +165,8 @@ import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
import { GridSetting } from '@/components/grid/grid.js';
|
import { GridSetting } from '@/components/grid/grid.js';
|
||||||
import MkTagItem from '@/components/MkTagItem.vue';
|
import MkTagItem from '@/components/MkTagItem.vue';
|
||||||
import { MenuItem } from '@/types/menu.js';
|
import { MenuItem } from '@/types/menu.js';
|
||||||
|
import { CellValue } from '@/components/grid/cell.js';
|
||||||
|
import { selectFile } from '@/scripts/select-file.js';
|
||||||
|
|
||||||
type GridItem = {
|
type GridItem = {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
|
@ -165,7 +179,7 @@ type GridItem = {
|
||||||
license: string;
|
license: string;
|
||||||
isSensitive: boolean;
|
isSensitive: boolean;
|
||||||
localOnly: boolean;
|
localOnly: boolean;
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: string;
|
roleIdsThatCanBeUsedThisEmojiAsReaction: { id: string, name: string }[];
|
||||||
fileId?: string;
|
fileId?: string;
|
||||||
updatedAt: string | null;
|
updatedAt: string | null;
|
||||||
publicUrl?: string | null;
|
publicUrl?: string | null;
|
||||||
|
@ -230,14 +244,54 @@ function setupGrid(): GridSetting {
|
||||||
},
|
},
|
||||||
cols: [
|
cols: [
|
||||||
{ bindTo: 'checked', icon: 'ti-trash', type: 'boolean', editable: true, width: 34 },
|
{ bindTo: 'checked', icon: 'ti-trash', type: 'boolean', editable: true, width: 34 },
|
||||||
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: true, width: 'auto', validators: [required] },
|
{
|
||||||
|
bindTo: 'url', icon: 'ti-icons', type: 'image', editable: true, width: 'auto', validators: [required],
|
||||||
|
customValueEditor: async (row, col, value, cellElement) => {
|
||||||
|
const file = await selectFile(cellElement);
|
||||||
|
if (file) {
|
||||||
|
gridItems.value[row.index].url = file.url;
|
||||||
|
gridItems.value[row.index].fileId = file.id;
|
||||||
|
} else {
|
||||||
|
gridItems.value[row.index].url = '';
|
||||||
|
gridItems.value[row.index].fileId = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file ? file.url : '';
|
||||||
|
},
|
||||||
|
},
|
||||||
{ bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140, validators: [required, regex] },
|
{ bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140, validators: [required, regex] },
|
||||||
{ bindTo: 'category', title: 'category', type: 'text', editable: true, width: 140 },
|
{ bindTo: 'category', title: 'category', type: 'text', editable: true, width: 140 },
|
||||||
{ bindTo: 'aliases', title: 'aliases', type: 'text', editable: true, width: 140 },
|
{ bindTo: 'aliases', title: 'aliases', type: 'text', editable: true, width: 140 },
|
||||||
{ bindTo: 'license', title: 'license', type: 'text', editable: true, width: 140 },
|
{ bindTo: 'license', title: 'license', type: 'text', editable: true, width: 140 },
|
||||||
{ bindTo: 'isSensitive', title: 'sensitive', type: 'boolean', editable: true, width: 90 },
|
{ bindTo: 'isSensitive', title: 'sensitive', type: 'boolean', editable: true, width: 90 },
|
||||||
{ bindTo: 'localOnly', title: 'localOnly', type: 'boolean', editable: true, width: 90 },
|
{ bindTo: 'localOnly', title: 'localOnly', type: 'boolean', editable: true, width: 90 },
|
||||||
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 140 },
|
{
|
||||||
|
bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 140,
|
||||||
|
valueTransformer: (row) => {
|
||||||
|
// バックエンドからからはIDと名前のペア配列で受け取るが、表示にIDがあると煩雑なので名前だけにする
|
||||||
|
return gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction
|
||||||
|
.map(({ name }) => name)
|
||||||
|
.join(',');
|
||||||
|
},
|
||||||
|
customValueEditor: async (row) => {
|
||||||
|
// ID直記入は体験的に最悪なのでモーダルを使って入力する
|
||||||
|
const current = gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction.map(it => it.id);
|
||||||
|
const result = await os.selectRole({
|
||||||
|
initialRoleIds: current,
|
||||||
|
title: i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction,
|
||||||
|
infoMessage: i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription,
|
||||||
|
publicOnly: true,
|
||||||
|
});
|
||||||
|
if (result.canceled) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transform = result.result.map(it => ({ id: it.id, name: it.name }));
|
||||||
|
gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = transform;
|
||||||
|
|
||||||
|
return transform;
|
||||||
|
},
|
||||||
|
},
|
||||||
{ bindTo: 'updatedAt', type: 'text', editable: false, width: 'auto' },
|
{ bindTo: 'updatedAt', type: 'text', editable: false, width: 'auto' },
|
||||||
{ bindTo: 'publicUrl', type: 'text', editable: false, width: 180 },
|
{ bindTo: 'publicUrl', type: 'text', editable: false, width: 180 },
|
||||||
{ bindTo: 'originalUrl', type: 'text', editable: false, width: 180 },
|
{ bindTo: 'originalUrl', type: 'text', editable: false, width: 180 },
|
||||||
|
@ -249,7 +303,9 @@ function setupGrid(): GridSetting {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
text: '選択範囲をコピー',
|
text: '選択範囲をコピー',
|
||||||
icon: 'ti ti-copy',
|
icon: 'ti ti-copy',
|
||||||
action: () => optInGridUtils.copyToClipboard(gridItems, context),
|
action: () => {
|
||||||
|
return optInGridUtils.copyToClipboard(gridItems, context);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'button',
|
type: 'button',
|
||||||
|
@ -286,6 +342,7 @@ const queryUpdatedAtFrom = ref<string | null>(null);
|
||||||
const queryUpdatedAtTo = ref<string | null>(null);
|
const queryUpdatedAtTo = ref<string | null>(null);
|
||||||
const querySensitive = ref<string | null>(null);
|
const querySensitive = ref<string | null>(null);
|
||||||
const queryLocalOnly = ref<string | null>(null);
|
const queryLocalOnly = ref<string | null>(null);
|
||||||
|
const queryRoles = ref<{ id: string, name: string }[]>([]);
|
||||||
const previousQuery = ref<string | undefined>(undefined);
|
const previousQuery = ref<string | undefined>(undefined);
|
||||||
const sortOrders = ref<GridSortOrder[]>([]);
|
const sortOrders = ref<GridSortOrder[]>([]);
|
||||||
const requestLogs = ref<RequestLogItem[]>([]);
|
const requestLogs = ref<RequestLogItem[]>([]);
|
||||||
|
@ -295,6 +352,7 @@ const originGridItems = ref<GridItem[]>([]);
|
||||||
const updateButtonDisabled = ref<boolean>(false);
|
const updateButtonDisabled = ref<boolean>(false);
|
||||||
|
|
||||||
const spMode = computed(() => ['smartphone', 'tablet'].includes(deviceKind));
|
const spMode = computed(() => ['smartphone', 'tablet'].includes(deviceKind));
|
||||||
|
const queryRolesText = computed(() => queryRoles.value.map(it => it.name).join(','));
|
||||||
|
|
||||||
async function onUpdateButtonClicked() {
|
async function onUpdateButtonClicked() {
|
||||||
const _items = gridItems.value;
|
const _items = gridItems.value;
|
||||||
|
@ -334,7 +392,7 @@ async function onUpdateButtonClicked() {
|
||||||
license: emptyStrToNull(item.license),
|
license: emptyStrToNull(item.license),
|
||||||
isSensitive: item.isSensitive,
|
isSensitive: item.isSensitive,
|
||||||
localOnly: item.localOnly,
|
localOnly: item.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction),
|
roleIdsThatCanBeUsedThisEmojiAsReaction: item.roleIdsThatCanBeUsedThisEmojiAsReaction.map(it => it.id),
|
||||||
fileId: item.fileId,
|
fileId: item.fileId,
|
||||||
})
|
})
|
||||||
.then(() => ({ item, success: true, err: undefined }))
|
.then(() => ({ item, success: true, err: undefined }))
|
||||||
|
@ -402,6 +460,19 @@ function onGridResetButtonClicked() {
|
||||||
refreshGridItems();
|
refreshGridItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onQueryRolesEditClicked() {
|
||||||
|
const result = await os.selectRole({
|
||||||
|
initialRoleIds: queryRoles.value.map(it => it.id),
|
||||||
|
title: '絵文字に設定されたロールで検索',
|
||||||
|
publicOnly: true,
|
||||||
|
});
|
||||||
|
if (result.canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
queryRoles.value = result.result;
|
||||||
|
}
|
||||||
|
|
||||||
function onToggleSortOrderButtonClicked(order: GridSortOrder) {
|
function onToggleSortOrderButtonClicked(order: GridSortOrder) {
|
||||||
console.log(order);
|
console.log(order);
|
||||||
switch (order.direction) {
|
switch (order.direction) {
|
||||||
|
@ -446,6 +517,7 @@ function onQueryResetButtonClicked() {
|
||||||
queryUpdatedAtTo.value = null;
|
queryUpdatedAtTo.value = null;
|
||||||
querySensitive.value = null;
|
querySensitive.value = null;
|
||||||
queryLocalOnly.value = null;
|
queryLocalOnly.value = null;
|
||||||
|
queryRoles.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onPageChanged(pageNumber: number) {
|
async function onPageChanged(pageNumber: number) {
|
||||||
|
@ -474,17 +546,27 @@ function onGridCellValidation(event: GridCellValidationEvent) {
|
||||||
function onGridCellValueChange(event: GridCellValueChangeEvent) {
|
function onGridCellValueChange(event: GridCellValueChangeEvent) {
|
||||||
const { row, column, newValue } = event;
|
const { row, column, newValue } = event;
|
||||||
if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
|
if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
|
||||||
if (column.setting.bindTo === 'url') {
|
|
||||||
const file = JSON.parse(newValue as string) as Misskey.entities.DriveFile;
|
|
||||||
gridItems.value[row.index].url = file.url;
|
|
||||||
gridItems.value[row.index].fileId = file.id;
|
|
||||||
} else {
|
|
||||||
gridItems.value[row.index][column.setting.bindTo] = newValue;
|
gridItems.value[row.index][column.setting.bindTo] = newValue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onGridKeyDown(event: GridKeyDownEvent, currentState: GridContext) {
|
async function onGridKeyDown(event: GridKeyDownEvent, currentState: GridContext) {
|
||||||
|
function roleIdConverter(value: string): CellValue {
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(value);
|
||||||
|
if (!Array.isArray(obj)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!obj.every(it => typeof it === 'object' && 'id' in it && 'name' in it)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj.map(it => ({ id: it.id, name: it.name }));
|
||||||
|
} catch (ex) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { ctrlKey, shiftKey, code } = event.event;
|
const { ctrlKey, shiftKey, code } = event.event;
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
|
@ -498,7 +580,13 @@ async function onGridKeyDown(event: GridKeyDownEvent, currentState: GridContext)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'KeyV': {
|
case 'KeyV': {
|
||||||
await optInGridUtils.pasteFromClipboard(gridItems, currentState);
|
await optInGridUtils.pasteFromClipboard(
|
||||||
|
gridItems,
|
||||||
|
currentState,
|
||||||
|
[
|
||||||
|
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', converter: roleIdConverter },
|
||||||
|
],
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -543,6 +631,7 @@ async function refreshCustomEmojis() {
|
||||||
localOnly: queryLocalOnly.value ? Boolean(queryLocalOnly.value).valueOf() : undefined,
|
localOnly: queryLocalOnly.value ? Boolean(queryLocalOnly.value).valueOf() : undefined,
|
||||||
updatedAtFrom: emptyStrToUndefined(queryUpdatedAtFrom.value),
|
updatedAtFrom: emptyStrToUndefined(queryUpdatedAtFrom.value),
|
||||||
updatedAtTo: emptyStrToUndefined(queryUpdatedAtTo.value),
|
updatedAtTo: emptyStrToUndefined(queryUpdatedAtTo.value),
|
||||||
|
roleIds: queryRoles.value.map(it => it.id),
|
||||||
hostType: 'local',
|
hostType: 'local',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -584,7 +673,7 @@ function refreshGridItems() {
|
||||||
license: it.license ?? '',
|
license: it.license ?? '',
|
||||||
isSensitive: it.isSensitive,
|
isSensitive: it.isSensitive,
|
||||||
localOnly: it.localOnly,
|
localOnly: it.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: it.roleIdsThatCanBeUsedThisEmojiAsReaction.join(','),
|
roleIdsThatCanBeUsedThisEmojiAsReaction: it.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||||
updatedAt: it.updatedAt,
|
updatedAt: it.updatedAt,
|
||||||
publicUrl: it.publicUrl,
|
publicUrl: it.publicUrl,
|
||||||
originalUrl: it.originalUrl,
|
originalUrl: it.originalUrl,
|
||||||
|
|
|
@ -116,7 +116,7 @@ type GridItem = {
|
||||||
license: string;
|
license: string;
|
||||||
isSensitive: boolean;
|
isSensitive: boolean;
|
||||||
localOnly: boolean;
|
localOnly: boolean;
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: string;
|
roleIdsThatCanBeUsedThisEmojiAsReaction: { id: string, name: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupGrid(): GridSetting {
|
function setupGrid(): GridSetting {
|
||||||
|
@ -159,7 +159,33 @@ function setupGrid(): GridSetting {
|
||||||
{ bindTo: 'license', title: 'license', type: 'text', editable: true, width: 140 },
|
{ bindTo: 'license', title: 'license', type: 'text', editable: true, width: 140 },
|
||||||
{ bindTo: 'isSensitive', title: 'sensitive', type: 'boolean', editable: true, width: 90 },
|
{ bindTo: 'isSensitive', title: 'sensitive', type: 'boolean', editable: true, width: 90 },
|
||||||
{ bindTo: 'localOnly', title: 'localOnly', type: 'boolean', editable: true, width: 90 },
|
{ bindTo: 'localOnly', title: 'localOnly', type: 'boolean', editable: true, width: 90 },
|
||||||
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 100 },
|
{
|
||||||
|
bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 140,
|
||||||
|
valueTransformer: (row) => {
|
||||||
|
// バックエンドからからはIDと名前のペア配列で受け取るが、表示にIDがあると煩雑なので名前だけにする
|
||||||
|
return gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction
|
||||||
|
.map(({ name }) => name)
|
||||||
|
.join(',');
|
||||||
|
},
|
||||||
|
customValueEditor: async (row) => {
|
||||||
|
// ID直記入は体験的に最悪なのでモーダルを使って入力する
|
||||||
|
const current = gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction.map(it => it.id);
|
||||||
|
const result = await os.selectRole({
|
||||||
|
initialRoleIds: current,
|
||||||
|
title: i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction,
|
||||||
|
infoMessage: i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription,
|
||||||
|
publicOnly: true,
|
||||||
|
});
|
||||||
|
if (result.canceled) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transform = result.result.map(it => ({ id: it.id, name: it.name }));
|
||||||
|
gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = transform;
|
||||||
|
|
||||||
|
return transform;
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
cells: {
|
cells: {
|
||||||
contextMenuFactory: (col, row, value, context) => {
|
contextMenuFactory: (col, row, value, context) => {
|
||||||
|
@ -214,7 +240,7 @@ async function onRegistryClicked() {
|
||||||
license: emptyStrToNull(item.license),
|
license: emptyStrToNull(item.license),
|
||||||
isSensitive: item.isSensitive,
|
isSensitive: item.isSensitive,
|
||||||
localOnly: item.localOnly,
|
localOnly: item.localOnly,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction),
|
roleIdsThatCanBeUsedThisEmojiAsReaction: item.roleIdsThatCanBeUsedThisEmojiAsReaction.map(it => it.id),
|
||||||
fileId: item.fileId!,
|
fileId: item.fileId!,
|
||||||
})
|
})
|
||||||
.then(() => ({ item, success: true, err: undefined }))
|
.then(() => ({ item, success: true, err: undefined }))
|
||||||
|
@ -372,7 +398,7 @@ function fromDriveFile(it: Misskey.entities.DriveFile): GridItem {
|
||||||
license: '',
|
license: '',
|
||||||
isSensitive: it.isSensitive,
|
isSensitive: it.isSensitive,
|
||||||
localOnly: false,
|
localOnly: false,
|
||||||
roleIdsThatCanBeUsedThisEmojiAsReaction: '',
|
roleIdsThatCanBeUsedThisEmojiAsReaction: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue