Merge branch 'develop' of https://codeberg.org/calckey/calckey into upstream

This commit is contained in:
freeplay 2023-07-17 12:16:19 -04:00
commit 23b7c3c1b0
152 changed files with 6865 additions and 4764 deletions

1
.gitignore vendored
View File

@ -46,6 +46,7 @@ files
ormconfig.json ormconfig.json
packages/backend/assets/instance.css packages/backend/assets/instance.css
packages/backend/assets/sounds/None.mp3 packages/backend/assets/sounds/None.mp3
packages/backend/assets/LICENSE
!packages/backend/src/db !packages/backend/src/db

View File

@ -6,23 +6,16 @@
## Planned ## Planned
- Stucture - Stucture
- [DragonflyDB](https://dragonflydb.io/) support as a Redis alternative
- Optionally use [ScyllaDB](https://www.scylladb.com/open-source-nosql-database/) for storing notes
- Rewrite backend in Rust and [Rocket](https://rocket.rs/) - Rewrite backend in Rust and [Rocket](https://rocket.rs/)
- Use [Magic RegExP](https://regexp.dev/) for RegEx 🦄
- Function - Function
- User "choices" (recommended users) and featured hashtags like Mastodon and Soapbox - User "choices" (recommended users) and featured hashtags like Mastodon and Soapbox
- Join Reason system like Mastodon/Pleroma - Join Reason system like Mastodon/Pleroma
- Option to publicize server blocks - Option to publicize server blocks
- More antenna options - More antenna options
- Groups - Groups
- Form
- Lookup/details for post/file/server
- [Rat mode?](https://stop.voring.me/notes/933fx97bmd)
## Work in progress ## Work in progress
- Link verification
- Better Messaging UI - Better Messaging UI
- Better API Documentation - Better API Documentation
- Remote follow button - Remote follow button
@ -30,6 +23,7 @@
- Timeline filters - Timeline filters
- Events - Events
- Fully revamp non-logged-in screen - Fully revamp non-logged-in screen
- Optionally use [ScyllaDB](https://www.scylladb.com/open-source-nosql-database/) for storing notes
## Implemented ## Implemented
@ -122,6 +116,8 @@
- Let moderators see moderation nodes - Let moderators see moderation nodes
- Non-mangled unicode emojis - Non-mangled unicode emojis
- Skin tone selection support - Skin tone selection support
- [DragonflyDB](https://dragonflydb.io/) support as a Redis alternative
- Link verification
## Implemented (remote) ## Implemented (remote)

View File

@ -1179,7 +1179,6 @@ _profile:
youCanIncludeHashtags: "يمكنك أيضًا إضافة وسوم إلى سيرتك التعريفية." youCanIncludeHashtags: "يمكنك أيضًا إضافة وسوم إلى سيرتك التعريفية."
metadata: "معلومات إضافية" metadata: "معلومات إضافية"
metadataEdit: "عدّل المعلومات الإضافية" metadataEdit: "عدّل المعلومات الإضافية"
metadataDescription: "يُمكنك عرض 4 حقول معلومات في ملفك الشخصي"
metadataLabel: "التسمية" metadataLabel: "التسمية"
metadataContent: "المحتوى" metadataContent: "المحتوى"
changeAvatar: "غيّر الصورة الرمزية" changeAvatar: "غيّر الصورة الرمزية"

View File

@ -1268,7 +1268,7 @@ _profile:
youCanIncludeHashtags: "হ্যাশট্যাগ অন্তর্ভুক্ত করা যেতে পারে।" youCanIncludeHashtags: "হ্যাশট্যাগ অন্তর্ভুক্ত করা যেতে পারে।"
metadata: "অতিরিক্ত তথ্য" metadata: "অতিরিক্ত তথ্য"
metadataEdit: "অতিরিক্ত তথ্য সম্পাদনা করুন" metadataEdit: "অতিরিক্ত তথ্য সম্পাদনা করুন"
metadataDescription: "আপনি আপনার প্রোফাইলে একটি টেবিল হিসাবে চারটি অতিরিক্ত তথ্য দেখাতে পারেন।" metadataDescription: "আপনি আপনার প্রোফাইলে একটি টেবিল হিসাবে চারটি অতিরিক্ত তথ্য দেখাতে পারেন।. আপনি আপনার প্রোফাইলে লিঙ্কটি যাচাই করতে {rel} এর সাথে একটি {a} ট্যাগ বা {l} ট্যাগ যোগ করতে পারেন!"
metadataLabel: "লেবেল" metadataLabel: "লেবেল"
metadataContent: "বিষয়বস্তু" metadataContent: "বিষয়বস্তু"
changeAvatar: "অ্যাভাটার পরিবর্তন করুন" changeAvatar: "অ্যাভাটার পরিবর্তন করুন"

View File

@ -409,8 +409,9 @@ _profile:
locationDescription: Si primer introduïu la vostra ciutat, es mostrarà l'hora local locationDescription: Si primer introduïu la vostra ciutat, es mostrarà l'hora local
a altres usuaris. a altres usuaris.
name: Nom name: Nom
metadataDescription: Fent servir això, podràs mostrar camps d'informació addicionals metadataDescription: "Fent servir això, podràs mostrar camps d'informació addicionals
al vostre perfil. al vostre perfil. Podeu afegir una etiqueta {a} o una etiqueta {l} amb {rel} per
verificar l'enllaç al vostre perfil."
_exportOrImport: _exportOrImport:
followingList: "Usuaris que segueixes" followingList: "Usuaris que segueixes"
muteList: "Silencia" muteList: "Silencia"
@ -2161,3 +2162,4 @@ remindMeLater: Potser després
removeMember: Elimina el membre removeMember: Elimina el membre
removeQuote: Elimina la cita removeQuote: Elimina la cita
removeRecipient: Elimina el destinatari removeRecipient: Elimina el destinatari
verifiedLink: Enllaç verificat

View File

@ -1551,7 +1551,7 @@ _profile:
metadata: "Zusätzliche Informationen" metadata: "Zusätzliche Informationen"
metadataEdit: "Zusätzliche Informationen bearbeiten" metadataEdit: "Zusätzliche Informationen bearbeiten"
metadataDescription: "Hierdurch kannst du auf deinem Profil zusätzliche Informationsblöcke metadataDescription: "Hierdurch kannst du auf deinem Profil zusätzliche Informationsblöcke
anzeigen lassen." anzeigen lassen. Sie können ein {a}-Tag oder ein {l}-Tag mit {rel} hinzufügen, um den Link in Ihrem Profil zu überprüfen!"
metadataLabel: "Beschriftung" metadataLabel: "Beschriftung"
metadataContent: "Inhalt" metadataContent: "Inhalt"
changeAvatar: "Profilbild ändern" changeAvatar: "Profilbild ändern"

View File

@ -1124,6 +1124,7 @@ remindMeLater: "Maybe later"
removeQuote: "Remove quote" removeQuote: "Remove quote"
removeRecipient: "Remove recipient" removeRecipient: "Remove recipient"
removeMember: "Remove member" removeMember: "Remove member"
verifiedLink: "Verified link"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing description: "Reduces the effort of server moderation through automatically recognizing
@ -1676,8 +1677,10 @@ _profile:
youCanIncludeHashtags: "You can also include hashtags in your bio." youCanIncludeHashtags: "You can also include hashtags in your bio."
metadata: "Additional Information" metadata: "Additional Information"
metadataEdit: "Edit additional Information" metadataEdit: "Edit additional Information"
metadataDescription: "Using these, you can display additional information fields metadataDescription:
in your profile." "Using these, you can display additional information fields
in your profile. You can add an {a} tag or {l} tag with {rel}
to verify the link on your profile!"
metadataLabel: "Label" metadataLabel: "Label"
metadataContent: "Content" metadataContent: "Content"
changeAvatar: "Change avatar" changeAvatar: "Change avatar"

View File

@ -1475,7 +1475,7 @@ _profile:
youCanIncludeHashtags: "Puedes añadir hashtags" youCanIncludeHashtags: "Puedes añadir hashtags"
metadata: "información adicional" metadata: "información adicional"
metadataEdit: "Editar información adicional" metadataEdit: "Editar información adicional"
metadataDescription: "Muestra la información adicional en el perfil" metadataDescription: "Muestra la información adicional en el perfil. ¡Puede agregar una etiqueta {a} o una etiqueta {l} con {rel} para verificar el enlace en su perfil!"
metadataLabel: "Etiqueta" metadataLabel: "Etiqueta"
metadataContent: "Contenido" metadataContent: "Contenido"
changeAvatar: "Cambiar avatar" changeAvatar: "Cambiar avatar"

View File

@ -1413,7 +1413,7 @@ _profile:
metadata: "Informations supplémentaires" metadata: "Informations supplémentaires"
metadataEdit: "Éditer les informations supplémentaires" metadataEdit: "Éditer les informations supplémentaires"
metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires
dans votre profil." dans votre profil. Vous pouvez ajouter une balise {a} ou une balise {l} avec {rel} pour vérifier le lien sur votre profil!"
metadataLabel: "Étiquette" metadataLabel: "Étiquette"
metadataContent: "Contenu" metadataContent: "Contenu"
changeAvatar: "Changer l'image de profil" changeAvatar: "Changer l'image de profil"

View File

@ -1399,7 +1399,7 @@ _profile:
metadata: "Informasi tambahan" metadata: "Informasi tambahan"
metadataEdit: "Sunting informasi tambahan" metadataEdit: "Sunting informasi tambahan"
metadataDescription: "Kamu dapat menampilkan hingga 4 bagian informasi tambahan\ metadataDescription: "Kamu dapat menampilkan hingga 4 bagian informasi tambahan\
\ ke dalam profilmu." \ ke dalam profilmu. Anda dapat menambahkan tag {a} atau tag {l} dengan {rel} untuk memverifikasi tautan di profil Anda!"
metadataLabel: "Label" metadataLabel: "Label"
metadataContent: "Isi" metadataContent: "Isi"
changeAvatar: "Ubah avatar" changeAvatar: "Ubah avatar"

View File

@ -1266,7 +1266,7 @@ _profile:
metadata: "Informazioni aggiuntive" metadata: "Informazioni aggiuntive"
metadataEdit: "Modifica informazioni aggiuntive" metadataEdit: "Modifica informazioni aggiuntive"
metadataDescription: "Puoi pubblicare fino a quattro informazioni aggiuntive sul metadataDescription: "Puoi pubblicare fino a quattro informazioni aggiuntive sul
profilo." profilo. Puoi aggiungere un tag {a} o {l} con {rel} per verificare il link sul tuo profilo!"
metadataLabel: "Etichetta" metadataLabel: "Etichetta"
metadataContent: "Contenuto" metadataContent: "Contenuto"
changeAvatar: "Modifica immagine profilo" changeAvatar: "Modifica immagine profilo"

View File

@ -1491,7 +1491,7 @@ _profile:
youCanIncludeHashtags: "ハッシュタグを含められます。" youCanIncludeHashtags: "ハッシュタグを含められます。"
metadata: "追加情報" metadata: "追加情報"
metadataEdit: "追加情報を編集" metadataEdit: "追加情報を編集"
metadataDescription: "プロフィールに表として追加情報を表示できます。" metadataDescription: "プロフィールに表として追加情報を表示できます。{a}タグまたは{l}タグを{rel}とともに追加すると、プロフィールのリンクを確認できます。"
metadataLabel: "ラベル" metadataLabel: "ラベル"
metadataContent: "内容" metadataContent: "内容"
changeAvatar: "アバター画像を変更" changeAvatar: "アバター画像を変更"

View File

@ -1319,7 +1319,7 @@ _profile:
youCanIncludeHashtags: "해시 태그를 포함할 수 있습니다." youCanIncludeHashtags: "해시 태그를 포함할 수 있습니다."
metadata: "추가 정보" metadata: "추가 정보"
metadataEdit: "추가 정보 편집" metadataEdit: "추가 정보 편집"
metadataDescription: "프로필에 추가 정보를 표시할 수 있어요" metadataDescription: "프로필에 추가 정보를 표시할 수 있어요. {rel}과 함께 {a} 태그 또는 {l} 태그를 추가하여 프로필의 링크를 확인할 수 있습니다!"
metadataLabel: "라벨" metadataLabel: "라벨"
metadataContent: "내용" metadataContent: "내용"
changeAvatar: "아바타 이미지 변경" changeAvatar: "아바타 이미지 변경"

View File

@ -1404,7 +1404,7 @@ _profile:
metadata: "Dodatkowe informacje" metadata: "Dodatkowe informacje"
metadataEdit: "Edytuj dodatkowe informacje" metadataEdit: "Edytuj dodatkowe informacje"
metadataDescription: "Możesz wyświetlać do czterech sekcji dodatkowych informacji metadataDescription: "Możesz wyświetlać do czterech sekcji dodatkowych informacji
na swoim profilu." na swoim profilu. Możesz dodać tag {a} lub tag {l} z {rel}, aby zweryfikować link w swoim profilu!"
metadataLabel: "Etykieta" metadataLabel: "Etykieta"
metadataContent: "Treść" metadataContent: "Treść"
changeAvatar: "Zmień awatar" changeAvatar: "Zmień awatar"

View File

@ -1398,7 +1398,7 @@ _profile:
youCanIncludeHashtags: "Можете использовать здесь хэштеги." youCanIncludeHashtags: "Можете использовать здесь хэштеги."
metadata: "Дополнительные сведения" metadata: "Дополнительные сведения"
metadataEdit: "Редактировать дополнительные сведения" metadataEdit: "Редактировать дополнительные сведения"
metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль." metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль. Вы можете добавить тег {a} или тег {l} с {rel}, чтобы подтвердить ссылку в своем профиле!"
metadataLabel: "Метка" metadataLabel: "Метка"
metadataContent: "Содержимое" metadataContent: "Содержимое"
changeAvatar: "Поменять аватар" changeAvatar: "Поменять аватар"

View File

@ -1337,7 +1337,7 @@ _profile:
youCanIncludeHashtags: "Vo svojom bio môžete mať aj hashtagy." youCanIncludeHashtags: "Vo svojom bio môžete mať aj hashtagy."
metadata: "Dodatočné informácie" metadata: "Dodatočné informácie"
metadataEdit: "Upraviť dodatočné informácie" metadataEdit: "Upraviť dodatočné informácie"
metadataDescription: "Vo svojom profile môžete uviesť až štyri dodatočné informačné polia." metadataDescription: "Vo svojom profile môžete uviesť až štyri dodatočné informačné polia. Dodate lahko oznako {a} ali oznako {l} z {rel}, da preverite povezavo v svojem profile!"
metadataLabel: "Popisok" metadataLabel: "Popisok"
metadataContent: "Obsah" metadataContent: "Obsah"
changeAvatar: "Zmeniť avatara" changeAvatar: "Zmeniť avatara"

View File

@ -182,7 +182,7 @@ _profile:
gösterecektir. gösterecektir.
youCanIncludeHashtags: Hakkımdan'da etiket kullanabilirsin. youCanIncludeHashtags: Hakkımdan'da etiket kullanabilirsin.
description: Hakkımda description: Hakkımda
metadataDescription: Bunları kullanarak profilinizde ek bilgi alanları görüntüleyebilirsiniz. metadataDescription: 'Bunları kullanarak profilinizde ek bilgi alanları görüntüleyebilirsiniz. Profilinizdeki bağlantıyı doğrulamak için {rel} ile bir {a} etiketi veya {l} etiketi ekleyebilirsiniz!'
metadata: Ek Bilgi metadata: Ek Bilgi
metadataContent: İçerik metadataContent: İçerik
metadataLabel: Etiket metadataLabel: Etiket

View File

@ -155,7 +155,7 @@ flagAsBotDescription: "Ввімкніть якщо цей обліковий з
flagAsCat: "Акаунт кота" flagAsCat: "Акаунт кота"
flagAsCatDescription: "Ввімкніть, щоб позначити, що обліковий запис є котиком, та flagAsCatDescription: "Ввімкніть, щоб позначити, що обліковий запис є котиком, та
отримати котячі вуха!" отримати котячі вуха!"
flagShowTimelineReplies: "Показувати відповіді на нотатки на часовій шкалі" flagShowTimelineReplies: "Показувати відповіді на записи в стрічці"
flagShowTimelineRepliesDescription: "Показує відповіді користувачів на записи інших flagShowTimelineRepliesDescription: "Показує відповіді користувачів на записи інших
користувачів у стрічці." користувачів у стрічці."
autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на
@ -1250,7 +1250,7 @@ _poll:
_visibility: _visibility:
public: "Публічний" public: "Публічний"
publicDescription: "Ваш запис буде видно в усіх публічних стрічках" publicDescription: "Ваш запис буде видно в усіх публічних стрічках"
home: "Скритий" home: "Домашній"
homeDescription: "Лише на домашній стрічці" homeDescription: "Лише на домашній стрічці"
followers: "Підписники" followers: "Підписники"
followersDescription: "Зробити видимим тільки для ваших підписників і згаданих користувачів" followersDescription: "Зробити видимим тільки для ваших підписників і згаданих користувачів"
@ -1277,7 +1277,8 @@ _profile:
metadata: "Додаткова інформація" metadata: "Додаткова інформація"
metadataEdit: "Редагувати додаткову інформацію" metadataEdit: "Редагувати додаткову інформацію"
metadataDescription: "Ви можете вказати до чотирьох пунктів додаткової інформації metadataDescription: "Ви можете вказати до чотирьох пунктів додаткової інформації
у своєму профілі." у своєму профілі. Ви можете додати тег {a} або {l} за допомогою {rel}, щоб підтвердити
посилання у своєму профілі!"
metadataLabel: "Назва" metadataLabel: "Назва"
metadataContent: "Вміст" metadataContent: "Вміст"
changeAvatar: "Змінити аватар" changeAvatar: "Змінити аватар"
@ -2131,3 +2132,4 @@ customSplashIconsDescription: URL-адреси іконок для застав
які будуть показуватися випадковим чином щоразу, коли користувач завантажує/перезавантажує які будуть показуватися випадковим чином щоразу, коли користувач завантажує/перезавантажує
сторінку. Будь ласка, переконайтеся, що зображення знаходяться на статичній URL-адресі, сторінку. Будь ласка, переконайтеся, що зображення знаходяться на статичній URL-адресі,
бажано, щоб вони були змінені до розміру 192x192. бажано, щоб вони були змінені до розміру 192x192.
verifiedLink: Перевірене посилання

View File

@ -1342,7 +1342,7 @@ _profile:
youCanIncludeHashtags: "Bạn có thể dùng hashtag trong tiểu sử." youCanIncludeHashtags: "Bạn có thể dùng hashtag trong tiểu sử."
metadata: "Thông tin bổ sung" metadata: "Thông tin bổ sung"
metadataEdit: "Sửa thông tin bổ sung" metadataEdit: "Sửa thông tin bổ sung"
metadataDescription: "Sử dụng phần này, bạn có thể hiển thị các mục thông tin bổ sung trong hồ sơ của mình." metadataDescription: "Sử dụng phần này, bạn có thể hiển thị các mục thông tin bổ sung trong hồ sơ của mình. Bạn có thể thêm thẻ {a} hoặc thẻ {l} với {rel} để xác minh liên kết trên tiểu sử của mình!"
metadataLabel: "Nhãn" metadataLabel: "Nhãn"
metadataContent: "Nội dung" metadataContent: "Nội dung"
changeAvatar: "Đổi ảnh đại diện" changeAvatar: "Đổi ảnh đại diện"

View File

@ -1402,7 +1402,7 @@ _profile:
youCanIncludeHashtags: "您可以包含一个话题标签。" youCanIncludeHashtags: "您可以包含一个话题标签。"
metadata: "附加信息" metadata: "附加信息"
metadataEdit: "附加信息编辑" metadataEdit: "附加信息编辑"
metadataDescription: "使用这些,您可以在您的个人资料中显示其它信息字段。" metadataDescription: "使用这些,您可以在您的个人资料中显示其它信息字段。您可以添加带有 {rel} 的 {a} 标签或 {l} 标签来验证您个人资料上的链接!"
metadataLabel: "标签" metadataLabel: "标签"
metadataContent: "内容" metadataContent: "内容"
changeAvatar: "修改头像" changeAvatar: "修改头像"

View File

@ -984,6 +984,12 @@ _aboutMisskey:
donate: "贊助Calckey" donate: "贊助Calckey"
morePatrons: "還有許許多多幫助我們的其他人,非常感謝你們。 🥰" morePatrons: "還有許許多多幫助我們的其他人,非常感謝你們。 🥰"
patrons: "贊助者" patrons: "贊助者"
patronsList: 按時間順序列出,而不是按贊助規模列出。使用上面的連結贊助,在這裡獲得顯示您名字的機會!
sponsors: Calckey 贊助者們
donateTitle: 覺得 Calckey 棒嗎?
pleaseDonateToCalckey: 請考慮向 Calckey 贊助以支持其發展。
pleaseDonateToHost: 還請考慮捐贈給您在使用的伺服器 {host},以支援龐大的運營成本。
donateHost: 贊助給 {host}
_nsfw: _nsfw:
respect: "隱藏敏感內容" respect: "隱藏敏感內容"
ignore: "不隱藏敏感內容" ignore: "不隱藏敏感內容"
@ -1060,6 +1066,8 @@ _mfm:
position: 位置 position: 位置
alwaysPlay: 自動播放所有MFM動畫 alwaysPlay: 自動播放所有MFM動畫
positionDescription: 按指定數量移動內容。 positionDescription: 按指定數量移動內容。
advancedDescription: 如果禁用,則僅允許基本標記,除非正在播放 MFM 動畫
advanced: 高級MFM
_instanceTicker: _instanceTicker:
none: "隱藏" none: "隱藏"
remote: "向遠端使用者顯示" remote: "向遠端使用者顯示"
@ -1202,14 +1210,14 @@ _tutorial:
step1_1: "歡迎!" step1_1: "歡迎!"
step1_2: "讓我們把你安排好。你很快就會啟動並運行!" step1_2: "讓我們把你安排好。你很快就會啟動並運行!"
step2_1: "首先,請完成你的個人資料。" step2_1: "首先,請完成你的個人資料。"
step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的帖子或關注你。" step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的貼文或關注你。"
step3_1: "現在是時候追隨一些人了!" step3_1: "現在是時候追隨一些人了!"
step3_2: "你的主頁和社交時間線是基於你所追蹤的人,所以試著先追蹤幾個帳戶。\n點擊個人資料右上角的加號圈就可以關注它。" step3_2: "你的主頁和社交時間線是基於你所追蹤的人,所以試著先追蹤幾個帳戶。\n點擊個人資料右上角的加號圈就可以關注它。"
step4_1: "讓我們出去找你。" step4_1: "讓我們出去找你。"
step4_2: "對於他們的第一條信息,有些人喜歡做 {introduction} 或一個簡單的 \"hello world!\"" step4_2: "對於他們的第一條信息,有些人喜歡做 {introduction} 或一個簡單的 \"hello world!\""
step5_1: "時間線,到處都是時間線!" step5_1: "時間線,到處都是時間線!"
step5_2: "您的伺服器已啟用了{timelines}個時間線。" step5_2: "您的伺服器已啟用了{timelines}個時間線。"
step5_3: "首頁 {icon} 時間線是顯示你追蹤的帳號的帖子。" step5_3: "首頁 {icon} 時間線是顯示你追蹤的帳號的貼文。"
step5_4: "本地 {icon} 時間線是你可以看到伺服器中所有其他用戶的貼文的時間線。" step5_4: "本地 {icon} 時間線是你可以看到伺服器中所有其他用戶的貼文的時間線。"
step5_5: "社交 {icon} 時間線是你的 首頁時間線 和 本地時間線 的結合體。" step5_5: "社交 {icon} 時間線是你的 首頁時間線 和 本地時間線 的結合體。"
step5_6: "推薦 {icon} 時間線是顯示你的伺服器管理員推薦的貼文。" step5_6: "推薦 {icon} 時間線是顯示你的伺服器管理員推薦的貼文。"
@ -1361,7 +1369,7 @@ _profile:
youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag。" youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag。"
metadata: "進階資訊" metadata: "進階資訊"
metadataEdit: "編輯進階資訊" metadataEdit: "編輯進階資訊"
metadataDescription: "可以在個人資料中以表格形式顯示其他資訊。" metadataDescription: "可以在個人資料中以表格形式顯示其他資訊。您可以添加帶有 {rel} 的 {a} 標籤或 {l} 標籤來驗證您個人資料上的鏈接!"
metadataLabel: "標籤" metadataLabel: "標籤"
metadataContent: "内容" metadataContent: "内容"
changeAvatar: "更換大頭貼" changeAvatar: "更換大頭貼"
@ -1820,12 +1828,12 @@ _experiments:
title: 試驗功能 title: 試驗功能
findOtherInstance: 找找另一個伺服器 findOtherInstance: 找找另一個伺服器
noGraze: 瀏覽器擴展 "Graze for Mastodon" 會與Calckey發生衝突請停用該擴展。 noGraze: 瀏覽器擴展 "Graze for Mastodon" 會與Calckey發生衝突請停用該擴展。
userSaysSomethingReasonRenote: '{name} 轉傳了包含 {reason} 的帖子' userSaysSomethingReasonRenote: '{name} 轉傳了包含 {reason} 的貼文'
pushNotificationNotSupported: 你的瀏覽器或伺服器不支援推送通知 pushNotificationNotSupported: 你的瀏覽器或伺服器不支援推送通知
accessibility: 輔助功能 accessibility: 輔助功能
userSaysSomethingReasonReply: '{name} 回復了包含 {reason} 的帖子' userSaysSomethingReasonReply: '{name} 回覆了包含 {reason} 的貼文'
hiddenTags: 隱藏主題標籤 hiddenTags: 隱藏主題標籤
indexPosts: 索引帖子 indexPosts: 索引貼文
indexNotice: 現在開始索引。 這可能需要一段時間,請不要在一個小時內重啟你的伺服器。 indexNotice: 現在開始索引。 這可能需要一段時間,請不要在一個小時內重啟你的伺服器。
deleted: 已刪除 deleted: 已刪除
editNote: 編輯筆記 editNote: 編輯筆記
@ -1861,9 +1869,33 @@ audio: 音訊
sendPushNotificationReadMessageCaption: 包含文本 “{emptyPushNotificationMessage}” 的通知將顯示一小段時間。 sendPushNotificationReadMessageCaption: 包含文本 “{emptyPushNotificationMessage}” 的通知將顯示一小段時間。
這可能會增加您設備的電池使用量(如果適用)。 這可能會增加您設備的電池使用量(如果適用)。
channelFederationWarn: 頻道功能尚未與聯邦宇宙連動 channelFederationWarn: 頻道功能尚未與聯邦宇宙連動
swipeOnMobile: 允許在頁面之間滑動 swipeOnMobile: 允許以滑動在頁面之間切換
sendPushNotificationReadMessage: 閱讀相關通知或消息後刪除推送通知 sendPushNotificationReadMessage: 閱讀相關通知或消息後刪除推送通知
image: 圖片 image: 圖片
seperateRenoteQuote: 分別獨立的轉傳及引用按鈕 seperateRenoteQuote: 分別獨立的轉傳及引用按鈕
clipsDesc: 摘錄就像一個可以分享的書籤。 你可以從每個貼文的菜單創建新摘錄或將貼文加入已有的摘錄。 clipsDesc: 摘錄就像一個可以分享的書籤。 你可以從每個貼文的菜單創建新摘錄或將貼文加入已有的摘錄。
noteId: 貼文 ID noteId: 貼文 ID
sendModMail: 發送審核通知
enableIdenticonGeneration: 啟用碎片生成
enableServerMachineStats: 啟用伺服器硬體統計資訊
reactionPickerSkinTone: 首選表情符號膚色
indexFromDescription: 留空以索引每個貼文
preventAiLearning: 防止 AI 機器人抓取
preventAiLearningDescription: 請求第三方 AI 語言模型不要研究您上傳的內容,例如貼文和圖像。
indexFrom: 從貼文 ID 開始的索引
isLocked: 該帳戶已獲得以下批准
isModerator: 板主
isAdmin: 管理員
isPatron: Calckey 項目贊助者
silencedWarning: 顯示此頁面是因為這些使用者來自您伺服器管理員已靜音的伺服器,因此他們可能是垃圾訊息。
signupsDisabled: 該伺服器上的註冊當前已被禁用,但您隨時可以在另一台伺服器上註冊!或是您有該伺服器的邀請碼,請在下面輸入。
showPopup: 通過彈出式視窗通知用戶
showWithSparkles: 閃閃發光的顯示
youHaveUnreadAnnouncements: 您有未讀的公告
donationLink: 連結到贊助頁面
neverShow: 不再顯示
remindMeLater: 可能之後
removeQuote: 删除引用
removeRecipient: 刪除收件者
removeMember: 刪除成員
isBot: 此帳戶是機器人

View File

@ -1,6 +1,6 @@
{ {
"name": "calckey", "name": "calckey",
"version": "14.0.0-dev78", "version": "14.0.0-dev79",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",
@ -57,7 +57,7 @@
"gulp-replace": "1.1.4", "gulp-replace": "1.1.4",
"gulp-terser": "2.1.0", "gulp-terser": "2.1.0",
"install-peers": "^1.0.4", "install-peers": "^1.0.4",
"rome": "^12.1.3", "rome": "^v12.1.3-nightly.f65b0d9",
"start-server-and-test": "1.15.2", "start-server-and-test": "1.15.2",
"typescript": "5.1.6" "typescript": "5.1.6"
} }

View File

@ -0,0 +1,13 @@
Copyright 2023 Calckey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,9 +1,15 @@
export class tweakVarcharLength1678426061773 { export class tweakVarcharLength1678426061773 {
name = 'tweakVarcharLength1678426061773' name = "tweakVarcharLength1678426061773";
async up(queryRunner) { async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpUser" TYPE character varying(1024)`, undefined); await queryRunner.query(
await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "smtpPass" TYPE character varying(1024)`, undefined); `ALTER TABLE "meta" ALTER COLUMN "smtpUser" TYPE character varying(1024)`,
undefined,
);
await queryRunner.query(
`ALTER TABLE "meta" ALTER COLUMN "smtpPass" TYPE character varying(1024)`,
undefined,
);
} }
async down(queryRunner) {} async down(queryRunner) {}

View File

@ -43,6 +43,7 @@
"universal": "napi universal", "universal": "napi universal",
"version": "napi version", "version": "napi version",
"format": "cargo fmt --all", "format": "cargo fmt --all",
"lint": "cargo clippy --fix",
"cargo:test": "pnpm run cargo:unit && pnpm run cargo:integration", "cargo:test": "pnpm run cargo:unit && pnpm run cargo:integration",
"cargo:unit": "cargo test unit_test && cargo test -F napi unit_test", "cargo:unit": "cargo test unit_test && cargo test -F napi unit_test",
"cargo:integration": "cargo test -F noarray int_test -- --test-threads=1" "cargo:integration": "cargo test -F noarray int_test -- --test-threads=1"

View File

@ -46,7 +46,7 @@ impl Repository<Antenna> for antenna::Model {
src: self.src.try_into()?, src: self.src.try_into()?,
user_list_id: self.user_list_id, user_list_id: self.user_list_id,
user_group_id, user_group_id,
users: self.users.into(), users: self.users,
instances: self.instances.into(), instances: self.instances.into(),
case_sensitive: self.case_sensitive, case_sensitive: self.case_sensitive,
notify: self.notify, notify: self.notify,

View File

@ -58,7 +58,7 @@ impl TryFrom<AntennaSrcEnum> for super::AntennaSrc {
// ---- TODO: could be macro // ---- TODO: could be macro
impl Schema<Self> for super::Antenna {} impl Schema<Self> for super::Antenna {}
pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| super::Antenna::validator()); pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(super::Antenna::validator);
// ---- // ----
cfg_if! { cfg_if! {

View File

@ -91,7 +91,7 @@ pub enum AppPermission {
impl Schema<Self> for App {} impl Schema<Self> for App {}
pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| App::validator()); pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(App::validator);
#[cfg(test)] #[cfg(test)]
mod unit_test { mod unit_test {

View File

@ -148,8 +148,8 @@ async fn setup_model(db: &DbConn) {
let user_model = entity::user::Model { let user_model = entity::user::Model {
id: user_id.to_owned(), id: user_id.to_owned(),
created_at: Utc::now().into(), created_at: Utc::now().into(),
username: name.to_lowercase().to_string(), username: name.to_lowercase(),
username_lower: name.to_lowercase().to_string(), username_lower: name.to_lowercase(),
name: Some(name.to_string()), name: Some(name.to_string()),
token: Some(gen_string(16)), token: Some(gen_string(16)),
is_admin: true, is_admin: true,

View File

@ -43,18 +43,16 @@ mod int_test {
keywords: vec![ keywords: vec![
vec!["foo".to_string(), "bar".to_string()], vec!["foo".to_string(), "bar".to_string()],
vec!["foobar".to_string()], vec!["foobar".to_string()],
] ],
.into(),
exclude_keywords: vec![ exclude_keywords: vec![
vec!["abc".to_string()], vec!["abc".to_string()],
vec!["def".to_string(), "ghi".to_string()], vec!["def".to_string(), "ghi".to_string()],
] ],
.into(),
src: schema::AntennaSrc::All, src: schema::AntennaSrc::All,
user_list_id: None, user_list_id: None,
user_group_id: None, user_group_id: None,
users: vec![].into(), users: vec![],
instances: vec![].into(), instances: vec![],
case_sensitive: true, case_sensitive: true,
notify: true, notify: true,
with_replies: false, with_replies: false,

View File

@ -71,6 +71,7 @@
"is-svg": "4.3.2", "is-svg": "4.3.2",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "20.0.3", "jsdom": "20.0.3",
"json5": "2.2.3",
"jsonld": "8.2.0", "jsonld": "8.2.0",
"jsrsasign": "10.8.6", "jsrsasign": "10.8.6",
"koa": "2.14.2", "koa": "2.14.2",
@ -185,7 +186,6 @@
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "^8.44.0", "eslint": "^8.44.0",
"execa": "6.1.0", "execa": "6.1.0",
"json5": "2.2.3",
"json5-loader": "4.0.1", "json5-loader": "4.0.1",
"mocha": "10.2.0", "mocha": "10.2.0",
"pug": "3.0.2", "pug": "3.0.2",

View File

@ -1,5 +1,8 @@
import config from "@/config/index.js"; import config from "@/config/index.js";
import { DB_MAX_NOTE_TEXT_LENGTH, DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; import {
DB_MAX_NOTE_TEXT_LENGTH,
DB_MAX_IMAGE_COMMENT_LENGTH,
} from "@/misc/hard-limits.js";
export const MAX_NOTE_TEXT_LENGTH = Math.min( export const MAX_NOTE_TEXT_LENGTH = Math.min(
config.maxNoteLength ?? 3000, config.maxNoteLength ?? 3000,

View File

@ -24,6 +24,7 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
.stream(url, { .stream(url, {
headers: { headers: {
"User-Agent": config.userAgent, "User-Agent": config.userAgent,
Host: new URL(url).hostname,
}, },
timeout: { timeout: {
lookup: timeout, lookup: timeout,

View File

@ -51,6 +51,7 @@ export class UserProfile {
public fields: { public fields: {
name: string; name: string;
value: string; value: string;
verified?: boolean;
}[]; }[];
@Column("varchar", { @Column("varchar", {

View File

@ -576,6 +576,16 @@ export default function () {
{ removeOnComplete: true, removeOnFail: true }, { removeOnComplete: true, removeOnFail: true },
); );
systemQueue.add(
"verifyLinks",
{},
{
repeat: { cron: "0 0 * * 0" },
removeOnComplete: true,
removeOnFail: true,
},
);
processSystemQueue(systemQueue); processSystemQueue(systemQueue);
} }

View File

@ -5,6 +5,7 @@ import { cleanCharts } from "./clean-charts.js";
import { checkExpiredMutings } from "./check-expired-mutings.js"; import { checkExpiredMutings } from "./check-expired-mutings.js";
import { clean } from "./clean.js"; import { clean } from "./clean.js";
import { setLocalEmojiSizes } from "./local-emoji-size.js"; import { setLocalEmojiSizes } from "./local-emoji-size.js";
import { verifyLinks } from "./verify-links.js";
const jobs = { const jobs = {
tickCharts, tickCharts,
@ -13,6 +14,7 @@ const jobs = {
checkExpiredMutings, checkExpiredMutings,
clean, clean,
setLocalEmojiSizes, setLocalEmojiSizes,
verifyLinks,
} as Record< } as Record<
string, string,
| Bull.ProcessCallbackFunction<Record<string, unknown>> | Bull.ProcessCallbackFunction<Record<string, unknown>>

View File

@ -0,0 +1,44 @@
import type Bull from "bull";
import { UserProfiles } from "@/models/index.js";
import { Not } from "typeorm";
import { queueLogger } from "../../logger.js";
import { verifyLink } from "@/services/fetch-rel-me.js";
import config from "@/config/index.js";
const logger = queueLogger.createSubLogger("verify-links");
export async function verifyLinks(
job: Bull.Job<Record<string, unknown>>,
done: any,
): Promise<void> {
logger.info("Verifying links...");
const usersToVerify = await UserProfiles.findBy({
fields: Not(null),
userHost: "",
});
for (const user of usersToVerify) {
for (const field of user.fields) {
if (!field || field.name === "" || field.value === "") {
continue;
}
if (field.value.startsWith("http") && user.user?.username) {
field.verified = await verifyLink(field.value, user.user.username);
}
}
if (user.fields.length > 0) {
try {
await UserProfiles.update(user.userId, {
fields: user.fields,
});
} catch (e) {
logger.error(`Failed to update user ${user.userId} ${e}`);
done(e);
}
}
}
logger.succ("All links successfully verified.");
done();
}

View File

@ -12,7 +12,9 @@ import type { UserProfile } from "@/models/entities/user-profile.js";
import { notificationTypes } from "@/types.js"; import { notificationTypes } from "@/types.js";
import { normalizeForSearch } from "@/misc/normalize-for-search.js"; import { normalizeForSearch } from "@/misc/normalize-for-search.js";
import { langmap } from "@/misc/langmap.js"; import { langmap } from "@/misc/langmap.js";
import { verifyLink } from "@/services/fetch-rel-me.js";
import { ApiError } from "../../error.js"; import { ApiError } from "../../error.js";
import config from "@/config/index.js";
import define from "../../define.js"; import define from "../../define.js";
export const meta = { export const meta = {
@ -58,6 +60,18 @@ export const meta = {
code: "INVALID_REGEXP", code: "INVALID_REGEXP",
id: "0d786918-10df-41cd-8f33-8dec7d9a89a5", id: "0d786918-10df-41cd-8f33-8dec7d9a89a5",
}, },
invalidFieldName: {
message: "Invalid field name.",
code: "INVALID_FIELD_NAME",
id: "8f81972e-8b53-4d30-b0d2-efb026dda673",
},
invalidFieldValue: {
message: "Invalid field value.",
code: "INVALID_FIELD_VALUE",
id: "aede7444-244b-11ee-be56-0242ac120002",
},
}, },
res: { res: {
@ -234,16 +248,29 @@ export default define(meta, paramDef, async (ps, _user, token) => {
} }
if (ps.fields) { if (ps.fields) {
for (const field of ps.fields) {
if (!field || field.name === "" || field.value === "") {
continue;
}
if (typeof field.name !== "string" || field.name === "") {
throw new ApiError(meta.errors.invalidFieldName);
}
if (typeof field.value !== "string" || field.value === "") {
throw new ApiError(meta.errors.invalidFieldValue);
}
if (field.value.startsWith("http")) {
field.verified = await verifyLink(field.value, user.username);
}
}
profileUpdates.fields = ps.fields profileUpdates.fields = ps.fields
.filter( .filter((x) => Object.keys(x).length !== 0)
(x) =>
typeof x.name === "string" &&
x.name !== "" &&
typeof x.value === "string" &&
x.value !== "",
)
.map((x) => { .map((x) => {
return { name: x.name, value: x.value }; return {
name: x.name,
value: x.value,
verified: x.verified,
};
}); });
} }

View File

@ -1,4 +1,6 @@
import * as fs from "node:fs"; import * as fs from "node:fs";
import net from "node:net";
import { promises } from "node:dns";
import type Koa from "koa"; import type Koa from "koa";
import sharp from "sharp"; import sharp from "sharp";
import type { IImage } from "@/services/drive/image-processor.js"; import type { IImage } from "@/services/drive/image-processor.js";
@ -19,6 +21,40 @@ export async function proxyMedia(ctx: Koa.Context) {
return; return;
} }
const { hostname } = new URL(url);
let resolvedIps;
try {
resolvedIps = await promises.resolve(hostname);
} catch (error) {
ctx.status = 400;
ctx.body = { message: "Invalid URL" };
return;
}
const isSSRF = resolvedIps.some((ip) => {
if (net.isIPv4(ip)) {
const parts = ip.split(".").map(Number);
return (
parts[0] === 10 ||
(parts[0] === 172 && parts[1] >= 16 && parts[1] < 32) ||
(parts[0] === 192 && parts[1] === 168) ||
parts[0] === 127 ||
parts[0] === 0
);
} else if (net.isIPv6(ip)) {
return (
ip.startsWith("::") || ip.startsWith("fc00:") || ip.startsWith("fe80:")
);
}
return false;
});
if (isSSRF) {
ctx.status = 400;
ctx.body = { message: "Access to this URL is not allowed" };
return;
}
// Create temp file // Create temp file
const [path, cleanup] = await createTemp(); const [path, cleanup] = await createTemp();

View File

@ -0,0 +1,33 @@
import { getHtml } from "@/misc/fetch.js";
import { JSDOM } from "jsdom";
import config from "@/config/index.js";
async function getRelMeLinks(url: string): Promise<string[]> {
try {
const html = await getHtml(url);
const dom = new JSDOM(html);
const relMeLinks = [
...dom.window.document.querySelectorAll("a[rel='me']"),
...dom.window.document.querySelectorAll("link[rel='me']"),
].map((a) => (a as HTMLAnchorElement | HTMLLinkElement).href);
return relMeLinks;
} catch {
return [];
}
}
export async function verifyLink(link: string, username: string): Promise<boolean> {
let verified = false;
if (link.startsWith("http")) {
const relMeLinks = await getRelMeLinks(link);
verified = relMeLinks.some((href) =>
new RegExp(
`^https?:\/\/${config.host.replace(
/[.*+\-?^${}()|[\]\\]/g,
"\\$&",
)}\/@${username.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&")}$`,
).test(href),
);
}
return verified;
}

View File

@ -38,7 +38,11 @@ export type UserDetailed = UserLite & {
createdAt: DateString; createdAt: DateString;
description: string | null; description: string | null;
ffVisibility: "public" | "followers" | "private"; ffVisibility: "public" | "followers" | "private";
fields: { name: string; value: string }[]; fields: {
name: string;
value: string;
verified?: boolean;
}[];
followersCount: number; followersCount: number;
followingCount: number; followingCount: number;
hasPendingFollowRequestFromYou: boolean; hasPendingFollowRequestFromYou: boolean;

View File

@ -0,0 +1,7 @@
{
"extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"],
"plugins": ["file-progress", "prettier"],
"rules": {
"file-progress/activate": 1
}
}

View File

@ -4,11 +4,14 @@
"scripts": { "scripts": {
"watch": "pnpm vite build --watch --mode development", "watch": "pnpm vite build --watch --mode development",
"build": "pnpm vite build", "build": "pnpm vite build",
"lint": "pnpm rome check \"src/**/*.{ts,vue}\"", "lint": "pnpm rome check **/*.ts --apply && pnpm run lint:vue",
"format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}'" "lint:vue": "pnpm paralint --ext .vue --fix '**/*.vue' --cache",
"format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata"
}, },
"devDependencies": { "devDependencies": {
"@discordapp/twemoji": "14.1.2", "@discordapp/twemoji": "14.1.2",
"@eslint-sets/eslint-config-vue3": "^5.6.1",
"@eslint-sets/eslint-config-vue3-ts": "^3.3.0",
"@phosphor-icons/web": "^2.0.3", "@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-alias": "3.1.9", "@rollup/plugin-alias": "3.1.9",
"@rollup/plugin-json": "4.1.0", "@rollup/plugin-json": "4.1.0",
@ -46,6 +49,8 @@
"date-fns": "2.30.0", "date-fns": "2.30.0",
"emojilib": "github:thatonecalculator/emojilib", "emojilib": "github:thatonecalculator/emojilib",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-file-progress": "^1.3.0",
"eventemitter3": "5.0.1", "eventemitter3": "5.0.1",
"fast-blurhash": "^1.1.2", "fast-blurhash": "^1.1.2",
"focus-trap": "^7.5.2", "focus-trap": "^7.5.2",
@ -57,6 +62,7 @@
"katex": "0.16.8", "katex": "0.16.8",
"matter-js": "0.18.0", "matter-js": "0.18.0",
"mfm-js": "0.23.3", "mfm-js": "0.23.3",
"paralint": "^1.2.1",
"photoswipe": "5.3.8", "photoswipe": "5.3.8",
"prettier": "3.0.0", "prettier": "3.0.0",
"prettier-plugin-vue": "1.1.6", "prettier-plugin-vue": "1.1.6",

View File

@ -80,11 +80,11 @@ const emit = defineEmits<{
(ev: "resolved", reportId: string): void; (ev: "resolved", reportId: string): void;
}>(); }>();
let forward = $ref(props.report.forwarded); const forward = $ref(props.report.forwarded);
function resolve() { function resolve() {
os.apiWithDialog("admin/resolve-abuse-user-report", { os.apiWithDialog("admin/resolve-abuse-user-report", {
forward: forward, forward,
reportId: props.report.id, reportId: props.report.id,
}).then(() => { }).then(() => {
emit("resolved", props.report.id); emit("resolved", props.report.id);

View File

@ -41,7 +41,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import * as Misskey from "calckey-js"; import type * as Misskey from "calckey-js";
import XWindow from "@/components/MkWindow.vue"; import XWindow from "@/components/MkWindow.vue";
import MkTextarea from "@/components/form/textarea.vue"; import MkTextarea from "@/components/form/textarea.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";

View File

@ -109,12 +109,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
ref,
computed, computed,
onMounted,
onBeforeUnmount,
shallowRef,
nextTick, nextTick,
onBeforeUnmount,
onMounted,
ref,
shallowRef,
} from "vue"; } from "vue";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
import { globalEvents } from "@/events.js"; import { globalEvents } from "@/events.js";
@ -173,21 +173,21 @@ const texts = computed(() => {
return angles; return angles;
}); });
let enabled = true; let enabled = true,
let majorGraduationColor = $ref<string>(); majorGraduationColor = $ref<string>(),
// let minorGraduationColor = $ref<string>(); // let minorGraduationColor = $ref<string>();
let sHandColor = $ref<string>(); sHandColor = $ref<string>(),
let mHandColor = $ref<string>(); mHandColor = $ref<string>(),
let hHandColor = $ref<string>(); hHandColor = $ref<string>(),
let nowColor = $ref<string>(); nowColor = $ref<string>(),
let h = $ref<number>(0); h = $ref<number>(0),
let m = $ref<number>(0); m = $ref<number>(0),
let s = $ref<number>(0); s = $ref<number>(0),
let hAngle = $ref<number>(0); hAngle = $ref<number>(0),
let mAngle = $ref<number>(0); mAngle = $ref<number>(0),
let sAngle = $ref<number>(0); sAngle = $ref<number>(0),
let disableSAnimate = $ref(false); disableSAnimate = $ref(false),
let sOneRound = false; sOneRound = false;
function tick() { function tick() {
const now = new Date(); const now = new Date();

View File

@ -85,11 +85,11 @@
<script lang="ts"> <script lang="ts">
import { import {
markRaw, markRaw,
ref,
onUpdated,
onMounted,
onBeforeUnmount,
nextTick, nextTick,
onBeforeUnmount,
onMounted,
onUpdated,
ref,
watch, watch,
} from "vue"; } from "vue";
import contains from "@/scripts/contains"; import contains from "@/scripts/contains";
@ -99,17 +99,17 @@ import { acct } from "@/filters/user";
import * as os from "@/os"; import * as os from "@/os";
import { MFM_TAGS } from "@/scripts/mfm-tags"; import { MFM_TAGS } from "@/scripts/mfm-tags";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { emojilist, addSkinTone } from "@/scripts/emojilist"; import { addSkinTone, emojilist } from "@/scripts/emojilist";
import { instance } from "@/instance"; import { instance } from "@/instance";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
type EmojiDef = { interface EmojiDef {
emoji: string; emoji: string;
name: string; name: string;
aliasOf?: string; aliasOf?: string;
url?: string; url?: string;
isCustomEmoji?: boolean; isCustomEmoji?: boolean;
}; }
const lib = emojilist.filter((x) => x.category !== "flags"); const lib = emojilist.filter((x) => x.category !== "flags");
@ -436,10 +436,7 @@ onMounted(() => {
setPosition(); setPosition();
props.textarea.addEventListener("keydown", onKeydown); props.textarea.addEventListener("keydown", onKeydown);
document.body.addEventListener("mousedown", onMousedown);
for (const el of Array.from(document.querySelectorAll("body *"))) {
el.addEventListener("mousedown", onMousedown);
}
nextTick(() => { nextTick(() => {
exec(); exec();
@ -457,10 +454,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
props.textarea.removeEventListener("keydown", onKeydown); props.textarea.removeEventListener("keydown", onKeydown);
document.body.removeEventListener("mousedown", onMousedown);
for (const el of Array.from(document.querySelectorAll("body *"))) {
el.removeEventListener("mousedown", onMousedown);
}
}); });
</script> </script>

View File

@ -49,8 +49,8 @@ const emit = defineEmits<{
(ev: "click", payload: MouseEvent): void; (ev: "click", payload: MouseEvent): void;
}>(); }>();
let el = $ref<HTMLElement | null>(null); const el = $ref<HTMLElement | null>(null);
let ripples = $ref<HTMLElement | null>(null); const ripples = $ref<HTMLElement | null>(null);
onMounted(() => { onMounted(() => {
if (props.autofocus) { if (props.autofocus) {

View File

@ -6,11 +6,11 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount, watch } from "vue"; import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
type Captcha = { interface Captcha {
render( render(
container: string | Node, container: string | Node,
options: { options: {
@ -31,7 +31,7 @@ type Captcha = {
execute(id: string): void; execute(id: string): void;
reset(id?: string): void; reset(id?: string): void;
getResponse(id: string): string; getResponse(id: string): string;
}; }
type CaptchaProvider = "hcaptcha" | "recaptcha"; type CaptchaProvider = "hcaptcha" | "recaptcha";
@ -105,7 +105,7 @@ function requestRender() {
captcha.value.render(captchaEl.value, { captcha.value.render(captchaEl.value, {
sitekey: props.sitekey, sitekey: props.sitekey,
theme: defaultStore.state.darkMode ? "dark" : "light", theme: defaultStore.state.darkMode ? "dark" : "light",
callback: callback, callback,
"expired-callback": callback, "expired-callback": callback,
"error-callback": callback, "error-callback": callback,
}); });

View File

@ -24,7 +24,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import MkChannelPreview from "@/components/MkChannelPreview.vue"; import MkChannelPreview from "@/components/MkChannelPreview.vue";
import MkPagination, { Paging } from "@/components/MkPagination.vue"; import type { Paging } from "@/components/MkPagination.vue";
import MkPagination from "@/components/MkPagination.vue";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
const props = withDefaults( const props = withDefaults(

View File

@ -8,23 +8,24 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, watch, PropType, onUnmounted } from "vue"; import type { PropType } from "vue";
import { onMounted, onUnmounted, ref, watch } from "vue";
import { import {
Chart,
ArcElement, ArcElement,
LineElement,
BarElement,
PointElement,
BarController, BarController,
LineController, BarElement,
CategoryScale, CategoryScale,
LinearScale, Chart,
TimeScale, Filler,
Legend, Legend,
LineController,
LineElement,
LinearScale,
PointElement,
SubTitle,
TimeScale,
Title, Title,
Tooltip, Tooltip,
SubTitle,
Filler,
} from "chart.js"; } from "chart.js";
import "chartjs-adapter-date-fns"; import "chartjs-adapter-date-fns";
import { enUS } from "date-fns/locale"; import { enUS } from "date-fns/locale";
@ -127,8 +128,8 @@ const getColor = (i) => {
}; };
const now = new Date(); const now = new Date();
let chartInstance: Chart = null; let chartInstance: Chart = null,
let chartData: { chartData: {
series: { series: {
name: string; name: string;
type: "line" | "area"; type: "line" | "area";

View File

@ -26,7 +26,7 @@
: message.user : message.user
" "
:show-indicator="true" :show-indicator="true"
disableLink disable-link
/> />
<header v-if="message.groupId"> <header v-if="message.groupId">
<span class="name">{{ message.group.name }}</span> <span class="name">{{ message.group.name }}</span>

View File

@ -12,9 +12,9 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onBeforeUnmount } from "vue"; import { onBeforeUnmount, onMounted } from "vue";
import MkMenu from "./MkMenu.vue"; import MkMenu from "./MkMenu.vue";
import { MenuItem } from "./types/menu.vue"; import type { MenuItem } from "./types/menu.vue";
import contains from "@/scripts/contains"; import contains from "@/scripts/contains";
import * as os from "@/os"; import * as os from "@/os";
@ -27,13 +27,13 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
let rootEl = $ref<HTMLDivElement>(); const rootEl = $ref<HTMLDivElement>();
let zIndex = $ref<number>(os.claimZIndex("high")); const zIndex = $ref<number>(os.claimZIndex("high"));
onMounted(() => { onMounted(() => {
let left = props.ev.pageX + 1; // + 1 let left = props.ev.pageX + 1, // + 1
let top = props.ev.pageY + 1; // + 1 top = props.ev.pageY + 1; // + 1
const width = rootEl.offsetWidth; const width = rootEl.offsetWidth;
const height = rootEl.offsetHeight; const height = rootEl.offsetHeight;
@ -57,15 +57,11 @@ onMounted(() => {
rootEl.style.top = `${top}px`; rootEl.style.top = `${top}px`;
rootEl.style.left = `${left}px`; rootEl.style.left = `${left}px`;
for (const el of Array.from(document.querySelectorAll("body *"))) { document.body.addEventListener("mousedown", onMousedown);
el.addEventListener("mousedown", onMousedown);
}
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
for (const el of Array.from(document.querySelectorAll("body *"))) { document.body.removeEventListener("mousedown", onMousedown);
el.removeEventListener("mousedown", onMousedown);
}
}); });
function onMousedown(evt: Event) { function onMousedown(evt: Event) {

View File

@ -37,7 +37,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted } from "vue"; import { nextTick, onMounted } from "vue";
import * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
import Cropper from "cropperjs"; import Cropper from "cropperjs";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
import XModalWindow from "@/components/MkModalWindow.vue"; import XModalWindow from "@/components/MkModalWindow.vue";
@ -62,10 +62,10 @@ const props = defineProps<{
const imgUrl = `${url}/proxy/image.webp?${query({ const imgUrl = `${url}/proxy/image.webp?${query({
url: props.file.url, url: props.file.url,
})}`; })}`;
let dialogEl = $ref<InstanceType<typeof XModalWindow>>(); const dialogEl = $ref<InstanceType<typeof XModalWindow>>();
let imgEl = $ref<HTMLImageElement>(); const imgEl = $ref<HTMLImageElement>();
let cropper: Cropper | null = null; let cropper: Cropper | null = null,
let loading = $ref(true); loading = $ref(true);
const ok = async () => { const ok = async () => {
const promise = new Promise<misskey.entities.DriveFile>(async (res) => { const promise = new Promise<misskey.entities.DriveFile>(async (res) => {

View File

@ -15,7 +15,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { length } from "stringz"; import { length } from "stringz";
import * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
import { concat } from "@/scripts/array"; import { concat } from "@/scripts/array";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, h, PropType, TransitionGroup } from "vue"; import type { PropType } from "vue";
import { TransitionGroup, defineComponent, h } from "vue";
import MkAd from "@/components/global/MkAd.vue"; import MkAd from "@/components/global/MkAd.vue";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
@ -51,7 +52,7 @@ export default defineComponent({
if (!slots || !slots.default) return; if (!slots || !slots.default) return;
const el = slots.default({ const el = slots.default({
item: item, item,
})[0]; })[0];
if (el.key == null && item.id) el.key = item.id; if (el.key == null && item.id) el.key = item.id;

View File

@ -57,17 +57,17 @@
<Mfm :text="text" /> <Mfm :text="text" />
</div> </div>
<MkInput <MkInput
ref="inputEl"
v-if="input && input.type !== 'paragraph'" v-if="input && input.type !== 'paragraph'"
ref="inputEl"
v-model="inputValue" v-model="inputValue"
autofocus autofocus
:autocomplete="input.autocomplete" :autocomplete="input.autocomplete"
:type="input.type == 'search' ? 'search' : input.type || 'text'" :type="input.type == 'search' ? 'search' : input.type || 'text'"
:placeholder="input.placeholder || undefined" :placeholder="input.placeholder || undefined"
@keydown="onInputKeydown"
:style="{ :style="{
width: input.type === 'search' ? '300px' : null, width: input.type === 'search' ? '300px' : null,
}" }"
@keydown="onInputKeydown"
> >
<template v-if="input.type === 'password'" #prefix <template v-if="input.type === 'password'" #prefix
><i class="ph-password ph-bold ph-lg"></i ><i class="ph-password ph-bold ph-lg"></i
@ -100,9 +100,9 @@
</template> </template>
<template v-if="input.type === 'search'" #suffix> <template v-if="input.type === 'search'" #suffix>
<button <button
v-tooltip.noDelay="i18n.ts.filter"
class="_buttonIcon" class="_buttonIcon"
@click.stop="openSearchFilters" @click.stop="openSearchFilters"
v-tooltip.noDelay="i18n.ts.filter"
> >
<i class="ph-funnel ph-bold"></i> <i class="ph-funnel ph-bold"></i>
</button> </button>
@ -200,6 +200,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue"; import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
import * as Acct from "calckey-js/built/acct";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue"; import MkInput from "@/components/form/input.vue";
@ -207,18 +208,17 @@ import MkTextarea from "@/components/form/textarea.vue";
import MkSelect from "@/components/form/select.vue"; import MkSelect from "@/components/form/select.vue";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import * as Acct from "calckey-js/built/acct";
type Input = { interface Input {
type: HTMLInputElement["type"]; type: HTMLInputElement["type"];
placeholder?: string | null; placeholder?: string | null;
autocomplete?: string; autocomplete?: string;
default: string | number | null; default: string | number | null;
minLength?: number; minLength?: number;
maxLength?: number; maxLength?: number;
}; }
type Select = { interface Select {
items: { items: {
value: string; value: string;
text: string; text: string;
@ -231,7 +231,7 @@ type Select = {
}[]; }[];
}[]; }[];
default: string | null; default: string | null;
}; }
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{

View File

@ -49,8 +49,8 @@
<button <button
class="_button" class="_button"
:class="$style.close" :class="$style.close"
@click="close"
:aria-label="i18n.t('close')" :aria-label="i18n.t('close')"
@click="close"
> >
<i class="ph-x ph-bold ph-lg"></i> <i class="ph-x ph-bold ph-lg"></i>
</button> </button>
@ -59,14 +59,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick } from "vue"; import { nextTick, ref } from "vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import { host } from "@/config"; import { host } from "@/config";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import * as os from "@/os"; import * as os from "@/os";
import { instance } from "@/instance"; import { instance } from "@/instance";
let show = ref(false); const show = ref(false);
const emit = defineEmits<{ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;

View File

@ -59,7 +59,6 @@ const emit = defineEmits<{
const modal = ref<InstanceType<typeof MkModal>>(); const modal = ref<InstanceType<typeof MkModal>>();
const picker = ref<InstanceType<typeof MkEmojiPicker>>(); const picker = ref<InstanceType<typeof MkEmojiPicker>>();
function checkForShift(ev?: MouseEvent) { function checkForShift(ev?: MouseEvent) {
if (ev?.shiftKey) return; if (ev?.shiftKey) return;
modal.value?.close(ev); modal.value?.close(ev);

View File

@ -113,7 +113,9 @@ const url =
: props.media.thumbnailUrl; : props.media.thumbnailUrl;
const mediaType = computed(() => { const mediaType = computed(() => {
return props.media.type === 'video/quicktime' ? 'video/mp4' : props.media.type; return props.media.type === "video/quicktime"
? "video/mp4"
: props.media.type;
}); });
function captionPopup() { function captionPopup() {

View File

@ -33,11 +33,7 @@
detailedView detailedView
></MkNote> ></MkNote>
<MkTab <MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
v-model="tab"
:style="'underline'"
@update:modelValue="loadTab"
>
<option value="replies"> <option value="replies">
<!-- <i class="ph-arrow-u-up-left ph-bold ph-lg"></i> --> <!-- <i class="ph-arrow-u-up-left ph-bold ph-lg"></i> -->
<span v-if="note.repliesCount > 0" class="count">{{ <span v-if="note.repliesCount > 0" class="count">{{

View File

@ -1,5 +1,9 @@
<template> <template>
<div ref="reactionsEl" class="reactions-list swiper-no-swiping tdflqwzn" :class="{ isMe }"> <div
ref="reactionsEl"
class="reactions-list swiper-no-swiping tdflqwzn"
:class="{ isMe }"
>
<XReaction <XReaction
v-for="(count, reaction) in note.reactions" v-for="(count, reaction) in note.reactions"
:key="reaction" :key="reaction"

View File

@ -47,13 +47,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
computed,
nextTick,
onMounted, onMounted,
onUnmounted, onUnmounted,
nextTick,
ref, ref,
watch,
computed,
toRefs, toRefs,
watch,
} from "vue"; } from "vue";
import { debounce } from "throttle-debounce"; import { debounce } from "throttle-debounce";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";

View File

@ -4,7 +4,7 @@
type="radio" type="radio"
:disabled="disabled" :disabled="disabled"
:checked="checked" :checked="checked"
v-on:change="(x) => toggle(x)" @change="(x) => toggle(x)"
/> />
<span class="button"> <span class="button">
<span></span> <span></span>
@ -26,7 +26,7 @@ const emit = defineEmits<{
(ev: "update:modelValue", value: any): void; (ev: "update:modelValue", value: any): void;
}>(); }>();
let checked = $computed(() => props.modelValue === props.value); const checked = $computed(() => props.modelValue === props.value);
function toggle(x) { function toggle(x) {
if (props.disabled) return; if (props.disabled) return;

View File

@ -12,7 +12,7 @@
:list="id" :list="id"
:value="modelValue" :value="modelValue"
:disabled="disabled" :disabled="disabled"
v-on:change="(x) => onChange(x)" @change="(x) => onChange(x)"
@focus="tooltipShow" @focus="tooltipShow"
@blur="tooltipHide" @blur="tooltipHide"
@touchstart="tooltipShow" @touchstart="tooltipShow"
@ -35,7 +35,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, defineAsyncComponent } from "vue"; import { computed, defineAsyncComponent, ref } from "vue";
import * as os from "@/os"; import * as os from "@/os";
const id = os.getUniqueId(); const id = os.getUniqueId();
@ -59,7 +59,7 @@ const props = withDefaults(
); );
const inputEl = ref<HTMLElement>(); const inputEl = ref<HTMLElement>();
let inputVal = $ref(props.modelValue); const inputVal = $ref(props.modelValue);
const emit = defineEmits<{ const emit = defineEmits<{
(ev: "update:modelValue", value: number): void; (ev: "update:modelValue", value: number): void;

View File

@ -43,15 +43,15 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { VNode } from "vue";
import { import {
onMounted,
nextTick,
ref,
watch,
computed, computed,
nextTick,
onMounted,
ref,
toRefs, toRefs,
VNode,
useSlots, useSlots,
watch,
} from "vue"; } from "vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import * as os from "@/os"; import * as os from "@/os";
@ -151,7 +151,7 @@ function show(ev: MouseEvent) {
opening.value = true; opening.value = true;
const menu = []; const menu = [];
let options = slots.default!(); const options = slots.default!();
const pushOption = (option: VNode) => { const pushOption = (option: VNode) => {
menu.push({ menu.push({

View File

@ -22,7 +22,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, ref, watch } from "vue"; import type { PropType } from "vue";
import { defineComponent, ref, watch } from "vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";

View File

@ -4,7 +4,7 @@
type="checkbox" type="checkbox"
:checked="modelValue" :checked="modelValue"
:disabled="disabled" :disabled="disabled"
v-on:change="(x) => toggle(x)" @change="(x) => toggle(x)"
/> />
<div class="button"> <div class="button">
<div class="knob"></div> <div class="knob"></div>
@ -18,7 +18,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Ref } from "vue"; import type { Ref } from "vue";
const props = defineProps<{ const props = defineProps<{
modelValue: boolean | Ref<boolean>; modelValue: boolean | Ref<boolean>;

View File

@ -39,14 +39,14 @@
<script lang="ts"> <script lang="ts">
import { import {
computed,
defineComponent, defineComponent,
nextTick,
onMounted, onMounted,
onUnmounted, onUnmounted,
nextTick,
ref, ref,
watch,
computed,
toRefs, toRefs,
watch,
} from "vue"; } from "vue";
import { debounce } from "throttle-debounce"; import { debounce } from "throttle-debounce";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";

View File

@ -10,7 +10,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
import { toUnicode } from "punycode/"; import { toUnicode } from "punycode/";
import { host as hostRaw } from "@/config"; import { host as hostRaw } from "@/config";

View File

@ -1,7 +1,7 @@
<template> <template>
<div <div
v-if="chosen && chosen.length > 0 && defaultStore.state.showAds"
v-for="chosenItem in chosen" v-for="chosenItem in chosen"
v-if="chosen && chosen.length > 0 && defaultStore.state.showAds"
class="qiivuoyo" class="qiivuoyo"
> >
<div v-if="!showMenu" class="main" :class="chosenItem.place"> <div v-if="!showMenu" class="main" :class="chosenItem.place">

View File

@ -37,7 +37,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, watch } from "vue"; import { onMounted, watch } from "vue";
import * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
import { getStaticImageUrl } from "@/scripts/get-static-image-url"; import { getStaticImageUrl } from "@/scripts/get-static-image-url";
import { extractAvgColorFromBlurhash } from "@/scripts/extract-avg-color-from-blurhash"; import { extractAvgColorFromBlurhash } from "@/scripts/extract-avg-color-from-blurhash";
import { acct, userPage } from "@/filters/user"; import { acct, userPage } from "@/filters/user";

View File

@ -22,7 +22,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { CustomEmoji } from "calckey-js/built/entities"; import type { CustomEmoji } from "calckey-js/built/entities";
import { getStaticImageUrl } from "@/scripts/get-static-image-url"; import { getStaticImageUrl } from "@/scripts/get-static-image-url";
import { char2filePath } from "@/scripts/twemoji-base"; import { char2filePath } from "@/scripts/twemoji-base";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";

View File

@ -4,8 +4,8 @@
:plain="plain" :plain="plain"
:nowrap="nowrap" :nowrap="nowrap"
:author="author" :author="author"
:customEmojis="customEmojis" :custom-emojis="customEmojis"
:isNote="isNote" :is-note="isNote"
class="mfm-object" class="mfm-object"
:class="{ :class="{
nowrap, nowrap,

View File

@ -11,10 +11,10 @@
<div class="buttons"> <div class="buttons">
<button <button
v-if="displayBackButton" v-if="displayBackButton"
v-tooltip.noDelay="i18n.ts.goBack"
class="_buttonIcon button icon backButton" class="_buttonIcon button icon backButton"
@click.stop="goBack()" @click.stop="goBack()"
@touchstart="preventDrag" @touchstart="preventDrag"
v-tooltip.noDelay="i18n.ts.goBack"
> >
<i class="ph-caret-left ph-bold ph-lg"></i> <i class="ph-caret-left ph-bold ph-lg"></i>
</button> </button>
@ -23,7 +23,7 @@
class="avatar button" class="avatar button"
:user="$i" :user="$i"
:disable-preview="true" :disable-preview="true"
disableLink disable-link
@click.stop="openAccountMenu" @click.stop="openAccountMenu"
/> />
</div> </div>
@ -68,8 +68,8 @@
</div> </div>
<template v-if="metadata"> <template v-if="metadata">
<nav <nav
ref="tabsEl"
v-if="hasTabs" v-if="hasTabs"
ref="tabsEl"
class="tabs" class="tabs"
:class="{ collapse: hasTabs && tabs.length > 3 }" :class="{ collapse: hasTabs && tabs.length > 3 }"
> >
@ -123,14 +123,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
computed, computed,
inject,
nextTick,
onMounted, onMounted,
onUnmounted, onUnmounted,
ref,
inject,
watch,
shallowReactive,
nextTick,
reactive, reactive,
ref,
shallowReactive,
watch,
} from "vue"; } from "vue";
import MkFollowButton from "@/components/MkFollowButton.vue"; import MkFollowButton from "@/components/MkFollowButton.vue";
import { popupMenu } from "@/os"; import { popupMenu } from "@/os";
@ -140,13 +140,13 @@ import { injectPageMetadata } from "@/scripts/page-metadata";
import { $i, openAccountMenu as openAccountMenu_ } from "@/account"; import { $i, openAccountMenu as openAccountMenu_ } from "@/account";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
type Tab = { interface Tab {
key?: string | null; key?: string | null;
title: string; title: string;
icon?: string; icon?: string;
iconOnly?: boolean; iconOnly?: boolean;
onClick?: (ev: MouseEvent) => void; onClick?: (ev: MouseEvent) => void;
}; }
const props = defineProps<{ const props = defineProps<{
tabs?: Tab[]; tabs?: Tab[];

View File

@ -24,10 +24,10 @@ const props = withDefaults(
}, },
); );
let ro: ResizeObserver; let ro: ResizeObserver,
let root = $ref<HTMLElement>(); root = $ref<HTMLElement>(),
let content = $ref<HTMLElement>(); content = $ref<HTMLElement>(),
let margin = $ref(0); margin = $ref(0);
const shouldSpacerMin = inject("shouldSpacerMin", false); const shouldSpacerMin = inject("shouldSpacerMin", false);
const adjust = (rect: { width: number; height: number }) => { const adjust = (rect: { width: number; height: number }) => {

View File

@ -16,14 +16,15 @@ const CURRENT_STICKY_TOP = "CURRENT_STICKY_TOP";
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, provide, inject, Ref, ref, watch } from "vue"; import type { Ref } from "vue";
import { inject, onMounted, onUnmounted, provide, ref, watch } from "vue";
const rootEl = $ref<HTMLElement>(); const rootEl = $ref<HTMLElement>();
const headerEl = $ref<HTMLElement>(); const headerEl = $ref<HTMLElement>();
const bodyEl = $ref<HTMLElement>(); const bodyEl = $ref<HTMLElement>();
let headerHeight = $ref<string | undefined>(); let headerHeight = $ref<string | undefined>(),
let childStickyTop = $ref(0); childStickyTop = $ref(0);
const parentStickyTop = inject<Ref<number>>(CURRENT_STICKY_TOP, ref(0)); const parentStickyTop = inject<Ref<number>>(CURRENT_STICKY_TOP, ref(0));
provide(CURRENT_STICKY_TOP, $$(childStickyTop)); provide(CURRENT_STICKY_TOP, $$(childStickyTop));

View File

@ -10,7 +10,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue"; import {} from "vue";
import * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{

View File

@ -5,8 +5,8 @@
:is="currentPageComponent" :is="currentPageComponent"
:key="key" :key="key"
v-bind="Object.fromEntries(currentPageProps)" v-bind="Object.fromEntries(currentPageProps)"
tabindex="-1"
v-focus v-focus
tabindex="-1"
style="outline: none" style="outline: none"
/> />
@ -27,7 +27,7 @@ import {
provide, provide,
watch, watch,
} from "vue"; } from "vue";
import { Resolved, Router } from "@/nirax"; import type { Resolved, Router } from "@/nirax";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
const props = defineProps<{ const props = defineProps<{
@ -56,9 +56,9 @@ function resolveNested(current: Resolved, d = 0): Resolved | null {
} }
const current = resolveNested(router.current)!; const current = resolveNested(router.current)!;
let currentPageComponent = $shallowRef(current.route.component); let currentPageComponent = $shallowRef(current.route.component),
let currentPageProps = $ref(current.props); currentPageProps = $ref(current.props),
let key = $ref( key = $ref(
current.route.path + JSON.stringify(Object.fromEntries(current.props)), current.route.path + JSON.stringify(Object.fromEntries(current.props)),
); );

View File

@ -126,7 +126,11 @@
</div> </div>
</FormFolder> </FormFolder>
<template #caption>{{ <template #caption>{{
i18n.ts._profile.metadataDescription i18n.t("_profile.metadataDescription", {
a: '<code><a></code>',
l: '<code><a></code>',
rel: `rel="me" href="https://${host}/@${$i.username}"`
})
}}</template> }}</template>
</FormSlot> </FormSlot>
@ -173,6 +177,7 @@ import { i18n } from "@/i18n";
import { $i } from "@/account"; import { $i } from "@/account";
import { langmap } from "@/scripts/langmap"; import { langmap } from "@/scripts/langmap";
import { definePageMetadata } from "@/scripts/page-metadata"; import { definePageMetadata } from "@/scripts/page-metadata";
import { host } from "@/config";
const profile = reactive({ const profile = reactive({
name: $i?.name, name: $i?.name,

View File

@ -288,10 +288,17 @@
<div v-if="user.fields.length > 0" class="fields"> <div v-if="user.fields.length > 0" class="fields">
<dl <dl
v-for="(field, i) in user.fields" v-for="(field, i) in user.fields"
:class="field.verified ? 'verified' : ''"
:key="i" :key="i"
class="field" class="field"
> >
<dt class="name"> <dt class="name">
<i
v-if="field.verified"
class="ph-bold ph-seal-check ph-lg ph-fw"
style="padding: 5px"
v-tooltip="i18n.ts.verifiedLink"
></i>
<Mfm <Mfm
:text="field.name" :text="field.name"
:plain="true" :plain="true"
@ -748,6 +755,12 @@ onUnmounted(() => {
margin-bottom: 8px; margin-bottom: 8px;
} }
&.verified {
background-color: var(--hover);
border-radius: 10px;
color: var(--badge) !important;
}
> .name { > .name {
width: 30%; width: 30%;
overflow: hidden; overflow: hidden;

View File

@ -267,8 +267,8 @@ export function getUserMenu(user, router: Router = mainRouter) {
text: i18n.ts.showOnRemote, text: i18n.ts.showOnRemote,
href: user.url, href: user.url,
target: "_blank", target: "_blank",
}
} : undefined, : undefined,
null, null,
{ {
icon: "ph-list-bullets ph-bold ph-lg", icon: "ph-list-bullets ph-bold ph-lg",

View File

@ -193,12 +193,12 @@ import {
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import XCommon from "./_common_/common.vue"; import XCommon from "./_common_/common.vue";
import { import {
deckStore,
addColumn as addColumnToStore, addColumn as addColumnToStore,
loadDeck, deckStore,
getProfiles,
renameProfile as renameProfile_,
deleteProfile as deleteProfile_, deleteProfile as deleteProfile_,
getProfiles,
loadDeck,
renameProfile as renameProfile_,
} from "./deck/deck-store"; } from "./deck/deck-store";
import DeckColumnCore from "@/ui/deck/column-core.vue"; import DeckColumnCore from "@/ui/deck/column-core.vue";
import XSidebar from "@/ui/_common_/navbar.vue"; import XSidebar from "@/ui/_common_/navbar.vue";
@ -253,7 +253,7 @@ function showSettings() {
os.pageWindow("/settings/deck"); os.pageWindow("/settings/deck");
} }
let columnsEl = $ref<HTMLElement>(); const columnsEl = $ref<HTMLElement>();
const addColumn = async (ev) => { const addColumn = async (ev) => {
const columns = [ const columns = [
@ -384,6 +384,27 @@ async function deleteProfile() {
} }
</script> </script>
<style>
html,
body {
width: 100%;
height: 100%;
overflow: clip;
position: fixed;
top: 0;
left: 0;
}
#calckey_app {
width: 100%;
height: 100%;
overflow: clip;
position: absolute;
top: 0;
left: 0;
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
.menu-enter-active, .menu-enter-active,
.menu-leave-active { .menu-leave-active {

View File

@ -23,7 +23,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from "vue"; import { onMounted } from "vue";
import XColumn from "./column.vue"; import XColumn from "./column.vue";
import { updateColumn, Column } from "./deck-store"; import type { Column } from "./deck-store";
import { updateColumn } from "./deck-store";
import XTimeline from "@/components/MkTimeline.vue"; import XTimeline from "@/components/MkTimeline.vue";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
@ -38,7 +39,7 @@ const emit = defineEmits<{
(ev: "parent-focus", direction: "up" | "down" | "left" | "right"): void; (ev: "parent-focus", direction: "up" | "down" | "left" | "right"): void;
}>(); }>();
let timeline = $ref<InstanceType<typeof XTimeline>>(); const timeline = $ref<InstanceType<typeof XTimeline>>();
onMounted(() => { onMounted(() => {
if (props.column.antennaId == null) { if (props.column.antennaId == null) {

View File

@ -23,7 +23,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue"; import {} from "vue";
import XColumn from "./column.vue"; import XColumn from "./column.vue";
import { updateColumn, Column } from "./deck-store"; import type { Column } from "./deck-store";
import { updateColumn } from "./deck-store";
import XTimeline from "@/components/MkTimeline.vue"; import XTimeline from "@/components/MkTimeline.vue";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
@ -38,7 +39,7 @@ const emit = defineEmits<{
(ev: "parent-focus", direction: "up" | "down" | "left" | "right"): void; (ev: "parent-focus", direction: "up" | "down" | "left" | "right"): void;
}>(); }>();
let timeline = $ref<InstanceType<typeof XTimeline>>(); const timeline = $ref<InstanceType<typeof XTimeline>>();
if (props.column.channelId == null) { if (props.column.channelId == null) {
setChannel(); setChannel();

View File

@ -68,7 +68,7 @@ import XWidgetsColumn from "./widgets-column.vue";
import XMentionsColumn from "./mentions-column.vue"; import XMentionsColumn from "./mentions-column.vue";
import XDirectColumn from "./direct-column.vue"; import XDirectColumn from "./direct-column.vue";
import XChannelColumn from "./channel-column.vue"; import XChannelColumn from "./channel-column.vue";
import { Column } from "./deck-store"; import type { Column } from "./deck-store";
defineProps<{ defineProps<{
column?: Column; column?: Column;

View File

@ -56,23 +56,23 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onBeforeUnmount, onMounted, provide, Ref, watch } from "vue"; import { Ref, onBeforeUnmount, onMounted, provide, watch } from "vue";
import type { Column } from "./deck-store";
import { import {
updateColumn, deckStore,
popRightColumn,
removeColumn,
stackLeftColumn,
swapColumn,
swapDownColumn,
swapLeftColumn, swapLeftColumn,
swapRightColumn, swapRightColumn,
swapUpColumn, swapUpColumn,
swapDownColumn, updateColumn,
stackLeftColumn,
popRightColumn,
removeColumn,
swapColumn,
Column,
deckStore,
} from "./deck-store"; } from "./deck-store";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { MenuItem } from "@/types/menu"; import type { MenuItem } from "@/types/menu";
provide("shouldHeaderThin", true); provide("shouldHeaderThin", true);
provide("shouldOmitHeaderTitle", true); provide("shouldOmitHeaderTitle", true);
@ -99,15 +99,15 @@ const emit = defineEmits<{
(ev: "headerWheel", ctx: WheelEvent): void; (ev: "headerWheel", ctx: WheelEvent): void;
}>(); }>();
let body = $ref<HTMLDivElement>(); const body = $ref<HTMLDivElement>();
let dragging = $ref(false); let dragging = $ref(false);
watch($$(dragging), (v) => watch($$(dragging), (v) =>
os.deckGlobalEvents.emit(v ? "column.dragStart" : "column.dragEnd"), os.deckGlobalEvents.emit(v ? "column.dragStart" : "column.dragEnd"),
); );
let draghover = $ref(false); let draghover = $ref(false),
let dropready = $ref(false); dropready = $ref(false);
const isMainColumn = $computed(() => props.column.type === "main"); const isMainColumn = $computed(() => props.column.type === "main");
const active = $computed(() => props.column.active !== false); const active = $computed(() => props.column.active !== false);

View File

@ -20,6 +20,7 @@ export type Column = {
| "notifications" | "notifications"
| "tl" | "tl"
| "antenna" | "antenna"
| "channel"
| "list" | "list"
| "mentions" | "mentions"
| "direct"; | "direct";
@ -29,9 +30,10 @@ export type Column = {
active?: boolean; active?: boolean;
flexible?: boolean; flexible?: boolean;
antennaId?: string; antennaId?: string;
channelId?: string;
listId?: string; listId?: string;
includingTypes?: typeof notificationTypes[number][]; includingTypes?: typeof notificationTypes[number][];
tl?: "home" | "local" | "social" | "global"; tl?: "home" | "local" | "social" | "recommended" | "global";
}; };
export const deckStore = markRaw( export const deckStore = markRaw(

View File

@ -19,8 +19,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue"; import {} from "vue";
import XColumn from "./column.vue"; import XColumn from "./column.vue";
import type { Column } from "./deck-store";
import XNotes from "@/components/MkNotes.vue"; import XNotes from "@/components/MkNotes.vue";
import { Column } from "./deck-store";
defineProps<{ defineProps<{
column: Column; column: Column;

View File

@ -23,7 +23,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue"; import {} from "vue";
import XColumn from "./column.vue"; import XColumn from "./column.vue";
import { updateColumn, Column } from "./deck-store"; import type { Column } from "./deck-store";
import { updateColumn } from "./deck-store";
import XTimeline from "@/components/MkTimeline.vue"; import XTimeline from "@/components/MkTimeline.vue";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
@ -38,7 +39,7 @@ const emit = defineEmits<{
(ev: "parent-focus", direction: "up" | "down" | "left" | "right"): void; (ev: "parent-focus", direction: "up" | "down" | "left" | "right"): void;
}>(); }>();
let timeline = $ref<InstanceType<typeof XTimeline>>(); const timeline = $ref<InstanceType<typeof XTimeline>>();
if (props.column.listId == null) { if (props.column.listId == null) {
setList(); setList();

View File

@ -20,14 +20,16 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ComputedRef, provide } from "vue"; import type { ComputedRef } from "vue";
import { provide } from "vue";
import XColumn from "./column.vue"; import XColumn from "./column.vue";
import { deckStore, Column } from "@/ui/deck/deck-store"; import type { Column } from "@/ui/deck/deck-store";
import { deckStore } from "@/ui/deck/deck-store";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { mainRouter } from "@/router"; import { mainRouter } from "@/router";
import type { PageMetadata } from "@/scripts/page-metadata";
import { import {
PageMetadata,
provideMetadataReceiver, provideMetadataReceiver,
setPageMetadata, setPageMetadata,
} from "@/scripts/page-metadata"; } from "@/scripts/page-metadata";
@ -67,7 +69,7 @@ function onContextmenu(ev: MouseEvent) {
["INPUT", "TEXTAREA", "IMG", "VIDEO", "CANVAS"].includes( ["INPUT", "TEXTAREA", "IMG", "VIDEO", "CANVAS"].includes(
(ev.target as HTMLElement).tagName, (ev.target as HTMLElement).tagName,
) || ) ||
(ev.target as HTMLElement).attributes["contenteditable"] (ev.target as HTMLElement).attributes.contenteditable
) )
return; return;
if (window.getSelection()?.toString() !== "") return; if (window.getSelection()?.toString() !== "") return;

View File

@ -16,8 +16,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue"; import {} from "vue";
import XColumn from "./column.vue"; import XColumn from "./column.vue";
import type { Column } from "./deck-store";
import XNotes from "@/components/MkNotes.vue"; import XNotes from "@/components/MkNotes.vue";
import { Column } from "./deck-store";
defineProps<{ defineProps<{
column: Column; column: Column;

View File

@ -44,7 +44,7 @@ function func(): void {
done: async (res) => { done: async (res) => {
const { includingTypes } = res; const { includingTypes } = res;
updateColumn(props.column.id, { updateColumn(props.column.id, {
includingTypes: includingTypes, includingTypes,
}); });
}, },
}, },

View File

@ -46,7 +46,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from "vue"; import { onMounted } from "vue";
import XColumn from "./column.vue"; import XColumn from "./column.vue";
import { removeColumn, updateColumn, Column } from "./deck-store"; import type { Column } from "./deck-store";
import { removeColumn, updateColumn } from "./deck-store";
import XTimeline from "@/components/MkTimeline.vue"; import XTimeline from "@/components/MkTimeline.vue";
import * as os from "@/os"; import * as os from "@/os";
import { $i } from "@/account"; import { $i } from "@/account";
@ -63,9 +64,9 @@ const emit = defineEmits<{
(ev: "parent-focus", direction: "up" | "down" | "left" | "right"): void; (ev: "parent-focus", direction: "up" | "down" | "left" | "right"): void;
}>(); }>();
let disabled = $ref(false); let disabled = $ref(false),
let indicated = $ref(false); indicated = $ref(false),
let columnActive = $ref(true); columnActive = $ref(true);
onMounted(() => { onMounted(() => {
if (props.column.tl == null) { if (props.column.tl == null) {

View File

@ -34,9 +34,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue"; import {} from "vue";
import XColumn from "./column.vue"; import XColumn from "./column.vue";
import type { Column } from "./deck-store";
import { import {
addColumnWidget, addColumnWidget,
Column,
removeColumnWidget, removeColumnWidget,
setColumnWidgets, setColumnWidgets,
updateColumnWidget, updateColumnWidget,

View File

@ -169,10 +169,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, provide, onMounted, computed, ref } from "vue"; import { computed, defineAsyncComponent, onMounted, provide, ref } from "vue";
import XCommon from "./_common_/common.vue";
import * as Acct from "calckey-js/built/acct"; import * as Acct from "calckey-js/built/acct";
import type { ComputedRef } from "vue"; import type { ComputedRef } from "vue";
import XCommon from "./_common_/common.vue";
import type { PageMetadata } from "@/scripts/page-metadata"; import type { PageMetadata } from "@/scripts/page-metadata";
import { instanceName, ui } from "@/config"; import { instanceName, ui } from "@/config";
import XDrawerMenu from "@/ui/_common_/navbar-for-mobile.vue"; import XDrawerMenu from "@/ui/_common_/navbar-for-mobile.vue";
@ -232,7 +232,7 @@ const menuIndicated = computed(() => {
}); });
function updateButtonState(): void { function updateButtonState(): void {
let routerState = window.location.pathname; const routerState = window.location.pathname;
if (routerState === "/") { if (routerState === "/") {
buttonAnimIndex.value = 0; buttonAnimIndex.value = 0;
return; return;
@ -246,7 +246,6 @@ function updateButtonState(): void {
return; return;
} }
buttonAnimIndex.value = 3; buttonAnimIndex.value = 3;
return;
} }
updateButtonState(); updateButtonState();
@ -358,7 +357,7 @@ const onContextmenu = (ev: MouseEvent) => {
["INPUT", "TEXTAREA", "IMG", "VIDEO", "CANVAS"].includes( ["INPUT", "TEXTAREA", "IMG", "VIDEO", "CANVAS"].includes(
ev.target.tagName, ev.target.tagName,
) || ) ||
ev.target.attributes["contenteditable"] ev.target.attributes.contenteditable
) )
return; return;
if (window.getSelection()?.toString() !== "") return; if (window.getSelection()?.toString() !== "") return;
@ -411,6 +410,27 @@ const wallpaper = localStorage.getItem("wallpaper") != null;
console.log(mainRouter.currentRoute.value.name); console.log(mainRouter.currentRoute.value.name);
</script> </script>
<style>
html,
body {
width: 100%;
height: 100%;
overflow: clip;
position: fixed;
top: 0;
left: 0;
}
#calckey_app {
width: 100%;
height: 100%;
overflow: clip;
position: absolute;
top: 0;
left: 0;
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
.widgetsDrawer-enter-active, .widgetsDrawer-enter-active,
.widgetsDrawer-leave-active { .widgetsDrawer-leave-active {

View File

@ -39,8 +39,8 @@ const emit = defineEmits<{
(ev: "mounted", el: Element): void; (ev: "mounted", el: Element): void;
}>(); }>();
let editMode = $ref(false); const editMode = $ref(false);
let rootEl = $ref<HTMLDivElement>(); const rootEl = $ref<HTMLDivElement>();
onMounted(() => { onMounted(() => {
emit("mounted", rootEl); emit("mounted", rootEl);

View File

@ -4,7 +4,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue"; import { defineAsyncComponent, defineComponent } from "vue";
import DesignA from "./visitor/a.vue"; import DesignA from "./visitor/a.vue";
import DesignB from "./visitor/b.vue"; import DesignB from "./visitor/b.vue";
import XCommon from "./_common_/common.vue"; import XCommon from "./_common_/common.vue";

View File

@ -74,7 +74,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue"; import { defineAsyncComponent, defineComponent } from "vue";
import XHeader from "./header.vue"; import XHeader from "./header.vue";
import { host, instanceName } from "@/config"; import { host, instanceName } from "@/config";
import { search } from "@/scripts/search"; import { search } from "@/scripts/search";

View File

@ -71,7 +71,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ComputedRef, onMounted, provide } from "vue"; import type { ComputedRef } from "vue";
import { onMounted, provide } from "vue";
import XHeader from "./header.vue"; import XHeader from "./header.vue";
import XKanban from "./kanban.vue"; import XKanban from "./kanban.vue";
import { host, instanceName } from "@/config"; import { host, instanceName } from "@/config";
@ -84,8 +85,8 @@ import XSignupDialog from "@/components/MkSignupDialog.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import { ColdDeviceStorage, defaultStore } from "@/store"; import { ColdDeviceStorage, defaultStore } from "@/store";
import { mainRouter } from "@/router"; import { mainRouter } from "@/router";
import type { PageMetadata } from "@/scripts/page-metadata";
import { import {
PageMetadata,
provideMetadataReceiver, provideMetadataReceiver,
setPageMetadata, setPageMetadata,
} from "@/scripts/page-metadata"; } from "@/scripts/page-metadata";
@ -111,10 +112,10 @@ const isTimelineAvailable =
!instance.disableLocalTimeline || !instance.disableLocalTimeline ||
!instance.disableRecommendedTimeline || !instance.disableRecommendedTimeline ||
!instance.disableGlobalTimeline; !instance.disableGlobalTimeline;
let showMenu = $ref(false); const showMenu = $ref(false);
let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD); let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD),
let narrow = $ref(window.innerWidth < 1280); narrow = $ref(window.innerWidth < 1280),
let meta = $ref(); meta = $ref();
const keymap = $computed(() => { const keymap = $computed(() => {
return { return {

Some files were not shown because too many files have changed in this diff Show More