Compare commits
18 Commits
8624338dbd
...
01a804cd9d
Author | SHA1 | Date | |
---|---|---|---|
01a804cd9d | |||
2dc84bafa2 | |||
2e38ad3d90 | |||
2f6f1a7158 | |||
b2ec0852e6 | |||
|
046eab7496 | ||
|
4d928915f6 | ||
|
75b424192e | ||
|
762cc679a9 | ||
|
8f66f9ca59 | ||
|
a73a09a999 | ||
|
65f0138bd7 | ||
|
61b758e3dc | ||
|
edeca403e1 | ||
|
d005daae84 | ||
|
c47140eab7 | ||
|
a8bbccbefa | ||
|
3fdcf99011 |
@ -1234,7 +1234,7 @@ _announcement:
|
||||
forExistingUsers: "Anunci per usuaris registrats"
|
||||
forExistingUsersDescription: "Aquest avís només es mostrarà als usuaris existents fins al moment de la publicació. Si no també es mostrarà als usuaris que es registrin després de la publicació."
|
||||
needConfirmationToRead: "Es necessita confirmació de lectura de la notificació "
|
||||
needConfirmationToReadDescription: "Si s'activa es mostrarà un diàleg per confirmar la lectura d'aquesta notificació. A més aquesta notificació serà exclosa de qualsevol funcionalitat com \"Marcar tot com a llegit\"."
|
||||
needConfirmationToReadDescription: "Si s'activa es mostrarà un diàleg per confirmar la lectura d'aquesta notificació."
|
||||
end: "Final de la notificació "
|
||||
tooManyActiveAnnouncementDescription: "Tenir massa notificacions actives pot empitjorar l'experiència de l'usuari. Considera finalitzar els anuncis que siguin antics."
|
||||
readConfirmTitle: "Marcar com llegida?"
|
||||
|
@ -1189,7 +1189,7 @@ _announcement:
|
||||
forExistingUsers: "Nur für existierende Nutzer"
|
||||
forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt."
|
||||
needConfirmationToRead: "Separate Lesebestätigung erfordern"
|
||||
needConfirmationToReadDescription: "Ist dies aktiviert, so wird beim Markieren dieser Ankündigung als gelesen ein separates Bestätigungsfenster angezeigt. Auch wird sie von der \"Alle als gelesen markieren\"-Funktion ausgenommen."
|
||||
needConfirmationToReadDescription: "Ist dies aktiviert, so wird beim Markieren dieser Ankündigung als gelesen ein separates Bestätigungsfenster angezeigt."
|
||||
end: "Ankündigung archivieren"
|
||||
tooManyActiveAnnouncementDescription: "Zu viele aktive Ankündigungen können die Benutzerfreundlichkeit verschlechtern. Es wird empfohlen, veraltete Ankündigungen zu archivieren."
|
||||
readConfirmTitle: "Als gelesen markieren?"
|
||||
|
@ -138,6 +138,9 @@ mute: "Mute"
|
||||
unmute: "Unmute"
|
||||
renoteMute: "Mute Renotes"
|
||||
renoteUnmute: "Unmute Renotes"
|
||||
mutedReactions: "Mute reactions"
|
||||
muteThisReaction: "Mute this reaction"
|
||||
unmuteThisReaction: "Unmute this reaction"
|
||||
block: "Block"
|
||||
unblock: "Unblock"
|
||||
suspend: "Suspend"
|
||||
@ -1333,7 +1336,9 @@ _announcement:
|
||||
forExistingUsers: "Existing users only"
|
||||
forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it."
|
||||
needConfirmationToRead: "Require separate read confirmation"
|
||||
needConfirmationToReadDescription: "A separate prompt to confirm marking this announcement as read will be displayed if enabled. This announcement will also be excluded from any \"Mark all as read\" functionality."
|
||||
needConfirmationToReadDescription: "A separate prompt to confirm marking this announcement as read will be displayed if enabled."
|
||||
needEnrollmentTutorialToRead: "Require tutorial completion to read"
|
||||
needEnrollmentTutorialToReadDescription: "Users must complete the tutorial to read this announcement if enabled."
|
||||
end: "Archive announcement"
|
||||
tooManyActiveAnnouncementDescription: "Having too many active announcements may worsen the user experience. Please consider archiving announcements that have become obsolete."
|
||||
readConfirmTitle: "Mark as read?"
|
||||
|
@ -1252,7 +1252,7 @@ _announcement:
|
||||
forExistingUsers: "Solo para usuarios registrados"
|
||||
forExistingUsersDescription: "Este anuncio solo se mostrará a aquellos usuarios registrados en el momento de su publicación. Si se deshabilita esta opción, aquellos usuarios que se registren tras su publicación también lo verán."
|
||||
needConfirmationToRead: "Requerir confirmación de lectura aparte"
|
||||
needConfirmationToReadDescription: "Si se habilita esta opción, se pedirá una confirmación de lectura aparte. Además, este anuncio será excluido de cualquier funcionalidad de \"Marcar todos como leídos\"."
|
||||
needConfirmationToReadDescription: "Si se habilita esta opción, se pedirá una confirmación de lectura aparte."
|
||||
end: "Anuncios archivados"
|
||||
tooManyActiveAnnouncementDescription: "Tener demasiados anuncios activos empeora la experiencia de usuario. Por favor, considera archivar aquellos anuncios que hayan quedado obsoletos."
|
||||
readConfirmTitle: "¿Marcar como leído?"
|
||||
|
@ -1237,7 +1237,7 @@ _bubbleGame:
|
||||
_announcement:
|
||||
forExistingUsers: "Pour les utilisateurs existants seulement"
|
||||
needConfirmationToRead: "Exiger la confirmation de la lecture"
|
||||
needConfirmationToReadDescription: "Si activé, afficher un dialogue de confirmation quand l'annonce est marquée comme lue. Aussi, elle sera exclue de « marquer tout comme lu » ."
|
||||
needConfirmationToReadDescription: "Si activé, afficher un dialogue de confirmation quand l'annonce est marquée comme lue."
|
||||
end: "Archiver l'annonce"
|
||||
tooManyActiveAnnouncementDescription: "Un grand nombre d'annonces actives peut baisser l'expérience utilisateur. Considérez d'archiver les annonces obsolètes."
|
||||
readConfirmTitle: "Marquer comme lu ?"
|
||||
|
@ -1254,7 +1254,7 @@ _announcement:
|
||||
forExistingUsers: "Hanya pengguna yang telah ada"
|
||||
forExistingUsersDescription: "Pengumuman ini akan dimunculkan ke pengguna yang sudah ada dari titik waktu publikasi jika dinyalakan. Apabila dimatikan, mereka yang baru mendaftar setelah publikasi ini akan juga melihatnya."
|
||||
needConfirmationToRead: "Membutuhkan konfirmasi terpisah bahwa telah dibaca"
|
||||
needConfirmationToReadDescription: "Permintaan terpisah untuk mengonfirmasi menandai pengumuman ini telah dibaca akan ditampilkan apabila fitur ini dinyalakan. Pengumuman ini juga akan dikecualikan dari fungsi \"Tandai semua telah dibaca\"."
|
||||
needConfirmationToReadDescription: "Permintaan terpisah untuk mengonfirmasi menandai pengumuman ini telah dibaca akan ditampilkan apabila fitur ini dinyalakan."
|
||||
end: "Arsipkan pengumuman"
|
||||
tooManyActiveAnnouncementDescription: "Terlalu banyak pengumuman dapat memperburuk pengalaman pengguna. Mohon pertimbangkan untuk mengarsipkan pengumuman yang sudah usang/tidak relevan."
|
||||
readConfirmTitle: "Tandai telah dibaca?"
|
||||
|
30
locales/index.d.ts
vendored
30
locales/index.d.ts
vendored
@ -360,6 +360,14 @@ export interface Locale extends ILocale {
|
||||
* フォローされています
|
||||
*/
|
||||
"followsYou": string;
|
||||
/**
|
||||
* ブロックしています
|
||||
*/
|
||||
"youAreBlocking": string;
|
||||
/**
|
||||
* ブロックされています
|
||||
*/
|
||||
"youAreBlocked": string;
|
||||
/**
|
||||
* リスト作成
|
||||
*/
|
||||
@ -580,6 +588,18 @@ export interface Locale extends ILocale {
|
||||
* リノートのミュートを解除
|
||||
*/
|
||||
"renoteUnmute": string;
|
||||
/**
|
||||
* リアクションのミュート
|
||||
*/
|
||||
"mutedReactions": string;
|
||||
/**
|
||||
* このリアクションをミュートする
|
||||
*/
|
||||
"muteThisReaction": string;
|
||||
/**
|
||||
* このリアクションのミュートを解除する
|
||||
*/
|
||||
"unmuteThisReaction": string;
|
||||
/**
|
||||
* ブロック
|
||||
*/
|
||||
@ -5445,9 +5465,17 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"needConfirmationToRead": string;
|
||||
/**
|
||||
* 有効にすると、このお知らせを既読にする際に確認ダイアログが表示されます。また、一括既読操作の対象になりません。
|
||||
* 有効にすると、このお知らせを既読にする際に確認ダイアログが表示されます。
|
||||
*/
|
||||
"needConfirmationToReadDescription": string;
|
||||
/**
|
||||
* チュートリアルの受講が必要
|
||||
*/
|
||||
"needEnrollmentTutorialToRead": string;
|
||||
/**
|
||||
* 有効にすると、このお知らせを既読にするためにはチュートリアルの受講が必要です。
|
||||
*/
|
||||
"needEnrollmentTutorialToReadDescription": string;
|
||||
/**
|
||||
* お知らせを終了
|
||||
*/
|
||||
|
@ -1252,7 +1252,7 @@ _announcement:
|
||||
forExistingUsers: "Solo ai profili attuali"
|
||||
forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio."
|
||||
needConfirmationToRead: "Richiede la conferma di lettura"
|
||||
needConfirmationToReadDescription: "Sarà visualizzata una finestra di dialogo che richiede la conferma di lettura. Inoltre, non è soggetto a conferme di lettura massicce."
|
||||
needConfirmationToReadDescription: "Sarà visualizzata una finestra di dialogo che richiede la conferma di lettura."
|
||||
end: "Archivia l'annuncio"
|
||||
tooManyActiveAnnouncementDescription: "L'esperienza delle persone può peggiorare se ci sono troppi annunci attivi. Considera anche l'archiviazione degli annunci conclusi."
|
||||
readConfirmTitle: "Segnare come già letto?"
|
||||
|
@ -86,6 +86,8 @@ notes: "ノート"
|
||||
following: "フォロー"
|
||||
followers: "フォロワー"
|
||||
followsYou: "フォローされています"
|
||||
youAreBlocking: "ブロックしています"
|
||||
youAreBlocked: "ブロックされています"
|
||||
createList: "リスト作成"
|
||||
manageLists: "リストの管理"
|
||||
error: "エラー"
|
||||
@ -141,6 +143,9 @@ mute: "ミュート"
|
||||
unmute: "ミュート解除"
|
||||
renoteMute: "リノートをミュート"
|
||||
renoteUnmute: "リノートのミュートを解除"
|
||||
mutedReactions: "リアクションのミュート"
|
||||
muteThisReaction: "このリアクションをミュートする"
|
||||
unmuteThisReaction: "このリアクションのミュートを解除する"
|
||||
block: "ブロック"
|
||||
unblock: "ブロック解除"
|
||||
suspend: "凍結"
|
||||
@ -1361,7 +1366,9 @@ _announcement:
|
||||
forExistingUsers: "既存ユーザーのみ"
|
||||
forExistingUsersDescription: "有効にすると、このお知らせ作成時点で存在するユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。"
|
||||
needConfirmationToRead: "既読にするのに確認が必要"
|
||||
needConfirmationToReadDescription: "有効にすると、このお知らせを既読にする際に確認ダイアログが表示されます。また、一括既読操作の対象になりません。"
|
||||
needConfirmationToReadDescription: "有効にすると、このお知らせを既読にする際に確認ダイアログが表示されます。"
|
||||
needEnrollmentTutorialToRead: "チュートリアルの受講が必要"
|
||||
needEnrollmentTutorialToReadDescription: "有効にすると、このお知らせを既読にするためにはチュートリアルの受講が必要です。"
|
||||
end: "お知らせを終了"
|
||||
tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。"
|
||||
readConfirmTitle: "既読にしますか?"
|
||||
|
@ -1254,7 +1254,7 @@ _announcement:
|
||||
forExistingUsers: "もうおるユーザーのみ"
|
||||
forExistingUsersDescription: "オンにしたらこのお知らせができた時点でおる人らにだけお知らせが行くで。切ったらこの知らせが行ったあとにアカウント作った人にもちゃんとお知らせが行くで。"
|
||||
needConfirmationToRead: "既読にするんやったら確認してや"
|
||||
needConfirmationToReadDescription: "オンにしたら、このお知らせを既読にする時に確認するで。ついでに、一括既読しても既読扱いにならへんで。"
|
||||
needConfirmationToReadDescription: "オンにしたら、このお知らせを既読にする時に確認するで。"
|
||||
end: "お知らせやめる"
|
||||
tooManyActiveAnnouncementDescription: "お知らせが多すぎてUXが落ちそうや。終わったお知らせはアーカイブに突っ込んだほうがええかも。"
|
||||
readConfirmTitle: "既読にしてええんやな?"
|
||||
|
@ -85,7 +85,9 @@ note: "노트"
|
||||
notes: "노트"
|
||||
following: "팔로잉"
|
||||
followers: "팔로워"
|
||||
followsYou: "나를 팔로우 합니다"
|
||||
followsYou: "나를 팔로우하는 중"
|
||||
youAreBlocking: "내가 차단한 유저"
|
||||
youAreBlocked: "나를 차단하는 중"
|
||||
createList: "리스트 만들기"
|
||||
manageLists: "리스트 관리"
|
||||
error: "오류"
|
||||
@ -138,6 +140,9 @@ mute: "뮤트"
|
||||
unmute: "뮤트 해제"
|
||||
renoteMute: "리노트 뮤트하기"
|
||||
renoteUnmute: "리노트 뮤트 해제"
|
||||
mutedReactions: "리액션 뮤트"
|
||||
muteThisReaction: "이 리액션을 뮤트하기"
|
||||
unmuteThisReaction: "이 리액션의 뮤트를 해제하기"
|
||||
block: "차단"
|
||||
unblock: "차단 해제"
|
||||
suspend: "정지"
|
||||
@ -1348,7 +1353,9 @@ _announcement:
|
||||
forExistingUsers: "기존 유저에게만 알림"
|
||||
forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다."
|
||||
needConfirmationToRead: "읽음으로 표시하기 전에 확인하기"
|
||||
needConfirmationToReadDescription: "활성화하면 이 공지사항을 읽음으로 표시하기 전에 확인 알림창을 띄웁니다. '모두 읽음'의 대상에서도 제외됩니다."
|
||||
needConfirmationToReadDescription: "활성화하면 이 공지사항을 읽음으로 표시하기 전에 확인 알림창을 띄웁니다."
|
||||
needEnrollmentTutorialToRead: "읽음으로 표시하기 전에 튜토리얼 진행하기"
|
||||
needEnrollmentTutorialToReadDescription: "활성화하면 이 공지사항을 읽음으로 표시하기 전에 사용자에게 튜토리얼을 진행하도록 요구합니다."
|
||||
end: "공지에서 내리기"
|
||||
tooManyActiveAnnouncementDescription: "공지사항이 너무 많을 경우, 유저 경험에 영향을 끼칠 가능성이 있습니다. 오래된 공지사항은 아카이브하시는 것을 권장드립니다."
|
||||
readConfirmTitle: "읽음으로 표시합니까?"
|
||||
|
@ -1254,7 +1254,7 @@ _announcement:
|
||||
forExistingUsers: "ผู้ใช้งานที่มีอยู่เท่านั้น"
|
||||
forExistingUsersDescription: "การประกาศนี้จะแสดงต่อผู้ใช้ที่มีอยู่ ณ จุดที่เผยแพร่นั้นๆถ้าหากเปิดใช้งาน ถ้าหากปิดใช้งานผู้ที่กำลังสมัครใหม่หลังจากโพสต์แล้วนั้นก็จะเห็นเช่นกัน"
|
||||
needConfirmationToRead: "จำเป็นต้องยืนยันว่าอ่านแล้ว"
|
||||
needConfirmationToReadDescription: "กล่องโต้ตอบการยืนยันจะปรากฏขึ้นเมื่อจะทำเครื่องหมายว่าอ่านแล้ว นอกจากนี้ยังทำให้ประกาศนี้ยังไม่ถูกอ่านเมื่อใช้ฟังก์ชั่น “ทำเครื่องหมายฯ ทั้งหมดว่าอ่านแล้ว”"
|
||||
needConfirmationToReadDescription: "กล่องโต้ตอบการยืนยันจะปรากฏขึ้นเมื่อจะทำเครื่องหมายว่าอ่านแล้ว"
|
||||
end: "เก็บประกาศ"
|
||||
tooManyActiveAnnouncementDescription: "การมีประกาศที่ใช้งานมากเกินไปนั้นอาจจะทำให้ประสบการณ์ของผู้ใช้งานนั้นดูแย่ลง โปรดกรุณาพิจารณาการเก็บประกาศที่ล้าสมัยด้วยนะค่ะ"
|
||||
readConfirmTitle: "ทำเครื่องหมายว่าอ่านแล้วเลยไหม?"
|
||||
|
@ -1275,7 +1275,7 @@ _announcement:
|
||||
forExistingUsers: "仅限现有用户"
|
||||
forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。"
|
||||
needConfirmationToRead: "需要确认才能标记为已读"
|
||||
needConfirmationToReadDescription: "若启用,则会在标记已读时会显示确认对话框。此外,它也会不受批量已读操作的影响。"
|
||||
needConfirmationToReadDescription: "若启用,则会在标记已读时会显示确认对话框。"
|
||||
end: "结束公告"
|
||||
tooManyActiveAnnouncementDescription: "若有大量活动公告,可能会造成用户体验下降。请考虑归档已完成的公告。"
|
||||
readConfirmTitle: "标记为已读?"
|
||||
|
@ -1254,7 +1254,7 @@ _announcement:
|
||||
forExistingUsers: "僅限既有的使用者"
|
||||
forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
|
||||
needConfirmationToRead: "必須確認才能標記為已讀"
|
||||
needConfirmationToReadDescription: "啟用代表此公告將顯示對話方塊以確認是否標記為已讀,同時不會受「標記所有公告為已讀」功能影響。"
|
||||
needConfirmationToReadDescription: "啟用代表此公告將顯示對話方塊以確認是否標記為已讀。"
|
||||
end: "結束公告"
|
||||
tooManyActiveAnnouncementDescription: "有過多公告可能會影響使用者體驗。請考慮歸檔已結束的公告。"
|
||||
readConfirmTitle: "標記為已讀嗎?"
|
||||
|
@ -0,0 +1,11 @@
|
||||
export class AnnouncementNeedEnrollmentTutorialToRead1729270181417 {
|
||||
name = 'AnnouncementNeedEnrollmentTutorialToRead1729270181417'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "announcement" ADD "needEnrollmentTutorialToRead" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "announcement" DROP COLUMN "needEnrollmentTutorialToRead"`);
|
||||
}
|
||||
}
|
@ -51,9 +51,9 @@ type Source = {
|
||||
redisForSystemQueue?: RedisOptionsSource;
|
||||
redisForEndedPollNotificationQueue?: RedisOptionsSource;
|
||||
redisForDeliverQueues?: Array<RedisOptionsSource>;
|
||||
redisForInboxQueue?: RedisOptionsSource;
|
||||
redisForInboxQueues?: Array<RedisOptionsSource>;
|
||||
redisForDbQueue?: RedisOptionsSource;
|
||||
redisForRelationshipQueue?: RedisOptionsSource;
|
||||
redisForRelationshipQueues?: Array<RedisOptionsSource>;
|
||||
redisForObjectStorageQueue?: RedisOptionsSource;
|
||||
redisForWebhookDeliverQueue?: RedisOptionsSource;
|
||||
redisForTimelines?: RedisOptionsSource;
|
||||
@ -221,9 +221,9 @@ export type Config = {
|
||||
redisForSystemQueue: RedisOptions & RedisOptionsSource;
|
||||
redisForEndedPollNotificationQueue: RedisOptions & RedisOptionsSource;
|
||||
redisForDeliverQueues: Array<RedisOptions & RedisOptionsSource>;
|
||||
redisForInboxQueue: RedisOptions & RedisOptionsSource;
|
||||
redisForInboxQueues: Array<RedisOptions & RedisOptionsSource>;
|
||||
redisForDbQueue: RedisOptions & RedisOptionsSource;
|
||||
redisForRelationshipQueue: RedisOptions & RedisOptionsSource;
|
||||
redisForRelationshipQueues: Array<RedisOptions & RedisOptionsSource>;
|
||||
redisForObjectStorageQueue: RedisOptions & RedisOptionsSource;
|
||||
redisForWebhookDeliverQueue: RedisOptions & RedisOptionsSource;
|
||||
redisForTimelines: RedisOptions & RedisOptionsSource;
|
||||
@ -297,9 +297,9 @@ export function loadConfig(): Config {
|
||||
redisForSystemQueue: config.redisForSystemQueue ? convertRedisOptions(config.redisForSystemQueue, host) : redisForJobQueue,
|
||||
redisForEndedPollNotificationQueue: config.redisForEndedPollNotificationQueue ? convertRedisOptions(config.redisForEndedPollNotificationQueue, host) : redisForJobQueue,
|
||||
redisForDeliverQueues: config.redisForDeliverQueues ? config.redisForDeliverQueues.map(config => convertRedisOptions(config, host)) : [redisForJobQueue],
|
||||
redisForInboxQueue: config.redisForInboxQueue ? convertRedisOptions(config.redisForInboxQueue, host) : redisForJobQueue,
|
||||
redisForInboxQueues: config.redisForInboxQueues ? config.redisForInboxQueues.map(config => convertRedisOptions(config, host)) : [redisForJobQueue],
|
||||
redisForDbQueue: config.redisForDbQueue ? convertRedisOptions(config.redisForDbQueue, host) : redisForJobQueue,
|
||||
redisForRelationshipQueue: config.redisForRelationshipQueue ? convertRedisOptions(config.redisForRelationshipQueue, host) : redisForJobQueue,
|
||||
redisForRelationshipQueues: config.redisForRelationshipQueues ? config.redisForRelationshipQueues.map(config => convertRedisOptions(config, host)) : [redisForJobQueue],
|
||||
redisForObjectStorageQueue: config.redisForObjectStorageQueue ? convertRedisOptions(config.redisForObjectStorageQueue, host) : redisForJobQueue,
|
||||
redisForWebhookDeliverQueue: config.redisForWebhookDeliverQueue ? convertRedisOptions(config.redisForWebhookDeliverQueue, host) : redisForJobQueue,
|
||||
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
|
||||
|
@ -91,6 +91,7 @@ export class AnnouncementService {
|
||||
forExistingUsers: values.forExistingUsers,
|
||||
silence: values.silence,
|
||||
needConfirmationToRead: values.needConfirmationToRead,
|
||||
needEnrollmentTutorialToRead: values.needEnrollmentTutorialToRead,
|
||||
closeDuration: values.closeDuration,
|
||||
displayOrder: values.displayOrder,
|
||||
userId: values.userId,
|
||||
@ -200,6 +201,7 @@ export class AnnouncementService {
|
||||
icon: values.icon,
|
||||
forExistingUsers: values.forExistingUsers,
|
||||
needConfirmationToRead: values.needConfirmationToRead,
|
||||
needEnrollmentTutorialToRead: values.needEnrollmentTutorialToRead,
|
||||
closeDuration: values.closeDuration,
|
||||
displayOrder: values.displayOrder,
|
||||
silence: values.silence,
|
||||
|
@ -16,9 +16,9 @@ import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, Webhoo
|
||||
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
|
||||
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
||||
export type DeliverQueue = Queues<DeliverJobData>;
|
||||
export type InboxQueue = Bull.Queue<InboxJobData>;
|
||||
export type InboxQueue = Queues<InboxJobData>;
|
||||
export type DbQueue = Bull.Queue;
|
||||
export type RelationshipQueue = Bull.Queue<RelationshipJobData>;
|
||||
export type RelationshipQueue = Queues<RelationshipJobData>;
|
||||
export type ObjectStorageQueue = Bull.Queue;
|
||||
export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
|
||||
|
||||
@ -42,7 +42,7 @@ const $deliver: Provider = {
|
||||
|
||||
const $inbox: Provider = {
|
||||
provide: 'queue:inbox',
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.INBOX, baseQueueOptions(config.redisForInboxQueue, config.bullmqQueueOptions, QUEUE.INBOX)),
|
||||
useFactory: (config: Config) => new Queues(config.redisForInboxQueues.map(queueConfig => new Bull.Queue(QUEUE.INBOX, baseQueueOptions(queueConfig, config.bullmqQueueOptions, QUEUE.INBOX)))),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
@ -54,7 +54,7 @@ const $db: Provider = {
|
||||
|
||||
const $relationship: Provider = {
|
||||
provide: 'queue:relationship',
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.RELATIONSHIP, baseQueueOptions(config.redisForRelationshipQueue, config.bullmqQueueOptions, QUEUE.RELATIONSHIP)),
|
||||
useFactory: (config: Config) => new Queues(config.redisForRelationshipQueues.map(queueConfig => new Bull.Queue(QUEUE.RELATIONSHIP, baseQueueOptions(queueConfig, config.bullmqQueueOptions, QUEUE.RELATIONSHIP)))),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
|
@ -54,6 +54,7 @@ export class AnnouncementEntityService {
|
||||
display: announcement.display,
|
||||
forYou: announcement.userId === me?.id,
|
||||
needConfirmationToRead: announcement.needConfirmationToRead,
|
||||
needEnrollmentTutorialToRead: announcement.needEnrollmentTutorialToRead,
|
||||
closeDuration: announcement.closeDuration,
|
||||
displayOrder: announcement.displayOrder,
|
||||
silence: announcement.silence,
|
||||
|
@ -61,6 +61,11 @@ export class MiAnnouncement {
|
||||
})
|
||||
public needConfirmationToRead: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public needEnrollmentTutorialToRead: boolean;
|
||||
|
||||
@Column('integer', {
|
||||
nullable: false,
|
||||
default: 0,
|
||||
|
@ -48,6 +48,10 @@ export const packedAnnouncementSchema = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
needEnrollmentTutorialToRead: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
forYou: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
@ -79,9 +79,9 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
private systemQueueWorker: Bull.Worker;
|
||||
private dbQueueWorker: Bull.Worker;
|
||||
private deliverQueueWorkers: Bull.Worker[];
|
||||
private inboxQueueWorker: Bull.Worker;
|
||||
private inboxQueueWorkers: Bull.Worker[];
|
||||
private webhookDeliverQueueWorker: Bull.Worker;
|
||||
private relationshipQueueWorker: Bull.Worker;
|
||||
private relationshipQueueWorkers: Bull.Worker[];
|
||||
private objectStorageQueueWorker: Bull.Worker;
|
||||
private endedPollNotificationQueueWorker: Bull.Worker;
|
||||
|
||||
@ -243,8 +243,10 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
//#endregion
|
||||
|
||||
//#region inbox
|
||||
this.inboxQueueWorker = new Bull.Worker(QUEUE.INBOX, (job) => this.inboxProcessorService.process(job), {
|
||||
...baseWorkerOptions(this.config.redisForInboxQueue, this.config.bullmqWorkerOptions, QUEUE.INBOX),
|
||||
this.inboxQueueWorkers = this.config.redisForInboxQueues
|
||||
.filter((_, index) => process.env.QUEUE_WORKER_INDEX == null || index === Number.parseInt(process.env.QUEUE_WORKER_INDEX, 10))
|
||||
.map(config => new Bull.Worker(QUEUE.INBOX, (job) => this.inboxProcessorService.process(job), {
|
||||
...baseWorkerOptions(config, this.config.bullmqWorkerOptions, QUEUE.INBOX),
|
||||
autorun: false,
|
||||
concurrency: this.config.inboxJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
@ -254,16 +256,18 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
const inboxLogger = this.logger.createSubLogger('inbox');
|
||||
this.inboxQueueWorkers.forEach((worker, index) => {
|
||||
const inboxLogger = this.logger.createSubLogger(`inbox-${index}`);
|
||||
|
||||
this.inboxQueueWorker
|
||||
worker
|
||||
.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
|
||||
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
|
||||
.on('failed', (job, err) => inboxLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, error: renderError(err) }))
|
||||
.on('error', (err: Error) => inboxLogger.error(`error ${err.stack}`, { error: renderError(err) }))
|
||||
.on('stalled', (jobId) => inboxLogger.warn(`stalled id=${jobId}`));
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region webhook deliver
|
||||
@ -291,7 +295,9 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
//#endregion
|
||||
|
||||
//#region relationship
|
||||
this.relationshipQueueWorker = new Bull.Worker(QUEUE.RELATIONSHIP, (job) => {
|
||||
this.relationshipQueueWorkers = this.config.redisForRelationshipQueues
|
||||
.filter((_, index) => process.env.QUEUE_WORKER_INDEX == null || index === Number.parseInt(process.env.QUEUE_WORKER_INDEX, 10))
|
||||
.map(config => new Bull.Worker(QUEUE.RELATIONSHIP, (job) => {
|
||||
switch (job.name) {
|
||||
case 'follow': return this.relationshipProcessorService.processFollow(job);
|
||||
case 'unfollow': return this.relationshipProcessorService.processUnfollow(job);
|
||||
@ -300,23 +306,25 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
default: throw new Error(`unrecognized job type ${job.name} for relationship`);
|
||||
}
|
||||
}, {
|
||||
...baseWorkerOptions(this.config.redisForRelationshipQueue, this.config.bullmqWorkerOptions, QUEUE.RELATIONSHIP),
|
||||
...baseWorkerOptions(config, this.config.bullmqWorkerOptions, QUEUE.RELATIONSHIP),
|
||||
autorun: false,
|
||||
concurrency: this.config.relationshipJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
max: this.config.relationshipJobPerSec ?? 64,
|
||||
duration: 1000,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
const relationshipLogger = this.logger.createSubLogger('relationship');
|
||||
this.relationshipQueueWorkers.forEach((worker, index) => {
|
||||
const relationshipLogger = this.logger.createSubLogger(`relationship-${index}`);
|
||||
|
||||
this.relationshipQueueWorker
|
||||
worker
|
||||
.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, error: renderError(err) }))
|
||||
.on('error', (err: Error) => relationshipLogger.error(`error ${err.stack}`, { error: renderError(err) }))
|
||||
.on('stalled', (jobId) => relationshipLogger.warn(`stalled id=${jobId}`));
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region object storage
|
||||
@ -356,9 +364,9 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
this.systemQueueWorker.run(),
|
||||
this.dbQueueWorker.run(),
|
||||
...this.deliverQueueWorkers.map(worker => worker.run()),
|
||||
this.inboxQueueWorker.run(),
|
||||
this.inboxQueueWorkers.map(worker => worker.run()),
|
||||
this.webhookDeliverQueueWorker.run(),
|
||||
this.relationshipQueueWorker.run(),
|
||||
this.relationshipQueueWorkers.map(worker => worker.run()),
|
||||
this.objectStorageQueueWorker.run(),
|
||||
this.endedPollNotificationQueueWorker.run(),
|
||||
]);
|
||||
@ -370,9 +378,9 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
||||
this.systemQueueWorker.close(),
|
||||
this.dbQueueWorker.close(),
|
||||
...this.deliverQueueWorkers.map(worker => worker.close()),
|
||||
this.inboxQueueWorker.close(),
|
||||
this.inboxQueueWorkers.map(worker => worker.close()),
|
||||
this.webhookDeliverQueueWorker.close(),
|
||||
this.relationshipQueueWorker.close(),
|
||||
this.relationshipQueueWorkers.map(worker => worker.close()),
|
||||
this.objectStorageQueueWorker.close(),
|
||||
this.endedPollNotificationQueueWorker.close(),
|
||||
]);
|
||||
|
@ -62,6 +62,10 @@ export const meta = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
needEnrollmentTutorialToRead: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
closeDuration: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
@ -92,6 +96,7 @@ export const paramDef = {
|
||||
display: { type: 'string', enum: ['normal', 'banner', 'dialog'], default: 'normal' },
|
||||
forExistingUsers: { type: 'boolean', default: false },
|
||||
needConfirmationToRead: { type: 'boolean', default: false },
|
||||
needEnrollmentTutorialToRead: { type: 'boolean', default: false },
|
||||
closeDuration: { type: 'number', default: 0 },
|
||||
displayOrder: { type: 'number', default: 0 },
|
||||
silence: { type: 'boolean', default: false },
|
||||
@ -115,6 +120,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
display: ps.display,
|
||||
forExistingUsers: ps.forExistingUsers,
|
||||
needConfirmationToRead: ps.needConfirmationToRead,
|
||||
needEnrollmentTutorialToRead: ps.needEnrollmentTutorialToRead,
|
||||
closeDuration: ps.closeDuration,
|
||||
displayOrder: ps.displayOrder,
|
||||
silence: ps.silence,
|
||||
|
@ -72,6 +72,10 @@ export const meta = {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
needEnrollmentTutorialToRead: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
closeDuration: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
@ -139,6 +143,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
isActive: announcement.isActive,
|
||||
forExistingUsers: announcement.forExistingUsers,
|
||||
needConfirmationToRead: announcement.needConfirmationToRead,
|
||||
needEnrollmentTutorialToRead: announcement.needEnrollmentTutorialToRead,
|
||||
closeDuration: announcement.closeDuration,
|
||||
displayOrder: announcement.displayOrder,
|
||||
silence: announcement.silence,
|
||||
|
@ -37,6 +37,7 @@ export const paramDef = {
|
||||
display: { type: 'string', enum: ['normal', 'banner', 'dialog'] },
|
||||
forExistingUsers: { type: 'boolean' },
|
||||
needConfirmationToRead: { type: 'boolean' },
|
||||
needEnrollmentTutorialToRead: { type: 'boolean' },
|
||||
closeDuration: { type: 'number', default: 0 },
|
||||
displayOrder: { type: 'number', default: 0 },
|
||||
silence: { type: 'boolean' },
|
||||
@ -68,6 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
icon: ps.icon,
|
||||
forExistingUsers: ps.forExistingUsers,
|
||||
needConfirmationToRead: ps.needConfirmationToRead,
|
||||
needEnrollmentTutorialToRead: ps.needEnrollmentTutorialToRead,
|
||||
closeDuration: ps.closeDuration,
|
||||
displayOrder: ps.displayOrder,
|
||||
silence: ps.silence,
|
||||
|
@ -56,7 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const jobs = await this.deliverQueue.getJobs(['delayed']);
|
||||
|
||||
const res = [] as [string, number][];
|
||||
const res = new Map<string, number>();
|
||||
|
||||
for (const job of jobs) {
|
||||
let host: string;
|
||||
@ -68,17 +68,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
continue;
|
||||
}
|
||||
|
||||
const found = res.find(x => x[0] === host);
|
||||
const found = res.get(host);
|
||||
if (found) {
|
||||
found[1]++;
|
||||
res.set(host, found + 1);
|
||||
} else {
|
||||
res.push([host, 1]);
|
||||
res.set(host, 1);
|
||||
}
|
||||
}
|
||||
|
||||
res.sort((a, b) => b[1] - a[1]);
|
||||
|
||||
return res;
|
||||
return Array.from(res.entries()).sort((a, b) => b[1] - a[1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const jobs = await this.inboxQueue.getJobs(['delayed']);
|
||||
|
||||
const res = [] as [string, number][];
|
||||
const res = new Map<string, number>();
|
||||
|
||||
for (const job of jobs) {
|
||||
let host: string;
|
||||
@ -68,17 +68,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
continue;
|
||||
}
|
||||
|
||||
const found = res.find(x => x[0] === host);
|
||||
const found = res.get(host);
|
||||
if (found) {
|
||||
found[1]++;
|
||||
res.set(host, found + 1);
|
||||
} else {
|
||||
res.push([host, 1]);
|
||||
res.set(host, 1);
|
||||
}
|
||||
}
|
||||
|
||||
res.sort((a, b) => b[1] - a[1]);
|
||||
|
||||
return res;
|
||||
return Array.from(res.entries()).sort((a, b) => b[1] - a[1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -64,11 +64,12 @@ export class JWTIdentifyProviderService {
|
||||
|
||||
fastify.all<{
|
||||
Params: { serviceId: string };
|
||||
Querystring?: { serviceurl?: string, return_to?: string };
|
||||
Body?: { serviceurl?: string, return_to?: string };
|
||||
Querystring?: { serviceurl?: string, return_to?: string, prompt?: string };
|
||||
Body?: { serviceurl?: string, return_to?: string, prompt?: string };
|
||||
}>('/:serviceId', async (request, reply) => {
|
||||
const serviceId = request.params.serviceId;
|
||||
const returnTo = request.query?.return_to ?? request.query?.serviceurl ?? request.body?.return_to ?? request.body?.serviceurl;
|
||||
const prompt = request.query?.prompt ?? request.body?.prompt ?? 'consent';
|
||||
|
||||
const ssoServiceProvider = await this.singleSignOnServiceProviderRepository.findOneBy({ id: serviceId, type: 'jwt' });
|
||||
if (!ssoServiceProvider) {
|
||||
@ -101,6 +102,7 @@ export class JWTIdentifyProviderService {
|
||||
transactionId: transactionId,
|
||||
serviceName: ssoServiceProvider.name ?? ssoServiceProvider.issuer,
|
||||
kind: 'jwt',
|
||||
prompt: prompt,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -201,13 +201,14 @@ export class SAMLIdentifyProviderService {
|
||||
|
||||
fastify.all<{
|
||||
Params: { serviceId: string };
|
||||
Querystring?: { SAMLRequest?: string; RelayState?: string };
|
||||
Body?: { SAMLRequest?: string; RelayState?: string };
|
||||
Querystring?: { SAMLRequest?: string; RelayState?: string, prompt?: string };
|
||||
Body?: { SAMLRequest?: string; RelayState?: string, prompt?: string };
|
||||
}>('/:serviceId', async (request, reply) => {
|
||||
const serviceId = request.params.serviceId;
|
||||
const binding = request.query?.SAMLRequest ? 'redirect' : 'post';
|
||||
const samlRequest = request.query?.SAMLRequest ?? request.body?.SAMLRequest;
|
||||
const relayState = request.query?.RelayState ?? request.body?.RelayState;
|
||||
const prompt = request.query?.prompt ?? request.body?.prompt ?? 'consent';
|
||||
|
||||
const ssoServiceProvider = await this.singleSignOnServiceProviderRepository.findOneBy({ id: serviceId, type: 'saml', privateKey: Not(IsNull()) });
|
||||
if (!ssoServiceProvider) {
|
||||
@ -268,6 +269,7 @@ export class SAMLIdentifyProviderService {
|
||||
transactionId: transactionId,
|
||||
serviceName: ssoServiceProvider.name ?? ssoServiceProvider.issuer,
|
||||
kind: 'saml',
|
||||
prompt: prompt,
|
||||
});
|
||||
} catch (err) {
|
||||
this.#logger.error('Failed to parse SAML request', { error: err });
|
||||
|
@ -25,7 +25,16 @@ import { getNoteSummary } from '@/misc/get-note-summary.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/QueueModule.js';
|
||||
import type {
|
||||
DbQueue,
|
||||
DeliverQueue,
|
||||
EndedPollNotificationQueue,
|
||||
InboxQueue,
|
||||
ObjectStorageQueue,
|
||||
RelationshipQueue,
|
||||
SystemQueue,
|
||||
WebhookDeliverQueue,
|
||||
} from '@/core/QueueModule.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { PageEntityService } from '@/core/entities/PageEntityService.js';
|
||||
@ -110,6 +119,7 @@ export class ClientServerService {
|
||||
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
|
||||
@Inject('queue:inbox') public inboxQueue: InboxQueue,
|
||||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
|
||||
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
|
||||
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
|
||||
) {
|
||||
@ -235,12 +245,13 @@ export class ClientServerService {
|
||||
queues: [
|
||||
this.systemQueue,
|
||||
this.endedPollNotificationQueue,
|
||||
this.inboxQueue,
|
||||
this.dbQueue,
|
||||
this.objectStorageQueue,
|
||||
this.webhookDeliverQueue,
|
||||
].map(q => new BullMQAdapter(q))
|
||||
.concat(this.deliverQueue.queues.map((q, index) => new BullMQAdapter(q, { prefix: `${index}-` }))),
|
||||
.concat(this.deliverQueue.queues.map((q, index) => new BullMQAdapter(q, { prefix: `${index}-` })))
|
||||
.concat(this.inboxQueue.queues.map((q, index) => new BullMQAdapter(q, { prefix: `${index}-` })))
|
||||
.concat(this.relationshipQueue.queues.map((q, index) => new BullMQAdapter(q, { prefix: `${index}-` }))),
|
||||
serverAdapter,
|
||||
});
|
||||
|
||||
|
@ -4,3 +4,4 @@ block meta
|
||||
meta(name='misskey:sso:transaction-id' content=transactionId)
|
||||
meta(name='misskey:sso:service-name' content=serviceName)
|
||||
meta(name='misskey:sso:kind' content=kind)
|
||||
meta(name='misskey:sso:prompt' content=prompt)
|
||||
|
@ -11,7 +11,6 @@ import { alert, confirm, popup, post, toast } from '@/os.js';
|
||||
import { useStream } from '@/stream.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import { $i, signout, updateAccount } from '@/account.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { ColdDeviceStorage, defaultStore } from '@/store.js';
|
||||
import { makeHotkey } from '@/scripts/hotkey.js';
|
||||
import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||
@ -237,10 +236,10 @@ export async function mainBoot() {
|
||||
}
|
||||
}
|
||||
|
||||
const modifiedVersionMustProminentlyOfferInAgplV3Section13Read = miLocalStorage.getItem('modifiedVersionMustProminentlyOfferInAgplV3Section13Read');
|
||||
if (modifiedVersionMustProminentlyOfferInAgplV3Section13Read !== 'true' && instance.repositoryUrl !== 'https://github.com/misskey-dev/misskey') {
|
||||
popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {}, 'closed');
|
||||
}
|
||||
// const modifiedVersionMustProminentlyOfferInAgplV3Section13Read = miLocalStorage.getItem('modifiedVersionMustProminentlyOfferInAgplV3Section13Read');
|
||||
// if (modifiedVersionMustProminentlyOfferInAgplV3Section13Read !== 'true' && instance.repositoryUrl !== 'https://github.com/misskey-dev/misskey') {
|
||||
// popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {}, 'closed');
|
||||
// }
|
||||
|
||||
if ('Notification' in window) {
|
||||
// 許可を得ていなかったらリクエスト
|
||||
|
@ -34,8 +34,8 @@ misskeyApi('users/show', { userId: props.movedTo ?? props.movedFrom }).then(u =>
|
||||
.root {
|
||||
padding: 16px;
|
||||
font-size: 90%;
|
||||
background: var(--infoWarnBg);
|
||||
color: var(--error);
|
||||
background: var(--infoBg);
|
||||
color: var(--infoFg);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ export const Default = {
|
||||
imageUrl: null,
|
||||
display: 'dialog',
|
||||
needConfirmationToRead: false,
|
||||
needEnrollmentTutorialToRead: false,
|
||||
silence: false,
|
||||
forYou: true,
|
||||
},
|
||||
|
@ -19,13 +19,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<Mfm :text="announcement.text"/>
|
||||
<img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
|
||||
</div>
|
||||
<MkButton :class="$style.gotIt" primary full :disabled="gotItDisabled" @click="gotIt">{{ i18n.ts.gotIt }}<span v-if="secVisible"> ({{ sec }})</span></MkButton>
|
||||
<MkButton :class="$style.gotIt" primary gradate full :disabled="gotItDisabled" @click="gotIt">
|
||||
{{ !announcement.needEnrollmentTutorialToRead ? i18n.ts.gotIt : i18n.ts._initialAccountSetting.startTutorial }}
|
||||
<span v-if="secVisible"> ({{ sec }})</span>
|
||||
</MkButton>
|
||||
</div>
|
||||
</MkModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, shallowRef } from 'vue';
|
||||
import { defineAsyncComponent, onMounted, ref, shallowRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
@ -46,6 +49,18 @@ const secVisible = ref(true);
|
||||
const sec = ref(props.announcement.closeDuration);
|
||||
|
||||
async function gotIt(): Promise<void> {
|
||||
if (props.announcement.needEnrollmentTutorialToRead) {
|
||||
modal.value?.close();
|
||||
const tutorialCompleted = await (new Promise<boolean>(resolve => {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, {
|
||||
done: () => {
|
||||
resolve(true);
|
||||
},
|
||||
}, 'closed');
|
||||
}));
|
||||
if (!tutorialCompleted) return;
|
||||
}
|
||||
|
||||
if (props.announcement.needConfirmationToRead) {
|
||||
const confirm = await os.confirm({
|
||||
type: 'question',
|
||||
|
@ -6,12 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<button
|
||||
class="_button"
|
||||
:class="[$style.root, { [$style.wait]: wait, [$style.active]: userDetailed.isFollowing || userDetailed.hasPendingFollowRequestFromYou, [$style.full]: full, [$style.large]: large }]"
|
||||
:disabled="wait"
|
||||
:class="[$style.root, { [$style.wait]: wait, [$style.active]: userDetailed.isFollowing || userDetailed.hasPendingFollowRequestFromYou, [$style.full]: full, [$style.large]: large, [$style.activeBlocked]: userDetailed.isBlocking || userDetailed.isBlocked }]"
|
||||
:disabled="wait || userDetailed.isBlocked"
|
||||
@click="onClick"
|
||||
>
|
||||
<template v-if="!wait">
|
||||
<template v-if="userDetailed.hasPendingFollowRequestFromYou && userDetailed.isLocked">
|
||||
<template v-if="userDetailed.isBlocking || userDetailed.isBlocked">
|
||||
<span v-if="full" :class="$style.text">{{ i18n.ts.blocked }}</span><i class="ti ti-ban"></i>
|
||||
</template>
|
||||
<template v-else-if="userDetailed.hasPendingFollowRequestFromYou && userDetailed.isLocked">
|
||||
<span v-if="full" :class="$style.text">{{ i18n.ts.followRequestPending }}</span><i class="ti ti-hourglass-empty"></i>
|
||||
</template>
|
||||
<template v-else-if="userDetailed.hasPendingFollowRequestFromYou && !userDetailed.isLocked">
|
||||
@ -78,29 +81,45 @@ function onFollowChange(user: Misskey.entities.UserDetailed) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getConfirmed(text: string): Promise<boolean> {
|
||||
const confirm = await os.confirm({
|
||||
type: 'warning',
|
||||
title: i18n.ts.areYouSure,
|
||||
text,
|
||||
});
|
||||
|
||||
return !confirm.canceled;
|
||||
}
|
||||
|
||||
async function onClick() {
|
||||
wait.value = true;
|
||||
|
||||
try {
|
||||
if (userDetailed.value.isFollowing) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.tsx.unfollowConfirm({ name: (userDetailed.value.name || userDetailed.value.username) ?? i18n.ts.user }),
|
||||
});
|
||||
|
||||
if (canceled) return;
|
||||
if (!await getConfirmed(i18n.tsx.unfollowConfirm({ name: (userDetailed.value.name || userDetailed.value.username) ?? i18n.ts.user }))) return;
|
||||
|
||||
await misskeyApi('following/delete', {
|
||||
userId: props.user.id,
|
||||
});
|
||||
} else if (userDetailed.value.isBlocking) {
|
||||
if (!await getConfirmed(i18n.ts.unblockConfirm)) return;
|
||||
|
||||
os.apiWithDialog('blocking/delete', {
|
||||
userId: userDetailed.value.id,
|
||||
}).then(() => {
|
||||
misskeyApi('users/show', {
|
||||
userId: props.user.id,
|
||||
})
|
||||
.then(onFollowChange)
|
||||
.then(() => {
|
||||
wait.value = false;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (defaultStore.state.alwaysConfirmFollow) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
text: i18n.tsx.followConfirm({ name: props.user.name || props.user.username }),
|
||||
});
|
||||
const confirmed = await getConfirmed(i18n.tsx.followConfirm({ name: props.user.name || props.user.username }));
|
||||
|
||||
if (canceled) {
|
||||
if (!confirmed) {
|
||||
wait.value = false;
|
||||
return;
|
||||
}
|
||||
@ -214,6 +233,12 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
&.activeBlocked {
|
||||
color: var(--fgOnAccent);
|
||||
border-color: var(--error);
|
||||
background: var(--error);
|
||||
}
|
||||
|
||||
&.wait {
|
||||
cursor: wait !important;
|
||||
opacity: 0.7;
|
||||
|
@ -174,6 +174,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span style="margin-left: 4px;">{{ appearNote.reactions[reaction] }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<MkButton v-if="reactionTabType" :class="$style.reactionMuteButton" @click="reactionMuteToggle(reactionTabTypeTrimLocal)">
|
||||
<i :class="!mutedReactions.includes(reactionTabTypeTrimLocal) ? 'ti ti-mood-happy' : 'ti ti-mood-off'"/>
|
||||
{{ !mutedReactions.includes(reactionTabTypeTrimLocal) ? i18n.ts.muteThisReaction : i18n.ts.unmuteThisReaction }}
|
||||
</MkButton>
|
||||
<MkPagination v-if="reactionTabType" :key="reactionTabType" :pagination="reactionsPagination" :disableAutoLoad="true">
|
||||
<template #default="{ items }">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
|
||||
@ -297,6 +301,7 @@ const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultS
|
||||
const conversation = ref<Misskey.entities.Note[]>([]);
|
||||
const replies = ref<Misskey.entities.Note[]>([]);
|
||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i?.id);
|
||||
const mutedReactions = ref<string[]>(defaultStore.state.mutedReactions);
|
||||
|
||||
const keymap = {
|
||||
'r': () => reply(true),
|
||||
@ -316,6 +321,7 @@ provide('react', (reaction: string) => {
|
||||
|
||||
const tab = ref(props.initialTab);
|
||||
const reactionTabType = ref<string | null>(null);
|
||||
const reactionTabTypeTrimLocal = computed(() => reactionTabType.value?.replace('@.', '') ?? null);
|
||||
|
||||
const renotesPagination = computed<Paging>(() => ({
|
||||
endpoint: 'notes/renotes',
|
||||
@ -497,6 +503,18 @@ async function translate(): Promise<void> {
|
||||
translation.value = res;
|
||||
}
|
||||
|
||||
async function reactionMuteToggle(reactionName: string | null) {
|
||||
if (reactionName == null) return;
|
||||
|
||||
if (!mutedReactions.value.includes(reactionName)) {
|
||||
mutedReactions.value.push(reactionName);
|
||||
defaultStore.set('mutedReactions', mutedReactions.value);
|
||||
} else {
|
||||
mutedReactions.value = mutedReactions.value.filter(x => x !== reactionName);
|
||||
defaultStore.set('mutedReactions', mutedReactions.value);
|
||||
}
|
||||
}
|
||||
|
||||
function showRenoteMenu(viaKeyboard = false): void {
|
||||
if (!isMyRenote) return;
|
||||
pleaseLogin();
|
||||
@ -782,6 +800,10 @@ function loadConversation() {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.reactionMuteButton {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.reactionTab {
|
||||
padding: 4px 6px;
|
||||
border: solid 1px var(--divider);
|
||||
|
@ -22,6 +22,7 @@ import * as Misskey from 'misskey-js';
|
||||
import { inject, watch, ref } from 'vue';
|
||||
import XReaction from '@/components/MkReactionsViewer.reaction.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { $i } from '@/account.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
@ -45,6 +46,13 @@ if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.m
|
||||
reactions.value[props.note.myReaction] = props.note.reactions[props.note.myReaction];
|
||||
}
|
||||
|
||||
function shouldDisplayReaction([reaction]: [string, number]): boolean {
|
||||
if (!$i) return true; // 非ログイン状態なら全部のリアクションを見れるように
|
||||
if (reaction === props.note.myReaction) return true; // 自分がつけたリアクションなら表示する
|
||||
if (!defaultStore.state.mutedReactions.includes(reaction.replace('@.', ''))) return true; // ローカルの絵文字には @. というsuffixがつくのでそれを消してから比較してあげる
|
||||
return false;
|
||||
}
|
||||
|
||||
function onMockToggleReaction(emoji: string, count: number) {
|
||||
if (!mock) return;
|
||||
|
||||
@ -80,7 +88,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe
|
||||
newReactions.push([props.note.myReaction, newSource[props.note.myReaction]]);
|
||||
}
|
||||
|
||||
reactions.value = newReactions;
|
||||
reactions.value = newReactions.filter(shouldDisplayReaction);
|
||||
}, { immediate: true, deep: true });
|
||||
</script>
|
||||
|
||||
@ -104,6 +112,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin: 4px -2px 0 -2px;
|
||||
max-width: 100%;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
|
@ -19,8 +19,8 @@ defineProps<{
|
||||
.root {
|
||||
font-size: 0.8em;
|
||||
padding: 16px;
|
||||
background: var(--infoWarnBg);
|
||||
color: var(--infoWarnFg);
|
||||
background: var(--infoBg);
|
||||
color: var(--infoFg);
|
||||
border-radius: var(--radius);
|
||||
overflow: clip;
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
(ev: 'done'): void;
|
||||
}>();
|
||||
|
||||
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
@ -49,6 +50,10 @@ const page = ref(props.initialPage ?? 0);
|
||||
|
||||
function handlePageChange(to: number) {
|
||||
page.value = to;
|
||||
|
||||
if (page.value === 9) {
|
||||
emit('done');
|
||||
}
|
||||
}
|
||||
|
||||
async function close(skip?: boolean) {
|
||||
|
@ -42,6 +42,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
{{ i18n.ts._announcement.needConfirmationToRead }}
|
||||
<template #caption>{{ i18n.ts._announcement.needConfirmationToReadDescription }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="needEnrollmentTutorialToRead">
|
||||
{{ i18n.ts._announcement.needEnrollmentTutorialToRead }}
|
||||
<template #caption>{{ i18n.ts._announcement.needEnrollmentTutorialToReadDescription }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="closeDuration" type="number">
|
||||
<template #label>{{ i18n.ts.dialogCloseDuration }}</template>
|
||||
<template #suffix>{{ i18n.ts._time.second }}</template>
|
||||
@ -90,6 +94,7 @@ const text = ref<string>(props.announcement ? props.announcement.text : '');
|
||||
const icon = ref<string>(props.announcement ? props.announcement.icon : 'info');
|
||||
const display = ref<string>(props.announcement ? props.announcement.display : 'dialog');
|
||||
const needConfirmationToRead = ref(props.announcement ? props.announcement.needConfirmationToRead : false);
|
||||
const needEnrollmentTutorialToRead = ref(props.announcement ? props.announcement.needEnrollmentTutorialToRead : false);
|
||||
const closeDuration = ref<number>(props.announcement ? props.announcement.closeDuration : 0);
|
||||
const displayOrder = ref<number>(props.announcement ? props.announcement.displayOrder : 0);
|
||||
const silence = ref<boolean>(props.announcement ? props.announcement.silence : false);
|
||||
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<span v-if="errored">:{{ customEmojiName }}:</span>
|
||||
<img v-if="errored" src="/client-assets/dummy.png" :alt="alt" :title="alt" decoding="async" :class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"/>
|
||||
<img
|
||||
v-else
|
||||
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
|
||||
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { customEmojisMap } from '@/custom-emojis.js';
|
||||
@ -73,6 +73,10 @@ const url = computed(() => {
|
||||
: proxied;
|
||||
});
|
||||
|
||||
watch(url, (newValue) => {
|
||||
errored.value = (newValue === undefined);
|
||||
});
|
||||
|
||||
const alt = computed(() => `:${customEmojiName.value}:`);
|
||||
const errored = ref(url.value == null);
|
||||
|
||||
|
@ -66,6 +66,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
|
||||
{{ i18n.ts._announcement.needConfirmationToRead }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="announcement.needEnrollmentTutorialToRead" :helpText="i18n.ts._announcement.needEnrollmentTutorialToReadDescription">
|
||||
{{ i18n.ts._announcement.needEnrollmentTutorialToRead }}
|
||||
</MkSwitch>
|
||||
<MkInput v-model="announcement.closeDuration" type="number">
|
||||
<template #label>{{ i18n.ts.dialogCloseDuration }}</template>
|
||||
<template #suffix>{{ i18n.ts._time.second }}</template>
|
||||
|
@ -34,7 +34,10 @@
|
||||
</MkA>
|
||||
</div>
|
||||
<div v-if="$i && !announcement.silence && !announcement.isRead" :class="$style.footer">
|
||||
<MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton>
|
||||
<MkButton primary gradate @click="read(announcement)">
|
||||
<i :class="!announcement.needEnrollmentTutorialToRead ? 'ti ti-check' : 'ti ti-presentation'"/>
|
||||
{{ !announcement.needEnrollmentTutorialToRead ? i18n.ts.gotIt : i18n.ts._initialAccountSetting.startTutorial }}
|
||||
</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<MkError v-else-if="error" @retry="fetch()"/>
|
||||
@ -45,7 +48,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch, defineAsyncComponent } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
@ -75,6 +78,17 @@ function fetch() {
|
||||
}
|
||||
|
||||
async function read(announcement): Promise<void> {
|
||||
if (announcement.needEnrollmentTutorialToRead) {
|
||||
const tutorialCompleted = await (new Promise<boolean>(resolve => {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, {
|
||||
done: () => {
|
||||
resolve(true);
|
||||
},
|
||||
}, 'closed');
|
||||
}));
|
||||
if (!tutorialCompleted) return;
|
||||
}
|
||||
|
||||
if (announcement.needConfirmationToRead) {
|
||||
const confirm = await os.confirm({
|
||||
type: 'question',
|
||||
|
@ -36,7 +36,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkA>
|
||||
</div>
|
||||
<div v-if="$i && !announcement.silence && !announcement.isRead" :class="$style.footer">
|
||||
<MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton>
|
||||
<MkButton primary gradate @click="read(announcement)">
|
||||
<i :class="!announcement.needEnrollmentTutorialToRead ? 'ti ti-check' : 'ti ti-presentation'"/>
|
||||
{{ !announcement.needEnrollmentTutorialToRead ? i18n.ts.gotIt : i18n.ts._initialAccountSetting.startTutorial }}
|
||||
</MkButton>
|
||||
</div>
|
||||
</section>
|
||||
</MkPagination>
|
||||
@ -47,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch, defineAsyncComponent } from 'vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
@ -81,6 +84,17 @@ const paginationEl = ref<InstanceType<typeof MkPagination>>();
|
||||
const tab = ref('current');
|
||||
|
||||
async function read(announcement): Promise<void> {
|
||||
if (announcement.needEnrollmentTutorialToRead) {
|
||||
const tutorialCompleted = await (new Promise<boolean>(resolve => {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, {
|
||||
done: () => {
|
||||
resolve(true);
|
||||
},
|
||||
}, 'closed');
|
||||
}));
|
||||
if (!tutorialCompleted) return;
|
||||
}
|
||||
|
||||
if (announcement.needConfirmationToRead) {
|
||||
const confirm = await os.confirm({
|
||||
type: 'question',
|
||||
|
@ -12,6 +12,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-message-off"></i></template>
|
||||
<template #label>{{ i18n.ts.mutedReactions }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<div v-panel style="border-radius: var(--radius); padding: var(--margin);">
|
||||
<button v-for="emoji in mutedReactions" class="_button" :class="$style.emojisItem" @click="removeReaction(emoji, $event)">
|
||||
<MkCustomEmoji v-if="emoji && emoji[0] === ':'" :name="emoji"/>
|
||||
<MkEmoji v-else :emoji="emoji ? emoji : 'null'"/>
|
||||
</button>
|
||||
<button class="_button" @click="chooseReaction">
|
||||
<i class="ti ti-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-planet-off"></i></template>
|
||||
<template #label>{{ i18n.ts.instanceMute }}</template>
|
||||
@ -119,7 +136,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, watch, Ref } from 'vue';
|
||||
import XInstanceMute from './mute-block.instance-mute.vue';
|
||||
import XWordMute from './mute-block.word-mute.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
@ -132,6 +149,9 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
import { signinRequired } from '@/account.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
|
||||
import MkEmoji from '@/components/global/MkEmoji.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
@ -154,6 +174,37 @@ const expandedRenoteMuteItems = ref([]);
|
||||
const expandedMuteItems = ref([]);
|
||||
const expandedBlockItems = ref([]);
|
||||
|
||||
const mutedReactions = ref<string[]>(defaultStore.state.mutedReactions);
|
||||
|
||||
watch(mutedReactions, () => {
|
||||
defaultStore.set('mutedReactions', mutedReactions.value);
|
||||
}, {
|
||||
deep: true,
|
||||
});
|
||||
|
||||
const chooseReaction = (ev: MouseEvent) => pickEmoji(mutedReactions, ev);
|
||||
const removeReaction = (reaction: string, ev: MouseEvent) => remove(mutedReactions, reaction, ev);
|
||||
|
||||
function remove(itemsRef: Ref<string[]>, reaction: string, ev: MouseEvent) {
|
||||
os.popupMenu([{
|
||||
text: i18n.ts.remove,
|
||||
action: () => {
|
||||
itemsRef.value = itemsRef.value.filter(x => x !== reaction);
|
||||
},
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
async function pickEmoji(itemsRef: Ref<string[]>, ev: MouseEvent) {
|
||||
os.pickEmoji(ev.currentTarget ?? ev.target, {
|
||||
showPinned: false,
|
||||
}).then(it => {
|
||||
const emoji = it;
|
||||
if (!itemsRef.value.includes(emoji)) {
|
||||
itemsRef.value.push(emoji);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function unrenoteMute(user, ev) {
|
||||
os.popupMenu([{
|
||||
text: i18n.ts.renoteUnmute,
|
||||
@ -263,4 +314,9 @@ definePageMetadata(() => ({
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.emojisItem{
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
|
@ -120,6 +120,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
|
||||
'sound_notification',
|
||||
'sound_antenna',
|
||||
'sound_channel',
|
||||
'mutedReactions',
|
||||
];
|
||||
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
||||
'lightTheme',
|
||||
|
@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, nextTick } from 'vue';
|
||||
import { ref, nextTick, onMounted } from 'vue';
|
||||
import MkSignin from '@/components/MkSignin.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { $i, login } from '@/account.js';
|
||||
@ -47,6 +47,7 @@ if (transactionIdMeta) {
|
||||
}
|
||||
const name = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:service-name"]')?.content;
|
||||
const kind = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:kind"]')?.content;
|
||||
const prompt = document.querySelector<HTMLMetaElement>('meta[name="misskey:sso:prompt"]')?.content;
|
||||
|
||||
const loading = ref(false);
|
||||
const postBindingForm = ref<HTMLFormElement | null>(null);
|
||||
@ -90,6 +91,12 @@ async function authorize(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if ($i && prompt === 'none') {
|
||||
onAccept();
|
||||
}
|
||||
});
|
||||
|
||||
definePageMetadata(() => ({
|
||||
title: 'Single Sign-On',
|
||||
icon: 'ti ti-apps',
|
||||
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<div class="profile _gaps">
|
||||
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
|
||||
<MkAccountMoved v-if="movedFromLog" :movedFrom="movedFromLog[0]?.movedFromId"/>
|
||||
<MkAccountMoved v-if="movedFromLog && iAmModerator" :movedFrom="movedFromLog[0]?.movedFromId"/>
|
||||
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
|
||||
|
||||
<div :key="user.id" class="main _panel">
|
||||
@ -24,7 +24,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkUserName class="name" :user="user" :nowrap="true"/>
|
||||
<div class="bottom">
|
||||
<span class="username"><MkAcct :user="user" :detail="true"/></span>
|
||||
<span v-if="user.isAdmin" v-tooltip.noDelay="i18n.ts.administrator" style="color: var(--badge);"><i class="ti ti-shield"></i></span>
|
||||
<span v-if="user.isLocked" v-tooltip.noDelay="i18n.ts.isLocked"><i class="ti ti-lock"></i></span>
|
||||
<span v-if="user.isBot" v-tooltip.noDelay="i18n.ts.isBot"><i class="ti ti-robot"></i></span>
|
||||
<button v-if="$i && !isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea">
|
||||
@ -32,10 +31,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
|
||||
<span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
|
||||
<span v-if="$i && $i.id !== user.id && user.isBlocking" class="followed">{{ i18n.ts.youAreBlocking }}</span>
|
||||
<span v-if="$i && $i.id !== user.id && user.isBlocked" class="followed">{{ i18n.ts.youAreBlocked }}</span>
|
||||
<div v-if="$i" class="actions">
|
||||
<button class="menu _button" @click="menu"><i class="ti ti-dots"></i></button>
|
||||
<MkFollowButton v-if="$i.id != user.id" v-model:user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
|
||||
<MkFollowButton v-if="$i && $i.id !== user.id" v-model:user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkAvatar class="avatar" :user="user" indicator/>
|
||||
@ -43,7 +44,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkUserName :user="user" :nowrap="false" class="name"/>
|
||||
<div class="bottom">
|
||||
<span class="username"><MkAcct :user="user" :detail="true"/></span>
|
||||
<span v-if="user.isAdmin" :title="i18n.ts.administrator" style="color: var(--badge);"><i class="ti ti-shield"></i></span>
|
||||
<span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ti ti-lock"></i></span>
|
||||
<span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span>
|
||||
</div>
|
||||
@ -186,7 +186,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="contents _gaps">
|
||||
<div v-if="user.isBlocked !== true" class="contents _gaps">
|
||||
<div v-if="user.pinnedNotes.length > 0" class="_gaps">
|
||||
<MkNote v-for="note in user.pinnedNotes" :key="note.id" class="note _panel" :note="note" :pinned="true"/>
|
||||
</div>
|
||||
@ -438,8 +438,8 @@ onUnmounted(() => {
|
||||
> .punished {
|
||||
font-size: 0.8em;
|
||||
padding: 16px;
|
||||
background: var(--infoWarnBg);
|
||||
color: var(--infoWarnFg);
|
||||
background: var(--infoBg);
|
||||
color: var(--infoFg);
|
||||
border-radius: var(--radius);
|
||||
overflow: clip;
|
||||
}
|
||||
|
@ -21,15 +21,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<XPages v-else-if="tab === 'pages'" key="pages" :user="user"/>
|
||||
<XFlashs v-else-if="tab === 'flashs'" key="flashs" :user="user"/>
|
||||
<XGallery v-else-if="tab === 'gallery'" key="gallery" :user="user"/>
|
||||
<XRaw v-else-if="tab === 'raw'" key="raw" :user="user"/>
|
||||
</MkHorizontalSwipe>
|
||||
</div>
|
||||
<div v-else-if="error">
|
||||
<MkError @retry="fetchUser()"/>
|
||||
</div>
|
||||
<div v-else-if="userstatus">
|
||||
<MkUserNotFound v-if="userstatus === 'notfound'"/>
|
||||
<MkUserSuspended v-else-if="userstatus === 'suspended'"/>
|
||||
<MkUserNotFound v-if="userstatus === 'notfound' || userstatus === 'suspended'"/>
|
||||
</div>
|
||||
<MkLoading v-else/>
|
||||
</div>
|
||||
@ -45,7 +43,6 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i, iAmModerator } from '@/account.js';
|
||||
import MkUserNotFound from '@/components/MkUserNotFound.vue';
|
||||
import MkUserSuspended from '@/components/MkUserSuspended.vue';
|
||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||
|
||||
const XHome = defineAsyncComponent(() => import('./home.vue'));
|
||||
@ -58,7 +55,6 @@ const XLists = defineAsyncComponent(() => import('./lists.vue'));
|
||||
const XPages = defineAsyncComponent(() => import('./pages.vue'));
|
||||
const XFlashs = defineAsyncComponent(() => import('./flashs.vue'));
|
||||
const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
|
||||
const XRaw = defineAsyncComponent(() => import('./raw.vue'));
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
acct: string;
|
||||
@ -135,10 +131,6 @@ const headerTabs = computed(() => user.value ? [{
|
||||
key: 'gallery',
|
||||
title: i18n.ts.gallery,
|
||||
icon: 'ti ti-icons',
|
||||
}, {
|
||||
key: 'raw',
|
||||
title: 'Raw',
|
||||
icon: 'ti ti-code',
|
||||
}] : []);
|
||||
|
||||
definePageMetadata(() => ({
|
||||
|
@ -108,7 +108,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
|
||||
async function getConfirmed(text: string): Promise<boolean> {
|
||||
const confirm = await os.confirm({
|
||||
type: 'warning',
|
||||
title: 'confirm',
|
||||
title: i18n.ts.areYouSure,
|
||||
text,
|
||||
});
|
||||
|
||||
|
@ -543,6 +543,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||
where: 'device',
|
||||
default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
|
||||
},
|
||||
mutedReactions: {
|
||||
where: 'account',
|
||||
default: [] as string[],
|
||||
},
|
||||
}));
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
|
@ -4204,6 +4204,7 @@ export type components = {
|
||||
/** @enum {string} */
|
||||
display: 'dialog' | 'normal' | 'banner';
|
||||
needConfirmationToRead: boolean;
|
||||
needEnrollmentTutorialToRead: boolean;
|
||||
forYou: boolean;
|
||||
closeDuration: number;
|
||||
displayOrder: number;
|
||||
@ -6086,6 +6087,8 @@ export type operations = {
|
||||
forExistingUsers?: boolean;
|
||||
/** @default false */
|
||||
needConfirmationToRead?: boolean;
|
||||
/** @default false */
|
||||
needEnrollmentTutorialToRead?: boolean;
|
||||
/** @default 0 */
|
||||
closeDuration?: number;
|
||||
/** @default 0 */
|
||||
@ -6121,6 +6124,7 @@ export type operations = {
|
||||
display: string;
|
||||
forYou: boolean;
|
||||
needConfirmationToRead: boolean;
|
||||
needEnrollmentTutorialToRead: boolean;
|
||||
closeDuration: number;
|
||||
displayOrder: number;
|
||||
silence: boolean;
|
||||
@ -6253,6 +6257,7 @@ export type operations = {
|
||||
display: string;
|
||||
forExistingUsers: boolean;
|
||||
needConfirmationToRead: boolean;
|
||||
needEnrollmentTutorialToRead: boolean;
|
||||
closeDuration: number;
|
||||
displayOrder: number;
|
||||
silence: boolean;
|
||||
@ -6317,6 +6322,7 @@ export type operations = {
|
||||
display?: 'normal' | 'banner' | 'dialog';
|
||||
forExistingUsers?: boolean;
|
||||
needConfirmationToRead?: boolean;
|
||||
needEnrollmentTutorialToRead?: boolean;
|
||||
/** @default 0 */
|
||||
closeDuration?: number;
|
||||
/** @default 0 */
|
||||
|
Loading…
Reference in New Issue
Block a user