diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index d2df953346..3be8f095f1 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -14,7 +14,7 @@ jobs: - run: corepack enable - name: Setup Node.js - uses: actions/setup-node@v3.8.1 + uses: actions/setup-node@v4.0.0 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index eeb2fa0855..8e3437ad86 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -44,7 +44,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.8.1 + uses: actions/setup-node@v4.0.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -126,7 +126,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.8.1 + uses: actions/setup-node@v4.0.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bcffc512bc..5096e54af8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: with: version: 8 run_install: false - - uses: actions/setup-node@v3.8.1 + - uses: actions/setup-node@v4.0.0 with: node-version-file: '.node-version' cache: 'pnpm' @@ -46,7 +46,7 @@ jobs: with: version: 7 run_install: false - - uses: actions/setup-node@v3.8.1 + - uses: actions/setup-node@v4.0.0 with: node-version-file: '.node-version' cache: 'pnpm' @@ -72,7 +72,7 @@ jobs: with: version: 7 run_install: false - - uses: actions/setup-node@v3.8.1 + - uses: actions/setup-node@v4.0.0 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 752e29ff7a..cbf0275620 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -38,7 +38,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.8.1 + uses: actions/setup-node@v4.0.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 30829cb6a4..9575e22d02 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -25,7 +25,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.8.1 + uses: actions/setup-node@v4.0.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -83,7 +83,7 @@ jobs: version: 7 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.8.1 + uses: actions/setup-node@v4.0.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index b5c6bff641..ae1ddf31d1 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -26,7 +26,7 @@ jobs: - run: corepack enable - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.8.1 + uses: actions/setup-node@v4.0.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index bcb89bb457..d9bfb12be5 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -28,7 +28,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.8.1 + uses: actions/setup-node@v4.0.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/CHANGELOG.md b/CHANGELOG.md index 10eeb770bc..66b2e0ae4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ ### General - Feat: アイコンデコレーション機能 + - サーバーで用意された画像をアイコンに重ねることができます + - 画像のテンプレートはこちらです: https://misskey-hub.net/avatar-decoration-template.png + - 最大でも黄色いエリア内にデコレーションを収めることを推奨します。 + - 画像は512x512pxを推奨します。 - Enhance: すでにフォローしたすべての人の返信をTLに追加できるように - Enhance: ローカリゼーションの更新 - Enhance: 依存関係の更新 @@ -24,6 +28,9 @@ - Feat: プラグイン・テーマを外部サイトから直接インストールできるようになりました - 外部サイトでの実装が必要です。詳細は Misskey Hub をご覧ください https://misskey-hub.net/docs/advanced/publish-on-your-website.html +- Enhance: スワイプしてタイムラインを再読込できるように + - PCの場合は右上のボタンからでも再読込できます +- Enhance: タイムラインの自動更新を無効にできるように - Enhance: コードのシンタックスハイライトエンジンをShikiに変更 - AiScriptのシンタックスハイライトに対応 - MFMでAiScriptをハイライトする場合、コードブロックの開始部分を ` ```is ` もしくは ` ```aiscript ` としてください @@ -36,6 +43,8 @@ - Fix: 「検索」MFMにおいて一部の検索キーワードが正しく認識されない問題を修正 - Fix: 一部の言語でMisskey Webがクラッシュする問題を修正 - Fix: チャンネルの作成・更新時に失敗した場合何も表示されない問題を修正 #11983 +- Fix: 個人カードのemojiがバッテリーになっている問題を修正 +- Fix: 標準テーマと同じIDを使用してインストールできてしまう問題を修正 ### Server - Enhance: RedisへのTLのキャッシュをオフにできるように @@ -48,7 +57,8 @@ - Fix: RedisへのTLキャッシュが有効の場合にHTL/LTL/STLが空になることがある問題を修正 - Fix: STLでフォローしていないチャンネルが取得される問題を修正 - Fix: `hashtags/trend`にてRedisからトレンドの情報が取得できない際にInternal Server Errorになる問題を修正 -- Fix: HTLをリロードまたは遡行したとき、フォローしているチャンネルのノートが含まれない問題を修正 #11765 +- Fix: HTLをリロードまたは遡行したとき、フォローしているチャンネルのノートが含まれない問題を修正 #11765 #12181 +- Fix: リノートをリノートできるのを修正 ## 2023.10.2 diff --git a/locales/index.d.ts b/locales/index.d.ts index eaac30217b..a43203783a 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -982,6 +982,7 @@ export interface Locale { "unassign": string; "color": string; "manageCustomEmojis": string; + "manageAvatarDecorations": string; "youCannotCreateAnymore": string; "cannotPerformTemporary": string; "cannotPerformTemporaryDescription": string; @@ -1152,6 +1153,10 @@ export interface Locale { "angle": string; "flip": string; "showAvatarDecorations": string; + "releaseToRefresh": string; + "refreshing": string; + "pullDownToRefresh": string; + "disableStreamingTimeline": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; @@ -1612,6 +1617,7 @@ export interface Locale { "inviteLimitCycle": string; "inviteExpirationTime": string; "canManageCustomEmojis": string; + "canManageAvatarDecorations": string; "driveCapacity": string; "alwaysMarkNsfw": string; "pinMax": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 3b560c4bdc..afd13b0a9e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -979,6 +979,7 @@ assign: "アサイン" unassign: "アサインを解除" color: "色" manageCustomEmojis: "カスタム絵文字の管理" +manageAvatarDecorations: "アバターデコレーションの管理" youCannotCreateAnymore: "これ以上作成することはできません。" cannotPerformTemporary: "一時的に利用できません" cannotPerformTemporaryDescription: "操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。" @@ -1149,6 +1150,10 @@ detach: "外す" angle: "角度" flip: "反転" showAvatarDecorations: "アイコンのデコレーションを表示" +releaseToRefresh: "離してリロード" +refreshing: "リロード中" +pullDownToRefresh: "引っ張ってリロード" +disableStreamingTimeline: "タイムラインのリアルタイム更新を無効にする" _announcement: forExistingUsers: "既存ユーザーのみ" @@ -1529,6 +1534,7 @@ _role: inviteLimitCycle: "招待コードの発行間隔" inviteExpirationTime: "招待コードの有効期限" canManageCustomEmojis: "カスタム絵文字の管理" + canManageAvatarDecorations: "アバターデコレーションの管理" driveCapacity: "ドライブ容量" alwaysMarkNsfw: "ファイルにNSFWを常に付与" pinMax: "ノートのピン留めの最大数" diff --git a/package.json b/package.json index 4c8769ac9d..17cd49adf8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2023.11.0-beta.5", + "version": "2023.11.0-beta.6", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 9a817ffd76..632daf991a 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -24,6 +24,7 @@ import { bindThis } from '@/decorators.js'; import { MetaService } from '@/core/MetaService.js'; import { SearchService } from '@/core/SearchService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { isPureRenote } from '@/misc/is-pure-renote.js'; @Injectable() export class NoteDeleteService { @@ -77,8 +78,8 @@ export class NoteDeleteService { if (this.userEntityService.isLocalUser(user) && !note.localOnly) { let renote: MiNote | null = null; - // if deletd note is renote - if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { + // if deleted note is renote + if (isPureRenote(note)) { renote = await this.notesRepository.findOneBy({ id: note.renoteId, }); diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index ef05920d50..d6a414694a 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -32,6 +32,7 @@ export type RolePolicies = { inviteLimitCycle: number; inviteExpirationTime: number; canManageCustomEmojis: boolean; + canManageAvatarDecorations: boolean; canSearchNotes: boolean; canUseTranslator: boolean; canHideAds: boolean; @@ -57,6 +58,7 @@ export const DEFAULT_POLICIES: RolePolicies = { inviteLimitCycle: 60 * 24 * 7, inviteExpirationTime: 0, canManageCustomEmojis: false, + canManageAvatarDecorations: false, canSearchNotes: false, canUseTranslator: true, canHideAds: false, @@ -306,6 +308,7 @@ export class RoleService implements OnApplicationShutdown { inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)), inviteExpirationTime: calc('inviteExpirationTime', vs => Math.max(...vs)), canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)), + canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)), canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)), canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)), canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 7164feec9d..d6a7de0601 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -319,9 +319,17 @@ export class ApPersonService implements OnModuleInit { emojis, })) as MiRemoteUser; + let _description: string | null = null; + + if (person._misskey_summary) { + _description = truncate(person._misskey_summary, summaryLength); + } else if (person.summary) { + _description = this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag); + } + await transactionalEntityManager.save(new MiUserProfile({ userId: user.id, - description: person._misskey_summary ? truncate(person._misskey_summary, summaryLength) : person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, + description: _description, url, fields, birthday: bday?.[0] ?? null, @@ -487,10 +495,18 @@ export class ApPersonService implements OnModuleInit { }); } + let _description: string | null = null; + + if (person._misskey_summary) { + _description = truncate(person._misskey_summary, summaryLength); + } else if (person.summary) { + _description = this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag); + } + await this.userProfilesRepository.update({ userId: exist.id }, { url, fields, - description: person.summary ? this.apMfmService.htmlToMfm(truncate(person.summary, summaryLength), person.tag) : null, + description: _description, birthday: bday?.[0] ?? null, location: person['vcard:Address'] ?? null, }); diff --git a/packages/backend/src/misc/is-pure-renote.ts b/packages/backend/src/misc/is-pure-renote.ts new file mode 100644 index 0000000000..994d981522 --- /dev/null +++ b/packages/backend/src/misc/is-pure-renote.ts @@ -0,0 +1,10 @@ +import type { MiNote } from '@/models/Note.js'; + +export function isPureRenote(note: MiNote): note is MiNote & { renoteId: NonNullable } { + if (!note.renoteId) return false; + + if (note.text) return false; // it's quoted with text + if (note.fileIds.length !== 0) return false; // it's quoted with files + if (note.hasPoll) return false; // it's quoted with poll + return true; +} diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index a7f6f82daf..2e64d41c91 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -26,6 +26,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { IActivity } from '@/core/activitypub/type.js'; +import { isPureRenote } from '@/misc/is-pure-renote.js'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; import type { FindOptionsWhere } from 'typeorm'; @@ -88,7 +89,7 @@ export class ActivityPubServerService { */ @bindThis private async packActivity(note: MiNote): Promise { - if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { + if (isPureRenote(note)) { const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId }); return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note); } diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts index c1869b141a..ec143fcb53 100644 --- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts @@ -11,7 +11,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireModerator: true, + requireRolePolicy: 'canManageAvatarDecorations', } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts index 5aba24b426..6f1f386871 100644 --- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts @@ -13,8 +13,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireModerator: true, - + requireRolePolicy: 'canManageAvatarDecorations', errors: { }, } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts index 9a32a59081..d9c669377d 100644 --- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts @@ -16,7 +16,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireModerator: true, + requireRolePolicy: 'canManageAvatarDecorations', res: { type: 'array', diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts index 564014a3df..5ea9a40762 100644 --- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts @@ -13,7 +13,7 @@ export const meta = { tags: ['admin'], requireCredential: true, - requireModerator: true, + requireRolePolicy: 'canManageAvatarDecorations', errors: { }, diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 3ae4ac044a..649068fb20 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -17,6 +17,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { isPureRenote } from '@/misc/is-pure-renote.js'; export const meta = { tags: ['notes'], @@ -221,7 +222,7 @@ export default class extends Endpoint { // eslint- if (renote == null) { throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) { + } else if (isPureRenote(renote)) { throw new ApiError(meta.errors.cannotReRenote); } @@ -254,7 +255,7 @@ export default class extends Endpoint { // eslint- if (reply == null) { throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) { + } else if (isPureRenote(reply)) { throw new ApiError(meta.errors.cannotReplyToPureRenote); } diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index e048bc4dd2..5016bd3acb 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -183,7 +183,11 @@ export default class extends Endpoint { // eslint- const followingChannelIds = followingChannels.map(x => x.followeeId); query.andWhere(new Brackets(qb => { qb - .where('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }) + .where(new Brackets(qb2 => { + qb2 + .where('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds }) + .andWhere('note.channelId IS NULL'); + })) .orWhere('note.channelId IN (:...followingChannelIds)', { followingChannelIds }); })); } else if (followees.length > 0) { diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index ad32d08fee..3aa0d69c0b 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -67,6 +67,8 @@ export default abstract class Channel { } public abstract init(params: any): void; + public dispose?(): void; + public onMessage?(type: string, body: any): void; } diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index f2af951d63..800a3b079f 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -8,7 +8,7 @@ import { common } from './common.js'; import { version, ui, lang, updateLocale } from '@/config.js'; import { i18n, updateI18n } from '@/i18n.js'; import { confirm, alert, post, popup, toast } from '@/os.js'; -import { useStream } from '@/stream.js'; +import { useStream, isReloading } from '@/stream.js'; import * as sound from '@/scripts/sound.js'; import { $i, refreshAccount, login, updateAccount, signout } from '@/account.js'; import { defaultStore, ColdDeviceStorage } from '@/store.js'; @@ -39,6 +39,7 @@ export async function mainBoot() { let reloadDialogShowing = false; stream.on('_disconnected_', async () => { + if (isReloading) return; if (defaultStore.state.serverDisconnectedBehavior === 'reload') { location.reload(); } else if (defaultStore.state.serverDisconnectedBehavior === 'dialog') { diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index 3b273ac545..5edae1bc3c 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -166,6 +166,8 @@ defineExpose({ diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index cdd72febd1..a2ada35f91 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -4,13 +4,16 @@ SPDX-License-Identifier: AGPL-3.0-only -->