diff --git a/CHANGELOG.md b/CHANGELOG.md index 37f40667bf..ee98f4ccb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ --> -## 2023.11.0 (unreleased) +## 2023.11.0 ### Note - iOS 16.4未満を使用している場合はiOS 16.4以上にアップデートをお願いします @@ -29,6 +29,7 @@ - ユーザーが誤ったメールアドレスを入力した場合に招待コードが失効してしまう問題が解消されます。 - Enhance: すでにフォローしたすべての人の返信をTLに追加できるように - Enhance: 未読の通知数を表示できるように +- Enhance: 通知されず、確認の必要もないお知らせ(silence)を作成可能になりました - Enhance: ローカリゼーションの更新 - Enhance: 依存関係の更新 - Change: CWを使用する場合、注釈を空にすることは許可されなくなりました @@ -50,7 +51,7 @@ - Enhance: プラグインで`Plugin:register_note_view_interruptor`を用いてnoteの代わりにnullを返却することでノートを非表示にできるようになりました - Enhance: AiScript関数`Mk:nyaize()`が追加されました - Enhance: 情報→ツール はナビゲーションバーにツールとして独立した項目になりました -- Enhance: ノート内のカスタム絵文字をクリックすることで、コピーおよびリアクションができるように +- Enhance: ノート内の絵文字をクリックすることで、コピーおよびリアクションができるように - Enhance: その他細かなブラッシュアップ - Fix: 投稿フォームでのユーザー変更がプレビューに反映されない問題を修正 - Fix: ユーザーページの ノート > ファイル付き タブにリプライが表示されてしまう @@ -63,6 +64,7 @@ - Fix: 11以上されているリアクションにおいてツールチップで示されるリアクション数が本来よりも1多い問題を修正 #12174 - Fix: サイレンス状態で公開範囲のパブリックを選択できてしまう問題を修正 #12224 - Fix: In deck layout, replies option is not saved after refresh +- Fix: アーカイブしたお知らせがコントロールパネルに表示される問題を修正 - Note: アップデート後、サウンドに関する設定が初期化されます ### Server @@ -72,6 +74,7 @@ - Enhance: プロフィールの自己紹介欄のMFMが連合するようになりました - 相手がMisskey v2023.11.0以降である必要があります - Enhance: チャンネル取得時のパフォーマンスを向上 +- Enhance: AP: ApplicationタイプのアカウントをisBotとして扱うように - Fix: リストTLに自分のフォロワー限定投稿が含まれない問題を修正 - Fix: ローカルタイムラインに投稿者自身の投稿への返信が含まれない問題を修正 - Fix: 自分のフォローしているユーザーの自分のフォローしていないユーザーの visibility: followers な投稿への返信がストリーミングで流れてくる問題を修正 diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 9fea3df006..1a014d4dd0 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1088,7 +1088,26 @@ _initialAccountSetting: profileSetting: "Paramètres du profil" privacySetting: "Paramètres de confidentialité" initialAccountSettingCompleted: "Configuration du profil terminée avec succès !" + startTutorial: "Démarrer le tutoriel" skipAreYouSure: "Désirez-vous ignorer la configuration du profil ?" +_initialTutorial: + title: "Tutoriel" + wellDone: "Bien joué !" + skipAreYouSure: "Quitter le tutoriel ?" + _landing: + title: "Bienvenue dans le tutoriel" + description: "Ici, vous pouvez apprendre l'utilisation de base de Misskey et ses fonctionnalités." + _note: + title: "Qu'est-ce que les notes ?" + description: "Les messages sur Misskey sont appelés des « notes » . Les notes sont classées par ordre chronologique sur le fil et sont mises à jour en temps réel." + reply: "Vous pouvez répondre aux messages. Vous pouvez également répondre aux réponses et poursuivre la conversation comme un fil de discussion." + renote: "Vous pouvez partager cette note sur votre propre fil. Vous pouvez aussi ajouter du texte en citant." + reaction: "Vous pouvez ajouter des réactions. Les détails sont expliqués à la page suivante." + menu: "Vous pouvez afficher les détails de la note, copier le lien et effectuer d'autres actions." + _reaction: + title: "Qu'est-ce que les réactions ?" + description: "Vous pouvez ajouter des « réactions » aux notes. Les réactions vous permettent d'exprimer à l'aise des nuances qui ne peuvent pas être exprimées par des mentions j'aime." + letsTryReacting: "Des réactions peuvent être ajoutées en cliquant sur le bouton « + » de la note. Essayez d'ajouter une réaction à cet exemple de note !" _serverSettings: iconUrl: "URL de l’icône" fanoutTimelineDescription: "Si activée, la performance de la récupération de la chronologie augmentera considérablement et la charge sur la base de données sera réduite. En revanche, l'utilisation de la mémoire de Redis augmentera. Considérez désactiver cette option si le serveur est bas en mémoire ou instable." diff --git a/locales/index.d.ts b/locales/index.d.ts index 9b73db2d4c..c512896532 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1202,6 +1202,8 @@ export interface Locale { "readConfirmText": string; "shouldNotBeUsedToPresentPermanentInfo": string; "dialogAnnouncementUxWarn": string; + "silence": string; + "silenceDescription": string; }; "_initialAccountSetting": { "accountCreated": string; diff --git a/locales/index.js b/locales/index.js index 7801f1275b..67a406d98d 100644 --- a/locales/index.js +++ b/locales/index.js @@ -53,6 +53,19 @@ const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g') const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, import.meta.url), 'utf-8'))) || {}, a), {}); +// 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す +const removeEmpty = (obj) => { + for (const [k, v] of Object.entries(obj)) { + if (v === '') { + delete obj[k]; + } else if (typeof v === 'object') { + removeEmpty(v); + } + } + return obj; +}; +removeEmpty(locales); + export default Object.entries(locales) .reduce((a, [k ,v]) => (a[k] = (() => { const [lang] = k.split('-'); @@ -63,7 +76,7 @@ export default Object.entries(locales) default: return merge( locales['ja-JP'], locales['en-US'], - locales[`${lang}-${primaries[lang]}`] || {}, + locales[`${lang}-${primaries[lang]}`] ?? {}, v ); } diff --git a/locales/it-IT.yml b/locales/it-IT.yml index b87704d160..08cf0cb707 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1157,6 +1157,7 @@ disableStreamingTimeline: "Disabilitare gli aggiornamenti della TL in tempo real useGroupedNotifications: "Mostra le notifiche raggruppate" signupPendingError: "Si è verificato un problema durante la verifica del tuo indirizzo email. Potrebbe essere scaduto il collegamento temporaneo." cwNotationRequired: "Devi indicare perché il contenuto è indicato come esplicito." +doReaction: "Reagisci" _announcement: forExistingUsers: "Solo ai profili attuali" forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8508f93575..4da044a6c8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1200,6 +1200,8 @@ _announcement: readConfirmText: "「{title}」の内容を読み、既読にします。" shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。" dialogAnnouncementUxWarn: "ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。" + silence: "非通知" + silenceDescription: "オンにすると、このお知らせは通知されず、既読にする必要もなくなります。" _initialAccountSetting: accountCreated: "アカウントの作成が完了しました!" diff --git a/package.json b/package.json index 12912f4153..8a9d1c9643 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sharkey", - "version": "2023.11.0.beta3", + "version": "2023.11.0.beta4", "codename": "shonk", "repository": { "type": "git", diff --git a/packages/backend/migration/1699141698112-announcement-silence.js b/packages/backend/migration/1699141698112-announcement-silence.js new file mode 100644 index 0000000000..eef9b076fc --- /dev/null +++ b/packages/backend/migration/1699141698112-announcement-silence.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AnnouncementSilence1699141698112 { + name = 'AnnouncementSilence1699141698112' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "announcement" ADD "silence" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`CREATE INDEX "IDX_7b8d9225168e962f94ea517e00" ON "announcement" ("silence") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_7b8d9225168e962f94ea517e00"`); + await queryRunner.query(`ALTER TABLE "announcement" DROP COLUMN "silence"`); + } +} diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index ec1a082d78..8c348e595d 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -47,6 +47,7 @@ export class AnnouncementService { const q = this.announcementsRepository.createQueryBuilder('announcement') .where('announcement.isActive = true') + .andWhere('announcement.silence = false') .andWhere(new Brackets(qb => { qb.orWhere('announcement.userId = :userId', { userId: user.id }); qb.orWhere('announcement.userId IS NULL'); @@ -73,6 +74,7 @@ export class AnnouncementService { icon: values.icon, display: values.display, forExistingUsers: values.forExistingUsers, + silence: values.silence, needConfirmationToRead: values.needConfirmationToRead, userId: values.userId, }).then(x => this.announcementsRepository.findOneByOrFail(x.identifiers[0])); @@ -124,6 +126,7 @@ export class AnnouncementService { display: values.display, icon: values.icon, forExistingUsers: values.forExistingUsers, + silence: values.silence, needConfirmationToRead: values.needConfirmationToRead, isActive: values.isActive, }); @@ -210,6 +213,7 @@ export class AnnouncementService { icon: announcement.icon, display: announcement.display, needConfirmationToRead: announcement.needConfirmationToRead, + silence: announcement.silence, forYou: announcement.userId === me?.id, isRead: reads.some(read => read.announcementId === announcement.id), })); diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index a55181a9d1..cfb380a322 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -272,7 +272,7 @@ export class ApPersonService implements OnModuleInit { const tags = extractApHashtags(person.tag).map(normalizeForSearch).splice(0, 32); - const isBot = getApType(object) === 'Service'; + const isBot = getApType(object) === 'Service' || getApType(object) === 'Application'; const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); @@ -473,7 +473,7 @@ export class ApPersonService implements OnModuleInit { name: truncate(person.name, nameLength), tags, approved: true, - isBot: getApType(object) === 'Service', + isBot: getApType(object) === 'Service' || getApType(object) === 'Application', isCat: (person as any).isCat === true, speakAsCat: (person as any).speakAsCat != null ? (person as any).speakAsCat === true : (person as any).isCat === true, isLocked: person.manuallyApprovesFollowers, diff --git a/packages/backend/src/models/Announcement.ts b/packages/backend/src/models/Announcement.ts index 05d5a086f1..8f8be88fed 100644 --- a/packages/backend/src/models/Announcement.ts +++ b/packages/backend/src/models/Announcement.ts @@ -66,6 +66,12 @@ export class MiAnnouncement { }) public forExistingUsers: boolean; + @Index() + @Column('boolean', { + default: false, + }) + public silence: boolean; + @Index() @Column({ ...id(), diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 253a29cf5a..69c31a05eb 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -58,6 +58,7 @@ export const paramDef = { icon: { type: 'string', enum: ['info', 'warning', 'error', 'success'], default: 'info' }, display: { type: 'string', enum: ['normal', 'banner', 'dialog'], default: 'normal' }, forExistingUsers: { type: 'boolean', default: false }, + silence: { type: 'boolean', default: false }, needConfirmationToRead: { type: 'boolean', default: false }, userId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, }, @@ -78,6 +79,7 @@ export default class extends Endpoint { // eslint- icon: ps.icon, display: ps.display, forExistingUsers: ps.forExistingUsers, + silence: ps.silence, needConfirmationToRead: ps.needConfirmationToRead, userId: ps.userId, }, me); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index fefc379c00..9630299a6e 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -86,6 +86,7 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); + query.andWhere('announcement.isActive = true'); if (ps.userId) { query.andWhere('announcement.userId = :userId', { userId: ps.userId }); } else { @@ -113,6 +114,7 @@ export default class extends Endpoint { // eslint- display: announcement.display, isActive: announcement.isActive, forExistingUsers: announcement.forExistingUsers, + silence: announcement.silence, needConfirmationToRead: announcement.needConfirmationToRead, userId: announcement.userId, reads: reads.get(announcement)!, diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index d36590c264..717866aead 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -35,6 +35,7 @@ export const paramDef = { icon: { type: 'string', enum: ['info', 'warning', 'error', 'success'] }, display: { type: 'string', enum: ['normal', 'banner', 'dialog'] }, forExistingUsers: { type: 'boolean' }, + silence: { type: 'boolean' }, needConfirmationToRead: { type: 'boolean' }, isActive: { type: 'boolean' }, }, @@ -63,6 +64,7 @@ export default class extends Endpoint { // eslint- display: ps.display, icon: ps.icon, forExistingUsers: ps.forExistingUsers, + silence: ps.silence, needConfirmationToRead: ps.needConfirmationToRead, isActive: ps.isActive, }, me); diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue index ef1c931bc3..2ed615f5ff 100644 --- a/packages/frontend/src/components/global/MkCondensedLine.vue +++ b/packages/frontend/src/components/global/MkCondensedLine.vue @@ -18,10 +18,10 @@ interface Props { const contentSymbol = Symbol(); const observer = new ResizeObserver((entries) => { - const results: { - container: HTMLSpanElement; - transform: string; - }[] = []; + const results: { + container: HTMLSpanElement; + transform: string; + }[] = []; for (const entry of entries) { const content = (entry.target[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement; const props: Required = content[contentSymbol]; diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index e06549a891..0855f20b8d 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -4,21 +4,28 @@ SPDX-License-Identifier: AGPL-3.0-only -->