Merge tag '13.12.2' into merge-upstream

This commit is contained in:
riku6460 2023-05-12 12:58:00 +09:00
commit bc8cc006da
No known key found for this signature in database
GPG key ID: 27414FA27DB94CF6
61 changed files with 1681 additions and 1199 deletions

View file

@ -103,6 +103,7 @@ redis:
# port: 7700 # port: 7700
# apiKey: '' # apiKey: ''
# ssl: true # ssl: true
# index: ''
# ┌───────────────┐ # ┌───────────────┐
#───┘ ID generation └─────────────────────────────────────────── #───┘ ID generation └───────────────────────────────────────────

View file

@ -103,6 +103,7 @@ redis:
# port: 7700 # port: 7700
# apiKey: '' # apiKey: ''
# ssl: true # ssl: true
# index: ''
# ┌───────────────┐ # ┌───────────────┐
#───┘ ID generation └─────────────────────────────────────────── #───┘ ID generation └───────────────────────────────────────────

View file

@ -103,6 +103,7 @@ redis:
# port: 7700 # port: 7700
# apiKey: '' # apiKey: ''
# ssl: true # ssl: true
# index: ''
# ┌───────────────┐ # ┌───────────────┐
#───┘ ID generation └─────────────────────────────────────────── #───┘ ID generation └───────────────────────────────────────────

View file

@ -14,14 +14,27 @@
## 13.12.2 ## 13.12.2
## NOTE
Meilisearchの設定に`index`が必要になりました。値はMisskeyサーバーのホスト名にすることをお勧めします(アルファベット、ハイフン、アンダーバーのみ使用可能)。例: `misskey-io`
過去に作成された`notes`インデックスは、`<index名>---notes`にリネームが必要です。例: `misskey-io---notes`
### General ### General
- 投稿したコンテンツのAIによる学習を軽減するオプションを追加 - 投稿したコンテンツのAIによる学習を軽減するオプションを追加
### Client ### Client
- ユーザーを指定してのノート検索が可能に
- アカウント初期設定ウィザードにプライバシー設定を追加
- リテンション率チャートに折れ線グラフを追加
- Fix: ブラーエフェクトを有効にしている状態で高負荷になる問題を修正 - Fix: ブラーエフェクトを有効にしている状態で高負荷になる問題を修正
- Fix: Pageにおいて画像ブロックに画像を設定できない問題を修正
- Fix: カラーバーがリプライには表示されないのを修正
- Fix: チャンネル内の検索ボックスが挙動不審な問題を修正
- Fix: リテンションチャートのレンダリングを修正
- Fix: リアクションエフェクトのレンダリングの問題を修正
### Server ### Server
- センシティブワードの登録にAnd、正規表現が使用できるようになりました。 - センシティブワードの登録にAnd、正規表現が使用できるようになりました。
- Fix: ひとつのMeilisearchサーバーを複数のMisskeyサーバーで使えない問題を修正
## 13.12.1 ## 13.12.1

View file

@ -124,6 +124,7 @@ redis:
# port: 7700 # port: 7700
# apiKey: '' # apiKey: ''
# ssl: true # ssl: true
# index: ''
# ┌───────────────┐ # ┌───────────────┐
#───┘ ID generation └─────────────────────────────────────────── #───┘ ID generation └───────────────────────────────────────────

View file

@ -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();
// フォローはスキップ // フォローはスキップ
cy.get('[data-cy-user-setup-continue]').click(); cy.get('[data-cy-user-setup-continue]').click();

View file

@ -19,6 +19,7 @@ noNotes: "لم يُعثر على أية ملاحظات"
noNotifications: "ليس هناك أية اشعارات" noNotifications: "ليس هناك أية اشعارات"
instance: "مثيل الخادم" instance: "مثيل الخادم"
settings: "الاعدادات" settings: "الاعدادات"
notificationSettings: "إعدادات الإشعارات"
basicSettings: "الاعدادات الأساسية" basicSettings: "الاعدادات الأساسية"
otherSettings: "إعدادات أخرى" otherSettings: "إعدادات أخرى"
openInWindow: "افتح في نافذة جديدة" openInWindow: "افتح في نافذة جديدة"
@ -127,6 +128,7 @@ unblockConfirm: "أمتأكد من إلغاء حجب هذا الحساب؟"
suspendConfirm: "أمتأكد من تعليق الحساب؟" suspendConfirm: "أمتأكد من تعليق الحساب؟"
unsuspendConfirm: "أمتأكد من إلغاء تعليق؟" unsuspendConfirm: "أمتأكد من إلغاء تعليق؟"
selectList: "اختر قائمة" selectList: "اختر قائمة"
selectChannel: "اختر قناة"
selectAntenna: "اختر هوائيًا" selectAntenna: "اختر هوائيًا"
selectWidget: "اختر ودجة" selectWidget: "اختر ودجة"
editWidgets: "عدّل الودجات" editWidgets: "عدّل الودجات"
@ -250,6 +252,9 @@ noMoreHistory: "لا يوجد المزيد من التاريخ"
startMessaging: "ابدأ محادثة" startMessaging: "ابدأ محادثة"
nUsersRead: "قرأه {n}" nUsersRead: "قرأه {n}"
agreeTo: "اوافق على {0}" agreeTo: "اوافق على {0}"
agree: "أقبل"
basicNotesBeforeCreateAccount: "ملاحظات مهمة"
termsOfService: "شروط الخدمة"
start: "البداية" start: "البداية"
home: "الرئيسي" home: "الرئيسي"
remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد." remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد."
@ -379,6 +384,8 @@ about: "عن"
aboutMisskey: "عن Misskey" aboutMisskey: "عن Misskey"
administrator: "المدير" administrator: "المدير"
token: "الرمز المميز" token: "الرمز المميز"
2fa: "الاستيثاق بعاملَيْن"
totp: "تطبيق استيثاق"
moderator: "مشرِف" moderator: "مشرِف"
moderation: "الإشراف" moderation: "الإشراف"
nUsersMentioned: "{n} مستخدمين أُشير إليهم" nUsersMentioned: "{n} مستخدمين أُشير إليهم"
@ -506,6 +513,7 @@ userSuspended: "عُلق هذا المستخدم."
userSilenced: "كُتم هذا المستخدم." userSilenced: "كُتم هذا المستخدم."
yourAccountSuspendedTitle: "هذا الحساب معلق" yourAccountSuspendedTitle: "هذا الحساب معلق"
yourAccountSuspendedDescription: "عُلق الحساب بسبب انتهاك شروط خدمة المثيل و ما شابه. إذا أردت معرفة التفصيل تواصل مع مدير المثيل. رجاءً لا تنشئ حساب جديد." yourAccountSuspendedDescription: "عُلق الحساب بسبب انتهاك شروط خدمة المثيل و ما شابه. إذا أردت معرفة التفصيل تواصل مع مدير المثيل. رجاءً لا تنشئ حساب جديد."
accountDeleted: "حُذف الحساب"
menu: "القائمة" menu: "القائمة"
divider: "فاصل" divider: "فاصل"
addItem: "إضافة عنصر" addItem: "إضافة عنصر"

View file

@ -990,6 +990,7 @@ rolesAssignedToMe: "Mir zugewiesene Rollen"
resetPasswordConfirm: "Wirklich Passwort zurücksetzen?" resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
sensitiveWords: "Sensible Wörter" 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." 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." notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
license: "Lizenz" license: "Lizenz"
unfavoriteConfirm: "Wirklich aus Favoriten entfernen?" unfavoriteConfirm: "Wirklich aus Favoriten entfernen?"
@ -1038,13 +1039,16 @@ thisChannelArchived: "Dieser Kanal wurde archiviert."
displayOfNote: "Anzeige von Notizen" displayOfNote: "Anzeige von Notizen"
initialAccountSetting: "Kontoeinrichtung" initialAccountSetting: "Kontoeinrichtung"
youFollowing: "Gefolgt" youFollowing: "Gefolgt"
preventAiLarning: "Verwendung in machinellem Lernen (AI/KI) ablehnen" preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive 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." 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: _initialAccountSetting:
accountCreated: "Dein Konto wurde erfolgreich erstellt!" accountCreated: "Dein Konto wurde erfolgreich erstellt!"
letsStartAccountSetup: "Lass uns nun dein Konto einrichten." letsStartAccountSetup: "Lass uns nun dein Konto einrichten."
letsFillYourProfile: "Lass uns zuerst dein Profil einrichten." letsFillYourProfile: "Lass uns zuerst dein Profil einrichten."
profileSetting: "Profileinstellungen" profileSetting: "Profileinstellungen"
privacySetting: "Privatsphäreneinstellungen"
theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern." theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern."
youCanEditMoreSettingsInSettingsPageLater: "In den Einstellungen findest du noch viele weitere Optionen. Schau dort später mal vorbei." 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." followUsers: "Folge zuerst ein paar Nutzern, um deine Chronik zu füllen."

View file

@ -990,6 +990,7 @@ rolesAssignedToMe: "Roles assigned to me"
resetPasswordConfirm: "Really reset your password?" resetPasswordConfirm: "Really reset your password?"
sensitiveWords: "Sensitive words" 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." 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." notesSearchNotAvailable: "Note search is unavailable."
license: "License" license: "License"
unfavoriteConfirm: "Really remove from favorites?" unfavoriteConfirm: "Really remove from favorites?"
@ -1038,13 +1039,16 @@ thisChannelArchived: "This channel has been archived."
displayOfNote: "Note display" displayOfNote: "Note display"
initialAccountSetting: "Profile setup" initialAccountSetting: "Profile setup"
youFollowing: "Followed" youFollowing: "Followed"
preventAiLarning: "Reject usage in Machine Learning (AI)" preventAiLearning: "Reject usage in Machine Learning (Generative 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." 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: _initialAccountSetting:
accountCreated: "Your account was successfully created!" accountCreated: "Your account was successfully created!"
letsStartAccountSetup: "For starters, let's set up your profile." letsStartAccountSetup: "For starters, let's set up your profile."
letsFillYourProfile: "First, let's set up your profile." letsFillYourProfile: "First, let's set up your profile."
profileSetting: "Profile settings" profileSetting: "Profile settings"
privacySetting: "Privacy settings"
theseSettingsCanEditLater: "You can always change these settings later." 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." 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." followUsers: "Try following some users that interest you to build up your timeline."
@ -1324,7 +1328,7 @@ _role:
isConditionalRole: "This is a conditional role." isConditionalRole: "This is a conditional role."
isPublic: "Public 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." 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" policies: "Policies"
baseRole: "Role template" baseRole: "Role template"
useBaseValue: "Use role template value" useBaseValue: "Use role template value"

View file

@ -993,6 +993,7 @@ accountMigration: "Migración de cuenta"
accountMoved: "Este usuario se ha mudado a una nueva cuenta:" accountMoved: "Este usuario se ha mudado a una nueva cuenta:"
horizontal: "Horizontal" horizontal: "Horizontal"
youFollowing: "Siguiendo" youFollowing: "Siguiendo"
options: "Opción"
_accountMigration: _accountMigration:
moveFrom: "Trasladar de otra cuenta a ésta" moveFrom: "Trasladar de otra cuenta a ésta"
moveFromLabel: "Cuenta desde la que se realiza el traslado:" moveFromLabel: "Cuenta desde la que se realiza el traslado:"

View file

@ -452,6 +452,7 @@ native: "Natif"
disableDrawer: "Les menus ne s'affichent pas dans le tiroir" disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
noHistory: "Pas d'historique" noHistory: "Pas d'historique"
signinHistory: "Historique de connexion" signinHistory: "Historique de connexion"
enableAdvancedMfm: "Activer la MFM avancée"
doing: "En cours..." doing: "En cours..."
category: "Catégorie" category: "Catégorie"
tags: "Étiquettes" tags: "Étiquettes"
@ -846,6 +847,7 @@ rateLimitExceeded: "Limite de taux dépassée"
cropImage: "Recadrer l'image" cropImage: "Recadrer l'image"
cropImageAsk: "Voulez-vous recadrer cette image ?" cropImageAsk: "Voulez-vous recadrer cette image ?"
cropYes: "Rogner" cropYes: "Rogner"
cropNo: "Utiliser en l'état"
file: "Fichiers" file: "Fichiers"
recentNHours: "Dernières {n} heures" recentNHours: "Dernières {n} heures"
recentNDays: "Derniers {n} jours" recentNDays: "Derniers {n} jours"
@ -912,6 +914,7 @@ color: "Couleur"
manageCustomEmojis: "Gestion des émojis personnalisés" manageCustomEmojis: "Gestion des émojis personnalisés"
preset: "Préréglage" preset: "Préréglage"
selectFromPresets: "Sélectionner à partir des préréglages" selectFromPresets: "Sélectionner à partir des préréglages"
thisPostMayBeAnnoyingCancel: "Annuler"
license: "Licence" license: "Licence"
video: "Vidéo" video: "Vidéo"
videos: "Vidéos" videos: "Vidéos"
@ -925,6 +928,7 @@ leftBottom: "En bas à gauche"
rightBottom: "En bas à droite" rightBottom: "En bas à droite"
vertical: "Vertical" vertical: "Vertical"
horizontal: "Latéral" horizontal: "Latéral"
serverRules: "Règles du serveur"
youFollowing: "Abonné·e" youFollowing: "Abonné·e"
_achievements: _achievements:
_types: _types:
@ -934,10 +938,13 @@ _achievements:
_notes100000: _notes100000:
title: "ALL YOUR NOTE ARE BELONG TO US" title: "ALL YOUR NOTE ARE BELONG TO US"
_login3: _login3:
title: "Débutant "
description: "Se connecter pour un total de 3 jours" description: "Se connecter pour un total de 3 jours"
_login7: _login7:
title: "Débutant Ⅱ"
description: "Se connecter pour un total de 7 jours" description: "Se connecter pour un total de 7 jours"
_login15: _login15:
title: "Débutant Ⅲ"
description: "Se connecter pour un total de 15 jours" description: "Se connecter pour un total de 15 jours"
_login30: _login30:
description: "Se connecter pour un total de 30 jours" description: "Se connecter pour un total de 30 jours"
@ -945,6 +952,22 @@ _achievements:
description: "Se connecter pour un total de 60 jours" description: "Se connecter pour un total de 60 jours"
_login100: _login100:
description: "Se connecter pour un total de 100 jours" 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: _login1000:
flavor: "Merci d'utiliser Misskey !" flavor: "Merci d'utiliser Misskey !"
_markedAsCat: _markedAsCat:
@ -954,8 +977,12 @@ _achievements:
title: "Beaucoup d'amis" title: "Beaucoup d'amis"
_followers10: _followers10:
title: "Abonnez-moi !" title: "Abonnez-moi !"
_iLoveMisskey:
title: "Jadore Misskey"
_viewInstanceChart: _viewInstanceChart:
title: "Analyste" title: "Analyste"
_loggedInOnBirthday:
title: "Joyeux Anniversaire !"
_loggedInOnNewYearsDay: _loggedInOnNewYearsDay:
title: "Bonne année !" title: "Bonne année !"
_role: _role:

View file

@ -955,6 +955,7 @@ disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi
disableFederationOk: "Matikan federasi" disableFederationOk: "Matikan federasi"
horizontal: "Horisontal" horizontal: "Horisontal"
youFollowing: "Mengikuti" youFollowing: "Mengikuti"
options: "Opsi peran"
_achievements: _achievements:
earnedAt: "Terbuka pada" earnedAt: "Terbuka pada"
_types: _types:

View file

@ -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." pleaseAgreeAllToContinue: "Per continuare, occorre selezionare ed essere d'accordo su tutto."
continue: "Continua" continue: "Continua"
youFollowing: "Seguiti" youFollowing: "Seguiti"
options: "Opzioni del ruolo"
_serverRules: _serverRules:
description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio." description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio."
_accountMigration: _accountMigration:

View file

@ -689,7 +689,7 @@ no: "いいえ"
driveFilesCount: "ドライブのファイル数" driveFilesCount: "ドライブのファイル数"
driveUsage: "ドライブ使用量" driveUsage: "ドライブ使用量"
noCrawle: "クローラーによるインデックスを拒否" noCrawle: "クローラーによるインデックスを拒否"
noCrawleDescription: "検索エンジンにあなたのユーザーページ、ート、Pagesなどのコンテンツを登録(インデックス)しないよう要します。" noCrawleDescription: "外部の検索エンジンにあなたのユーザーページ、ート、Pagesなどのコンテンツを登録(インデックス)しないよう要します。"
lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。" lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。"
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする" alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
loadRawImages: "添付画像のサムネイルをオリジナル画質にする" loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
@ -832,6 +832,8 @@ breakFollow: "フォロワーを解除"
breakFollowConfirm: "フォロワー解除しますか?" breakFollowConfirm: "フォロワー解除しますか?"
itsOn: "オンになっています" itsOn: "オンになっています"
itsOff: "オフになっています" itsOff: "オフになっています"
on: "オン"
off: "オフ"
emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする" emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
unread: "未読" unread: "未読"
filter: "フィルタ" filter: "フィルタ"
@ -1039,14 +1041,17 @@ thisChannelArchived: "このチャンネルはアーカイブされています
displayOfNote: "ノートの表示" displayOfNote: "ノートの表示"
initialAccountSetting: "初期設定" initialAccountSetting: "初期設定"
youFollowing: "フォロー中" youFollowing: "フォロー中"
preventAiLarning: "AIによる学習を拒否" preventAiLearning: "生成AIによる学習を拒否"
preventAiLarningDescription: "投稿したートや画像などのコンテンツを学習の対象にしないようAIに要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されます。この機能は実験的であり、AIによる学習を完全に防止するものではありません。" preventAiLearningDescription: "外部の文章生成AIや画像生成AIに対して、投稿したートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。"
options: "オプション"
specifyUser: "ユーザー指定"
_initialAccountSetting: _initialAccountSetting:
accountCreated: "アカウントの作成が完了しました!" accountCreated: "アカウントの作成が完了しました!"
letsStartAccountSetup: "アカウントの初期設定を行いましょう。" letsStartAccountSetup: "アカウントの初期設定を行いましょう。"
letsFillYourProfile: "まずはあなたのプロフィールを設定しましょう。" letsFillYourProfile: "まずはあなたのプロフィールを設定しましょう。"
profileSetting: "プロフィール設定" profileSetting: "プロフィール設定"
privacySetting: "プライバシー設定"
theseSettingsCanEditLater: "これらの設定は後から変更できます。" theseSettingsCanEditLater: "これらの設定は後から変更できます。"
youCanEditMoreSettingsInSettingsPageLater: "この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。" youCanEditMoreSettingsInSettingsPageLater: "この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。"
followUsers: "タイムラインを構築するため、気になるユーザーをフォローしてみましょう。" followUsers: "タイムラインを構築するため、気になるユーザーをフォローしてみましょう。"

View file

@ -689,7 +689,7 @@ no: "あかん"
driveFilesCount: "ドライブのファイル数" driveFilesCount: "ドライブのファイル数"
driveUsage: "ドライブ使用量やで" driveUsage: "ドライブ使用量やで"
noCrawle: "クローラーによるインデックスを拒否するで" noCrawle: "クローラーによるインデックスを拒否するで"
noCrawleDescription: "検索エンジンにあんたのユーザーページ、ート、Pagesとかのコンテンツを登録(インデックス)せんように頼むで。邪魔すんねんやったら帰って〜。" noCrawleDescription: "検索エンジンにあんたのユーザーページ、ート、Pagesとかのコンテンツを登録(インデックス)せぇへんように頼むで。"
lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。" lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。"
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで" alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで"
loadRawImages: "添付画像のサムネイルをオリジナル画質にするで" loadRawImages: "添付画像のサムネイルをオリジナル画質にするで"
@ -990,6 +990,7 @@ rolesAssignedToMe: "自分に割り当てられたロール"
resetPasswordConfirm: "パスワード作り直すんでええな?" resetPasswordConfirm: "パスワード作り直すんでええな?"
sensitiveWords: "けったいな単語" sensitiveWords: "けったいな単語"
sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。" sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
notesSearchNotAvailable: "ノート検索は使われへんで。" notesSearchNotAvailable: "ノート検索は使われへんで。"
license: "ライセンス" license: "ライセンス"
unfavoriteConfirm: "ほんまに気に入らんの?" unfavoriteConfirm: "ほんまに気に入らんの?"
@ -1038,10 +1039,16 @@ thisChannelArchived: "このチャンネル、アーカイブされとるで。"
displayOfNote: "ノートの表示" displayOfNote: "ノートの表示"
initialAccountSetting: "初期設定" initialAccountSetting: "初期設定"
youFollowing: "フォロー中やで" youFollowing: "フォロー中やで"
preventAiLearning: "生成AIの学習に使わんといて"
preventAiLearningDescription: "他の文章生成AIとか画像生成AIに、投稿したートとか画像なんかを勝手に使わんように頼むで。具体的にはnoaiフラグをHTMLレスポンスに含めるんやけど、これ聞いてくれるんはAIの気分次第やから、使われる可能性もちょっとはあるな。"
options: "オプション"
specifyUser: "ユーザー指定"
_initialAccountSetting: _initialAccountSetting:
accountCreated: "アカウント作り終わったで。" accountCreated: "アカウント作り終わったで。"
letsStartAccountSetup: "アカウントの初期設定をしよか。"
letsFillYourProfile: "最初はあんたのプロフィールを設定しよか。" letsFillYourProfile: "最初はあんたのプロフィールを設定しよか。"
profileSetting: "プロフィール設定" profileSetting: "プロフィール設定"
privacySetting: "プライバシー設定"
theseSettingsCanEditLater: "この設定はあとから変えれるで。" theseSettingsCanEditLater: "この設定はあとから変えれるで。"
youCanEditMoreSettingsInSettingsPageLater: "これ以外にもいろんな設定を「設定」ページからできるで。後で確認してみてな。" youCanEditMoreSettingsInSettingsPageLater: "これ以外にもいろんな設定を「設定」ページからできるで。後で確認してみてな。"
followUsers: "タイムラインを構築するために、気になるユーザーをフォローしてみ。" followUsers: "タイムラインを構築するために、気になるユーザーをフォローしてみ。"
@ -1060,11 +1067,12 @@ _accountMigration:
moveTo: "このアカウントをさらのアカウントに引っ越すで" moveTo: "このアカウントをさらのアカウントに引っ越すで"
moveToLabel: "引っ越し先のアカウント:" moveToLabel: "引っ越し先のアカウント:"
moveCannotBeUndone: "アカウントを移行すると、取り消すことはできへんくなります。" 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" moveAccountHowTo: "アカウントの引っ越しには、まず引っ越し先のアカウントで自分のアカウントに対しエイリアスを作成しなはれや。\nエイリアス作成した後、引っ越し先のアカウントを次のように入力してくれへんか:@username@server.example.com"
startMigration: "引っ越しする" startMigration: "引っ越しする"
migrationConfirm: "ほんまにこのアカウントを {account} に引っ越すんか?一回引っ越してもうたら取り消されへんし、二度とこのアカウントを元に戻されへんくなるで。\nそれと、引っ越し先のアカウントでエイリアスが作れたかちゃんと確認しーや" migrationConfirm: "ほんまにこのアカウントを {account} に引っ越すんか?一回引っ越してもうたら取り消されへんし、二度とこのアカウントを元に戻されへんくなるで。\nそれと、引っ越し先のアカウントでエイリアスが作れたかちゃんと確認しーや"
movedAndCannotBeUndone: "\nアカウントはもう引っ越されてます。\n引っ越しを取り消すことはできまへん。" movedAndCannotBeUndone: "\nアカウントはもう引っ越されてます。\n引っ越しを取り消すことはできまへん。"
postMigrationNote: "このアカウントからのフォロー解除は移行操作から丸一日経ったら実行されんで。\nこのアカウントのフォロー・フォロワー数はどっちも0や。フォローの解除はされへんから、あんたのフォロワーはこのアカウントのフォロワー向けの投稿をこの後も見れるで。"
movedTo: "引っ越し先のアカウント:" movedTo: "引っ越し先のアカウント:"
_achievements: _achievements:
earnedAt: "貰った日ぃ" earnedAt: "貰った日ぃ"

View file

@ -1038,6 +1038,7 @@ thisChannelArchived: "이 채널은 아카이브되었습니다."
displayOfNote: "노트 표시" displayOfNote: "노트 표시"
initialAccountSetting: "초기 설정" initialAccountSetting: "초기 설정"
youFollowing: "팔로잉" youFollowing: "팔로잉"
options: "옵션"
_initialAccountSetting: _initialAccountSetting:
accountCreated: "계정 생성이 완료되었습니다!" accountCreated: "계정 생성이 완료되었습니다!"
letsStartAccountSetup: "계정의 초기 설정을 진행합니다." letsStartAccountSetup: "계정의 초기 설정을 진행합니다."

View file

@ -278,6 +278,7 @@ video: "Video"
videos: "Videoer" videos: "Videoer"
continue: "Fortsett" continue: "Fortsett"
youFollowing: "Følger" youFollowing: "Følger"
options: "Alternativ"
_initialAccountSetting: _initialAccountSetting:
theseSettingsCanEditLater: "Du kan endre disse innstillingene senere." theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
_achievements: _achievements:

View file

@ -1006,6 +1006,7 @@ videos: "Видео"
dataSaver: "Экономия трафика" dataSaver: "Экономия трафика"
horizontal: "Сбоку" horizontal: "Сбоку"
youFollowing: "Подписки" youFollowing: "Подписки"
options: "Настройки ролей"
_achievements: _achievements:
earnedAt: "Разблокировано в" earnedAt: "Разблокировано в"
_types: _types:

View file

@ -1031,6 +1031,7 @@ preservedUsernames: "ชื่อผู้ใช้ที่สงวนไว
preservedUsernamesDescription: "ลิสต์ชื่อผู้ใช้ที่จะสำรองโดยคั่นด้วยการแบ่งบรรทัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างการสร้างบัญชีตามปกติ บัญชีที่มีอยู่แล้วนั้นโดยใช้ชื่อผู้ใช้เหล่านี้จะไม่ได้รับผลกระทบอะไร" preservedUsernamesDescription: "ลิสต์ชื่อผู้ใช้ที่จะสำรองโดยคั่นด้วยการแบ่งบรรทัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างการสร้างบัญชีตามปกติ บัญชีที่มีอยู่แล้วนั้นโดยใช้ชื่อผู้ใช้เหล่านี้จะไม่ได้รับผลกระทบอะไร"
createNoteFromTheFile: "เรียบเรียงโน้ตจากไฟล์นี้" createNoteFromTheFile: "เรียบเรียงโน้ตจากไฟล์นี้"
youFollowing: "ติดตามแล้ว" youFollowing: "ติดตามแล้ว"
options: "ตัวเลือกบทบาท"
_serverRules: _serverRules:
description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ" description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
_accountMigration: _accountMigration:

View file

@ -1030,6 +1030,13 @@ continue: "继续"
preservedUsernames: "保留的用户名" preservedUsernames: "保留的用户名"
createNoteFromTheFile: "从文件创建帖子" createNoteFromTheFile: "从文件创建帖子"
youFollowing: "正在关注" youFollowing: "正在关注"
options: "选项"
_initialAccountSetting:
accountCreated: "账户创建完成了!"
letsStartAccountSetup: "来进行帐户的初始设置吧。"
letsFillYourProfile: "首先,来设定你的个人档案吧!"
profileSetting: "个人资料设置"
theseSettingsCanEditLater: "也可以在稍后修改这里的设置。"
_serverRules: _serverRules:
description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。" description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。"
_accountMigration: _accountMigration:
@ -1038,8 +1045,11 @@ _accountMigration:
moveFromDescription: "如果迁移时需要继承其他账户的关注者,请在此创造别名。此操作需要在实行迁移之前完成!请如已下输入需要迁移的账户:@person@instance.com" moveFromDescription: "如果迁移时需要继承其他账户的关注者,请在此创造别名。此操作需要在实行迁移之前完成!请如已下输入需要迁移的账户:@person@instance.com"
moveTo: "把这个账户迁移到新的账户" moveTo: "把这个账户迁移到新的账户"
moveToLabel: "迁移后的账户" moveToLabel: "迁移后的账户"
moveCannotBeUndone: "一旦迁移账户,就无法撤销。"
moveAccountDescription: "此操作无法取消。请先确认您已在迁移后的账户上,为此账户创造了别名。创造别名后,请如以下输入您的迁移后的账户:@person@instance.com" moveAccountDescription: "此操作无法取消。请先确认您已在迁移后的账户上,为此账户创造了别名。创造别名后,请如以下输入您的迁移后的账户:@person@instance.com"
startMigration: "迁移"
migrationConfirm: "确定要把此账户迁移到{account}吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时请确认迁移后的账户已创造别名。" migrationConfirm: "确定要把此账户迁移到{account}吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时请确认迁移后的账户已创造别名。"
movedAndCannotBeUndone: "该账户已被迁移。\n迁移操作无法撤销。"
movedTo: "迁移后的账户" movedTo: "迁移后的账户"
_achievements: _achievements:
earnedAt: "达成时间" earnedAt: "达成时间"
@ -1572,6 +1582,9 @@ _time:
minute: "分" minute: "分"
hour: "小时" hour: "小时"
day: "日" day: "日"
_timelineTutorial:
step3_1: "将想说的话发出去了吗?"
step3_2: "太棒了!现在你可以在你的时间线中看到刚刚发布的帖子了。"
_2fa: _2fa:
alreadyRegistered: "此设备已被注册" alreadyRegistered: "此设备已被注册"
registerTOTP: "开始设置认证应用" registerTOTP: "开始设置认证应用"

View file

@ -990,6 +990,7 @@ rolesAssignedToMe: "指派給自己的角色"
resetPasswordConfirm: "重設密碼?" resetPasswordConfirm: "重設密碼?"
sensitiveWords: "敏感詞" sensitiveWords: "敏感詞"
sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。" sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
sensitiveWordsDescription2: "用空格分隔關鍵詞構成AND格式用斜線包圍關鍵字構成正規表達式。"
notesSearchNotAvailable: "無法使用搜尋貼文功能。" notesSearchNotAvailable: "無法使用搜尋貼文功能。"
license: "授權" license: "授權"
unfavoriteConfirm: "要取消收錄我的最愛嗎?" unfavoriteConfirm: "要取消收錄我的最愛嗎?"
@ -1038,6 +1039,9 @@ thisChannelArchived: "這個頻道已被封存。"
displayOfNote: "顯示貼文" displayOfNote: "顯示貼文"
initialAccountSetting: "初始設定" initialAccountSetting: "初始設定"
youFollowing: "追隨中" youFollowing: "追隨中"
preventAiLearning: "拒絕接受產生式AI的學習"
preventAiLearningDescription: "要求外部的文章產生AI或圖像產生AI不以發布的貼文和圖像等內容為學習對象。這是透過在HTML響應中包含noai旗標來實現的但不能完全防止AI的學習因為這要看該AI是否遵守這個要求。"
options: "選項"
_initialAccountSetting: _initialAccountSetting:
accountCreated: "帳戶已建立完成!" accountCreated: "帳戶已建立完成!"
letsStartAccountSetup: "來進行帳戶的初始設定吧。" letsStartAccountSetup: "來進行帳戶的初始設定吧。"

View file

@ -56,11 +56,11 @@
"devDependencies": { "devDependencies": {
"@types/gulp": "4.0.10", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@typescript-eslint/eslint-plugin": "5.59.2", "@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.2", "@typescript-eslint/parser": "5.59.5",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "12.11.0", "cypress": "12.12.0",
"eslint": "8.39.0", "eslint": "8.40.0",
"start-server-and-test": "2.0.0" "start-server-and-test": "2.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {

View 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"`);
}
}

View file

@ -58,7 +58,7 @@
"@fastify/accepts": "4.1.0", "@fastify/accepts": "4.1.0",
"@fastify/cookie": "8.3.0", "@fastify/cookie": "8.3.0",
"@fastify/cors": "8.2.1", "@fastify/cors": "8.2.1",
"@fastify/http-proxy": "9.0.0", "@fastify/http-proxy": "9.1.0",
"@fastify/multipart": "7.6.0", "@fastify/multipart": "7.6.0",
"@fastify/static": "6.10.1", "@fastify/static": "6.10.1",
"@fastify/view": "7.4.1", "@fastify/view": "7.4.1",
@ -89,11 +89,11 @@
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"fastify": "4.17.0", "fastify": "4.17.0",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "18.3.0", "file-type": "18.4.0",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"form-data": "4.0.0", "form-data": "4.0.0",
"got": "12.6.0", "got": "12.6.0",
"happy-dom": "9.10.2", "happy-dom": "9.16.0",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"ioredis": "5.3.2", "ioredis": "5.3.2",
"ip-cidr": "3.1.0", "ip-cidr": "3.1.0",
@ -110,11 +110,11 @@
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.1", "node-fetch": "3.3.1",
"nodemailer": "6.9.1", "nodemailer": "6.9.2",
"nsfwjs": "2.4.2", "nsfwjs": "2.4.2",
"oauth": "0.10.0", "oauth": "0.10.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "9.1.1", "otpauth": "9.1.2",
"parse5": "7.1.2", "parse5": "7.1.2",
"pg": "8.10.0", "pg": "8.10.0",
"private-ip": "3.0.0", "private-ip": "3.0.0",
@ -149,7 +149,7 @@
"tsc-alias": "1.8.6", "tsc-alias": "1.8.6",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typeorm": "0.3.15", "typeorm": "0.3.16",
"typescript": "5.0.4", "typescript": "5.0.4",
"ulid": "2.3.0", "ulid": "2.3.0",
"unzipper": "0.10.11", "unzipper": "0.10.11",
@ -178,7 +178,7 @@
"@types/jsonld": "1.5.8", "@types/jsonld": "1.5.8",
"@types/jsrsasign": "10.5.8", "@types/jsrsasign": "10.5.8",
"@types/mime-types": "2.1.1", "@types/mime-types": "2.1.1",
"@types/node": "18.16.3", "@types/node": "20.1.3",
"@types/node-fetch": "3.0.3", "@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.7", "@types/nodemailer": "6.4.7",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
@ -191,7 +191,7 @@
"@types/redis": "4.0.11", "@types/redis": "4.0.11",
"@types/rename": "1.0.4", "@types/rename": "1.0.4",
"@types/sanitize-html": "2.9.0", "@types/sanitize-html": "2.9.0",
"@types/semver": "7.3.13", "@types/semver": "7.5.0",
"@types/sharp": "0.32.0", "@types/sharp": "0.32.0",
"@types/sinonjs__fake-timers": "8.1.2", "@types/sinonjs__fake-timers": "8.1.2",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
@ -202,11 +202,11 @@
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.59.2", "@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.2", "@typescript-eslint/parser": "5.59.5",
"aws-sdk-client-mock": "^2.1.1", "aws-sdk-client-mock": "2.1.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.39.0", "eslint": "8.40.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"execa": "6.1.0", "execa": "6.1.0",
"jest": "29.5.0", "jest": "29.5.0",

View file

@ -62,6 +62,7 @@ export type Source = {
port: string; port: string;
apiKey: string; apiKey: string;
ssl?: boolean; ssl?: boolean;
index: string;
}; };
proxy?: string; proxy?: string;

View file

@ -68,7 +68,7 @@ export class SearchService {
private idService: IdService, private idService: IdService,
) { ) {
if (meilisearch) { if (meilisearch) {
this.meilisearchNoteIndex = meilisearch.index('notes'); this.meilisearchNoteIndex = meilisearch.index(`${config.meilisearch!.index}---notes`);
this.meilisearchNoteIndex.updateSettings({ this.meilisearchNoteIndex.updateSettings({
searchableAttributes: [ searchableAttributes: [
'text', 'text',
@ -82,6 +82,7 @@ export class SearchService {
'userId', 'userId',
'userHost', 'userHost',
'channelId', 'channelId',
'tags',
], ],
typoTolerance: { typoTolerance: {
enabled: false, enabled: false,
@ -107,6 +108,7 @@ export class SearchService {
channelId: note.channelId, channelId: note.channelId,
cw: note.cw, cw: note.cw,
text: note.text, text: note.text,
tags: note.tags,
}], { }], {
primaryKey: 'id', primaryKey: 'id',
}); });

View file

@ -445,7 +445,7 @@ export class UserEntityService implements OnModuleInit {
carefulBot: profile!.carefulBot, carefulBot: profile!.carefulBot,
autoAcceptFollowed: profile!.autoAcceptFollowed, autoAcceptFollowed: profile!.autoAcceptFollowed,
noCrawle: profile!.noCrawle, noCrawle: profile!.noCrawle,
preventAiLarning: profile!.preventAiLarning, preventAiLearning: profile!.preventAiLearning,
isExplorable: user.isExplorable, isExplorable: user.isExplorable,
isDeleted: user.isDeleted, isDeleted: user.isDeleted,
hideOnlineStatus: user.hideOnlineStatus, hideOnlineStatus: user.hideOnlineStatus,

View file

@ -150,7 +150,7 @@ export class UserProfile {
@Column('boolean', { @Column('boolean', {
default: true, default: true,
}) })
public preventAiLarning: boolean; public preventAiLearning: boolean;
@Column('boolean', { @Column('boolean', {
default: false, default: false,

View file

@ -304,7 +304,7 @@ export const packedMeDetailedOnlySchema = {
type: 'boolean', type: 'boolean',
nullable: false, optional: false, nullable: false, optional: false,
}, },
preventAiLarning: { preventAiLearning: {
type: 'boolean', type: 'boolean',
nullable: false, optional: false, nullable: false, optional: false,
}, },

View file

@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
emailVerified: profile.emailVerified, emailVerified: profile.emailVerified,
autoAcceptFollowed: profile.autoAcceptFollowed, autoAcceptFollowed: profile.autoAcceptFollowed,
noCrawle: profile.noCrawle, noCrawle: profile.noCrawle,
preventAiLarning: profile.preventAiLarning, preventAiLearning: profile.preventAiLearning,
alwaysMarkNsfw: profile.alwaysMarkNsfw, alwaysMarkNsfw: profile.alwaysMarkNsfw,
autoSensitive: profile.autoSensitive, autoSensitive: profile.autoSensitive,
carefulBot: profile.carefulBot, carefulBot: profile.carefulBot,

View file

@ -138,7 +138,7 @@ export const paramDef = {
carefulBot: { type: 'boolean' }, carefulBot: { type: 'boolean' },
autoAcceptFollowed: { type: 'boolean' }, autoAcceptFollowed: { type: 'boolean' },
noCrawle: { type: 'boolean' }, noCrawle: { type: 'boolean' },
preventAiLarning: { type: 'boolean' }, preventAiLearning: { type: 'boolean' },
isBot: { type: 'boolean' }, isBot: { type: 'boolean' },
isCat: { type: 'boolean' }, isCat: { type: 'boolean' },
showTimelineReplies: { 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.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; 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.isCat === 'boolean') updates.isCat = ps.isCat;
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;

View file

@ -437,7 +437,7 @@ export class ClientServerService {
: []; : [];
reply.header('Cache-Control', 'public, max-age=15'); 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', 'noimageai');
reply.header('X-Robots-Tag', 'noai'); reply.header('X-Robots-Tag', 'noai');
} }
@ -485,7 +485,7 @@ export class ClientServerService {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId }); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
const meta = await this.metaService.fetch(); const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15'); 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', 'noimageai');
reply.header('X-Robots-Tag', 'noai'); reply.header('X-Robots-Tag', 'noai');
} }
@ -528,7 +528,7 @@ export class ClientServerService {
} else { } else {
reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); 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', 'noimageai');
reply.header('X-Robots-Tag', 'noai'); reply.header('X-Robots-Tag', 'noai');
} }
@ -556,7 +556,7 @@ export class ClientServerService {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId }); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
const meta = await this.metaService.fetch(); const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15'); 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', 'noimageai');
reply.header('X-Robots-Tag', 'noai'); reply.header('X-Robots-Tag', 'noai');
} }
@ -584,7 +584,7 @@ export class ClientServerService {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId }); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
const meta = await this.metaService.fetch(); const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15'); 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', 'noimageai');
reply.header('X-Robots-Tag', 'noai'); reply.header('X-Robots-Tag', 'noai');
} }
@ -610,7 +610,7 @@ export class ClientServerService {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId }); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
const meta = await this.metaService.fetch(); const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15'); 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', 'noimageai');
reply.header('X-Robots-Tag', 'noai'); reply.header('X-Robots-Tag', 'noai');
} }

View file

@ -21,7 +21,7 @@ block og
block meta block meta
if profile.noCrawle if profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLarning if profile.preventAiLearning
meta(name='robots' content='noimageai') meta(name='robots' content='noimageai')
meta(name='robots' content='noai') meta(name='robots' content='noai')

View file

@ -21,7 +21,7 @@ block og
block meta block meta
if profile.noCrawle if profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLarning if profile.preventAiLearning
meta(name='robots' content='noimageai') meta(name='robots' content='noimageai')
meta(name='robots' content='noai') meta(name='robots' content='noai')

View file

@ -21,7 +21,7 @@ block og
block meta block meta
if user.host || profile.noCrawle if user.host || profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLarning if profile.preventAiLearning
meta(name='robots' content='noimageai') meta(name='robots' content='noimageai')
meta(name='robots' content='noai') meta(name='robots' content='noai')

View file

@ -22,7 +22,7 @@ block og
block meta block meta
if user.host || isRenote || profile.noCrawle if user.host || isRenote || profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLarning if profile.preventAiLearning
meta(name='robots' content='noimageai') meta(name='robots' content='noimageai')
meta(name='robots' content='noai') meta(name='robots' content='noai')

View file

@ -21,7 +21,7 @@ block og
block meta block meta
if profile.noCrawle if profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLarning if profile.preventAiLearning
meta(name='robots' content='noimageai') meta(name='robots' content='noimageai')
meta(name='robots' content='noai') meta(name='robots' content='noai')

View file

@ -20,7 +20,7 @@ block og
block meta block meta
if user.host || profile.noCrawle if user.host || profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
if profile.preventAiLarning if profile.preventAiLearning
meta(name='robots' content='noimageai') meta(name='robots' content='noimageai')
meta(name='robots' content='noai') meta(name='robots' content='noai')

View file

@ -145,7 +145,7 @@ describe('ユーザー', () => {
carefulBot: user.carefulBot, carefulBot: user.carefulBot,
autoAcceptFollowed: user.autoAcceptFollowed, autoAcceptFollowed: user.autoAcceptFollowed,
noCrawle: user.noCrawle, noCrawle: user.noCrawle,
preventAiLarning: user.preventAiLarning, preventAiLearning: user.preventAiLearning,
isExplorable: user.isExplorable, isExplorable: user.isExplorable,
isDeleted: user.isDeleted, isDeleted: user.isDeleted,
hideOnlineStatus: user.hideOnlineStatus, hideOnlineStatus: user.hideOnlineStatus,
@ -391,7 +391,7 @@ describe('ユーザー', () => {
assert.strictEqual(response.carefulBot, false); assert.strictEqual(response.carefulBot, false);
assert.strictEqual(response.autoAcceptFollowed, true); assert.strictEqual(response.autoAcceptFollowed, true);
assert.strictEqual(response.noCrawle, false); assert.strictEqual(response.noCrawle, false);
assert.strictEqual(response.preventAiLarning, true); assert.strictEqual(response.preventAiLearning, true);
assert.strictEqual(response.isExplorable, true); assert.strictEqual(response.isExplorable, true);
assert.strictEqual(response.isDeleted, false); assert.strictEqual(response.isDeleted, false);
assert.strictEqual(response.hideOnlineStatus, false); assert.strictEqual(response.hideOnlineStatus, false);
@ -464,8 +464,8 @@ describe('ユーザー', () => {
{ parameters: (): object => ({ autoAcceptFollowed: false }) }, { parameters: (): object => ({ autoAcceptFollowed: false }) },
{ parameters: (): object => ({ noCrawle: true }) }, { parameters: (): object => ({ noCrawle: true }) },
{ parameters: (): object => ({ noCrawle: false }) }, { parameters: (): object => ({ noCrawle: false }) },
{ parameters: (): object => ({ preventAiLarning: false }) }, { parameters: (): object => ({ preventAiLearning: false }) },
{ parameters: (): object => ({ preventAiLarning: true }) }, { parameters: (): object => ({ preventAiLearning: true }) },
{ parameters: (): object => ({ isBot: true }) }, { parameters: (): object => ({ isBot: true }) },
{ parameters: (): object => ({ isBot: false }) }, { parameters: (): object => ({ isBot: false }) },
{ parameters: (): object => ({ isCat: true }) }, { parameters: (): object => ({ isCat: true }) },

View file

@ -45,7 +45,7 @@ fs.readFile(
micromatch(Array.from(modules), [ micromatch(Array.from(modules), [
'../../assets/**', '../../assets/**',
'../../fluent-emojis/**', '../../fluent-emojis/**',
'../../locales/**', '../../locales/ja-JP.yml',
'../../misskey-assets/**', '../../misskey-assets/**',
'assets/**', 'assets/**',
'public/**', 'public/**',

View file

@ -17,13 +17,13 @@
"@discordapp/twemoji": "14.1.2", "@discordapp/twemoji": "14.1.2",
"@rollup/plugin-alias": "5.0.0", "@rollup/plugin-alias": "5.0.0",
"@rollup/plugin-json": "6.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", "@rollup/pluginutils": "5.0.2",
"@syuilo/aiscript": "0.13.2", "@syuilo/aiscript": "0.13.2",
"@tabler/icons-webfont": "2.17.0", "@tabler/icons-webfont": "2.17.0",
"@vitejs/plugin-vue": "4.2.1", "@vitejs/plugin-vue": "4.2.2",
"@vue-macros/reactivity-transform": "^0.3.5", "@vue-macros/reactivity-transform": "0.3.6",
"@vue/compiler-sfc": "3.2.47", "@vue/compiler-sfc": "3.3.1",
"autosize": "5.0.2", "autosize": "5.0.2",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"broadcast-channel": "4.20.2", "broadcast-channel": "4.20.2",
@ -34,14 +34,14 @@
"chartjs-chart-matrix": "2.0.1", "chartjs-chart-matrix": "2.0.1",
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1", "chartjs-plugin-zoom": "2.0.1",
"chromatic": "6.17.3", "chromatic": "6.17.4",
"compare-versions": "5.0.1", "compare-versions": "5.0.3",
"cropperjs": "2.0.0-beta.2", "cropperjs": "2.0.0-beta.2",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eventemitter3": "5.0.1", "eventemitter3": "5.0.1",
"gsap": "3.11.5", "gsap": "3.11.5",
"idb-keyval": "6.2.0", "idb-keyval": "6.2.1",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"is-file-animated": "1.0.2", "is-file-animated": "1.0.2",
"json5": "2.2.3", "json5": "2.2.3",
@ -53,7 +53,7 @@
"punycode": "2.3.0", "punycode": "2.3.0",
"querystring": "0.2.1", "querystring": "0.2.1",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"rollup": "3.21.3", "rollup": "3.21.6",
"s-age": "1.1.2", "s-age": "1.1.2",
"sanitize-html": "2.10.0", "sanitize-html": "2.10.0",
"sass": "1.62.1", "sass": "1.62.1",
@ -70,40 +70,40 @@
"typescript": "5.0.4", "typescript": "5.0.4",
"uuid": "9.0.0", "uuid": "9.0.0",
"vanilla-tilt": "1.8.0", "vanilla-tilt": "1.8.0",
"vite": "4.3.4", "vite": "4.3.5",
"vue": "3.2.47", "vue": "3.3.1",
"vue-plyr": "7.0.0", "vue-plyr": "7.0.0",
"vue-prism-editor": "2.0.0-alpha.2", "vue-prism-editor": "2.0.0-alpha.2",
"vuedraggable": "next" "vuedraggable": "next"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-actions": "7.0.7", "@storybook/addon-actions": "7.0.10",
"@storybook/addon-essentials": "7.0.7", "@storybook/addon-essentials": "7.0.10",
"@storybook/addon-interactions": "7.0.7", "@storybook/addon-interactions": "7.0.10",
"@storybook/addon-links": "7.0.7", "@storybook/addon-links": "7.0.10",
"@storybook/addon-storysource": "7.0.7", "@storybook/addon-storysource": "7.0.10",
"@storybook/addons": "7.0.7", "@storybook/addons": "7.0.10",
"@storybook/blocks": "7.0.7", "@storybook/blocks": "7.0.10",
"@storybook/core-events": "7.0.7", "@storybook/core-events": "7.0.10",
"@storybook/jest": "0.1.0", "@storybook/jest": "0.1.0",
"@storybook/manager-api": "7.0.7", "@storybook/manager-api": "7.0.10",
"@storybook/preview-api": "7.0.7", "@storybook/preview-api": "7.0.10",
"@storybook/react": "7.0.7", "@storybook/react": "7.0.10",
"@storybook/react-vite": "7.0.7", "@storybook/react-vite": "7.0.10",
"@storybook/testing-library": "0.1.0", "@storybook/testing-library": "0.1.0",
"@storybook/theming": "7.0.7", "@storybook/theming": "7.0.10",
"@storybook/types": "7.0.7", "@storybook/types": "7.0.10",
"@storybook/vue3": "7.0.7", "@storybook/vue3": "7.0.10",
"@storybook/vue3-vite": "7.0.7", "@storybook/vue3-vite": "7.0.10",
"@testing-library/jest-dom": "5.16.5", "@testing-library/jest-dom": "5.16.5",
"@testing-library/vue": "7.0.0", "@testing-library/vue": "7.0.0",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/estree": "1.0.1", "@types/estree": "1.0.1",
"@types/gulp": "4.0.10", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.2",
"@types/matter-js": "0.18.2", "@types/matter-js": "0.18.3",
"@types/micromatch": "4.0.2", "@types/micromatch": "4.0.2",
"@types/node": "18.16.3", "@types/node": "20.1.3",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
"@types/sanitize-html": "2.9.0", "@types/sanitize-html": "2.9.0",
"@types/seedrandom": "3.0.5", "@types/seedrandom": "3.0.5",
@ -113,19 +113,19 @@
"@types/uuid": "9.0.1", "@types/uuid": "9.0.1",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.59.2", "@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.2", "@typescript-eslint/parser": "5.59.5",
"@vitest/coverage-c8": "0.30.1", "@vitest/coverage-c8": "0.31.0",
"@vue/runtime-core": "3.2.47", "@vue/runtime-core": "3.3.1",
"astring": "1.8.4", "astring": "1.8.4",
"chokidar-cli": "3.0.0", "chokidar-cli": "3.0.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "12.11.0", "cypress": "12.12.0",
"eslint": "8.39.0", "eslint": "8.40.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"eslint-plugin-vue": "9.11.0", "eslint-plugin-vue": "9.12.0",
"fast-glob": "3.2.12", "fast-glob": "3.2.12",
"happy-dom": "9.10.2", "happy-dom": "9.16.0",
"micromatch": "3.1.10", "micromatch": "3.1.10",
"msw": "1.2.1", "msw": "1.2.1",
"msw-storybook-addon": "1.8.0", "msw-storybook-addon": "1.8.0",
@ -133,13 +133,13 @@
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"start-server-and-test": "2.0.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", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"summaly": "github:misskey-dev/summaly", "summaly": "github:misskey-dev/summaly",
"vite-plugin-turbosnap": "1.0.2", "vite-plugin-turbosnap": "1.0.2",
"vitest": "0.30.1", "vitest": "0.31.0",
"vitest-fetch-mock": "0.2.2", "vitest-fetch-mock": "0.2.2",
"vue-eslint-parser": "9.1.1", "vue-eslint-parser": "9.2.1",
"vue-tsc": "1.6.3" "vue-tsc": "1.6.4"
} }
} }

View file

@ -52,9 +52,12 @@
<MkFoldableSection class="item"> <MkFoldableSection class="item">
<template #header>Retention rate</template> <template #header>Retention rate</template>
<div class="_panel" :class="$style.retention"> <div class="_panel" :class="$style.retentionHeatmap">
<MkRetentionHeatmap/> <MkRetentionHeatmap/>
</div> </div>
<div class="_panel" :class="$style.retentionLine">
<MkRetentionLineChart/>
</div>
</MkFoldableSection> </MkFoldableSection>
<MkFoldableSection class="item"> <MkFoldableSection class="item">
@ -86,6 +89,7 @@ import { i18n } from '@/i18n';
import MkHeatmap from '@/components/MkHeatmap.vue'; import MkHeatmap from '@/components/MkHeatmap.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue'; import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
import { initChart } from '@/scripts/init-chart'; import { initChart } from '@/scripts/init-chart';
initChart(); initChart();
@ -202,7 +206,12 @@ onMounted(() => {
margin-bottom: 16px; margin-bottom: 16px;
} }
.retention { .retentionHeatmap {
padding: 16px;
margin-bottom: 16px;
}
.retentionLine {
padding: 16px; padding: 16px;
margin-bottom: 16px; margin-bottom: 16px;
} }

View file

@ -1,6 +1,7 @@
<template> <template>
<div :class="[$style.root, { [$style.children]: depth > 1 }]"> <div :class="[$style.root, { [$style.children]: depth > 1 }]">
<div :class="$style.main"> <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/> <MkAvatar :class="$style.avatar" :user="note.user" link preview/>
<div :class="$style.body"> <div :class="$style.body">
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/> <MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
@ -62,6 +63,7 @@ if (props.detail) {
.root { .root {
padding: 16px 32px; padding: 16px 32px;
font-size: 0.9em; font-size: 0.9em;
position: relative;
&.children { &.children {
padding: 10px 0 0 16px; padding: 10px 0 0 16px;
@ -73,6 +75,16 @@ if (props.detail) {
display: flex; display: flex;
} }
.colorBar {
position: absolute;
top: 8px;
left: 8px;
width: 5px;
height: calc(100% - 8px);
border-radius: 999px;
pointer-events: none;
}
.avatar { .avatar {
flex-shrink: 0; flex-shrink: 0;
display: block; display: block;

View file

@ -40,7 +40,7 @@ async function renderChart() {
let raw = await os.api('retention', { }); let raw = await os.api('retention', { });
raw = raw.slice(0, maxDays); raw = raw.slice(0, maxDays + 1);
const data = []; const data = [];
for (const record of raw) { for (const record of raw) {
@ -90,8 +90,13 @@ async function renderChart() {
borderRadius: 3, borderRadius: 3,
backgroundColor(c) { backgroundColor(c) {
const value = c.dataset.data[c.dataIndex].v; const value = c.dataset.data[c.dataIndex].v;
const a = value / max(c.dataset.data[c.dataIndex].y); 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); return alpha(color, a);
}
}, },
fill: true, fill: true,
width(c) { width(c) {
@ -129,6 +134,10 @@ async function renderChart() {
autoSkip: false, autoSkip: false,
callback: (value, index, values) => value, callback: (value, index, values) => value,
}, },
title: {
display: true,
text: 'Days later',
},
}, },
y: { y: {
type: 'time', type: 'time',
@ -166,7 +175,12 @@ async function renderChart() {
}, },
label(context) { label(context) {
const v = context.dataset.data[context.dataIndex]; 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', //mode: 'index',

View 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>

View file

@ -40,10 +40,6 @@ import * as os from '@/os';
import { $i } from '@/account'; import { $i } from '@/account';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination from '@/components/MkPagination.vue';
const emit = defineEmits<{
(ev: 'done'): void;
}>();
const pinnedUsers = { endpoint: 'pinned-users', noPaging: true }; const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };
const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: { const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {

View file

@ -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>;

View file

@ -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>

View file

@ -37,10 +37,6 @@ import { chooseFileFromPc } from '@/scripts/select-file';
import * as os from '@/os'; import * as os from '@/os';
import { $i } from '@/account'; import { $i } from '@/account';
const emit = defineEmits<{
(ev: 'done'): void;
}>();
const name = ref(''); const name = ref('');
const description = ref(''); const description = ref('');

View file

@ -7,9 +7,17 @@
@close="close(true)" @close="close(true)"
@closed="emit('closed')" @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 style="overflow-x: clip;">
<div :class="$style.progressBar">
<div :class="$style.progressBarValue" :style="{ width: `${(page / 5) * 100}%` }"></div>
</div>
<Transition <Transition
mode="out-in" mode="out-in"
:enter-active-class="$style.transition_x_enterActive" :enter-active-class="$style.transition_x_enterActive"
@ -40,12 +48,22 @@
<template v-else-if="page === 2"> <template v-else-if="page === 2">
<div style="height: 100cqh; overflow: auto;"> <div style="height: 100cqh; overflow: auto;">
<MkSpacer :margin-min="20" :margin-max="28"> <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> <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> </MkSpacer>
</div> </div>
</template> </template>
<template v-else-if="page === 3"> <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"> <div :class="$style.centerPage">
<MkSpacer :margin-min="20" :margin-max="28"> <MkSpacer :margin-min="20" :margin-max="28">
<div class="_gaps" style="text-align: center;"> <div class="_gaps" style="text-align: center;">
@ -58,7 +76,7 @@
</MkSpacer> </MkSpacer>
</div> </div>
</template> </template>
<template v-else-if="page === 4"> <template v-else-if="page === 5">
<div :class="$style.centerPage"> <div :class="$style.centerPage">
<MkSpacer :margin-min="20" :margin-max="28"> <MkSpacer :margin-min="20" :margin-max="28">
<div class="_gaps" style="text-align: center;"> <div class="_gaps" style="text-align: center;">
@ -87,6 +105,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import XProfile from '@/components/MkUserSetupDialog.Profile.vue'; import XProfile from '@/components/MkUserSetupDialog.Profile.vue';
import XFollow from '@/components/MkUserSetupDialog.Follow.vue'; import XFollow from '@/components/MkUserSetupDialog.Follow.vue';
import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { instance } from '@/instance'; import { instance } from '@/instance';
import { host } from '@/config'; import { host } from '@/config';
@ -134,6 +153,21 @@ async function close(skip: boolean) {
transform: translateX(-50px); 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 { .centerPage {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -142,4 +176,14 @@ async function close(skip: boolean) {
padding-bottom: 30px; padding-bottom: 30px;
box-sizing: border-box; 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> </style>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="lzyxtsnt"> <div>
<ImgWithBlurhash v-if="image" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :cover="false"/> <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> </div>
</template> </template>
@ -17,11 +17,3 @@ const props = defineProps<{
const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId); const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId);
</script> </script>
<style lang="scss" scoped>
.lzyxtsnt {
> img {
max-width: 100%;
}
}
</style>

View file

@ -238,6 +238,7 @@ const patrons = [
'ずも', 'ずも',
'binvinyl', 'binvinyl',
'渡志郎', '渡志郎',
'ぷーざ',
]; ];
let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure')); let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure'));

View file

@ -46,7 +46,7 @@
</MkInput> </MkInput>
<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton> <MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
</div> </div>
<MkNotes v-if="searchPagination" :key="searchQuery" :pagination="searchPagination"/> <MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
</div> </div>
</div> </div>
</MkSpacer> </MkSpacer>
@ -93,6 +93,7 @@ let channel = $ref(null);
let favorited = $ref(false); let favorited = $ref(false);
let searchQuery = $ref(''); let searchQuery = $ref('');
let searchPagination = $ref(); let searchPagination = $ref();
let searchKey = $ref('');
const featuredPagination = $computed(() => ({ const featuredPagination = $computed(() => ({
endpoint: 'notes/featured' as const, endpoint: 'notes/featured' as const,
limit: 10, limit: 10,
@ -149,10 +150,12 @@ async function search() {
endpoint: 'notes/search', endpoint: 'notes/search',
limit: 10, limit: 10,
params: { params: {
query: searchQuery, query: query,
channelId: channel.id, channelId: channel.id,
}, },
}; };
searchKey = query;
} }
const headerActions = $computed(() => { const headerActions = $computed(() => {

View file

@ -37,7 +37,7 @@ async function choose() {
file = fileResponse[0]; file = fileResponse[0];
emit('update:modelValue', { emit('update:modelValue', {
...props.modelValue, ...props.modelValue,
fileId: fileResponse.id, fileId: file.id,
}); });
}); });
} }

View 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>

View 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>

View file

@ -1,133 +1,38 @@
<template> <template>
<MkStickyContainer> <MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> <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"> <MkSpacer v-if="tab === 'note'" :content-max="800">
<template #header>{{ i18n.ts.searchResult }}</template> <div v-if="notesSearchAvailable">
<MkNotes :key="key" :pagination="notePagination"/> <XNote/>
</MkFoldableSection>
</div> </div>
<div v-else> <div v-else>
<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo> <MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
</div> </div>
</MkSpacer> </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"> <MkSpacer v-else-if="tab === 'user'" :content-max="800">
<template #header>{{ i18n.ts.searchResult }}</template> <XUser/>
<MkUserList :key="key" :pagination="userPagination"/>
</MkFoldableSection>
</div>
</MkSpacer> </MkSpacer>
</MkStickyContainer> </MkStickyContainer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted } from 'vue'; import { computed, defineAsyncComponent, 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 { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os'; import * as os from '@/os';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { $i } from '@/account'; import { $i } from '@/account';
import { instance } from '@/instance'; import { instance } from '@/instance';
import MkInfo from '@/components/MkInfo.vue'; 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 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)); 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 headerActions = $computed(() => []);
const headerTabs = $computed(() => [{ const headerTabs = $computed(() => [{
@ -141,7 +46,7 @@ const headerTabs = $computed(() => [{
}]); }]);
definePageMetadata(computed(() => ({ definePageMetadata(computed(() => ({
title: searchQuery ? i18n.t('searchWith', { q: searchQuery }) : i18n.ts.search, title: i18n.ts.search,
icon: 'ti ti-search', icon: 'ti ti-search',
}))); })));
</script> </script>

View file

@ -24,9 +24,9 @@
{{ i18n.ts.noCrawle }} {{ i18n.ts.noCrawle }}
<template #caption>{{ i18n.ts.noCrawleDescription }}</template> <template #caption>{{ i18n.ts.noCrawleDescription }}</template>
</MkSwitch> </MkSwitch>
<MkSwitch v-model="preventAiLarning" @update:model-value="save()"> <MkSwitch v-model="preventAiLearning" @update:model-value="save()">
{{ i18n.ts.preventAiLarning }}<span class="_beta">{{ i18n.ts.beta }}</span> {{ i18n.ts.preventAiLearning }}<span class="_beta">{{ i18n.ts.beta }}</span>
<template #caption>{{ i18n.ts.preventAiLarningDescription }}</template> <template #caption>{{ i18n.ts.preventAiLearningDescription }}</template>
</MkSwitch> </MkSwitch>
<MkSwitch v-model="isExplorable" @update:model-value="save()"> <MkSwitch v-model="isExplorable" @update:model-value="save()">
{{ i18n.ts.makeExplorable }} {{ i18n.ts.makeExplorable }}
@ -75,7 +75,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
let isLocked = $ref($i.isLocked); let isLocked = $ref($i.isLocked);
let autoAcceptFollowed = $ref($i.autoAcceptFollowed); let autoAcceptFollowed = $ref($i.autoAcceptFollowed);
let noCrawle = $ref($i.noCrawle); let noCrawle = $ref($i.noCrawle);
let preventAiLarning = $ref($i.preventAiLarning); let preventAiLearning = $ref($i.preventAiLearning);
let isExplorable = $ref($i.isExplorable); let isExplorable = $ref($i.isExplorable);
let hideOnlineStatus = $ref($i.hideOnlineStatus); let hideOnlineStatus = $ref($i.hideOnlineStatus);
let publicReactions = $ref($i.publicReactions); let publicReactions = $ref($i.publicReactions);
@ -91,7 +91,7 @@ function save() {
isLocked: !!isLocked, isLocked: !!isLocked,
autoAcceptFollowed: !!autoAcceptFollowed, autoAcceptFollowed: !!autoAcceptFollowed,
noCrawle: !!noCrawle, noCrawle: !!noCrawle,
preventAiLarning: !!preventAiLarning, preventAiLearning: !!preventAiLearning,
isExplorable: !!isExplorable, isExplorable: !!isExplorable,
hideOnlineStatus: !!hideOnlineStatus, hideOnlineStatus: !!hideOnlineStatus,
publicReactions: !!publicReactions, publicReactions: !!publicReactions,

View file

@ -24,9 +24,9 @@
"@swc/jest": "0.2.26", "@swc/jest": "0.2.26",
"@types/jest": "29.5.1", "@types/jest": "29.5.1",
"@types/node": "18.16.3", "@types/node": "18.16.3",
"@typescript-eslint/eslint-plugin": "5.59.2", "@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.2", "@typescript-eslint/parser": "5.59.5",
"eslint": "8.39.0", "eslint": "8.40.0",
"jest": "29.5.0", "jest": "29.5.0",
"jest-fetch-mock": "3.0.3", "jest-fetch-mock": "3.0.3",
"jest-websocket-mock": "2.4.0", "jest-websocket-mock": "2.4.0",

View file

@ -14,9 +14,9 @@
"misskey-js": "workspace:*" "misskey-js": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/parser": "5.59.2", "@typescript-eslint/parser": "5.59.5",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67", "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
"eslint": "8.39.0", "eslint": "8.40.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"typescript": "5.0.4" "typescript": "5.0.4"
} }

File diff suppressed because it is too large Load diff