diff --git a/CHANGELOG.md b/CHANGELOG.md index 7abafd9ec7..a18fa3fec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,31 @@ -## Unreleased +## 2025.5.1 ### General -- +- Feat: 非ログインでサーバーを閲覧された際に、サーバー内のコンテンツを非公開にすることができるようになりました + - モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます + - 「全て公開(今までの挙動)」「ローカルのコンテンツだけ公開(=サーバー内で受信されたリモートのコンテンツは公開しない)」「何も公開しない」から選択できます + - デフォルト値は「ローカルのコンテンツだけ公開」になっています ### Client +- Feat: サーバー初期設定ウィザードが実装されました + - 簡単なウィザードに従うだけで、サーバーに最適な設定が適用されます +- Feat: Websocket接続を行わずにMisskeyを利用するNo Websocketモードが実装されました(beta) + - サーバーのパフォーマンス向上に寄与することが期待されます + - 何らの理由によりWebsocket接続が行えない環境でも快適に利用可能です + - 従来のWebsocket接続を行うモードはリアルタイムモードとして再定義されました + - チャットなど、一部の機能は引き続き設定に関わらずWebsocket接続が行われます +- Enhance: メモリ使用量を軽減しました +- Enhance: リプライ元にアンケートがあることが表示されるように +- Enhance: ノートのサーバー情報のデザインを改善・パフォーマンス向上 + (Based on https://github.com/taiyme/misskey/pull/198, https://github.com/taiyme/misskey/pull/211, https://github.com/taiyme/misskey/pull/283) +- Fix: "時計"ウィジェット(Clock)において、Transparent設定が有効でも、その背景が透過されない問題を修正 - Fix: カラムの名前が正しくリスト/チャンネルの名前にならない問題を修正 ### Server -- +- Enhance: ノートのレスポンスにアンケートが添付されているかどうかを示すフラグ`hasPoll`を追加 +- Fix: チャットルームが削除された場合・チャットルームから抜けた場合に、未読状態が残り続けることがあるのを修正 +- Fix: ユーザ除外アンテナをインポートできない問題を修正 +- Fix: アンテナのセンシティブなチャンネルのノートを含むかどうかの情報がエクスポートされない問題を修正 ## 2025.5.0 diff --git a/cypress.config.ts b/cypress.config.ts index e390c41a54..361acaf6e5 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -2,11 +2,6 @@ import { defineConfig } from 'cypress' export default defineConfig({ e2e: { - // We've imported your old cypress plugins here. - // You may want to clean this up later by importing these. - setupNodeEvents(on, config) { - return require('./cypress/plugins/index.js')(on, config) - }, baseUrl: 'http://localhost:61812', }, }) diff --git a/cypress/e2e/basic.cy.ts b/cypress/e2e/basic.cy.ts index 6471f96504..bd4021d2e3 100644 --- a/cypress/e2e/basic.cy.ts +++ b/cypress/e2e/basic.cy.ts @@ -31,6 +31,15 @@ describe('Before setup instance', () => { // なぜか動かない //cy.wait('@signup').should('have.property', 'response.statusCode'); cy.wait('@signup'); + + cy.intercept('POST', '/api/admin/update-meta').as('update-meta'); + + cy.get('[data-cy-next]').click(); + cy.get('[data-cy-next]').click(); + cy.get('[data-cy-server-name] input').type('Testskey'); + cy.get('[data-cy-server-setup-wizard-apply]').click(); + + cy.wait('@update-meta'); }); }); diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js deleted file mode 100644 index 59b2bab6e4..0000000000 --- a/cypress/plugins/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/// -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -/** - * @type {Cypress.PluginConfig} - */ -// eslint-disable-next-line no-unused-vars -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config -} diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index adc37c2414..9bd5e2fcc2 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -215,7 +215,6 @@ noUsers: "ليس هناك مستخدمون" editProfile: "تعديل الملف التعريفي" noteDeleteConfirm: "هل تريد حذف هذه الملاحظة؟" pinLimitExceeded: "لا يمكنك تثبيت الملاحظات بعد الآن." -intro: "لقد انتهت عملية تنصيب Misskey. الرجاء إنشاء حساب إداري." done: "تمّ" processing: "المعالجة جارية" preview: "معاينة" @@ -676,7 +675,6 @@ experimental: "اختباري" developer: "المطور" makeExplorable: "أظهر الحساب في صفحة \"استكشاف\"" makeExplorableDescription: "بتعطيل هذا الخيار لن يظهر حسابك في صفحة \"استكشاف\"" -showGapBetweenNotesInTimeline: "أظهر فجوات بين المشاركات في الخيط الزمني" left: "يسار" center: "وسط" wide: "عريض" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 442fed6e21..556f780298 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -215,7 +215,6 @@ noUsers: "কোন ব্যাবহারকারী নেই" editProfile: "প্রোফাইল সম্পাদনা করুন" noteDeleteConfirm: "আপনি কি নোট ডিলিট করার ব্যাপারে নিশ্চিত?" pinLimitExceeded: "আপনি আর কোন নোট পিন করতে পারবেন না" -intro: "Misskey এর ইন্সটলেশন সম্পন্ন হয়েছে!দয়া করে অ্যাডমিন ইউজার তৈরি করুন।" done: "সম্পন্ন" processing: "প্রক্রিয়াধীন..." preview: "পূর্বরূপ দেখুন" @@ -673,7 +672,6 @@ experimentalFeatures: "পরীক্ষামূলক বৈশিষ্ট developer: "ডেভেলপার" makeExplorable: "অ্যাকাউন্ট \"ঘুরে দেখুন\" পৃষ্ঠায় দেখান" makeExplorableDescription: "আপনি এটি বন্ধ করলে, আপনার অ্যাকাউন্ট \"ঘুরে দেখুন\" পৃষ্ঠায় প্রদর্শিত হবে না।" -showGapBetweenNotesInTimeline: "টাইমলাইন এবং নোটের মাঝে ফাকা জায়গা রাখুন" duplicate: "প্রতিরূপ" left: "বাম" center: "মাঝখান" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 12779fafa4..02b50fdb97 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -251,7 +251,6 @@ noUsers: "No hi ha usuaris" editProfile: "Edita el perfil" noteDeleteConfirm: "Segur que voleu eliminar aquesta publicació?" pinLimitExceeded: "No podeu fixar més publicacions" -intro: "La instal·lació de Misskey ha acabat! Crea un usuari d'administrador." done: "Fet" processing: "S'està processant..." preview: "Vista prèvia" @@ -785,7 +784,6 @@ thisIsExperimentalFeature: "Aquesta és una característica experimental. La sev developer: "Programador" makeExplorable: "Fes que el compte sigui visible a la secció \"Explorar\"" makeExplorableDescription: "Si desactives aquesta opció, el teu compte no sortirà a la secció \"Explorar\"" -showGapBetweenNotesInTimeline: "Notes separades a la línia de temps" duplicate: "Duplicat" left: "Esquerra" center: "Centre" @@ -1238,7 +1236,6 @@ showAvatarDecorations: "Mostrar les decoracions dels avatars" releaseToRefresh: "Deixar anar per actualitzar" refreshing: "Recarregant..." pullDownToRefresh: "Llisca cap a baix per recarregar" -disableStreamingTimeline: "Desactivar l'actualització en temps real de les línies de temps" useGroupedNotifications: "Mostrar les notificacions agrupades " signupPendingError: "Hi ha hagut un problema verificant l'adreça de correu electrònic. L'enllaç pot haver caducat." cwNotationRequired: "Si està activat \"Amagar contingut\" s'ha d'escriure una descripció " @@ -1434,6 +1431,7 @@ _preferencesProfile: profileName: "Nom del perfil" profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu." profileNameDescription2: "Per exemple: \"PC Principal\", \"Smartphone\", etc" + manageProfiles: "Gestionar perfils" _preferencesBackup: autoBackup: "Còpia de seguretat automàtica " restoreFromBackup: "Restaurar des d'una còpia de seguretat" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index 87f9445409..c23b0263b8 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -228,7 +228,6 @@ noUsers: "Žádní uživatelé" editProfile: "Upravit můj profil" noteDeleteConfirm: "Jste si jistí že chcete smazat tuhle poznámku?" pinLimitExceeded: "Nemůžete připnout další poznámky." -intro: "Instalace Misskey byla dokončena! Prosím vytvořte admina." done: "Hotovo" processing: "Zpracovávám" preview: "Náhled" @@ -726,7 +725,6 @@ thisIsExperimentalFeature: "Tohle je experimentální funkce. Její funkce se m developer: "Vývojář" makeExplorable: "Udělat účet viditelný v \"Objevit\"" makeExplorableDescription: "Pokud tohle vypnete, tak se účet přestane zobrazovat v sekci \"Objevit\"." -showGapBetweenNotesInTimeline: "Zobrazit mezeru mezi příspěvkama na časové ose" duplicate: "Duplikovat" left: "Vlevo" center: "Uprostřed" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 4ca8f27790..9899276075 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -251,7 +251,6 @@ noUsers: "Keine Benutzer gefunden" editProfile: "Profil bearbeiten" noteDeleteConfirm: "Möchtest du diese Notiz wirklich löschen?" pinLimitExceeded: "Du kannst nicht noch mehr Notizen anheften." -intro: "Misskey ist installiert! Lass uns nun ein Administratorkonto einrichten." done: "Fertig" processing: "In Bearbeitung …" preview: "Vorschau" @@ -785,7 +784,6 @@ thisIsExperimentalFeature: "Dies ist eine experimentelle Funktion. Änderungen a developer: "Entwickler" makeExplorable: "Benutzerkonto in „Erkunden“ sichtbar machen" makeExplorableDescription: "Wenn diese Option deaktiviert ist, ist dein Benutzerkonto nicht im „Erkunden“-Bereich sichtbar." -showGapBetweenNotesInTimeline: "Abstände zwischen Notizen auf der Chronik anzeigen" duplicate: "Duplizieren" left: "Links" center: "Mittig" @@ -1238,7 +1236,6 @@ showAvatarDecorations: "Profilbilddekoration anzeigen" releaseToRefresh: "Zum Aktualisieren loslassen" refreshing: "Wird aktualisiert..." pullDownToRefresh: "Zum Aktualisieren ziehen" -disableStreamingTimeline: "Echtzeitaktualisierung der Chronik deaktivieren" useGroupedNotifications: "Benachrichtigungen gruppieren" signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen. Der Link könnte abgelaufen sein." cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden." @@ -1434,6 +1431,7 @@ _preferencesProfile: profileName: "Profilname" profileNameDescription: "Lege einen Namen fest, der dieses Gerät identifiziert." profileNameDescription2: "Beispiel: \"Haupt-PC\", \"Smartphone\"" + manageProfiles: "Profile verwalten" _preferencesBackup: autoBackup: "Automatische Sicherung" restoreFromBackup: "Wiederherstellen aus der Sicherung" diff --git a/locales/en-US.yml b/locales/en-US.yml index 4efd1a36f6..a7fd93d9b0 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -251,7 +251,6 @@ noUsers: "There are no users" editProfile: "Edit profile" noteDeleteConfirm: "Are you sure you want to delete this note?" pinLimitExceeded: "You cannot pin any more notes" -intro: "Installation of Misskey has been finished! Please create an admin user." done: "Done" processing: "Processing..." preview: "Preview" @@ -785,7 +784,6 @@ thisIsExperimentalFeature: "This is an experimental feature. Its functionality i developer: "Developer" makeExplorable: "Make account visible in \"Explore\"" makeExplorableDescription: "If you turn this off, your account will not show up in the \"Explore\" section." -showGapBetweenNotesInTimeline: "Show a gap between posts on the timeline" duplicate: "Duplicate" left: "Left" center: "Center" @@ -1238,7 +1236,6 @@ showAvatarDecorations: "Show avatar decorations" releaseToRefresh: "Release to refresh" refreshing: "Refreshing..." pullDownToRefresh: "Pull down to refresh" -disableStreamingTimeline: "Disable real-time timeline updates" useGroupedNotifications: "Display grouped notifications" signupPendingError: "There was a problem verifying the email address. The link may have expired." cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided." @@ -1434,6 +1431,7 @@ _preferencesProfile: profileName: "Profile name" profileNameDescription: "Set a name that identifies this device." profileNameDescription2: "Example: \"Main PC\", \"Smartphone\"" + manageProfiles: "Manage Profiles" _preferencesBackup: autoBackup: "Auto backup" restoreFromBackup: "Restore from backup" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index e14f37d1d6..b3f7c5ff3d 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -250,7 +250,6 @@ noUsers: "No hay usuarios" editProfile: "Editar perfil" noteDeleteConfirm: "¿Desea borrar esta nota?" pinLimitExceeded: "Ya no se pueden fijar más posts" -intro: "¡La instalación de Misskey ha terminado! Crea el usuario administrador." done: "Terminado" processing: "Procesando" preview: "Vista previa" @@ -784,7 +783,6 @@ thisIsExperimentalFeature: "Se trata de una función experimental. Las especific developer: "Desarrolladores" makeExplorable: "Hacer visible la cuenta en \"Explorar\"" makeExplorableDescription: "Si desactiva esta opción, su cuenta no aparecerá en la sección \"Explorar\"." -showGapBetweenNotesInTimeline: "Mostrar un intervalo entre notas en la línea de tiempo" duplicate: "Duplicar" left: "Izquierda" center: "Centrar" @@ -1237,7 +1235,6 @@ showAvatarDecorations: "Mostrar decoraciones de avatar" releaseToRefresh: "Soltar para recargar" refreshing: "Recargando..." pullDownToRefresh: "Tira hacia abajo para recargar" -disableStreamingTimeline: "Desactivar actualizaciones en tiempo real de la línea de tiempo" useGroupedNotifications: "Mostrar notificaciones agrupadas" signupPendingError: "Ha habido un problema al verificar tu dirección de correo electrónico. Es posible que el enlace haya caducado." cwNotationRequired: "Si se ha activado \"ocultar contenido\", es necesario proporcionar una descripción." diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 3a6f520ae6..f07c08bd3d 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -238,7 +238,6 @@ noUsers: "Il n’y a pas d’utilisateur·rice·s" editProfile: "Modifier votre profil" noteDeleteConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note ?" pinLimitExceeded: "Vous ne pouvez plus épingler d’autres notes." -intro: "L’installation de Misskey est terminée ! Veuillez créer un compte administrateur." done: "Terminé" processing: "Traitement en cours" preview: "Aperçu" @@ -760,7 +759,6 @@ thisIsExperimentalFeature: "Ceci est une fonctionnalité expérimentale. Il y a developer: "Développeur" makeExplorable: "Rendre le compte visible sur la page \"Découvrir\"." makeExplorableDescription: "Si vous désactivez cette option, votre compte n'apparaîtra pas sur la page \"Découvrir\"." -showGapBetweenNotesInTimeline: "Afficher un écart entre les notes sur la Timeline" duplicate: "Duliquer" left: "Gauche" center: "Centrer" @@ -1209,7 +1207,6 @@ showAvatarDecorations: "Afficher les décorations d'avatar" releaseToRefresh: "Relâcher pour rafraîchir" refreshing: "Rafraîchissement..." pullDownToRefresh: "Tirer vers le bas pour rafraîchir" -disableStreamingTimeline: "Désactiver les mises à jour en temps réel de la ligne du temps" useGroupedNotifications: "Grouper les notifications" signupPendingError: "Un problème est survenu lors de la vérification de votre adresse e-mail. Le lien a peut-être expiré." cwNotationRequired: "Si « Masquer le contenu » est activé, une description doit être fournie." diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 22ccbf153a..3ad9afdc22 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -241,7 +241,6 @@ noUsers: "Tidak ada pengguna" editProfile: "Sunting profil" noteDeleteConfirm: "Apakah kamu yakin ingin menghapus catatan ini?" pinLimitExceeded: "Kamu tidak dapat menyematkan catatan lagi" -intro: "Instalasi Misskey telah selesai! Mohon untuk membuat pengguna admin." done: "Selesai" processing: "Memproses" preview: "Pratinjau" @@ -761,7 +760,6 @@ thisIsExperimentalFeature: "Fitur ini eksperimental. Fungsionalitas dari fitur i developer: "Pengembang" makeExplorable: "Buat akun tampil di \"Jelajahi\"" makeExplorableDescription: "Jika kamu mematikan ini, akun kamu tidak akan muncul di menu \"Jelajahi\"" -showGapBetweenNotesInTimeline: "Tampilkan jarak diantara catatan pada lini masa" duplicate: "Duplikat" left: "Kiri" center: "Tengah" @@ -1206,7 +1204,6 @@ showAvatarDecorations: "Tampilkan dekorasi avatar" releaseToRefresh: "Lepaskan untuk memuat ulang" refreshing: "Sedang memuat ulang..." pullDownToRefresh: "Tarik ke bawah untuk memuat ulang" -disableStreamingTimeline: "Nonaktifkan pembaharuan lini masa real-time" useGroupedNotifications: "Tampilkan notifikasi secara dikelompokkan" signupPendingError: "Terdapat masalah ketika memverifikasi alamat surel. Tautan kemungkinan telah kedaluwarsa." cwNotationRequired: "Jika \"Sembunyikan konten\" diaktifkan, deskripsi harus disediakan." diff --git a/locales/index.d.ts b/locales/index.d.ts index 281568a5fd..6bd5c8c3d7 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1022,10 +1022,6 @@ export interface Locale extends ILocale { * これ以上ピン留めできません */ "pinLimitExceeded": string; - /** - * Misskeyのインストールが完了しました!管理者アカウントを作成しましょう。 - */ - "intro": string; /** * 完了 */ @@ -2322,6 +2318,10 @@ export interface Locale extends ILocale { * 新しいノートがあります */ "newNoteRecived": string; + /** + * 新しいノート + */ + "newNote": string; /** * サウンド */ @@ -3158,10 +3158,6 @@ export interface Locale extends ILocale { * オフにすると、「みつける」にアカウントが載らなくなります。 */ "makeExplorableDescription": string; - /** - * タイムラインのノートを離して表示 - */ - "showGapBetweenNotesInTimeline": string; /** * 複製 */ @@ -4970,10 +4966,6 @@ export interface Locale extends ILocale { * 引っ張ってリロード */ "pullDownToRefresh": string; - /** - * タイムラインのリアルタイム更新を無効にする - */ - "disableStreamingTimeline": string; /** * 通知をグルーピング */ @@ -5417,6 +5409,22 @@ export interface Locale extends ILocale { * スクロールして閉じる */ "scrollToClose": string; + /** + * アドバイス + */ + "advice": string; + /** + * リアルタイムモード + */ + "realtimeMode": string; + /** + * オンにする + */ + "turnItOn": string; + /** + * オフにする + */ + "turnItOff": string; "_chat": { /** * まだメッセージはありません @@ -5721,6 +5729,22 @@ export interface Locale extends ILocale { * マウスでは、ホイールを押し込みながらドラッグします。 */ "enablePullToRefresh_description": string; + /** + * サーバーと接続を確立し、リアルタイムでコンテンツを更新します。通信量とバッテリーの消費が多くなる場合があります。 + */ + "realtimeMode_description": string; + /** + * コンテンツの取得頻度 + */ + "contentsUpdateFrequency": string; + /** + * 高いほどリアルタイムにコンテンツが更新されますが、パフォーマンスが低下し、通信量とバッテリーの消費が多くなります。 + */ + "contentsUpdateFrequency_description": string; + /** + * リアルタイムモードがオンのときは、この設定に関わらずリアルタイムでコンテンツが更新されます。 + */ + "contentsUpdateFrequency_description2": string; "_chat": { /** * 送信者の名前を表示 @@ -6388,6 +6412,40 @@ export interface Locale extends ILocale { * 脆弱性などの理由で、サーバーのソフトウェアの名前及びバージョンの範囲を指定して配信を停止できます。このバージョン情報はサーバーが提供したものであり、信頼性は保証されません。バージョン指定には semver の範囲指定が使用できますが、>= 2024.3.1 と指定すると 2024.3.1-custom.0 のようなカスタムバージョンが含まれないため、>= 2024.3.1-0 のように prerelease の指定を行うことを推奨します。 */ "deliverSuspendedSoftwareDescription": string; + /** + * お一人様モード + */ + "singleUserMode": string; + /** + * このサーバーを利用するのが自分だけの場合、このモードを有効にすることで動作が最適化されます。 + */ + "singleUserMode_description": string; + /** + * 非利用者に対するユーザー作成コンテンツの公開範囲 + */ + "userGeneratedContentsVisibilityForVisitor": string; + /** + * モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます。 + */ + "userGeneratedContentsVisibilityForVisitor_description": string; + /** + * サーバーで受信したリモートのコンテンツを含め、サーバー内の全てのコンテンツを無条件でインターネットに公開することはリスクが伴います。特に、分散型の特性を知らない閲覧者にとっては、リモートのコンテンツであってもサーバー内で作成されたコンテンツであると誤って認識してしまう可能性があるため、注意が必要です。 + */ + "userGeneratedContentsVisibilityForVisitor_description2": string; + "_userGeneratedContentsVisibilityForVisitor": { + /** + * 全て公開 + */ + "all": string; + /** + * ローカルコンテンツのみ公開し、リモートコンテンツは非公開 + */ + "localOnly": string; + /** + * 全て非公開 + */ + "none": string; + }; }; "_accountMigration": { /** @@ -11620,6 +11678,166 @@ export interface Locale extends ILocale { */ "serverHostPlaceholder": string; }; + "_serverSetupWizard": { + /** + * Misskeyのインストールが完了しました! + */ + "installCompleted": string; + /** + * まずは、管理者アカウントを作成しましょう。 + */ + "firstCreateAccount": string; + /** + * 管理者アカウントが作成されました! + */ + "accountCreated": string; + /** + * サーバーの設定 + */ + "serverSetting": string; + /** + * このウィザードで簡単に最適なサーバーの設定が行えます。 + */ + "youCanEasilyConfigureOptimalServerSettingsWithThisWizard": string; + /** + * ここでの設定は、あとからでも変更できます。 + */ + "settingsYouMakeHereCanBeChangedLater": string; + /** + * Misskeyをどのように使いますか? + */ + "howWillYouUseMisskey": string; + "_use": { + /** + * お一人様サーバー + */ + "single": string; + /** + * 自分専用のサーバーとして、一人で使う + */ + "single_description": string; + /** + * お一人様サーバーとして運用する場合でも、アカウントは必要に応じて複数作成可能です。 + */ + "single_youCanCreateMultipleAccounts": string; + /** + * グループサーバー + */ + "group": string; + /** + * 信頼できる他の利用者を招待して、複数人で使う + */ + "group_description": string; + /** + * オープンサーバー + */ + "open": string; + /** + * 不特定多数の利用者を受け入れる運営を行う + */ + "open_description": string; + }; + /** + * 不特定多数の利用者を受け入れることはリスクが伴います。トラブルに対処できるよう、確実なモデレーション体制で運営することを推奨します。 + */ + "openServerAdvice": string; + /** + * 自サーバーがスパムの踏み台にならないように、reCAPTCHAといったアンチボット機能を有効にするなど、セキュリティについても細心の注意が必要です。 + */ + "openServerAntiSpamAdvice": string; + /** + * どれくらいの人数を想定していますか? + */ + "howManyUsersDoYouExpect": string; + "_scale": { + /** + * 100人以下 (小規模) + */ + "small": string; + /** + * 100人以上1000人以下 (中規模) + */ + "medium": string; + /** + * 1000人以上 (大規模) + */ + "large": string; + }; + /** + * 大規模なサーバーでは、ロードバランシングやデータベースのレプリケーションなど、高度なインフラストラクチャーの知識が必要になる場合があります。 + */ + "largeScaleServerAdvice": string; + /** + * Fediverseと接続しますか? + */ + "doYouConnectToFediverse": string; + /** + * 分散型サーバーで構成されるネットワーク(Fediverse)に接続すると、他のサーバーと相互にコンテンツのやり取りが可能です。 + */ + "doYouConnectToFediverse_description1": string; + /** + * Fediverseと接続することは「連合」とも呼ばれます。 + */ + "doYouConnectToFediverse_description2": string; + /** + * 連合可能なサーバーの指定など、高度な設定も後ほど可能です。 + */ + "youCanConfigureMoreFederationSettingsLater": string; + /** + * 管理者情報 + */ + "adminInfo": string; + /** + * 問い合わせを受け付けるために使用される管理者情報を設定します。 + */ + "adminInfo_description": string; + /** + * オープンサーバー、または連合がオンの場合は必ず入力が必要です。 + */ + "adminInfo_mustBeFilled": string; + /** + * 以下の設定が推奨されます + */ + "followingSettingsAreRecommended": string; + /** + * この設定を適用 + */ + "applyTheseSettings": string; + /** + * 設定をスキップ + */ + "skipSettings": string; + /** + * 設定が完了しました! + */ + "settingsCompleted": string; + /** + * お疲れ様でした。準備が整ったので、さっそくサーバーの使用を開始できます。 + */ + "settingsCompleted_description": string; + /** + * 詳細なサーバー設定は、「コントロールパネル」から行えます。 + */ + "settingsCompleted_description2": string; + /** + * 寄付のお願い + */ + "donationRequest": string; + "_donationRequest": { + /** + * Misskeyは有志によって開発されている無料のソフトウェアです。 + */ + "text1": string; + /** + * 今後も開発を続けられるように、よろしければぜひカンパをお願いいたします。 + */ + "text2": string; + /** + * 支援者向け特典もあります! + */ + "text3": string; + }; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 787c110189..259fe6edd3 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -250,7 +250,6 @@ noUsers: "Non ci sono profili" editProfile: "Modifica profilo" noteDeleteConfirm: "Vuoi davvero eliminare questa Nota?" pinLimitExceeded: "Non puoi fissare altre note " -intro: "L'installazione di Misskey è terminata! Si prega di creare il profilo amministratore." done: "Fine" processing: "In elaborazione" preview: "Anteprima" @@ -784,7 +783,6 @@ thisIsExperimentalFeature: "Questa è una funzionalità sperimentale. Potrebbe e developer: "Sviluppatore" makeExplorable: "Profilo visibile pubblicamente nella pagina \"Esplora\"" makeExplorableDescription: "Disabilitando questa opzione, il tuo profilo non verrà elencato nella pagina \"Esplora\"." -showGapBetweenNotesInTimeline: "Mostrare un intervallo tra le note sulla timeline" duplicate: "Duplica" left: "Sinistra" center: "Centro" @@ -1237,7 +1235,6 @@ showAvatarDecorations: "Mostra decorazione della foto profilo" releaseToRefresh: "Rilascia per aggiornare" refreshing: "Aggiornamento..." pullDownToRefresh: "Trascinare per aggiornare" -disableStreamingTimeline: "Disabilitare gli aggiornamenti della TL in tempo reale" useGroupedNotifications: "Mostra le notifiche raggruppate" signupPendingError: "Si è verificato un problema durante la verifica del tuo indirizzo email. Potrebbe essere scaduto il collegamento temporaneo." cwNotationRequired: "Devi indicare perché il contenuto è indicato come esplicito." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index f0512925f3..6c73285295 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -251,7 +251,6 @@ noUsers: "ユーザーはいません" editProfile: "プロフィールを編集" noteDeleteConfirm: "このノートを削除しますか?" pinLimitExceeded: "これ以上ピン留めできません" -intro: "Misskeyのインストールが完了しました!管理者アカウントを作成しましょう。" done: "完了" processing: "処理中" preview: "プレビュー" @@ -576,6 +575,7 @@ showFixedPostForm: "タイムライン上部に投稿フォームを表示する showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)" withRepliesByDefaultForNewlyFollowed: "フォローする際、デフォルトで返信をTLに含むようにする" newNoteRecived: "新しいノートがあります" +newNote: "新しいノート" sounds: "サウンド" sound: "サウンド" listen: "聴く" @@ -785,7 +785,6 @@ thisIsExperimentalFeature: "これは実験的な機能です。仕様が変更 developer: "開発者" makeExplorable: "アカウントを見つけやすくする" makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らなくなります。" -showGapBetweenNotesInTimeline: "タイムラインのノートを離して表示" duplicate: "複製" left: "左" center: "中央" @@ -1238,7 +1237,6 @@ showAvatarDecorations: "アイコンのデコレーションを表示" releaseToRefresh: "離してリロード" refreshing: "リロード中" pullDownToRefresh: "引っ張ってリロード" -disableStreamingTimeline: "タイムラインのリアルタイム更新を無効にする" useGroupedNotifications: "通知をグルーピング" signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。" @@ -1349,6 +1347,10 @@ goToDeck: "デッキへ戻る" federationJobs: "連合ジョブ" driveAboutTip: "ドライブでは、過去にアップロードしたファイルの一覧が表示されます。
\nノートに添付する際に再利用したり、あとで投稿するファイルを予めアップロードしておくこともできます。
\nファイルを削除すると、今までそのファイルを使用した全ての場所(ノート、ページ、アバター、バナー等)からも見えなくなるので注意してください。
\nフォルダを作って整理することもできます。" scrollToClose: "スクロールして閉じる" +advice: "アドバイス" +realtimeMode: "リアルタイムモード" +turnItOn: "オンにする" +turnItOff: "オフにする" _chat: noMessagesYet: "まだメッセージはありません" @@ -1430,6 +1432,10 @@ _settings: enableSyncThemesBetweenDevices: "デバイス間でインストールしたテーマを同期" enablePullToRefresh: "ひっぱって更新" enablePullToRefresh_description: "マウスでは、ホイールを押し込みながらドラッグします。" + realtimeMode_description: "サーバーと接続を確立し、リアルタイムでコンテンツを更新します。通信量とバッテリーの消費が多くなる場合があります。" + contentsUpdateFrequency: "コンテンツの取得頻度" + contentsUpdateFrequency_description: "高いほどリアルタイムにコンテンツが更新されますが、パフォーマンスが低下し、通信量とバッテリーの消費が多くなります。" + contentsUpdateFrequency_description2: "リアルタイムモードがオンのときは、この設定に関わらずリアルタイムでコンテンツが更新されます。" _chat: showSenderName: "送信者の名前を表示" @@ -1623,6 +1629,16 @@ _serverSettings: thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。" deliverSuspendedSoftware: "配信停止中のソフトウェア" deliverSuspendedSoftwareDescription: "脆弱性などの理由で、サーバーのソフトウェアの名前及びバージョンの範囲を指定して配信を停止できます。このバージョン情報はサーバーが提供したものであり、信頼性は保証されません。バージョン指定には semver の範囲指定が使用できますが、>= 2024.3.1 と指定すると 2024.3.1-custom.0 のようなカスタムバージョンが含まれないため、>= 2024.3.1-0 のように prerelease の指定を行うことを推奨します。" + singleUserMode: "お一人様モード" + singleUserMode_description: "このサーバーを利用するのが自分だけの場合、このモードを有効にすることで動作が最適化されます。" + userGeneratedContentsVisibilityForVisitor: "非利用者に対するユーザー作成コンテンツの公開範囲" + userGeneratedContentsVisibilityForVisitor_description: "モデレーションが行き届きにくい不適切なリモートコンテンツなどが、自サーバー経由で図らずもインターネットに公開されてしまうことによるトラブル防止などに役立ちます。" + userGeneratedContentsVisibilityForVisitor_description2: "サーバーで受信したリモートのコンテンツを含め、サーバー内の全てのコンテンツを無条件でインターネットに公開することはリスクが伴います。特に、分散型の特性を知らない閲覧者にとっては、リモートのコンテンツであってもサーバー内で作成されたコンテンツであると誤って認識してしまう可能性があるため、注意が必要です。" + + _userGeneratedContentsVisibilityForVisitor: + all: "全て公開" + localOnly: "ローカルコンテンツのみ公開し、リモートコンテンツは非公開" + none: "全て非公開" _accountMigration: moveFrom: "別のアカウントからこのアカウントに移行" @@ -3107,3 +3123,46 @@ _search: pleaseEnterServerHost: "サーバーのホストを入力してください" pleaseSelectUser: "ユーザーを選択してください" serverHostPlaceholder: "例: misskey.example.com" + +_serverSetupWizard: + installCompleted: "Misskeyのインストールが完了しました!" + firstCreateAccount: "まずは、管理者アカウントを作成しましょう。" + accountCreated: "管理者アカウントが作成されました!" + serverSetting: "サーバーの設定" + youCanEasilyConfigureOptimalServerSettingsWithThisWizard: "このウィザードで簡単に最適なサーバーの設定が行えます。" + settingsYouMakeHereCanBeChangedLater: "ここでの設定は、あとからでも変更できます。" + howWillYouUseMisskey: "Misskeyをどのように使いますか?" + _use: + single: "お一人様サーバー" + single_description: "自分専用のサーバーとして、一人で使う" + single_youCanCreateMultipleAccounts: "お一人様サーバーとして運用する場合でも、アカウントは必要に応じて複数作成可能です。" + group: "グループサーバー" + group_description: "信頼できる他の利用者を招待して、複数人で使う" + open: "オープンサーバー" + open_description: "不特定多数の利用者を受け入れる運営を行う" + openServerAdvice: "不特定多数の利用者を受け入れることはリスクが伴います。トラブルに対処できるよう、確実なモデレーション体制で運営することを推奨します。" + openServerAntiSpamAdvice: "自サーバーがスパムの踏み台にならないように、reCAPTCHAといったアンチボット機能を有効にするなど、セキュリティについても細心の注意が必要です。" + howManyUsersDoYouExpect: "どれくらいの人数を想定していますか?" + _scale: + small: "100人以下 (小規模)" + medium: "100人以上1000人以下 (中規模)" + large: "1000人以上 (大規模)" + largeScaleServerAdvice: "大規模なサーバーでは、ロードバランシングやデータベースのレプリケーションなど、高度なインフラストラクチャーの知識が必要になる場合があります。" + doYouConnectToFediverse: "Fediverseと接続しますか?" + doYouConnectToFediverse_description1: "分散型サーバーで構成されるネットワーク(Fediverse)に接続すると、他のサーバーと相互にコンテンツのやり取りが可能です。" + doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。" + youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。" + adminInfo: "管理者情報" + adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。" + adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。" + followingSettingsAreRecommended: "以下の設定が推奨されます" + applyTheseSettings: "この設定を適用" + skipSettings: "設定をスキップ" + settingsCompleted: "設定が完了しました!" + settingsCompleted_description: "お疲れ様でした。準備が整ったので、さっそくサーバーの使用を開始できます。" + settingsCompleted_description2: "詳細なサーバー設定は、「コントロールパネル」から行えます。" + donationRequest: "寄付のお願い" + _donationRequest: + text1: "Misskeyは有志によって開発されている無料のソフトウェアです。" + text2: "今後も開発を続けられるように、よろしければぜひカンパをお願いいたします。" + text3: "支援者向け特典もあります!" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 378eaf2ad5..ca5a046b90 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -250,7 +250,6 @@ noUsers: "ユーザーはおらん" editProfile: "プロフィールをいじる" noteDeleteConfirm: "このノートをほかしてええか?" pinLimitExceeded: "これ以上ピン留めできひん" -intro: "Misskeyのインストールが完了したで!管理者アカウントを作ってや。" done: "でけた" processing: "処理しとる" preview: "プレビュー" @@ -781,7 +780,6 @@ thisIsExperimentalFeature: "これは実験的な機能やから、仕様が変 developer: "開発者やで" makeExplorable: "アカウントを見つけやすくするで" makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らんくなるで。" -showGapBetweenNotesInTimeline: "タイムラインのノートを離して表示するで" duplicate: "複製" left: "左" center: "真ん中" @@ -1233,7 +1231,6 @@ showAvatarDecorations: "アイコンのデコレーション映す" releaseToRefresh: "離したらリロード" refreshing: "リロードしとる" pullDownToRefresh: "引っ張ってリロードするで" -disableStreamingTimeline: "タイムラインのリアルタイム更新をやめるで" useGroupedNotifications: "通知をグループ分けして出すで" signupPendingError: "メアド確認してたらなんか変なことなったわ。リンクの期限切れてるかもしれん。" cwNotationRequired: "「内容を隠す」んやったら注釈書かなアカンで。" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index fb21b47fac..361d90d8fa 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -224,7 +224,6 @@ noUsers: "사용자가 어ᇝ십니다" editProfile: "프로필 적기" noteDeleteConfirm: "요 노트럴 뭉캡니꺼?" pinLimitExceeded: "더 몬 붙입니다" -intro: "Misskey럴 다 깔앗십니다! 간리자 게정얼 맨걸어 보입시다." done: "햇어예" processing: "처리하고 잇어예" preview: "미리보기" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 57c66fad33..9d3b130bf2 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -220,6 +220,7 @@ silenceThisInstance: "서버를 사일런스" mediaSilenceThisInstance: "서버의 미디어를 사일런스" operations: "작업" software: "소프트웨어" +softwareName: "소프트웨어 이름" version: "버전" metadata: "메타데이터" withNFiles: "{n}개의 파일" @@ -250,7 +251,6 @@ noUsers: "아무도 없습니다" editProfile: "프로필 수정" noteDeleteConfirm: "이 노트를 삭제하시겠습니까?" pinLimitExceeded: "더 이상 고정할 수 없습니다." -intro: "Misskey의 설치가 완료되었습니다! 관리자 계정을 생성해주세요." done: "완료" processing: "처리중" preview: "미리보기" @@ -784,7 +784,6 @@ thisIsExperimentalFeature: "이 기능은 실험적인 기능입니다. 사양 developer: "개발자" makeExplorable: "계정을 쉽게 발견하도록 하기" makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다." -showGapBetweenNotesInTimeline: "타임라인의 노트 사이를 띄워서 표시" duplicate: "복제" left: "왼쪽" center: "가운데" @@ -1237,7 +1236,6 @@ showAvatarDecorations: "아바타 장식 표시" releaseToRefresh: "놓아서 새로고침" refreshing: "새로고침 중" pullDownToRefresh: "아래로 내려서 새로고침" -disableStreamingTimeline: "타임라인의 실시간 갱신을 무효화하기" useGroupedNotifications: "알림을 그룹화하고 표시" signupPendingError: "메일 주소 확인중에 문제가 발생했습니다. 링크의 유효기간이 지났을 가능성이 있습니다." cwNotationRequired: "'내용을 숨기기'를 체크한 경우 주석을 써야 합니다." @@ -1346,6 +1344,8 @@ settingsMigrating: "설정을 이전하는 중입니다. 잠시 기다려주십 readonly: "읽기 전용" goToDeck: "덱으로 돌아가기" federationJobs: "연합 작업" +driveAboutTip: "드라이브는 이전에 업로드한 파일 목록을 표시해요.
\n노트에 첨부할 때 다시 사용하거나 나중에 게시할 파일을 미리 업로드할 수 있어요.
\n파일을 삭제하면, 지금까지 그 파일을 사용한 모든 장소(노트, 페이지, 아바타, 배너 등)에서도 보이지 않게 되므로 주의해 주세요. 폴더를 만들고 정리할 수도 있어요.
" +scrollToClose: "스크롤하여 닫기" _chat: noMessagesYet: "아직 메시지가 없습니다" newMessage: "새로운 메시지" @@ -1422,6 +1422,8 @@ _settings: ifOn: "켜져 있을 때" ifOff: "꺼져 있을 때" enableSyncThemesBetweenDevices: "기기 간 설치한 테마 동기화" + enablePullToRefresh: "계속해서 갱신" + enablePullToRefresh_description: "마우스에서 휠을 누르면서 드래그해요." _chat: showSenderName: "발신자 이름 표시" sendOnEnter: "엔터로 보내기" @@ -1429,6 +1431,7 @@ _preferencesProfile: profileName: "프로필 이름" profileNameDescription: "이 디바이스를 식별할 이름을 설정해 주세요." profileNameDescription2: "예: '메인PC', '스마트폰' 등" + manageProfiles: "프로파일 관리" _preferencesBackup: autoBackup: "자동 백업" restoreFromBackup: "백업으로 복구" @@ -1467,6 +1470,7 @@ _delivery: manuallySuspended: "수동 정지 중" goneSuspended: "서버 삭제를 이유로 정지 중" autoSuspendedForNotResponding: "서버 응답 없음을 이유로 정지 중" + softwareSuspended: "전달 정지 중인 소프트웨어이므로 정지 중" _bubbleGame: howToPlay: "설명" hold: "홀드" @@ -1598,6 +1602,8 @@ _serverSettings: openRegistration: "회원 가입을 활성화 하기" openRegistrationWarning: "회원 가입을 개방하는 것은 리스크가 따릅니다. 서버를 항상 감시할 수 있고, 문제가 발생했을 때 바로 대응할 수 있는 상태에서만 활성화 하는 것을 권장합니다." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "일정 기간동안 모더레이터의 활동이 감지되지 않는 경우, 스팸 방지를 위해 이 설정은 자동으로 꺼집니다." + deliverSuspendedSoftware: "전달 정지 중인 소프트웨어" + deliverSuspendedSoftwareDescription: "취약성 등의 이유로 서버의 소프트웨어 이름 및 버전 범위를 지정하여 전달을 정지할 수 있어요. 이 버전 정보는 서버가 제공한 것이며 신뢰성은 보장되지 않아요. 버전 지정에는 semver의 범위 지정을 사용할 수 있지만, >= 2024.3.1로 지정하면 2024.3.1-custom.0과 같은 custom.0과 같은 custom 버전이 포함되지 않기 때문에 >= 2024.3.1-0과 같이 prerelease를 지정하는 것이 좋아요." _accountMigration: moveFrom: "다른 계정에서 이 계정으로 이사" moveFromSub: "다른 계정에 대한 별칭을 생성" @@ -1915,6 +1921,7 @@ _role: canManageCustomEmojis: "커스텀 이모지 관리" canManageAvatarDecorations: "아바타 꾸미기 관리" driveCapacity: "드라이브 용량" + maxFileSize: "업로드 가능한 최대 파일 크기" alwaysMarkNsfw: "파일을 항상 NSFW로 지정" canUpdateBioMedia: "아바타 및 배너 이미지 변경 허용" pinMax: "고정할 수 있는 노트 수" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index cc59d04595..1fc4342e92 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -250,7 +250,6 @@ noUsers: "Er zijn geen gebruikers." editProfile: "Bewerk Profiel" noteDeleteConfirm: "Ben je zeker dat je dit bericht wil verwijderen?" pinLimitExceeded: "Je kunt geen berichten meer vastprikken" -intro: "Installatie van Misskey geëindigd! Maak nu een beheerder aan." done: "Klaar" processing: "Bezig met verwerken" preview: "Voorbeeld" @@ -784,7 +783,6 @@ thisIsExperimentalFeature: "Dit is een experimentele functie. De functionaliteit developer: "Ontwikkelaar" makeExplorable: "Gebruikersaccount zichtbaar maken in “Verkennen”" makeExplorableDescription: "Als deze optie is uitgeschakeld, is uw gebruikersaccount niet zichtbaar in het gedeelte “Verkennen”." -showGapBetweenNotesInTimeline: "Een gat tussen noten op de tijdlijn weergeven" duplicate: "Dupliceren" left: "Links" center: "Center" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index 44b2cd2704..578183efa5 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -171,7 +171,6 @@ noUsers: "Det er ingen brukere" editProfile: "Rediger profil" noteDeleteConfirm: "Er du sikker på at du vil slette denne Noten?" pinLimitExceeded: "Du kan ikke feste flere." -intro: "Installasjonen av Misskey er ferdig! Vennligst opprett en administratorkonto." done: "Ferdig" default: "Standard" defaultValueIs: "Standard: {value}" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 46f8939137..38ccad9835 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -230,7 +230,6 @@ noUsers: "Brak użytkowników" editProfile: "Edytuj profil" noteDeleteConfirm: "Czy na pewno chcesz usunąć ten wpis?" pinLimitExceeded: "Nie możesz przypiąć więcej wpisów." -intro: "Zakończono instalację Misskey! Utwórz konto administratora." done: "Gotowe" processing: "Przetwarzanie" preview: "Podgląd" @@ -749,7 +748,6 @@ thisIsExperimentalFeature: "Ta funkcja jest eksperymentalna. Jej funkcjonalnoś developer: "Programista" makeExplorable: "Pokazuj konto na stronie „Eksploruj”" makeExplorableDescription: "Jeżeli wyłączysz tę opcję, Twoje konto nie będzie wyświetlać się w sekcji „Eksploruj”." -showGapBetweenNotesInTimeline: "Pokazuj odstęp między wpisami na osi czasu." duplicate: "Duplikuj" left: "Lewo" center: "Wyśsrodkuj" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 3800de6bcf..57978509d3 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -250,7 +250,6 @@ noUsers: "Sem usuários" editProfile: "Editar Perfil" noteDeleteConfirm: "Deseja excluir esta nota?" pinLimitExceeded: "Não é possível fixar novas notas" -intro: "A instalação do Misskey está completa! Crie uma conta de administrador." done: "Concluído" processing: "Em Progresso" preview: "Pré-visualizar" @@ -784,7 +783,6 @@ thisIsExperimentalFeature: "Este é um recurso experimental. As funções podem developer: "Programador" makeExplorable: "Deixe a sua conta encontrável em \"Explorar\"." makeExplorableDescription: "Se você desativá-lo, outros usuários não poderão encontrar a sua conta na aba Descoberta." -showGapBetweenNotesInTimeline: "Mostrar um espaço entre as notas na linha de tempo" duplicate: "Duplicar" left: "Esquerda" center: "Centralizar" @@ -1237,7 +1235,6 @@ showAvatarDecorations: "Exibir decorações de avatar" releaseToRefresh: "Solte para atualizar" refreshing: "Atualizando..." pullDownToRefresh: "Puxe para baixo para atualizar" -disableStreamingTimeline: "Desabilitar atualizações em tempo real da linha do tempo" useGroupedNotifications: "Agrupar notificações" signupPendingError: "Houve um problema ao verificar o endereço de email. O link pode ter expirado." cwNotationRequired: "Se \"Esconder conteúdo\" está habilitado, uma descrição deve ser adicionada." diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index ee2b17cfc5..abfaac7121 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -250,7 +250,6 @@ noUsers: "Niciun utilizator" editProfile: "Editează profilul" noteDeleteConfirm: "Ești sigur(ă) că vrei să ștergi această notă?" pinLimitExceeded: "Nu poți mai fixa mai multe note" -intro: "Misskey s-a instalat! Te rog crează un utilizator admin." done: "Gata" processing: "Se procesează" preview: "Previzualizare" @@ -784,7 +783,6 @@ thisIsExperimentalFeature: "Aceasta este o funcție experimentală. Funcționali developer: "Dezvoltator" makeExplorable: "Fă-ți contul vizibil în secțiunea„Explorați”" makeExplorableDescription: "Dacă dezactivezi această opțiune, contul dvs. nu va fi vizibil în secțiunea\"Explorați\"." -showGapBetweenNotesInTimeline: "Afișați un decalaj între postările de pe cronologie" duplicate: "Duplicat" left: "Stânga" center: "Centru" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index c801126435..5851530786 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -251,7 +251,6 @@ noUsers: "Нет ни одного пользователя" editProfile: "Редактировать профиль" noteDeleteConfirm: "Вы хотите удалить эту заметку?" pinLimitExceeded: "Нельзя закрепить ещё больше заметок" -intro: "Установка Misskey завершена! А теперь создайте учетную запись администратора." done: "Готово" processing: "Обработка" preview: "Предпросмотр" @@ -785,7 +784,6 @@ thisIsExperimentalFeature: "Это экспериментальная функц developer: "Разработчик" makeExplorable: "Опубликовать профиль в «Обзоре»." makeExplorableDescription: "Если выключить, ваш профиль не будет показан в разделе «Обзор»." -showGapBetweenNotesInTimeline: "Показывать разделитель между заметками в ленте" duplicate: "Дубликат" left: "Слева" center: "По центру" @@ -1200,7 +1198,6 @@ privacyPolicyUrl: "Ссылка на Политику Конфиденциаль attach: "Прикрепить" angle: "Угол" flip: "Переворот" -disableStreamingTimeline: "Отключить обновление ленты в режиме реального времени" useGroupedNotifications: "Отображать уведомления сгруппировано" doReaction: "Добавить реакцию" code: "Код" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 1638fd293e..9877080f0c 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -204,7 +204,6 @@ noUsers: "Žiadni používatelia" editProfile: "Upraviť profil" noteDeleteConfirm: "Naozaj chcete odstrániť túto poznámku?" pinLimitExceeded: "Ďalšie poznámky už nemôžete pripnúť." -intro: "Inštalácia Misskey je dokončená! Prosím vytvorte administrátora." done: "Hotovo" processing: "Pracujem..." preview: "Náhľad" @@ -682,7 +681,6 @@ experimentalFeatures: "Experimentálne funkcie" developer: "Vývojár" makeExplorable: "Spraviť účet viditeľný v \"Objavovať\"" makeExplorableDescription: "Ak toto vypnete, váš účet sa nezobrazí v sekcii \"Objavovat\"." -showGapBetweenNotesInTimeline: "Zobraziť medzeru medzi príspevkami časovej osi." duplicate: "Duplikovať" left: "Naľavo" center: "Stred" diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml index ceb02ffc4c..ba6d8a93d2 100644 --- a/locales/sv-SE.yml +++ b/locales/sv-SE.yml @@ -211,7 +211,6 @@ noUsers: "Det finns inga användare" editProfile: "Redigera profil" noteDeleteConfirm: "Är du säker på att du vill ta bort denna not?" pinLimitExceeded: "Du kan inte fästa fler noter" -intro: "Misskey har installerats! Vänligen skapa en adminanvändare." done: "Klar" processing: "Bearbetar..." preview: "Förhandsvisning" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 6398268274..44148b714b 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -250,7 +250,6 @@ noUsers: "ไม่พบผู้ใช้งาน" editProfile: "แก้ไขโปรไฟล์" noteDeleteConfirm: "ต้องการลบโน้ตนี้ใช่ไหม?" pinLimitExceeded: "คุณไม่สามารถปักหมุดโน้ตเพิ่มเติมใดๆได้อีก" -intro: "การติดตั้ง Misskey เสร็จสิ้นแล้วนะ! โปรดสร้างผู้ใช้งานที่เป็นผู้ดูแลระบบ" done: "เสร็จสิ้น" processing: "กำลังประมวลผล..." preview: "แสดงตัวอย่าง" @@ -778,7 +777,6 @@ thisIsExperimentalFeature: "นี่เป็นฟีเจอร์ทดล developer: "สำหรับนักพัฒนา" makeExplorable: "ทำให้บัญชีมองเห็นใน “สำรวจ”" makeExplorableDescription: "ถ้าหากคุณปิดการทำงานนี้ บัญชีของคุณนั้นจะไม่แสดงในส่วน “สำรวจ”" -showGapBetweenNotesInTimeline: "แสดงช่องว่างระหว่างโพสต์บนไทม์ไลน์" duplicate: "ทำซ้ำ" left: "ซ้าย" center: "กึ่งกลาง" @@ -1227,7 +1225,6 @@ showAvatarDecorations: "แสดงตกแต่งอวตาร" releaseToRefresh: "ปล่อยเพื่อรีเฟรช" refreshing: "กำลังรีเฟรช..." pullDownToRefresh: "ดึงลงเพื่อรีเฟรช" -disableStreamingTimeline: "ปิดใช้งานอัปเดตไทม์ไลน์แบบเรียลไทม์" useGroupedNotifications: "แสดงผลการแจ้งเตือนแบบกลุ่มแล้ว" signupPendingError: "มีปัญหาในการตรวจสอบที่อยู่อีเมลลิงก์อาจหมดอายุแล้ว" cwNotationRequired: "หากเปิดใช้งาน “ซ่อนเนื้อหา” จะต้องระบุคำอธิบาย" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index 1756e8dbeb..f63dcc9467 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -224,7 +224,6 @@ noUsers: "Kullanıcı yok" editProfile: "Profili düzenle" noteDeleteConfirm: "Bu notu silmek istediğinizden emin misiniz?" pinLimitExceeded: "Daha fazla not sabitlenemez" -intro: "Misskey yüklemesi tamamlandı! Lütfen yönetici hesabını oluşturun." done: "Tamamlandı" preview: "Önizleme" default: "Varsayılan" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 6a970fadfb..8ddfd8f5a1 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -208,7 +208,6 @@ noUsers: "Немає користувачів" editProfile: "Редагувати обліковий запис" noteDeleteConfirm: "Ви дійсно хочете видалити цей запис?" pinLimitExceeded: "Більше записів не можна закріпити" -intro: "Встановлення Misskey завершено! Будь ласка, створіть обліковий запис адміністратора." done: "Готово" processing: "Обробка" preview: "Попередній перегляд" @@ -681,7 +680,6 @@ experimentalFeatures: "Експериментальні функції" developer: "Розробник" makeExplorable: "Зробіть обліковий запис видимим у розділі \"Огляд\"" makeExplorableDescription: "Вимкніть, щоб обліковий запис не показувався у розділі \"Огляд\"." -showGapBetweenNotesInTimeline: "Показувати розрив між записами у стрічці новин" duplicate: "Дублікат" left: "Лівий" center: "Центр" diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml index 8289a6d60c..612df9e43c 100644 --- a/locales/uz-UZ.yml +++ b/locales/uz-UZ.yml @@ -219,7 +219,6 @@ noUsers: "Foydalanuvchilar yo‘q" editProfile: "Profilni o'zgartirish" noteDeleteConfirm: "Haqiqatan ham bu qaydni oʻchirib tashlamoqchimisiz?" pinLimitExceeded: "Siz boshqa qaydlarni mahkamlay olmaysiz" -intro: "Misskeyni o'rnatish tugallandi! Iltimos, administrator foydalanuvchi yarating." done: "Bajarildi" processing: "Amaliyotda" preview: "Ko'rish" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 525c3c233f..1fe9347711 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -250,7 +250,6 @@ noUsers: "Chưa có ai" editProfile: "Sửa hồ sơ" noteDeleteConfirm: "Bạn có chắc muốn xóa tút này?" pinLimitExceeded: "Bạn không thể ghim bài viết nữa" -intro: "Đã cài đặt Misskey! Xin hãy tạo tài khoản admin." done: "Xong" processing: "Đang xử lý" preview: "Xem trước" @@ -783,7 +782,6 @@ thisIsExperimentalFeature: "Tính năng này đang trong quá trình thử nghi developer: "Nhà phát triển" makeExplorable: "Không hiện tôi trong \"Khám phá\"" makeExplorableDescription: "Nếu bạn tắt, tài khoản của bạn sẽ không hiện trong mục \"Khám phá\"." -showGapBetweenNotesInTimeline: "Hiện dải phân cách giữa các tút trên bảng tin" duplicate: "Tạo bản sao" left: "Bên trái" center: "Giữa" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 28053bb7b0..ed9a4a3e66 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -251,7 +251,6 @@ noUsers: "无用户" editProfile: "编辑资料" noteDeleteConfirm: "确定要删除该帖子吗?" pinLimitExceeded: "无法置顶更多了" -intro: "Misskey 的部署结束啦!创建管理员账号吧!" done: "完成" processing: "正在处理" preview: "预览" @@ -785,7 +784,6 @@ thisIsExperimentalFeature: "这是一项实验性功能。规范可能会变更 developer: "开发者" makeExplorable: "使账号可见。" makeExplorableDescription: "关闭时,账号不会显示在\"发现\"中。" -showGapBetweenNotesInTimeline: "时间线上的帖子分开显示。" duplicate: "复制" left: "左" center: "中央" @@ -1238,7 +1236,6 @@ showAvatarDecorations: "显示头像挂件" releaseToRefresh: "松开以刷新" refreshing: "刷新中" pullDownToRefresh: "下拉以刷新" -disableStreamingTimeline: "禁止实时更新时间线" useGroupedNotifications: "分组显示通知" signupPendingError: "确认电子邮件时出现错误。链接可能已过期。" cwNotationRequired: "在启用「隐藏内容」时必须输入注释" @@ -1348,6 +1345,7 @@ readonly: "只读" goToDeck: "返回至 Deck" federationJobs: "联合作业" driveAboutTip: "网盘可以显示以前上传的文件。
\n也可以在发布帖子时重复使用文件,或在发布帖子前预先上传文件。
\n删除文件时,其将从至今为止所有用到该文件的地方(如帖子、页面、头像、横幅)消失。
\n也可以新建文件夹来整理文件。" +scrollToClose: "滑动并关闭" _chat: noMessagesYet: "还没有消息" newMessage: "新消息" @@ -1432,6 +1430,7 @@ _preferencesProfile: profileName: "配置名" profileNameDescription: "请指定用于识别此设备的名称" profileNameDescription2: "如「PC」、「手机」等" + manageProfiles: "管理配置文件" _preferencesBackup: autoBackup: "自动备份" restoreFromBackup: "从备份恢复" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 1c15fd48d1..5900bccff7 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -251,7 +251,6 @@ noUsers: "沒有任何使用者" editProfile: "編輯個人檔案" noteDeleteConfirm: "確定刪除此貼文嗎?" pinLimitExceeded: "不能置頂更多貼文了" -intro: "Misskey 部署完成!請建立管理員帳戶。" done: "完成" processing: "處理中" preview: "預覽" @@ -785,7 +784,6 @@ thisIsExperimentalFeature: "這是一項實驗性功能,其行為會隨需要 developer: "開發者" makeExplorable: "使自己的帳戶更容易被找到" makeExplorableDescription: "如果關閉,帳戶將不會被顯示在「探索」頁面中。" -showGapBetweenNotesInTimeline: "分開顯示時間軸上的貼文" duplicate: "複製" left: "左" center: "置中" @@ -1238,7 +1236,6 @@ showAvatarDecorations: "顯示頭像裝飾" releaseToRefresh: "放開以更新內容" refreshing: "載入更新中" pullDownToRefresh: "往下拉來更新內容" -disableStreamingTimeline: "停用時間軸的即時更新" useGroupedNotifications: "分組顯示通知訊息" signupPendingError: "驗證您的電子郵件地址時出現問題。連結可能已過期。" cwNotationRequired: "如果開啟「隱藏內容」,則需要註解說明。" @@ -1434,6 +1431,7 @@ _preferencesProfile: profileName: "設定檔案名稱" profileNameDescription: "設定一個名稱來識別此裝置。" profileNameDescription2: "例如:「主要個人電腦」、「智慧型手機」等" + manageProfiles: "管理個人檔案" _preferencesBackup: autoBackup: "自動備份" restoreFromBackup: "從備份還原" diff --git a/package.json b/package.json index ff08cbe97b..f5210a545d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.5.0", + "version": "2025.5.1-alpha.0", "codename": "nasubi", "repository": { "type": "git", @@ -34,7 +34,7 @@ "watch": "pnpm dev", "dev": "node scripts/dev.mjs", "lint": "pnpm -r lint", - "cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts", + "cy:open": "pnpm cypress open --config-file=cypress.config.ts", "cy:run": "pnpm cypress run", "e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run", "e2e-dev-container": "ncp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run", diff --git a/packages/backend/migration/1746330901644-visibleUserGeneratedContentsForNonLoggedInVisitors.js b/packages/backend/migration/1746330901644-visibleUserGeneratedContentsForNonLoggedInVisitors.js new file mode 100644 index 0000000000..115698a420 --- /dev/null +++ b/packages/backend/migration/1746330901644-visibleUserGeneratedContentsForNonLoggedInVisitors.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class VisibleUserGeneratedContentsForNonLoggedInVisitors1746330901644 { + name = 'VisibleUserGeneratedContentsForNonLoggedInVisitors1746330901644' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "ugcVisibilityForVisitor" character varying(128) NOT NULL DEFAULT 'local'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "ugcVisibilityForVisitor"`); + } +} diff --git a/packages/backend/migration/1746422049376-singleUserMode.js b/packages/backend/migration/1746422049376-singleUserMode.js new file mode 100644 index 0000000000..9a79d46d5b --- /dev/null +++ b/packages/backend/migration/1746422049376-singleUserMode.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SingleUserMode1746422049376 { + name = 'SingleUserMode1746422049376' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "singleUserMode" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "singleUserMode"`); + } +} diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts index 9d294a80cb..2ceff341cc 100644 --- a/packages/backend/src/core/ChatService.ts +++ b/packages/backend/src/core/ChatService.ts @@ -578,6 +578,20 @@ export class ChatService { @bindThis public async deleteRoom(room: MiChatRoom, deleter?: MiUser) { + const memberships = (await this.chatRoomMembershipsRepository.findBy({ roomId: room.id })).map(m => ({ + userId: m.userId, + })).concat({ // ownerはmembershipレコードを作らないため + userId: room.ownerId, + }); + + // 未読フラグ削除 + const redisPipeline = this.redisClient.pipeline(); + for (const membership of memberships) { + redisPipeline.del(`newRoomChatMessageExists:${membership.userId}:${room.id}`); + redisPipeline.srem(`newChatMessagesExists:${membership.userId}`, `room:${room.id}`); + } + await redisPipeline.exec(); + await this.chatRoomsRepository.delete(room.id); if (deleter) { @@ -709,6 +723,12 @@ export class ChatService { public async leaveRoom(userId: MiUser['id'], roomId: MiChatRoom['id']) { const membership = await this.chatRoomMembershipsRepository.findOneByOrFail({ roomId, userId }); await this.chatRoomMembershipsRepository.delete(membership.id); + + // 未読フラグを消す (「既読にする」というわけでもないのでreadメソッドは使わないでおく) + const redisPipeline = this.redisClient.pipeline(); + redisPipeline.del(`newRoomChatMessageExists:${userId}:${roomId}`); + redisPipeline.srem(`newChatMessagesExists:${userId}`, `room:${roomId}`); + await redisPipeline.exec(); } @bindThis diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 97f1c3d739..92caad908c 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -429,6 +429,7 @@ export class NoteEntityService implements OnModuleInit { userId: channel.userId, } : undefined, mentions: note.mentions.length > 0 ? note.mentions : undefined, + hasPoll: note.hasPoll || undefined, uri: note.uri ?? undefined, url: note.url ?? undefined, @@ -593,4 +594,42 @@ export class NoteEntityService implements OnModuleInit { relations: ['user'], }); } + + @bindThis + public async fetchDiffs(noteIds: MiNote['id'][]) { + if (noteIds.length === 0) return []; + + const notes = await this.notesRepository.find({ + where: { + id: In(noteIds), + }, + select: { + id: true, + userHost: true, + reactions: true, + reactionAndUserPairCache: true, + }, + }); + + const bufferedReactionsMap = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(noteIds) : null; + + const packings = notes.map(note => { + const bufferedReactions = bufferedReactionsMap?.get(note.id); + //const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/'))); + + const reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.deltas ?? {})); + + const reactionEmojiNames = Object.keys(reactions) + .filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ + .map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', '')); + + return this.customEmojiService.populateEmojis(reactionEmojiNames, note.userHost).then(reactionEmojis => ({ + id: note.id, + reactions, + reactionEmojis, + })); + }); + + return await Promise.all(packings); + } } diff --git a/packages/backend/src/models/Antenna.ts b/packages/backend/src/models/Antenna.ts index 17ec0c0f79..ccc8823703 100644 --- a/packages/backend/src/models/Antenna.ts +++ b/packages/backend/src/models/Antenna.ts @@ -106,3 +106,6 @@ export class MiAntenna { }) public excludeNotesInSensitiveChannel: boolean; } +// Note for future developers: When you added a new column, +// You should update ExportAntennaProcessorService and ImportAntennaProcessorService +// to export and import antennas correctly. diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 46f3b2e3c0..e3625b247c 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -659,6 +659,12 @@ export class MiMeta { }) public federationHosts: string[]; + @Column('varchar', { + length: 128, + default: 'local', + }) + public ugcVisibilityForVisitor: 'all' | 'local' | 'none'; + @Column('varchar', { length: 64, nullable: true, @@ -669,6 +675,11 @@ export class MiMeta { default: [], }) public deliverSuspendedSoftware: SoftwareSuspension[]; + + @Column('boolean', { + default: false, + }) + public singleUserMode: boolean; } export type SoftwareSuspension = { diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index 432c096e48..f3901691a4 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -256,6 +256,10 @@ export const packedNoteSchema = { type: 'number', optional: true, nullable: false, }, + hasPoll: { + type: 'boolean', + optional: true, nullable: false, + }, myReaction: { type: 'string', diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index b3111865ad..053ba99005 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -15,6 +15,7 @@ import { bindThis } from '@/decorators.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { ExportedAntenna } from '@/queue/processors/ImportAntennasProcessorService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { DBExportAntennasData } from '../types.js'; import type * as Bull from 'bullmq'; @@ -86,7 +87,8 @@ export class ExportAntennasProcessorService { excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, - })); + excludeNotesInSensitiveChannel: antenna.excludeNotesInSensitiveChannel, + } satisfies Required)); if (antennas.length - 1 !== index) { write(', '); } diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index 9c033b73e2..4c7f2d09bb 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -11,17 +11,18 @@ import Logger from '@/logger.js'; import type { AntennasRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; +import { Schema, SchemaType } from '@/misc/json-schema.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import { DBAntennaImportJobData } from '../types.js'; import type * as Bull from 'bullmq'; const Ajv = _Ajv.default; -const validate = new Ajv().compile({ +const exportedAntennaSchema = { type: 'object', properties: { name: { type: 'string', minLength: 1, maxLength: 100 }, - src: { type: 'string', enum: ['home', 'all', 'users', 'list'] }, + src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'users_blacklist'] }, userListAccts: { type: 'array', items: { @@ -47,9 +48,14 @@ const validate = new Ajv().compile({ excludeBots: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, + excludeNotesInSensitiveChannel: { type: 'boolean' }, }, required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'], -}); +} as const satisfies Schema; + +export type ExportedAntenna = SchemaType; + +const validate = new Ajv().compile(exportedAntennaSchema); @Injectable() export class ImportAntennasProcessorService { @@ -91,6 +97,7 @@ export class ImportAntennasProcessorService { excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, + excludeNotesInSensitiveChannel: antenna.excludeNotesInSensitiveChannel, }); this.logger.succ('Antenna created: ' + result.id); this.globalEventService.publishInternalEvent('antennaCreated', result); diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index e5170aa2dc..bd466b3cad 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -323,6 +323,7 @@ export * as 'notes/replies' from './endpoints/notes/replies.js'; export * as 'notes/search' from './endpoints/notes/search.js'; export * as 'notes/search-by-tag' from './endpoints/notes/search-by-tag.js'; export * as 'notes/show' from './endpoints/notes/show.js'; +export * as 'notes/show-partial-bulk' from './endpoints/notes/show-partial-bulk.js'; export * as 'notes/state' from './endpoints/notes/state.js'; export * as 'notes/thread-muting/create' from './endpoints/notes/thread-muting/create.js'; export * as 'notes/thread-muting/delete' from './endpoints/notes/thread-muting/delete.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 4a106e7175..cd36985485 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -546,6 +546,15 @@ export const meta = { }, }, }, + singleUserMode: { + type: 'boolean', + optional: false, nullable: false, + }, + ugcVisibilityForVisitor: { + type: 'string', + enum: ['all', 'local', 'none'], + optional: false, nullable: false, + }, }, }, } as const; @@ -691,6 +700,8 @@ export default class extends Endpoint { // eslint- federation: instance.federation, federationHosts: instance.federationHosts, deliverSuspendedSoftware: instance.deliverSuspendedSoftware, + singleUserMode: instance.singleUserMode, + ugcVisibilityForVisitor: instance.ugcVisibilityForVisitor, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 31eeaa5e38..a96fbd759c 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -196,6 +196,11 @@ export const paramDef = { required: ['software', 'versionRange'], }, }, + singleUserMode: { type: 'boolean' }, + ugcVisibilityForVisitor: { + type: 'string', + enum: ['all', 'local', 'none'], + }, }, required: [], } as const; @@ -690,6 +695,14 @@ export default class extends Endpoint { // eslint- set.federationHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase()); } + if (ps.singleUserMode !== undefined) { + set.singleUserMode = ps.singleUserMode; + } + + if (ps.ugcVisibilityForVisitor !== undefined) { + set.ugcVisibilityForVisitor = ps.ugcVisibilityForVisitor; + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts b/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts new file mode 100644 index 0000000000..87b368e17e --- /dev/null +++ b/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import { GetterService } from '@/server/api/GetterService.js'; +import { ApiError } from '../../error.js'; + +export const meta = { + tags: ['notes'], + + requireCredential: false, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + }, + }, + + errors: { + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + noteIds: { type: 'array', items: { type: 'string', format: 'misskey:id' }, maxItems: 100, minItems: 1 }, + }, + required: ['noteIds'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private noteEntityService: NoteEntityService, + ) { + super(meta, paramDef, async (ps, me) => { + return await this.noteEntityService.fetchDiffs(ps.noteIds); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 11839bce36..b93c73b0c5 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -3,10 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { GetterService } from '@/server/api/GetterService.js'; +import { DI } from '@/di-symbols.js'; +import { MiMeta } from '@/models/Meta.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -46,6 +48,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + private noteEntityService: NoteEntityService, private getterService: GetterService, ) { @@ -59,6 +64,14 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.signinRequired); } + if (this.serverSettings.ugcVisibilityForVisitor === 'none' && me == null) { + throw new ApiError(meta.errors.signinRequired); + } + + if (this.serverSettings.ugcVisibilityForVisitor === 'local' && note.userHost != null && me == null) { + throw new ApiError(meta.errors.signinRequired); + } + return await this.noteEntityService.pack(note, me, { detail: true, }); diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 062326e28d..431869d47f 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -5,7 +5,7 @@ import { In, IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { UsersRepository } from '@/models/_.js'; +import type { MiMeta, UsersRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -82,6 +82,9 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.meta) + private serverSettings: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -92,6 +95,10 @@ export default class extends Endpoint { // eslint- private apiLoggerService: ApiLoggerService, ) { super(meta, paramDef, async (ps, me, _1, _2, _3, ip) => { + if (this.serverSettings.ugcVisibilityForVisitor === 'none' && me == null) { + throw new ApiError(meta.errors.noSuchUser); + } + let user; const isModerator = await this.roleService.isModerator(me); @@ -123,6 +130,10 @@ export default class extends Endpoint { // eslint- } else { // Lookup user if (typeof ps.host === 'string' && typeof ps.username === 'string') { + if (this.serverSettings.ugcVisibilityForVisitor === 'local' && me == null) { + throw new ApiError(meta.errors.noSuchUser); + } + user = await this.remoteUserResolveService.resolveUser(ps.username, ps.host).catch(err => { this.apiLoggerService.logger.warn(`failed to resolve remote user: ${err}`); throw new ApiError(meta.errors.failedToResolveRemoteUser); @@ -139,6 +150,10 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchUser); } + if (this.serverSettings.ugcVisibilityForVisitor === 'local' && user.host != null && me == null) { + throw new ApiError(meta.errors.noSuchUser); + } + if (user.host == null) { if (me == null && ip != null) { this.perUserPvChart.commitByVisitor(user, ip); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 30a911088e..9a33d27d86 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -513,7 +513,12 @@ export class ClientServerService { vary(reply.raw, 'Accept'); - if (user != null) { + if ( + user != null && ( + this.meta.ugcVisibilityForVisitor === 'all' || + (this.meta.ugcVisibilityForVisitor === 'local' && user.host == null) + ) + ) { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); const me = profile.fields ? profile.fields @@ -577,7 +582,13 @@ export class ClientServerService { relations: ['user'], }); - if (note && !note.user!.requireSigninToViewContents) { + if ( + note && + !note.user!.requireSigninToViewContents && + (this.meta.ugcVisibilityForVisitor === 'all' || + (this.meta.ugcVisibilityForVisitor === 'local' && note.userHost == null) + ) + ) { const _note = await this.noteEntityService.pack(note); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId }); reply.header('Cache-Control', 'public, max-age=15'); diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index fad6ce3825..ae4e0445db 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -79,39 +79,6 @@ export async function mainBoot() { } } - const stream = useStream(); - - let reloadDialogShowing = false; - stream.on('_disconnected_', async () => { - if (prefer.s.serverDisconnectedBehavior === 'reload') { - window.location.reload(); - } else if (prefer.s.serverDisconnectedBehavior === 'dialog') { - if (reloadDialogShowing) return; - reloadDialogShowing = true; - const { canceled } = await confirm({ - type: 'warning', - title: i18n.ts.disconnectedFromServer, - text: i18n.ts.reloadConfirm, - }); - reloadDialogShowing = false; - if (!canceled) { - window.location.reload(); - } - } - }); - - stream.on('emojiAdded', emojiData => { - addCustomEmoji(emojiData.emoji); - }); - - stream.on('emojiUpdated', emojiData => { - updateCustomEmojis(emojiData.emojis); - }); - - stream.on('emojiDeleted', emojiData => { - removeCustomEmojis(emojiData.emojis); - }); - launchPlugins(); try { @@ -169,8 +136,6 @@ export async function mainBoot() { } } - stream.on('announcementCreated', onAnnouncementCreated); - if ($i.isDeleted) { alert({ type: 'warning', @@ -348,50 +313,81 @@ export async function mainBoot() { } } - const main = markRaw(stream.useChannel('main', null, 'System')); + if (store.s.realtimeMode) { + const stream = useStream(); - // 自分の情報が更新されたとき - main.on('meUpdated', i => { - updateCurrentAccountPartial(i); - }); - - main.on('readAllNotifications', () => { - updateCurrentAccountPartial({ - hasUnreadNotification: false, - unreadNotificationsCount: 0, + let reloadDialogShowing = false; + stream.on('_disconnected_', async () => { + if (prefer.s.serverDisconnectedBehavior === 'reload') { + window.location.reload(); + } else if (prefer.s.serverDisconnectedBehavior === 'dialog') { + if (reloadDialogShowing) return; + reloadDialogShowing = true; + const { canceled } = await confirm({ + type: 'warning', + title: i18n.ts.disconnectedFromServer, + text: i18n.ts.reloadConfirm, + }); + reloadDialogShowing = false; + if (!canceled) { + window.location.reload(); + } + } }); - }); - main.on('unreadNotification', () => { - const unreadNotificationsCount = ($i?.unreadNotificationsCount ?? 0) + 1; - updateCurrentAccountPartial({ - hasUnreadNotification: true, - unreadNotificationsCount, + stream.on('emojiAdded', emojiData => { + addCustomEmoji(emojiData.emoji); }); - }); - main.on('unreadAntenna', () => { - updateCurrentAccountPartial({ hasUnreadAntenna: true }); - sound.playMisskeySfx('antenna'); - }); + stream.on('emojiUpdated', emojiData => { + updateCustomEmojis(emojiData.emojis); + }); - main.on('newChatMessage', () => { - updateCurrentAccountPartial({ hasUnreadChatMessages: true }); - sound.playMisskeySfx('chatMessage'); - }); + stream.on('emojiDeleted', emojiData => { + removeCustomEmojis(emojiData.emojis); + }); - main.on('readAllAnnouncements', () => { - updateCurrentAccountPartial({ hasUnreadAnnouncement: false }); - }); + stream.on('announcementCreated', onAnnouncementCreated); - // 個人宛てお知らせが発行されたとき - main.on('announcementCreated', onAnnouncementCreated); + const main = markRaw(stream.useChannel('main', null, 'System')); - // トークンが再生成されたとき - // このままではMisskeyが利用できないので強制的にサインアウトさせる - main.on('myTokenRegenerated', () => { - signout(); - }); + // 自分の情報が更新されたとき + main.on('meUpdated', i => { + updateCurrentAccountPartial(i); + }); + + main.on('readAllNotifications', () => { + updateCurrentAccountPartial({ + hasUnreadNotification: false, + unreadNotificationsCount: 0, + }); + }); + + main.on('unreadNotification', () => { + const unreadNotificationsCount = ($i?.unreadNotificationsCount ?? 0) + 1; + updateCurrentAccountPartial({ + hasUnreadNotification: true, + unreadNotificationsCount, + }); + }); + + main.on('unreadAntenna', () => { + updateCurrentAccountPartial({ hasUnreadAntenna: true }); + sound.playMisskeySfx('antenna'); + }); + + main.on('newChatMessage', () => { + updateCurrentAccountPartial({ hasUnreadChatMessages: true }); + sound.playMisskeySfx('chatMessage'); + }); + + main.on('readAllAnnouncements', () => { + updateCurrentAccountPartial({ hasUnreadAnnouncement: false }); + }); + + // 個人宛てお知らせが発行されたとき + main.on('announcementCreated', onAnnouncementCreated); + } } // shortcut diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue index d0b50f04f2..5562be682b 100644 --- a/packages/frontend/src/components/MkChannelList.vue +++ b/packages/frontend/src/components/MkChannelList.vue @@ -14,13 +14,13 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -60,7 +84,6 @@ $height: 2ex; height: $height; border-radius: 4px 0 0 4px; overflow: clip; - color: #fff; // text-shadowは重いから使うな diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 980636f551..9b7658292d 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -6,11 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 54da5a889d..37e15df39b 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -4,483 +4,74 @@ SPDX-License-Identifier: AGPL-3.0-only --> - - diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 2d3ec45bca..359ee08812 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -29,19 +29,21 @@ import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js'; import { useTooltip } from '@/use/use-tooltip.js'; import { $i } from '@/i.js'; import MkReactionEffect from '@/components/MkReactionEffect.vue'; -import { claimAchievement } from '@/utility/achievements.js'; import { i18n } from '@/i18n.js'; import * as sound from '@/utility/sound.js'; import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js'; import { customEmojisMap } from '@/custom-emojis.js'; import { prefer } from '@/preferences.js'; import { DI } from '@/di.js'; +import { noteEvents } from '@/use/use-note-capture.js'; const props = defineProps<{ + noteId: Misskey.entities.Note['id']; reaction: string; + reactionEmojis: Misskey.entities.Note['reactionEmojis']; + myReaction: Misskey.entities.Note['myReaction']; count: number; isInitial: boolean; - note: Misskey.entities.Note; }>(); const mock = inject(DI.mock, false); @@ -56,14 +58,16 @@ const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction)); const canToggle = computed(() => { - return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value); + // TODO + //return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value); + return !props.reaction.match(/@\w/) && $i && emoji.value; }); const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':')); async function toggleReaction() { if (!canToggle.value) return; - const oldReaction = props.note.myReaction; + const oldReaction = props.myReaction; if (oldReaction) { const confirm = await os.confirm({ type: 'warning', @@ -81,12 +85,23 @@ async function toggleReaction() { } misskeyApi('notes/reactions/delete', { - noteId: props.note.id, + noteId: props.noteId, }).then(() => { + noteEvents.emit(`unreacted:${props.noteId}`, { + userId: $i!.id, + reaction: props.reaction, + emoji: emoji.value, + }); if (oldReaction !== props.reaction) { misskeyApi('notes/reactions/create', { - noteId: props.note.id, + noteId: props.noteId, reaction: props.reaction, + }).then(() => { + noteEvents.emit(`reacted:${props.noteId}`, { + userId: $i!.id, + reaction: props.reaction, + emoji: emoji.value, + }); }); } }); @@ -108,12 +123,19 @@ async function toggleReaction() { } misskeyApi('notes/reactions/create', { - noteId: props.note.id, + noteId: props.noteId, reaction: props.reaction, + }).then(() => { + noteEvents.emit(`reacted:${props.noteId}`, { + userId: $i!.id, + reaction: props.reaction, + emoji: emoji.value, + }); }); - if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) { - claimAchievement('reactWithoutRead'); - } + // TODO: 上位コンポーネントでやる + //if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) { + // claimAchievement('reactWithoutRead'); + //} } } @@ -157,7 +179,7 @@ onMounted(() => { if (!mock) { useTooltip(buttonEl, async (showing) => { const reactions = await misskeyApiGet('notes/reactions', { - noteId: props.note.id, + noteId: props.noteId, type: props.reaction, limit: 10, _cacheKey_: props.count, diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue index e8cf6c36db..725978179e 100644 --- a/packages/frontend/src/components/MkReactionsViewer.vue +++ b/packages/frontend/src/components/MkReactionsViewer.vue @@ -13,7 +13,17 @@ SPDX-License-Identifier: AGPL-3.0-only :moveClass="$style.transition_x_move" tag="div" :class="$style.root" > - + @@ -27,7 +37,10 @@ import { prefer } from '@/preferences.js'; import { DI } from '@/di.js'; const props = withDefaults(defineProps<{ - note: Misskey.entities.Note; + noteId: Misskey.entities.Note['id']; + reactions: Misskey.entities.Note['reactions']; + reactionEmojis: Misskey.entities.Note['reactionEmojis']; + myReaction: Misskey.entities.Note['myReaction']; maxNumber?: number; }>(), { maxNumber: Infinity, @@ -39,33 +52,33 @@ const emit = defineEmits<{ (ev: 'mockUpdateMyReaction', emoji: string, delta: number): void; }>(); -const initialReactions = new Set(Object.keys(props.note.reactions)); +const initialReactions = new Set(Object.keys(props.reactions)); -const reactions = ref<[string, number][]>([]); +const _reactions = ref<[string, number][]>([]); const hasMoreReactions = ref(false); -if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.myReaction)) { - reactions.value[props.note.myReaction] = props.note.reactions[props.note.myReaction]; +if (props.myReaction && !Object.keys(_reactions.value).includes(props.myReaction)) { + _reactions.value[props.myReaction] = props.reactions[props.myReaction]; } function onMockToggleReaction(emoji: string, count: number) { if (!mock) return; - const i = reactions.value.findIndex((item) => item[0] === emoji); + const i = _reactions.value.findIndex((item) => item[0] === emoji); if (i < 0) return; - emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1])); + emit('mockUpdateMyReaction', emoji, (count - _reactions.value[i][1])); } -watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => { +watch([() => props.reactions, () => props.maxNumber], ([newSource, maxNumber]) => { let newReactions: [string, number][] = []; hasMoreReactions.value = Object.keys(newSource).length > maxNumber; - for (let i = 0; i < reactions.value.length; i++) { - const reaction = reactions.value[i][0]; + for (let i = 0; i < _reactions.value.length; i++) { + const reaction = _reactions.value[i][0]; if (reaction in newSource && newSource[reaction] !== 0) { - reactions.value[i][1] = newSource[reaction]; - newReactions.push(reactions.value[i]); + _reactions.value[i][1] = newSource[reaction]; + newReactions.push(_reactions.value[i]); } } @@ -79,11 +92,11 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe newReactions = newReactions.slice(0, props.maxNumber); - if (props.note.myReaction && !newReactions.map(([x]) => x).includes(props.note.myReaction)) { - newReactions.push([props.note.myReaction, newSource[props.note.myReaction]]); + if (props.myReaction && !newReactions.map(([x]) => x).includes(props.myReaction)) { + newReactions.push([props.myReaction, newSource[props.myReaction]]); } - reactions.value = newReactions; + _reactions.value = newReactions; }, { immediate: true, deep: true }); diff --git a/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue b/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue index cb50df1743..abe6466971 100644 --- a/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue +++ b/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue @@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/components/MkStreamingNotesTimeline.vue b/packages/frontend/src/components/MkStreamingNotesTimeline.vue new file mode 100644 index 0000000000..75b2d10100 --- /dev/null +++ b/packages/frontend/src/components/MkStreamingNotesTimeline.vue @@ -0,0 +1,531 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue b/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue new file mode 100644 index 0000000000..931f6ae115 --- /dev/null +++ b/packages/frontend/src/components/MkStreamingNotificationsTimeline.vue @@ -0,0 +1,199 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 5fb37ce8dc..06b19880d2 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -18,8 +18,16 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.poll }} - +
+ ({{ i18n.ts.poll }}) diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue deleted file mode 100644 index 6a265aa836..0000000000 --- a/packages/frontend/src/components/MkTimeline.vue +++ /dev/null @@ -1,372 +0,0 @@ - - - - - - - diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue index 59e1b096ae..95f53e7635 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Note.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue @@ -76,8 +76,6 @@ const onceReacted = ref(false); function addReaction(emoji) { onceReacted.value = true; emit('reacted'); - exampleNote.reactions[emoji] = 1; - exampleNote.myReaction = emoji; doNotification(emoji); } diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue index aaefa5036a..8ec48dcc3f 100644 --- a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue +++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue @@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue index 69a654595a..675e82a71d 100644 --- a/packages/frontend/src/pages/welcome.setup.vue +++ b/packages/frontend/src/pages/welcome.setup.vue @@ -6,39 +6,126 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index ce8efa3324..7cfedc939f 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -10,6 +10,9 @@ SPDX-License-Identifier: AGPL-3.0-only +
@@ -50,6 +53,9 @@ SPDX-License-Identifier: AGPL-3.0-only + @@ -76,16 +82,18 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
-
- - - - - - -
+ @@ -108,15 +116,16 @@ const router = useRouter(); const props = defineProps<{ showWidgetButton?: boolean; + asDrawer?: boolean; }>(); const emit = defineEmits<{ (ev: 'widgetButtonClick'): void; }>(); -const forceIconOnly = ref(window.innerWidth <= 1279); +const forceIconOnly = ref(!props.asDrawer && window.innerWidth <= 1279); const iconOnly = computed(() => { - return forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon'); + return !props.asDrawer && (forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon')); }); const otherMenuItemIndicated = computed(() => { @@ -147,6 +156,20 @@ function toggleIconOnly() { } } +function toggleRealtimeMode(ev: MouseEvent) { + os.popupMenu([{ + type: 'label', + text: i18n.ts.realtimeMode, + }, { + text: store.s.realtimeMode ? i18n.ts.turnItOff : i18n.ts.turnItOn, + icon: store.s.realtimeMode ? 'ti ti-bolt-off' : 'ti ti-bolt', + action: () => { + store.set('realtimeMode', !store.s.realtimeMode); + window.location.reload(); + }, + }], ev.currentTarget ?? ev.target); +} + function openAccountMenu(ev: MouseEvent) { openAccountMenu_({ withExtraOperation: true, @@ -191,21 +214,108 @@ function menuEdit() { overscroll-behavior: contain; background: var(--MI_THEME-navBg); contain: strict; + + /* 画面が縦に長い、設置している項目数が少ないなどの環境においても確実にbottomを最下部に表示するため */ display: flex; flex-direction: column; - direction: rtl; // スクロールバーを左に表示したいため + + direction: rtl; /* スクロールバーを左に表示したいため */ } .top { + flex-shrink: 0; direction: ltr; + + /* 疑似progressive blur */ + &::before { + position: absolute; + z-index: -1; + inset: 0; + content: ""; + backdrop-filter: blur(8px); + mask-image: linear-gradient( + to top, + rgb(0 0 0 / 0%) 0%, + rgb(0 0 0 / 4.9%) 7.75%, + rgb(0 0 0 / 10.4%) 11.25%, + rgb(0 0 0 / 45%) 23.55%, + rgb(0 0 0 / 55%) 26.45%, + rgb(0 0 0 / 89.6%) 38.75%, + rgb(0 0 0 / 95.1%) 42.25%, + rgb(0 0 0 / 100%) 50% + ); + } + + &::after { + position: absolute; + z-index: -1; + inset: 0; + bottom: 25%; + content: ""; + backdrop-filter: blur(16px); + mask-image: linear-gradient( + to top, + rgb(0 0 0 / 0%) 0%, + rgb(0 0 0 / 4.9%) 15.5%, + rgb(0 0 0 / 10.4%) 22.5%, + rgb(0 0 0 / 45%) 47.1%, + rgb(0 0 0 / 55%) 52.9%, + rgb(0 0 0 / 89.6%) 77.5%, + rgb(0 0 0 / 95.1%) 91.9%, + rgb(0 0 0 / 100%) 100% + ); + } } .middle { + flex: 1; direction: ltr; } .bottom { + flex-shrink: 0; direction: ltr; + + /* 疑似progressive blur */ + &::before { + position: absolute; + z-index: -1; + inset: -30px 0 0 0; + content: ""; + backdrop-filter: blur(8px); + mask-image: linear-gradient( + to bottom, + rgb(0 0 0 / 0%) 0%, + rgb(0 0 0 / 4.9%) 7.75%, + rgb(0 0 0 / 10.4%) 11.25%, + rgb(0 0 0 / 45%) 23.55%, + rgb(0 0 0 / 55%) 26.45%, + rgb(0 0 0 / 89.6%) 38.75%, + rgb(0 0 0 / 95.1%) 42.25%, + rgb(0 0 0 / 100%) 50% + ); + pointer-events: none; + } + + &::after { + position: absolute; + z-index: -1; + inset: 0; + top: 25%; + content: ""; + backdrop-filter: blur(16px); + mask-image: linear-gradient( + to bottom, + rgb(0 0 0 / 0%) 0%, + rgb(0 0 0 / 4.9%) 15.5%, + rgb(0 0 0 / 10.4%) 22.5%, + rgb(0 0 0 / 45%) 47.1%, + rgb(0 0 0 / 55%) 52.9%, + rgb(0 0 0 / 89.6%) 77.5%, + rgb(0 0 0 / 95.1%) 91.9%, + rgb(0 0 0 / 100%) 100% + ); + } } .subButtons { @@ -290,29 +400,18 @@ function menuEdit() { } .top { + --top-height: 80px; + position: sticky; top: 0; z-index: 1; - padding: 20px 0; - background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--MI-blur, blur(8px)); - backdrop-filter: var(--MI-blur, blur(8px)); + display: flex; + height: var(--top-height); } .instance { position: relative; - display: block; - text-align: center; - width: 100%; - - &:focus-visible { - outline: none; - - > .instanceIcon { - outline: 2px solid var(--MI_THEME-focus); - outline-offset: 2px; - } - } + width: var(--top-height); } .instanceIcon { @@ -322,13 +421,20 @@ function menuEdit() { border-radius: 8px; } + .realtimeMode { + display: inline-block; + width: var(--top-height); + margin-left: auto; + + &.on { + color: var(--MI_THEME-accent); + } + } + .bottom { position: sticky; bottom: 0; padding-top: 20px; - background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--MI-blur, blur(8px)); - backdrop-filter: var(--MI-blur, blur(8px)); } .post { @@ -416,10 +522,6 @@ function menuEdit() { padding-right: 8px; } - .middle { - flex: 1; - } - .divider { margin: 16px 16px; border-top: solid 0.5px var(--MI_THEME-divider); @@ -520,9 +622,6 @@ function menuEdit() { top: 0; z-index: 1; padding: 20px 0; - background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--MI-blur, blur(8px)); - backdrop-filter: var(--MI-blur, blur(8px)); } .instance { @@ -551,9 +650,6 @@ function menuEdit() { position: sticky; bottom: 0; padding-top: 20px; - background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--MI-blur, blur(8px)); - backdrop-filter: var(--MI-blur, blur(8px)); } .widget { @@ -564,6 +660,18 @@ function menuEdit() { text-align: center; } + .realtimeMode { + display: block; + position: relative; + width: 100%; + height: 52px; + text-align: center; + + &.on { + color: var(--MI_THEME-accent); + } + } + .post { display: block; position: relative; @@ -637,10 +745,6 @@ function menuEdit() { display: none; } - .middle { - flex: 1; - } - .divider { margin: 8px auto; width: calc(100% - 32px); @@ -650,7 +754,7 @@ function menuEdit() { .item { display: block; position: relative; - padding: 18px 0; + padding: 16px 0; width: 100%; text-align: center; diff --git a/packages/frontend/src/ui/_common_/stream-indicator.vue b/packages/frontend/src/ui/_common_/stream-indicator.vue index 5f7600881f..35508b7ce6 100644 --- a/packages/frontend/src/ui/_common_/stream-indicator.vue +++ b/packages/frontend/src/ui/_common_/stream-indicator.vue @@ -20,6 +20,7 @@ import { i18n } from '@/i18n.js'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { prefer } from '@/preferences.js'; +import { store } from '@/store.js'; const zIndex = os.claimZIndex('high'); @@ -37,11 +38,13 @@ function reload() { window.location.reload(); } -useStream().on('_disconnected_', onDisconnected); +if (store.s.realtimeMode) { + useStream().on('_disconnected_', onDisconnected); -onUnmounted(() => { - useStream().off('_disconnected_', onDisconnected); -}); + onUnmounted(() => { + useStream().off('_disconnected_', onDisconnected); + }); +}