fix validation and register roles
This commit is contained in:
parent
cb668b22ad
commit
390af67949
|
@ -269,7 +269,7 @@ useTooltip(rootEl, (showing) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = cell.value.violation.violations.map(it => it.result.message).join('\n');
|
const content = cell.value.violation.violations.filter(it => !it.valid).map(it => it.result.message).join('\n');
|
||||||
os.popup(defineAsyncComponent(() => import('@/components/grid/MkCellTooltip.vue')), {
|
os.popup(defineAsyncComponent(() => import('@/components/grid/MkCellTooltip.vue')), {
|
||||||
showing,
|
showing,
|
||||||
content,
|
content,
|
||||||
|
|
|
@ -76,12 +76,14 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// non-reactive
|
// non-reactive
|
||||||
|
// eslint-disable-next-line vue/no-setup-props-destructure
|
||||||
const rowSetting: Required<GridRowSetting> = {
|
const rowSetting: Required<GridRowSetting> = {
|
||||||
...defaultGridRowSetting,
|
...defaultGridRowSetting,
|
||||||
...props.settings.row,
|
...props.settings.row,
|
||||||
};
|
};
|
||||||
|
|
||||||
// non-reactive
|
// non-reactive
|
||||||
|
// eslint-disable-next-line vue/no-setup-props-destructure
|
||||||
const columnSettings = props.settings.cols;
|
const columnSettings = props.settings.cols;
|
||||||
|
|
||||||
// non-reactive
|
// non-reactive
|
||||||
|
@ -1117,21 +1119,22 @@ function refreshData() {
|
||||||
// 行・列の定義から、元データの配列より値を取得してセルを作成する。
|
// 行・列の定義から、元データの配列より値を取得してセルを作成する。
|
||||||
// 行・列の定義はそれぞれインデックスを持っており、そのインデックスは元データの配列番地に対応している。
|
// 行・列の定義はそれぞれインデックスを持っており、そのインデックスは元データの配列番地に対応している。
|
||||||
const _cells: RowHolder[] = _rows.map(row => {
|
const _cells: RowHolder[] = _rows.map(row => {
|
||||||
const cells = row.using
|
const newCells = row.using
|
||||||
? _cols.map(col => {
|
? _cols.map(col => createCell(col, row, _data[row.index][col.setting.bindTo], cellSettings))
|
||||||
const cell = createCell(col, row, _data[row.index][col.setting.bindTo], cellSettings);
|
|
||||||
cell.violation = cellValidation(cell, cell.value);
|
|
||||||
return cell;
|
|
||||||
})
|
|
||||||
: _cols.map(col => createCell(col, row, undefined, cellSettings));
|
: _cols.map(col => createCell(col, row, undefined, cellSettings));
|
||||||
|
|
||||||
return { row, cells, origin: _data[row.index] };
|
return { row, cells: newCells, origin: _data[row.index] };
|
||||||
});
|
});
|
||||||
|
|
||||||
rows.value = _rows;
|
rows.value = _rows;
|
||||||
cells.value = _cells;
|
cells.value = _cells;
|
||||||
|
|
||||||
applyRowRules(_cells.filter(it => it.row.using).flatMap(it => it.cells));
|
const allCells = _cells.filter(it => it.row.using).flatMap(it => it.cells);
|
||||||
|
for (const cell of allCells) {
|
||||||
|
cell.violation = cellValidation(allCells, cell, cell.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyRowRules(allCells);
|
||||||
|
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
console.log('[grid][refresh-data][end]');
|
console.log('[grid][refresh-data][end]');
|
||||||
|
@ -1205,7 +1208,6 @@ function patchData(newItems: DataSource[]) {
|
||||||
const oldCell = oldCells[colIdx];
|
const oldCell = oldCells[colIdx];
|
||||||
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.value = _col.setting.valueTransformer
|
oldCell.value = _col.setting.valueTransformer
|
||||||
? _col.setting.valueTransformer(holder.row, _col, newValue)
|
? _col.setting.valueTransformer(holder.row, _col, newValue)
|
||||||
: newValue;
|
: newValue;
|
||||||
|
@ -1215,6 +1217,11 @@ function patchData(newItems: DataSource[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedCells.length > 0) {
|
if (changedCells.length > 0) {
|
||||||
|
const allCells = cells.value.slice(0, newItems.length).flatMap(it => it.cells);
|
||||||
|
for (const cell of allCells) {
|
||||||
|
cell.violation = cellValidation(allCells, cell, cell.value);
|
||||||
|
}
|
||||||
|
|
||||||
applyRowRules(changedCells);
|
applyRowRules(changedCells);
|
||||||
|
|
||||||
// セル値が書き換わっており、バリデーションの結果も変わっているので外部に通知する必要がある
|
// セル値が書き換わっており、バリデーションの結果も変わっているので外部に通知する必要がある
|
||||||
|
|
|
@ -6,6 +6,7 @@ export type ValidatorParams = {
|
||||||
column: GridColumn;
|
column: GridColumn;
|
||||||
row: GridRow;
|
row: GridRow;
|
||||||
value: CellValue;
|
value: CellValue;
|
||||||
|
allCells: GridCell[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ValidatorResult = {
|
export type ValidatorResult = {
|
||||||
|
@ -13,7 +14,7 @@ export type ValidatorResult = {
|
||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CellValidator = {
|
export type GridCellValidator = {
|
||||||
name?: string;
|
name?: string;
|
||||||
ignoreViolation?: boolean;
|
ignoreViolation?: boolean;
|
||||||
validate: (params: ValidatorParams) => ValidatorResult;
|
validate: (params: ValidatorParams) => ValidatorResult;
|
||||||
|
@ -27,11 +28,11 @@ export type ValidateViolation = {
|
||||||
|
|
||||||
export type ValidateViolationItem = {
|
export type ValidateViolationItem = {
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
validator: CellValidator;
|
validator: GridCellValidator;
|
||||||
result: ValidatorResult;
|
result: ValidatorResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cellValidation(cell: GridCell, newValue: CellValue): ValidateViolation {
|
export function cellValidation(allCells: GridCell[], cell: GridCell, newValue: CellValue): ValidateViolation {
|
||||||
const { column, row } = cell;
|
const { column, row } = cell;
|
||||||
const validators = column.setting.validators ?? [];
|
const validators = column.setting.validators ?? [];
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ export function cellValidation(cell: GridCell, newValue: CellValue): ValidateVio
|
||||||
column,
|
column,
|
||||||
row,
|
row,
|
||||||
value: newValue,
|
value: newValue,
|
||||||
|
allCells,
|
||||||
};
|
};
|
||||||
|
|
||||||
const violations: ValidateViolationItem[] = validators.map(validator => {
|
const violations: ValidateViolationItem[] = validators.map(validator => {
|
||||||
|
@ -58,11 +60,10 @@ export function cellValidation(cell: GridCell, newValue: CellValue): ValidateVio
|
||||||
}
|
}
|
||||||
|
|
||||||
class ValidatorPreset {
|
class ValidatorPreset {
|
||||||
required(): CellValidator {
|
required(): GridCellValidator {
|
||||||
return {
|
return {
|
||||||
name: 'required',
|
name: 'required',
|
||||||
validate: (params: ValidatorParams): ValidatorResult => {
|
validate: ({ value }): ValidatorResult => {
|
||||||
const { value } = params;
|
|
||||||
return {
|
return {
|
||||||
valid: value !== null && value !== undefined && value !== '',
|
valid: value !== null && value !== undefined && value !== '',
|
||||||
message: 'This field is required.',
|
message: 'This field is required.',
|
||||||
|
@ -71,11 +72,10 @@ class ValidatorPreset {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
regex(pattern: RegExp): CellValidator {
|
regex(pattern: RegExp): GridCellValidator {
|
||||||
return {
|
return {
|
||||||
name: 'regex',
|
name: 'regex',
|
||||||
validate: (params: ValidatorParams): ValidatorResult => {
|
validate: ({ value, column }): ValidatorResult => {
|
||||||
const { value, column } = params;
|
|
||||||
if (column.setting.type !== 'text') {
|
if (column.setting.type !== 'text') {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
|
@ -90,6 +90,22 @@ class ValidatorPreset {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unique(): GridCellValidator {
|
||||||
|
return {
|
||||||
|
name: 'unique',
|
||||||
|
validate: ({ column, row, value, allCells }): ValidatorResult => {
|
||||||
|
const bindTo = column.setting.bindTo;
|
||||||
|
const isUnique = allCells
|
||||||
|
.filter(it => it.column.setting.bindTo === bindTo && it.row.index !== row.index)
|
||||||
|
.every(cell => cell.value !== value);
|
||||||
|
return {
|
||||||
|
valid: isUnique,
|
||||||
|
message: 'This value is already used.',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const validators = new ValidatorPreset();
|
export const validators = new ValidatorPreset();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CellValidator } from '@/components/grid/cell-validators.js';
|
import { GridCellValidator } from '@/components/grid/cell-validators.js';
|
||||||
import { Size, SizeStyle } from '@/components/grid/grid.js';
|
import { Size, SizeStyle } from '@/components/grid/grid.js';
|
||||||
import { calcCellWidth } from '@/components/grid/grid-utils.js';
|
import { calcCellWidth } from '@/components/grid/grid-utils.js';
|
||||||
import { CellValue, GridCell } from '@/components/grid/cell.js';
|
import { CellValue, GridCell } from '@/components/grid/cell.js';
|
||||||
|
@ -19,7 +19,7 @@ export type GridColumnSetting = {
|
||||||
type: ColumnType;
|
type: ColumnType;
|
||||||
width: SizeStyle;
|
width: SizeStyle;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
validators?: CellValidator[];
|
validators?: GridCellValidator[];
|
||||||
customValueEditor?: CustomValueEditor;
|
customValueEditor?: CustomValueEditor;
|
||||||
valueTransformer?: CellValueTransformer;
|
valueTransformer?: CellValueTransformer;
|
||||||
contextMenuFactory?: GridColumnContextMenuFactory;
|
contextMenuFactory?: GridColumnContextMenuFactory;
|
||||||
|
|
|
@ -17,3 +17,20 @@ export function emptyStrToEmptyArray(value: string) {
|
||||||
return value === '' ? [] : value.split(',').map(it => it.trim());
|
return value === '' ? [] : value.split(',').map(it => it.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function roleIdsParser(text: string): { id: string, name: string }[] {
|
||||||
|
// idとnameのペア配列をJSONで受け取る。それ以外の形式は許容しない
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(text);
|
||||||
|
if (!Array.isArray(obj)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!obj.every(it => typeof it === 'object' && 'id' in it && 'name' in it)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj.map(it => ({ id: it.id, name: it.name }));
|
||||||
|
} catch (ex) {
|
||||||
|
console.warn(ex);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -189,13 +189,13 @@ import {
|
||||||
emptyStrToEmptyArray,
|
emptyStrToEmptyArray,
|
||||||
emptyStrToNull,
|
emptyStrToNull,
|
||||||
emptyStrToUndefined,
|
emptyStrToUndefined,
|
||||||
RequestLogItem,
|
RequestLogItem, roleIdsParser,
|
||||||
} from '@/pages/admin/custom-emojis-manager.impl.js';
|
} from '@/pages/admin/custom-emojis-manager.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';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { validators } from '@/components/grid/cell-validators.js';
|
import { GridCellValidator, validators } from '@/components/grid/cell-validators.js';
|
||||||
import { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
|
import { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.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';
|
||||||
|
@ -247,6 +247,7 @@ type GridSortOrder = {
|
||||||
function setupGrid(): GridSetting {
|
function setupGrid(): GridSetting {
|
||||||
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 unique = validators.unique();
|
||||||
return {
|
return {
|
||||||
row: {
|
row: {
|
||||||
showNumber: true,
|
showNumber: true,
|
||||||
|
@ -302,7 +303,10 @@ function setupGrid(): GridSetting {
|
||||||
return file.url;
|
return 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, unique],
|
||||||
|
},
|
||||||
{ 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 },
|
||||||
|
@ -335,23 +339,7 @@ function setupGrid(): GridSetting {
|
||||||
return transform;
|
return transform;
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
paste(text) {
|
paste: roleIdsParser,
|
||||||
// idとnameのペア配列をJSONで受け取る。それ以外の形式は許容しない
|
|
||||||
try {
|
|
||||||
const obj = JSON.parse(text);
|
|
||||||
if (!Array.isArray(obj)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (!obj.every(it => typeof it === 'object' && 'id' in it && 'name' in it)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj.map(it => ({ id: it.id, name: it.name }));
|
|
||||||
} catch (ex) {
|
|
||||||
console.warn(ex);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
delete(cell) {
|
delete(cell) {
|
||||||
// デフォルトはundefinedになるが、このプロパティは空配列にしたい
|
// デフォルトはundefinedになるが、このプロパティは空配列にしたい
|
||||||
gridItems.value[cell.row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = [];
|
gridItems.value[cell.row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = [];
|
||||||
|
|
|
@ -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 { emptyStrToEmptyArray, emptyStrToNull, RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
|
import {
|
||||||
|
emptyStrToEmptyArray,
|
||||||
|
emptyStrToNull,
|
||||||
|
RequestLogItem,
|
||||||
|
roleIdsParser,
|
||||||
|
} from '@/pages/admin/custom-emojis-manager.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';
|
||||||
|
@ -117,6 +122,7 @@ type GridItem = {
|
||||||
function setupGrid(): GridSetting {
|
function setupGrid(): GridSetting {
|
||||||
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 unique = validators.unique();
|
||||||
|
|
||||||
function removeRows(rows: GridRow[]) {
|
function removeRows(rows: GridRow[]) {
|
||||||
const idxes = [...new Set(rows.map(it => it.index))];
|
const idxes = [...new Set(rows.map(it => it.index))];
|
||||||
|
@ -158,7 +164,10 @@ function setupGrid(): GridSetting {
|
||||||
},
|
},
|
||||||
cols: [
|
cols: [
|
||||||
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto', validators: [required] },
|
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto', validators: [required] },
|
||||||
{ 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, unique],
|
||||||
|
},
|
||||||
{ 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 },
|
||||||
|
@ -190,6 +199,13 @@ function setupGrid(): GridSetting {
|
||||||
|
|
||||||
return transform;
|
return transform;
|
||||||
},
|
},
|
||||||
|
events: {
|
||||||
|
paste: roleIdsParser,
|
||||||
|
delete(cell) {
|
||||||
|
// デフォルトはundefinedになるが、このプロパティは空配列にしたい
|
||||||
|
gridItems.value[cell.row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = [];
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
cells: {
|
cells: {
|
||||||
|
@ -343,8 +359,7 @@ async function onDrop(ev: DragEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onFileSelectClicked() {
|
async function onFileSelectClicked() {
|
||||||
const driveFiles = await os.promiseDialog(
|
const driveFiles = await chooseFileFromPc(
|
||||||
chooseFileFromPc(
|
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
uploadFolder: selectedFolderId.value,
|
uploadFolder: selectedFolderId.value,
|
||||||
|
@ -352,7 +367,6 @@ async function onFileSelectClicked() {
|
||||||
// 拡張子は消す
|
// 拡張子は消す
|
||||||
nameConverter: (file) => file.name.replace(/\.[a-zA-Z0-9]+$/, ''),
|
nameConverter: (file) => file.name.replace(/\.[a-zA-Z0-9]+$/, ''),
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gridItems.value.push(...driveFiles.map(fromDriveFile));
|
gridItems.value.push(...driveFiles.map(fromDriveFile));
|
||||||
|
|
|
@ -118,18 +118,18 @@ function createRender(params: {
|
||||||
const body = await new Response(bodyStream).json() as entities.AdminEmojiAddRequest;
|
const body = await new Response(bodyStream).json() as entities.AdminEmojiAddRequest;
|
||||||
|
|
||||||
const fileId = body.fileId;
|
const fileId = body.fileId;
|
||||||
const file = storedDriveFiles.find(f => f.id === fileId);
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const file = storedDriveFiles.find(f => f.id === fileId)!;
|
||||||
|
|
||||||
const em = emoji({
|
const em = emoji({
|
||||||
id: fakeId(),
|
id: fakeId(),
|
||||||
name: body.name,
|
name: body.name,
|
||||||
url: body.url,
|
|
||||||
publicUrl: file.url,
|
publicUrl: file.url,
|
||||||
originalUrl: file.url,
|
originalUrl: file.url,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
aliases: body.aliases,
|
aliases: body.aliases,
|
||||||
category: body.category,
|
category: body.category ?? undefined,
|
||||||
license: body.license,
|
license: body.license ?? undefined,
|
||||||
localOnly: body.localOnly,
|
localOnly: body.localOnly,
|
||||||
isSensitive: body.isSensitive,
|
isSensitive: body.isSensitive,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue