@@ -103,6 +103,7 @@ redis:
 | 
			
		||||
#  port: 7700
 | 
			
		||||
#  apiKey: ''
 | 
			
		||||
#  ssl: true
 | 
			
		||||
#  index: ''
 | 
			
		||||
 | 
			
		||||
#   ┌───────────────┐
 | 
			
		||||
#───┘ ID generation └───────────────────────────────────────────
 | 
			
		||||
 
 | 
			
		||||
@@ -103,6 +103,7 @@ redis:
 | 
			
		||||
#  port: 7700
 | 
			
		||||
#  apiKey: ''
 | 
			
		||||
#  ssl: true
 | 
			
		||||
#  index: ''
 | 
			
		||||
 | 
			
		||||
#   ┌───────────────┐
 | 
			
		||||
#───┘ ID generation └───────────────────────────────────────────
 | 
			
		||||
 
 | 
			
		||||
@@ -103,6 +103,7 @@ redis:
 | 
			
		||||
#  port: 7700
 | 
			
		||||
#  apiKey: ''
 | 
			
		||||
#  ssl: true
 | 
			
		||||
#  index: ''
 | 
			
		||||
 | 
			
		||||
#   ┌───────────────┐
 | 
			
		||||
#───┘ ID generation └───────────────────────────────────────────
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -14,14 +14,27 @@
 | 
			
		||||
 | 
			
		||||
## 13.12.2
 | 
			
		||||
 | 
			
		||||
## NOTE
 | 
			
		||||
Meilisearchの設定に`index`が必要になりました。値はMisskeyサーバーのホスト名にすることをお勧めします(アルファベット、ハイフン、アンダーバーのみ使用可能)。例: `misskey-io`
 | 
			
		||||
過去に作成された`notes`インデックスは、`<index名>---notes`にリネームが必要です。例: `misskey-io---notes`
 | 
			
		||||
 | 
			
		||||
### General
 | 
			
		||||
- 投稿したコンテンツのAIによる学習を軽減するオプションを追加
 | 
			
		||||
 | 
			
		||||
### Client
 | 
			
		||||
- ユーザーを指定してのノート検索が可能に
 | 
			
		||||
- アカウント初期設定ウィザードにプライバシー設定を追加
 | 
			
		||||
- リテンション率チャートに折れ線グラフを追加
 | 
			
		||||
- Fix: ブラーエフェクトを有効にしている状態で高負荷になる問題を修正
 | 
			
		||||
- Fix: Pageにおいて画像ブロックに画像を設定できない問題を修正
 | 
			
		||||
- Fix: カラーバーがリプライには表示されないのを修正
 | 
			
		||||
- Fix: チャンネル内の検索ボックスが挙動不審な問題を修正
 | 
			
		||||
- Fix: リテンションチャートのレンダリングを修正
 | 
			
		||||
- Fix: リアクションエフェクトのレンダリングの問題を修正
 | 
			
		||||
 | 
			
		||||
### Server
 | 
			
		||||
- センシティブワードの登録にAnd、正規表現が使用できるようになりました。
 | 
			
		||||
- Fix: ひとつのMeilisearchサーバーを複数のMisskeyサーバーで使えない問題を修正
 | 
			
		||||
 | 
			
		||||
## 13.12.1
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -124,6 +124,7 @@ redis:
 | 
			
		||||
#  port: 7700
 | 
			
		||||
#  apiKey: ''
 | 
			
		||||
#  ssl: true
 | 
			
		||||
#  index: ''
 | 
			
		||||
 | 
			
		||||
#   ┌───────────────┐
 | 
			
		||||
#───┘ ID generation └───────────────────────────────────────────
 | 
			
		||||
 
 | 
			
		||||
@@ -171,6 +171,10 @@ describe('After user signed in', () => {
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-user-setup-continue]').click();
 | 
			
		||||
 | 
			
		||||
		// プライバシー設定
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-user-setup-continue]').click();
 | 
			
		||||
 | 
			
		||||
		// フォローはスキップ
 | 
			
		||||
 | 
			
		||||
		cy.get('[data-cy-user-setup-continue]').click();
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ noNotes: "لم يُعثر على أية ملاحظات"
 | 
			
		||||
noNotifications: "ليس هناك أية اشعارات"
 | 
			
		||||
instance: "مثيل الخادم"
 | 
			
		||||
settings: "الاعدادات"
 | 
			
		||||
notificationSettings: "إعدادات الإشعارات"
 | 
			
		||||
basicSettings: "الاعدادات الأساسية"
 | 
			
		||||
otherSettings: "إعدادات أخرى"
 | 
			
		||||
openInWindow: "افتح في نافذة جديدة"
 | 
			
		||||
@@ -127,6 +128,7 @@ unblockConfirm: "أمتأكد من إلغاء حجب هذا الحساب؟"
 | 
			
		||||
suspendConfirm: "أمتأكد من تعليق الحساب؟"
 | 
			
		||||
unsuspendConfirm: "أمتأكد من إلغاء تعليق؟"
 | 
			
		||||
selectList: "اختر قائمة"
 | 
			
		||||
selectChannel: "اختر قناة"
 | 
			
		||||
selectAntenna: "اختر هوائيًا"
 | 
			
		||||
selectWidget: "اختر ودجة"
 | 
			
		||||
editWidgets: "عدّل الودجات"
 | 
			
		||||
@@ -250,6 +252,9 @@ noMoreHistory: "لا يوجد المزيد من التاريخ"
 | 
			
		||||
startMessaging: "ابدأ محادثة"
 | 
			
		||||
nUsersRead: "قرأه {n}"
 | 
			
		||||
agreeTo: "اوافق على {0}"
 | 
			
		||||
agree: "أقبل"
 | 
			
		||||
basicNotesBeforeCreateAccount: "ملاحظات مهمة"
 | 
			
		||||
termsOfService: "شروط الخدمة"
 | 
			
		||||
start: "البداية"
 | 
			
		||||
home: "الرئيسي"
 | 
			
		||||
remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد."
 | 
			
		||||
@@ -379,6 +384,8 @@ about: "عن"
 | 
			
		||||
aboutMisskey: "عن Misskey"
 | 
			
		||||
administrator: "المدير"
 | 
			
		||||
token: "الرمز المميز"
 | 
			
		||||
2fa: "الاستيثاق بعاملَيْن"
 | 
			
		||||
totp: "تطبيق استيثاق"
 | 
			
		||||
moderator: "مشرِف"
 | 
			
		||||
moderation: "الإشراف"
 | 
			
		||||
nUsersMentioned: "{n} مستخدمين أُشير إليهم"
 | 
			
		||||
@@ -506,6 +513,7 @@ userSuspended: "عُلق هذا المستخدم."
 | 
			
		||||
userSilenced: "كُتم هذا المستخدم."
 | 
			
		||||
yourAccountSuspendedTitle: "هذا الحساب معلق"
 | 
			
		||||
yourAccountSuspendedDescription: "عُلق الحساب بسبب انتهاك شروط خدمة المثيل و ما شابه. إذا أردت معرفة التفصيل تواصل مع مدير المثيل. رجاءً لا تنشئ حساب جديد."
 | 
			
		||||
accountDeleted: "حُذف الحساب"
 | 
			
		||||
menu: "القائمة"
 | 
			
		||||
divider: "فاصل"
 | 
			
		||||
addItem: "إضافة عنصر"
 | 
			
		||||
 
 | 
			
		||||
@@ -990,6 +990,7 @@ rolesAssignedToMe: "Mir zugewiesene Rollen"
 | 
			
		||||
resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
 | 
			
		||||
sensitiveWords: "Sensible Wörter"
 | 
			
		||||
sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden."
 | 
			
		||||
sensitiveWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden."
 | 
			
		||||
notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
 | 
			
		||||
license: "Lizenz"
 | 
			
		||||
unfavoriteConfirm: "Wirklich aus Favoriten entfernen?"
 | 
			
		||||
@@ -1038,13 +1039,16 @@ thisChannelArchived: "Dieser Kanal wurde archiviert."
 | 
			
		||||
displayOfNote: "Anzeige von Notizen"
 | 
			
		||||
initialAccountSetting: "Kontoeinrichtung"
 | 
			
		||||
youFollowing: "Gefolgt"
 | 
			
		||||
preventAiLarning: "Verwendung in machinellem Lernen (AI/KI) ablehnen"
 | 
			
		||||
preventAiLarningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (AI/KI) zu verwenden. Dies wird durch das Hinzufügen eines \"noai\"-HTML-Tags an den jeweiligen Inhalt erreicht. Da dieser Tag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich."
 | 
			
		||||
preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive AI/KI) ablehnen"
 | 
			
		||||
preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich."
 | 
			
		||||
options: "Optionen"
 | 
			
		||||
specifyUser: "Spezifischer Benutzer"
 | 
			
		||||
_initialAccountSetting:
 | 
			
		||||
  accountCreated: "Dein Konto wurde erfolgreich erstellt!"
 | 
			
		||||
  letsStartAccountSetup: "Lass uns nun dein Konto einrichten."
 | 
			
		||||
  letsFillYourProfile: "Lass uns zuerst dein Profil einrichten."
 | 
			
		||||
  profileSetting: "Profileinstellungen"
 | 
			
		||||
  privacySetting: "Privatsphäreneinstellungen"
 | 
			
		||||
  theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern."
 | 
			
		||||
  youCanEditMoreSettingsInSettingsPageLater: "In den Einstellungen findest du noch viele weitere Optionen. Schau dort später mal vorbei."
 | 
			
		||||
  followUsers: "Folge zuerst ein paar Nutzern, um deine Chronik zu füllen."
 | 
			
		||||
 
 | 
			
		||||
@@ -990,6 +990,7 @@ rolesAssignedToMe: "Roles assigned to me"
 | 
			
		||||
resetPasswordConfirm: "Really reset your password?"
 | 
			
		||||
sensitiveWords: "Sensitive words"
 | 
			
		||||
sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks."
 | 
			
		||||
sensitiveWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression."
 | 
			
		||||
notesSearchNotAvailable: "Note search is unavailable."
 | 
			
		||||
license: "License"
 | 
			
		||||
unfavoriteConfirm: "Really remove from favorites?"
 | 
			
		||||
@@ -1038,13 +1039,16 @@ thisChannelArchived: "This channel has been archived."
 | 
			
		||||
displayOfNote: "Note display"
 | 
			
		||||
initialAccountSetting: "Profile setup"
 | 
			
		||||
youFollowing: "Followed"
 | 
			
		||||
preventAiLarning: "Reject usage in Machine Learning (AI)"
 | 
			
		||||
preventAiLarningDescription: "Requests crawlers to not use posted text or image material etc. in machine learning (AI) data sets. This is achieved by adding a \"noai\" HTML-Tag to the respective content. A complete prevention can however not be achieved through this tag, as it may simply be ignored."
 | 
			
		||||
preventAiLearning: "Reject usage in Machine Learning (Generative AI)"
 | 
			
		||||
preventAiLearningDescription: "Requests crawlers to not use posted text or image material etc. in machine learning (Predictive / Generative AI) data sets. This is achieved by adding a \"noai\" HTML-Response flag to the respective content. A complete prevention can however not be achieved through this flag, as it may simply be ignored."
 | 
			
		||||
options: "Options"
 | 
			
		||||
specifyUser: "Specific user"
 | 
			
		||||
_initialAccountSetting:
 | 
			
		||||
  accountCreated: "Your account was successfully created!"
 | 
			
		||||
  letsStartAccountSetup: "For starters, let's set up your profile."
 | 
			
		||||
  letsFillYourProfile: "First, let's set up your profile."
 | 
			
		||||
  profileSetting: "Profile settings"
 | 
			
		||||
  privacySetting: "Privacy settings"
 | 
			
		||||
  theseSettingsCanEditLater: "You can always change these settings later."
 | 
			
		||||
  youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later."
 | 
			
		||||
  followUsers: "Try following some users that interest you to build up your timeline."
 | 
			
		||||
@@ -1324,7 +1328,7 @@ _role:
 | 
			
		||||
  isConditionalRole: "This is a conditional role."
 | 
			
		||||
  isPublic: "Public role"
 | 
			
		||||
  descriptionOfIsPublic: "Anyone will be able to view a list of users assigned to this role. In addition, this role will be displayed in the profiles of assigned users."
 | 
			
		||||
  options: "Role options"
 | 
			
		||||
  options: "Options"
 | 
			
		||||
  policies: "Policies"
 | 
			
		||||
  baseRole: "Role template"
 | 
			
		||||
  useBaseValue: "Use role template value"
 | 
			
		||||
 
 | 
			
		||||
@@ -993,6 +993,7 @@ accountMigration: "Migración de cuenta"
 | 
			
		||||
accountMoved: "Este usuario se ha mudado a una nueva cuenta:"
 | 
			
		||||
horizontal: "Horizontal"
 | 
			
		||||
youFollowing: "Siguiendo"
 | 
			
		||||
options: "Opción"
 | 
			
		||||
_accountMigration:
 | 
			
		||||
  moveFrom: "Trasladar de otra cuenta a ésta"
 | 
			
		||||
  moveFromLabel: "Cuenta desde la que se realiza el traslado:"
 | 
			
		||||
 
 | 
			
		||||
@@ -452,6 +452,7 @@ native: "Natif"
 | 
			
		||||
disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
 | 
			
		||||
noHistory: "Pas d'historique"
 | 
			
		||||
signinHistory: "Historique de connexion"
 | 
			
		||||
enableAdvancedMfm: "Activer la MFM avancée"
 | 
			
		||||
doing: "En cours..."
 | 
			
		||||
category: "Catégorie"
 | 
			
		||||
tags: "Étiquettes"
 | 
			
		||||
@@ -846,6 +847,7 @@ rateLimitExceeded: "Limite de taux dépassée"
 | 
			
		||||
cropImage: "Recadrer l'image"
 | 
			
		||||
cropImageAsk: "Voulez-vous recadrer cette image ?"
 | 
			
		||||
cropYes: "Rogner"
 | 
			
		||||
cropNo: "Utiliser en l'état"
 | 
			
		||||
file: "Fichiers"
 | 
			
		||||
recentNHours: "Dernières {n} heures"
 | 
			
		||||
recentNDays: "Derniers {n} jours"
 | 
			
		||||
@@ -912,6 +914,7 @@ color: "Couleur"
 | 
			
		||||
manageCustomEmojis: "Gestion des émojis personnalisés"
 | 
			
		||||
preset: "Préréglage"
 | 
			
		||||
selectFromPresets: "Sélectionner à partir des préréglages"
 | 
			
		||||
thisPostMayBeAnnoyingCancel: "Annuler"
 | 
			
		||||
license: "Licence"
 | 
			
		||||
video: "Vidéo"
 | 
			
		||||
videos: "Vidéos"
 | 
			
		||||
@@ -925,6 +928,7 @@ leftBottom: "En bas à gauche"
 | 
			
		||||
rightBottom: "En bas à droite"
 | 
			
		||||
vertical: "Vertical"
 | 
			
		||||
horizontal: "Latéral"
 | 
			
		||||
serverRules: "Règles du serveur"
 | 
			
		||||
youFollowing: "Abonné·e"
 | 
			
		||||
_achievements:
 | 
			
		||||
  _types:
 | 
			
		||||
@@ -934,10 +938,13 @@ _achievements:
 | 
			
		||||
    _notes100000:
 | 
			
		||||
      title: "ALL YOUR NOTE ARE BELONG TO US"
 | 
			
		||||
    _login3:
 | 
			
		||||
      title: "Débutant Ⅰ"
 | 
			
		||||
      description: "Se connecter pour un total de 3 jours"
 | 
			
		||||
    _login7:
 | 
			
		||||
      title: "Débutant Ⅱ"
 | 
			
		||||
      description: "Se connecter pour un total de 7 jours"
 | 
			
		||||
    _login15:
 | 
			
		||||
      title: "Débutant Ⅲ"
 | 
			
		||||
      description: "Se connecter pour un total de 15 jours"
 | 
			
		||||
    _login30:
 | 
			
		||||
      description: "Se connecter pour un total de 30 jours"
 | 
			
		||||
@@ -945,6 +952,22 @@ _achievements:
 | 
			
		||||
      description: "Se connecter pour un total de 60 jours"
 | 
			
		||||
    _login100:
 | 
			
		||||
      description: "Se connecter pour un total de 100 jours"
 | 
			
		||||
    _login200:
 | 
			
		||||
      description: "Se connecter pour un total de 200 jours"
 | 
			
		||||
    _login300:
 | 
			
		||||
      description: "Se connecter pour un total de 300 jours"
 | 
			
		||||
    _login400:
 | 
			
		||||
      description: "Se connecter pour un total de 400 jours"
 | 
			
		||||
    _login500:
 | 
			
		||||
      description: "Se connecter pour un total de 500 jours"
 | 
			
		||||
    _login600:
 | 
			
		||||
      description: "Se connecter pour un total de 600 jours"
 | 
			
		||||
    _login700:
 | 
			
		||||
      description: "Se connecter pour un total de 700 jours"
 | 
			
		||||
    _login800:
 | 
			
		||||
      description: "Se connecter pour un total de 800 jours"
 | 
			
		||||
    _login900:
 | 
			
		||||
      description: "Se connecter pour un total de 900 jours"
 | 
			
		||||
    _login1000:
 | 
			
		||||
      flavor: "Merci d'utiliser Misskey !"
 | 
			
		||||
    _markedAsCat:
 | 
			
		||||
@@ -954,8 +977,12 @@ _achievements:
 | 
			
		||||
      title: "Beaucoup d'amis"
 | 
			
		||||
    _followers10:
 | 
			
		||||
      title: "Abonnez-moi !"
 | 
			
		||||
    _iLoveMisskey:
 | 
			
		||||
      title: "J’adore Misskey"
 | 
			
		||||
    _viewInstanceChart:
 | 
			
		||||
      title: "Analyste"
 | 
			
		||||
    _loggedInOnBirthday:
 | 
			
		||||
      title: "Joyeux Anniversaire !"
 | 
			
		||||
    _loggedInOnNewYearsDay:
 | 
			
		||||
      title: "Bonne année !"
 | 
			
		||||
_role:
 | 
			
		||||
 
 | 
			
		||||
@@ -955,6 +955,7 @@ disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi
 | 
			
		||||
disableFederationOk: "Matikan federasi"
 | 
			
		||||
horizontal: "Horisontal"
 | 
			
		||||
youFollowing: "Mengikuti"
 | 
			
		||||
options: "Opsi peran"
 | 
			
		||||
_achievements:
 | 
			
		||||
  earnedAt: "Terbuka pada"
 | 
			
		||||
  _types:
 | 
			
		||||
 
 | 
			
		||||
@@ -1020,6 +1020,7 @@ pleaseConfirmBelowBeforeSignup: "Ai sensi del regolamento EU 679/2016 GDPR, auto
 | 
			
		||||
pleaseAgreeAllToContinue: "Per continuare, occorre selezionare ed essere d'accordo su tutto."
 | 
			
		||||
continue: "Continua"
 | 
			
		||||
youFollowing: "Seguiti"
 | 
			
		||||
options: "Opzioni del ruolo"
 | 
			
		||||
_serverRules:
 | 
			
		||||
  description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio."
 | 
			
		||||
_accountMigration:
 | 
			
		||||
 
 | 
			
		||||
@@ -689,7 +689,7 @@ no: "いいえ"
 | 
			
		||||
driveFilesCount: "ドライブのファイル数"
 | 
			
		||||
driveUsage: "ドライブ使用量"
 | 
			
		||||
noCrawle: "クローラーによるインデックスを拒否"
 | 
			
		||||
noCrawleDescription: "検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要請します。"
 | 
			
		||||
noCrawleDescription: "外部の検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要求します。"
 | 
			
		||||
lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。"
 | 
			
		||||
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
 | 
			
		||||
loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
 | 
			
		||||
@@ -832,6 +832,8 @@ breakFollow: "フォロワーを解除"
 | 
			
		||||
breakFollowConfirm: "フォロワー解除しますか?"
 | 
			
		||||
itsOn: "オンになっています"
 | 
			
		||||
itsOff: "オフになっています"
 | 
			
		||||
on: "オン"
 | 
			
		||||
off: "オフ"
 | 
			
		||||
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
 | 
			
		||||
unread: "未読"
 | 
			
		||||
filter: "フィルタ"
 | 
			
		||||
@@ -1039,14 +1041,17 @@ thisChannelArchived: "このチャンネルはアーカイブされています
 | 
			
		||||
displayOfNote: "ノートの表示"
 | 
			
		||||
initialAccountSetting: "初期設定"
 | 
			
		||||
youFollowing: "フォロー中"
 | 
			
		||||
preventAiLarning: "AIによる学習を拒否"
 | 
			
		||||
preventAiLarningDescription: "投稿したノートや画像などのコンテンツを学習の対象にしないようAIに要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されます。この機能は実験的であり、AIによる学習を完全に防止するものではありません。"
 | 
			
		||||
preventAiLearning: "生成AIによる学習を拒否"
 | 
			
		||||
preventAiLearningDescription: "外部の文章生成AIや画像生成AIに対して、投稿したノートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。"
 | 
			
		||||
options: "オプション"
 | 
			
		||||
specifyUser: "ユーザー指定"
 | 
			
		||||
 | 
			
		||||
_initialAccountSetting:
 | 
			
		||||
  accountCreated: "アカウントの作成が完了しました!"
 | 
			
		||||
  letsStartAccountSetup: "アカウントの初期設定を行いましょう。"
 | 
			
		||||
  letsFillYourProfile: "まずはあなたのプロフィールを設定しましょう。"
 | 
			
		||||
  profileSetting: "プロフィール設定"
 | 
			
		||||
  privacySetting: "プライバシー設定"
 | 
			
		||||
  theseSettingsCanEditLater: "これらの設定は後から変更できます。"
 | 
			
		||||
  youCanEditMoreSettingsInSettingsPageLater: "この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。"
 | 
			
		||||
  followUsers: "タイムラインを構築するため、気になるユーザーをフォローしてみましょう。"
 | 
			
		||||
 
 | 
			
		||||
@@ -689,7 +689,7 @@ no: "あかん"
 | 
			
		||||
driveFilesCount: "ドライブのファイル数"
 | 
			
		||||
driveUsage: "ドライブ使用量やで"
 | 
			
		||||
noCrawle: "クローラーによるインデックスを拒否するで"
 | 
			
		||||
noCrawleDescription: "検索エンジンにあんたのユーザーページ、ノート、Pagesとかのコンテンツを登録(インデックス)せんように頼むで。邪魔すんねんやったら帰って〜。"
 | 
			
		||||
noCrawleDescription: "検索エンジンにあんたのユーザーページ、ノート、Pagesとかのコンテンツを登録(インデックス)せぇへんように頼むで。"
 | 
			
		||||
lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。"
 | 
			
		||||
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで"
 | 
			
		||||
loadRawImages: "添付画像のサムネイルをオリジナル画質にするで"
 | 
			
		||||
@@ -990,6 +990,7 @@ rolesAssignedToMe: "自分に割り当てられたロール"
 | 
			
		||||
resetPasswordConfirm: "パスワード作り直すんでええな?"
 | 
			
		||||
sensitiveWords: "けったいな単語"
 | 
			
		||||
sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
 | 
			
		||||
sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
 | 
			
		||||
notesSearchNotAvailable: "ノート検索は使われへんで。"
 | 
			
		||||
license: "ライセンス"
 | 
			
		||||
unfavoriteConfirm: "ほんまに気に入らんの?"
 | 
			
		||||
@@ -1038,10 +1039,16 @@ thisChannelArchived: "このチャンネル、アーカイブされとるで。"
 | 
			
		||||
displayOfNote: "ノートの表示"
 | 
			
		||||
initialAccountSetting: "初期設定"
 | 
			
		||||
youFollowing: "フォロー中やで"
 | 
			
		||||
preventAiLearning: "生成AIの学習に使わんといて"
 | 
			
		||||
preventAiLearningDescription: "他の文章生成AIとか画像生成AIに、投稿したノートとか画像なんかを勝手に使わんように頼むで。具体的にはnoaiフラグをHTMLレスポンスに含めるんやけど、これ聞いてくれるんはAIの気分次第やから、使われる可能性もちょっとはあるな。"
 | 
			
		||||
options: "オプション"
 | 
			
		||||
specifyUser: "ユーザー指定"
 | 
			
		||||
_initialAccountSetting:
 | 
			
		||||
  accountCreated: "アカウント作り終わったで。"
 | 
			
		||||
  letsStartAccountSetup: "アカウントの初期設定をしよか。"
 | 
			
		||||
  letsFillYourProfile: "最初はあんたのプロフィールを設定しよか。"
 | 
			
		||||
  profileSetting: "プロフィール設定"
 | 
			
		||||
  privacySetting: "プライバシー設定"
 | 
			
		||||
  theseSettingsCanEditLater: "この設定はあとから変えれるで。"
 | 
			
		||||
  youCanEditMoreSettingsInSettingsPageLater: "これ以外にもいろんな設定を「設定」ページからできるで。後で確認してみてな。"
 | 
			
		||||
  followUsers: "タイムラインを構築するために、気になるユーザーをフォローしてみ。"
 | 
			
		||||
@@ -1060,11 +1067,12 @@ _accountMigration:
 | 
			
		||||
  moveTo: "このアカウントをさらのアカウントに引っ越すで"
 | 
			
		||||
  moveToLabel: "引っ越し先のアカウント:"
 | 
			
		||||
  moveCannotBeUndone: "アカウントを移行すると、取り消すことはできへんくなります。"
 | 
			
		||||
  moveAccountDescription: "この操作は戻されへんで。まず引っ越し先のアカウントでこのアカウントへのエイリアスが作れたか確認してきなはれや。エイリアスができてたら、引っ越し先のアカウントをこんな風に入力してくれへんか?:@person@instance.com"
 | 
			
		||||
  moveAccountDescription: "おニューのアカウントに移行すんで。\n ・フォロワーがおニューの方を勝手にフォローすんで。\n ・このアカウントからのフォローはまるまる全部解除されんで。\n ・このアカウントでノート作れへんようになるで。\n\nフォロワーの移行は勝手にこっちでやっとくけど、フォローの移行は自分でしてや。移行前にこのアカウントでフォローエクスポートして、移行したあとすぐにおニューのところでインポートしてくれな。\nリストとかミュート、あとブロックもおんなじや。自分で移行してな。\n\n(この説明はこのサーバー、つまりMisskey v13.12.0から後の仕様や。Mastodonとか他のActivityPubソフトやとちょっと挙動が違うこともあんで。)"
 | 
			
		||||
  moveAccountHowTo: "アカウントの引っ越しには、まず引っ越し先のアカウントで自分のアカウントに対しエイリアスを作成しなはれや。\nエイリアス作成した後、引っ越し先のアカウントを次のように入力してくれへんか?:@username@server.example.com"
 | 
			
		||||
  startMigration: "引っ越しする"
 | 
			
		||||
  migrationConfirm: "ほんまにこのアカウントを {account} に引っ越すんか?一回引っ越してもうたら取り消されへんし、二度とこのアカウントを元に戻されへんくなるで。\nそれと、引っ越し先のアカウントでエイリアスが作れたかちゃ~んと確認しーや?"
 | 
			
		||||
  movedAndCannotBeUndone: "\nアカウントはもう引っ越されてます。\n引っ越しを取り消すことはできまへん。"
 | 
			
		||||
  postMigrationNote: "このアカウントからのフォロー解除は移行操作から丸一日経ったら実行されんで。\nこのアカウントのフォロー・フォロワー数はどっちも0や。フォローの解除はされへんから、あんたのフォロワーはこのアカウントのフォロワー向けの投稿をこの後も見れるで。"
 | 
			
		||||
  movedTo: "引っ越し先のアカウント:"
 | 
			
		||||
_achievements:
 | 
			
		||||
  earnedAt: "貰った日ぃ"
 | 
			
		||||
 
 | 
			
		||||
@@ -1038,6 +1038,7 @@ thisChannelArchived: "이 채널은 아카이브되었습니다."
 | 
			
		||||
displayOfNote: "노트 표시"
 | 
			
		||||
initialAccountSetting: "초기 설정"
 | 
			
		||||
youFollowing: "팔로잉"
 | 
			
		||||
options: "옵션"
 | 
			
		||||
_initialAccountSetting:
 | 
			
		||||
  accountCreated: "계정 생성이 완료되었습니다!"
 | 
			
		||||
  letsStartAccountSetup: "계정의 초기 설정을 진행합니다."
 | 
			
		||||
 
 | 
			
		||||
@@ -278,6 +278,7 @@ video: "Video"
 | 
			
		||||
videos: "Videoer"
 | 
			
		||||
continue: "Fortsett"
 | 
			
		||||
youFollowing: "Følger"
 | 
			
		||||
options: "Alternativ"
 | 
			
		||||
_initialAccountSetting:
 | 
			
		||||
  theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
 | 
			
		||||
_achievements:
 | 
			
		||||
 
 | 
			
		||||
@@ -1006,6 +1006,7 @@ videos: "Видео"
 | 
			
		||||
dataSaver: "Экономия трафика"
 | 
			
		||||
horizontal: "Сбоку"
 | 
			
		||||
youFollowing: "Подписки"
 | 
			
		||||
options: "Настройки ролей"
 | 
			
		||||
_achievements:
 | 
			
		||||
  earnedAt: "Разблокировано в"
 | 
			
		||||
  _types:
 | 
			
		||||
 
 | 
			
		||||
@@ -1031,6 +1031,7 @@ preservedUsernames: "ชื่อผู้ใช้ที่สงวนไว
 | 
			
		||||
preservedUsernamesDescription: "ลิสต์ชื่อผู้ใช้ที่จะสำรองโดยคั่นด้วยการแบ่งบรรทัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างการสร้างบัญชีตามปกติ บัญชีที่มีอยู่แล้วนั้นโดยใช้ชื่อผู้ใช้เหล่านี้จะไม่ได้รับผลกระทบอะไร"
 | 
			
		||||
createNoteFromTheFile: "เรียบเรียงโน้ตจากไฟล์นี้"
 | 
			
		||||
youFollowing: "ติดตามแล้ว"
 | 
			
		||||
options: "ตัวเลือกบทบาท"
 | 
			
		||||
_serverRules:
 | 
			
		||||
  description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
 | 
			
		||||
_accountMigration:
 | 
			
		||||
 
 | 
			
		||||
@@ -1030,6 +1030,13 @@ continue: "继续"
 | 
			
		||||
preservedUsernames: "保留的用户名"
 | 
			
		||||
createNoteFromTheFile: "从文件创建帖子"
 | 
			
		||||
youFollowing: "正在关注"
 | 
			
		||||
options: "选项"
 | 
			
		||||
_initialAccountSetting:
 | 
			
		||||
  accountCreated: "账户创建完成了!"
 | 
			
		||||
  letsStartAccountSetup: "来进行帐户的初始设置吧。"
 | 
			
		||||
  letsFillYourProfile: "首先,来设定你的个人档案吧!"
 | 
			
		||||
  profileSetting: "个人资料设置"
 | 
			
		||||
  theseSettingsCanEditLater: "也可以在稍后修改这里的设置。"
 | 
			
		||||
_serverRules:
 | 
			
		||||
  description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。"
 | 
			
		||||
_accountMigration:
 | 
			
		||||
@@ -1038,8 +1045,11 @@ _accountMigration:
 | 
			
		||||
  moveFromDescription: "如果迁移时需要继承其他账户的关注者,请在此创造别名。此操作需要在实行迁移之前完成!请如已下输入需要迁移的账户:@person@instance.com"
 | 
			
		||||
  moveTo: "把这个账户迁移到新的账户"
 | 
			
		||||
  moveToLabel: "迁移后的账户"
 | 
			
		||||
  moveCannotBeUndone: "一旦迁移账户,就无法撤销。"
 | 
			
		||||
  moveAccountDescription: "此操作无法取消。请先确认您已在迁移后的账户上,为此账户创造了别名。创造别名后,请如以下输入您的迁移后的账户:@person@instance.com"
 | 
			
		||||
  startMigration: "迁移"
 | 
			
		||||
  migrationConfirm: "确定要把此账户迁移到{account}吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时,请确认迁移后的账户,已创造别名。"
 | 
			
		||||
  movedAndCannotBeUndone: "该账户已被迁移。\n迁移操作无法撤销。"
 | 
			
		||||
  movedTo: "迁移后的账户"
 | 
			
		||||
_achievements:
 | 
			
		||||
  earnedAt: "达成时间"
 | 
			
		||||
@@ -1572,6 +1582,9 @@ _time:
 | 
			
		||||
  minute: "分"
 | 
			
		||||
  hour: "小时"
 | 
			
		||||
  day: "日"
 | 
			
		||||
_timelineTutorial:
 | 
			
		||||
  step3_1: "将想说的话发出去了吗?"
 | 
			
		||||
  step3_2: "太棒了!现在你可以在你的时间线中看到刚刚发布的帖子了。"
 | 
			
		||||
_2fa:
 | 
			
		||||
  alreadyRegistered: "此设备已被注册"
 | 
			
		||||
  registerTOTP: "开始设置认证应用"
 | 
			
		||||
 
 | 
			
		||||
@@ -990,6 +990,7 @@ rolesAssignedToMe: "指派給自己的角色"
 | 
			
		||||
resetPasswordConfirm: "重設密碼?"
 | 
			
		||||
sensitiveWords: "敏感詞"
 | 
			
		||||
sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
 | 
			
		||||
sensitiveWordsDescription2: "用空格分隔關鍵詞構成AND格式,用斜線包圍關鍵字構成正規表達式。"
 | 
			
		||||
notesSearchNotAvailable: "無法使用搜尋貼文功能。"
 | 
			
		||||
license: "授權"
 | 
			
		||||
unfavoriteConfirm: "要取消收錄我的最愛嗎?"
 | 
			
		||||
@@ -1038,6 +1039,9 @@ thisChannelArchived: "這個頻道已被封存。"
 | 
			
		||||
displayOfNote: "顯示貼文"
 | 
			
		||||
initialAccountSetting: "初始設定"
 | 
			
		||||
youFollowing: "追隨中"
 | 
			
		||||
preventAiLearning: "拒絕接受產生式AI的學習"
 | 
			
		||||
preventAiLearningDescription: "要求外部的文章產生AI或圖像產生AI不以發布的貼文和圖像等內容為學習對象。這是透過在HTML響應中包含noai旗標來實現的,但不能完全防止AI的學習,因為這要看該AI是否遵守這個要求。"
 | 
			
		||||
options: "選項"
 | 
			
		||||
_initialAccountSetting:
 | 
			
		||||
  accountCreated: "帳戶已建立完成!"
 | 
			
		||||
  letsStartAccountSetup: "來進行帳戶的初始設定吧。"
 | 
			
		||||
 
 | 
			
		||||
@@ -56,11 +56,11 @@
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@types/gulp": "4.0.10",
 | 
			
		||||
		"@types/gulp-rename": "2.0.1",
 | 
			
		||||
		"@typescript-eslint/eslint-plugin": "5.59.2",
 | 
			
		||||
		"@typescript-eslint/parser": "5.59.2",
 | 
			
		||||
		"@typescript-eslint/eslint-plugin": "5.59.5",
 | 
			
		||||
		"@typescript-eslint/parser": "5.59.5",
 | 
			
		||||
		"cross-env": "7.0.3",
 | 
			
		||||
		"cypress": "12.11.0",
 | 
			
		||||
		"eslint": "8.39.0",
 | 
			
		||||
		"cypress": "12.12.0",
 | 
			
		||||
		"eslint": "8.40.0",
 | 
			
		||||
		"start-server-and-test": "2.0.0"
 | 
			
		||||
	},
 | 
			
		||||
	"optionalDependencies": {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/backend/migration/1683789676867-fix-typo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/backend/migration/1683789676867-fix-typo.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
export class FixTypo1683789676867 {
 | 
			
		||||
    name = 'FixTypo1683789676867'
 | 
			
		||||
 | 
			
		||||
    async up(queryRunner) {
 | 
			
		||||
				await queryRunner.query(`ALTER TABLE "user_profile" RENAME COLUMN "preventAiLarning" TO "preventAiLearning"`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async down(queryRunner) {
 | 
			
		||||
			await queryRunner.query(`ALTER TABLE "user_profile" RENAME COLUMN "preventAiLearning" TO "preventAiLarning"`);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -58,7 +58,7 @@
 | 
			
		||||
		"@fastify/accepts": "4.1.0",
 | 
			
		||||
		"@fastify/cookie": "8.3.0",
 | 
			
		||||
		"@fastify/cors": "8.2.1",
 | 
			
		||||
		"@fastify/http-proxy": "9.0.0",
 | 
			
		||||
		"@fastify/http-proxy": "9.1.0",
 | 
			
		||||
		"@fastify/multipart": "7.6.0",
 | 
			
		||||
		"@fastify/static": "6.10.1",
 | 
			
		||||
		"@fastify/view": "7.4.1",
 | 
			
		||||
@@ -89,11 +89,11 @@
 | 
			
		||||
		"escape-regexp": "0.0.1",
 | 
			
		||||
		"fastify": "4.17.0",
 | 
			
		||||
		"feed": "4.2.2",
 | 
			
		||||
		"file-type": "18.3.0",
 | 
			
		||||
		"file-type": "18.4.0",
 | 
			
		||||
		"fluent-ffmpeg": "2.1.2",
 | 
			
		||||
		"form-data": "4.0.0",
 | 
			
		||||
		"got": "12.6.0",
 | 
			
		||||
		"happy-dom": "9.10.2",
 | 
			
		||||
		"happy-dom": "9.16.0",
 | 
			
		||||
		"hpagent": "1.2.0",
 | 
			
		||||
		"ioredis": "5.3.2",
 | 
			
		||||
		"ip-cidr": "3.1.0",
 | 
			
		||||
@@ -110,11 +110,11 @@
 | 
			
		||||
		"ms": "3.0.0-canary.1",
 | 
			
		||||
		"nested-property": "4.0.0",
 | 
			
		||||
		"node-fetch": "3.3.1",
 | 
			
		||||
		"nodemailer": "6.9.1",
 | 
			
		||||
		"nodemailer": "6.9.2",
 | 
			
		||||
		"nsfwjs": "2.4.2",
 | 
			
		||||
		"oauth": "0.10.0",
 | 
			
		||||
		"os-utils": "0.0.14",
 | 
			
		||||
		"otpauth": "9.1.1",
 | 
			
		||||
		"otpauth": "9.1.2",
 | 
			
		||||
		"parse5": "7.1.2",
 | 
			
		||||
		"pg": "8.10.0",
 | 
			
		||||
		"private-ip": "3.0.0",
 | 
			
		||||
@@ -149,7 +149,7 @@
 | 
			
		||||
		"tsc-alias": "1.8.6",
 | 
			
		||||
		"tsconfig-paths": "4.2.0",
 | 
			
		||||
		"twemoji-parser": "14.0.0",
 | 
			
		||||
		"typeorm": "0.3.15",
 | 
			
		||||
		"typeorm": "0.3.16",
 | 
			
		||||
		"typescript": "5.0.4",
 | 
			
		||||
		"ulid": "2.3.0",
 | 
			
		||||
		"unzipper": "0.10.11",
 | 
			
		||||
@@ -178,7 +178,7 @@
 | 
			
		||||
		"@types/jsonld": "1.5.8",
 | 
			
		||||
		"@types/jsrsasign": "10.5.8",
 | 
			
		||||
		"@types/mime-types": "2.1.1",
 | 
			
		||||
		"@types/node": "18.16.3",
 | 
			
		||||
		"@types/node": "20.1.3",
 | 
			
		||||
		"@types/node-fetch": "3.0.3",
 | 
			
		||||
		"@types/nodemailer": "6.4.7",
 | 
			
		||||
		"@types/oauth": "0.9.1",
 | 
			
		||||
@@ -191,7 +191,7 @@
 | 
			
		||||
		"@types/redis": "4.0.11",
 | 
			
		||||
		"@types/rename": "1.0.4",
 | 
			
		||||
		"@types/sanitize-html": "2.9.0",
 | 
			
		||||
		"@types/semver": "7.3.13",
 | 
			
		||||
		"@types/semver": "7.5.0",
 | 
			
		||||
		"@types/sharp": "0.32.0",
 | 
			
		||||
		"@types/sinonjs__fake-timers": "8.1.2",
 | 
			
		||||
		"@types/tinycolor2": "1.4.3",
 | 
			
		||||
@@ -202,11 +202,11 @@
 | 
			
		||||
		"@types/web-push": "3.3.2",
 | 
			
		||||
		"@types/websocket": "1.0.5",
 | 
			
		||||
		"@types/ws": "8.5.4",
 | 
			
		||||
		"@typescript-eslint/eslint-plugin": "5.59.2",
 | 
			
		||||
		"@typescript-eslint/parser": "5.59.2",
 | 
			
		||||
		"aws-sdk-client-mock": "^2.1.1",
 | 
			
		||||
		"@typescript-eslint/eslint-plugin": "5.59.5",
 | 
			
		||||
		"@typescript-eslint/parser": "5.59.5",
 | 
			
		||||
		"aws-sdk-client-mock": "2.1.1",
 | 
			
		||||
		"cross-env": "7.0.3",
 | 
			
		||||
		"eslint": "8.39.0",
 | 
			
		||||
		"eslint": "8.40.0",
 | 
			
		||||
		"eslint-plugin-import": "2.27.5",
 | 
			
		||||
		"execa": "6.1.0",
 | 
			
		||||
		"jest": "29.5.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,7 @@ export type Source = {
 | 
			
		||||
		port: string;
 | 
			
		||||
		apiKey: string;
 | 
			
		||||
		ssl?: boolean;
 | 
			
		||||
		index: string;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	proxy?: string;
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ export class SearchService {
 | 
			
		||||
		private idService: IdService,
 | 
			
		||||
	) {
 | 
			
		||||
		if (meilisearch) {
 | 
			
		||||
			this.meilisearchNoteIndex = meilisearch.index('notes');
 | 
			
		||||
			this.meilisearchNoteIndex = meilisearch.index(`${config.meilisearch!.index}---notes`);
 | 
			
		||||
			this.meilisearchNoteIndex.updateSettings({
 | 
			
		||||
				searchableAttributes: [
 | 
			
		||||
					'text',
 | 
			
		||||
@@ -82,6 +82,7 @@ export class SearchService {
 | 
			
		||||
					'userId',
 | 
			
		||||
					'userHost',
 | 
			
		||||
					'channelId',
 | 
			
		||||
					'tags',
 | 
			
		||||
				],
 | 
			
		||||
				typoTolerance: {
 | 
			
		||||
					enabled: false,
 | 
			
		||||
@@ -107,6 +108,7 @@ export class SearchService {
 | 
			
		||||
				channelId: note.channelId,
 | 
			
		||||
				cw: note.cw,
 | 
			
		||||
				text: note.text,
 | 
			
		||||
				tags: note.tags,
 | 
			
		||||
			}], {
 | 
			
		||||
				primaryKey: 'id',
 | 
			
		||||
			});
 | 
			
		||||
 
 | 
			
		||||
@@ -445,7 +445,7 @@ export class UserEntityService implements OnModuleInit {
 | 
			
		||||
				carefulBot: profile!.carefulBot,
 | 
			
		||||
				autoAcceptFollowed: profile!.autoAcceptFollowed,
 | 
			
		||||
				noCrawle: profile!.noCrawle,
 | 
			
		||||
				preventAiLarning: profile!.preventAiLarning,
 | 
			
		||||
				preventAiLearning: profile!.preventAiLearning,
 | 
			
		||||
				isExplorable: user.isExplorable,
 | 
			
		||||
				isDeleted: user.isDeleted,
 | 
			
		||||
				hideOnlineStatus: user.hideOnlineStatus,
 | 
			
		||||
 
 | 
			
		||||
@@ -150,7 +150,7 @@ export class UserProfile {
 | 
			
		||||
	@Column('boolean', {
 | 
			
		||||
		default: true,
 | 
			
		||||
	})
 | 
			
		||||
	public preventAiLarning: boolean;
 | 
			
		||||
	public preventAiLearning: boolean;
 | 
			
		||||
 | 
			
		||||
	@Column('boolean', {
 | 
			
		||||
		default: false,
 | 
			
		||||
 
 | 
			
		||||
@@ -304,7 +304,7 @@ export const packedMeDetailedOnlySchema = {
 | 
			
		||||
			type: 'boolean',
 | 
			
		||||
			nullable: false, optional: false,
 | 
			
		||||
		},
 | 
			
		||||
		preventAiLarning: {
 | 
			
		||||
		preventAiLearning: {
 | 
			
		||||
			type: 'boolean',
 | 
			
		||||
			nullable: false, optional: false,
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 | 
			
		||||
				emailVerified: profile.emailVerified,
 | 
			
		||||
				autoAcceptFollowed: profile.autoAcceptFollowed,
 | 
			
		||||
				noCrawle: profile.noCrawle,
 | 
			
		||||
				preventAiLarning: profile.preventAiLarning,
 | 
			
		||||
				preventAiLearning: profile.preventAiLearning,
 | 
			
		||||
				alwaysMarkNsfw: profile.alwaysMarkNsfw,
 | 
			
		||||
				autoSensitive: profile.autoSensitive,
 | 
			
		||||
				carefulBot: profile.carefulBot,
 | 
			
		||||
 
 | 
			
		||||
@@ -138,7 +138,7 @@ export const paramDef = {
 | 
			
		||||
		carefulBot: { type: 'boolean' },
 | 
			
		||||
		autoAcceptFollowed: { type: 'boolean' },
 | 
			
		||||
		noCrawle: { type: 'boolean' },
 | 
			
		||||
		preventAiLarning: { type: 'boolean' },
 | 
			
		||||
		preventAiLearning: { type: 'boolean' },
 | 
			
		||||
		isBot: { type: 'boolean' },
 | 
			
		||||
		isCat: { type: 'boolean' },
 | 
			
		||||
		showTimelineReplies: { type: 'boolean' },
 | 
			
		||||
@@ -243,7 +243,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 | 
			
		||||
			if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
 | 
			
		||||
			if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
 | 
			
		||||
			if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
 | 
			
		||||
			if (typeof ps.preventAiLarning === 'boolean') profileUpdates.preventAiLarning = ps.preventAiLarning;
 | 
			
		||||
			if (typeof ps.preventAiLearning === 'boolean') profileUpdates.preventAiLearning = ps.preventAiLearning;
 | 
			
		||||
			if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
 | 
			
		||||
			if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
 | 
			
		||||
			if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
 | 
			
		||||
 
 | 
			
		||||
@@ -437,7 +437,7 @@ export class ClientServerService {
 | 
			
		||||
					: [];
 | 
			
		||||
 | 
			
		||||
				reply.header('Cache-Control', 'public, max-age=15');
 | 
			
		||||
				if (profile.preventAiLarning) {
 | 
			
		||||
				if (profile.preventAiLearning) {
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noimageai');
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noai');
 | 
			
		||||
				}
 | 
			
		||||
@@ -485,7 +485,7 @@ export class ClientServerService {
 | 
			
		||||
				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
 | 
			
		||||
				const meta = await this.metaService.fetch();
 | 
			
		||||
				reply.header('Cache-Control', 'public, max-age=15');
 | 
			
		||||
				if (profile.preventAiLarning) {
 | 
			
		||||
				if (profile.preventAiLearning) {
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noimageai');
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noai');
 | 
			
		||||
				}
 | 
			
		||||
@@ -528,7 +528,7 @@ export class ClientServerService {
 | 
			
		||||
				} else {
 | 
			
		||||
					reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
 | 
			
		||||
				}
 | 
			
		||||
				if (profile.preventAiLarning) {
 | 
			
		||||
				if (profile.preventAiLearning) {
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noimageai');
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noai');
 | 
			
		||||
				}
 | 
			
		||||
@@ -556,7 +556,7 @@ export class ClientServerService {
 | 
			
		||||
				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
 | 
			
		||||
				const meta = await this.metaService.fetch();
 | 
			
		||||
				reply.header('Cache-Control', 'public, max-age=15');
 | 
			
		||||
				if (profile.preventAiLarning) {
 | 
			
		||||
				if (profile.preventAiLearning) {
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noimageai');
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noai');
 | 
			
		||||
				}
 | 
			
		||||
@@ -584,7 +584,7 @@ export class ClientServerService {
 | 
			
		||||
				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
 | 
			
		||||
				const meta = await this.metaService.fetch();
 | 
			
		||||
				reply.header('Cache-Control', 'public, max-age=15');
 | 
			
		||||
				if (profile.preventAiLarning) {
 | 
			
		||||
				if (profile.preventAiLearning) {
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noimageai');
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noai');
 | 
			
		||||
				}
 | 
			
		||||
@@ -610,7 +610,7 @@ export class ClientServerService {
 | 
			
		||||
				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
 | 
			
		||||
				const meta = await this.metaService.fetch();
 | 
			
		||||
				reply.header('Cache-Control', 'public, max-age=15');
 | 
			
		||||
				if (profile.preventAiLarning) {
 | 
			
		||||
				if (profile.preventAiLearning) {
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noimageai');
 | 
			
		||||
					reply.header('X-Robots-Tag', 'noai');
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ block og
 | 
			
		||||
block meta
 | 
			
		||||
	if profile.noCrawle
 | 
			
		||||
		meta(name='robots' content='noindex')
 | 
			
		||||
	if profile.preventAiLarning
 | 
			
		||||
	if profile.preventAiLearning
 | 
			
		||||
		meta(name='robots' content='noimageai')
 | 
			
		||||
		meta(name='robots' content='noai')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ block og
 | 
			
		||||
block meta
 | 
			
		||||
	if profile.noCrawle
 | 
			
		||||
		meta(name='robots' content='noindex')
 | 
			
		||||
	if profile.preventAiLarning
 | 
			
		||||
	if profile.preventAiLearning
 | 
			
		||||
		meta(name='robots' content='noimageai')
 | 
			
		||||
		meta(name='robots' content='noai')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ block og
 | 
			
		||||
block meta
 | 
			
		||||
	if user.host || profile.noCrawle
 | 
			
		||||
		meta(name='robots' content='noindex')
 | 
			
		||||
	if profile.preventAiLarning
 | 
			
		||||
	if profile.preventAiLearning
 | 
			
		||||
		meta(name='robots' content='noimageai')
 | 
			
		||||
		meta(name='robots' content='noai')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ block og
 | 
			
		||||
block meta
 | 
			
		||||
	if user.host || isRenote || profile.noCrawle
 | 
			
		||||
		meta(name='robots' content='noindex')
 | 
			
		||||
	if profile.preventAiLarning
 | 
			
		||||
	if profile.preventAiLearning
 | 
			
		||||
		meta(name='robots' content='noimageai')
 | 
			
		||||
		meta(name='robots' content='noai')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ block og
 | 
			
		||||
block meta
 | 
			
		||||
	if profile.noCrawle
 | 
			
		||||
		meta(name='robots' content='noindex')
 | 
			
		||||
	if profile.preventAiLarning
 | 
			
		||||
	if profile.preventAiLearning
 | 
			
		||||
		meta(name='robots' content='noimageai')
 | 
			
		||||
		meta(name='robots' content='noai')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ block og
 | 
			
		||||
block meta
 | 
			
		||||
	if user.host || profile.noCrawle
 | 
			
		||||
		meta(name='robots' content='noindex')
 | 
			
		||||
	if profile.preventAiLarning
 | 
			
		||||
	if profile.preventAiLearning
 | 
			
		||||
		meta(name='robots' content='noimageai')
 | 
			
		||||
		meta(name='robots' content='noai')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -145,7 +145,7 @@ describe('ユーザー', () => {
 | 
			
		||||
			carefulBot: user.carefulBot,
 | 
			
		||||
			autoAcceptFollowed: user.autoAcceptFollowed,
 | 
			
		||||
			noCrawle: user.noCrawle,
 | 
			
		||||
			preventAiLarning: user.preventAiLarning,
 | 
			
		||||
			preventAiLearning: user.preventAiLearning,
 | 
			
		||||
			isExplorable: user.isExplorable,
 | 
			
		||||
			isDeleted: user.isDeleted,
 | 
			
		||||
			hideOnlineStatus: user.hideOnlineStatus,
 | 
			
		||||
@@ -391,7 +391,7 @@ describe('ユーザー', () => {
 | 
			
		||||
		assert.strictEqual(response.carefulBot, false);
 | 
			
		||||
		assert.strictEqual(response.autoAcceptFollowed, true);
 | 
			
		||||
		assert.strictEqual(response.noCrawle, false);
 | 
			
		||||
		assert.strictEqual(response.preventAiLarning, true);
 | 
			
		||||
		assert.strictEqual(response.preventAiLearning, true);
 | 
			
		||||
		assert.strictEqual(response.isExplorable, true);
 | 
			
		||||
		assert.strictEqual(response.isDeleted, false);
 | 
			
		||||
		assert.strictEqual(response.hideOnlineStatus, false);
 | 
			
		||||
@@ -464,8 +464,8 @@ describe('ユーザー', () => {
 | 
			
		||||
		{ parameters: (): object => ({ autoAcceptFollowed: false }) },
 | 
			
		||||
		{ parameters: (): object => ({ noCrawle: true }) },
 | 
			
		||||
		{ parameters: (): object => ({ noCrawle: false }) },
 | 
			
		||||
		{ parameters: (): object => ({ preventAiLarning: false }) },
 | 
			
		||||
		{ parameters: (): object => ({ preventAiLarning: true }) },
 | 
			
		||||
		{ parameters: (): object => ({ preventAiLearning: false }) },
 | 
			
		||||
		{ parameters: (): object => ({ preventAiLearning: true }) },
 | 
			
		||||
		{ parameters: (): object => ({ isBot: true }) },
 | 
			
		||||
		{ parameters: (): object => ({ isBot: false }) },
 | 
			
		||||
		{ parameters: (): object => ({ isCat: true }) },
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ fs.readFile(
 | 
			
		||||
		micromatch(Array.from(modules), [
 | 
			
		||||
			'../../assets/**',
 | 
			
		||||
			'../../fluent-emojis/**',
 | 
			
		||||
			'../../locales/**',
 | 
			
		||||
			'../../locales/ja-JP.yml',
 | 
			
		||||
			'../../misskey-assets/**',
 | 
			
		||||
			'assets/**',
 | 
			
		||||
			'public/**',
 | 
			
		||||
 
 | 
			
		||||
@@ -17,13 +17,13 @@
 | 
			
		||||
		"@discordapp/twemoji": "14.1.2",
 | 
			
		||||
		"@rollup/plugin-alias": "5.0.0",
 | 
			
		||||
		"@rollup/plugin-json": "6.0.0",
 | 
			
		||||
		"@rollup/plugin-replace": "^5.0.2",
 | 
			
		||||
		"@rollup/plugin-replace": "5.0.2",
 | 
			
		||||
		"@rollup/pluginutils": "5.0.2",
 | 
			
		||||
		"@syuilo/aiscript": "0.13.2",
 | 
			
		||||
		"@tabler/icons-webfont": "2.17.0",
 | 
			
		||||
		"@vitejs/plugin-vue": "4.2.1",
 | 
			
		||||
		"@vue-macros/reactivity-transform": "^0.3.5",
 | 
			
		||||
		"@vue/compiler-sfc": "3.2.47",
 | 
			
		||||
		"@vitejs/plugin-vue": "4.2.2",
 | 
			
		||||
		"@vue-macros/reactivity-transform": "0.3.6",
 | 
			
		||||
		"@vue/compiler-sfc": "3.3.1",
 | 
			
		||||
		"autosize": "5.0.2",
 | 
			
		||||
		"blurhash": "2.0.5",
 | 
			
		||||
		"broadcast-channel": "4.20.2",
 | 
			
		||||
@@ -34,14 +34,14 @@
 | 
			
		||||
		"chartjs-chart-matrix": "2.0.1",
 | 
			
		||||
		"chartjs-plugin-gradient": "0.6.1",
 | 
			
		||||
		"chartjs-plugin-zoom": "2.0.1",
 | 
			
		||||
		"chromatic": "6.17.3",
 | 
			
		||||
		"compare-versions": "5.0.1",
 | 
			
		||||
		"chromatic": "6.17.4",
 | 
			
		||||
		"compare-versions": "5.0.3",
 | 
			
		||||
		"cropperjs": "2.0.0-beta.2",
 | 
			
		||||
		"date-fns": "2.30.0",
 | 
			
		||||
		"escape-regexp": "0.0.1",
 | 
			
		||||
		"eventemitter3": "5.0.1",
 | 
			
		||||
		"gsap": "3.11.5",
 | 
			
		||||
		"idb-keyval": "6.2.0",
 | 
			
		||||
		"idb-keyval": "6.2.1",
 | 
			
		||||
		"insert-text-at-cursor": "0.3.0",
 | 
			
		||||
		"is-file-animated": "1.0.2",
 | 
			
		||||
		"json5": "2.2.3",
 | 
			
		||||
@@ -53,7 +53,7 @@
 | 
			
		||||
		"punycode": "2.3.0",
 | 
			
		||||
		"querystring": "0.2.1",
 | 
			
		||||
		"rndstr": "1.0.0",
 | 
			
		||||
		"rollup": "3.21.3",
 | 
			
		||||
		"rollup": "3.21.6",
 | 
			
		||||
		"s-age": "1.1.2",
 | 
			
		||||
		"sanitize-html": "2.10.0",
 | 
			
		||||
		"sass": "1.62.1",
 | 
			
		||||
@@ -70,40 +70,40 @@
 | 
			
		||||
		"typescript": "5.0.4",
 | 
			
		||||
		"uuid": "9.0.0",
 | 
			
		||||
		"vanilla-tilt": "1.8.0",
 | 
			
		||||
		"vite": "4.3.4",
 | 
			
		||||
		"vue": "3.2.47",
 | 
			
		||||
		"vite": "4.3.5",
 | 
			
		||||
		"vue": "3.3.1",
 | 
			
		||||
		"vue-plyr": "7.0.0",
 | 
			
		||||
		"vue-prism-editor": "2.0.0-alpha.2",
 | 
			
		||||
		"vuedraggable": "next"
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@storybook/addon-actions": "7.0.7",
 | 
			
		||||
		"@storybook/addon-essentials": "7.0.7",
 | 
			
		||||
		"@storybook/addon-interactions": "7.0.7",
 | 
			
		||||
		"@storybook/addon-links": "7.0.7",
 | 
			
		||||
		"@storybook/addon-storysource": "7.0.7",
 | 
			
		||||
		"@storybook/addons": "7.0.7",
 | 
			
		||||
		"@storybook/blocks": "7.0.7",
 | 
			
		||||
		"@storybook/core-events": "7.0.7",
 | 
			
		||||
		"@storybook/addon-actions": "7.0.10",
 | 
			
		||||
		"@storybook/addon-essentials": "7.0.10",
 | 
			
		||||
		"@storybook/addon-interactions": "7.0.10",
 | 
			
		||||
		"@storybook/addon-links": "7.0.10",
 | 
			
		||||
		"@storybook/addon-storysource": "7.0.10",
 | 
			
		||||
		"@storybook/addons": "7.0.10",
 | 
			
		||||
		"@storybook/blocks": "7.0.10",
 | 
			
		||||
		"@storybook/core-events": "7.0.10",
 | 
			
		||||
		"@storybook/jest": "0.1.0",
 | 
			
		||||
		"@storybook/manager-api": "7.0.7",
 | 
			
		||||
		"@storybook/preview-api": "7.0.7",
 | 
			
		||||
		"@storybook/react": "7.0.7",
 | 
			
		||||
		"@storybook/react-vite": "7.0.7",
 | 
			
		||||
		"@storybook/manager-api": "7.0.10",
 | 
			
		||||
		"@storybook/preview-api": "7.0.10",
 | 
			
		||||
		"@storybook/react": "7.0.10",
 | 
			
		||||
		"@storybook/react-vite": "7.0.10",
 | 
			
		||||
		"@storybook/testing-library": "0.1.0",
 | 
			
		||||
		"@storybook/theming": "7.0.7",
 | 
			
		||||
		"@storybook/types": "7.0.7",
 | 
			
		||||
		"@storybook/vue3": "7.0.7",
 | 
			
		||||
		"@storybook/vue3-vite": "7.0.7",
 | 
			
		||||
		"@storybook/theming": "7.0.10",
 | 
			
		||||
		"@storybook/types": "7.0.10",
 | 
			
		||||
		"@storybook/vue3": "7.0.10",
 | 
			
		||||
		"@storybook/vue3-vite": "7.0.10",
 | 
			
		||||
		"@testing-library/jest-dom": "5.16.5",
 | 
			
		||||
		"@testing-library/vue": "7.0.0",
 | 
			
		||||
		"@types/escape-regexp": "0.0.1",
 | 
			
		||||
		"@types/estree": "1.0.1",
 | 
			
		||||
		"@types/gulp": "4.0.10",
 | 
			
		||||
		"@types/gulp-rename": "2.0.1",
 | 
			
		||||
		"@types/matter-js": "0.18.2",
 | 
			
		||||
		"@types/gulp-rename": "2.0.2",
 | 
			
		||||
		"@types/matter-js": "0.18.3",
 | 
			
		||||
		"@types/micromatch": "4.0.2",
 | 
			
		||||
		"@types/node": "18.16.3",
 | 
			
		||||
		"@types/node": "20.1.3",
 | 
			
		||||
		"@types/punycode": "2.1.0",
 | 
			
		||||
		"@types/sanitize-html": "2.9.0",
 | 
			
		||||
		"@types/seedrandom": "3.0.5",
 | 
			
		||||
@@ -113,19 +113,19 @@
 | 
			
		||||
		"@types/uuid": "9.0.1",
 | 
			
		||||
		"@types/websocket": "1.0.5",
 | 
			
		||||
		"@types/ws": "8.5.4",
 | 
			
		||||
		"@typescript-eslint/eslint-plugin": "5.59.2",
 | 
			
		||||
		"@typescript-eslint/parser": "5.59.2",
 | 
			
		||||
		"@vitest/coverage-c8": "0.30.1",
 | 
			
		||||
		"@vue/runtime-core": "3.2.47",
 | 
			
		||||
		"@typescript-eslint/eslint-plugin": "5.59.5",
 | 
			
		||||
		"@typescript-eslint/parser": "5.59.5",
 | 
			
		||||
		"@vitest/coverage-c8": "0.31.0",
 | 
			
		||||
		"@vue/runtime-core": "3.3.1",
 | 
			
		||||
		"astring": "1.8.4",
 | 
			
		||||
		"chokidar-cli": "3.0.0",
 | 
			
		||||
		"cross-env": "7.0.3",
 | 
			
		||||
		"cypress": "12.11.0",
 | 
			
		||||
		"eslint": "8.39.0",
 | 
			
		||||
		"cypress": "12.12.0",
 | 
			
		||||
		"eslint": "8.40.0",
 | 
			
		||||
		"eslint-plugin-import": "2.27.5",
 | 
			
		||||
		"eslint-plugin-vue": "9.11.0",
 | 
			
		||||
		"eslint-plugin-vue": "9.12.0",
 | 
			
		||||
		"fast-glob": "3.2.12",
 | 
			
		||||
		"happy-dom": "9.10.2",
 | 
			
		||||
		"happy-dom": "9.16.0",
 | 
			
		||||
		"micromatch": "3.1.10",
 | 
			
		||||
		"msw": "1.2.1",
 | 
			
		||||
		"msw-storybook-addon": "1.8.0",
 | 
			
		||||
@@ -133,13 +133,13 @@
 | 
			
		||||
		"react": "18.2.0",
 | 
			
		||||
		"react-dom": "18.2.0",
 | 
			
		||||
		"start-server-and-test": "2.0.0",
 | 
			
		||||
		"storybook": "7.0.7",
 | 
			
		||||
		"storybook": "7.0.10",
 | 
			
		||||
		"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
 | 
			
		||||
		"summaly": "github:misskey-dev/summaly",
 | 
			
		||||
		"vite-plugin-turbosnap": "1.0.2",
 | 
			
		||||
		"vitest": "0.30.1",
 | 
			
		||||
		"vitest": "0.31.0",
 | 
			
		||||
		"vitest-fetch-mock": "0.2.2",
 | 
			
		||||
		"vue-eslint-parser": "9.1.1",
 | 
			
		||||
		"vue-tsc": "1.6.3"
 | 
			
		||||
		"vue-eslint-parser": "9.2.1",
 | 
			
		||||
		"vue-tsc": "1.6.4"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -52,9 +52,12 @@
 | 
			
		||||
 | 
			
		||||
	<MkFoldableSection class="item">
 | 
			
		||||
		<template #header>Retention rate</template>
 | 
			
		||||
		<div class="_panel" :class="$style.retention">
 | 
			
		||||
		<div class="_panel" :class="$style.retentionHeatmap">
 | 
			
		||||
			<MkRetentionHeatmap/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="_panel" :class="$style.retentionLine">
 | 
			
		||||
			<MkRetentionLineChart/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</MkFoldableSection>
 | 
			
		||||
 | 
			
		||||
	<MkFoldableSection class="item">
 | 
			
		||||
@@ -86,6 +89,7 @@ import { i18n } from '@/i18n';
 | 
			
		||||
import MkHeatmap from '@/components/MkHeatmap.vue';
 | 
			
		||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
 | 
			
		||||
import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
 | 
			
		||||
import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
 | 
			
		||||
import { initChart } from '@/scripts/init-chart';
 | 
			
		||||
 | 
			
		||||
initChart();
 | 
			
		||||
@@ -202,7 +206,12 @@ onMounted(() => {
 | 
			
		||||
	margin-bottom: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.retention {
 | 
			
		||||
.retentionHeatmap {
 | 
			
		||||
	padding: 16px;
 | 
			
		||||
	margin-bottom: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.retentionLine {
 | 
			
		||||
	padding: 16px;
 | 
			
		||||
	margin-bottom: 16px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div :class="[$style.root, { [$style.children]: depth > 1 }]">
 | 
			
		||||
	<div :class="$style.main">
 | 
			
		||||
		<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
 | 
			
		||||
		<MkAvatar :class="$style.avatar" :user="note.user" link preview/>
 | 
			
		||||
		<div :class="$style.body">
 | 
			
		||||
			<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
 | 
			
		||||
@@ -62,6 +63,7 @@ if (props.detail) {
 | 
			
		||||
.root {
 | 
			
		||||
	padding: 16px 32px;
 | 
			
		||||
	font-size: 0.9em;
 | 
			
		||||
	position: relative;
 | 
			
		||||
 | 
			
		||||
	&.children {
 | 
			
		||||
		padding: 10px 0 0 16px;
 | 
			
		||||
@@ -73,6 +75,16 @@ if (props.detail) {
 | 
			
		||||
	display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.colorBar {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 8px;
 | 
			
		||||
	left: 8px;
 | 
			
		||||
	width: 5px;
 | 
			
		||||
	height: calc(100% - 8px);
 | 
			
		||||
	border-radius: 999px;
 | 
			
		||||
	pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.avatar {
 | 
			
		||||
	flex-shrink: 0;
 | 
			
		||||
	display: block;
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ async function renderChart() {
 | 
			
		||||
 | 
			
		||||
	let raw = await os.api('retention', { });
 | 
			
		||||
 | 
			
		||||
	raw = raw.slice(0, maxDays);
 | 
			
		||||
	raw = raw.slice(0, maxDays + 1);
 | 
			
		||||
 | 
			
		||||
	const data = [];
 | 
			
		||||
	for (const record of raw) {
 | 
			
		||||
@@ -90,8 +90,13 @@ async function renderChart() {
 | 
			
		||||
				borderRadius: 3,
 | 
			
		||||
				backgroundColor(c) {
 | 
			
		||||
					const value = c.dataset.data[c.dataIndex].v;
 | 
			
		||||
					const a = value / max(c.dataset.data[c.dataIndex].y);
 | 
			
		||||
					return alpha(color, a);
 | 
			
		||||
					const m = max(c.dataset.data[c.dataIndex].y);
 | 
			
		||||
					if (m === 0) {
 | 
			
		||||
						return alpha(color, 0);
 | 
			
		||||
					} else {
 | 
			
		||||
						const a = value / m;
 | 
			
		||||
						return alpha(color, a);
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				fill: true,
 | 
			
		||||
				width(c) {
 | 
			
		||||
@@ -129,6 +134,10 @@ async function renderChart() {
 | 
			
		||||
						autoSkip: false,
 | 
			
		||||
						callback: (value, index, values) => value,
 | 
			
		||||
					},
 | 
			
		||||
					title: {
 | 
			
		||||
						display: true,
 | 
			
		||||
						text: 'Days later',
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				y: {
 | 
			
		||||
					type: 'time',
 | 
			
		||||
@@ -166,7 +175,12 @@ async function renderChart() {
 | 
			
		||||
						},
 | 
			
		||||
						label(context) {
 | 
			
		||||
							const v = context.dataset.data[context.dataIndex];
 | 
			
		||||
							return [`Active: ${v.v} (${Math.round((v.v / max(v.y)) * 100)}%)`];
 | 
			
		||||
							const m = max(v.y);
 | 
			
		||||
							if (m === 0) {
 | 
			
		||||
								return [`Active: ${v.v} (-%)`];
 | 
			
		||||
							} else {
 | 
			
		||||
								return [`Active: ${v.v} (${Math.round((v.v / m) * 100)}%)`];
 | 
			
		||||
							}
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					//mode: 'index',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										130
									
								
								packages/frontend/src/components/MkRetentionLineChart.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								packages/frontend/src/components/MkRetentionLineChart.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
			
		||||
<template>
 | 
			
		||||
<canvas ref="chartEl"></canvas>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted, shallowRef } from 'vue';
 | 
			
		||||
import { Chart } from 'chart.js';
 | 
			
		||||
import tinycolor from 'tinycolor2';
 | 
			
		||||
import { defaultStore } from '@/store';
 | 
			
		||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip';
 | 
			
		||||
import { chartVLine } from '@/scripts/chart-vline';
 | 
			
		||||
import { alpha } from '@/scripts/color';
 | 
			
		||||
import { initChart } from '@/scripts/init-chart';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
 | 
			
		||||
initChart();
 | 
			
		||||
 | 
			
		||||
const chartEl = shallowRef<HTMLCanvasElement>(null);
 | 
			
		||||
 | 
			
		||||
const { handler: externalTooltipHandler } = useChartTooltip();
 | 
			
		||||
 | 
			
		||||
let chartInstance: Chart;
 | 
			
		||||
 | 
			
		||||
const getYYYYMMDD = (date: Date) => {
 | 
			
		||||
	const y = date.getFullYear().toString().padStart(2, '0');
 | 
			
		||||
	const m = (date.getMonth() + 1).toString().padStart(2, '0');
 | 
			
		||||
	const d = date.getDate().toString().padStart(2, '0');
 | 
			
		||||
	return `${y}/${m}/${d}`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getDate = (ymd: string) => {
 | 
			
		||||
	const [y, m, d] = ymd.split('-').map(x => parseInt(x, 10));
 | 
			
		||||
	const date = new Date(y, m + 1, d, 0, 0, 0, 0);
 | 
			
		||||
	return date;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
	let raw = await os.api('retention', { });
 | 
			
		||||
 | 
			
		||||
	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 | 
			
		||||
 | 
			
		||||
	const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent'));
 | 
			
		||||
	const color = accent.toHex();
 | 
			
		||||
 | 
			
		||||
	chartInstance = new Chart(chartEl.value, {
 | 
			
		||||
		type: 'line',
 | 
			
		||||
		data: {
 | 
			
		||||
			labels: [],
 | 
			
		||||
			datasets: raw.map((record, i) => ({
 | 
			
		||||
				label: getYYYYMMDD(new Date(record.createdAt)),
 | 
			
		||||
				pointRadius: 0,
 | 
			
		||||
				borderWidth: 2,
 | 
			
		||||
				borderJoinStyle: 'round',
 | 
			
		||||
				borderColor: alpha(color, Math.min(1, (raw.length - (i - 1)) / raw.length)),
 | 
			
		||||
				fill: false,
 | 
			
		||||
				tension: 0.4,
 | 
			
		||||
				data: [{
 | 
			
		||||
					x: '0',
 | 
			
		||||
					y: 100,
 | 
			
		||||
					d: getYYYYMMDD(new Date(record.createdAt)),
 | 
			
		||||
				}, ...Object.entries(record.data).sort((a, b) => getDate(a[0]) > getDate(b[0]) ? 1 : -1).map(([k, v], i) => ({
 | 
			
		||||
					x: (i + 1).toString(),
 | 
			
		||||
					y: (v / record.users) * 100,
 | 
			
		||||
					d: getYYYYMMDD(new Date(record.createdAt)),
 | 
			
		||||
				}))],
 | 
			
		||||
			})),
 | 
			
		||||
		},
 | 
			
		||||
		options: {
 | 
			
		||||
			aspectRatio: 2.5,
 | 
			
		||||
			layout: {
 | 
			
		||||
				padding: {
 | 
			
		||||
					left: 0,
 | 
			
		||||
					right: 0,
 | 
			
		||||
					top: 0,
 | 
			
		||||
					bottom: 0,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			scales: {
 | 
			
		||||
				x: {
 | 
			
		||||
					title: {
 | 
			
		||||
						display: true,
 | 
			
		||||
						text: 'Days later',
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				y: {
 | 
			
		||||
					title: {
 | 
			
		||||
						display: true,
 | 
			
		||||
						text: 'Rate (%)',
 | 
			
		||||
					},
 | 
			
		||||
					ticks: {
 | 
			
		||||
						callback: (value, index, values) => value + '%',
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			interaction: {
 | 
			
		||||
				intersect: false,
 | 
			
		||||
			},
 | 
			
		||||
			plugins: {
 | 
			
		||||
				legend: {
 | 
			
		||||
					display: false,
 | 
			
		||||
				},
 | 
			
		||||
				tooltip: {
 | 
			
		||||
					enabled: false,
 | 
			
		||||
					callbacks: {
 | 
			
		||||
						title(context) {
 | 
			
		||||
							const v = context[0].dataset.data[context[0].dataIndex];
 | 
			
		||||
							return `${v.x} days later`;
 | 
			
		||||
						},
 | 
			
		||||
						label(context) {
 | 
			
		||||
							const v = context.dataset.data[context.dataIndex];
 | 
			
		||||
							const p = Math.round(v.y) + '%';
 | 
			
		||||
							return `${v.d} ${p}`;
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					mode: 'index',
 | 
			
		||||
					animation: {
 | 
			
		||||
						duration: 0,
 | 
			
		||||
					},
 | 
			
		||||
					external: externalTooltipHandler,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		plugins: [chartVLine(vLineColor)],
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -40,10 +40,6 @@ import * as os from '@/os';
 | 
			
		||||
import { $i } from '@/account';
 | 
			
		||||
import MkPagination from '@/components/MkPagination.vue';
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
	(ev: 'done'): void;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };
 | 
			
		||||
 | 
			
		||||
const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
 | 
			
		||||
import { StoryObj } from '@storybook/vue3';
 | 
			
		||||
import MkUserSetupDialog_Privacy from './MkUserSetupDialog.Privacy.vue';
 | 
			
		||||
export const Default = {
 | 
			
		||||
	render(args) {
 | 
			
		||||
		return {
 | 
			
		||||
			components: {
 | 
			
		||||
				MkUserSetupDialog_Privacy,
 | 
			
		||||
			},
 | 
			
		||||
			setup() {
 | 
			
		||||
				return {
 | 
			
		||||
					args,
 | 
			
		||||
				};
 | 
			
		||||
			},
 | 
			
		||||
			computed: {
 | 
			
		||||
				props() {
 | 
			
		||||
					return {
 | 
			
		||||
						...this.args,
 | 
			
		||||
					};
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			template: '<MkUserSetupDialog_Privacy v-bind="props" />',
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	args: {
 | 
			
		||||
		
 | 
			
		||||
	},
 | 
			
		||||
	parameters: {
 | 
			
		||||
		layout: 'centered',
 | 
			
		||||
	},
 | 
			
		||||
} satisfies StoryObj<typeof MkUserSetupDialog_Privacy>;
 | 
			
		||||
@@ -0,0 +1,64 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="_gaps">
 | 
			
		||||
	<MkInfo>{{ i18n.ts._initialAccountSetting.theseSettingsCanEditLater }}</MkInfo>
 | 
			
		||||
 | 
			
		||||
	<MkFolder>
 | 
			
		||||
		<template #label>{{ i18n.ts.makeFollowManuallyApprove }}</template>
 | 
			
		||||
		<template #suffix>{{ isLocked ? i18n.ts.on : i18n.ts.off }}</template>
 | 
			
		||||
 | 
			
		||||
		<MkSwitch v-model="isLocked">{{ i18n.ts.makeFollowManuallyApprove }}<template #caption>{{ i18n.ts.lockedAccountInfo }}</template></MkSwitch>
 | 
			
		||||
	</MkFolder>
 | 
			
		||||
 | 
			
		||||
	<MkFolder>
 | 
			
		||||
		<template #label>{{ i18n.ts.hideOnlineStatus }}</template>
 | 
			
		||||
		<template #suffix>{{ hideOnlineStatus ? i18n.ts.on : i18n.ts.off }}</template>
 | 
			
		||||
 | 
			
		||||
		<MkSwitch v-model="hideOnlineStatus">{{ i18n.ts.hideOnlineStatus }}<template #caption>{{ i18n.ts.hideOnlineStatusDescription }}</template></MkSwitch>
 | 
			
		||||
	</MkFolder>
 | 
			
		||||
 | 
			
		||||
	<MkFolder>
 | 
			
		||||
		<template #label>{{ i18n.ts.noCrawle }}</template>
 | 
			
		||||
		<template #suffix>{{ noCrawle ? i18n.ts.on : i18n.ts.off }}</template>
 | 
			
		||||
 | 
			
		||||
		<MkSwitch v-model="noCrawle">{{ i18n.ts.noCrawle }}<template #caption>{{ i18n.ts.noCrawleDescription }}</template></MkSwitch>
 | 
			
		||||
	</MkFolder>
 | 
			
		||||
 | 
			
		||||
	<MkFolder>
 | 
			
		||||
		<template #label>{{ i18n.ts.preventAiLearning }}</template>
 | 
			
		||||
		<template #suffix>{{ preventAiLearning ? i18n.ts.on : i18n.ts.off }}</template>
 | 
			
		||||
 | 
			
		||||
		<MkSwitch v-model="preventAiLearning">{{ i18n.ts.preventAiLearning }}<template #caption>{{ i18n.ts.preventAiLearningDescription }}</template></MkSwitch>
 | 
			
		||||
	</MkFolder>
 | 
			
		||||
 | 
			
		||||
	<MkInfo>{{ i18n.ts._initialAccountSetting.youCanEditMoreSettingsInSettingsPageLater }}</MkInfo>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, ref, watch } from 'vue';
 | 
			
		||||
import { instance } from '@/instance';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import MkSwitch from '@/components/MkSwitch.vue';
 | 
			
		||||
import MkInfo from '@/components/MkInfo.vue';
 | 
			
		||||
import MkFolder from '@/components/MkFolder.vue';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import { $i } from '@/account';
 | 
			
		||||
 | 
			
		||||
let isLocked = ref(false);
 | 
			
		||||
let hideOnlineStatus = ref(false);
 | 
			
		||||
let noCrawle = ref(false);
 | 
			
		||||
let preventAiLearning = ref(true);
 | 
			
		||||
 | 
			
		||||
watch([isLocked, hideOnlineStatus, noCrawle, preventAiLearning], () => {
 | 
			
		||||
	os.api('i/update', {
 | 
			
		||||
		isLocked: !!isLocked.value,
 | 
			
		||||
		hideOnlineStatus: !!hideOnlineStatus.value,
 | 
			
		||||
		noCrawle: !!noCrawle.value,
 | 
			
		||||
		preventAiLearning: !!preventAiLearning.value,
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -37,10 +37,6 @@ import { chooseFileFromPc } from '@/scripts/select-file';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import { $i } from '@/account';
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
	(ev: 'done'): void;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const name = ref('');
 | 
			
		||||
const description = ref('');
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,17 @@
 | 
			
		||||
	@close="close(true)"
 | 
			
		||||
	@closed="emit('closed')"
 | 
			
		||||
>
 | 
			
		||||
	<template #header>{{ i18n.ts.initialAccountSetting }}</template>
 | 
			
		||||
	<template v-if="page === 1" #header>{{ i18n.ts._initialAccountSetting.profileSetting }}</template>
 | 
			
		||||
	<template v-else-if="page === 2" #header>{{ i18n.ts._initialAccountSetting.privacySetting }}</template>
 | 
			
		||||
	<template v-else-if="page === 3" #header>{{ i18n.ts.follow }}</template>
 | 
			
		||||
	<template v-else-if="page === 4" #header>{{ i18n.ts.pushNotification }}</template>
 | 
			
		||||
	<template v-else-if="page === 5" #header>{{ i18n.ts.done }}</template>
 | 
			
		||||
	<template v-else #header>{{ i18n.ts.initialAccountSetting }}</template>
 | 
			
		||||
 | 
			
		||||
	<div style="overflow-x: clip;">
 | 
			
		||||
		<div :class="$style.progressBar">
 | 
			
		||||
			<div :class="$style.progressBarValue" :style="{ width: `${(page / 5) * 100}%` }"></div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<Transition
 | 
			
		||||
			mode="out-in"
 | 
			
		||||
			:enter-active-class="$style.transition_x_enterActive"
 | 
			
		||||
@@ -40,12 +48,22 @@
 | 
			
		||||
			<template v-else-if="page === 2">
 | 
			
		||||
				<div style="height: 100cqh; overflow: auto;">
 | 
			
		||||
					<MkSpacer :margin-min="20" :margin-max="28">
 | 
			
		||||
						<XFollow/>
 | 
			
		||||
						<XPrivacy/>
 | 
			
		||||
						<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
 | 
			
		||||
					</MkSpacer>
 | 
			
		||||
				</div>
 | 
			
		||||
			</template>
 | 
			
		||||
			<template v-else-if="page === 3">
 | 
			
		||||
				<div style="height: 100cqh; overflow: auto;">
 | 
			
		||||
					<MkSpacer :margin-min="20" :margin-max="28">
 | 
			
		||||
						<XFollow/>
 | 
			
		||||
					</MkSpacer>
 | 
			
		||||
					<div :class="$style.pageFooter">
 | 
			
		||||
						<MkButton primary rounded gradate style="margin: 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</template>
 | 
			
		||||
			<template v-else-if="page === 4">
 | 
			
		||||
				<div :class="$style.centerPage">
 | 
			
		||||
					<MkSpacer :margin-min="20" :margin-max="28">
 | 
			
		||||
						<div class="_gaps" style="text-align: center;">
 | 
			
		||||
@@ -58,7 +76,7 @@
 | 
			
		||||
					</MkSpacer>
 | 
			
		||||
				</div>
 | 
			
		||||
			</template>
 | 
			
		||||
			<template v-else-if="page === 4">
 | 
			
		||||
			<template v-else-if="page === 5">
 | 
			
		||||
				<div :class="$style.centerPage">
 | 
			
		||||
					<MkSpacer :margin-min="20" :margin-max="28">
 | 
			
		||||
						<div class="_gaps" style="text-align: center;">
 | 
			
		||||
@@ -87,6 +105,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import XProfile from '@/components/MkUserSetupDialog.Profile.vue';
 | 
			
		||||
import XFollow from '@/components/MkUserSetupDialog.Follow.vue';
 | 
			
		||||
import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import { instance } from '@/instance';
 | 
			
		||||
import { host } from '@/config';
 | 
			
		||||
@@ -134,6 +153,21 @@ async function close(skip: boolean) {
 | 
			
		||||
	transform: translateX(-50px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.progressBar {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	top: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	z-index: 10;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	height: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.progressBarValue {
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
 | 
			
		||||
	transition: all 0.5s cubic-bezier(0,.5,.5,1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.centerPage {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	justify-content: center;
 | 
			
		||||
@@ -142,4 +176,14 @@ async function close(skip: boolean) {
 | 
			
		||||
	padding-bottom: 30px;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pageFooter {
 | 
			
		||||
	position: sticky;
 | 
			
		||||
	bottom: 0;
 | 
			
		||||
	left: 0;
 | 
			
		||||
	padding: 12px;
 | 
			
		||||
	border-top: solid 0.5px var(--divider);
 | 
			
		||||
	-webkit-backdrop-filter: var(--blur, blur(15px));
 | 
			
		||||
	backdrop-filter: var(--blur, blur(15px));
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="lzyxtsnt">
 | 
			
		||||
	<ImgWithBlurhash v-if="image" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :cover="false"/>
 | 
			
		||||
<div>
 | 
			
		||||
	<ImgWithBlurhash v-if="image" style="max-width: 100%;" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :width="image.properties.width" :height="image.properties.height" :cover="false"/>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -17,11 +17,3 @@ const props = defineProps<{
 | 
			
		||||
 | 
			
		||||
const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.lzyxtsnt {
 | 
			
		||||
	> img {
 | 
			
		||||
		max-width: 100%;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -238,6 +238,7 @@ const patrons = [
 | 
			
		||||
	'ずも',
 | 
			
		||||
	'binvinyl',
 | 
			
		||||
	'渡志郎',
 | 
			
		||||
	'ぷーざ',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure'));
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@
 | 
			
		||||
					</MkInput>
 | 
			
		||||
					<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
 | 
			
		||||
				</div>
 | 
			
		||||
				<MkNotes v-if="searchPagination" :key="searchQuery" :pagination="searchPagination"/>
 | 
			
		||||
				<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</MkSpacer>
 | 
			
		||||
@@ -93,6 +93,7 @@ let channel = $ref(null);
 | 
			
		||||
let favorited = $ref(false);
 | 
			
		||||
let searchQuery = $ref('');
 | 
			
		||||
let searchPagination = $ref();
 | 
			
		||||
let searchKey = $ref('');
 | 
			
		||||
const featuredPagination = $computed(() => ({
 | 
			
		||||
	endpoint: 'notes/featured' as const,
 | 
			
		||||
	limit: 10,
 | 
			
		||||
@@ -149,10 +150,12 @@ async function search() {
 | 
			
		||||
		endpoint: 'notes/search',
 | 
			
		||||
		limit: 10,
 | 
			
		||||
		params: {
 | 
			
		||||
			query: searchQuery,
 | 
			
		||||
			query: query,
 | 
			
		||||
			channelId: channel.id,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	searchKey = query;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const headerActions = $computed(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@ async function choose() {
 | 
			
		||||
		file = fileResponse[0];
 | 
			
		||||
		emit('update:modelValue', {
 | 
			
		||||
			...props.modelValue,
 | 
			
		||||
			fileId: fileResponse.id,
 | 
			
		||||
			fileId: file.id,
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										98
									
								
								packages/frontend/src/pages/search.note.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								packages/frontend/src/pages/search.note.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="_gaps">
 | 
			
		||||
	<div class="_gaps">
 | 
			
		||||
		<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
 | 
			
		||||
			<template #prefix><i class="ti ti-search"></i></template>
 | 
			
		||||
		</MkInput>
 | 
			
		||||
		<MkFolder>
 | 
			
		||||
			<template #label>{{ i18n.ts.options }}</template>
 | 
			
		||||
 | 
			
		||||
			<MkFolder>
 | 
			
		||||
				<template #label>{{ i18n.ts.specifyUser }}</template>
 | 
			
		||||
				<template v-if="user" #suffix>@{{ user.username }}</template>
 | 
			
		||||
 | 
			
		||||
				<div style="text-align: center;" class="_gaps">
 | 
			
		||||
					<div v-if="user">@{{ user.username }}</div>
 | 
			
		||||
					<div>
 | 
			
		||||
						<MkButton v-if="user == null" primary rounded inline @click="selectUser">{{ i18n.ts.selectUser }}</MkButton>
 | 
			
		||||
						<MkButton v-else danger rounded inline @click="user = null">{{ i18n.ts.remove }}</MkButton>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</MkFolder>
 | 
			
		||||
		</MkFolder>
 | 
			
		||||
		<div>
 | 
			
		||||
			<MkButton large primary gradate rounded style="margin: 0 auto;" @click="search">{{ i18n.ts.search }}</MkButton>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<MkFoldableSection v-if="notePagination">
 | 
			
		||||
		<template #header>{{ i18n.ts.searchResult }}</template>
 | 
			
		||||
		<MkNotes :key="key" :pagination="notePagination"/>
 | 
			
		||||
	</MkFoldableSection>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, onMounted } from 'vue';
 | 
			
		||||
import MkNotes from '@/components/MkNotes.vue';
 | 
			
		||||
import MkInput from '@/components/MkInput.vue';
 | 
			
		||||
import MkRadios from '@/components/MkRadios.vue';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
 | 
			
		||||
import { $i } from '@/account';
 | 
			
		||||
import { instance } from '@/instance';
 | 
			
		||||
import MkInfo from '@/components/MkInfo.vue';
 | 
			
		||||
import { useRouter } from '@/router';
 | 
			
		||||
import MkFolder from '@/components/MkFolder.vue';
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
 | 
			
		||||
let key = $ref(0);
 | 
			
		||||
let searchQuery = $ref('');
 | 
			
		||||
let searchOrigin = $ref('combined');
 | 
			
		||||
let notePagination = $ref();
 | 
			
		||||
let user = $ref(null);
 | 
			
		||||
 | 
			
		||||
function selectUser() {
 | 
			
		||||
	os.selectUser().then(_user => {
 | 
			
		||||
		user = _user;
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function search() {
 | 
			
		||||
	const query = searchQuery.toString().trim();
 | 
			
		||||
 | 
			
		||||
	if (query == null || query === '') return;
 | 
			
		||||
 | 
			
		||||
	if (query.startsWith('https://')) {
 | 
			
		||||
		const promise = os.api('ap/show', {
 | 
			
		||||
			uri: query,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
 | 
			
		||||
 | 
			
		||||
		const res = await promise;
 | 
			
		||||
 | 
			
		||||
		if (res.type === 'User') {
 | 
			
		||||
			router.push(`/@${res.object.username}@${res.object.host}`);
 | 
			
		||||
		} else if (res.type === 'Note') {
 | 
			
		||||
			router.push(`/notes/${res.object.id}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	notePagination = {
 | 
			
		||||
		endpoint: 'notes/search',
 | 
			
		||||
		limit: 10,
 | 
			
		||||
		params: {
 | 
			
		||||
			query: searchQuery,
 | 
			
		||||
			userId: user ? user.id : null,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	key++;
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										77
									
								
								packages/frontend/src/pages/search.user.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								packages/frontend/src/pages/search.user.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="_gaps">
 | 
			
		||||
	<div class="_gaps">
 | 
			
		||||
		<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
 | 
			
		||||
			<template #prefix><i class="ti ti-search"></i></template>
 | 
			
		||||
		</MkInput>
 | 
			
		||||
		<MkRadios v-model="searchOrigin" @update:model-value="search()">
 | 
			
		||||
			<option value="combined">{{ i18n.ts.all }}</option>
 | 
			
		||||
			<option value="local">{{ i18n.ts.local }}</option>
 | 
			
		||||
			<option value="remote">{{ i18n.ts.remote }}</option>
 | 
			
		||||
		</MkRadios>
 | 
			
		||||
		<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
	<MkFoldableSection v-if="userPagination">
 | 
			
		||||
		<template #header>{{ i18n.ts.searchResult }}</template>
 | 
			
		||||
		<MkUserList :key="key" :pagination="userPagination"/>
 | 
			
		||||
	</MkFoldableSection>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, defineAsyncComponent, onMounted } from 'vue';
 | 
			
		||||
import MkUserList from '@/components/MkUserList.vue';
 | 
			
		||||
import MkInput from '@/components/MkInput.vue';
 | 
			
		||||
import MkRadios from '@/components/MkRadios.vue';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
 | 
			
		||||
import { $i } from '@/account';
 | 
			
		||||
import { instance } from '@/instance';
 | 
			
		||||
import MkInfo from '@/components/MkInfo.vue';
 | 
			
		||||
import { useRouter } from '@/router';
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
 | 
			
		||||
let key = $ref('');
 | 
			
		||||
let searchQuery = $ref('');
 | 
			
		||||
let searchOrigin = $ref('combined');
 | 
			
		||||
let userPagination = $ref();
 | 
			
		||||
 | 
			
		||||
async function search() {
 | 
			
		||||
	const query = searchQuery.toString().trim();
 | 
			
		||||
 | 
			
		||||
	if (query == null || query === '') return;
 | 
			
		||||
 | 
			
		||||
	if (query.startsWith('https://')) {
 | 
			
		||||
		const promise = os.api('ap/show', {
 | 
			
		||||
			uri: query,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
 | 
			
		||||
 | 
			
		||||
		const res = await promise;
 | 
			
		||||
 | 
			
		||||
		if (res.type === 'User') {
 | 
			
		||||
			router.push(`/@${res.object.username}@${res.object.host}`);
 | 
			
		||||
		} else if (res.type === 'Note') {
 | 
			
		||||
			router.push(`/notes/${res.object.id}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userPagination = {
 | 
			
		||||
		endpoint: 'users/search',
 | 
			
		||||
		limit: 10,
 | 
			
		||||
		params: {
 | 
			
		||||
			query: searchQuery,
 | 
			
		||||
			origin: searchOrigin,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	key = query;
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,133 +1,38 @@
 | 
			
		||||
<template>
 | 
			
		||||
<MkStickyContainer>
 | 
			
		||||
	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 | 
			
		||||
	<MkSpacer v-if="tab === 'note'" :content-max="800">
 | 
			
		||||
		<div v-if="notesSearchAvailable" class="_gaps">
 | 
			
		||||
			<div class="_gaps">
 | 
			
		||||
				<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
 | 
			
		||||
					<template #prefix><i class="ti ti-search"></i></template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
				<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<MkFoldableSection v-if="notePagination">
 | 
			
		||||
				<template #header>{{ i18n.ts.searchResult }}</template>
 | 
			
		||||
				<MkNotes :key="key" :pagination="notePagination"/>
 | 
			
		||||
			</MkFoldableSection>
 | 
			
		||||
	<MkSpacer v-if="tab === 'note'" :content-max="800">
 | 
			
		||||
		<div v-if="notesSearchAvailable">
 | 
			
		||||
			<XNote/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div v-else>
 | 
			
		||||
			<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
 | 
			
		||||
		</div>
 | 
			
		||||
	</MkSpacer>
 | 
			
		||||
	<MkSpacer v-else-if="tab === 'user'" :content-max="800">
 | 
			
		||||
		<div class="_gaps">
 | 
			
		||||
			<div class="_gaps">
 | 
			
		||||
				<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
 | 
			
		||||
					<template #prefix><i class="ti ti-search"></i></template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
				<MkRadios v-model="searchOrigin" @update:model-value="search()">
 | 
			
		||||
					<option value="combined">{{ i18n.ts.all }}</option>
 | 
			
		||||
					<option value="local">{{ i18n.ts.local }}</option>
 | 
			
		||||
					<option value="remote">{{ i18n.ts.remote }}</option>
 | 
			
		||||
				</MkRadios>
 | 
			
		||||
				<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<MkFoldableSection v-if="userPagination">
 | 
			
		||||
				<template #header>{{ i18n.ts.searchResult }}</template>
 | 
			
		||||
				<MkUserList :key="key" :pagination="userPagination"/>
 | 
			
		||||
			</MkFoldableSection>
 | 
			
		||||
		</div>
 | 
			
		||||
	<MkSpacer v-else-if="tab === 'user'" :content-max="800">
 | 
			
		||||
		<XUser/>
 | 
			
		||||
	</MkSpacer>
 | 
			
		||||
</MkStickyContainer>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, onMounted } from 'vue';
 | 
			
		||||
import MkNotes from '@/components/MkNotes.vue';
 | 
			
		||||
import MkUserList from '@/components/MkUserList.vue';
 | 
			
		||||
import MkInput from '@/components/MkInput.vue';
 | 
			
		||||
import MkRadios from '@/components/MkRadios.vue';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import { computed, defineAsyncComponent, onMounted } from 'vue';
 | 
			
		||||
import { i18n } from '@/i18n';
 | 
			
		||||
import { definePageMetadata } from '@/scripts/page-metadata';
 | 
			
		||||
import * as os from '@/os';
 | 
			
		||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
 | 
			
		||||
import { $i } from '@/account';
 | 
			
		||||
import { instance } from '@/instance';
 | 
			
		||||
import MkInfo from '@/components/MkInfo.vue';
 | 
			
		||||
import { useRouter } from '@/router';
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const XNote = defineAsyncComponent(() => import('./search.note.vue'));
 | 
			
		||||
const XUser = defineAsyncComponent(() => import('./search.user.vue'));
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
	query: string;
 | 
			
		||||
	channel?: string;
 | 
			
		||||
	type?: string;
 | 
			
		||||
	origin?: string;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
let key = $ref('');
 | 
			
		||||
let tab = $ref('note');
 | 
			
		||||
let searchQuery = $ref('');
 | 
			
		||||
let searchOrigin = $ref('combined');
 | 
			
		||||
let notePagination = $ref();
 | 
			
		||||
let userPagination = $ref();
 | 
			
		||||
 | 
			
		||||
const notesSearchAvailable = (($i == null && instance.policies.canSearchNotes) || ($i != null && $i.policies.canSearchNotes));
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
	tab = props.type ?? 'note';
 | 
			
		||||
	searchQuery = props.query ?? '';
 | 
			
		||||
	searchOrigin = props.origin ?? 'combined';
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
async function search() {
 | 
			
		||||
	const query = searchQuery.toString().trim();
 | 
			
		||||
 | 
			
		||||
	if (query == null || query === '') return;
 | 
			
		||||
 | 
			
		||||
	if (query.startsWith('https://')) {
 | 
			
		||||
		const promise = os.api('ap/show', {
 | 
			
		||||
			uri: query,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
 | 
			
		||||
 | 
			
		||||
		const res = await promise;
 | 
			
		||||
 | 
			
		||||
		if (res.type === 'User') {
 | 
			
		||||
			router.push(`/@${res.object.username}@${res.object.host}`);
 | 
			
		||||
		} else if (res.type === 'Note') {
 | 
			
		||||
			router.push(`/notes/${res.object.id}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (tab === 'note') {
 | 
			
		||||
		notePagination = {
 | 
			
		||||
			endpoint: 'notes/search',
 | 
			
		||||
			limit: 10,
 | 
			
		||||
			params: {
 | 
			
		||||
				query: searchQuery,
 | 
			
		||||
				channelId: props.channel,
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
	} else if (tab === 'user') {
 | 
			
		||||
		userPagination = {
 | 
			
		||||
			endpoint: 'users/search',
 | 
			
		||||
			limit: 10,
 | 
			
		||||
			params: {
 | 
			
		||||
				query: searchQuery,
 | 
			
		||||
				origin: searchOrigin,
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	key = query;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const headerActions = $computed(() => []);
 | 
			
		||||
 | 
			
		||||
const headerTabs = $computed(() => [{
 | 
			
		||||
@@ -141,7 +46,7 @@ const headerTabs = $computed(() => [{
 | 
			
		||||
}]);
 | 
			
		||||
 | 
			
		||||
definePageMetadata(computed(() => ({
 | 
			
		||||
	title: searchQuery ? i18n.t('searchWith', { q: searchQuery }) : i18n.ts.search,
 | 
			
		||||
	title: i18n.ts.search,
 | 
			
		||||
	icon: 'ti ti-search',
 | 
			
		||||
})));
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,9 @@
 | 
			
		||||
		{{ i18n.ts.noCrawle }}
 | 
			
		||||
		<template #caption>{{ i18n.ts.noCrawleDescription }}</template>
 | 
			
		||||
	</MkSwitch>
 | 
			
		||||
	<MkSwitch v-model="preventAiLarning" @update:model-value="save()">
 | 
			
		||||
		{{ i18n.ts.preventAiLarning }}<span class="_beta">{{ i18n.ts.beta }}</span>
 | 
			
		||||
		<template #caption>{{ i18n.ts.preventAiLarningDescription }}</template>
 | 
			
		||||
	<MkSwitch v-model="preventAiLearning" @update:model-value="save()">
 | 
			
		||||
		{{ i18n.ts.preventAiLearning }}<span class="_beta">{{ i18n.ts.beta }}</span>
 | 
			
		||||
		<template #caption>{{ i18n.ts.preventAiLearningDescription }}</template>
 | 
			
		||||
	</MkSwitch>
 | 
			
		||||
	<MkSwitch v-model="isExplorable" @update:model-value="save()">
 | 
			
		||||
		{{ i18n.ts.makeExplorable }}
 | 
			
		||||
@@ -75,7 +75,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
 | 
			
		||||
let isLocked = $ref($i.isLocked);
 | 
			
		||||
let autoAcceptFollowed = $ref($i.autoAcceptFollowed);
 | 
			
		||||
let noCrawle = $ref($i.noCrawle);
 | 
			
		||||
let preventAiLarning = $ref($i.preventAiLarning);
 | 
			
		||||
let preventAiLearning = $ref($i.preventAiLearning);
 | 
			
		||||
let isExplorable = $ref($i.isExplorable);
 | 
			
		||||
let hideOnlineStatus = $ref($i.hideOnlineStatus);
 | 
			
		||||
let publicReactions = $ref($i.publicReactions);
 | 
			
		||||
@@ -91,7 +91,7 @@ function save() {
 | 
			
		||||
		isLocked: !!isLocked,
 | 
			
		||||
		autoAcceptFollowed: !!autoAcceptFollowed,
 | 
			
		||||
		noCrawle: !!noCrawle,
 | 
			
		||||
		preventAiLarning: !!preventAiLarning,
 | 
			
		||||
		preventAiLearning: !!preventAiLearning,
 | 
			
		||||
		isExplorable: !!isExplorable,
 | 
			
		||||
		hideOnlineStatus: !!hideOnlineStatus,
 | 
			
		||||
		publicReactions: !!publicReactions,
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,9 @@
 | 
			
		||||
		"@swc/jest": "0.2.26",
 | 
			
		||||
		"@types/jest": "29.5.1",
 | 
			
		||||
		"@types/node": "18.16.3",
 | 
			
		||||
		"@typescript-eslint/eslint-plugin": "5.59.2",
 | 
			
		||||
		"@typescript-eslint/parser": "5.59.2",
 | 
			
		||||
		"eslint": "8.39.0",
 | 
			
		||||
		"@typescript-eslint/eslint-plugin": "5.59.5",
 | 
			
		||||
		"@typescript-eslint/parser": "5.59.5",
 | 
			
		||||
		"eslint": "8.40.0",
 | 
			
		||||
		"jest": "29.5.0",
 | 
			
		||||
		"jest-fetch-mock": "3.0.3",
 | 
			
		||||
		"jest-websocket-mock": "2.4.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,9 @@
 | 
			
		||||
		"misskey-js": "workspace:*"
 | 
			
		||||
	},
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@typescript-eslint/parser": "5.59.2",
 | 
			
		||||
		"@typescript-eslint/parser": "5.59.5",
 | 
			
		||||
		"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
 | 
			
		||||
		"eslint": "8.39.0",
 | 
			
		||||
		"eslint": "8.40.0",
 | 
			
		||||
		"eslint-plugin-import": "2.27.5",
 | 
			
		||||
		"typescript": "5.0.4"
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1923
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1923
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user