diff --git a/CHANGELOG.md b/CHANGELOG.md index 37f40667bf16058435082e8019241f298e6c73ac..ee98f4ccb99b66a0fb764f5ed65600432a5c67ce 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 9fea3df0067cfc3052f8b21c864404e12564514a..1a014d4dd0cd3279cba20bf8a3914381bb7f7068 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 9b73db2d4ceaed54d7ea8a00aa71375d819d2cb0..c5128965324527e05d5e3a2ba4132a5cb88b5994 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 7801f1275bf84d337eb06a0eef7f246a99ce0814..67a406d98d977797fb3d05e16f20adad660f4851 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 b87704d160d04f3794a87e5d99a4c8f53ad246ea..08cf0cb7070ae496d683f8def2098f6c22282d47 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 8508f93575cf0496744159a3e906dccf28e7b288..4da044a6c809f95b3a5aa055c0a5353300915cd4 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 12912f4153568dad903f936c906dd0fbfc6719c6..8a9d1c964354bf394dbc016412981fb53e014bbc 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 0000000000000000000000000000000000000000..eef9b076fc13aee09ce21ac149c0f7e02204f885 --- /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 ec1a082d781e0bd1696d6ec40d806752e5732465..8c348e595dab16deb0b71a99cc56f7d7ef213d19 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 a55181a9d174cdebc3a8b9da5371e0a113c423b9..cfb380a3229bd498de92e7a4f69dd0dbbfcb4668 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 05d5a086f140492bc615aee1182a363eeb022536..8f8be88fed504ed93929650cf0df0f31c18aecaf 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 253a29cf5a9ae9b9126c23614d42178befa0fbab..69c31a05eb792b5bb50dd308ed067d101217ff55 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<typeof meta, typeof paramDef> { // 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 fefc379c00fa5f90f4dff55fa9151cb36639097b..9630299a6e005c75b52f6e69586aaea63e006bb9 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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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 d36590c2640ca7301054be1d6a3b1dc94ffee84c..717866aeadba038501e17211aa69b16732ff10e5 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<typeof meta, typeof paramDef> { // 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 ef1c931bc32877e783f5b44ea3d98c2d903e8f4e..2ed615f5ff41d8b8cc62810b25bb710ecb9bd915 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<Props> = content[contentSymbol]; diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index e06549a89129cd6102830f904faa11e1e3f80922..0855f20b8d7ee5b44562170db107843be6f2aefc 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 --> <template> -<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle"/> -<span v-else-if="useOsNativeEmojis" :alt="props.emoji" @pointerenter="computeTitle">{{ props.emoji }}</span> +<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick"/> +<span v-else-if="useOsNativeEmojis" :alt="props.emoji" @pointerenter="computeTitle" @click="onClick">{{ props.emoji }}</span> <span v-else>{{ emoji }}</span> </template> <script lang="ts" setup> -import { computed } from 'vue'; +import { computed, inject } from 'vue'; import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js'; import { defaultStore } from '@/store.js'; import { getEmojiName } from '@/scripts/emojilist.js'; +import * as os from '@/os.js'; +import copyToClipboard from '@/scripts/copy-to-clipboard.js'; +import { i18n } from '@/i18n.js'; const props = defineProps<{ emoji: string; + menu?: boolean; + menuReaction?: boolean; }>(); +const react = inject<((name: string) => void) | null>('react', null); + const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native'); @@ -31,6 +38,28 @@ function computeTitle(event: PointerEvent): void { const title = getEmojiName(props.emoji as string) ?? props.emoji as string; (event.target as HTMLElement).title = title; } + +function onClick(ev: MouseEvent) { + if (props.menu) { + os.popupMenu([{ + type: 'label', + text: props.emoji, + }, { + text: i18n.ts.copy, + icon: 'ti ti-copy', + action: () => { + copyToClipboard(props.emoji); + os.success(); + }, + }, ...(props.menuReaction && react ? [{ + text: i18n.ts.doReaction, + icon: 'ti ti-plus', + action: () => { + react(props.emoji); + }, + }] : [])], ev.currentTarget ?? ev.target); + } +} </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts index 53cf7c29655de409d08405f2dd189c2f26f95fcc..5ff20fbe9bebf773a12699ea64ad937e44f142ad 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -354,6 +354,8 @@ export default function(props: MfmProps) { return [h(MkEmoji, { key: Math.random(), emoji: token.props.emoji, + menu: props.enableEmojiMenu, + menuReaction: props.enableEmojiMenuReaction, })]; } diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue index 4d71dd464fe14b6cc978dd304aa3c662b986fdc1..77649c6c4af209b40a4223c128ca8972160f2128 100644 --- a/packages/frontend/src/pages/admin/announcements.vue +++ b/packages/frontend/src/pages/admin/announcements.vue @@ -48,6 +48,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="announcement.forExistingUsers" :helpText="i18n.ts._announcement.forExistingUsersDescription"> {{ i18n.ts._announcement.forExistingUsers }} </MkSwitch> + <MkSwitch v-model="announcement.silence" :helpText="i18n.ts._announcement.silenceDescription"> + {{ i18n.ts._announcement.silence }} + </MkSwitch> <MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription"> {{ i18n.ts._announcement.needConfirmationToRead }} </MkSwitch> @@ -97,6 +100,7 @@ function add() { icon: 'info', display: 'normal', forExistingUsers: false, + silence: false, needConfirmationToRead: false, }); } diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index 95ad6909755662b0079566d1fb679ef5396fc35d..51e1efda53926703a0801f1f3e4f3765fa30561e 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only <section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement"> <div v-if="announcement.forYou" :class="$style.forYou"><i class="ph-push-pin ph-bold ph-lg"></i> {{ i18n.ts.forYou }}</div> <div :class="$style.header"> - <span v-if="$i && !announcement.isRead" style="margin-right: 0.5em;">🆕</span> + <span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span> <span style="margin-right: 0.5em;"> <i v-if="announcement.icon === 'info'" class="ph-info ph-bold ph-lg"></i> <i v-else-if="announcement.icon === 'warning'" class="ph-warning ph-bold ph-lg" style="color: var(--warn);"></i> @@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkTime :time="announcement.updatedAt ?? announcement.createdAt" mode="detail"/> </div> </div> - <div v-if="tab !== 'past' && $i && !announcement.isRead" :class="$style.footer"> + <div v-if="tab !== 'past' && $i && !announcement.silence && !announcement.isRead" :class="$style.footer"> <MkButton primary @click="read(announcement)"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts.gotIt }}</MkButton> </div> </section> diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index 4acb4a7c517daef3e90e612b9d13bac89172222d..b585333bbd98f576eac5ad17174071b48cd71563 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -100,7 +100,7 @@ SPDX-License-Identifier: AGPL-3.0-only :class="[$style.avatarDecoration, { [$style.avatarDecorationActive]: $i.avatarDecorations.some(x => x.id === avatarDecoration.id) }]" @click="openDecoration(avatarDecoration)" > - <div :class="$style.avatarDecorationName"><MkCondensedLine :minScale="2 / 3">{{ avatarDecoration.name }}</MkCondensedLine></div> + <div :class="$style.avatarDecorationName"><MkCondensedLine :minScale="0.5">{{ avatarDecoration.name }}</MkCondensedLine></div> <MkAvatar style="width: 60px; height: 60px;" :user="$i" :decoration="{ url: avatarDecoration.url }" forceShowDecoration/> <i v-if="avatarDecoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => avatarDecoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))" :class="$style.avatarDecorationLock" class="ph-lock ph-bold ph-lg"></i> </div>