Merge branch 'develop' of https://codeberg.org/calckey/calckey into upstream
This commit is contained in:
commit
23b7c3c1b0
|
@ -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
|
||||||
|
|
||||||
|
|
10
CALCKEY.md
10
CALCKEY.md
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -1179,7 +1179,6 @@ _profile:
|
||||||
youCanIncludeHashtags: "يمكنك أيضًا إضافة وسوم إلى سيرتك التعريفية."
|
youCanIncludeHashtags: "يمكنك أيضًا إضافة وسوم إلى سيرتك التعريفية."
|
||||||
metadata: "معلومات إضافية"
|
metadata: "معلومات إضافية"
|
||||||
metadataEdit: "عدّل المعلومات الإضافية"
|
metadataEdit: "عدّل المعلومات الإضافية"
|
||||||
metadataDescription: "يُمكنك عرض 4 حقول معلومات في ملفك الشخصي"
|
|
||||||
metadataLabel: "التسمية"
|
metadataLabel: "التسمية"
|
||||||
metadataContent: "المحتوى"
|
metadataContent: "المحتوى"
|
||||||
changeAvatar: "غيّر الصورة الرمزية"
|
changeAvatar: "غيّر الصورة الرمزية"
|
||||||
|
|
|
@ -1268,7 +1268,7 @@ _profile:
|
||||||
youCanIncludeHashtags: "হ্যাশট্যাগ অন্তর্ভুক্ত করা যেতে পারে।"
|
youCanIncludeHashtags: "হ্যাশট্যাগ অন্তর্ভুক্ত করা যেতে পারে।"
|
||||||
metadata: "অতিরিক্ত তথ্য"
|
metadata: "অতিরিক্ত তথ্য"
|
||||||
metadataEdit: "অতিরিক্ত তথ্য সম্পাদনা করুন"
|
metadataEdit: "অতিরিক্ত তথ্য সম্পাদনা করুন"
|
||||||
metadataDescription: "আপনি আপনার প্রোফাইলে একটি টেবিল হিসাবে চারটি অতিরিক্ত তথ্য দেখাতে পারেন।"
|
metadataDescription: "আপনি আপনার প্রোফাইলে একটি টেবিল হিসাবে চারটি অতিরিক্ত তথ্য দেখাতে পারেন।. আপনি আপনার প্রোফাইলে লিঙ্কটি যাচাই করতে {rel} এর সাথে একটি {a} ট্যাগ বা {l} ট্যাগ যোগ করতে পারেন!"
|
||||||
metadataLabel: "লেবেল"
|
metadataLabel: "লেবেল"
|
||||||
metadataContent: "বিষয়বস্তু"
|
metadataContent: "বিষয়বস্তু"
|
||||||
changeAvatar: "অ্যাভাটার পরিবর্তন করুন"
|
changeAvatar: "অ্যাভাটার পরিবর্তন করুন"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -1491,7 +1491,7 @@ _profile:
|
||||||
youCanIncludeHashtags: "ハッシュタグを含められます。"
|
youCanIncludeHashtags: "ハッシュタグを含められます。"
|
||||||
metadata: "追加情報"
|
metadata: "追加情報"
|
||||||
metadataEdit: "追加情報を編集"
|
metadataEdit: "追加情報を編集"
|
||||||
metadataDescription: "プロフィールに表として追加情報を表示できます。"
|
metadataDescription: "プロフィールに表として追加情報を表示できます。{a}タグまたは{l}タグを{rel}とともに追加すると、プロフィールのリンクを確認できます。"
|
||||||
metadataLabel: "ラベル"
|
metadataLabel: "ラベル"
|
||||||
metadataContent: "内容"
|
metadataContent: "内容"
|
||||||
changeAvatar: "アバター画像を変更"
|
changeAvatar: "アバター画像を変更"
|
||||||
|
|
|
@ -1319,7 +1319,7 @@ _profile:
|
||||||
youCanIncludeHashtags: "해시 태그를 포함할 수 있습니다."
|
youCanIncludeHashtags: "해시 태그를 포함할 수 있습니다."
|
||||||
metadata: "추가 정보"
|
metadata: "추가 정보"
|
||||||
metadataEdit: "추가 정보 편집"
|
metadataEdit: "추가 정보 편집"
|
||||||
metadataDescription: "프로필에 추가 정보를 표시할 수 있어요"
|
metadataDescription: "프로필에 추가 정보를 표시할 수 있어요. {rel}과 함께 {a} 태그 또는 {l} 태그를 추가하여 프로필의 링크를 확인할 수 있습니다!"
|
||||||
metadataLabel: "라벨"
|
metadataLabel: "라벨"
|
||||||
metadataContent: "내용"
|
metadataContent: "내용"
|
||||||
changeAvatar: "아바타 이미지 변경"
|
changeAvatar: "아바타 이미지 변경"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -1398,7 +1398,7 @@ _profile:
|
||||||
youCanIncludeHashtags: "Можете использовать здесь хэштеги."
|
youCanIncludeHashtags: "Можете использовать здесь хэштеги."
|
||||||
metadata: "Дополнительные сведения"
|
metadata: "Дополнительные сведения"
|
||||||
metadataEdit: "Редактировать дополнительные сведения"
|
metadataEdit: "Редактировать дополнительные сведения"
|
||||||
metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль."
|
metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль. Вы можете добавить тег {a} или тег {l} с {rel}, чтобы подтвердить ссылку в своем профиле!"
|
||||||
metadataLabel: "Метка"
|
metadataLabel: "Метка"
|
||||||
metadataContent: "Содержимое"
|
metadataContent: "Содержимое"
|
||||||
changeAvatar: "Поменять аватар"
|
changeAvatar: "Поменять аватар"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: Перевірене посилання
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -1402,7 +1402,7 @@ _profile:
|
||||||
youCanIncludeHashtags: "您可以包含一个话题标签。"
|
youCanIncludeHashtags: "您可以包含一个话题标签。"
|
||||||
metadata: "附加信息"
|
metadata: "附加信息"
|
||||||
metadataEdit: "附加信息编辑"
|
metadataEdit: "附加信息编辑"
|
||||||
metadataDescription: "使用这些,您可以在您的个人资料中显示其它信息字段。"
|
metadataDescription: "使用这些,您可以在您的个人资料中显示其它信息字段。您可以添加带有 {rel} 的 {a} 标签或 {l} 标签来验证您个人资料上的链接!"
|
||||||
metadataLabel: "标签"
|
metadataLabel: "标签"
|
||||||
metadataContent: "内容"
|
metadataContent: "内容"
|
||||||
changeAvatar: "修改头像"
|
changeAvatar: "修改头像"
|
||||||
|
|
|
@ -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: 此帳戶是機器人
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
|
@ -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) {}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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! {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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", {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>>
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"],
|
||||||
|
"plugins": ["file-progress", "prettier"],
|
||||||
|
"rules": {
|
||||||
|
"file-progress/activate": 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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<{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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">{{
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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[];
|
||||||
|
|
|
@ -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 }) => {
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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<{
|
||||||
|
|
|
@ -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)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue