Compare commits
24 Commits
13.0.0-rc.
...
13.0.0-rc.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3ae798d526 | ||
![]() |
e1bd61c70e | ||
![]() |
0296f841c3 | ||
![]() |
bd1f4b8d98 | ||
![]() |
dc19f20153 | ||
![]() |
f5cd809f62 | ||
![]() |
09d5a7806a | ||
![]() |
4606f23ed8 | ||
![]() |
8451e08aaa | ||
![]() |
2047449294 | ||
![]() |
d61eee695f | ||
![]() |
73b62797cd | ||
![]() |
170cfc6a0e | ||
![]() |
6bf1d7e398 | ||
![]() |
e46e7f5252 | ||
![]() |
5952f1ac24 | ||
![]() |
a08369fe36 | ||
![]() |
6cb9612943 | ||
![]() |
76c049522e | ||
![]() |
c41879c542 | ||
![]() |
99bdb11d24 | ||
![]() |
c2009acb2d | ||
![]() |
46d2a8726e | ||
![]() |
7df3ca7388 |
3
.github/workflows/pr-preview-deploy.yml
vendored
3
.github/workflows/pr-preview-deploy.yml
vendored
@@ -1,7 +1,5 @@
|
||||
# Run secret-dependent integration tests only after /deploy approval
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
repository_dispatch:
|
||||
types: [deploy-command]
|
||||
|
||||
@@ -12,7 +10,6 @@ jobs:
|
||||
deploy-preview-environment:
|
||||
runs-on: ubuntu-latest
|
||||
if:
|
||||
github.event_name == 'repository_dispatch' &&
|
||||
github.event.client_payload.slash_command.sha != '' &&
|
||||
contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha)
|
||||
steps:
|
||||
|
1
.github/workflows/pr-preview-destroy.yml
vendored
1
.github/workflows/pr-preview-destroy.yml
vendored
@@ -9,6 +9,7 @@ name: Destroy preview environment
|
||||
jobs:
|
||||
destroy-preview-environment:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == github.event.pull_request.head.repo.full_name
|
||||
steps:
|
||||
- name: Context
|
||||
uses: okteto/context@latest
|
||||
|
@@ -52,6 +52,7 @@ You should also include the user name that made the change.
|
||||
- 0.12.x未満のプラグインは読み込むことはできません
|
||||
- iOS15以下のデバイスはサポートされなくなりました
|
||||
- Firefox110以下はサポートされなくなりました
|
||||
- 109でもContainerQueriesのフラグを有効にする事で問題なく使用できます
|
||||
|
||||
#### For app developers
|
||||
- API: metaのレスポンスに`emojis`プロパティが含まれなくなりました
|
||||
@@ -75,14 +76,18 @@ You should also include the user name that made the change.
|
||||
- Add Cloudflare Turnstile CAPTCHA support @CyberRex0
|
||||
- 非モデレーターでも、権限を持つロールをアサインされたユーザーはインスタンスの招待コードを発行できるように @syuilo
|
||||
- 非モデレーターでも、権限を持つロールをアサインされたユーザーはカスタム絵文字の追加、編集、削除を行えるように @syuilo
|
||||
- クリップおよびクリップ内のノートの作成可能数を設定可能に @syuilo
|
||||
- ユーザーリストおよびユーザーリスト内のユーザーの作成可能数を設定可能に @syuilo
|
||||
- ハードワードミュートの最大文字数を設定可能に @syuilo
|
||||
- Webhookの作成可能数を設定可能に @syuilo
|
||||
- ノートをピン留めできる数を設定可能に @syuilo
|
||||
- Server: signToActivityPubGet is set to true by default @syuilo
|
||||
- Server: improve syslog performance @syuilo
|
||||
- Server: Use undici instead of node-fetch and got @tamaina
|
||||
- Server: Judge instance block by endsWith @tamaina
|
||||
- Server: improve note scoring for featured notes @CyberRex0
|
||||
- Server: アンケート選択肢の文字数制限を緩和 @syuilo
|
||||
- Server: add rate limits for some endpoints @syuilo
|
||||
- Server: improve stats api performance @syuilo
|
||||
- Server: improve nodeinfo performance @syuilo
|
||||
- Server: delete outdated notifications regularly to improve db performance @syuilo
|
||||
|
30
Dockerfile
30
Dockerfile
@@ -1,4 +1,6 @@
|
||||
FROM node:18.13.0-bullseye AS builder
|
||||
ARG NODE_VERSION=18.13.0-bullseye
|
||||
|
||||
FROM node:${NODE_VERSION} AS builder
|
||||
|
||||
ARG NODE_ENV=production
|
||||
|
||||
@@ -22,23 +24,29 @@ COPY . ./
|
||||
RUN git submodule update --init
|
||||
RUN yarn build
|
||||
|
||||
FROM node:18.13.0-bullseye-slim AS runner
|
||||
FROM node:${NODE_VERSION}-slim AS runner
|
||||
|
||||
WORKDIR /misskey
|
||||
ARG UID="991"
|
||||
ARG GID="991"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ffmpeg tini \
|
||||
&& apt-get -y clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& groupadd -g "${GID}" misskey \
|
||||
&& useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey
|
||||
|
||||
COPY --from=builder /misskey/.yarn/install-state.gz ./.yarn/install-state.gz
|
||||
COPY --from=builder /misskey/node_modules ./node_modules
|
||||
COPY --from=builder /misskey/built ./built
|
||||
COPY --from=builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
|
||||
COPY --from=builder /misskey/packages/backend/built ./packages/backend/built
|
||||
COPY --from=builder /misskey/packages/frontend/node_modules ./packages/frontend/node_modules
|
||||
COPY . ./
|
||||
USER misskey
|
||||
WORKDIR /misskey
|
||||
|
||||
COPY --chown=misskey:misskey --from=builder /misskey/.yarn/install-state.gz ./.yarn/install-state.gz
|
||||
COPY --chown=misskey:misskey --from=builder /misskey/node_modules ./node_modules
|
||||
COPY --chown=misskey:misskey --from=builder /misskey/built ./built
|
||||
COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/node_modules ./packages/backend/node_modules
|
||||
COPY --chown=misskey:misskey --from=builder /misskey/packages/backend/built ./packages/backend/built
|
||||
COPY --chown=misskey:misskey --from=builder /misskey/packages/frontend/node_modules ./packages/frontend/node_modules
|
||||
COPY --chown=misskey:misskey . ./
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
|
@@ -29,8 +29,8 @@ describe('After user signed in', () => {
|
||||
|
||||
it('first widget should be removed', () => {
|
||||
cy.get('.mk-widget-edit').click();
|
||||
cy.get('.customize-container:first-child .remove._button').click();
|
||||
cy.get('.customize-container').should('have.length', 2);
|
||||
cy.get('.data-cy-customize-container:first-child .data-cy-customize-container-remove._button').click();
|
||||
cy.get('.data-cy-customize-container').should('have.length', 2);
|
||||
});
|
||||
|
||||
function buildWidgetTest(widgetName) {
|
||||
|
@@ -932,6 +932,7 @@ assign: "Zuweisen"
|
||||
unassign: "Entfernen"
|
||||
color: "Farbe"
|
||||
manageCustomEmojis: "Benutzerdefinierte Emojis verwalten"
|
||||
youCannotCreateAnymore: "Du hast das Erstellungslimit erreicht."
|
||||
_role:
|
||||
new: "Rolle erstellen"
|
||||
edit: "Rolle bearbeiten"
|
||||
@@ -940,10 +941,10 @@ _role:
|
||||
permission: "Rollenberechtigungen"
|
||||
descriptionOfPermission: "<b>Moderatoren</b> können grundlegende Verwaltungsaufgaben erledigen.\n<b>Administratoren</b> können alle Einstellungen der Instanz verwalten."
|
||||
assignTarget: "Zuweisungsart"
|
||||
descriptionOfAssignTarget: "<b>Manuell</b> bedeutet, dass die Liste der Benutzer einer Rolle manuell verwaltet wird.\n<b>Konditionell</b> bedeutet, dass die Liste der Benutzer einer Rolle durch eine Liste an Konditionen automatisch verwaltet wird."
|
||||
descriptionOfAssignTarget: "<b>Manuell</b> bedeutet, dass die Liste der Benutzer einer Rolle manuell verwaltet wird.\n<b>Konditionell</b> bedeutet, dass die Liste der Benutzer einer Rolle durch eine Bedingung automatisch verwaltet wird."
|
||||
manual: "Manuell"
|
||||
conditional: "Konditional"
|
||||
condition: "Konditionen"
|
||||
condition: "Bedingung"
|
||||
isConditionalRole: "Dies ist eine konditionale Rolle."
|
||||
isPublic: "Öffentliche Rolle"
|
||||
descriptionOfIsPublic: "Ist dies aktiviert, so kann jeder die Liste der Benutzer, die dieser Rolle zugewiesen sind, einsehen. Zusätzlich wird diese Rolle im Profil zugewiesener Benutzer angezeigt."
|
||||
@@ -960,13 +961,26 @@ _role:
|
||||
canInvite: "Einladungscodes für diese Instanz erstellen"
|
||||
canManageCustomEmojis: "Benutzerdefinierte Emojis verwalten"
|
||||
driveCapacity: "Drive-Kapazität"
|
||||
pinMax: "Maximale Anzahl an angehefteten Notizen"
|
||||
antennaMax: "Maximale Anzahl an Antennen"
|
||||
wordMuteMax: "Maximale Zeichenlänge für Wortstummschaltungen"
|
||||
webhookMax: "Maximale Anzahl an Webhooks"
|
||||
clipMax: "Maximale Anzahl an Clips"
|
||||
noteEachClipsMax: "Maximale Anzahl an Notizen innerhalb eines Clips"
|
||||
userListMax: "Maximale Anzahl an Benutzern in einer Benutzerliste"
|
||||
userEachUserListsMax: "Maximale Anzahl an Benutzerlisten"
|
||||
_condition:
|
||||
isLocal: "Lokaler Benutzer"
|
||||
isRemote: "Benutzer fremder Instanz"
|
||||
and: "UND"
|
||||
or: "ODER"
|
||||
not: "NICHT"
|
||||
createdLessThan: "Kontoerstellung liegt weniger als X zurück"
|
||||
createdMoreThan: "Kontoerstellung liegt mehr als X zurück"
|
||||
followersLessThanOrEq: "Hat X oder weniger Follower"
|
||||
followersMoreThanOrEq: "Hat X oder mehr Follower"
|
||||
followingLessThanOrEq: "Folgt X oder weniger Benutzern"
|
||||
followingMoreThanOrEq: "Folgt X oder mehr Benutzern"
|
||||
and: "UND-Bedingung"
|
||||
or: "ODER-Bedingung"
|
||||
not: "NICHT-Bedingung"
|
||||
_sensitiveMediaDetection:
|
||||
description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht."
|
||||
sensitivity: "Erkennungssensitivität"
|
||||
|
@@ -932,6 +932,7 @@ assign: "Assign"
|
||||
unassign: "Unassign"
|
||||
color: "Color"
|
||||
manageCustomEmojis: "Manage Custom Emojis"
|
||||
youCannotCreateAnymore: "You've hit the creation limit."
|
||||
_role:
|
||||
new: "New role"
|
||||
edit: "Edit role"
|
||||
@@ -939,11 +940,11 @@ _role:
|
||||
description: "Role description"
|
||||
permission: "Role permissions"
|
||||
descriptionOfPermission: "<b>Moderators</b> can perform basic moderation operations.\n<b>Administrators</b> can change all settings of the instance."
|
||||
assignTarget: "Assign target"
|
||||
descriptionOfAssignTarget: "<b>Manual</b> to manually change who is part of this role and who is not.\n<b>Conditional</b> to have users be automatically assigned and removed from this role based on a set of conditions."
|
||||
assignTarget: "Assignment type"
|
||||
descriptionOfAssignTarget: "<b>Manual</b> to manually change who is part of this role and who is not.\n<b>Conditional</b> to have users be automatically assigned and removed from this role based on a condition."
|
||||
manual: "Manual"
|
||||
conditional: "Conditional"
|
||||
condition: "Conditions"
|
||||
condition: "Condition"
|
||||
isConditionalRole: "This is a conditional role."
|
||||
isPublic: "Public role"
|
||||
descriptionOfIsPublic: "Anyone will be able to view a list of users assigned to this role. In addition, this role will be displayed in the profiles of assigned users."
|
||||
@@ -960,20 +961,26 @@ _role:
|
||||
canInvite: "Create instance invite codes"
|
||||
canManageCustomEmojis: "Manage Custom Emojis"
|
||||
driveCapacity: "Drive capacity"
|
||||
pinMax: "Maximum number of pinned notes"
|
||||
antennaMax: "Maximum number of antennas"
|
||||
wordMuteMax: "Maximum number of characters allowed in the word mute string"
|
||||
wordMuteMax: "Maximum number of characters allowed in word mutes"
|
||||
webhookMax: "Maximum number of Webhooks"
|
||||
clipMax: "Maximum number of Clips"
|
||||
noteEachClipsMax: "Maximum number of notes within a clip"
|
||||
userListMax: "Maximum number of user lists"
|
||||
userEachUserListsMax: "Maximum number of users within a user list"
|
||||
_condition:
|
||||
isLocal: "Local user"
|
||||
isRemote: "Remote user"
|
||||
createdLessThan: "Created less than"
|
||||
createdMoreThan: "Created more than"
|
||||
followersLessThanOrEq: "The number of followers is less than or equal to"
|
||||
followersMoreThanOrEq: "The number of followers is greater than or equal to"
|
||||
followingLessThanOrEq: "The number of accounts following is less than or equal to"
|
||||
followingMoreThanOrEq: "The number of accounts following is greater than or equal to"
|
||||
and: "AND"
|
||||
or: "OR"
|
||||
not: "NOT"
|
||||
createdLessThan: "Less than X has passed since account creation"
|
||||
createdMoreThan: "More than X has passed since account creation"
|
||||
followersLessThanOrEq: "Has X or fewer followers"
|
||||
followersMoreThanOrEq: "Has X or more followers"
|
||||
followingLessThanOrEq: "Follows X or fewer accounts"
|
||||
followingMoreThanOrEq: "Follows X or more accounts"
|
||||
and: "AND-Condition"
|
||||
or: "OR-Condition"
|
||||
not: "NOT-Condition"
|
||||
_sensitiveMediaDetection:
|
||||
description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server."
|
||||
sensitivity: "Detection sensitivity"
|
||||
|
@@ -932,6 +932,7 @@ assign: "アサイン"
|
||||
unassign: "アサインを解除"
|
||||
color: "色"
|
||||
manageCustomEmojis: "カスタム絵文字の管理"
|
||||
youCannotCreateAnymore: "これ以上作成することはできません。"
|
||||
|
||||
_role:
|
||||
new: "ロールの作成"
|
||||
@@ -961,9 +962,14 @@ _role:
|
||||
canInvite: "インスタンス招待コードの発行"
|
||||
canManageCustomEmojis: "カスタム絵文字の管理"
|
||||
driveCapacity: "ドライブ容量"
|
||||
pinMax: "ノートのピン留めの最大数"
|
||||
antennaMax: "アンテナの作成可能数"
|
||||
wordMuteMax: "ワードミュートの最大文字数"
|
||||
webhookMax: "Webhookの作成可能数"
|
||||
clipMax: "クリップの作成可能数"
|
||||
noteEachClipsMax: "クリップ内のノートの最大数"
|
||||
userListMax: "ユーザーリストの作成可能数"
|
||||
userEachUserListsMax: "ユーザーリスト内のユーザーの最大数"
|
||||
_condition:
|
||||
isLocal: "ローカルユーザー"
|
||||
isRemote: "リモートユーザー"
|
||||
|
@@ -966,6 +966,10 @@ _role:
|
||||
isRemote: "ผู้ใช้ระยะไกล"
|
||||
createdLessThan: "สร้างน้อยกว่า"
|
||||
createdMoreThan: "สร้างมากกว่า"
|
||||
followersLessThanOrEq: "จำนวนผู้ติดตามน้อยกว่าหรือเท่ากับ\n"
|
||||
followersMoreThanOrEq: "จำนวนผู้ติดตามมากกว่าหรือเท่ากับ\n"
|
||||
followingLessThanOrEq: "จำนวนบัญชีต่อไปนี้คือ น้อยกว่าหรือเท่ากับ"
|
||||
followingMoreThanOrEq: "จำนวนบัญชีต่อไปนี้คือ มากกว่าหรือเท่ากับ"
|
||||
and: "และ"
|
||||
or: "หรือ"
|
||||
not: "ไม่"
|
||||
|
@@ -932,6 +932,7 @@ assign: "分配"
|
||||
unassign: "取消分配"
|
||||
color: "颜色"
|
||||
manageCustomEmojis: "管理自定义表情符号"
|
||||
youCannotCreateAnymore: "抱歉,您无法再创建更多了。"
|
||||
_role:
|
||||
new: "创建角色"
|
||||
edit: "编辑角色"
|
||||
@@ -960,9 +961,14 @@ _role:
|
||||
canInvite: "发放实例邀请码"
|
||||
canManageCustomEmojis: "管理自定义表情符号"
|
||||
driveCapacity: "网盘容量"
|
||||
pinMax: "帖子置顶数量限制"
|
||||
antennaMax: "可创建的最大天线数量"
|
||||
wordMuteMax: "屏蔽词的字数限制"
|
||||
webhookMax: "Webhook 创建数量限制"
|
||||
clipMax: "便签创建数量限制"
|
||||
noteEachClipsMax: "单个便签内的贴文数量限制"
|
||||
userListMax: "用户列表创建数量限制"
|
||||
userEachUserListsMax: "单个用户列表内用户数量限制"
|
||||
_condition:
|
||||
isLocal: "是本地用户"
|
||||
isRemote: "是远程用户"
|
||||
|
@@ -961,7 +961,8 @@ _role:
|
||||
canManageCustomEmojis: "管理自訂表情符號"
|
||||
driveCapacity: "雲端硬碟容量"
|
||||
antennaMax: "可建立的天線數量"
|
||||
webhookMax: "可建立的Webhook數"
|
||||
webhookMax: "可建立的Webhook數量"
|
||||
clipMax: "可建立的摘錄數量"
|
||||
_condition:
|
||||
isLocal: "本地使用者"
|
||||
isRemote: "遠端使用者"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "13.0.0-rc.3",
|
||||
"version": "13.0.0-rc.5",
|
||||
"codename": "indigo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -12,6 +12,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
@Injectable()
|
||||
export class NotePiningService {
|
||||
@@ -30,6 +31,7 @@ export class NotePiningService {
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private idService: IdService,
|
||||
private roleService: RoleService,
|
||||
private relayService: RelayService,
|
||||
private apDeliverManagerService: ApDeliverManagerService,
|
||||
private apRendererService: ApRendererService,
|
||||
@@ -55,7 +57,7 @@ export class NotePiningService {
|
||||
|
||||
const pinings = await this.userNotePiningsRepository.findBy({ userId: user.id });
|
||||
|
||||
if (pinings.length >= 5) {
|
||||
if (pinings.length >= (await this.roleService.getUserRoleOptions(user.id)).pinLimit) {
|
||||
throw new IdentifiableError('15a018eb-58e5-4da1-93be-330fcc5e4e1a', 'You can not pin notes any more.');
|
||||
}
|
||||
|
||||
|
@@ -20,9 +20,14 @@ export type RoleOptions = {
|
||||
canInvite: boolean;
|
||||
canManageCustomEmojis: boolean;
|
||||
driveCapacityMb: number;
|
||||
pinLimit: number;
|
||||
antennaLimit: number;
|
||||
wordMuteLimit: number;
|
||||
webhookLimit: number;
|
||||
clipLimit: number;
|
||||
noteEachClipsLimit: number;
|
||||
userListLimit: number;
|
||||
userEachUserListsLimit: number;
|
||||
};
|
||||
|
||||
export const DEFAULT_ROLE: RoleOptions = {
|
||||
@@ -32,9 +37,14 @@ export const DEFAULT_ROLE: RoleOptions = {
|
||||
canInvite: false,
|
||||
canManageCustomEmojis: false,
|
||||
driveCapacityMb: 100,
|
||||
pinLimit: 5,
|
||||
antennaLimit: 5,
|
||||
wordMuteLimit: 200,
|
||||
webhookLimit: 3,
|
||||
clipLimit: 10,
|
||||
noteEachClipsLimit: 200,
|
||||
userListLimit: 10,
|
||||
userEachUserListsLimit: 50,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -203,9 +213,14 @@ export class RoleService implements OnApplicationShutdown {
|
||||
canInvite: getOptionValues('canInvite').some(x => x === true),
|
||||
canManageCustomEmojis: getOptionValues('canManageCustomEmojis').some(x => x === true),
|
||||
driveCapacityMb: Math.max(...getOptionValues('driveCapacityMb')),
|
||||
pinLimit: Math.max(...getOptionValues('pinLimit')),
|
||||
antennaLimit: Math.max(...getOptionValues('antennaLimit')),
|
||||
wordMuteLimit: Math.max(...getOptionValues('wordMuteLimit')),
|
||||
webhookLimit: Math.max(...getOptionValues('webhookLimit')),
|
||||
clipLimit: Math.max(...getOptionValues('clipLimit')),
|
||||
noteEachClipsLimit: Math.max(...getOptionValues('noteEachClipsLimit')),
|
||||
userListLimit: Math.max(...getOptionValues('userListLimit')),
|
||||
userEachUserListsLimit: Math.max(...getOptionValues('userEachUserListsLimit')),
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
@Injectable()
|
||||
export class UserListService {
|
||||
@@ -23,13 +24,21 @@ export class UserListService {
|
||||
private userEntityService: UserEntityService,
|
||||
private idService: IdService,
|
||||
private userFollowingService: UserFollowingService,
|
||||
private roleService: RoleService,
|
||||
private globalEventServie: GlobalEventService,
|
||||
private proxyAccountService: ProxyAccountService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async push(target: User, list: UserList) {
|
||||
public async push(target: User, list: UserList, me: User) {
|
||||
const currentCount = await this.userListJoiningsRepository.countBy({
|
||||
userListId: list.id,
|
||||
});
|
||||
if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).userEachUserListsLimit) {
|
||||
throw new Error('Too many users');
|
||||
}
|
||||
|
||||
await this.userListJoiningsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
|
@@ -10,10 +10,10 @@ import { DownloadService } from '@/core/DownloadService.js';
|
||||
import { UserListService } from '@/core/UserListService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type Bull from 'bull';
|
||||
import type { DbUserImportJobData } from '../types.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class ImportUserListsProcessorService {
|
||||
@@ -102,7 +102,7 @@ export class ImportUserListsProcessorService {
|
||||
|
||||
if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue;
|
||||
|
||||
this.userListService.push(target, list!);
|
||||
this.userListService.push(target, list!, user);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Error in line:${linenum} ${e}`);
|
||||
}
|
||||
|
@@ -5,15 +5,15 @@ import type { UsersRepository, BlockingsRepository } from '@/models/index.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { UserBlockingService } from '@/core/UserBlockingService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account'],
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 100,
|
||||
max: 20,
|
||||
},
|
||||
|
||||
requireCredential: true,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { ChannelsRepository, DriveFilesRepository } from '@/models/index.js';
|
||||
import type { Channel } from '@/models/entities/Channel.js';
|
||||
@@ -14,6 +15,11 @@ export const meta = {
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 10,
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
|
@@ -1,10 +1,12 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account', 'notes', 'clips'],
|
||||
@@ -13,6 +15,11 @@ export const meta = {
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 20,
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchClip: {
|
||||
message: 'No such clip.',
|
||||
@@ -31,6 +38,12 @@ export const meta = {
|
||||
code: 'ALREADY_CLIPPED',
|
||||
id: '734806c4-542c-463a-9311-15c512803965',
|
||||
},
|
||||
|
||||
tooManyClipNotes: {
|
||||
message: 'You cannot add notes to the clip any more.',
|
||||
code: 'TOO_MANY_CLIP_NOTES',
|
||||
id: 'f0dba960-ff73-4615-8df4-d6ac5d9dc118',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -54,6 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private clipNotesRepository: ClipNotesRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private roleService: RoleService,
|
||||
private getterService: GetterService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
@@ -80,6 +94,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
throw new ApiError(meta.errors.alreadyClipped);
|
||||
}
|
||||
|
||||
const currentCount = await this.clipNotesRepository.countBy({
|
||||
clipId: clip.id,
|
||||
});
|
||||
if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).noteEachClipsLimit) {
|
||||
throw new ApiError(meta.errors.tooManyClipNotes);
|
||||
}
|
||||
|
||||
await this.clipNotesRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
noteId: note.id,
|
||||
|
@@ -4,6 +4,8 @@ import { IdService } from '@/core/IdService.js';
|
||||
import type { ClipsRepository } from '@/models/index.js';
|
||||
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['clips'],
|
||||
@@ -17,6 +19,14 @@ export const meta = {
|
||||
optional: false, nullable: false,
|
||||
ref: 'Clip',
|
||||
},
|
||||
|
||||
errors: {
|
||||
tooManyClips: {
|
||||
message: 'You cannot create clip any more.',
|
||||
code: 'TOO_MANY_CLIPS',
|
||||
id: '920f7c2d-6208-4b76-8082-e632020f5883',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
@@ -37,9 +47,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
private clipsRepository: ClipsRepository,
|
||||
|
||||
private clipEntityService: ClipEntityService,
|
||||
private roleService: RoleService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const currentCount = await this.clipsRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).clipLimit) {
|
||||
throw new ApiError(meta.errors.tooManyClips);
|
||||
}
|
||||
|
||||
const clip = await this.clipsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFoldersRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
@@ -14,6 +15,11 @@ export const meta = {
|
||||
|
||||
kind: 'write:drive',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 10,
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchFolder: {
|
||||
message: 'No such folder.',
|
||||
|
@@ -6,15 +6,15 @@ import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['following', 'users'],
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 100,
|
||||
max: 50,
|
||||
},
|
||||
|
||||
requireCredential: true,
|
||||
|
@@ -18,7 +18,7 @@ export const meta = {
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 300,
|
||||
max: 20,
|
||||
},
|
||||
|
||||
res: {
|
||||
|
@@ -6,6 +6,7 @@ import { webhookEventTypes } from '@/models/entities/Webhook.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['webhooks'],
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { BlockingsRepository, UserGroupJoiningsRepository, DriveFilesRepository, UserGroupsRepository } from '@/models/index.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
@@ -15,6 +16,11 @@ export const meta = {
|
||||
|
||||
kind: 'write:messaging',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 120,
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { MutingsRepository } from '@/models/index.js';
|
||||
import type { Muting } from '@/models/entities/Muting.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account'],
|
||||
@@ -15,6 +16,11 @@ export const meta = {
|
||||
|
||||
kind: 'write:mutes',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 20,
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import type { NoteFavoritesRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
@@ -13,6 +14,11 @@ export const meta = {
|
||||
|
||||
kind: 'write:favorites',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 20,
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
message: 'No such note.',
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import type { NotesRepository, NoteThreadMutingsRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
@@ -14,6 +15,11 @@ export const meta = {
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 10,
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
message: 'No such note.',
|
||||
|
@@ -17,7 +17,7 @@ export const meta = {
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 300,
|
||||
max: 10,
|
||||
},
|
||||
|
||||
res: {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { UserGroup } from '@/models/entities/UserGroup.js';
|
||||
@@ -16,6 +17,11 @@ export const meta = {
|
||||
|
||||
description: 'Create a new group.',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 10,
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
|
@@ -5,6 +5,8 @@ import type { UserList } from '@/models/entities/UserList.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['lists'],
|
||||
@@ -20,6 +22,14 @@ export const meta = {
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserList',
|
||||
},
|
||||
|
||||
errors: {
|
||||
tooManyUserLists: {
|
||||
message: 'You cannot create user list any more.',
|
||||
code: 'TOO_MANY_USERLISTS',
|
||||
id: '0cf21a28-7715-4f39-a20d-777bfdb8d138',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
@@ -39,8 +49,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
private userListEntityService: UserListEntityService,
|
||||
private idService: IdService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const currentCount = await this.userListsRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
if (currentCount > (await this.roleService.getUserRoleOptions(me.id)).userListLimit) {
|
||||
throw new ApiError(meta.errors.tooManyUserLists);
|
||||
}
|
||||
|
||||
const userList = await this.userListsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
@@ -15,6 +16,11 @@ export const meta = {
|
||||
|
||||
description: 'Add a user to an existing list.',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 30,
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchList: {
|
||||
message: 'No such list.',
|
||||
@@ -105,7 +111,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
|
||||
// Push the user
|
||||
await this.userListService.push(user, userList);
|
||||
await this.userListService.push(user, userList, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,34 +1,34 @@
|
||||
<template>
|
||||
<div ref="rootEl" class="swhvrteh _popup _shadow" :style="{ zIndex }" @contextmenu.prevent="() => {}">
|
||||
<ol v-if="type === 'user'" ref="suggests" class="users">
|
||||
<li v-for="user in users" tabindex="-1" class="user" @click="complete(type, user)" @keydown="onKeydown">
|
||||
<img class="avatar" :src="user.avatarUrl"/>
|
||||
<span class="name">
|
||||
<div ref="rootEl" :class="$style.root" class="_popup _shadow" :style="{ zIndex }" @contextmenu.prevent="() => {}">
|
||||
<ol v-if="type === 'user'" ref="suggests" :class="$style.list">
|
||||
<li v-for="user in users" tabindex="-1" :class="$style.item" @click="complete(type, user)" @keydown="onKeydown">
|
||||
<img :class="$style.avatar" :src="user.avatarUrl"/>
|
||||
<span :class="$style.userName">
|
||||
<MkUserName :key="user.id" :user="user"/>
|
||||
</span>
|
||||
<span class="username">@{{ acct(user) }}</span>
|
||||
<span>@{{ acct(user) }}</span>
|
||||
</li>
|
||||
<li tabindex="-1" class="choose" @click="chooseUser()" @keydown="onKeydown">{{ i18n.ts.selectUser }}</li>
|
||||
<li tabindex="-1" :class="$style.item" @click="chooseUser()" @keydown="onKeydown">{{ i18n.ts.selectUser }}</li>
|
||||
</ol>
|
||||
<ol v-else-if="hashtags.length > 0" ref="suggests" class="hashtags">
|
||||
<li v-for="hashtag in hashtags" tabindex="-1" @click="complete(type, hashtag)" @keydown="onKeydown">
|
||||
<ol v-else-if="hashtags.length > 0" ref="suggests" :class="[$style.list, $style.hashtags]">
|
||||
<li v-for="hashtag in hashtags" tabindex="-1" :class="$style.item" @click="complete(type, hashtag)" @keydown="onKeydown">
|
||||
<span class="name">{{ hashtag }}</span>
|
||||
</li>
|
||||
</ol>
|
||||
<ol v-else-if="emojis.length > 0" ref="suggests" class="emojis">
|
||||
<li v-for="emoji in emojis" :key="emoji.emoji" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
|
||||
<div class="emoji">
|
||||
<ol v-else-if="emojis.length > 0" ref="suggests" :class="$style.list">
|
||||
<li v-for="emoji in emojis" :key="emoji.emoji" :class="$style.item" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
|
||||
<div :class="$style.emoji">
|
||||
<MkEmoji :emoji="emoji.emoji"/>
|
||||
</div>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-if="q" class="name" v-html="sanitizeHtml(emoji.name.replace(q, `<b>${q}</b>`))"></span>
|
||||
<span v-if="q" :class="$style.emojiName" v-html="sanitizeHtml(emoji.name.replace(q, `<b>${q}</b>`))"></span>
|
||||
<span v-else v-text="emoji.name"></span>
|
||||
<span v-if="emoji.aliasOf" class="alias">({{ emoji.aliasOf }})</span>
|
||||
<span v-if="emoji.aliasOf" :class="$style.emojiAlias">({{ emoji.aliasOf }})</span>
|
||||
</li>
|
||||
</ol>
|
||||
<ol v-else-if="mfmTags.length > 0" ref="suggests" class="mfmTags">
|
||||
<li v-for="tag in mfmTags" tabindex="-1" @click="complete(type, tag)" @keydown="onKeydown">
|
||||
<span class="tag">{{ tag }}</span>
|
||||
<ol v-else-if="mfmTags.length > 0" ref="suggests" :class="$style.list">
|
||||
<li v-for="tag in mfmTags" tabindex="-1" :class="$style.item" @click="complete(type, tag)" @keydown="onKeydown">
|
||||
<span>{{ tag }}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
@@ -379,113 +379,88 @@ onBeforeUnmount(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.swhvrteh {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: fixed;
|
||||
max-width: 100%;
|
||||
margin-top: calc(1em + 8px);
|
||||
overflow: clip;
|
||||
transition: top 0.1s ease, left 0.1s ease;
|
||||
}
|
||||
|
||||
> ol {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 4px 0;
|
||||
max-height: 190px;
|
||||
max-width: 500px;
|
||||
overflow: auto;
|
||||
list-style: none;
|
||||
.list {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 4px 0;
|
||||
max-height: 190px;
|
||||
max-width: 500px;
|
||||
overflow: auto;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
> li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
white-space: nowrap;
|
||||
overflow: clip;
|
||||
font-size: 0.9em;
|
||||
cursor: default;
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
white-space: nowrap;
|
||||
overflow: clip;
|
||||
font-size: 0.9em;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&, * {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
* {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--X3);
|
||||
}
|
||||
|
||||
&[data-selected='true'] {
|
||||
background: var(--accent);
|
||||
|
||||
&, * {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--accentDarken);
|
||||
|
||||
&, * {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background: var(--X3);
|
||||
}
|
||||
|
||||
> .users > li {
|
||||
|
||||
.avatar {
|
||||
min-width: 28px;
|
||||
min-height: 28px;
|
||||
max-width: 28px;
|
||||
max-height: 28px;
|
||||
margin: 0 8px 0 0;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
&[data-selected='true'] {
|
||||
background: var(--accent);
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
> .emojis > li {
|
||||
|
||||
.emoji {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
margin: 0 4px 0 0;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
|
||||
> img {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
object-fit: scale-down;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.name {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.alias {
|
||||
flex-shrink: 9999999;
|
||||
margin: 0 0 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> .mfmTags > li {
|
||||
|
||||
.name {
|
||||
}
|
||||
&:active {
|
||||
background: var(--accentDarken);
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
min-width: 28px;
|
||||
min-height: 28px;
|
||||
max-width: 28px;
|
||||
max-height: 28px;
|
||||
margin: 0 8px 0 0;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.userName {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
margin: 0 4px 0 0;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.emojiImg {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
object-fit: scale-down;
|
||||
}
|
||||
|
||||
.emojiName {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.emojiAlias {
|
||||
flex-shrink: 9999999;
|
||||
margin: 0 0 0 8px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -146,6 +146,7 @@ onBeforeUnmount(() => {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
padding: 32px;
|
||||
min-width: 320px;
|
||||
max-width: 480px;
|
||||
|
@@ -15,7 +15,7 @@
|
||||
</template>
|
||||
|
||||
<MkSpacer :margin-min="20" :margin-max="32">
|
||||
<div class="xkpnjxcv _gaps_m">
|
||||
<div class="_gaps_m">
|
||||
<template v-for="item in Object.keys(form).filter(item => !form[item].hidden)">
|
||||
<MkInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1">
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
|
||||
@@ -119,9 +119,3 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xkpnjxcv {
|
||||
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="mk-google">
|
||||
<input v-model="query" type="search" :placeholder="q">
|
||||
<button @click="search"><i class="ti ti-search"></i> {{ $ts.searchByGoogle }}</button>
|
||||
<div :class="$style.root">
|
||||
<input v-model="query" :class="$style.input" type="search" :placeholder="q">
|
||||
<button :class="$style.button" @click="search"><i class="ti ti-search"></i> {{ $ts.searchByGoogle }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -19,33 +19,33 @@ const search = () => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mk-google {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: flex;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
> input {
|
||||
flex-shrink: 1;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
font-size: 16px;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 4px 0 0 4px;
|
||||
-webkit-appearance: textfield;
|
||||
}
|
||||
.input {
|
||||
flex-shrink: 1;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
font-size: 16px;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 4px 0 0 4px;
|
||||
-webkit-appearance: textfield;
|
||||
}
|
||||
|
||||
> button {
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
border: solid 1px var(--divider);
|
||||
border-left: none;
|
||||
border-radius: 0 4px 4px 0;
|
||||
.button {
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
border: solid 1px var(--divider);
|
||||
border-left: none;
|
||||
border-radius: 0 4px 4px 0;
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 2px 4px rgba(#000, 0.15) inset;
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 2px 4px rgba(#000, 0.15) inset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -33,6 +33,7 @@ const modal = $shallowRef<InstanceType<typeof MkModal>>();
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xubzgfga {
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="xubzgfgb" :class="{ cover }" :title="title">
|
||||
<canvas v-if="!loaded" ref="canvas" :width="size" :height="size" :title="title"/>
|
||||
<img v-if="src" :src="src" :title="title" :alt="alt" @load="onLoad"/>
|
||||
<div :class="[$style.root, { [$style.cover]: cover }]" :title="title">
|
||||
<canvas v-if="!loaded" ref="canvas" :class="$style.canvas" :width="size" :height="size" :title="title"/>
|
||||
<img v-if="src" :class="$style.img" :src="src" :title="title" :alt="alt" @load="onLoad"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -45,32 +45,32 @@ onMounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.xubzgfgb {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> canvas,
|
||||
> img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
> canvas {
|
||||
position: absolute;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
> img {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&.cover {
|
||||
> img {
|
||||
> .img {
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.canvas,
|
||||
.img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
position: absolute;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.img {
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
@@ -3,12 +3,12 @@
|
||||
<div class="szkkfdyq _popup _shadow" :class="{ asDrawer: type === 'drawer' }" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : '' }">
|
||||
<div class="main">
|
||||
<template v-for="item in items">
|
||||
<button v-if="item.action" v-click-anime class="_button" @click="$event => { item.action($event); close(); }">
|
||||
<button v-if="item.action" v-click-anime class="_button item" @click="$event => { item.action($event); close(); }">
|
||||
<i class="icon" :class="item.icon"></i>
|
||||
<div class="text">{{ item.text }}</div>
|
||||
<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span>
|
||||
</button>
|
||||
<MkA v-else v-click-anime :to="item.to" @click.passive="close()">
|
||||
<MkA v-else v-click-anime :to="item.to" class="item" @click.passive="close()">
|
||||
<i class="icon" :class="item.icon"></i>
|
||||
<div class="text">{{ item.text }}</div>
|
||||
<span v-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span>
|
||||
@@ -66,6 +66,7 @@ function close() {
|
||||
.szkkfdyq {
|
||||
max-height: 100%;
|
||||
width: min(460px, 100vw);
|
||||
margin: auto;
|
||||
padding: 24px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
@@ -82,11 +83,11 @@ function close() {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> .main, > .sub {
|
||||
> .main {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
|
||||
> * {
|
||||
> .item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -128,11 +129,5 @@ function close() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .sub {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,17 +1,11 @@
|
||||
<template>
|
||||
<MkA v-if="url.startsWith('/')" v-user-preview="canonical" class="akbvjaqn" :class="{ isMe }" :to="url" :style="{ background: bgCss }">
|
||||
<img class="icon" :src="`/avatar/@${username}@${host}`" alt="">
|
||||
<span class="main">
|
||||
<span class="username">@{{ username }}</span>
|
||||
<span v-if="(host != localHost) || $store.state.showFullAcct" class="host">@{{ toUnicode(host) }}</span>
|
||||
<MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :style="{ background: bgCss }">
|
||||
<img :class="$style.icon" :src="`/avatar/@${username}@${host}`" alt="">
|
||||
<span>
|
||||
<span :class="$style.username">@{{ username }}</span>
|
||||
<span v-if="(host != localHost) || $store.state.showFullAcct" :class="$style.host">@{{ toUnicode(host) }}</span>
|
||||
</span>
|
||||
</MkA>
|
||||
<a v-else class="akbvjaqn" :href="url" target="_blank" rel="noopener" :style="{ background: bgCss }">
|
||||
<span class="main">
|
||||
<span class="username">@{{ username }}</span>
|
||||
<span class="host">@{{ toUnicode(host) }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -39,8 +33,8 @@ bg.setAlpha(0.1);
|
||||
const bgCss = bg.toRgbString();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.akbvjaqn {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: inline-block;
|
||||
padding: 4px 8px 4px 4px;
|
||||
border-radius: 999px;
|
||||
@@ -49,18 +43,18 @@ const bgCss = bg.toRgbString();
|
||||
&.isMe {
|
||||
color: var(--mentionMe);
|
||||
}
|
||||
}
|
||||
|
||||
> .icon {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
object-fit: cover;
|
||||
margin: 0 0.2em 0 0;
|
||||
vertical-align: bottom;
|
||||
border-radius: 100%;
|
||||
}
|
||||
.icon {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
object-fit: cover;
|
||||
margin: 0 0.2em 0 0;
|
||||
vertical-align: bottom;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
> .main > .host {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.host {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
@@ -7,9 +7,9 @@
|
||||
:leave-to-class="$style['transition_' + transitionName + '_leaveTo']"
|
||||
:duration="transitionDuration" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened"
|
||||
>
|
||||
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" :class="[$style.root, { [$style.drawer]: type === 'drawer', [$style.dialog]: type === 'dialog' || type === 'dialog:top', [$style.popup]: type === 'popup' }]" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
||||
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" :class="[$style.root, { [$style.drawer]: type === 'drawer', [$style.dialog]: type === 'dialog', [$style.popup]: type === 'popup' }]" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
||||
<div class="_modalBg" :class="[$style.bg, { [$style.bgTransparent]: transparentBg && (type === 'popup') }]" :style="{ zIndex }" @click="onBgClick" @mousedown="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
||||
<div ref="content" :class="[$style.content, { [$style.fixed]: fixed, [$style.top]: type === 'dialog:top' }]" :style="{ zIndex }" @click.self="onBgClick">
|
||||
<div ref="content" :class="[$style.content, { [$style.fixed]: fixed }]" :style="{ zIndex }" @click.self="onBgClick">
|
||||
<slot :max-height="maxHeight" :type="type"></slot>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,7 +33,7 @@ function getFixedContainer(el: Element | null): Element | null {
|
||||
}
|
||||
}
|
||||
|
||||
type ModalTypes = 'popup' | 'dialog' | 'dialog:top' | 'drawer';
|
||||
type ModalTypes = 'popup' | 'dialog' | 'drawer';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
manualShowing?: boolean | null;
|
||||
@@ -413,16 +413,6 @@ defineExpose({
|
||||
-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
|
||||
mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
|
||||
}
|
||||
|
||||
&:global > * {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
&.top {
|
||||
&:global > * {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,10 +440,6 @@ defineExpose({
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
|
||||
&:global > * {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -117,6 +117,7 @@ function onContextmenu(ev: MouseEvent) {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hrmcaedk {
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@@ -85,6 +85,7 @@ defineExpose({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ebkgoccj {
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@@ -8,7 +8,7 @@
|
||||
</template>
|
||||
|
||||
<template #default="{ items: notifications }">
|
||||
<MkDateSeparatedList v-slot="{ item: notification }" class="elsfgstc" :items="notifications" :no-gap="true">
|
||||
<MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :no-gap="true">
|
||||
<XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/>
|
||||
<XNotification v-else :key="notification.id" :notification="notification" :with-time="true" :full="true" class="_panel notification"/>
|
||||
</MkDateSeparatedList>
|
||||
@@ -97,8 +97,8 @@ onUnmounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.elsfgstc {
|
||||
<style lang="scss" module>
|
||||
.list {
|
||||
background: var(--panel);
|
||||
}
|
||||
</style>
|
||||
|
@@ -17,7 +17,7 @@
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<div class="yrolvcoq" :style="{ background: pageMetadata?.value?.bg }" style="container-type: inline-size;">
|
||||
<div :class="$style.root" :style="{ background: pageMetadata?.value?.bg }" style="container-type: inline-size;">
|
||||
<RouterView :router="router"/>
|
||||
</div>
|
||||
</MkWindow>
|
||||
@@ -133,8 +133,8 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.yrolvcoq {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
min-height: 100%;
|
||||
background: var(--bg);
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<MkModal ref="modal" :prefer-type="'dialog:top'" @click="modal.close()" @closed="onModalClosed()">
|
||||
<MkPostForm ref="form" v-bind="props" autofocus freeze-after-posted @posted="onPosted" @cancel="modal.close()" @esc="modal.close()"/>
|
||||
<MkModal ref="modal" :prefer-type="'dialog'" @click="modal.close()" @closed="onModalClosed()">
|
||||
<MkPostForm ref="form" style="margin: 0 auto auto auto;" v-bind="props" autofocus freeze-after-posted @posted="onPosted" @cancel="modal.close()" @esc="modal.close()"/>
|
||||
</MkModal>
|
||||
</template>
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
|
||||
<span class="text" :class="{ up }">
|
||||
<span :class="[$style.text, { [$style.up]: up }]">
|
||||
<MkReactionIcon class="icon" :reaction="reaction"/>
|
||||
</span>
|
||||
</div>
|
||||
@@ -43,30 +43,28 @@ onMounted(() => {
|
||||
position: fixed;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
&:global {
|
||||
> .text {
|
||||
display: block;
|
||||
height: 1em;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
color: var(--accent);
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
transform: translateY(-30px);
|
||||
transition: transform 1s cubic-bezier(0,.5,0,1), opacity 1s cubic-bezier(.5,0,1,.5);
|
||||
will-change: opacity, transform;
|
||||
.text {
|
||||
display: block;
|
||||
height: 1em;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
color: var(--accent);
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
transform: translateY(-30px);
|
||||
transition: transform 1s cubic-bezier(0,.5,0,1), opacity 1s cubic-bezier(.5,0,1,.5);
|
||||
will-change: opacity, transform;
|
||||
|
||||
&.up {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px) rotateZ(v-bind(angle));
|
||||
}
|
||||
}
|
||||
&.up {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px) rotateZ(v-bind(angle));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
|
||||
<div class="beeadbfb">
|
||||
<MkReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
|
||||
<div class="name">{{ reaction.replace('@.', '') }}</div>
|
||||
<div :class="$style.root">
|
||||
<MkReactionIcon :reaction="reaction" :class="$style.icon" :no-style="true"/>
|
||||
<div :class="$style.name">{{ reaction.replace('@.', '') }}</div>
|
||||
</div>
|
||||
</MkTooltip>
|
||||
</template>
|
||||
@@ -23,20 +23,20 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.beeadbfb {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
display: block;
|
||||
width: 60px;
|
||||
font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
|
||||
margin: 0 auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
.icon {
|
||||
display: block;
|
||||
width: 60px;
|
||||
font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
|
||||
margin: 0 auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
> .name {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.name {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
|
||||
<div class="bqxuuuey">
|
||||
<div class="reaction">
|
||||
<MkReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
|
||||
<div class="name">{{ getReactionName(reaction) }}</div>
|
||||
<div :class="$style.root">
|
||||
<div :class="$style.reaction">
|
||||
<MkReactionIcon :reaction="reaction" :class="$style.reactionIcon" :no-style="true"/>
|
||||
<div :class="$style.reactionName">{{ getReactionName(reaction) }}</div>
|
||||
</div>
|
||||
<div class="users">
|
||||
<div v-for="u in users" :key="u.id" class="user">
|
||||
<MkAvatar class="avatar" :user="u"/>
|
||||
<MkUserName class="name" :user="u" :nowrap="true"/>
|
||||
<div :class="$style.users">
|
||||
<div v-for="u in users" :key="u.id" :class="$style.user">
|
||||
<MkAvatar :class="$style.avatar" :user="u"/>
|
||||
<MkUserName :user="u" :nowrap="true"/>
|
||||
</div>
|
||||
<div v-if="users.length > 10" class="omitted">+{{ count - 10 }}</div>
|
||||
<div v-if="users.length > 10">+{{ count - 10 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkTooltip>
|
||||
@@ -43,53 +43,53 @@ function getReactionName(reaction: string): string {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bqxuuuey {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
> .reaction {
|
||||
max-width: 100px;
|
||||
text-align: center;
|
||||
.reaction {
|
||||
max-width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
display: block;
|
||||
width: 60px;
|
||||
font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
|
||||
object-fit: contain;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.reactionIcon {
|
||||
display: block;
|
||||
width: 60px;
|
||||
font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
|
||||
object-fit: contain;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
> .name {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
.reactionName {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
> .users {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 0.95em;
|
||||
border-left: solid 0.5px var(--divider);
|
||||
padding-left: 10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 14px;
|
||||
text-align: left;
|
||||
.users {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 0.95em;
|
||||
border-left: solid 0.5px var(--divider);
|
||||
padding-left: 10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
> .user {
|
||||
line-height: 24px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.user {
|
||||
line-height: 24px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
> .avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -2,12 +2,12 @@
|
||||
<button
|
||||
ref="buttonEl"
|
||||
v-ripple="canToggle"
|
||||
class="hkzvhatu _button"
|
||||
:class="{ reacted: note.myReaction == reaction, canToggle }"
|
||||
class="_button"
|
||||
:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle }]"
|
||||
@click="toggleReaction()"
|
||||
>
|
||||
<MkReactionIcon class="icon" :reaction="reaction"/>
|
||||
<span class="count">{{ count }}</span>
|
||||
<MkReactionIcon :class="$style.icon" :reaction="reaction"/>
|
||||
<span :class="$style.count">{{ count }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -92,8 +92,8 @@ useTooltip(buttonEl, async (showing) => {
|
||||
}, 100);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hkzvhatu {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
margin: 2px;
|
||||
@@ -127,11 +127,11 @@ useTooltip(buttonEl, async (showing) => {
|
||||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .count {
|
||||
font-size: 0.9em;
|
||||
line-height: 32px;
|
||||
margin: 0 0 0 4px;
|
||||
}
|
||||
.count {
|
||||
font-size: 0.9em;
|
||||
line-height: 32px;
|
||||
margin: 0 0 0 4px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<TransitionGroup :name="$store.state.animation ? 'x' : ''" tag="div" class="tdflqwzn" :class="{ isMe }">
|
||||
<TransitionGroup
|
||||
:enter-active-class="$store.state.animation ? $style.transition_x_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_x_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_x_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_x_leaveTo : ''"
|
||||
:move-class="$store.state.animation ? $style.transition_x_move : ''"
|
||||
tag="div" :class="$style.root"
|
||||
>
|
||||
<XReaction v-for="(count, reaction) in note.reactions" :key="reaction" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note"/>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
@@ -19,29 +26,26 @@ const initialReactions = new Set(Object.keys(props.note.reactions));
|
||||
const isMe = computed(() => $i && $i.id === props.note.userId);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.x-move, .x-enter-active, .x-leave-active {
|
||||
<style lang="scss" module>
|
||||
.transition_x_move,
|
||||
.transition_x_enterActive,
|
||||
.transition_x_leaveActive {
|
||||
transition: opacity 0.2s cubic-bezier(0,.5,.5,1), transform 0.2s cubic-bezier(0,.5,.5,1) !important;
|
||||
}
|
||||
.x-enter-from, .x-leave-to {
|
||||
.transition_x_enterFrom,
|
||||
.transition_x_leaveTo {
|
||||
opacity: 0;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
.x-leave-active {
|
||||
position: absolute;
|
||||
.transition_x_leaveActive {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.tdflqwzn {
|
||||
.root {
|
||||
margin: 4px -2px 0 -2px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.isMe {
|
||||
> span {
|
||||
cursor: default !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="jmgmzlwq"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i>{{ i18n.ts.remoteUserCaution }}<a class="link" :href="href" rel="nofollow noopener" target="_blank">{{ i18n.ts.showOnRemote }}</a></div>
|
||||
<div :class="$style.root"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i>{{ i18n.ts.remoteUserCaution }}<a :class="$style.link" :href="href" rel="nofollow noopener" target="_blank">{{ i18n.ts.showOnRemote }}</a></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -10,18 +10,18 @@ defineProps<{
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.jmgmzlwq {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
font-size: 0.8em;
|
||||
padding: 16px;
|
||||
background: var(--infoWarnBg);
|
||||
color: var(--infoWarnFg);
|
||||
border-radius: var(--radius);
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
> .link {
|
||||
margin-left: 4px;
|
||||
color: var(--accent);
|
||||
}
|
||||
.link {
|
||||
margin-left: 4px;
|
||||
color: var(--accent);
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span class="mk-sparkle">
|
||||
<span ref="el">
|
||||
<span :class="$style.root">
|
||||
<span ref="el" style="display: inline-block;">
|
||||
<slot></slot>
|
||||
</span>
|
||||
<!-- なぜか path に対する key が機能しないため
|
||||
@@ -32,7 +32,7 @@
|
||||
</path>
|
||||
</svg>
|
||||
-->
|
||||
<svg v-for="particle in particles" :key="particle.id" :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg v-for="particle in particles" :key="particle.id" :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" xmlns="http://www.w3.org/2000/svg" style="position: absolute; top: -32px; left: -32px;">
|
||||
<path
|
||||
style="transform-origin: center; transform-box: fill-box;"
|
||||
:transform="`translate(${particle.x} ${particle.y})`"
|
||||
@@ -111,20 +111,10 @@ onUnmounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mk-sparkle {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
> svg {
|
||||
position: absolute;
|
||||
top: -32px;
|
||||
left: -32px;
|
||||
pointer-events: none;
|
||||
}
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
@@ -34,6 +34,7 @@ onMounted(() => {
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
margin: auto;
|
||||
position: relative;
|
||||
padding: 32px;
|
||||
min-width: 320px;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-tooltip="text" class="fzgwjkgc" :class="user.onlineStatus"></div>
|
||||
<div v-tooltip="text" :class="[$style.root, $style['status_' + user.onlineStatus]]"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -21,24 +21,24 @@ const text = $computed(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fzgwjkgc {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
box-shadow: 0 0 0 3px var(--panel);
|
||||
border-radius: 120%; // Blinkのバグか知らんけど、100%ぴったりにすると何故か若干楕円でレンダリングされる
|
||||
|
||||
&.online {
|
||||
&.status_online {
|
||||
background: #58d4c9;
|
||||
}
|
||||
|
||||
&.active {
|
||||
&.status_active {
|
||||
background: #e4bc48;
|
||||
}
|
||||
|
||||
&.offline {
|
||||
&.status_offline {
|
||||
background: #ea5353;
|
||||
}
|
||||
|
||||
&.unknown {
|
||||
&.status_unknown {
|
||||
background: #888;
|
||||
}
|
||||
}
|
||||
|
@@ -37,6 +37,7 @@ watch(() => props.showing, () => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.iuyakobc {
|
||||
margin: auto;
|
||||
position: relative;
|
||||
padding: 32px;
|
||||
box-sizing: border-box;
|
||||
|
@@ -19,9 +19,9 @@
|
||||
@update:model-value="v => emit('updateWidgets', v)"
|
||||
>
|
||||
<template #item="{element}">
|
||||
<div :class="[$style.widget, $style['customize-container']]">
|
||||
<div :class="[$style.widget, $style['customize-container']]" class="data-cy-customize-container">
|
||||
<button :class="$style['customize-container-config']" class="_button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button>
|
||||
<button :class="$style['customize-container-remove']" class="_button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button>
|
||||
<button :class="$style['customize-container-remove']" class="_button data-cy-customize-container-remove" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button>
|
||||
<div class="handle">
|
||||
<component :is="`widget-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style['customize-container-handle-widget']" :widget="element" @update-props="updateWidget(element.id, $event)"/>
|
||||
</div>
|
||||
@@ -171,7 +171,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
|
||||
|
||||
&-widget {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import { Component, markRaw, Ref, ref, defineAsyncComponent } from 'vue';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import insertTextAtCursor from 'insert-text-at-cursor';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { i18n } from './i18n';
|
||||
import MkPostFormDialog from '@/components/MkPostFormDialog.vue';
|
||||
import MkWaitingDialog from '@/components/MkWaitingDialog.vue';
|
||||
import { MenuItem } from '@/types/menu';
|
||||
@@ -17,9 +18,16 @@ export const apiWithDialog = ((
|
||||
) => {
|
||||
const promise = api(endpoint, data, token);
|
||||
promiseDialog(promise, null, (err) => {
|
||||
let title = null;
|
||||
let text = err.message + '\n' + (err as any).id;
|
||||
if (err.code.startsWith('TOO_MANY')) {
|
||||
title = i18n.ts.youCannotCreateAnymore;
|
||||
text = `${i18n.ts.error}: ${err.id}`;
|
||||
}
|
||||
alert({
|
||||
type: 'error',
|
||||
text: err.message + '\n' + (err as any).id,
|
||||
title,
|
||||
text,
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -116,6 +116,18 @@
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.pinMax }}</template>
|
||||
<template #suffix>{{ options_pinLimit_useDefault ? i18n.ts._role.useBaseValue : (options_pinLimit_value) }}</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="options_pinLimit_useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="options_pinLimit_value" :disabled="options_pinLimit_useDefault" type="number" :readonly="readonly">
|
||||
</MkInput>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.antennaMax }}</template>
|
||||
<template #suffix>{{ options_antennaLimit_useDefault ? i18n.ts._role.useBaseValue : (options_antennaLimit_value) }}</template>
|
||||
@@ -152,6 +164,54 @@
|
||||
</MkInput>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.clipMax }}</template>
|
||||
<template #suffix>{{ options_clipLimit_useDefault ? i18n.ts._role.useBaseValue : (options_clipLimit_value) }}</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="options_clipLimit_useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="options_clipLimit_value" :disabled="options_clipLimit_useDefault" type="number" :readonly="readonly">
|
||||
</MkInput>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.noteEachClipsMax }}</template>
|
||||
<template #suffix>{{ options_noteEachClipsLimit_useDefault ? i18n.ts._role.useBaseValue : (options_noteEachClipsLimit_value) }}</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="options_noteEachClipsLimit_useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="options_noteEachClipsLimit_value" :disabled="options_noteEachClipsLimit_useDefault" type="number" :readonly="readonly">
|
||||
</MkInput>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.userListMax }}</template>
|
||||
<template #suffix>{{ options_userListLimit_useDefault ? i18n.ts._role.useBaseValue : (options_userListLimit_value) }}</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="options_userListLimit_useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="options_userListLimit_value" :disabled="options_userListLimit_useDefault" type="number" :readonly="readonly">
|
||||
</MkInput>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.userEachUserListsMax }}</template>
|
||||
<template #suffix>{{ options_userEachUserListsLimit_useDefault ? i18n.ts._role.useBaseValue : (options_userEachUserListsLimit_value) }}</template>
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="options_userEachUserListsLimit_useDefault" :readonly="readonly">
|
||||
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="options_userEachUserListsLimit_value" :disabled="options_userEachUserListsLimit_useDefault" type="number" :readonly="readonly">
|
||||
</MkInput>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSlot>
|
||||
|
||||
@@ -217,12 +277,22 @@ let options_canManageCustomEmojis_useDefault = $ref(role?.options?.canManageCust
|
||||
let options_canManageCustomEmojis_value = $ref(role?.options?.canManageCustomEmojis?.value ?? false);
|
||||
let options_driveCapacityMb_useDefault = $ref(role?.options?.driveCapacityMb?.useDefault ?? true);
|
||||
let options_driveCapacityMb_value = $ref(role?.options?.driveCapacityMb?.value ?? 0);
|
||||
let options_pinLimit_useDefault = $ref(role?.options?.pinLimit?.useDefault ?? true);
|
||||
let options_pinLimit_value = $ref(role?.options?.pinLimit?.value ?? 0);
|
||||
let options_antennaLimit_useDefault = $ref(role?.options?.antennaLimit?.useDefault ?? true);
|
||||
let options_antennaLimit_value = $ref(role?.options?.antennaLimit?.value ?? 0);
|
||||
let options_wordMuteLimit_useDefault = $ref(role?.options?.wordMuteLimit?.useDefault ?? true);
|
||||
let options_wordMuteLimit_value = $ref(role?.options?.wordMuteLimit?.value ?? 0);
|
||||
let options_webhookLimit_useDefault = $ref(role?.options?.webhookLimit?.useDefault ?? true);
|
||||
let options_webhookLimit_value = $ref(role?.options?.webhookLimit?.value ?? 0);
|
||||
let options_clipLimit_useDefault = $ref(role?.options?.clipLimit?.useDefault ?? true);
|
||||
let options_clipLimit_value = $ref(role?.options?.clipLimit?.value ?? 0);
|
||||
let options_noteEachClipsLimit_useDefault = $ref(role?.options?.noteEachClipsLimit?.useDefault ?? true);
|
||||
let options_noteEachClipsLimit_value = $ref(role?.options?.noteEachClipsLimit?.value ?? 0);
|
||||
let options_userListLimit_useDefault = $ref(role?.options?.userListLimit?.useDefault ?? true);
|
||||
let options_userListLimit_value = $ref(role?.options?.userListLimit?.value ?? 0);
|
||||
let options_userEachUserListsLimit_useDefault = $ref(role?.options?.userEachUserListsLimit?.useDefault ?? true);
|
||||
let options_userEachUserListsLimit_value = $ref(role?.options?.userEachUserListsLimit?.value ?? 0);
|
||||
|
||||
if (_DEV_) {
|
||||
watch($$(condFormula), () => {
|
||||
@@ -238,9 +308,14 @@ function getOptions() {
|
||||
canInvite: { useDefault: options_canInvite_useDefault, value: options_canInvite_value },
|
||||
canManageCustomEmojis: { useDefault: options_canManageCustomEmojis_useDefault, value: options_canManageCustomEmojis_value },
|
||||
driveCapacityMb: { useDefault: options_driveCapacityMb_useDefault, value: options_driveCapacityMb_value },
|
||||
pinLimit: { useDefault: options_pinLimit_useDefault, value: options_pinLimit_value },
|
||||
antennaLimit: { useDefault: options_antennaLimit_useDefault, value: options_antennaLimit_value },
|
||||
wordMuteLimit: { useDefault: options_wordMuteLimit_useDefault, value: options_wordMuteLimit_value },
|
||||
webhookLimit: { useDefault: options_webhookLimit_useDefault, value: options_webhookLimit_value },
|
||||
clipLimit: { useDefault: options_clipLimit_useDefault, value: options_clipLimit_value },
|
||||
noteEachClipsLimit: { useDefault: options_noteEachClipsLimit_useDefault, value: options_noteEachClipsLimit_value },
|
||||
userListLimit: { useDefault: options_userListLimit_useDefault, value: options_userListLimit_value },
|
||||
userEachUserListsLimit: { useDefault: options_userEachUserListsLimit_useDefault, value: options_userEachUserListsLimit_value },
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -56,6 +56,13 @@
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.pinMax }}</template>
|
||||
<template #suffix>{{ options_pinLimit }}</template>
|
||||
<MkInput v-model="options_pinLimit" type="number">
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.antennaMax }}</template>
|
||||
<template #suffix>{{ options_antennaLimit }}</template>
|
||||
@@ -78,6 +85,34 @@
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.clipMax }}</template>
|
||||
<template #suffix>{{ options_clipLimit }}</template>
|
||||
<MkInput v-model="options_clipLimit" type="number">
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.noteEachClipsMax }}</template>
|
||||
<template #suffix>{{ options_noteEachClipsLimit }}</template>
|
||||
<MkInput v-model="options_noteEachClipsLimit" type="number">
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.userListMax }}</template>
|
||||
<template #suffix>{{ options_userListLimit }}</template>
|
||||
<MkInput v-model="options_userListLimit" type="number">
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._role._options.userEachUserListsMax }}</template>
|
||||
<template #suffix>{{ options_userEachUserListsLimit }}</template>
|
||||
<MkInput v-model="options_userEachUserListsLimit" type="number">
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</MkFolder>
|
||||
@@ -116,9 +151,14 @@ let options_canPublicNote = $ref(instance.baseRole.canPublicNote);
|
||||
let options_canInvite = $ref(instance.baseRole.canInvite);
|
||||
let options_canManageCustomEmojis = $ref(instance.baseRole.canManageCustomEmojis);
|
||||
let options_driveCapacityMb = $ref(instance.baseRole.driveCapacityMb);
|
||||
let options_pinLimit = $ref(instance.baseRole.pinLimit);
|
||||
let options_antennaLimit = $ref(instance.baseRole.antennaLimit);
|
||||
let options_wordMuteLimit = $ref(instance.baseRole.wordMuteLimit);
|
||||
let options_webhookLimit = $ref(instance.baseRole.webhookLimit);
|
||||
let options_clipLimit = $ref(instance.baseRole.clipLimit);
|
||||
let options_noteEachClipsLimit = $ref(instance.baseRole.noteEachClipsLimit);
|
||||
let options_userListLimit = $ref(instance.baseRole.userListLimit);
|
||||
let options_userEachUserListsLimit = $ref(instance.baseRole.userEachUserListsLimit);
|
||||
|
||||
async function updateBaseRole() {
|
||||
await os.apiWithDialog('admin/roles/update-default-role-override', {
|
||||
@@ -129,9 +169,14 @@ async function updateBaseRole() {
|
||||
canInvite: options_canInvite,
|
||||
canManageCustomEmojis: options_canManageCustomEmojis,
|
||||
driveCapacityMb: options_driveCapacityMb,
|
||||
pinLimit: options_pinLimit,
|
||||
antennaLimit: options_antennaLimit,
|
||||
wordMuteLimit: options_wordMuteLimit,
|
||||
webhookLimit: options_webhookLimit,
|
||||
clipLimit: options_clipLimit,
|
||||
noteEachClipsLimit: options_noteEachClipsLimit,
|
||||
userListLimit: options_userListLimit,
|
||||
userEachUserListsLimit: options_userEachUserListsLimit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -172,6 +172,7 @@ hr {
|
||||
}
|
||||
|
||||
._button {
|
||||
@extend ._noSelect;
|
||||
appearance: none;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
@@ -188,14 +189,6 @@ hr {
|
||||
line-height: inherit;
|
||||
max-width: 100%;
|
||||
|
||||
&, * {
|
||||
@extend ._noSelect;
|
||||
}
|
||||
|
||||
* {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
Reference in New Issue
Block a user