diff --git a/.dockerignore b/.dockerignore index 854e643d393b..8f984831ef86 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,9 +16,15 @@ files/ misskey-assets/ fluent-emojis/ .pnp.* + +# .yarn関連 .yarn/* !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks !.yarn/versions + +.idea/ +packages/*/.vscode/ +packages/backend/test/docker-compose.yml diff --git a/.dockleignore b/.dockleignore new file mode 100644 index 000000000000..2f9326645641 --- /dev/null +++ b/.dockleignore @@ -0,0 +1,3 @@ +DKL-DI-0005 +DKL-DI-0006 +DKL-LI-0003 diff --git a/.github/workflows/docker-develop.yml b/.github/workflows/docker-develop.yml index 63dc940e24c4..a999dc51e61d 100644 --- a/.github/workflows/docker-develop.yml +++ b/.github/workflows/docker-develop.yml @@ -14,6 +14,8 @@ jobs: steps: - name: Check out the repo uses: actions/checkout@v3.3.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2.3.0 - name: Docker meta id: meta uses: docker/metadata-action@v4 diff --git a/.github/workflows/dockle.yml b/.github/workflows/dockle.yml new file mode 100644 index 000000000000..9b79ee54f0ac --- /dev/null +++ b/.github/workflows/dockle.yml @@ -0,0 +1,30 @@ +--- +name: Dockle + +on: + push: + branches: + - master + - develop + pull_request: + +jobs: + dockle: + runs-on: ubuntu-latest + env: + DOCKER_CONTENT_TRUST: 1 + steps: + - uses: actions/checkout@v3.2.0 + - run: | + curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v0.4.10/dockle_0.4.10_Linux-64bit.deb" + sudo dpkg -i dockle.deb + - run: | + cp .config/docker_example.env .config/docker.env + cp ./docker-compose.yml.example ./docker-compose.yml + - run: | + docker compose up -d web + docker tag "$(docker compose images web | awk 'OFS=":" {print $4}' | tail -n +2)" misskey-web:latest + - run: | + cmd="dockle --exit-code 1 misskey-web:latest ${image_name}" + echo "> ${cmd}" + eval "${cmd}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 914bde051ca7..71426953508d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,23 @@ You should also include the user name that made the change. --> +## 13.4.0 (2023/02/05) + +### Improvements +- ロールにアイコンを設定してユーザー名の横に表示できるように +- feat: timeline page for non-login users +- 実績の単なるラッキーの獲得確立を調整 +- Add Thai language support + +### Bugfixes +- fix(server): 自分のノートをお気に入りに登録しても実績解除される問題を修正 +- fix(server): clean up file in FileServer +- fix(server): Deny UNIX domain socket +- fix(server): validate filename and emoji name to improve security +- fix(client): validate input response in aiscript +- fix(client): add webhook delete button +- fix(client): tweak notification style +- fix(client): インラインコードを折り返して表示する ## 13.3.3 (2023/02/04) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 811e4219e5c6..e5399267894d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -121,7 +121,7 @@ cp .github/misskey/test.yml .config/ ``` Prepare DB/Redis for testing. ``` -docker-compose -f packages/backend/test/docker-compose.yml up +docker compose -f packages/backend/test/docker-compose.yml up ``` Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`. diff --git a/Dockerfile b/Dockerfile index 3876b5f6ce2e..89a8d38f8c1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,9 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \ && apt-get update \ && apt-get install -yqq --no-install-recommends \ - build-essential + build-essential wget ca-certificates \ + && wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq \ + && chmod +x /usr/bin/yq RUN corepack enable @@ -29,6 +31,7 @@ ARG NODE_ENV=production RUN git submodule update --init RUN pnpm build +RUN rm -rf .git/ FROM node:${NODE_VERSION}-slim AS runner @@ -44,11 +47,14 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ffmpeg tini \ && corepack enable \ && groupadd -g "${GID}" misskey \ - && useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey + && useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \ + && find / -type f -perm /u+s -ignore_readdir_race -exec chmod u-s {} \; \ + && find / -type f -perm /g+s -ignore_readdir_race -exec chmod g-s {} \; USER misskey WORKDIR /misskey +COPY --from=builder /usr/bin/yq /usr/bin/yq 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 @@ -58,5 +64,6 @@ COPY --chown=misskey:misskey --from=builder /misskey/fluent-emojis /misskey/flue COPY --chown=misskey:misskey . ./ ENV NODE_ENV=production +HEALTHCHECK --interval=5s --retries=20 CMD ["/bin/bash", "/misskey/healthcheck.sh"] ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["pnpm", "run", "migrateandstart"] diff --git a/healthcheck.sh b/healthcheck.sh new file mode 100644 index 000000000000..f8e598b28233 --- /dev/null +++ b/healthcheck.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +PORT=$(yq '.port' /misskey/.config/default.yml) +curl -s -S -o /dev/null "http://localhost:${PORT}" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 423116416a4e..dd1494fb216d 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1195,6 +1195,9 @@ _role: baseRole: "Rollenvorlage" useBaseValue: "Wert der Rollenvorlage verwenden" chooseRoleToAssign: "Zuzuweisende Rolle auswählen" + iconUrl: "Icon-URL" + asBadge: "Als Abzeichen anzeigen" + descriptionOfAsBadge: "Ist dies aktiviert, so wird das Icon dieser Rolle an der Seite der Namen von Benutzern mit dieser Rolle angezeigt." canEditMembersByModerator: "Moderatoren können Benutzern diese Rolle zuweisen" descriptionOfCanEditMembersByModerator: "Wenn aktiviert, so können Moderatoren und Adminstratoren anderen Benutzern diese Rolle zuweisen bzw. diese Zuweisung aufheben. Wenn deaktiviert, so ist es nur Administratoren möglich, Zuweisungen dieser Rolle zu verwalten." priority: "Priorität" diff --git a/locales/en-US.yml b/locales/en-US.yml index 4fd3bf3f0d50..0c39a5e35657 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1195,6 +1195,9 @@ _role: baseRole: "Role template" useBaseValue: "Use role template value" chooseRoleToAssign: "Select the role to assign" + iconUrl: "Icon URL" + asBadge: "Show as badge" + descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on." canEditMembersByModerator: "Allow moderators to edit the list of members for this role" descriptionOfCanEditMembersByModerator: "When turned on, moderators as well as administrators will be able to assign and unassign users to this role. When turned off, only administrators will be able to assign users." priority: "Priority" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 3e062ba51bf9..ba1c717f856b 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1195,6 +1195,9 @@ _role: baseRole: "Rol base" useBaseValue: "Usar los valores del rol base" chooseRoleToAssign: "Selecciona el rol para asignar" + iconUrl: "URL del ícono" + asBadge: "Mostrar como emblema" + descriptionOfAsBadge: "Este ícono de rol se mostrará a lado del nombre de usuario cuando este rol se encuentre activo." canEditMembersByModerator: "Permitir a los moderadores editar los miembros" descriptionOfCanEditMembersByModerator: "Si se activa, los moderadores, al igual que los administradores, serán capaces de asignar/quitar usuarios a éste rol. Si se desactiva, sólo los administradores podrán hacerlo." priority: "Prioridad" diff --git a/locales/index.js b/locales/index.js index 92cd9b467c28..2248bb6ac976 100644 --- a/locales/index.js +++ b/locales/index.js @@ -34,6 +34,7 @@ const languages = [ 'pt-PT', 'ru-RU', 'sk-SK', + 'th-TH', 'ug-CN', 'uk-UA', 'vi-VN', diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a241d54b47f4..6286367b50f6 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1148,7 +1148,7 @@ _achievements: description: "ここをクリックした" _justPlainLucky: title: "単なるラッキー" - description: "10秒ごとに0.01%の確率で獲得" + description: "10秒ごとに0.005%の確率で獲得" _setNameToSyuilo: title: "神様コンプレックス" description: "名前を syuilo に設定した" @@ -1184,7 +1184,7 @@ _role: description: "ロールの説明" permission: "ロールの権限" descriptionOfPermission: "モデレーターは基本的なモデレーションに関する操作を行えます。\n管理者はインスタンスの全ての設定を変更できます。" - assignTarget: "アサインターゲット" + assignTarget: "アサイン" descriptionOfAssignTarget: "マニュアルは誰がこのロールに含まれるかを手動で管理します。\nコンディショナルは条件を設定し、それに合致するユーザーが自動で含まれるようになります。" manual: "マニュアル" conditional: "コンディショナル" @@ -1197,6 +1197,9 @@ _role: baseRole: "ベースロール" useBaseValue: "ベースロールの値を使用" chooseRoleToAssign: "アサインするロールを選択" + iconUrl: "アイコン画像のURL" + asBadge: "バッジとして表示" + descriptionOfAsBadge: "オンにすると、ユーザー名の横にロールのアイコンが表示されます。" canEditMembersByModerator: "モデレーターのメンバー編集を許可" descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。" priority: "優先度" diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml new file mode 100644 index 000000000000..a46ddcc10f98 --- /dev/null +++ b/locales/lo-LA.yml @@ -0,0 +1,2 @@ +--- +_lang_: "ພາສາລາວ" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index ca23e44fa46f..8087090f5afd 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1195,6 +1195,9 @@ _role: baseRole: "บทบาทพื้นฐาน" useBaseValue: "ใช้บทบาทพื้นฐานเริ่มต้น" chooseRoleToAssign: "เลือกบทบาทที่ต้องการกำหนด" + iconUrl: "ไอคอน URL" + asBadge: "แสดงเป็นตรา" + descriptionOfAsBadge: "ไอคอนของบทบาทนี้จะปรากฏถัดจากชื่อผู้ใช้ของผู้ใช้งานด้วยบทบาทนี้ถ้าหากเปิดใช้งาน" canEditMembersByModerator: "อนุญาตให้ผู้ดูแลแก้ไขสมาชิก" descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ ผู้ดูแลนอกเหนือจากผู้ดูแลระบบแล้ว จะสามารถกำหนดและยกเลิกการมอบหมายบทบาทนี้ให้กับผู้ใช้ได้ เมื่อปิด เฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถกำหนดผู้ใช้ได้นะ" priority: "ลำดับความสำคัญ" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index bc29aba0a010..7796dc3de198 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1195,6 +1195,9 @@ _role: baseRole: "基本角色" useBaseValue: "使用基本角色的值" chooseRoleToAssign: "选择要分配的角色" + iconUrl: "图标URL" + asBadge: "作为徽章显示" + descriptionOfAsBadge: "开启后,用户名旁边将会出现角色图标。" canEditMembersByModerator: "允许监察者编辑成员" descriptionOfCanEditMembersByModerator: "如果选中,监察者和管理员都能够为用户分配/取消分配角色。如果未选中,则只有管理员可以执行此操作。" priority: "优先级" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 058ee416ef86..74f3c237faf0 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1195,6 +1195,9 @@ _role: baseRole: "基本角色" useBaseValue: "使用基本角色的值" chooseRoleToAssign: "選擇要指派的角色" + iconUrl: "圖示的URL" + asBadge: "顯示為徽章" + descriptionOfAsBadge: "開啟的話,角色圖示會顯示在用戶名旁邊。" canEditMembersByModerator: "允許編輯監察員的成員" descriptionOfCanEditMembersByModerator: "如果開啟,管理員與監察員都可以為使用者指派/解除指派該角色。如果關閉,則只有管理員可以執行。" priority: "優先級" diff --git a/package.json b/package.json index 5599a87ee781..591623defe4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "13.3.4", + "version": "13.4.0", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/migration/1675557528704-role-icon-badge.js b/packages/backend/migration/1675557528704-role-icon-badge.js new file mode 100644 index 000000000000..0ebca088e322 --- /dev/null +++ b/packages/backend/migration/1675557528704-role-icon-badge.js @@ -0,0 +1,13 @@ +export class roleIconBadge1675557528704 { + name = 'roleIconBadge1675557528704' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "role" ADD "iconUrl" character varying(512)`); + await queryRunner.query(`ALTER TABLE "role" ADD "asBadge" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "asBadge"`); + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "iconUrl"`); + } +} diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts index a971e06fd830..852c1f32e3a5 100644 --- a/packages/backend/src/core/DownloadService.ts +++ b/packages/backend/src/core/DownloadService.ts @@ -60,6 +60,7 @@ export class DownloadService { retry: { limit: 0, }, + enableUnixSockets: false, }).on('response', (res: Got.Response) => { if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !this.config.proxy && res.ip) { if (this.isPrivateIp(res.ip)) { diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index f8f9231cdda3..d15d8c0aeead 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -202,6 +202,19 @@ export class RoleService implements OnApplicationShutdown { return [...assignedRoles, ...matchedCondRoles]; } + /** + * 指定ユーザーのバッジロール一覧取得 + */ + @bindThis + public async getUserBadgeRoles(userId: User['id']) { + const assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); + const assignedRoleIds = assigns.map(x => x.roleId); + const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({})); + const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id)); + // コンディショナルロールも含めるのは負荷高そうだから一旦無し + return assignedBadgeRoles; + } + @bindThis public async getUserPolicies(userId: User['id'] | null): Promise { const meta = await this.metaService.fetch(); diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts index 52f33744682d..dbb89ff19b87 100644 --- a/packages/backend/src/core/entities/RoleEntityService.ts +++ b/packages/backend/src/core/entities/RoleEntityService.ts @@ -56,11 +56,13 @@ export class RoleEntityService { name: role.name, description: role.description, color: role.color, + iconUrl: role.iconUrl, target: role.target, condFormula: role.condFormula, isPublic: role.isPublic, isAdministrator: role.isAdministrator, isModerator: role.isModerator, + asBadge: role.asBadge, canEditMembersByModerator: role.canEditMembersByModerator, policies: policies, usersCount: assigns.length, diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index ff42c0735957..eea9d5567d16 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -415,6 +415,11 @@ export class UserEntityService implements OnModuleInit { } : undefined) : undefined, emojis: this.customEmojiService.populateEmojis(user.emojis, user.host), onlineStatus: this.getOnlineStatus(user), + // パフォーマンス上の理由でローカルユーザーのみ + badgeRoles: user.host == null ? this.roleService.getUserBadgeRoles(user.id).then(rs => rs.map(r => ({ + name: r.name, + iconUrl: r.iconUrl, + }))) : undefined, ...(opts.detail ? { url: profile!.url, @@ -454,6 +459,7 @@ export class UserEntityService implements OnModuleInit { id: role.id, name: role.name, color: role.color, + iconUrl: role.iconUrl, description: role.description, isModerator: role.isModerator, isAdministrator: role.isAdministrator, diff --git a/packages/backend/src/models/entities/Role.ts b/packages/backend/src/models/entities/Role.ts index abd5f864a27b..8cf6811863b4 100644 --- a/packages/backend/src/models/entities/Role.ts +++ b/packages/backend/src/models/entities/Role.ts @@ -102,6 +102,11 @@ export class Role { }) public color: string | null; + @Column('varchar', { + length: 512, nullable: true, + }) + public iconUrl: string | null; + @Column('enum', { enum: ['manual', 'conditional'], default: 'manual', @@ -118,6 +123,12 @@ export class Role { }) public isPublic: boolean; + // trueの場合ユーザー名の横にバッジとして表示 + @Column('boolean', { + default: false, + }) + public asBadge: boolean; + @Column('boolean', { default: false, }) diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index 87b23f1891c2..df024a8f3ccc 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -12,9 +12,9 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp, createTempDir } from '@/misc/create-temp.js'; import { DownloadService } from '@/core/DownloadService.js'; +import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type Bull from 'bull'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ExportCustomEmojisProcessorService { @@ -82,6 +82,10 @@ export class ExportCustomEmojisProcessorService { }); for (const emoji of customEmojis) { + if (!/^[a-zA-Z0-9_]+$/.test(emoji.name)) { + this.logger.error(`invalid emoji name: ${emoji.name}`); + continue; + } const ext = mime.extension(emoji.type ?? 'image/png'); const fileName = emoji.name + (ext ? '.' + ext : ''); const emojiPath = path + '/' + fileName; diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 0061c2a8f722..2d43615e2579 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -81,6 +81,10 @@ export class ImportCustomEmojisProcessorService { for (const record of meta.emojis) { if (!record.downloaded) continue; + if (!/^[a-zA-Z0-9_]+?([a-zA-Z0-9\.]+)?$/.test(record.fileName)) { + this.logger.error(`invalid filename: ${record.fileName}`); + continue; + } const emojiInfo = record.emoji; const emojiPath = outputPath + '/' + record.fileName; await this.emojisRepository.delete({ diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 39bc4c1d969a..4bd6d0f55686 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -146,6 +146,8 @@ export class FileServerService { const url = new URL(`${this.config.mediaProxy}/static.webp`); url.searchParams.set('url', file.url); url.searchParams.set('static', '1'); + + file.cleanup(); return await reply.redirect(301, url.toString()); } else if (file.mime.startsWith('video/')) { image = await this.videoProcessingService.generateVideoThumbnail(file.path); @@ -158,6 +160,8 @@ export class FileServerService { const url = new URL(`${this.config.mediaProxy}/svg.webp`); url.searchParams.set('url', file.url); + + file.cleanup(); return await reply.redirect(301, url.toString()); } } diff --git a/packages/backend/src/server/api/endpoints/admin/roles/create.ts b/packages/backend/src/server/api/endpoints/admin/roles/create.ts index f136c6d62439..1a2a9fb7470e 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/create.ts @@ -19,11 +19,13 @@ export const paramDef = { name: { type: 'string' }, description: { type: 'string' }, color: { type: 'string', nullable: true }, + iconUrl: { type: 'string', nullable: true }, target: { type: 'string' }, condFormula: { type: 'object' }, isPublic: { type: 'boolean' }, isModerator: { type: 'boolean' }, isAdministrator: { type: 'boolean' }, + asBadge: { type: 'boolean' }, canEditMembersByModerator: { type: 'boolean' }, policies: { type: 'object', @@ -33,11 +35,13 @@ export const paramDef = { 'name', 'description', 'color', + 'iconUrl', 'target', 'condFormula', 'isPublic', 'isModerator', 'isAdministrator', + 'asBadge', 'canEditMembersByModerator', 'policies', ], @@ -64,11 +68,13 @@ export default class extends Endpoint { name: ps.name, description: ps.description, color: ps.color, + iconUrl: ps.iconUrl, target: ps.target, condFormula: ps.condFormula, isPublic: ps.isPublic, isAdministrator: ps.isAdministrator, isModerator: ps.isModerator, + asBadge: ps.asBadge, canEditMembersByModerator: ps.canEditMembersByModerator, policies: ps.policies, }).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0])); diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts index fc4c3d8f1175..c9f4a9fed8f0 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts @@ -27,11 +27,13 @@ export const paramDef = { name: { type: 'string' }, description: { type: 'string' }, color: { type: 'string', nullable: true }, + iconUrl: { type: 'string', nullable: true }, target: { type: 'string' }, condFormula: { type: 'object' }, isPublic: { type: 'boolean' }, isModerator: { type: 'boolean' }, isAdministrator: { type: 'boolean' }, + asBadge: { type: 'boolean' }, canEditMembersByModerator: { type: 'boolean' }, policies: { type: 'object', @@ -42,11 +44,13 @@ export const paramDef = { 'name', 'description', 'color', + 'iconUrl', 'target', 'condFormula', 'isPublic', 'isModerator', 'isAdministrator', + 'asBadge', 'canEditMembersByModerator', 'policies', ], @@ -73,11 +77,13 @@ export default class extends Endpoint { name: ps.name, description: ps.description, color: ps.color, + iconUrl: ps.iconUrl, target: ps.target, condFormula: ps.condFormula, isPublic: ps.isPublic, isModerator: ps.isModerator, isAdministrator: ps.isAdministrator, + asBadge: ps.asBadge, canEditMembersByModerator: ps.canEditMembersByModerator, policies: ps.policies, }); diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index e423f0f10967..0ce80a1a63e8 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -5,8 +5,8 @@ import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; -import { ApiError } from '../../../error.js'; import { AchievementService } from '@/core/AchievementService.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['notes', 'favorites'], @@ -79,7 +79,7 @@ export default class extends Endpoint { userId: me.id, }); - if (note.userHost == null) { + if (note.userHost == null && note.userId !== me.id) { this.achievementService.create(note.userId, 'myNoteFavorited1'); } }); diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue index b074028821ba..b656307d902c 100644 --- a/packages/frontend/src/components/MkCode.core.vue +++ b/packages/frontend/src/components/MkCode.core.vue @@ -1,6 +1,6 @@ diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue index cb88444d346b..4525d3a009df 100644 --- a/packages/frontend/src/components/MkDateSeparatedList.vue +++ b/packages/frontend/src/components/MkDateSeparatedList.vue @@ -107,19 +107,19 @@ export default defineComponent({ return () => h( defaultStore.state.animation ? TransitionGroup : 'div', { - class: { - [$style['date-separated-list']]: true, - [$style['date-separated-list-nogap']]: props.noGap, - [$style['reversed']]: props.reversed, - [$style['direction-down']]: props.direction === 'down', - [$style['direction-up']]: props.direction === 'up', - }, - ...(defaultStore.state.animation ? { - name: 'list', - tag: 'div', - onBeforeLeave, - onLeaveCanceled, - } : {}), + class: { + [$style['date-separated-list']]: true, + [$style['date-separated-list-nogap']]: props.noGap, + [$style['reversed']]: props.reversed, + [$style['direction-down']]: props.direction === 'down', + [$style['direction-up']]: props.direction === 'up', + }, + ...(defaultStore.state.animation ? { + name: 'list', + tag: 'div', + onBeforeLeave, + onLeaveCanceled, + } : {}), }, { default: renderChildren }); }, @@ -139,18 +139,10 @@ export default defineComponent({ transition: none !important; } - > .list-leave-active, > .list-enter-active { transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); } - > .list-leave-from, - > .list-leave-to, - > .list-leave-active { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); - position: absolute !important; - } - > *:empty { display: none; } diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue index 8771168a423a..6b43f146650b 100644 --- a/packages/frontend/src/components/MkNoteHeader.vue +++ b/packages/frontend/src/components/MkNoteHeader.vue @@ -5,6 +5,9 @@
bot
+
+ +
@@ -77,4 +80,17 @@ defineProps<{ margin-left: auto; font-size: 0.9em; } + +.badgeRoles { + margin: 0 .5em 0 0; +} + +.badgeRole { + height: 1.3em; + vertical-align: -20%; + + & + .badgeRole { + margin-left: .125em; + } +} diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index b51d456eab50..e7a951dd27d6 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -63,10 +63,23 @@ {{ i18n.ts._achievements._types['_' + notification.achievement].title }} - {{ i18n.ts.youGotNewFollower }}
+ {{ i18n.ts.followRequestAccepted }} - {{ i18n.ts.receiveFollowRequest }}
|
- {{ i18n.ts.groupInvited }}: {{ notification.invitation.group.name }}
|
+ + diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts index 4227f5cf4a35..64c252ce552c 100644 --- a/packages/frontend/src/init.ts +++ b/packages/frontend/src/init.ts @@ -438,7 +438,7 @@ if ($i) { } window.setInterval(() => { - if (Math.floor(Math.random() * 10000) === 0) { + if (Math.floor(Math.random() * 20000) === 0) { claimAchievement('justPlainLucky'); } }, 1000 * 10); diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index ae5ef39bae36..086537a94a75 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -13,6 +13,10 @@ + + + + @@ -35,6 +39,21 @@
+ + + + + + + + + + + + + + +
@@ -358,16 +377,6 @@
- - - - - - - - - -
{{ role ? i18n.ts.save : i18n.ts.create }}
@@ -426,9 +435,11 @@ let name = $ref(role?.name ?? 'New Role'); let description = $ref(role?.description ?? ''); let rolePermission = $ref(role?.isAdministrator ? 'administrator' : role?.isModerator ? 'moderator' : 'normal'); let color = $ref(role?.color ?? null); +let iconUrl = $ref(role?.iconUrl ?? null); let target = $ref(role?.target ?? 'manual'); let condFormula = $ref(role?.condFormula ?? { id: uuid(), type: 'isRemote' }); let isPublic = $ref(role?.isPublic ?? false); +let asBadge = $ref(role?.asBadge ?? false); let canEditMembersByModerator = $ref(role?.canEditMembersByModerator ?? false); const policies = reactive>({}); @@ -466,11 +477,13 @@ async function save() { name, description, color: color === '' ? null : color, + iconUrl: iconUrl === '' ? null : iconUrl, target, condFormula, isAdministrator: rolePermission === 'administrator', isModerator: rolePermission === 'moderator', isPublic, + asBadge, canEditMembersByModerator, policies, }); @@ -480,11 +493,13 @@ async function save() { name, description, color: color === '' ? null : color, + iconUrl: iconUrl === '' ? null : iconUrl, target, condFormula, isAdministrator: rolePermission === 'administrator', isModerator: rolePermission === 'moderator', isPublic, + asBadge, canEditMembersByModerator, policies, }); diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 0e785f259cc8..c82559d55a35 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -155,7 +155,11 @@ async function run() { os.inputText({ title: q, }).then(({ canceled, result: a }) => { - ok(a); + if (canceled) { + ok(''); + } else { + ok(a); + } }); }); }, diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index 0d52850b5d38..6075dde32623 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -86,7 +86,11 @@ async function run() { os.inputText({ title: q, }).then(({ canceled, result: a }) => { - ok(a); + if (canceled) { + ok(''); + } else { + ok(a); + } }); }); }, diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue index 7a819eb9f085..a01e3f8cee4b 100644 --- a/packages/frontend/src/pages/settings/webhook.edit.vue +++ b/packages/frontend/src/pages/settings/webhook.edit.vue @@ -31,6 +31,7 @@
{{ i18n.ts.save }} + {{ i18n.ts.delete }}
@@ -44,6 +45,9 @@ import MkButton from '@/components/MkButton.vue'; import * as os from '@/os'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { useRouter } from '@/router'; + +const router = useRouter(); const props = defineProps<{ webhookId: string; @@ -86,6 +90,19 @@ async function save(): Promise { }); } +async function del(): Promise { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('deleteAreYouSure', { x: webhook.name }), + }); + if (canceled) return; + + await os.apiWithDialog('i/webhooks/delete', { + webhookId: props.webhookId, + }); + + router.push('/settings/webhook'); +} const headerActions = $computed(() => []); const headerTabs = $computed(() => []); diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 59dc1114d1c8..080772951e63 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -1,9 +1,9 @@