mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 20:44:34 +00:00
add new role conditions for local/remote followers/followees
This commit is contained in:
parent
a31089d054
commit
40a73bfcbe
6 changed files with 239 additions and 7 deletions
32
locales/index.d.ts
vendored
32
locales/index.d.ts
vendored
|
@ -7689,6 +7689,38 @@ export interface Locale extends ILocale {
|
||||||
* Match subdomains
|
* Match subdomains
|
||||||
*/
|
*/
|
||||||
"isFromInstanceSubdomains": string;
|
"isFromInstanceSubdomains": string;
|
||||||
|
/**
|
||||||
|
* Has X or fewer local followers
|
||||||
|
*/
|
||||||
|
"localFollowersLessThanOrEq": string;
|
||||||
|
/**
|
||||||
|
* Has X or more local followers
|
||||||
|
*/
|
||||||
|
"localFollowersMoreThanOrEq": string;
|
||||||
|
/**
|
||||||
|
* Follows X or fewer local accounts
|
||||||
|
*/
|
||||||
|
"localFollowingLessThanOrEq": string;
|
||||||
|
/**
|
||||||
|
* Follows X or more local accounts
|
||||||
|
*/
|
||||||
|
"localFollowingMoreThanOrEq": string;
|
||||||
|
/**
|
||||||
|
* Has X or fewer remote followers
|
||||||
|
*/
|
||||||
|
"remoteFollowersLessThanOrEq": string;
|
||||||
|
/**
|
||||||
|
* Has X or more remote followers
|
||||||
|
*/
|
||||||
|
"remoteFollowersMoreThanOrEq": string;
|
||||||
|
/**
|
||||||
|
* Follows X or fewer remote accounts
|
||||||
|
*/
|
||||||
|
"remoteFollowingLessThanOrEq": string;
|
||||||
|
/**
|
||||||
|
* Follows X or more remote accounts
|
||||||
|
*/
|
||||||
|
"remoteFollowingMoreThanOrEq": string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
"_sensitiveMediaDetection": {
|
"_sensitiveMediaDetection": {
|
||||||
|
|
|
@ -15,6 +15,13 @@ import { bindThis } from '@/decorators.js';
|
||||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
|
export interface FollowStats {
|
||||||
|
localFollowing: number;
|
||||||
|
localFollowers: number;
|
||||||
|
remoteFollowing: number;
|
||||||
|
remoteFollowers: number;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CacheService implements OnApplicationShutdown {
|
export class CacheService implements OnApplicationShutdown {
|
||||||
public userByIdCache: MemoryKVCache<MiUser>;
|
public userByIdCache: MemoryKVCache<MiUser>;
|
||||||
|
@ -27,6 +34,7 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
||||||
public renoteMutingsCache: RedisKVCache<Set<string>>;
|
public renoteMutingsCache: RedisKVCache<Set<string>>;
|
||||||
public userFollowingsCache: RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
|
public userFollowingsCache: RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
|
||||||
|
private readonly userFollowStatsCache = new MemoryKVCache<FollowStats>(1000 * 60 * 10); // 10 minutes
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
|
@ -167,6 +175,18 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
const followee = this.userByIdCache.get(body.followeeId);
|
const followee = this.userByIdCache.get(body.followeeId);
|
||||||
if (followee) followee.followersCount++;
|
if (followee) followee.followersCount++;
|
||||||
this.userFollowingsCache.delete(body.followerId);
|
this.userFollowingsCache.delete(body.followerId);
|
||||||
|
this.userFollowStatsCache.delete(body.followerId);
|
||||||
|
this.userFollowStatsCache.delete(body.followeeId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'unfollow': {
|
||||||
|
const follower = this.userByIdCache.get(body.followerId);
|
||||||
|
if (follower) follower.followingCount--;
|
||||||
|
const followee = this.userByIdCache.get(body.followeeId);
|
||||||
|
if (followee) followee.followersCount--;
|
||||||
|
this.userFollowingsCache.delete(body.followerId);
|
||||||
|
this.userFollowStatsCache.delete(body.followerId);
|
||||||
|
this.userFollowStatsCache.delete(body.followeeId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -187,6 +207,52 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
}) ?? null;
|
}) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async getFollowStats(userId: MiUser['id']): Promise<FollowStats> {
|
||||||
|
return await this.userFollowStatsCache.fetch(userId, async () => {
|
||||||
|
const stats = {
|
||||||
|
localFollowing: 0,
|
||||||
|
localFollowers: 0,
|
||||||
|
remoteFollowing: 0,
|
||||||
|
remoteFollowers: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const followings = await this.followingsRepository.findBy([
|
||||||
|
{ followerId: userId },
|
||||||
|
{ followeeId: userId },
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (const following of followings) {
|
||||||
|
if (following.followerId === userId) {
|
||||||
|
// increment following; user is a follower of someone else
|
||||||
|
if (following.followeeHost == null) {
|
||||||
|
stats.localFollowing++;
|
||||||
|
} else {
|
||||||
|
stats.remoteFollowing++;
|
||||||
|
}
|
||||||
|
} else if (following.followeeId === userId) {
|
||||||
|
// increment followers; user is followed by someone else
|
||||||
|
if (following.followerHost == null) {
|
||||||
|
stats.localFollowers++;
|
||||||
|
} else {
|
||||||
|
stats.remoteFollowers++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Should never happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infer remote-remote followers heuristically, since we don't track that info directly.
|
||||||
|
const user = await this.findUserById(userId);
|
||||||
|
if (user.host !== null) {
|
||||||
|
stats.remoteFollowing = Math.max(0, user.followingCount - stats.localFollowing);
|
||||||
|
stats.remoteFollowers = Math.max(0, user.followersCount - stats.localFollowers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.redisForSub.off('message', this.onMessage);
|
this.redisForSub.off('message', this.onMessage);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import type { MiUser } from '@/models/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
|
import type { FollowStats } from '@/core/CacheService.js';
|
||||||
import type { RoleCondFormulaValue } from '@/models/Role.js';
|
import type { RoleCondFormulaValue } from '@/models/Role.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
|
@ -221,20 +222,20 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private evalCond(user: MiUser, roles: MiRole[], value: RoleCondFormulaValue): boolean {
|
private evalCond(user: MiUser, roles: MiRole[], value: RoleCondFormulaValue, followStats: FollowStats): boolean {
|
||||||
try {
|
try {
|
||||||
switch (value.type) {
|
switch (value.type) {
|
||||||
// ~かつ~
|
// ~かつ~
|
||||||
case 'and': {
|
case 'and': {
|
||||||
return value.values.every(v => this.evalCond(user, roles, v));
|
return value.values.every(v => this.evalCond(user, roles, v, followStats));
|
||||||
}
|
}
|
||||||
// ~または~
|
// ~または~
|
||||||
case 'or': {
|
case 'or': {
|
||||||
return value.values.some(v => this.evalCond(user, roles, v));
|
return value.values.some(v => this.evalCond(user, roles, v, followStats));
|
||||||
}
|
}
|
||||||
// ~ではない
|
// ~ではない
|
||||||
case 'not': {
|
case 'not': {
|
||||||
return !this.evalCond(user, roles, value.value);
|
return !this.evalCond(user, roles, value.value, followStats);
|
||||||
}
|
}
|
||||||
// マニュアルロールがアサインされている
|
// マニュアルロールがアサインされている
|
||||||
case 'roleAssignedTo': {
|
case 'roleAssignedTo': {
|
||||||
|
@ -305,6 +306,30 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
case 'followingMoreThanOrEq': {
|
case 'followingMoreThanOrEq': {
|
||||||
return user.followingCount >= value.value;
|
return user.followingCount >= value.value;
|
||||||
}
|
}
|
||||||
|
case 'localFollowersLessThanOrEq': {
|
||||||
|
return followStats.localFollowers <= value.value;
|
||||||
|
}
|
||||||
|
case 'localFollowersMoreThanOrEq': {
|
||||||
|
return followStats.localFollowers >= value.value;
|
||||||
|
}
|
||||||
|
case 'localFollowingLessThanOrEq': {
|
||||||
|
return followStats.localFollowing <= value.value;
|
||||||
|
}
|
||||||
|
case 'localFollowingMoreThanOrEq': {
|
||||||
|
return followStats.localFollowing >= value.value;
|
||||||
|
}
|
||||||
|
case 'remoteFollowersLessThanOrEq': {
|
||||||
|
return followStats.remoteFollowers <= value.value;
|
||||||
|
}
|
||||||
|
case 'remoteFollowersMoreThanOrEq': {
|
||||||
|
return followStats.remoteFollowers >= value.value;
|
||||||
|
}
|
||||||
|
case 'remoteFollowingLessThanOrEq': {
|
||||||
|
return followStats.remoteFollowing <= value.value;
|
||||||
|
}
|
||||||
|
case 'remoteFollowingMoreThanOrEq': {
|
||||||
|
return followStats.remoteFollowing >= value.value;
|
||||||
|
}
|
||||||
// ノート数が指定値以下
|
// ノート数が指定値以下
|
||||||
case 'notesLessThanOrEq': {
|
case 'notesLessThanOrEq': {
|
||||||
return user.notesCount <= value.value;
|
return user.notesCount <= value.value;
|
||||||
|
@ -340,10 +365,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getUserRoles(userId: MiUser['id']) {
|
public async getUserRoles(userId: MiUser['id']) {
|
||||||
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||||
|
const followStats = await this.cacheService.getFollowStats(userId);
|
||||||
const assigns = await this.getUserAssigns(userId);
|
const assigns = await this.getUserAssigns(userId);
|
||||||
const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
|
const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
|
||||||
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
|
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
|
||||||
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, assignedRoles, r.condFormula));
|
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, assignedRoles, r.condFormula, followStats));
|
||||||
return [...assignedRoles, ...matchedCondRoles];
|
return [...assignedRoles, ...matchedCondRoles];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,12 +383,13 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
// 期限切れのロールを除外
|
// 期限切れのロールを除外
|
||||||
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
|
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
|
||||||
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||||
|
const followStats = await this.cacheService.getFollowStats(userId);
|
||||||
const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
|
const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
|
||||||
const assignedBadgeRoles = assignedRoles.filter(r => r.asBadge);
|
const assignedBadgeRoles = assignedRoles.filter(r => r.asBadge);
|
||||||
const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional'));
|
const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional'));
|
||||||
if (badgeCondRoles.length > 0) {
|
if (badgeCondRoles.length > 0) {
|
||||||
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
|
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
|
||||||
const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, assignedRoles, r.condFormula));
|
const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, assignedRoles, r.condFormula, followStats));
|
||||||
return [...assignedBadgeRoles, ...matchedBadgeCondRoles];
|
return [...assignedBadgeRoles, ...matchedBadgeCondRoles];
|
||||||
} else {
|
} else {
|
||||||
return assignedBadgeRoles;
|
return assignedBadgeRoles;
|
||||||
|
|
|
@ -147,6 +147,70 @@ type CondFormulaValueFollowingMoreThanOrEq = {
|
||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is followed by at most N local users
|
||||||
|
*/
|
||||||
|
type CondFormulaValueLocalFollowersLessThanOrEq = {
|
||||||
|
type: 'localFollowersLessThanOrEq';
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is followed by at least N local users
|
||||||
|
*/
|
||||||
|
type CondFormulaValueLocalFollowersMoreThanOrEq = {
|
||||||
|
type: 'localFollowersMoreThanOrEq';
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is following at most N local users
|
||||||
|
*/
|
||||||
|
type CondFormulaValueLocalFollowingLessThanOrEq = {
|
||||||
|
type: 'localFollowingLessThanOrEq';
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is following at least N local users
|
||||||
|
*/
|
||||||
|
type CondFormulaValueLocalFollowingMoreThanOrEq = {
|
||||||
|
type: 'localFollowingMoreThanOrEq';
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is followed by at most N remote users
|
||||||
|
*/
|
||||||
|
type CondFormulaValueRemoteFollowersLessThanOrEq = {
|
||||||
|
type: 'remoteFollowersLessThanOrEq';
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is followed by at least N remote users
|
||||||
|
*/
|
||||||
|
type CondFormulaValueRemoteFollowersMoreThanOrEq = {
|
||||||
|
type: 'remoteFollowersMoreThanOrEq';
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is following at most N remote users
|
||||||
|
*/
|
||||||
|
type CondFormulaValueRemoteFollowingLessThanOrEq = {
|
||||||
|
type: 'remoteFollowingLessThanOrEq';
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is following at least N remote users
|
||||||
|
*/
|
||||||
|
type CondFormulaValueRemoteFollowingMoreThanOrEq = {
|
||||||
|
type: 'remoteFollowingMoreThanOrEq';
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 投稿数が指定値以下の場合のみ成立とする
|
* 投稿数が指定値以下の場合のみ成立とする
|
||||||
*/
|
*/
|
||||||
|
@ -182,6 +246,14 @@ export type RoleCondFormulaValue = { id: string } & (
|
||||||
CondFormulaValueFollowersMoreThanOrEq |
|
CondFormulaValueFollowersMoreThanOrEq |
|
||||||
CondFormulaValueFollowingLessThanOrEq |
|
CondFormulaValueFollowingLessThanOrEq |
|
||||||
CondFormulaValueFollowingMoreThanOrEq |
|
CondFormulaValueFollowingMoreThanOrEq |
|
||||||
|
CondFormulaValueLocalFollowersLessThanOrEq |
|
||||||
|
CondFormulaValueLocalFollowersMoreThanOrEq |
|
||||||
|
CondFormulaValueLocalFollowingLessThanOrEq |
|
||||||
|
CondFormulaValueLocalFollowingMoreThanOrEq |
|
||||||
|
CondFormulaValueRemoteFollowersLessThanOrEq |
|
||||||
|
CondFormulaValueRemoteFollowersMoreThanOrEq |
|
||||||
|
CondFormulaValueRemoteFollowingLessThanOrEq |
|
||||||
|
CondFormulaValueRemoteFollowingMoreThanOrEq |
|
||||||
CondFormulaValueNotesLessThanOrEq |
|
CondFormulaValueNotesLessThanOrEq |
|
||||||
CondFormulaValueNotesMoreThanOrEq
|
CondFormulaValueNotesMoreThanOrEq
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,6 +22,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="followersMoreThanOrEq">{{ i18n.ts._role._condition.followersMoreThanOrEq }}</option>
|
<option value="followersMoreThanOrEq">{{ i18n.ts._role._condition.followersMoreThanOrEq }}</option>
|
||||||
<option value="followingLessThanOrEq">{{ i18n.ts._role._condition.followingLessThanOrEq }}</option>
|
<option value="followingLessThanOrEq">{{ i18n.ts._role._condition.followingLessThanOrEq }}</option>
|
||||||
<option value="followingMoreThanOrEq">{{ i18n.ts._role._condition.followingMoreThanOrEq }}</option>
|
<option value="followingMoreThanOrEq">{{ i18n.ts._role._condition.followingMoreThanOrEq }}</option>
|
||||||
|
<option value="localFollowersLessThanOrEq">{{ i18n.ts._role._condition.localFollowersLessThanOrEq }}</option>
|
||||||
|
<option value="localFollowersMoreThanOrEq">{{ i18n.ts._role._condition.localFollowersMoreThanOrEq }}</option>
|
||||||
|
<option value="localFollowingLessThanOrEq">{{ i18n.ts._role._condition.localFollowingLessThanOrEq }}</option>
|
||||||
|
<option value="localFollowingMoreThanOrEq">{{ i18n.ts._role._condition.localFollowingMoreThanOrEq }}</option>
|
||||||
|
<option value="remoteFollowersLessThanOrEq">{{ i18n.ts._role._condition.remoteFollowersLessThanOrEq }}</option>
|
||||||
|
<option value="remoteFollowersMoreThanOrEq">{{ i18n.ts._role._condition.remoteFollowersMoreThanOrEq }}</option>
|
||||||
|
<option value="remoteFollowingLessThanOrEq">{{ i18n.ts._role._condition.remoteFollowingLessThanOrEq }}</option>
|
||||||
|
<option value="remoteFollowingMoreThanOrEq">{{ i18n.ts._role._condition.remoteFollowingMoreThanOrEq }}</option>
|
||||||
<option value="notesLessThanOrEq">{{ i18n.ts._role._condition.notesLessThanOrEq }}</option>
|
<option value="notesLessThanOrEq">{{ i18n.ts._role._condition.notesLessThanOrEq }}</option>
|
||||||
<option value="notesMoreThanOrEq">{{ i18n.ts._role._condition.notesMoreThanOrEq }}</option>
|
<option value="notesMoreThanOrEq">{{ i18n.ts._role._condition.notesMoreThanOrEq }}</option>
|
||||||
<option value="and">{{ i18n.ts._role._condition.and }}</option>
|
<option value="and">{{ i18n.ts._role._condition.and }}</option>
|
||||||
|
@ -56,7 +64,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #suffix>sec</template>
|
<template #suffix>sec</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<MkInput v-else-if="['followersLessThanOrEq', 'followersMoreThanOrEq', 'followingLessThanOrEq', 'followingMoreThanOrEq', 'notesLessThanOrEq', 'notesMoreThanOrEq'].includes(type)" v-model="v.value" type="number">
|
<MkInput
|
||||||
|
v-else-if="[
|
||||||
|
'followersLessThanOrEq',
|
||||||
|
'followersMoreThanOrEq',
|
||||||
|
'followingLessThanOrEq',
|
||||||
|
'followingMoreThanOrEq',
|
||||||
|
'localFollowersLessThanOrEq',
|
||||||
|
'localFollowersMoreThanOrEq',
|
||||||
|
'localFollowingLessThanOrEq',
|
||||||
|
'localFollowingMoreThanOrEq',
|
||||||
|
'remoteFollowersLessThanOrEq',
|
||||||
|
'remoteFollowersMoreThanOrEq',
|
||||||
|
'remoteFollowingLessThanOrEq',
|
||||||
|
'remoteFollowingMoreThanOrEq',
|
||||||
|
'notesLessThanOrEq',
|
||||||
|
'notesMoreThanOrEq'
|
||||||
|
].includes(type)"
|
||||||
|
v-model="v.value"
|
||||||
|
type="number"
|
||||||
|
>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<MkSelect v-else-if="type === 'roleAssignedTo'" v-model="v.roleId">
|
<MkSelect v-else-if="type === 'roleAssignedTo'" v-model="v.roleId">
|
||||||
|
|
|
@ -245,6 +245,14 @@ _role:
|
||||||
isFromInstance: "Is from a specific instance"
|
isFromInstance: "Is from a specific instance"
|
||||||
isFromInstanceHost: "Hostname (case-insensitive)"
|
isFromInstanceHost: "Hostname (case-insensitive)"
|
||||||
isFromInstanceSubdomains: "Match subdomains"
|
isFromInstanceSubdomains: "Match subdomains"
|
||||||
|
localFollowersLessThanOrEq: "Has X or fewer local followers"
|
||||||
|
localFollowersMoreThanOrEq: "Has X or more local followers"
|
||||||
|
localFollowingLessThanOrEq: "Follows X or fewer local accounts"
|
||||||
|
localFollowingMoreThanOrEq: "Follows X or more local accounts"
|
||||||
|
remoteFollowersLessThanOrEq: "Has X or fewer remote followers"
|
||||||
|
remoteFollowersMoreThanOrEq: "Has X or more remote followers"
|
||||||
|
remoteFollowingLessThanOrEq: "Follows X or fewer remote accounts"
|
||||||
|
remoteFollowingMoreThanOrEq: "Follows X or more remote accounts"
|
||||||
_emailUnavailable:
|
_emailUnavailable:
|
||||||
banned: "This email address is banned"
|
banned: "This email address is banned"
|
||||||
_signup:
|
_signup:
|
||||||
|
|
Loading…
Add table
Reference in a new issue