Compare commits
65 Commits
2023.10.0-
...
2023.10.1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7e7138c0eb | ||
![]() |
87c9870447 | ||
![]() |
34eeccf908 | ||
![]() |
c2e177e37a | ||
![]() |
7b6b3ad821 | ||
![]() |
7132958948 | ||
![]() |
a26d9ea132 | ||
![]() |
566cb35370 | ||
![]() |
cf3624a54f | ||
![]() |
8a302a9af4 | ||
![]() |
7a8d5e5840 | ||
![]() |
1f0c27edf2 | ||
![]() |
26b7112b20 | ||
![]() |
f964ef163b | ||
![]() |
854ac95511 | ||
![]() |
51b6a012a5 | ||
![]() |
085bcf24da | ||
![]() |
66940d6cf1 | ||
![]() |
61ff98c8dd | ||
![]() |
43fe0cfda8 | ||
![]() |
57b794edfb | ||
![]() |
47de264478 | ||
![]() |
373c2af46a | ||
![]() |
f5e72f7d3e | ||
![]() |
d81c833775 | ||
![]() |
cf6e53b2ac | ||
![]() |
9dd0f8c39b | ||
![]() |
d94380780f | ||
![]() |
af1087aed4 | ||
![]() |
9f33ce1cd0 | ||
![]() |
4eb9e50a36 | ||
![]() |
8ab3640291 | ||
![]() |
fc777be7bc | ||
![]() |
edf847d966 | ||
![]() |
0e6cd577cc | ||
![]() |
7adc8fcaf5 | ||
![]() |
e57b536767 | ||
![]() |
f32915b515 | ||
![]() |
a8d45d4b0d | ||
![]() |
4e24aff408 | ||
![]() |
e64a81aa1d | ||
![]() |
7093662ce5 | ||
![]() |
32c741154d | ||
![]() |
407a965c1d | ||
![]() |
de6348e8a0 | ||
![]() |
9ad57324db | ||
![]() |
94690c835e | ||
![]() |
c5d2dba28d | ||
![]() |
272e0c874f | ||
![]() |
d429f810a9 | ||
![]() |
75b28d6782 | ||
![]() |
8b1362ab03 | ||
![]() |
a096f621cf | ||
![]() |
f54a9542bb | ||
![]() |
a52bbc7c8d | ||
![]() |
59768bdf3f | ||
![]() |
1e67e9c661 | ||
![]() |
ae517a99a7 | ||
![]() |
b23a9b1a88 | ||
![]() |
5bd68aa3e0 | ||
![]() |
647ce174b3 | ||
![]() |
02c8fd9de5 | ||
![]() |
1ba49b614d | ||
![]() |
40de14415c | ||
![]() |
7c9330a02f |
19
CHANGELOG.md
19
CHANGELOG.md
@@ -12,11 +12,22 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## 2023.10.1
|
||||||
|
### General
|
||||||
|
- Enhance: ローカルタイムライン、ソーシャルタイムラインで返信を含むかどうか設定可能に
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- Fix: 絵文字ピッカーで横に長いカスタム絵文字が見切れる問題を修正
|
||||||
|
|
||||||
|
### Server
|
||||||
|
- Fix: フォローしているユーザーからの自分の投稿への返信がタイムラインに含まれない問題を修正
|
||||||
|
- Fix: users/notesでセンシティブチャンネルの投稿が含まれる場合がある問題を修正
|
||||||
|
|
||||||
## 2023.10.0
|
## 2023.10.0
|
||||||
### NOTE
|
### NOTE
|
||||||
- 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました
|
- 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました
|
||||||
- アップデート後、アップデートより前の時点にTLを遡ることはできません
|
- アップデートを行うと、タイムラインが一時的にリセットされます
|
||||||
- アップデート後であっても、今後のアップデートで2023.10.0以前のTLに遡れるようになる可能性はあります
|
- ソフトミュート設定はクライアントではなくサーバー側に保存されるようになったため、アップデートを行うとソフトミュートの設定がリセットされます
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- API: users/notes, notes/local-timeline で fileType 指定はできなくなりました
|
- API: users/notes, notes/local-timeline で fileType 指定はできなくなりました
|
||||||
@@ -39,11 +50,15 @@
|
|||||||
- Fix: ユーザーリストTLにチャンネル投稿が含まれる問題を修正
|
- Fix: ユーザーリストTLにチャンネル投稿が含まれる問題を修正
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
- Feat: 「ファイルの詳細」ページを追加
|
||||||
|
- ドライブのファイルの拡大プレビューができるように
|
||||||
|
- ファイルが添付されたノートの一覧が表示できるように
|
||||||
- Enhance: 二要素認証のバックアップコード一覧をテキストファイルでダウンロード可能に
|
- Enhance: 二要素認証のバックアップコード一覧をテキストファイルでダウンロード可能に
|
||||||
- Enhance: 動画再生時のデフォルトボリュームを30%に
|
- Enhance: 動画再生時のデフォルトボリュームを30%に
|
||||||
- Fix: リアクションしたユーザ一覧のUIが稀に左上に残ってしまう不具合を修正
|
- Fix: リアクションしたユーザ一覧のUIが稀に左上に残ってしまう不具合を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
|
- Enhance: drive/files/attached-notes がページネーションに対応しました
|
||||||
- Enhance: タイムライン取得時のパフォーマンスを大幅に向上
|
- Enhance: タイムライン取得時のパフォーマンスを大幅に向上
|
||||||
- Enhance: ハイライト取得時のパフォーマンスを大幅に向上
|
- Enhance: ハイライト取得時のパフォーマンスを大幅に向上
|
||||||
- Enhance: トレンドハッシュタグ取得時のパフォーマンスを大幅に向上
|
- Enhance: トレンドハッシュタグ取得時のパフォーマンスを大幅に向上
|
||||||
|
@@ -995,9 +995,6 @@ _theme:
|
|||||||
infoFg: "তথ্যের পাঠ্য"
|
infoFg: "তথ্যের পাঠ্য"
|
||||||
infoWarnBg: "ওয়ার্নিং এর পটভূমি"
|
infoWarnBg: "ওয়ার্নিং এর পটভূমি"
|
||||||
infoWarnFg: "ওয়ার্নিং এর পাঠ্য"
|
infoWarnFg: "ওয়ার্নিং এর পাঠ্য"
|
||||||
cwBg: "CW বাটনের পটভূমি"
|
|
||||||
cwFg: "CW বাটনের পাঠ্য"
|
|
||||||
cwHoverBg: "CW বাটনের পটভূমি (হভার)"
|
|
||||||
toastBg: "বিজ্ঞপ্তির পটভূমি"
|
toastBg: "বিজ্ঞপ্তির পটভূমি"
|
||||||
toastFg: "বিজ্ঞপ্তির পাঠ্য"
|
toastFg: "বিজ্ঞপ্তির পাঠ্য"
|
||||||
buttonBg: "বাটনের পটভূমি"
|
buttonBg: "বাটনের পটভূমি"
|
||||||
|
@@ -1622,9 +1622,6 @@ _theme:
|
|||||||
infoFg: "Text informací"
|
infoFg: "Text informací"
|
||||||
infoWarnBg: "Pozadí varování"
|
infoWarnBg: "Pozadí varování"
|
||||||
infoWarnFg: "Text varování"
|
infoWarnFg: "Text varování"
|
||||||
cwBg: "Pozadí CW tlačítka"
|
|
||||||
cwFg: "Text CW tlačítka"
|
|
||||||
cwHoverBg: "Pozadí CW tlačítka (Hover)"
|
|
||||||
toastBg: "Pozadí oznámení"
|
toastBg: "Pozadí oznámení"
|
||||||
toastFg: "Text oznámení"
|
toastFg: "Text oznámení"
|
||||||
buttonBg: "Pozadí tlačítka"
|
buttonBg: "Pozadí tlačítka"
|
||||||
|
@@ -1685,9 +1685,6 @@ _theme:
|
|||||||
infoFg: "Text von Informationen"
|
infoFg: "Text von Informationen"
|
||||||
infoWarnBg: "Hintergrund von Warnungen"
|
infoWarnBg: "Hintergrund von Warnungen"
|
||||||
infoWarnFg: "Text von Warnungen"
|
infoWarnFg: "Text von Warnungen"
|
||||||
cwBg: "Hintergrund des Inhaltswarnungsknopfs"
|
|
||||||
cwFg: "Text des Inhaltswarnungsknopfs"
|
|
||||||
cwHoverBg: "Hintergrund des Inhaltswarnungsknopfs (Mouseover)"
|
|
||||||
toastBg: "Hintergrund von Benachrichtigungen"
|
toastBg: "Hintergrund von Benachrichtigungen"
|
||||||
toastFg: "Text von Benachrichtigungen"
|
toastFg: "Text von Benachrichtigungen"
|
||||||
buttonBg: "Hintergrund von Schaltflächen"
|
buttonBg: "Hintergrund von Schaltflächen"
|
||||||
@@ -2144,3 +2141,11 @@ _moderationLogTypes:
|
|||||||
createAd: "Werbung erstellt"
|
createAd: "Werbung erstellt"
|
||||||
deleteAd: "Werbung gelöscht"
|
deleteAd: "Werbung gelöscht"
|
||||||
updateAd: "Werbung aktualisiert"
|
updateAd: "Werbung aktualisiert"
|
||||||
|
_fileViewer:
|
||||||
|
title: "Dateiinformationen"
|
||||||
|
type: "Dateityp"
|
||||||
|
size: "Dateigröße"
|
||||||
|
url: "URL"
|
||||||
|
uploadedAt: "Hochgeladen am"
|
||||||
|
attachedNotes: "Zugehörige Notizen"
|
||||||
|
thisPageCanBeSeenFromTheAuthor: "Nur der Benutzer, der diese Datei hochgeladen hat, kann diese Seite sehen."
|
||||||
|
@@ -1685,9 +1685,6 @@ _theme:
|
|||||||
infoFg: "Information text"
|
infoFg: "Information text"
|
||||||
infoWarnBg: "Warning background"
|
infoWarnBg: "Warning background"
|
||||||
infoWarnFg: "Warning text"
|
infoWarnFg: "Warning text"
|
||||||
cwBg: "CW button background"
|
|
||||||
cwFg: "CW button text"
|
|
||||||
cwHoverBg: "CW button background (Hover)"
|
|
||||||
toastBg: "Notification background"
|
toastBg: "Notification background"
|
||||||
toastFg: "Notification text"
|
toastFg: "Notification text"
|
||||||
buttonBg: "Button background"
|
buttonBg: "Button background"
|
||||||
@@ -2144,3 +2141,11 @@ _moderationLogTypes:
|
|||||||
createAd: "Ad created"
|
createAd: "Ad created"
|
||||||
deleteAd: "Ad deleted"
|
deleteAd: "Ad deleted"
|
||||||
updateAd: "Ad updated"
|
updateAd: "Ad updated"
|
||||||
|
_fileViewer:
|
||||||
|
title: "File details"
|
||||||
|
type: "File type"
|
||||||
|
size: "Filesize"
|
||||||
|
url: "URL"
|
||||||
|
uploadedAt: "Uploaded at"
|
||||||
|
attachedNotes: "Attached notes"
|
||||||
|
thisPageCanBeSeenFromTheAuthor: "This page can only be seen by the user who uploaded this file."
|
||||||
|
@@ -1666,9 +1666,6 @@ _theme:
|
|||||||
infoFg: "Texto de información"
|
infoFg: "Texto de información"
|
||||||
infoWarnBg: "Fondo de advertencias"
|
infoWarnBg: "Fondo de advertencias"
|
||||||
infoWarnFg: "Texto de advertencias"
|
infoWarnFg: "Texto de advertencias"
|
||||||
cwBg: "Fondo del botón CW"
|
|
||||||
cwFg: "Texto del botón CW"
|
|
||||||
cwHoverBg: "Fondo del botón CW (hover)"
|
|
||||||
toastBg: "Fondo de notificaciones"
|
toastBg: "Fondo de notificaciones"
|
||||||
toastFg: "Texto de notificaciones"
|
toastFg: "Texto de notificaciones"
|
||||||
buttonBg: "Fondo de botón"
|
buttonBg: "Fondo de botón"
|
||||||
|
@@ -45,6 +45,7 @@ pin: "Épingler sur le profil"
|
|||||||
unpin: "Désépingler"
|
unpin: "Désépingler"
|
||||||
copyContent: "Copier le contenu"
|
copyContent: "Copier le contenu"
|
||||||
copyLink: "Copier le lien"
|
copyLink: "Copier le lien"
|
||||||
|
copyLinkRenote: "Copier le lien de la renote"
|
||||||
delete: "Supprimer"
|
delete: "Supprimer"
|
||||||
deleteAndEdit: "Supprimer et réécrire"
|
deleteAndEdit: "Supprimer et réécrire"
|
||||||
deleteAndEditConfirm: "Êtes-vous sûr de vouloir effacer cette note et la modifier ? Vous perdrez toutes les réactions, renotes et réponses."
|
deleteAndEditConfirm: "Êtes-vous sûr de vouloir effacer cette note et la modifier ? Vous perdrez toutes les réactions, renotes et réponses."
|
||||||
@@ -129,6 +130,8 @@ unmarkAsSensitive: "Supprimer le marquage comme sensible"
|
|||||||
enterFileName: "Entrer le nom du fichier"
|
enterFileName: "Entrer le nom du fichier"
|
||||||
mute: "Masquer"
|
mute: "Masquer"
|
||||||
unmute: "Ne plus masquer"
|
unmute: "Ne plus masquer"
|
||||||
|
renoteMute: "Masquer les renotes"
|
||||||
|
renoteUnmute: "Ne plus masquer les renotes"
|
||||||
block: "Bloquer"
|
block: "Bloquer"
|
||||||
unblock: "Débloquer"
|
unblock: "Débloquer"
|
||||||
suspend: "Suspendre"
|
suspend: "Suspendre"
|
||||||
@@ -414,6 +417,7 @@ moderator: "Modérateur·rice·s"
|
|||||||
moderation: "Modérations"
|
moderation: "Modérations"
|
||||||
moderationNote: "Note de modération"
|
moderationNote: "Note de modération"
|
||||||
addModerationNote: "Ajouter une note de modération"
|
addModerationNote: "Ajouter une note de modération"
|
||||||
|
moderationLogs: "Journal de modération"
|
||||||
nUsersMentioned: "{n} utilisateur·rice·s mentionné·e·s"
|
nUsersMentioned: "{n} utilisateur·rice·s mentionné·e·s"
|
||||||
securityKeyAndPasskey: "Sécurité et clés de sécurité"
|
securityKeyAndPasskey: "Sécurité et clés de sécurité"
|
||||||
securityKey: "Clé de sécurité"
|
securityKey: "Clé de sécurité"
|
||||||
@@ -472,6 +476,7 @@ aboutX: "À propos de {x}"
|
|||||||
emojiStyle: "Style des émojis"
|
emojiStyle: "Style des émojis"
|
||||||
native: "Natif"
|
native: "Natif"
|
||||||
disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
|
disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
|
||||||
|
showNoteActionsOnlyHover: "Afficher les actions de note uniquement au survol"
|
||||||
noHistory: "Pas d'historique"
|
noHistory: "Pas d'historique"
|
||||||
signinHistory: "Historique de connexion"
|
signinHistory: "Historique de connexion"
|
||||||
enableAdvancedMfm: "Activer la MFM avancée"
|
enableAdvancedMfm: "Activer la MFM avancée"
|
||||||
@@ -647,6 +652,7 @@ behavior: "Comportement"
|
|||||||
sample: "Exemple"
|
sample: "Exemple"
|
||||||
abuseReports: "Signalements"
|
abuseReports: "Signalements"
|
||||||
reportAbuse: "Signaler"
|
reportAbuse: "Signaler"
|
||||||
|
reportAbuseRenote: "Signaler la renote"
|
||||||
reportAbuseOf: "Signaler {name}"
|
reportAbuseOf: "Signaler {name}"
|
||||||
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit d'une note précise, veuillez en donner le lien."
|
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit d'une note précise, veuillez en donner le lien."
|
||||||
abuseReported: "Le rapport est envoyé. Merci."
|
abuseReported: "Le rapport est envoyé. Merci."
|
||||||
@@ -671,6 +677,8 @@ clip: "Clip"
|
|||||||
createNew: "Créer nouveau"
|
createNew: "Créer nouveau"
|
||||||
optional: "Facultatif"
|
optional: "Facultatif"
|
||||||
createNewClip: "Créer un nouveau clip"
|
createNewClip: "Créer un nouveau clip"
|
||||||
|
unclip: "Supprimer le clip"
|
||||||
|
confirmToUnclipAlreadyClippedNote: "Cette note fait déjà partie du clip « {name} ». Souhaitez-vous la supprimer de ce clip ?"
|
||||||
public: "Public"
|
public: "Public"
|
||||||
private: "Privé"
|
private: "Privé"
|
||||||
i18nInfo: "Misskey est traduit dans différentes langues par des bénévoles. Vous pouvez contribuer à {link}."
|
i18nInfo: "Misskey est traduit dans différentes langues par des bénévoles. Vous pouvez contribuer à {link}."
|
||||||
@@ -933,12 +941,15 @@ unsubscribePushNotification: "Désactiver les notifications push"
|
|||||||
pushNotificationAlreadySubscribed: "Les notifications push sont déjà activées"
|
pushNotificationAlreadySubscribed: "Les notifications push sont déjà activées"
|
||||||
pushNotificationNotSupported: "Votre navigateur ou votre instance ne prend pas en charge les notifications push"
|
pushNotificationNotSupported: "Votre navigateur ou votre instance ne prend pas en charge les notifications push"
|
||||||
sendPushNotificationReadMessage: "Supprimer les notifications push une fois que les notifications ou messages pertinents ont été lus."
|
sendPushNotificationReadMessage: "Supprimer les notifications push une fois que les notifications ou messages pertinents ont été lus."
|
||||||
|
windowMaximize: "Maximiser"
|
||||||
|
windowMinimize: "Minimaliser"
|
||||||
windowRestore: "Restaurer"
|
windowRestore: "Restaurer"
|
||||||
caption: "Libellé"
|
caption: "Libellé"
|
||||||
loggedInAsBot: "Connecté actuellement en tant que bot"
|
loggedInAsBot: "Connecté actuellement en tant que bot"
|
||||||
tools: "Outils"
|
tools: "Outils"
|
||||||
cannotLoad: "Chargement impossible"
|
cannotLoad: "Chargement impossible"
|
||||||
like: "J'aime"
|
like: "J'aime"
|
||||||
|
unlike: "Ne plus aimer"
|
||||||
numberOfLikes: "Favoris"
|
numberOfLikes: "Favoris"
|
||||||
show: "Affichage"
|
show: "Affichage"
|
||||||
neverShow: "Ne plus afficher"
|
neverShow: "Ne plus afficher"
|
||||||
@@ -949,6 +960,7 @@ noRole: "Aucun rôle"
|
|||||||
normalUser: "Simple utilisateur·rice"
|
normalUser: "Simple utilisateur·rice"
|
||||||
undefined: "Non défini"
|
undefined: "Non défini"
|
||||||
assign: "Attribuer"
|
assign: "Attribuer"
|
||||||
|
unassign: "Retirer"
|
||||||
color: "Couleur"
|
color: "Couleur"
|
||||||
manageCustomEmojis: "Gestion des émojis personnalisés"
|
manageCustomEmojis: "Gestion des émojis personnalisés"
|
||||||
preset: "Préréglage"
|
preset: "Préréglage"
|
||||||
@@ -958,12 +970,16 @@ thisPostMayBeAnnoying: "Cette note peut gêner d'autres personnes."
|
|||||||
thisPostMayBeAnnoyingHome: "Publier vers le fil principal"
|
thisPostMayBeAnnoyingHome: "Publier vers le fil principal"
|
||||||
thisPostMayBeAnnoyingCancel: "Annuler"
|
thisPostMayBeAnnoyingCancel: "Annuler"
|
||||||
thisPostMayBeAnnoyingIgnore: "Publier quand-même"
|
thisPostMayBeAnnoyingIgnore: "Publier quand-même"
|
||||||
|
collapseRenotes: "Réduire les renotes déjà vues"
|
||||||
internalServerError: "Erreur interne du serveur"
|
internalServerError: "Erreur interne du serveur"
|
||||||
copyErrorInfo: "Copier les détails de l’erreur"
|
copyErrorInfo: "Copier les détails de l’erreur"
|
||||||
exploreOtherServers: "Trouver une autre instance"
|
exploreOtherServers: "Trouver une autre instance"
|
||||||
disableFederationOk: "Désactiver"
|
disableFederationOk: "Désactiver"
|
||||||
likeOnly: "Les favoris uniquement"
|
likeOnly: "Les favoris uniquement"
|
||||||
|
sensitiveWords: "Mots sensibles"
|
||||||
|
notesSearchNotAvailable: "La recherche de notes n'est pas disponible."
|
||||||
license: "Licence"
|
license: "Licence"
|
||||||
|
myClips: "Mes clips"
|
||||||
video: "Vidéo"
|
video: "Vidéo"
|
||||||
videos: "Vidéos"
|
videos: "Vidéos"
|
||||||
dataSaver: "Économiseur de données"
|
dataSaver: "Économiseur de données"
|
||||||
@@ -973,6 +989,7 @@ accountMovedShort: "Ce compte a migré"
|
|||||||
operationForbidden: "Opération non autorisée"
|
operationForbidden: "Opération non autorisée"
|
||||||
addMemo: "Ajouter un mémo"
|
addMemo: "Ajouter un mémo"
|
||||||
reactionsList: "Réactions"
|
reactionsList: "Réactions"
|
||||||
|
renotesList: "Liste de renotes"
|
||||||
notificationDisplay: "Style des notifications"
|
notificationDisplay: "Style des notifications"
|
||||||
leftTop: "En haut à gauche"
|
leftTop: "En haut à gauche"
|
||||||
rightTop: "En haut à droite"
|
rightTop: "En haut à droite"
|
||||||
@@ -982,6 +999,7 @@ vertical: "Vertical"
|
|||||||
horizontal: "Latéral"
|
horizontal: "Latéral"
|
||||||
serverRules: "Règles du serveur"
|
serverRules: "Règles du serveur"
|
||||||
archive: "Archive"
|
archive: "Archive"
|
||||||
|
displayOfNote: "Affichage de la note"
|
||||||
youFollowing: "Abonné·e"
|
youFollowing: "Abonné·e"
|
||||||
options: "Options"
|
options: "Options"
|
||||||
later: "Plus tard"
|
later: "Plus tard"
|
||||||
@@ -1001,6 +1019,7 @@ pinnedList: "Liste épinglée"
|
|||||||
notifyNotes: "Notifier à propos des nouvelles notes"
|
notifyNotes: "Notifier à propos des nouvelles notes"
|
||||||
authentication: "Authentification"
|
authentication: "Authentification"
|
||||||
authenticationRequiredToContinue: "Veuillez vous authentifier pour continuer"
|
authenticationRequiredToContinue: "Veuillez vous authentifier pour continuer"
|
||||||
|
showRenotes: "Afficher les renotes"
|
||||||
_announcement:
|
_announcement:
|
||||||
readConfirmTitle: "Marquer comme lu ?"
|
readConfirmTitle: "Marquer comme lu ?"
|
||||||
_initialAccountSetting:
|
_initialAccountSetting:
|
||||||
@@ -1082,12 +1101,20 @@ _achievements:
|
|||||||
title: "Beaucoup d'amis"
|
title: "Beaucoup d'amis"
|
||||||
_followers10:
|
_followers10:
|
||||||
title: "Abonnez-moi !"
|
title: "Abonnez-moi !"
|
||||||
|
description: "Obtenir plus de 10 abonné·e·s"
|
||||||
|
_followers50:
|
||||||
|
description: "Obtenir plus de 50 abonné·e·s"
|
||||||
_followers100:
|
_followers100:
|
||||||
title: "Populaire"
|
title: "Populaire"
|
||||||
|
description: "Obtenir plus de 100 abonné·e·s"
|
||||||
|
_followers300:
|
||||||
|
description: "Obtenir plus de 300 abonné·e·s"
|
||||||
_followers500:
|
_followers500:
|
||||||
title: "Tour radio"
|
title: "Tour radio"
|
||||||
|
description: "Obtenir plus de 500 abonné·e·s"
|
||||||
_followers1000:
|
_followers1000:
|
||||||
title: "Influenceur·euse"
|
title: "Influenceur·euse"
|
||||||
|
description: "Obtenir plus de 1000 abonné·e·s"
|
||||||
_iLoveMisskey:
|
_iLoveMisskey:
|
||||||
title: "J’adore Misskey"
|
title: "J’adore Misskey"
|
||||||
description: "Publication « J’❤ #Misskey »"
|
description: "Publication « J’❤ #Misskey »"
|
||||||
@@ -1151,6 +1178,7 @@ _role:
|
|||||||
high: "Haute"
|
high: "Haute"
|
||||||
_options:
|
_options:
|
||||||
canManageCustomEmojis: "Gestion des émojis personnalisés"
|
canManageCustomEmojis: "Gestion des émojis personnalisés"
|
||||||
|
wordMuteMax: "Nombre maximal de caractères dans le filtre de mots"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement."
|
description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement."
|
||||||
sensitivity: "Sensibilité de la détection"
|
sensitivity: "Sensibilité de la détection"
|
||||||
@@ -1330,9 +1358,6 @@ _theme:
|
|||||||
infoFg: "Texte d'information"
|
infoFg: "Texte d'information"
|
||||||
infoWarnBg: "Arrière-plan des avertissements"
|
infoWarnBg: "Arrière-plan des avertissements"
|
||||||
infoWarnFg: "Texte d’avertissement"
|
infoWarnFg: "Texte d’avertissement"
|
||||||
cwBg: "Arrière-plan du CW"
|
|
||||||
cwFg: "Texte du bouton CW"
|
|
||||||
cwHoverBg: "Arrière-plan du bouton CW (survolé)"
|
|
||||||
toastBg: "Arrière-plan de la bulle de notification"
|
toastBg: "Arrière-plan de la bulle de notification"
|
||||||
toastFg: "Texte de la bulle de notification"
|
toastFg: "Texte de la bulle de notification"
|
||||||
buttonBg: "Arrière-plan du bouton"
|
buttonBg: "Arrière-plan du bouton"
|
||||||
|
@@ -1627,9 +1627,6 @@ _theme:
|
|||||||
infoFg: "Teks informasi"
|
infoFg: "Teks informasi"
|
||||||
infoWarnBg: "Latar belakang peringatan"
|
infoWarnBg: "Latar belakang peringatan"
|
||||||
infoWarnFg: "Teks peringatan"
|
infoWarnFg: "Teks peringatan"
|
||||||
cwBg: "Latar belakang tombol Sembunyikan Konten"
|
|
||||||
cwFg: "Teks tombol Sembunyikan Konten"
|
|
||||||
cwHoverBg: "Latar belakang tombol Sembunyikan Konten (Mengambang)"
|
|
||||||
toastBg: "Latar belakang notifikasi"
|
toastBg: "Latar belakang notifikasi"
|
||||||
toastFg: "Teks notifikasi"
|
toastFg: "Teks notifikasi"
|
||||||
buttonBg: "Latar belakang tombol"
|
buttonBg: "Latar belakang tombol"
|
||||||
|
12
locales/index.d.ts
vendored
12
locales/index.d.ts
vendored
@@ -1796,9 +1796,6 @@ export interface Locale {
|
|||||||
"infoFg": string;
|
"infoFg": string;
|
||||||
"infoWarnBg": string;
|
"infoWarnBg": string;
|
||||||
"infoWarnFg": string;
|
"infoWarnFg": string;
|
||||||
"cwBg": string;
|
|
||||||
"cwFg": string;
|
|
||||||
"cwHoverBg": string;
|
|
||||||
"toastBg": string;
|
"toastBg": string;
|
||||||
"toastFg": string;
|
"toastFg": string;
|
||||||
"buttonBg": string;
|
"buttonBg": string;
|
||||||
@@ -2294,6 +2291,15 @@ export interface Locale {
|
|||||||
"deleteAd": string;
|
"deleteAd": string;
|
||||||
"updateAd": string;
|
"updateAd": string;
|
||||||
};
|
};
|
||||||
|
"_fileViewer": {
|
||||||
|
"title": string;
|
||||||
|
"type": string;
|
||||||
|
"size": string;
|
||||||
|
"url": string;
|
||||||
|
"uploadedAt": string;
|
||||||
|
"attachedNotes": string;
|
||||||
|
"thisPageCanBeSeenFromTheAuthor": string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
declare const locales: {
|
declare const locales: {
|
||||||
[lang: string]: Locale;
|
[lang: string]: Locale;
|
||||||
|
@@ -113,7 +113,7 @@ cantReRenote: "È impossibile rinotare una Rinota."
|
|||||||
quote: "Cita"
|
quote: "Cita"
|
||||||
inChannelRenote: "Rinota nel canale"
|
inChannelRenote: "Rinota nel canale"
|
||||||
inChannelQuote: "Cita nel canale"
|
inChannelQuote: "Cita nel canale"
|
||||||
pinnedNote: "Nota fissata"
|
pinnedNote: "Nota in primo piano"
|
||||||
pinned: "Fissa sul profilo"
|
pinned: "Fissa sul profilo"
|
||||||
you: "Tu"
|
you: "Tu"
|
||||||
clickToShow: "Clicca per visualizzare"
|
clickToShow: "Clicca per visualizzare"
|
||||||
@@ -364,7 +364,7 @@ pinnedUsersDescription: "Elenca gli/le utenti che vuoi fissare in cima alla pagi
|
|||||||
pinnedPages: "Pagine in evidenza"
|
pinnedPages: "Pagine in evidenza"
|
||||||
pinnedPagesDescription: "Specifica il percorso delle pagine che vuoi fissare in cima alla pagina dell'istanza. Una pagina per riga."
|
pinnedPagesDescription: "Specifica il percorso delle pagine che vuoi fissare in cima alla pagina dell'istanza. Una pagina per riga."
|
||||||
pinnedClipId: "ID della Clip in evidenza"
|
pinnedClipId: "ID della Clip in evidenza"
|
||||||
pinnedNotes: "Nota fissata"
|
pinnedNotes: "Note in primo piano"
|
||||||
hcaptcha: "hCaptcha"
|
hcaptcha: "hCaptcha"
|
||||||
enableHcaptcha: "Abilita hCaptcha"
|
enableHcaptcha: "Abilita hCaptcha"
|
||||||
hcaptchaSiteKey: "Chiave del sito"
|
hcaptchaSiteKey: "Chiave del sito"
|
||||||
@@ -384,7 +384,7 @@ name: "Nome"
|
|||||||
antennaSource: "Fonte dell'antenna"
|
antennaSource: "Fonte dell'antenna"
|
||||||
antennaKeywords: "Parole chiavi da ricevere"
|
antennaKeywords: "Parole chiavi da ricevere"
|
||||||
antennaExcludeKeywords: "Parole chiavi da escludere"
|
antennaExcludeKeywords: "Parole chiavi da escludere"
|
||||||
antennaKeywordsDescription: "Separare con uno spazio indica la condizione \"E\". Separare con un'interruzzione riga indica la condizione \"O\"."
|
antennaKeywordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)."
|
||||||
notifyAntenna: "Invia notifiche delle nuove note"
|
notifyAntenna: "Invia notifiche delle nuove note"
|
||||||
withFileAntenna: "Solo note con file in allegato"
|
withFileAntenna: "Solo note con file in allegato"
|
||||||
enableServiceworker: "Abilita ServiceWorker"
|
enableServiceworker: "Abilita ServiceWorker"
|
||||||
@@ -393,7 +393,7 @@ caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
|
|||||||
withReplies: "Includere le risposte"
|
withReplies: "Includere le risposte"
|
||||||
connectedTo: "Connessione ai seguenti profili:"
|
connectedTo: "Connessione ai seguenti profili:"
|
||||||
notesAndReplies: "Note e risposte"
|
notesAndReplies: "Note e risposte"
|
||||||
withFiles: "Con file in allegato"
|
withFiles: "Con allegati"
|
||||||
silence: "Silenzia"
|
silence: "Silenzia"
|
||||||
silenceConfirm: "Vuoi davvero silenziare questo profilo?"
|
silenceConfirm: "Vuoi davvero silenziare questo profilo?"
|
||||||
unsilence: "Riattiva"
|
unsilence: "Riattiva"
|
||||||
@@ -1121,11 +1121,11 @@ unnotifyNotes: "Interrompi le notifiche di nuove Note"
|
|||||||
authentication: "Autenticazione"
|
authentication: "Autenticazione"
|
||||||
authenticationRequiredToContinue: "Per procedere, è richiesta l'autenticazione"
|
authenticationRequiredToContinue: "Per procedere, è richiesta l'autenticazione"
|
||||||
dateAndTime: "Data e Ora"
|
dateAndTime: "Data e Ora"
|
||||||
showRenotes: "Leggi le Rinota"
|
showRenotes: "Includi le Rinota"
|
||||||
edited: "Modificato"
|
edited: "Modificato"
|
||||||
notificationRecieveConfig: "Preferenze di notifica"
|
notificationRecieveConfig: "Preferenze di notifica"
|
||||||
mutualFollow: "Follow reciproco"
|
mutualFollow: "Follow reciproco"
|
||||||
fileAttachedOnly: "Con file in allegato"
|
fileAttachedOnly: "Solo con allegati"
|
||||||
showRepliesToOthersInTimeline: "Risposte altrui nella TL"
|
showRepliesToOthersInTimeline: "Risposte altrui nella TL"
|
||||||
hideRepliesToOthersInTimeline: "Nascondi Riposte altrui nella TL"
|
hideRepliesToOthersInTimeline: "Nascondi Riposte altrui nella TL"
|
||||||
externalServices: "Servizi esterni"
|
externalServices: "Servizi esterni"
|
||||||
@@ -1533,6 +1533,10 @@ _ad:
|
|||||||
reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso"
|
reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso"
|
||||||
hide: "Nascondi"
|
hide: "Nascondi"
|
||||||
timezoneinfo: "Il giorno della settimana è determinato in base al fuso orario del server."
|
timezoneinfo: "Il giorno della settimana è determinato in base al fuso orario del server."
|
||||||
|
adsSettings: "Impostazioni banner"
|
||||||
|
notesPerOneAd: "Quantità di Note tra i banner"
|
||||||
|
setZeroToDisable: "Imposta 0 (zero) per disattivare la distribuzione dei banner durante gli aggiornamenti in tempo reale"
|
||||||
|
adsTooClose: "Attenzione, l'intervallo di pubblicazione dei banner è molto breve, potrebbe infastidire significativamente la fruizione"
|
||||||
_forgotPassword:
|
_forgotPassword:
|
||||||
enterEmail: "Inserisci l'indirizzo di posta elettronica che hai registrato nel tuo profilo. Il collegamento necessario per ripristinare la password verrà inviato a questo indirizzo."
|
enterEmail: "Inserisci l'indirizzo di posta elettronica che hai registrato nel tuo profilo. Il collegamento necessario per ripristinare la password verrà inviato a questo indirizzo."
|
||||||
ifNoEmail: "Se il tuo indirizzo email non risulta registrato, contatta l'amministrazione dell'istanza."
|
ifNoEmail: "Se il tuo indirizzo email non risulta registrato, contatta l'amministrazione dell'istanza."
|
||||||
@@ -1616,7 +1620,7 @@ _menuDisplay:
|
|||||||
hide: "Nascondere"
|
hide: "Nascondere"
|
||||||
_wordMute:
|
_wordMute:
|
||||||
muteWords: "Parole da filtrare"
|
muteWords: "Parole da filtrare"
|
||||||
muteWordsDescription: "Separare con uno spazio indica la condizione \"E\". Separare con una interruzione di riga, indica la condizione \"O\""
|
muteWordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)."
|
||||||
muteWordsDescription2: "Se vuoi indicare delle Espressioni Regolari (regexp), metti la condizione all'interno di due slash (/)"
|
muteWordsDescription2: "Se vuoi indicare delle Espressioni Regolari (regexp), metti la condizione all'interno di due slash (/)"
|
||||||
_instanceMute:
|
_instanceMute:
|
||||||
instanceMuteDescription: "Disattiva tutte le note, le note di rinvio (condivisione) dell'istanza configurata, comprese le risposte agli utenti dell'istanza."
|
instanceMuteDescription: "Disattiva tutte le note, le note di rinvio (condivisione) dell'istanza configurata, comprese le risposte agli utenti dell'istanza."
|
||||||
@@ -1626,7 +1630,7 @@ _instanceMute:
|
|||||||
_theme:
|
_theme:
|
||||||
explore: "Esplora temi"
|
explore: "Esplora temi"
|
||||||
install: "Installa un tema"
|
install: "Installa un tema"
|
||||||
manage: "Gerisci temi"
|
manage: "Gestione temi"
|
||||||
code: "Codice tema"
|
code: "Codice tema"
|
||||||
description: "Descrizione"
|
description: "Descrizione"
|
||||||
installed: "{name} è installato"
|
installed: "{name} è installato"
|
||||||
@@ -1681,9 +1685,6 @@ _theme:
|
|||||||
infoFg: "Testo di informazioni"
|
infoFg: "Testo di informazioni"
|
||||||
infoWarnBg: "Sfondo degli avvisi"
|
infoWarnBg: "Sfondo degli avvisi"
|
||||||
infoWarnFg: "Testo di avviso"
|
infoWarnFg: "Testo di avviso"
|
||||||
cwBg: "Sfondo del CW"
|
|
||||||
cwFg: "Testo del pulsante CW"
|
|
||||||
cwHoverBg: "Sfondo del pulsante CW (sorvolato)"
|
|
||||||
toastBg: "Sfondo di notifica a comparsa"
|
toastBg: "Sfondo di notifica a comparsa"
|
||||||
toastFg: "Testo di notifica a comparsa"
|
toastFg: "Testo di notifica a comparsa"
|
||||||
buttonBg: "Sfondo del pulsante"
|
buttonBg: "Sfondo del pulsante"
|
||||||
@@ -1885,7 +1886,7 @@ _visibility:
|
|||||||
followersDescription: "Visibile solo ai tuoi follower"
|
followersDescription: "Visibile solo ai tuoi follower"
|
||||||
specified: "Nota diretta"
|
specified: "Nota diretta"
|
||||||
specifiedDescription: "Visibile solo ai profili menzionati"
|
specifiedDescription: "Visibile solo ai profili menzionati"
|
||||||
disableFederation: "Non federare"
|
disableFederation: "Senza federazione"
|
||||||
disableFederationDescription: "Non spedire attività alle altre istanze remote"
|
disableFederationDescription: "Non spedire attività alle altre istanze remote"
|
||||||
_postForm:
|
_postForm:
|
||||||
replyPlaceholder: "Rispondi a questa nota..."
|
replyPlaceholder: "Rispondi a questa nota..."
|
||||||
|
@@ -1714,9 +1714,6 @@ _theme:
|
|||||||
infoFg: "情報の文字"
|
infoFg: "情報の文字"
|
||||||
infoWarnBg: "警告の背景"
|
infoWarnBg: "警告の背景"
|
||||||
infoWarnFg: "警告の文字"
|
infoWarnFg: "警告の文字"
|
||||||
cwBg: "CW ボタンの背景"
|
|
||||||
cwFg: "CW ボタンの文字"
|
|
||||||
cwHoverBg: "CW ボタンの背景 (ホバー)"
|
|
||||||
toastBg: "通知トーストの背景"
|
toastBg: "通知トーストの背景"
|
||||||
toastFg: "通知トーストの文字"
|
toastFg: "通知トーストの文字"
|
||||||
buttonBg: "ボタンの背景"
|
buttonBg: "ボタンの背景"
|
||||||
@@ -2206,3 +2203,12 @@ _moderationLogTypes:
|
|||||||
createAd: "広告を作成"
|
createAd: "広告を作成"
|
||||||
deleteAd: "広告を削除"
|
deleteAd: "広告を削除"
|
||||||
updateAd: "広告を更新"
|
updateAd: "広告を更新"
|
||||||
|
|
||||||
|
_fileViewer:
|
||||||
|
title: "ファイルの詳細"
|
||||||
|
type: "ファイルタイプ"
|
||||||
|
size: "ファイルサイズ"
|
||||||
|
url: "URL"
|
||||||
|
uploadedAt: "追加日"
|
||||||
|
attachedNotes: "添付されているノート"
|
||||||
|
thisPageCanBeSeenFromTheAuthor: "このページは、このファイルをアップロードしたユーザーしか閲覧できません。"
|
||||||
|
@@ -1649,9 +1649,6 @@ _theme:
|
|||||||
infoFg: "情報の文字"
|
infoFg: "情報の文字"
|
||||||
infoWarnBg: "警告の背景"
|
infoWarnBg: "警告の背景"
|
||||||
infoWarnFg: "警告の文字"
|
infoWarnFg: "警告の文字"
|
||||||
cwBg: "CW ボタンの背景"
|
|
||||||
cwFg: "CW ボタンの文字"
|
|
||||||
cwHoverBg: "CW ボタンの背景 (ホバー)"
|
|
||||||
toastBg: "通知トーストの背景"
|
toastBg: "通知トーストの背景"
|
||||||
toastFg: "通知トーストの文字"
|
toastFg: "通知トーストの文字"
|
||||||
buttonBg: "ボタンの背景"
|
buttonBg: "ボタンの背景"
|
||||||
|
@@ -1663,9 +1663,6 @@ _theme:
|
|||||||
infoFg: "정보창 텍스트"
|
infoFg: "정보창 텍스트"
|
||||||
infoWarnBg: "경고창 배경"
|
infoWarnBg: "경고창 배경"
|
||||||
infoWarnFg: "경고창 텍스트"
|
infoWarnFg: "경고창 텍스트"
|
||||||
cwBg: "CW 버튼 배경"
|
|
||||||
cwFg: "CW 버튼 텍스트"
|
|
||||||
cwHoverBg: "CW 버튼 배경 (호버)"
|
|
||||||
toastBg: "알림창 배경"
|
toastBg: "알림창 배경"
|
||||||
toastFg: "알림창 텍스트"
|
toastFg: "알림창 텍스트"
|
||||||
buttonBg: "버튼 배경"
|
buttonBg: "버튼 배경"
|
||||||
|
@@ -1043,9 +1043,6 @@ _theme:
|
|||||||
infoFg: "Tekst informacji"
|
infoFg: "Tekst informacji"
|
||||||
infoWarnBg: "Tło ostrzeżenia"
|
infoWarnBg: "Tło ostrzeżenia"
|
||||||
infoWarnFg: "Tekst ostrzeżenia"
|
infoWarnFg: "Tekst ostrzeżenia"
|
||||||
cwBg: "Tło CW"
|
|
||||||
cwFg: "Tekst CW"
|
|
||||||
cwHoverBg: "Tło CW (po najechaniu)"
|
|
||||||
toastBg: "Tło powiadomień"
|
toastBg: "Tło powiadomień"
|
||||||
toastFg: "Tekst powiadomień"
|
toastFg: "Tekst powiadomień"
|
||||||
buttonBg: "Tło przycisku"
|
buttonBg: "Tło przycisku"
|
||||||
|
@@ -1551,9 +1551,6 @@ _theme:
|
|||||||
infoFg: "Текст сообщения"
|
infoFg: "Текст сообщения"
|
||||||
infoWarnBg: "Фон предупреждения"
|
infoWarnBg: "Фон предупреждения"
|
||||||
infoWarnFg: "Текст предупреждения"
|
infoWarnFg: "Текст предупреждения"
|
||||||
cwBg: "Фон предупреждения о содержимом"
|
|
||||||
cwFg: "Текст предупреждения о содержимом"
|
|
||||||
cwHoverBg: "Фон предупреждения о содержимом (под указателем)"
|
|
||||||
toastBg: "Фон оповещения"
|
toastBg: "Фон оповещения"
|
||||||
toastFg: "Текст оповещения"
|
toastFg: "Текст оповещения"
|
||||||
buttonBg: "Фон кнопки"
|
buttonBg: "Фон кнопки"
|
||||||
|
@@ -1102,9 +1102,6 @@ _theme:
|
|||||||
infoFg: "Informačný text"
|
infoFg: "Informačný text"
|
||||||
infoWarnBg: "Pozadie varovania"
|
infoWarnBg: "Pozadie varovania"
|
||||||
infoWarnFg: "Text varovania"
|
infoWarnFg: "Text varovania"
|
||||||
cwBg: "CW pozadie tlačidla"
|
|
||||||
cwFg: "CW text tlačidla"
|
|
||||||
cwHoverBg: "CW pozadie tlačidla (pod kurzorom)"
|
|
||||||
toastBg: "Pozadie upozornenia"
|
toastBg: "Pozadie upozornenia"
|
||||||
toastFg: "Text upozornenia"
|
toastFg: "Text upozornenia"
|
||||||
buttonBg: "Pozadie tlačidla"
|
buttonBg: "Pozadie tlačidla"
|
||||||
|
@@ -1663,9 +1663,6 @@ _theme:
|
|||||||
infoFg: "ข้อความข้อมูล"
|
infoFg: "ข้อความข้อมูล"
|
||||||
infoWarnBg: "คำเตือนพื้นหลัง"
|
infoWarnBg: "คำเตือนพื้นหลัง"
|
||||||
infoWarnFg: "คำเตือนข้อความ"
|
infoWarnFg: "คำเตือนข้อความ"
|
||||||
cwBg: "ปุ่ม CW พื้นหลัง"
|
|
||||||
cwFg: "ปุ่ม CW ข้อความ"
|
|
||||||
cwHoverBg: "ปุ่ม CW พื้นหลัง (โฮเวอร์)"
|
|
||||||
toastBg: "ประวัติการแจ้งเตือน"
|
toastBg: "ประวัติการแจ้งเตือน"
|
||||||
toastFg: "ข้อความแจ้งเตือน"
|
toastFg: "ข้อความแจ้งเตือน"
|
||||||
buttonBg: "ปุ่มพื้นหลัง"
|
buttonBg: "ปุ่มพื้นหลัง"
|
||||||
|
@@ -1290,9 +1290,6 @@ _theme:
|
|||||||
infoFg: "Текст інформації"
|
infoFg: "Текст інформації"
|
||||||
infoWarnBg: "Фон попередження"
|
infoWarnBg: "Фон попередження"
|
||||||
infoWarnFg: "Текст попередження"
|
infoWarnFg: "Текст попередження"
|
||||||
cwBg: "Фон чутливого змісту"
|
|
||||||
cwFg: "Текст чутливого змісту"
|
|
||||||
cwHoverBg: "Фон чутливого змісту (при наведенні)"
|
|
||||||
toastBg: "Фон повідомлення"
|
toastBg: "Фон повідомлення"
|
||||||
toastFg: "Текст повідомлення"
|
toastFg: "Текст повідомлення"
|
||||||
buttonBg: "Фон кнопки"
|
buttonBg: "Фон кнопки"
|
||||||
|
@@ -1467,9 +1467,6 @@ _theme:
|
|||||||
infoFg: "Chữ thông tin"
|
infoFg: "Chữ thông tin"
|
||||||
infoWarnBg: "Nền cảnh báo"
|
infoWarnBg: "Nền cảnh báo"
|
||||||
infoWarnFg: "Chữ cảnh báo"
|
infoWarnFg: "Chữ cảnh báo"
|
||||||
cwBg: "Nền nút nội dung ẩn"
|
|
||||||
cwFg: "Chữ nút nội dung ẩn"
|
|
||||||
cwHoverBg: "Nền nút nội dung ẩn (Chạm)"
|
|
||||||
toastBg: "Nền thông báo"
|
toastBg: "Nền thông báo"
|
||||||
toastFg: "Chữ thông báo"
|
toastFg: "Chữ thông báo"
|
||||||
buttonBg: "Nền nút"
|
buttonBg: "Nền nút"
|
||||||
|
@@ -1673,9 +1673,6 @@ _theme:
|
|||||||
infoFg: "信息文本"
|
infoFg: "信息文本"
|
||||||
infoWarnBg: "警告背景"
|
infoWarnBg: "警告背景"
|
||||||
infoWarnFg: "警告文本"
|
infoWarnFg: "警告文本"
|
||||||
cwBg: "隐藏内容按钮背景"
|
|
||||||
cwFg: "隐藏内容按钮文本"
|
|
||||||
cwHoverBg: "隐藏内容按钮背景(悬停)"
|
|
||||||
toastBg: "Toast 通知背景"
|
toastBg: "Toast 通知背景"
|
||||||
toastFg: "Toast 通知文本"
|
toastFg: "Toast 通知文本"
|
||||||
buttonBg: "按钮背景"
|
buttonBg: "按钮背景"
|
||||||
|
@@ -1685,9 +1685,6 @@ _theme:
|
|||||||
infoFg: "資訊內容"
|
infoFg: "資訊內容"
|
||||||
infoWarnBg: "警告背景"
|
infoWarnBg: "警告背景"
|
||||||
infoWarnFg: "警告文字"
|
infoWarnFg: "警告文字"
|
||||||
cwBg: "隱藏內容按鈕背景"
|
|
||||||
cwFg: "隱藏內容按鈕文字"
|
|
||||||
cwHoverBg: "隱藏內容按鈕背景(懸浮)"
|
|
||||||
toastBg: "通知背景"
|
toastBg: "通知背景"
|
||||||
toastFg: "通知文本"
|
toastFg: "通知文本"
|
||||||
buttonBg: "按鈕背景"
|
buttonBg: "按鈕背景"
|
||||||
@@ -2144,3 +2141,11 @@ _moderationLogTypes:
|
|||||||
createAd: "建立廣告"
|
createAd: "建立廣告"
|
||||||
deleteAd: "刪除廣告"
|
deleteAd: "刪除廣告"
|
||||||
updateAd: "更新廣告"
|
updateAd: "更新廣告"
|
||||||
|
_fileViewer:
|
||||||
|
title: "檔案詳細資訊"
|
||||||
|
type: "檔案類型 "
|
||||||
|
size: "檔案大小"
|
||||||
|
url: "URL"
|
||||||
|
uploadedAt: "加入日期"
|
||||||
|
attachedNotes: "含有附件的貼文"
|
||||||
|
thisPageCanBeSeenFromTheAuthor: "本頁面僅限上傳了這個檔案的使用者可以檢視。"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2023.10.0-beta.13",
|
"version": "2023.10.1",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -51,8 +51,8 @@
|
|||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "6.7.4",
|
"@typescript-eslint/eslint-plugin": "6.7.5",
|
||||||
"@typescript-eslint/parser": "6.7.4",
|
"@typescript-eslint/parser": "6.7.5",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.3.0",
|
"cypress": "13.3.0",
|
||||||
"eslint": "8.51.0",
|
"eslint": "8.51.0",
|
||||||
|
@@ -86,7 +86,7 @@
|
|||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.2",
|
||||||
"bullmq": "4.12.2",
|
"bullmq": "4.12.3",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "9.0.1",
|
"cbor": "9.0.1",
|
||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
@@ -189,13 +189,13 @@
|
|||||||
"@types/jsrsasign": "10.5.9",
|
"@types/jsrsasign": "10.5.9",
|
||||||
"@types/mime-types": "2.1.2",
|
"@types/mime-types": "2.1.2",
|
||||||
"@types/ms": "0.7.32",
|
"@types/ms": "0.7.32",
|
||||||
"@types/node": "20.8.3",
|
"@types/node": "20.8.4",
|
||||||
"@types/node-fetch": "3.0.3",
|
"@types/node-fetch": "3.0.3",
|
||||||
"@types/nodemailer": "6.4.11",
|
"@types/nodemailer": "6.4.11",
|
||||||
"@types/oauth": "0.9.2",
|
"@types/oauth": "0.9.2",
|
||||||
"@types/oauth2orize": "1.11.1",
|
"@types/oauth2orize": "1.11.1",
|
||||||
"@types/oauth2orize-pkce": "0.1.0",
|
"@types/oauth2orize-pkce": "0.1.0",
|
||||||
"@types/pg": "8.10.3",
|
"@types/pg": "8.10.4",
|
||||||
"@types/pug": "2.0.7",
|
"@types/pug": "2.0.7",
|
||||||
"@types/punycode": "2.1.0",
|
"@types/punycode": "2.1.0",
|
||||||
"@types/qrcode": "1.5.2",
|
"@types/qrcode": "1.5.2",
|
||||||
@@ -212,8 +212,8 @@
|
|||||||
"@types/vary": "1.1.1",
|
"@types/vary": "1.1.1",
|
||||||
"@types/web-push": "3.6.1",
|
"@types/web-push": "3.6.1",
|
||||||
"@types/ws": "8.5.6",
|
"@types/ws": "8.5.6",
|
||||||
"@typescript-eslint/eslint-plugin": "6.7.4",
|
"@typescript-eslint/eslint-plugin": "6.7.5",
|
||||||
"@typescript-eslint/parser": "6.7.4",
|
"@typescript-eslint/parser": "6.7.5",
|
||||||
"aws-sdk-client-mock": "3.0.0",
|
"aws-sdk-client-mock": "3.0.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.51.0",
|
"eslint": "8.51.0",
|
||||||
|
@@ -868,8 +868,8 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
// 基本的にvisibleUserIdsには自身のidが含まれている前提であること
|
// 基本的にvisibleUserIdsには自身のidが含まれている前提であること
|
||||||
if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue;
|
if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue;
|
||||||
|
|
||||||
// 自分自身以外への返信
|
// 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合
|
||||||
if (note.replyId && note.replyUserId !== note.userId) {
|
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === following.followerId)) {
|
||||||
if (!following.withReplies) continue;
|
if (!following.withReplies) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,8 +886,8 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
|
!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
|
||||||
) continue;
|
) continue;
|
||||||
|
|
||||||
// 自分自身以外への返信
|
// 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合
|
||||||
if (note.replyId && note.replyUserId !== note.userId) {
|
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userListUserId)) {
|
||||||
if (!userListMembership.withReplies) continue;
|
if (!userListMembership.withReplies) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -907,6 +907,10 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||||||
// 自分自身以外への返信
|
// 自分自身以外への返信
|
||||||
if (note.replyId && note.replyUserId !== note.userId) {
|
if (note.replyId && note.replyUserId !== note.userId) {
|
||||||
this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||||
|
|
||||||
|
if (note.visibility === 'public' && note.userHost == null) {
|
||||||
|
this.redisTimelineService.push('localTimelineWithReplies', note.id, 300, r);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
|
||||||
if (note.fileIds.length > 0) {
|
if (note.fileIds.length > 0) {
|
||||||
|
@@ -44,13 +44,13 @@ export class RedisTimelineService {
|
|||||||
public get(name: string, untilId?: string | null, sinceId?: string | null) {
|
public get(name: string, untilId?: string | null, sinceId?: string | null) {
|
||||||
if (untilId && sinceId) {
|
if (untilId && sinceId) {
|
||||||
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
||||||
.then(ids => ids.filter(id => id > untilId && id < sinceId).sort((a, b) => a > b ? -1 : 1));
|
.then(ids => ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1));
|
||||||
} else if (untilId) {
|
} else if (untilId) {
|
||||||
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
||||||
.then(ids => ids.filter(id => id > untilId).sort((a, b) => a > b ? -1 : 1));
|
.then(ids => ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1));
|
||||||
} else if (sinceId) {
|
} else if (sinceId) {
|
||||||
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
||||||
.then(ids => ids.filter(id => id < sinceId).sort((a, b) => a < b ? -1 : 1));
|
.then(ids => ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1));
|
||||||
} else {
|
} else {
|
||||||
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
return this.redisForTimelines.lrange('list:' + name, 0, -1)
|
||||||
.then(ids => ids.sort((a, b) => a > b ? -1 : 1));
|
.then(ids => ids.sort((a, b) => a > b ? -1 : 1));
|
||||||
@@ -68,11 +68,11 @@ export class RedisTimelineService {
|
|||||||
const tls = res.map(r => r[1] as string[]);
|
const tls = res.map(r => r[1] as string[]);
|
||||||
return tls.map(ids =>
|
return tls.map(ids =>
|
||||||
(untilId && sinceId)
|
(untilId && sinceId)
|
||||||
? ids.filter(id => id > untilId && id < sinceId).sort((a, b) => a > b ? -1 : 1)
|
? ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1)
|
||||||
: untilId
|
: untilId
|
||||||
? ids.filter(id => id > untilId).sort((a, b) => a > b ? -1 : 1)
|
? ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1)
|
||||||
: sinceId
|
: sinceId
|
||||||
? ids.filter(id => id < sinceId).sort((a, b) => a < b ? -1 : 1)
|
? ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1)
|
||||||
: ids.sort((a, b) => a > b ? -1 : 1),
|
: ids.sort((a, b) => a > b ? -1 : 1),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@@ -73,8 +73,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private redisTimelineService: RedisTimelineService,
|
private redisTimelineService: RedisTimelineService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null;
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null;
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
|
|
||||||
const antenna = await this.antennasRepository.findOneBy({
|
const antenna = await this.antennasRepository.findOneBy({
|
||||||
id: ps.antennaId,
|
id: ps.antennaId,
|
||||||
@@ -109,7 +109,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
this.queryService.generateBlockedUserQuery(query, me);
|
this.queryService.generateBlockedUserQuery(query, me);
|
||||||
|
|
||||||
const notes = await query.getMany();
|
const notes = await query.getMany();
|
||||||
notes.sort((a, b) => a.id > b.id ? -1 : 1);
|
if (sinceId != null && untilId == null) {
|
||||||
|
notes.sort((a, b) => a.id < b.id ? -1 : 1);
|
||||||
|
} else {
|
||||||
|
notes.sort((a, b) => a.id > b.id ? -1 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (notes.length > 0) {
|
if (notes.length > 0) {
|
||||||
this.noteReadService.read(me.id, notes);
|
this.noteReadService.read(me.id, notes);
|
||||||
|
@@ -74,8 +74,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private activeUsersChart: ActiveUsersChart,
|
private activeUsersChart: ActiveUsersChart,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null;
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null;
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
const isRangeSpecified = untilId != null && sinceId != null;
|
const isRangeSpecified = untilId != null && sinceId != null;
|
||||||
|
|
||||||
const channel = await this.channelsRepository.findOneBy({
|
const channel = await this.channelsRepository.findOneBy({
|
||||||
@@ -111,7 +111,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
let timeline = await query.getMany();
|
let timeline = await query.getMany();
|
||||||
|
|
||||||
timeline = timeline.filter(note => {
|
timeline = timeline.filter(note => {
|
||||||
if (me && isUserRelated(note, userIdsWhoMeMuting, true)) return false;
|
if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { NotesRepository, DriveFilesRepository } from '@/models/_.js';
|
import type { NotesRepository, DriveFilesRepository } from '@/models/_.js';
|
||||||
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { ApiError } from '../../../error.js';
|
import { ApiError } from '../../../error.js';
|
||||||
@@ -41,6 +42,9 @@ export const meta = {
|
|||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
sinceId: { type: 'string', format: 'misskey:id' },
|
||||||
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||||
fileId: { type: 'string', format: 'misskey:id' },
|
fileId: { type: 'string', format: 'misskey:id' },
|
||||||
},
|
},
|
||||||
required: ['fileId'],
|
required: ['fileId'],
|
||||||
@@ -56,6 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private notesRepository: NotesRepository,
|
private notesRepository: NotesRepository,
|
||||||
|
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
private queryService: QueryService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
// Fetch file
|
// Fetch file
|
||||||
@@ -68,9 +73,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
throw new ApiError(meta.errors.noSuchFile);
|
throw new ApiError(meta.errors.noSuchFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
const notes = await this.notesRepository.createQueryBuilder('note')
|
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId);
|
||||||
.where(':file = ANY(note.fileIds)', { file: file.id })
|
query.andWhere(':file = ANY(note.fileIds)', { file: file.id });
|
||||||
.getMany();
|
|
||||||
|
const notes = await query.limit(ps.limit).getMany();
|
||||||
|
|
||||||
return await this.noteEntityService.packMany(notes, me, {
|
return await this.noteEntityService.packMany(notes, me, {
|
||||||
detail: true,
|
detail: true,
|
||||||
|
@@ -55,6 +55,7 @@ export const paramDef = {
|
|||||||
includeLocalRenotes: { type: 'boolean', default: true },
|
includeLocalRenotes: { type: 'boolean', default: true },
|
||||||
withFiles: { type: 'boolean', default: false },
|
withFiles: { type: 'boolean', default: false },
|
||||||
withRenotes: { type: 'boolean', default: true },
|
withRenotes: { type: 'boolean', default: true },
|
||||||
|
withReplies: { type: 'boolean', default: false },
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
@@ -76,8 +77,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private redisTimelineService: RedisTimelineService,
|
private redisTimelineService: RedisTimelineService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null;
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null;
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
|
|
||||||
const policies = await this.roleService.getUserPolicies(me.id);
|
const policies = await this.roleService.getUserPolicies(me.id);
|
||||||
if (!policies.ltlAvailable) {
|
if (!policies.ltlAvailable) {
|
||||||
@@ -94,12 +95,29 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
this.cacheService.userBlockedCache.fetch(me.id),
|
this.cacheService.userBlockedCache.fetch(me.id),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([
|
let noteIds: string[];
|
||||||
ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`,
|
|
||||||
ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline',
|
if (ps.withFiles) {
|
||||||
], untilId, sinceId);
|
const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([
|
||||||
|
`homeTimelineWithFiles:${me.id}`,
|
||||||
|
'localTimelineWithFiles',
|
||||||
|
], untilId, sinceId);
|
||||||
|
noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
|
||||||
|
} else if (ps.withReplies) {
|
||||||
|
const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.redisTimelineService.getMulti([
|
||||||
|
`homeTimeline:${me.id}`,
|
||||||
|
'localTimeline',
|
||||||
|
'localTimelineWithReplies',
|
||||||
|
], untilId, sinceId);
|
||||||
|
noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds, ...ltlReplyNoteIds]));
|
||||||
|
} else {
|
||||||
|
const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([
|
||||||
|
`homeTimeline:${me.id}`,
|
||||||
|
'localTimeline',
|
||||||
|
], untilId, sinceId);
|
||||||
|
noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
|
||||||
|
}
|
||||||
|
|
||||||
let noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
|
|
||||||
noteIds.sort((a, b) => a > b ? -1 : 1);
|
noteIds.sort((a, b) => a > b ? -1 : 1);
|
||||||
noteIds = noteIds.slice(0, ps.limit);
|
noteIds = noteIds.slice(0, ps.limit);
|
||||||
|
|
||||||
|
@@ -45,6 +45,7 @@ export const paramDef = {
|
|||||||
properties: {
|
properties: {
|
||||||
withFiles: { type: 'boolean', default: false },
|
withFiles: { type: 'boolean', default: false },
|
||||||
withRenotes: { type: 'boolean', default: true },
|
withRenotes: { type: 'boolean', default: true },
|
||||||
|
withReplies: { type: 'boolean', default: false },
|
||||||
excludeNsfw: { type: 'boolean', default: false },
|
excludeNsfw: { type: 'boolean', default: false },
|
||||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||||
sinceId: { type: 'string', format: 'misskey:id' },
|
sinceId: { type: 'string', format: 'misskey:id' },
|
||||||
@@ -72,8 +73,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private redisTimelineService: RedisTimelineService,
|
private redisTimelineService: RedisTimelineService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null;
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null;
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
|
|
||||||
const policies = await this.roleService.getUserPolicies(me ? me.id : null);
|
const policies = await this.roleService.getUserPolicies(me ? me.id : null);
|
||||||
if (!policies.ltlAvailable) {
|
if (!policies.ltlAvailable) {
|
||||||
@@ -90,7 +91,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
this.cacheService.userBlockedCache.fetch(me.id),
|
this.cacheService.userBlockedCache.fetch(me.id),
|
||||||
]) : [new Set<string>(), new Set<string>(), new Set<string>()];
|
]) : [new Set<string>(), new Set<string>(), new Set<string>()];
|
||||||
|
|
||||||
let noteIds = await this.redisTimelineService.get(ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', untilId, sinceId);
|
let noteIds: string[];
|
||||||
|
|
||||||
|
if (ps.withFiles) {
|
||||||
|
noteIds = await this.redisTimelineService.get('localTimelineWithFiles', untilId, sinceId);
|
||||||
|
} else {
|
||||||
|
const [nonReplyNoteIds, replyNoteIds] = await this.redisTimelineService.getMulti([
|
||||||
|
'localTimeline',
|
||||||
|
'localTimelineWithReplies',
|
||||||
|
], untilId, sinceId);
|
||||||
|
noteIds = Array.from(new Set([...nonReplyNoteIds, ...replyNoteIds]));
|
||||||
|
noteIds.sort((a, b) => a > b ? -1 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
noteIds = noteIds.slice(0, ps.limit);
|
noteIds = noteIds.slice(0, ps.limit);
|
||||||
|
|
||||||
if (noteIds.length === 0) {
|
if (noteIds.length === 0) {
|
||||||
@@ -112,6 +125,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
if (me && (note.userId === me.id)) {
|
if (me && (note.userId === me.id)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!ps.withReplies && note.replyId && (me == null || note.replyUserId !== me.id)) return false;
|
||||||
if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
|
if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
|
||||||
if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
|
if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
|
||||||
if (note.renoteId) {
|
if (note.renoteId) {
|
||||||
|
@@ -66,8 +66,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private redisTimelineService: RedisTimelineService,
|
private redisTimelineService: RedisTimelineService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null;
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null;
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
followings,
|
followings,
|
||||||
|
@@ -83,8 +83,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private redisTimelineService: RedisTimelineService,
|
private redisTimelineService: RedisTimelineService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null;
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null;
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
|
|
||||||
const list = await this.userListsRepository.findOneBy({
|
const list = await this.userListsRepository.findOneBy({
|
||||||
id: ps.listId,
|
id: ps.listId,
|
||||||
|
@@ -69,8 +69,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private redisTimelineService: RedisTimelineService,
|
private redisTimelineService: RedisTimelineService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null;
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null;
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
|
|
||||||
const role = await this.rolesRepository.findOneBy({
|
const role = await this.rolesRepository.findOneBy({
|
||||||
id: ps.roleId,
|
id: ps.roleId,
|
||||||
|
@@ -74,9 +74,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
private redisTimelineService: RedisTimelineService,
|
private redisTimelineService: RedisTimelineService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const untilId = ps.untilId ?? ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null;
|
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
|
||||||
const sinceId = ps.sinceId ?? ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null;
|
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
|
||||||
const isRangeSpecified = untilId != null && sinceId != null;
|
const isRangeSpecified = untilId != null && sinceId != null;
|
||||||
|
const isSelf = me && (me.id === ps.userId);
|
||||||
|
|
||||||
if (isRangeSpecified || sinceId == null) {
|
if (isRangeSpecified || sinceId == null) {
|
||||||
const [
|
const [
|
||||||
@@ -100,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
noteIds = noteIds.slice(0, ps.limit);
|
noteIds = noteIds.slice(0, ps.limit);
|
||||||
|
|
||||||
if (noteIds.length > 0) {
|
if (noteIds.length > 0) {
|
||||||
const isFollowing = me ? me.id === ps.userId || Object.hasOwn(await this.cacheService.userFollowingsCache.fetch(me.id), ps.userId) : false;
|
const isFollowing = me && Object.hasOwn(await this.cacheService.userFollowingsCache.fetch(me.id), ps.userId);
|
||||||
|
|
||||||
const query = this.notesRepository.createQueryBuilder('note')
|
const query = this.notesRepository.createQueryBuilder('note')
|
||||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||||
@@ -122,8 +123,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (note.channel?.isSensitive && !isSelf) return false;
|
||||||
if (note.visibility === 'specified' && (!me || (me.id !== note.userId && !note.visibleUserIds.some(v => v === me.id)))) return false;
|
if (note.visibility === 'specified' && (!me || (me.id !== note.userId && !note.visibleUserIds.some(v => v === me.id)))) return false;
|
||||||
if (note.visibility === 'followers' && !isFollowing) return false;
|
if (note.visibility === 'followers' && !isFollowing && !isSelf) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -148,7 +150,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
.leftJoinAndSelect('reply.user', 'replyUser')
|
.leftJoinAndSelect('reply.user', 'replyUser')
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||||
|
|
||||||
if (!ps.withChannelNotes) {
|
if (ps.withChannelNotes) {
|
||||||
|
if (!isSelf) query.andWhere('channel.isSensitive = false');
|
||||||
|
} else {
|
||||||
query.andWhere('note.channelId IS NULL');
|
query.andWhere('note.channelId IS NULL');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,6 +19,7 @@ class HybridTimelineChannel extends Channel {
|
|||||||
public static shouldShare = false;
|
public static shouldShare = false;
|
||||||
public static requireCredential = true;
|
public static requireCredential = true;
|
||||||
private withRenotes: boolean;
|
private withRenotes: boolean;
|
||||||
|
private withReplies: boolean;
|
||||||
private withFiles: boolean;
|
private withFiles: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -39,6 +40,7 @@ class HybridTimelineChannel extends Channel {
|
|||||||
if (!policies.ltlAvailable) return;
|
if (!policies.ltlAvailable) return;
|
||||||
|
|
||||||
this.withRenotes = params.withRenotes ?? true;
|
this.withRenotes = params.withRenotes ?? true;
|
||||||
|
this.withReplies = params.withReplies ?? false;
|
||||||
this.withFiles = params.withFiles ?? false;
|
this.withFiles = params.withFiles ?? false;
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
@@ -87,7 +89,7 @@ class HybridTimelineChannel extends Channel {
|
|||||||
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
|
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
|
||||||
|
|
||||||
// 関係ない返信は除外
|
// 関係ない返信は除外
|
||||||
if (note.reply && !this.following[note.userId]?.withReplies) {
|
if (note.reply && !this.following[note.userId]?.withReplies && !this.withReplies) {
|
||||||
const reply = note.reply;
|
const reply = note.reply;
|
||||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||||
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||||
|
@@ -18,6 +18,7 @@ class LocalTimelineChannel extends Channel {
|
|||||||
public static shouldShare = false;
|
public static shouldShare = false;
|
||||||
public static requireCredential = false;
|
public static requireCredential = false;
|
||||||
private withRenotes: boolean;
|
private withRenotes: boolean;
|
||||||
|
private withReplies: boolean;
|
||||||
private withFiles: boolean;
|
private withFiles: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -38,6 +39,7 @@ class LocalTimelineChannel extends Channel {
|
|||||||
if (!policies.ltlAvailable) return;
|
if (!policies.ltlAvailable) return;
|
||||||
|
|
||||||
this.withRenotes = params.withRenotes ?? true;
|
this.withRenotes = params.withRenotes ?? true;
|
||||||
|
this.withReplies = params.withReplies ?? false;
|
||||||
this.withFiles = params.withFiles ?? false;
|
this.withFiles = params.withFiles ?? false;
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
@@ -66,7 +68,7 @@ class LocalTimelineChannel extends Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 関係ない返信は除外
|
// 関係ない返信は除外
|
||||||
if (note.reply && this.user && !this.following[note.userId]?.withReplies) {
|
if (note.reply && this.user && !this.following[note.userId]?.withReplies && !this.withReplies) {
|
||||||
const reply = note.reply;
|
const reply = note.reply;
|
||||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||||
if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
|
if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
|
||||||
|
@@ -19,7 +19,7 @@ function genHost() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function waitForPushToTl() {
|
function waitForPushToTl() {
|
||||||
return sleep(300);
|
return sleep(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
let app: INestApplicationContext;
|
let app: INestApplicationContext;
|
||||||
@@ -41,7 +41,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
|
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
|
||||||
@@ -57,7 +57,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -73,7 +73,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
|
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
|
||||||
@@ -90,7 +90,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -107,7 +107,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -124,7 +124,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -141,7 +141,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -159,7 +159,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
||||||
@@ -178,7 +178,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
||||||
@@ -194,12 +194,28 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
|
||||||
|
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||||
|
|
||||||
|
await api('/following/create', { userId: bob.id }, alice);
|
||||||
|
await sleep(1000);
|
||||||
|
const aliceNote = await post(alice, { text: 'hi' });
|
||||||
|
const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
|
});
|
||||||
|
|
||||||
test.concurrent('自分の他人への返信が含まれる', async () => {
|
test.concurrent('自分の他人への返信が含まれる', async () => {
|
||||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||||
|
|
||||||
@@ -208,7 +224,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
||||||
@@ -224,7 +240,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -275,7 +291,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
@@ -291,7 +307,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -309,7 +325,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -324,7 +340,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
@@ -338,7 +354,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
@@ -359,7 +375,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', { withFiles: true }, alice);
|
const res = await api('/notes/timeline', { limit: 100, withFiles: true }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
||||||
@@ -377,7 +393,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
@@ -389,7 +405,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
|
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
|
||||||
@@ -404,7 +420,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
|
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
|
||||||
@@ -417,7 +433,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
@@ -431,7 +447,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
@@ -444,7 +460,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'ok');
|
assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'ok');
|
||||||
@@ -459,7 +475,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'ok');
|
assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'ok');
|
||||||
@@ -475,7 +491,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/timeline', {}, alice);
|
const res = await api('/notes/timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
@@ -490,12 +506,26 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/local-timeline', {}, alice);
|
const res = await api('/notes/local-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.concurrent('他人の他人への返信が含まれない', async () => {
|
||||||
|
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||||
|
|
||||||
|
const carolNote = await post(carol, { text: 'hi' });
|
||||||
|
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
const res = await api('/notes/local-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
||||||
|
});
|
||||||
|
|
||||||
test.concurrent('チャンネル投稿が含まれない', async () => {
|
test.concurrent('チャンネル投稿が含まれない', async () => {
|
||||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||||
|
|
||||||
@@ -504,7 +534,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/local-timeline', {}, alice);
|
const res = await api('/notes/local-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
@@ -516,7 +546,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/local-timeline', {}, alice);
|
const res = await api('/notes/local-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
@@ -532,7 +562,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/local-timeline', {}, alice);
|
const res = await api('/notes/local-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -548,7 +578,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/local-timeline', {}, alice);
|
const res = await api('/notes/local-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -565,7 +595,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/local-timeline', {}, alice);
|
const res = await api('/notes/local-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
@@ -583,12 +613,41 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/local-timeline', {}, alice);
|
const res = await api('/notes/local-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
|
||||||
|
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||||
|
|
||||||
|
await api('/following/create', { userId: bob.id }, alice);
|
||||||
|
await sleep(1000);
|
||||||
|
const aliceNote = await post(alice, { text: 'hi' });
|
||||||
|
const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
const res = await api('/notes/local-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => {
|
||||||
|
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||||
|
|
||||||
|
const carolNote = await post(carol, { text: 'hi' });
|
||||||
|
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
const res = await api('/notes/local-timeline', { limit: 100, withReplies: true }, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
|
});
|
||||||
|
|
||||||
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
|
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
|
||||||
const [alice, bob] = await Promise.all([signup(), signup()]);
|
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||||
|
|
||||||
@@ -598,7 +657,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/local-timeline', { withFiles: true }, alice);
|
const res = await api('/notes/local-timeline', { limit: 100, withFiles: true }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
||||||
@@ -613,7 +672,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/hybrid-timeline', {}, alice);
|
const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
@@ -625,7 +684,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/hybrid-timeline', {}, alice);
|
const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
@@ -639,11 +698,41 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/hybrid-timeline', {}, alice);
|
const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
|
||||||
|
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||||
|
|
||||||
|
await api('/following/create', { userId: bob.id }, alice);
|
||||||
|
await sleep(1000);
|
||||||
|
const aliceNote = await post(alice, { text: 'hi' });
|
||||||
|
const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.concurrent('他人の他人への返信が含まれない', async () => {
|
||||||
|
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||||
|
|
||||||
|
const carolNote = await post(carol, { text: 'hi' });
|
||||||
|
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
const res = await api('/notes/hybrid-timeline', { }, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
|
||||||
|
});
|
||||||
|
|
||||||
test.concurrent('リモートユーザーのノートが含まれない', async () => {
|
test.concurrent('リモートユーザーのノートが含まれない', async () => {
|
||||||
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
|
||||||
|
|
||||||
@@ -651,7 +740,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/local-timeline', {}, alice);
|
const res = await api('/notes/local-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
});
|
});
|
||||||
@@ -665,7 +754,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/hybrid-timeline', {}, alice);
|
const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
@@ -679,7 +768,20 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/hybrid-timeline', {}, alice);
|
const res = await api('/notes/hybrid-timeline', { limit: 100 }, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => {
|
||||||
|
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||||
|
|
||||||
|
const carolNote = await post(carol, { text: 'hi' });
|
||||||
|
const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
const res = await api('/notes/hybrid-timeline', { limit: 100, withReplies: true }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
@@ -693,7 +795,7 @@ describe('Timelines', () => {
|
|||||||
|
|
||||||
await waitForPushToTl();
|
await waitForPushToTl();
|
||||||
|
|
||||||
const res = await api('/notes/hybrid-timeline', { withFiles: true }, alice);
|
const res = await api('/notes/hybrid-timeline', { limit: 100, withFiles: true }, alice);
|
||||||
|
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
|
||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
||||||
@@ -779,6 +881,22 @@ describe('Timelines', () => {
|
|||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.concurrent('withReplies: false でリスインしているフォローしていないユーザーからの自分への返信が含まれる', async () => {
|
||||||
|
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||||
|
|
||||||
|
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
|
||||||
|
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
|
||||||
|
await sleep(1000);
|
||||||
|
const aliceNote = await post(alice, { text: 'hi' });
|
||||||
|
const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
const res = await api('/notes/user-list-timeline', { listId: list.id, withReplies: false }, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
|
});
|
||||||
|
|
||||||
test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => {
|
test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => {
|
||||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||||
|
|
||||||
@@ -1034,6 +1152,32 @@ describe('Timelines', () => {
|
|||||||
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.concurrent('[withChannelNotes: true] 他人が取得した場合センシティブチャンネル投稿が含まれない', async () => {
|
||||||
|
const [alice, bob] = await Promise.all([signup(), signup()]);
|
||||||
|
|
||||||
|
const channel = await api('/channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body);
|
||||||
|
const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
const res = await api('/users/notes', { userId: bob.id, withChannelNotes: true }, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.concurrent('[withChannelNotes: true] 自分が取得した場合センシティブチャンネル投稿が含まれる', async () => {
|
||||||
|
const [bob] = await Promise.all([signup()]);
|
||||||
|
|
||||||
|
const channel = await api('/channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body);
|
||||||
|
const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
|
||||||
|
|
||||||
|
await waitForPushToTl();
|
||||||
|
|
||||||
|
const res = await api('/users/notes', { userId: bob.id, withChannelNotes: true }, bob);
|
||||||
|
|
||||||
|
assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
|
||||||
|
});
|
||||||
|
|
||||||
test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => {
|
test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => {
|
||||||
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
|
||||||
|
|
||||||
|
@@ -457,6 +457,7 @@ export async function testPaginationConsistency<Entity extends { id: string, cre
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const limit of [1, 5, 10, 100, undefined]) {
|
for (const limit of [1, 5, 10, 100, undefined]) {
|
||||||
|
/*
|
||||||
// 1. sinceId/DateとuntilId/Dateで両端を指定して取得した結果が期待通りになっていること
|
// 1. sinceId/DateとuntilId/Dateで両端を指定して取得した結果が期待通りになっていること
|
||||||
if (ordering === 'desc') {
|
if (ordering === 'desc') {
|
||||||
const end = expected.at(-1)!;
|
const end = expected.at(-1)!;
|
||||||
@@ -485,6 +486,7 @@ export async function testPaginationConsistency<Entity extends { id: string, cre
|
|||||||
actual.map(({ id, createdAt }) => id + ':' + createdAt),
|
actual.map(({ id, createdAt }) => id + ':' + createdAt),
|
||||||
expected.map(({ id, createdAt }) => id + ':' + createdAt));
|
expected.map(({ id, createdAt }) => id + ':' + createdAt));
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// 3. untilId指定+limitで取得してつなぎ合わせた結果が期待通りになっていること
|
// 3. untilId指定+limitで取得してつなぎ合わせた結果が期待通りになっていること
|
||||||
if (ordering === 'desc') {
|
if (ordering === 'desc') {
|
||||||
|
@@ -59,7 +59,7 @@
|
|||||||
"querystring": "0.2.1",
|
"querystring": "0.2.1",
|
||||||
"rollup": "4.0.2",
|
"rollup": "4.0.2",
|
||||||
"sanitize-html": "2.11.0",
|
"sanitize-html": "2.11.0",
|
||||||
"sass": "1.69.0",
|
"sass": "1.69.1",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.157.0",
|
"three": "0.157.0",
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
"@types/estree": "1.0.2",
|
"@types/estree": "1.0.2",
|
||||||
"@types/matter-js": "0.19.1",
|
"@types/matter-js": "0.19.1",
|
||||||
"@types/micromatch": "4.0.3",
|
"@types/micromatch": "4.0.3",
|
||||||
"@types/node": "20.8.3",
|
"@types/node": "20.8.4",
|
||||||
"@types/punycode": "2.1.0",
|
"@types/punycode": "2.1.0",
|
||||||
"@types/sanitize-html": "2.9.1",
|
"@types/sanitize-html": "2.9.1",
|
||||||
"@types/throttle-debounce": "5.0.0",
|
"@types/throttle-debounce": "5.0.0",
|
||||||
@@ -109,8 +109,8 @@
|
|||||||
"@types/uuid": "9.0.5",
|
"@types/uuid": "9.0.5",
|
||||||
"@types/websocket": "1.0.7",
|
"@types/websocket": "1.0.7",
|
||||||
"@types/ws": "8.5.6",
|
"@types/ws": "8.5.6",
|
||||||
"@typescript-eslint/eslint-plugin": "6.7.4",
|
"@typescript-eslint/eslint-plugin": "6.7.5",
|
||||||
"@typescript-eslint/parser": "6.7.4",
|
"@typescript-eslint/parser": "6.7.5",
|
||||||
"@vitest/coverage-v8": "0.34.6",
|
"@vitest/coverage-v8": "0.34.6",
|
||||||
"@vue/runtime-core": "3.3.4",
|
"@vue/runtime-core": "3.3.4",
|
||||||
"acorn": "8.10.0",
|
"acorn": "8.10.0",
|
||||||
|
@@ -4,10 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button class="_button" :class="$style.root" @mousedown="toggle">
|
<MkButton rounded full small @click="toggle"><b>{{ modelValue ? i18n.ts._cw.hide : i18n.ts._cw.show }}</b><span v-if="!modelValue" :class="$style.label">{{ label }}</span></MkButton>
|
||||||
<b>{{ modelValue ? i18n.ts._cw.hide : i18n.ts._cw.show }}</b>
|
|
||||||
<span v-if="!modelValue" :class="$style.label">{{ label }}</span>
|
|
||||||
</button>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -15,6 +12,7 @@ import { computed } from 'vue';
|
|||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { concat } from '@/scripts/array.js';
|
import { concat } from '@/scripts/array.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: boolean;
|
modelValue: boolean;
|
||||||
@@ -33,25 +31,12 @@ const label = computed(() => {
|
|||||||
] as string[][]).join(' / ');
|
] as string[][]).join(' / ');
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggle = () => {
|
function toggle() {
|
||||||
emit('update:modelValue', !props.modelValue);
|
emit('update:modelValue', !props.modelValue);
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.root {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 4px 8px;
|
|
||||||
font-size: 0.7em;
|
|
||||||
color: var(--cwFg);
|
|
||||||
background: var(--cwBg);
|
|
||||||
border-radius: 2px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--cwHoverBg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
|
||||||
|
@@ -45,8 +45,11 @@ import bytes from '@/filters/bytes.js';
|
|||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
import { useRouter } from '@/router.js';
|
||||||
import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js';
|
import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
file: Misskey.entities.DriveFile;
|
file: Misskey.entities.DriveFile;
|
||||||
folder: Misskey.entities.DriveFolder | null;
|
folder: Misskey.entities.DriveFolder | null;
|
||||||
@@ -71,7 +74,7 @@ function onClick(ev: MouseEvent) {
|
|||||||
if (props.selectMode) {
|
if (props.selectMode) {
|
||||||
emit('chosen', props.file);
|
emit('chosen', props.file);
|
||||||
} else {
|
} else {
|
||||||
os.popupMenu(getDriveFileMenu(props.file, props.folder), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
|
router.push(`/my/drive/file/${props.file.id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -615,6 +615,8 @@ defineExpose({
|
|||||||
height: 1.25em;
|
height: 1.25em;
|
||||||
vertical-align: -.25em;
|
vertical-align: -.25em;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div style="container-type: inline-size;">
|
<div style="container-type: inline-size;">
|
||||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||||
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
|
<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
|
||||||
<MkCwButton v-model="showContent" :note="appearNote"/>
|
<MkCwButton v-model="showContent" :note="appearNote" style="margin: 4px 0;"/>
|
||||||
</p>
|
</p>
|
||||||
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
|
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
|
||||||
<div :class="$style.text">
|
<div :class="$style.text">
|
||||||
|
@@ -24,9 +24,11 @@ const props = withDefaults(defineProps<{
|
|||||||
role?: string;
|
role?: string;
|
||||||
sound?: boolean;
|
sound?: boolean;
|
||||||
withRenotes?: boolean;
|
withRenotes?: boolean;
|
||||||
|
withReplies?: boolean;
|
||||||
onlyFiles?: boolean;
|
onlyFiles?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
withRenotes: true,
|
withRenotes: true,
|
||||||
|
withReplies: false,
|
||||||
onlyFiles: false,
|
onlyFiles: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -90,10 +92,12 @@ if (props.src === 'antenna') {
|
|||||||
endpoint = 'notes/local-timeline';
|
endpoint = 'notes/local-timeline';
|
||||||
query = {
|
query = {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
|
withReplies: props.withReplies,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
};
|
};
|
||||||
connection = stream.useChannel('localTimeline', {
|
connection = stream.useChannel('localTimeline', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
|
withReplies: props.withReplies,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
});
|
});
|
||||||
connection.on('note', prepend);
|
connection.on('note', prepend);
|
||||||
@@ -101,10 +105,12 @@ if (props.src === 'antenna') {
|
|||||||
endpoint = 'notes/hybrid-timeline';
|
endpoint = 'notes/hybrid-timeline';
|
||||||
query = {
|
query = {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
|
withReplies: props.withReplies,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
};
|
};
|
||||||
connection = stream.useChannel('hybridTimeline', {
|
connection = stream.useChannel('hybridTimeline', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
|
withReplies: props.withReplies,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
});
|
});
|
||||||
connection.on('note', prepend);
|
connection.on('note', prepend);
|
||||||
|
302
packages/frontend/src/pages/drive.file.info.vue
Normal file
302
packages/frontend/src/pages/drive.file.info.vue
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkInfo>{{ i18n.ts._fileViewer.thisPageCanBeSeenFromTheAuthor }}</MkInfo>
|
||||||
|
<MkLoading v-if="fetching"/>
|
||||||
|
<div v-else-if="file" class="_gaps">
|
||||||
|
<div :class="$style.filePreviewRoot">
|
||||||
|
<MkMediaList :mediaList="[file]"></MkMediaList>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.fileQuickActionsRoot">
|
||||||
|
<button class="_button" :class="$style.fileNameEditBtn" @click="rename()">
|
||||||
|
<h2 class="_nowrap" :class="$style.fileName">{{ file.name }}</h2>
|
||||||
|
<i class="ti ti-pencil" :class="$style.fileNameEditIcon"></i>
|
||||||
|
</button>
|
||||||
|
<div :class="$style.fileQuickActionsOthers">
|
||||||
|
<button v-tooltip="i18n.ts.createNoteFromTheFile" class="_button" :class="$style.fileQuickActionsOthersButton" @click="postThis()">
|
||||||
|
<i class="ti ti-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<button v-if="isImage" v-tooltip="i18n.ts.cropImage" class="_button" :class="$style.fileQuickActionsOthersButton" @click="crop()">
|
||||||
|
<i class="ti ti-crop"></i>
|
||||||
|
</button>
|
||||||
|
<button v-if="file.isSensitive" v-tooltip="i18n.ts.unmarkAsSensitive" class="_button" :class="$style.fileQuickActionsOthersButton" @click="toggleSensitive()">
|
||||||
|
<i class="ti ti-eye"></i>
|
||||||
|
</button>
|
||||||
|
<button v-else v-tooltip="i18n.ts.markAsSensitive" class="_button" :class="$style.fileQuickActionsOthersButton" @click="toggleSensitive()">
|
||||||
|
<i class="ti ti-eye-exclamation"></i>
|
||||||
|
</button>
|
||||||
|
<a v-tooltip="i18n.ts.download" :href="file.url" :download="file.name" class="_button" :class="$style.fileQuickActionsOthersButton">
|
||||||
|
<i class="ti ti-download"></i>
|
||||||
|
</a>
|
||||||
|
<button v-tooltip="i18n.ts.delete" class="_button" :class="[$style.fileQuickActionsOthersButton, $style.danger]" @click="deleteFile()">
|
||||||
|
<i class="ti ti-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="_button" :class="$style.fileAltEditBtn" @click="describe()">
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.description }}</template>
|
||||||
|
<template #value>{{ file.comment ? file.comment : `(${i18n.ts.none})` }}<i class="ti ti-pencil" :class="$style.fileAltEditIcon"></i></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</button>
|
||||||
|
<MkKeyValue :class="$style.fileMetaDataChildren">
|
||||||
|
<template #key>{{ i18n.ts._fileViewer.uploadedAt }}</template>
|
||||||
|
<template #value><MkTime :time="file.createdAt" mode="detail"/></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue :class="$style.fileMetaDataChildren">
|
||||||
|
<template #key>{{ i18n.ts._fileViewer.type }}</template>
|
||||||
|
<template #value>{{ file.type }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue :class="$style.fileMetaDataChildren">
|
||||||
|
<template #key>{{ i18n.ts._fileViewer.size }}</template>
|
||||||
|
<template #value>{{ bytes(file.size) }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="_fullinfo">
|
||||||
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, defineAsyncComponent, onMounted } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import MkMediaList from '@/components/MkMediaList.vue';
|
||||||
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
|
import bytes from '@/filters/bytes.js';
|
||||||
|
import { infoImageUrl } from '@/instance.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { useRouter } from '@/router.js';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fileId: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const fetching = ref(true);
|
||||||
|
const file = ref<Misskey.entities.DriveFile>();
|
||||||
|
const isImage = computed(() => file.value?.type.startsWith('image/'));
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
fetching.value = true;
|
||||||
|
|
||||||
|
file.value = await os.api('drive/files/show', {
|
||||||
|
fileId: props.fileId,
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
fetching.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function postThis() {
|
||||||
|
if (!file.value) return;
|
||||||
|
|
||||||
|
os.post({
|
||||||
|
initialFiles: [file.value],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function crop() {
|
||||||
|
if (!file.value) return;
|
||||||
|
|
||||||
|
os.cropImage(file.value, {
|
||||||
|
aspectRatio: NaN,
|
||||||
|
uploadFolder: file.value.folderId ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSensitive() {
|
||||||
|
if (!file.value) return;
|
||||||
|
|
||||||
|
os.apiWithDialog('drive/files/update', {
|
||||||
|
fileId: file.value.id,
|
||||||
|
isSensitive: !file.value.isSensitive,
|
||||||
|
}).then(async () => {
|
||||||
|
await fetch();
|
||||||
|
}).catch(err => {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.error,
|
||||||
|
text: err.message,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function rename() {
|
||||||
|
if (!file.value) return;
|
||||||
|
|
||||||
|
os.inputText({
|
||||||
|
title: i18n.ts.renameFile,
|
||||||
|
placeholder: i18n.ts.inputNewFileName,
|
||||||
|
default: file.value.name,
|
||||||
|
}).then(({ canceled, result: name }) => {
|
||||||
|
if (canceled) return;
|
||||||
|
os.apiWithDialog('drive/files/update', {
|
||||||
|
fileId: file.value.id,
|
||||||
|
name: name,
|
||||||
|
}).then(async () => {
|
||||||
|
await fetch();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function describe() {
|
||||||
|
if (!file.value) return;
|
||||||
|
|
||||||
|
os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {
|
||||||
|
default: file.value.comment ?? '',
|
||||||
|
file: file.value,
|
||||||
|
}, {
|
||||||
|
done: caption => {
|
||||||
|
os.apiWithDialog('drive/files/update', {
|
||||||
|
fileId: file.value.id,
|
||||||
|
comment: caption.length === 0 ? null : caption,
|
||||||
|
}).then(async () => {
|
||||||
|
await fetch();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}, 'closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteFile() {
|
||||||
|
if (!file.value) return;
|
||||||
|
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.t('driveFileDeleteConfirm', { name: file.value.name }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
await os.apiWithDialog('drive/files/delete', {
|
||||||
|
fileId: file.value.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
router.push('/my/drive');
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetch();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
|
||||||
|
.filePreviewRoot {
|
||||||
|
background: var(--panel);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
// MkMediaList 内の上部マージン 4px
|
||||||
|
padding: calc(1rem - 4px) 1rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileQuickActionsRoot {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (min-width: 500px) {
|
||||||
|
.fileQuickActionsRoot {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileQuickActionsOthers {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 1rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.fileQuickActionsOthersButton {
|
||||||
|
padding: .5rem;
|
||||||
|
border-radius: 99rem;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: var(--accentedBg);
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
color: #ff2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.danger:hover,
|
||||||
|
&.danger:focus-visible {
|
||||||
|
background-color: rgba(255, 42, 42, .15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileNameEditBtn {
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: .8rem;
|
||||||
|
|
||||||
|
>.fileNameEditIcon {
|
||||||
|
color: transparent;
|
||||||
|
visibility: hidden;
|
||||||
|
padding-left: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
>.fileName {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--accentedBg);
|
||||||
|
|
||||||
|
>.fileName,
|
||||||
|
>.fileNameEditIcon {
|
||||||
|
visibility: visible;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileMetaDataChildren {
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileAltEditBtn {
|
||||||
|
text-align: start;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
|
||||||
|
.fileAltEditIcon {
|
||||||
|
display: inline-block;
|
||||||
|
color: transparent;
|
||||||
|
visibility: hidden;
|
||||||
|
padding-left: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
background-color: var(--accentedBg);
|
||||||
|
|
||||||
|
.fileAltEditIcon {
|
||||||
|
color: var(--accent);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
33
packages/frontend/src/pages/drive.file.notes.vue
Normal file
33
packages/frontend/src/pages/drive.file.notes.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkInfo>{{ i18n.ts._fileViewer.thisPageCanBeSeenFromTheAuthor }}</MkInfo>
|
||||||
|
<MkNotes ref="tlComponent" :pagination="pagination"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { Paging } from '@/components/MkPagination.vue';
|
||||||
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import MkNotes from '@/components/MkNotes.vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fileId: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const realFileId = computed(() => props.fileId);
|
||||||
|
|
||||||
|
const pagination = ref<Paging>({
|
||||||
|
endpoint: 'drive/files/attached-notes',
|
||||||
|
limit: 10,
|
||||||
|
params: {
|
||||||
|
fileId: realFileId.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
52
packages/frontend/src/pages/drive.file.vue
Normal file
52
packages/frontend/src/pages/drive.file.vue
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<MkStickyContainer>
|
||||||
|
<template #header>
|
||||||
|
<MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<MkSpacer v-if="tab === 'info'" :contentMax="800">
|
||||||
|
<XFileInfo :fileId="fileId"/>
|
||||||
|
</MkSpacer>
|
||||||
|
|
||||||
|
<MkSpacer v-else-if="tab === 'notes'" :contentMax="800">
|
||||||
|
<XNotes :fileId="fileId"/>
|
||||||
|
</MkSpacer>
|
||||||
|
</MkStickyContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, defineAsyncComponent } from 'vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fileId: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const XFileInfo = defineAsyncComponent(() => import('./drive.file.info.vue'));
|
||||||
|
const XNotes = defineAsyncComponent(() => import('./drive.file.notes.vue'));
|
||||||
|
|
||||||
|
const tab = ref('info');
|
||||||
|
|
||||||
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
|
const headerTabs = computed(() => [{
|
||||||
|
key: 'info',
|
||||||
|
title: i18n.ts.info,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
}, {
|
||||||
|
key: 'notes',
|
||||||
|
title: i18n.ts._fileViewer.attachedNotes,
|
||||||
|
icon: 'ti ti-pencil',
|
||||||
|
}]);
|
||||||
|
|
||||||
|
definePageMetadata(computed(() => ({
|
||||||
|
title: i18n.ts._fileViewer.title,
|
||||||
|
icon: 'ti ti-file',
|
||||||
|
})));
|
||||||
|
</script>
|
@@ -15,10 +15,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div :class="$style.tl">
|
<div :class="$style.tl">
|
||||||
<MkTimeline
|
<MkTimeline
|
||||||
ref="tlComponent"
|
ref="tlComponent"
|
||||||
:key="src + withRenotes + onlyFiles"
|
:key="src + withRenotes + withReplies + onlyFiles"
|
||||||
:src="src.split(':')[0]"
|
:src="src.split(':')[0]"
|
||||||
:list="src.split(':')[1]"
|
:list="src.split(':')[1]"
|
||||||
:withRenotes="withRenotes"
|
:withRenotes="withRenotes"
|
||||||
|
:withReplies="withReplies"
|
||||||
:onlyFiles="onlyFiles"
|
:onlyFiles="onlyFiles"
|
||||||
:sound="true"
|
:sound="true"
|
||||||
@queue="queueUpdated"
|
@queue="queueUpdated"
|
||||||
@@ -61,6 +62,7 @@ let queue = $ref(0);
|
|||||||
let srcWhenNotSignin = $ref(isLocalTimelineAvailable ? 'local' : 'global');
|
let srcWhenNotSignin = $ref(isLocalTimelineAvailable ? 'local' : 'global');
|
||||||
const src = $computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin), set: (x) => saveSrc(x) });
|
const src = $computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin), set: (x) => saveSrc(x) });
|
||||||
const withRenotes = $ref(true);
|
const withRenotes = $ref(true);
|
||||||
|
const withReplies = $ref(false);
|
||||||
const onlyFiles = $ref(false);
|
const onlyFiles = $ref(false);
|
||||||
|
|
||||||
watch($$(src), () => queue = 0);
|
watch($$(src), () => queue = 0);
|
||||||
@@ -142,7 +144,11 @@ const headerActions = $computed(() => [{
|
|||||||
text: i18n.ts.showRenotes,
|
text: i18n.ts.showRenotes,
|
||||||
icon: 'ti ti-repeat',
|
icon: 'ti ti-repeat',
|
||||||
ref: $$(withRenotes),
|
ref: $$(withRenotes),
|
||||||
}, {
|
}, src === 'local' || src === 'social' ? {
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.showRepliesToOthersInTimeline,
|
||||||
|
ref: $$(withReplies),
|
||||||
|
} : undefined, {
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
text: i18n.ts.fileAttachedOnly,
|
text: i18n.ts.fileAttachedOnly,
|
||||||
icon: 'ti ti-photo',
|
icon: 'ti ti-photo',
|
||||||
|
@@ -61,20 +61,7 @@ function settings() {
|
|||||||
router.push(`/my/lists/${props.listId}`);
|
router.push(`/my/lists/${props.listId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function timetravel() {
|
|
||||||
const { canceled, result: date } = await os.inputDate({
|
|
||||||
title: i18n.ts.date,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
tlEl.timetravel(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerActions = $computed(() => list ? [{
|
const headerActions = $computed(() => list ? [{
|
||||||
icon: 'ti ti-calendar-time',
|
|
||||||
text: i18n.ts.jumpToSpecifiedDate,
|
|
||||||
handler: timetravel,
|
|
||||||
}, {
|
|
||||||
icon: 'ti ti-settings',
|
icon: 'ti ti-settings',
|
||||||
text: i18n.ts.settings,
|
text: i18n.ts.settings,
|
||||||
handler: settings,
|
handler: settings,
|
||||||
|
@@ -10,15 +10,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<MkLoading v-if="fetching"/>
|
<MkLoading v-if="fetching"/>
|
||||||
<div v-if="!fetching && files.length > 0" :class="$style.stream">
|
<div v-if="!fetching && files.length > 0" :class="$style.stream">
|
||||||
<MkA
|
<template v-for="file in files" :key="file.note.id + file.file.id">
|
||||||
v-for="file in files"
|
<div v-if="file.file.isSensitive && !showingFiles.includes(file.file.id)" :class="$style.sensitive" @click="showingFiles.push(file.file.id)">
|
||||||
:key="file.note.id + file.file.id"
|
<div>
|
||||||
:class="$style.img"
|
<div><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}</div>
|
||||||
:to="notePage(file.note)"
|
<div>{{ i18n.ts.clickToShow }}</div>
|
||||||
>
|
</div>
|
||||||
<!-- TODO: 画像以外のファイルに対応 -->
|
</div>
|
||||||
<ImgWithBlurhash :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name"/>
|
<MkA v-else :class="$style.img" :to="notePage(file.note)">
|
||||||
</MkA>
|
<!-- TODO: 画像以外のファイルに対応 -->
|
||||||
|
<ImgWithBlurhash :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name"/>
|
||||||
|
</MkA>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!fetching && files.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
|
<p v-if="!fetching && files.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,6 +48,7 @@ let files = $ref<{
|
|||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
file: Misskey.entities.DriveFile;
|
file: Misskey.entities.DriveFile;
|
||||||
}[]>([]);
|
}[]>([]);
|
||||||
|
let showingFiles = $ref<string[]>([]);
|
||||||
|
|
||||||
function thumbnail(image: Misskey.entities.DriveFile): string {
|
function thumbnail(image: Misskey.entities.DriveFile): string {
|
||||||
return defaultStore.state.disableShowingAnimatedImages
|
return defaultStore.state.disableShowingAnimatedImages
|
||||||
@@ -94,4 +98,9 @@ onMounted(() => {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sensitive {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -467,6 +467,10 @@ export const routes = [{
|
|||||||
path: '/my/drive',
|
path: '/my/drive',
|
||||||
component: page(() => import('./pages/drive.vue')),
|
component: page(() => import('./pages/drive.vue')),
|
||||||
loginRequired: true,
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/my/drive/file/:fileId',
|
||||||
|
component: page(() => import('./pages/drive.file.vue')),
|
||||||
|
loginRequired: true,
|
||||||
}, {
|
}, {
|
||||||
path: '/my/follow-requests',
|
path: '/my/follow-requests',
|
||||||
component: page(() => import('./pages/follow-requests.vue')),
|
component: page(() => import('./pages/follow-requests.vue')),
|
||||||
|
@@ -27,7 +27,7 @@ function rename(file: Misskey.entities.DriveFile) {
|
|||||||
|
|
||||||
function describe(file: Misskey.entities.DriveFile) {
|
function describe(file: Misskey.entities.DriveFile) {
|
||||||
os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {
|
os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {
|
||||||
default: file.comment != null ? file.comment : '',
|
default: file.comment ?? '',
|
||||||
file: file,
|
file: file,
|
||||||
}, {
|
}, {
|
||||||
done: caption => {
|
done: caption => {
|
||||||
@@ -112,6 +112,11 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
|
|||||||
text: i18n.ts.download,
|
text: i18n.ts.download,
|
||||||
icon: 'ti ti-download',
|
icon: 'ti ti-download',
|
||||||
download: file.name,
|
download: file.name,
|
||||||
|
}, null, {
|
||||||
|
type: 'link',
|
||||||
|
to: `/my/drive/file/${file.id}`,
|
||||||
|
text: i18n.ts._fileViewer.title,
|
||||||
|
icon: 'ti ti-file',
|
||||||
}, null, {
|
}, null, {
|
||||||
text: i18n.ts.delete,
|
text: i18n.ts.delete,
|
||||||
icon: 'ti ti-trash',
|
icon: 'ti ti-trash',
|
||||||
|
@@ -54,9 +54,6 @@
|
|||||||
infoWarnBg: '#42321c',
|
infoWarnBg: '#42321c',
|
||||||
infoWarnFg: '#ffbd3e',
|
infoWarnFg: '#ffbd3e',
|
||||||
switchBg: 'rgba(255, 255, 255, 0.15)',
|
switchBg: 'rgba(255, 255, 255, 0.15)',
|
||||||
cwBg: '#687390',
|
|
||||||
cwFg: '#393f4f',
|
|
||||||
cwHoverBg: '#707b97',
|
|
||||||
buttonBg: 'rgba(255, 255, 255, 0.05)',
|
buttonBg: 'rgba(255, 255, 255, 0.05)',
|
||||||
buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
|
buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
|
||||||
buttonGradateA: '@accent',
|
buttonGradateA: '@accent',
|
||||||
|
@@ -54,9 +54,6 @@
|
|||||||
infoWarnBg: '#fff0db',
|
infoWarnBg: '#fff0db',
|
||||||
infoWarnFg: '#8f6e31',
|
infoWarnFg: '#8f6e31',
|
||||||
switchBg: 'rgba(0, 0, 0, 0.15)',
|
switchBg: 'rgba(0, 0, 0, 0.15)',
|
||||||
cwBg: '#b1b9c1',
|
|
||||||
cwFg: '#fff',
|
|
||||||
cwHoverBg: '#bbc4ce',
|
|
||||||
buttonBg: 'rgba(0, 0, 0, 0.05)',
|
buttonBg: 'rgba(0, 0, 0, 0.05)',
|
||||||
buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
|
buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
|
||||||
buttonGradateA: '@accent',
|
buttonGradateA: '@accent',
|
||||||
|
@@ -6,8 +6,6 @@
|
|||||||
props: {
|
props: {
|
||||||
bg: '#232125',
|
bg: '#232125',
|
||||||
fg: '#efdab9',
|
fg: '#efdab9',
|
||||||
cwBg: '#687390',
|
|
||||||
cwFg: '#393f4f',
|
|
||||||
link: '#78b0a0',
|
link: '#78b0a0',
|
||||||
warn: '#ecb637',
|
warn: '#ecb637',
|
||||||
badge: '#31b1ce',
|
badge: '#31b1ce',
|
||||||
@@ -29,7 +27,6 @@
|
|||||||
success: '#86b300',
|
success: '#86b300',
|
||||||
buttonBg: 'rgba(255, 255, 255, 0.05)',
|
buttonBg: 'rgba(255, 255, 255, 0.05)',
|
||||||
acrylicBg: ':alpha<0.5<@bg',
|
acrylicBg: ':alpha<0.5<@bg',
|
||||||
cwHoverBg: '#707b97',
|
|
||||||
indicator: '@accent',
|
indicator: '@accent',
|
||||||
mentionMe: '#fb5d38',
|
mentionMe: '#fb5d38',
|
||||||
messageBg: '@bg',
|
messageBg: '@bg',
|
||||||
|
@@ -21,8 +21,6 @@
|
|||||||
X15: ':alpha<0<@panel',
|
X15: ':alpha<0<@panel',
|
||||||
X16: ':alpha<0.7<@panel',
|
X16: ':alpha<0.7<@panel',
|
||||||
X17: ':alpha<0.8<@bg',
|
X17: ':alpha<0.8<@bg',
|
||||||
cwBg: '#687390',
|
|
||||||
cwFg: '#393f4f',
|
|
||||||
link: '@accent',
|
link: '@accent',
|
||||||
warn: '#ecb637',
|
warn: '#ecb637',
|
||||||
badge: '#31b1ce',
|
badge: '#31b1ce',
|
||||||
@@ -46,7 +44,6 @@
|
|||||||
buttonBg: 'rgba(255, 255, 255, 0.05)',
|
buttonBg: 'rgba(255, 255, 255, 0.05)',
|
||||||
switchBg: 'rgba(255, 255, 255, 0.15)',
|
switchBg: 'rgba(255, 255, 255, 0.15)',
|
||||||
acrylicBg: ':alpha<0.5<@bg',
|
acrylicBg: ':alpha<0.5<@bg',
|
||||||
cwHoverBg: '#707b97',
|
|
||||||
indicator: '@accent',
|
indicator: '@accent',
|
||||||
mentionMe: '@mention',
|
mentionMe: '@mention',
|
||||||
messageBg: '@bg',
|
messageBg: '@bg',
|
||||||
|
@@ -21,8 +21,6 @@
|
|||||||
X15: ':alpha<0<@panel',
|
X15: ':alpha<0<@panel',
|
||||||
X16: ':alpha<0.7<@panel',
|
X16: ':alpha<0.7<@panel',
|
||||||
X17: ':alpha<0.8<@bg',
|
X17: ':alpha<0.8<@bg',
|
||||||
cwBg: '#687390',
|
|
||||||
cwFg: '#393f4f',
|
|
||||||
link: '@accent',
|
link: '@accent',
|
||||||
warn: '#ecb637',
|
warn: '#ecb637',
|
||||||
badge: '#31b1ce',
|
badge: '#31b1ce',
|
||||||
@@ -46,7 +44,6 @@
|
|||||||
buttonBg: '#0000000d',
|
buttonBg: '#0000000d',
|
||||||
switchBg: 'rgba(255, 255, 255, 0.15)',
|
switchBg: 'rgba(255, 255, 255, 0.15)',
|
||||||
acrylicBg: ':alpha<0.5<@bg',
|
acrylicBg: ':alpha<0.5<@bg',
|
||||||
cwHoverBg: '#707b97',
|
|
||||||
indicator: '@accent',
|
indicator: '@accent',
|
||||||
mentionMe: '@mention',
|
mentionMe: '@mention',
|
||||||
messageBg: '@bg',
|
messageBg: '@bg',
|
||||||
|
@@ -9,8 +9,6 @@
|
|||||||
props: {
|
props: {
|
||||||
bg: '#fafafa',
|
bg: '#fafafa',
|
||||||
fg: '#444',
|
fg: '#444',
|
||||||
cwBg: '#b1b9c1',
|
|
||||||
cwFg: '#fff',
|
|
||||||
link: '#ff9400',
|
link: '#ff9400',
|
||||||
warn: '#ecb637',
|
warn: '#ecb637',
|
||||||
badge: '#31b1ce',
|
badge: '#31b1ce',
|
||||||
@@ -32,7 +30,6 @@
|
|||||||
success: '#86b300',
|
success: '#86b300',
|
||||||
buttonBg: 'rgba(0, 0, 0, 0.05)',
|
buttonBg: 'rgba(0, 0, 0, 0.05)',
|
||||||
acrylicBg: ':alpha<0.5<@bg',
|
acrylicBg: ':alpha<0.5<@bg',
|
||||||
cwHoverBg: '#bbc4ce',
|
|
||||||
indicator: '@accent',
|
indicator: '@accent',
|
||||||
mentionMe: '@mention',
|
mentionMe: '@mention',
|
||||||
messageBg: '@bg',
|
messageBg: '@bg',
|
||||||
|
@@ -31,6 +31,7 @@ export type Column = {
|
|||||||
excludeTypes?: typeof notificationTypes[number][];
|
excludeTypes?: typeof notificationTypes[number][];
|
||||||
tl?: 'home' | 'local' | 'social' | 'global';
|
tl?: 'home' | 'local' | 'social' | 'global';
|
||||||
withRenotes?: boolean;
|
withRenotes?: boolean;
|
||||||
|
withReplies?: boolean;
|
||||||
onlyFiles?: boolean;
|
onlyFiles?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -23,9 +23,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkTimeline
|
<MkTimeline
|
||||||
v-else-if="column.tl"
|
v-else-if="column.tl"
|
||||||
ref="timeline"
|
ref="timeline"
|
||||||
:key="column.tl + withRenotes + onlyFiles"
|
:key="column.tl + withRenotes + withReplies + onlyFiles"
|
||||||
:src="column.tl"
|
:src="column.tl"
|
||||||
:withRenotes="withRenotes"
|
:withRenotes="withRenotes"
|
||||||
|
:withReplies="withReplies"
|
||||||
:onlyFiles="onlyFiles"
|
:onlyFiles="onlyFiles"
|
||||||
/>
|
/>
|
||||||
</XColumn>
|
</XColumn>
|
||||||
@@ -51,6 +52,7 @@ let disabled = $ref(false);
|
|||||||
const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
|
const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
|
||||||
const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
|
const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
|
||||||
const withRenotes = $ref(props.column.withRenotes ?? true);
|
const withRenotes = $ref(props.column.withRenotes ?? true);
|
||||||
|
const withReplies = $ref(props.column.withReplies ?? false);
|
||||||
const onlyFiles = $ref(props.column.onlyFiles ?? false);
|
const onlyFiles = $ref(props.column.onlyFiles ?? false);
|
||||||
|
|
||||||
watch($$(withRenotes), v => {
|
watch($$(withRenotes), v => {
|
||||||
@@ -107,7 +109,11 @@ const menu = [{
|
|||||||
type: 'switch',
|
type: 'switch',
|
||||||
text: i18n.ts.showRenotes,
|
text: i18n.ts.showRenotes,
|
||||||
ref: $$(withRenotes),
|
ref: $$(withRenotes),
|
||||||
}, {
|
}, props.column.tl === 'local' || props.column.tl === 'social' ? {
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.showRepliesToOthersInTimeline,
|
||||||
|
ref: $$(withReplies),
|
||||||
|
} : undefined, {
|
||||||
type: 'switch',
|
type: 'switch',
|
||||||
text: i18n.ts.fileAttachedOnly,
|
text: i18n.ts.fileAttachedOnly,
|
||||||
ref: $$(onlyFiles),
|
ref: $$(onlyFiles),
|
||||||
|
@@ -23,9 +23,9 @@
|
|||||||
"@microsoft/api-extractor": "7.38.0",
|
"@microsoft/api-extractor": "7.38.0",
|
||||||
"@swc/jest": "0.2.29",
|
"@swc/jest": "0.2.29",
|
||||||
"@types/jest": "29.5.5",
|
"@types/jest": "29.5.5",
|
||||||
"@types/node": "20.8.3",
|
"@types/node": "20.8.4",
|
||||||
"@typescript-eslint/eslint-plugin": "6.7.4",
|
"@typescript-eslint/eslint-plugin": "6.7.5",
|
||||||
"@typescript-eslint/parser": "6.7.4",
|
"@typescript-eslint/parser": "6.7.5",
|
||||||
"eslint": "8.51.0",
|
"eslint": "8.51.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-fetch-mock": "3.0.3",
|
"jest-fetch-mock": "3.0.3",
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
"misskey-js": "workspace:*"
|
"misskey-js": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/parser": "6.7.4",
|
"@typescript-eslint/parser": "6.7.5",
|
||||||
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
|
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
|
||||||
"eslint": "8.51.0",
|
"eslint": "8.51.0",
|
||||||
"eslint-plugin-import": "2.28.1",
|
"eslint-plugin-import": "2.28.1",
|
||||||
|
335
pnpm-lock.yaml
generated
335
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user