diff --git a/locales/index.d.ts b/locales/index.d.ts index 39f0b80fdf..5ee0d2f0e1 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -8105,6 +8105,14 @@ export interface Locale extends ILocale { * 文字の上にルビを表示します。 */ "rubyDescription": string; + /** + * ボーダー + */ + "border": string; + /** + * 内容を枠線で囲みます。 + */ + "borderDescription": string; }; "_instanceTicker": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9a25d81d0f..1ad1dd9c48 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2062,7 +2062,7 @@ _mfm: inlineCode: "コード(インライン)" inlineCodeDescription: "プログラムなどのコードをインラインでシンタックスハイライトします。" blockCode: "コード(ブロック)" - blockCodeDescription: "複数行のプログラムなどのコードをブロックでシンタックスハイライトします。" + blockCodeDescription: "複数行のプログラムなどのコードをブロックでシンタックスハイライトします。いくつかの言語を指定するとその言語に合わせたシンタックスハイライトになります。" inlineMath: "数式(インライン)" inlineMathDescription: "数式(KaTeX)をインラインで表示します。" blockMath: "数式(ブロック)" @@ -2119,6 +2119,8 @@ _mfm: plainDescription: "内側の構文を全て無効にします。" ruby: "ルビ" rubyDescription: "文字の上にルビを表示します。" + border: "border" + borderDescription: "内容を枠線で囲むことができます。" _instanceTicker: none: "表示しない" diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index f3cda78038..71fa742686 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -39,6 +39,8 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j import type { AccountMoveService } from '@/core/AccountMoveService.js'; import { checkHttps } from '@/misc/check-https.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { isNotNull } from '@/misc/is-not-null.js'; import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js'; import { extractApHashtags } from './tag.js'; import type { OnModuleInit } from '@nestjs/common'; @@ -103,6 +105,7 @@ export class ApPersonService implements OnModuleInit { private followingsRepository: FollowingsRepository, private avatarDecorationService: AvatarDecorationService, + private httpRequestService: HttpRequestService, ) { } @@ -371,6 +374,34 @@ export class ApPersonService implements OnModuleInit { }); //#endregion + let isReactionPublic = false; + let followingVisibility: 'private' | 'followers' | 'public' | undefined = typeof person.following === 'string' ? 'private' : 'public'; + let followersVisibility: 'private' | 'followers' | 'public' | undefined = typeof person.followers === 'string' ? 'private' : 'public'; + const instance = await this.instancesRepository.findOneBy({ host: host }); + + if (instance?.softwareName === 'misskey' || instance?.softwareName === 'cherrypick') { + const userHostUrl = `https://${host}`; + const showUserApiUrl = `${userHostUrl}/api/users/show`; + const res = await this.httpRequestService.send(showUserApiUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ 'username': person.preferredUsername }), + }); + + const userData: any = await res.json(); + if (userData.publicReactions === undefined ) { + isReactionPublic = userData.publicReactions; + } + + if (userData._ffVisibility === undefined) { + followingVisibility = userData._ffVisibility; + followersVisibility = userData._ffVisibility; + } else { + followingVisibility = userData.followingVisibility; + followersVisibility = userData.followersVisibility; + } + } + try { // Start transaction await this.db.transaction(async transactionalEntityManager => { @@ -439,6 +470,9 @@ export class ApPersonService implements OnModuleInit { birthday: bday?.[0] ?? null, location: person['vcard:Address'] ?? null, userHost: host, + followersVisibility: followersVisibility, + followingVisibility: followingVisibility, + publicReactions: isReactionPublic, })); if (person.publicKey) { @@ -549,10 +583,37 @@ export class ApPersonService implements OnModuleInit { const url = getOneApHrefNullable(person.url); + const host = this.punyHost(uri); + if (url && !checkHttps(url)) { throw new Error('unexpected schema of person url: ' + url); } + let isReactionPublic = false; + let followingVisibility: 'private' | 'followers' | 'public' | undefined = typeof person.following === 'string' ? 'private' : 'public'; + let followersVisibility: 'private' | 'followers' | 'public' | undefined = typeof person.followers === 'string' ? 'private' : 'public'; + const instance = await this.instancesRepository.findOneBy({ host: host }); + + if (instance?.softwareName === 'misskey' || instance?.softwareName === 'cherrypick') { + const userHostUrl = `https://${host}`; + const showUserApiUrl = `${userHostUrl}/api/users/show`; + const res = await this.httpRequestService.send(showUserApiUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ 'username': person.preferredUsername }), + }); + const userData: any = await res.json(); + + if (userData.publicReactions === undefined ) { + isReactionPublic = userData.publicReactions; + } + + if (userData._ffVisibility === undefined) { + followingVisibility = userData._ffVisibility; + followersVisibility = userData._ffVisibility; + } + } + let followersCount: number | undefined; if (typeof person.followers === 'string') { @@ -686,6 +747,9 @@ export class ApPersonService implements OnModuleInit { description: _description, birthday: bday?.[0] ?? null, location: person['vcard:Address'] ?? null, + followersVisibility: followersVisibility, + followingVisibility: followingVisibility, + publicReactions: isReactionPublic, }); this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: exist.id }); diff --git a/packages/frontend/src/pages/mfm-cheat-sheet.vue b/packages/frontend/src/pages/mfm-cheat-sheet.vue index a93d2019d7..37bb0a6a4b 100644 --- a/packages/frontend/src/pages/mfm-cheat-sheet.vue +++ b/packages/frontend/src/pages/mfm-cheat-sheet.vue @@ -380,6 +380,16 @@ SPDX-License-Identifier: AGPL-3.0-only +
+
{{ i18n.ts._mfm.border }}
+
+

{{ i18n.ts._mfm.borderDescription }}

+
+ + MFM {{ i18n.ts.sample }} +
+
+
@@ -432,6 +442,7 @@ const preview_fg = ref('$[fg.color=ffbcdc Cherry]$[fg.color=b1d3ff Pick]'); const preview_bg = ref('$[bg.color=ffbcdc Cherry]$[bg.color=b1d3ff Pick]'); const preview_plain = ref('**bold** @mention #hashtag `code` $[x2 🍮]'); const preview_ruby = ref('$[ruby CherryPick Misskey]'); +const preview_border = ref('$[border Example]\n$[border.color=ffbcdc Cherry]$[border.color=b1d3ff Pick]\n\n$[border $[position.x=1.5 MFM]\n$[position.x=1.5 なんもわからん]]\n$[border.noclip $[position.x=1.5 MFM]\n$[position.x=1.5 完全に理解した]]\n\n$[border.radius=4 角丸の半径] $[border.width=4 枠線の太さ]\n$[border.style=solid これが普通]\n$[border.style=hidden 枠線を隠せる]\n$[border.style=dotted ドット]\n$[border.style=dashed 点線]\n$[border.style=double 二重枠]\n$[border.style=groove 額縁1]\n$[border.style=ridge 額縁2]\n$[border.style=inset インセット]\n$[border.style=outset アウトセット] '); const headerActions = computed(() => []);