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

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

3
.gitignore vendored
View file

@ -27,7 +27,7 @@ coverage
!/.config/helm_values_example.yml
!/.config/LICENSE
#docker dev config
# docker dev config
/dev/docker-compose.yml
# misskey
@ -46,6 +46,7 @@ files
ormconfig.json
packages/backend/assets/instance.css
packages/backend/assets/sounds/None.mp3
packages/backend/assets/LICENSE
!packages/backend/src/db

View file

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

View file

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

View file

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

View file

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

View file

@ -1551,7 +1551,7 @@ _profile:
metadata: "Zusätzliche Informationen"
metadataEdit: "Zusätzliche Informationen bearbeiten"
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"
metadataContent: "Inhalt"
changeAvatar: "Profilbild ändern"

View file

@ -1124,6 +1124,7 @@ remindMeLater: "Maybe later"
removeQuote: "Remove quote"
removeRecipient: "Remove recipient"
removeMember: "Remove member"
verifiedLink: "Verified link"
_sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing
@ -1676,8 +1677,10 @@ _profile:
youCanIncludeHashtags: "You can also include hashtags in your bio."
metadata: "Additional Information"
metadataEdit: "Edit additional Information"
metadataDescription: "Using these, you can display additional information fields
in your profile."
metadataDescription:
"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"
metadataContent: "Content"
changeAvatar: "Change avatar"

View file

@ -1475,7 +1475,7 @@ _profile:
youCanIncludeHashtags: "Puedes añadir hashtags"
metadata: "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"
metadataContent: "Contenido"
changeAvatar: "Cambiar avatar"

View file

@ -1413,7 +1413,7 @@ _profile:
metadata: "Informations supplémentaires"
metadataEdit: "Éditer les 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"
metadataContent: "Contenu"
changeAvatar: "Changer l'image de profil"

View file

@ -1399,7 +1399,7 @@ _profile:
metadata: "Informasi tambahan"
metadataEdit: "Sunting 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"
metadataContent: "Isi"
changeAvatar: "Ubah avatar"

View file

@ -1266,7 +1266,7 @@ _profile:
metadata: "Informazioni aggiuntive"
metadataEdit: "Modifica informazioni aggiuntive"
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"
metadataContent: "Contenuto"
changeAvatar: "Modifica immagine profilo"

View file

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

View file

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

View file

@ -1404,7 +1404,7 @@ _profile:
metadata: "Dodatkowe informacje"
metadataEdit: "Edytuj dodatkowe informacje"
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"
metadataContent: "Treść"
changeAvatar: "Zmień awatar"

View file

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

View file

@ -1337,7 +1337,7 @@ _profile:
youCanIncludeHashtags: "Vo svojom bio môžete mať aj hashtagy."
metadata: "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"
metadataContent: "Obsah"
changeAvatar: "Zmeniť avatara"

View file

@ -182,7 +182,7 @@ _profile:
gösterecektir.
youCanIncludeHashtags: Hakkımdan'da etiket kullanabilirsin.
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
metadataContent: İçerik
metadataLabel: Etiket

View file

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

View file

@ -1342,7 +1342,7 @@ _profile:
youCanIncludeHashtags: "Bạn có thể dùng hashtag trong tiểu sử."
metadata: "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"
metadataContent: "Nội dung"
changeAvatar: "Đổi ảnh đại diện"

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,16 @@
export class tweakVarcharLength1678426061773 {
name = 'tweakVarcharLength1678426061773'
name = "tweakVarcharLength1678426061773";
async up(queryRunner) {
await queryRunner.query(`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 up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" ALTER COLUMN "smtpUser" TYPE character varying(1024)`,
undefined,
);
await queryRunner.query(
`ALTER TABLE "meta" ALTER COLUMN "smtpPass" TYPE character varying(1024)`,
undefined,
);
}
async down(queryRunner) {}
async down(queryRunner) {}
}

View file

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

View file

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

View file

@ -58,7 +58,7 @@ impl TryFrom<AntennaSrcEnum> for super::AntennaSrc {
// ---- TODO: could be macro
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! {

View file

@ -91,7 +91,7 @@ pub enum AppPermission {
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)]
mod unit_test {

View file

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

View file

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

View file

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

View file

@ -1,5 +1,8 @@
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(
config.maxNoteLength ?? 3000,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,9 @@ import type { UserProfile } from "@/models/entities/user-profile.js";
import { notificationTypes } from "@/types.js";
import { normalizeForSearch } from "@/misc/normalize-for-search.js";
import { langmap } from "@/misc/langmap.js";
import { verifyLink } from "@/services/fetch-rel-me.js";
import { ApiError } from "../../error.js";
import config from "@/config/index.js";
import define from "../../define.js";
export const meta = {
@ -58,6 +60,18 @@ export const meta = {
code: "INVALID_REGEXP",
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: {
@ -234,16 +248,29 @@ export default define(meta, paramDef, async (ps, _user, token) => {
}
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
.filter(
(x) =>
typeof x.name === "string" &&
x.name !== "" &&
typeof x.value === "string" &&
x.value !== "",
)
.filter((x) => Object.keys(x).length !== 0)
.map((x) => {
return { name: x.name, value: x.value };
return {
name: x.name,
value: x.value,
verified: x.verified,
};
});
}

View file

@ -1,4 +1,6 @@
import * as fs from "node:fs";
import net from "node:net";
import { promises } from "node:dns";
import type Koa from "koa";
import sharp from "sharp";
import type { IImage } from "@/services/drive/image-processor.js";
@ -19,6 +21,40 @@ export async function proxyMedia(ctx: Koa.Context) {
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
const [path, cleanup] = await createTemp();

View file

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

View file

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

View file

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

View file

@ -4,11 +4,14 @@
"scripts": {
"watch": "pnpm vite build --watch --mode development",
"build": "pnpm vite build",
"lint": "pnpm rome check \"src/**/*.{ts,vue}\"",
"format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}'"
"lint": "pnpm rome check **/*.ts --apply && pnpm run lint: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": {
"@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",
"@rollup/plugin-alias": "3.1.9",
"@rollup/plugin-json": "4.1.0",
@ -46,6 +49,8 @@
"date-fns": "2.30.0",
"emojilib": "github:thatonecalculator/emojilib",
"escape-regexp": "0.0.1",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-file-progress": "^1.3.0",
"eventemitter3": "5.0.1",
"fast-blurhash": "^1.1.2",
"focus-trap": "^7.5.2",
@ -57,6 +62,7 @@
"katex": "0.16.8",
"matter-js": "0.18.0",
"mfm-js": "0.23.3",
"paralint": "^1.2.1",
"photoswipe": "5.3.8",
"prettier": "3.0.0",
"prettier-plugin-vue": "1.1.6",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,11 +6,11 @@
</template>
<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 { i18n } from "@/i18n";
type Captcha = {
interface Captcha {
render(
container: string | Node,
options: {
@ -31,7 +31,7 @@ type Captcha = {
execute(id: string): void;
reset(id?: string): void;
getResponse(id: string): string;
};
}
type CaptchaProvider = "hcaptcha" | "recaptcha";
@ -105,7 +105,7 @@ function requestRender() {
captcha.value.render(captchaEl.value, {
sitekey: props.sitekey,
theme: defaultStore.state.darkMode ? "dark" : "light",
callback: callback,
callback,
"expired-callback": callback,
"error-callback": callback,
});

View file

@ -24,7 +24,8 @@
<script lang="ts" setup>
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";
const props = withDefaults(

View file

@ -8,30 +8,31 @@
</template>
<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 {
Chart,
ArcElement,
LineElement,
BarElement,
PointElement,
BarController,
LineController,
BarElement,
CategoryScale,
LinearScale,
TimeScale,
Chart,
Filler,
Legend,
LineController,
LineElement,
LinearScale,
PointElement,
SubTitle,
TimeScale,
Title,
Tooltip,
SubTitle,
Filler,
} from "chart.js";
import "chartjs-adapter-date-fns";
import { enUS } from "date-fns/locale";
import zoomPlugin from "chartjs-plugin-zoom";
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114242002
// We can't use gradient because Vite throws a error.
//import gradient from 'chartjs-plugin-gradient';
// import gradient from 'chartjs-plugin-gradient';
import * as os from "@/os";
import { defaultStore } from "@/store";
import { useChartTooltip } from "@/scripts/use-chart-tooltip";
@ -92,7 +93,7 @@ Chart.register(
SubTitle,
Filler,
zoomPlugin,
//gradient,
// gradient,
);
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
@ -127,20 +128,20 @@ const getColor = (i) => {
};
const now = new Date();
let chartInstance: Chart = null;
let chartData: {
series: {
name: string;
type: "line" | "area";
color?: string;
dashed?: boolean;
hidden?: boolean;
data: {
x: number;
y: number;
let chartInstance: Chart = null,
chartData: {
series: {
name: string;
type: "line" | "area";
color?: string;
dashed?: boolean;
hidden?: boolean;
data: {
x: number;
y: number;
}[];
}[];
}[];
} = null;
} = null;
const chartEl = ref<HTMLCanvasElement>(null);
const fetching = ref(true);
@ -210,7 +211,7 @@ const render = () => {
? x.color
: getColor(i)
: alpha(x.color ? x.color : getColor(i), 0.1),
/*gradient: props.bar ? undefined : {
/* gradient: props.bar ? undefined : {
backgroundColor: {
axis: 'y',
colors: {
@ -218,7 +219,7 @@ const render = () => {
[maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.2),
},
},
},*/
}, */
barPercentage: 0.9,
categoryPercentage: 0.9,
fill: x.type === "area",
@ -271,7 +272,7 @@ const render = () => {
},
ticks: {
display: props.detailed,
//mirror: true,
// mirror: true,
},
},
},
@ -331,7 +332,7 @@ const render = () => {
},
}
: undefined,
//gradient,
// gradient,
},
},
plugins: [

View file

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

View file

@ -12,9 +12,9 @@
</template>
<script lang="ts" setup>
import { onMounted, onBeforeUnmount } from "vue";
import { onBeforeUnmount, onMounted } from "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 * as os from "@/os";
@ -27,13 +27,13 @@ const emit = defineEmits<{
(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(() => {
let left = props.ev.pageX + 1; // + 1
let top = props.ev.pageY + 1; // + 1
let left = props.ev.pageX + 1, // + 1
top = props.ev.pageY + 1; // + 1
const width = rootEl.offsetWidth;
const height = rootEl.offsetHeight;
@ -57,15 +57,11 @@ onMounted(() => {
rootEl.style.top = `${top}px`;
rootEl.style.left = `${left}px`;
for (const el of Array.from(document.querySelectorAll("body *"))) {
el.addEventListener("mousedown", onMousedown);
}
document.body.addEventListener("mousedown", onMousedown);
});
onBeforeUnmount(() => {
for (const el of Array.from(document.querySelectorAll("body *"))) {
el.removeEventListener("mousedown", onMousedown);
}
document.body.removeEventListener("mousedown", onMousedown);
});
function onMousedown(evt: Event) {

View file

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

View file

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

View file

@ -1,5 +1,6 @@
<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 { i18n } from "@/i18n";
import { defaultStore } from "@/store";
@ -51,7 +52,7 @@ export default defineComponent({
if (!slots || !slots.default) return;
const el = slots.default({
item: item,
item,
})[0];
if (el.key == null && item.id) el.key = item.id;

View file

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

View file

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

View file

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

View file

@ -113,7 +113,9 @@ const url =
: props.media.thumbnailUrl;
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() {

View file

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

View file

@ -1,5 +1,9 @@
<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
v-for="(count, reaction) in note.reactions"
:key="reaction"

View file

@ -106,7 +106,7 @@ export default defineComponent({
);
padding-right: 90px !important;
white-space: nowrap;
&::-webkit-scrollbar {
display: none;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,8 @@
</template>
<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 { i18n } from "@/i18n";

View file

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

View file

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

View file

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

View file

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

View file

@ -37,7 +37,7 @@
<script lang="ts" setup>
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 { extractAvgColorFromBlurhash } from "@/scripts/extract-avg-color-from-blurhash";
import { acct, userPage } from "@/filters/user";

View file

@ -22,7 +22,7 @@
<script lang="ts" setup>
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 { char2filePath } from "@/scripts/twemoji-base";
import { defaultStore } from "@/store";

View file

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

View file

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

View file

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

View file

@ -11,19 +11,20 @@
<script lang="ts">
//
//const CURRENT_STICKY_TOP = Symbol('CURRENT_STICKY_TOP');
// const CURRENT_STICKY_TOP = Symbol('CURRENT_STICKY_TOP');
const CURRENT_STICKY_TOP = "CURRENT_STICKY_TOP";
</script>
<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 headerEl = $ref<HTMLElement>();
const bodyEl = $ref<HTMLElement>();
let headerHeight = $ref<string | undefined>();
let childStickyTop = $ref(0);
let headerHeight = $ref<string | undefined>(),
childStickyTop = $ref(0);
const parentStickyTop = inject<Ref<number>>(CURRENT_STICKY_TOP, ref(0));
provide(CURRENT_STICKY_TOP, $$(childStickyTop));

View file

@ -42,7 +42,7 @@ const relative = $computed<string>(() => {
if (props.mode === "absolute") return ""; // absoluterelative使
if (invalid) return i18n.ts._ago.invalid;
const ago = (now - _time) / 1000; /*ms*/
const ago = (now - _time) / 1000; /* ms */
return ago >= 31536000
? i18n.t("_ago.yearsAgo", { n: Math.round(ago / 31536000).toString() })
: ago >= 2592000
@ -66,11 +66,11 @@ let tickId: number;
function tick() {
const _now = new Date().getTime();
const agoPrev = (now - _time) / 1000; /*ms*/ // interval
const agoPrev = (now - _time) / 1000; /* ms */ // interval
now = _now;
const ago = (now - _time) / 1000; /*ms*/ // interval
const ago = (now - _time) / 1000; /* ms */ // interval
const prev = agoPrev < 60 ? 10000 : agoPrev < 3600 ? 60000 : 180000;
const next = ago < 60 ? 10000 : ago < 3600 ? 60000 : 180000;

View file

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

View file

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

View file

@ -126,7 +126,11 @@
</div>
</FormFolder>
<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>
</FormSlot>
@ -173,6 +177,7 @@ import { i18n } from "@/i18n";
import { $i } from "@/account";
import { langmap } from "@/scripts/langmap";
import { definePageMetadata } from "@/scripts/page-metadata";
import { host } from "@/config";
const profile = reactive({
name: $i?.name,

View file

@ -288,10 +288,17 @@
<div v-if="user.fields.length > 0" class="fields">
<dl
v-for="(field, i) in user.fields"
:class="field.verified ? 'verified' : ''"
:key="i"
class="field"
>
<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
:text="field.name"
:plain="true"
@ -748,6 +755,12 @@ onUnmounted(() => {
margin-bottom: 8px;
}
&.verified {
background-color: var(--hover);
border-radius: 10px;
color: var(--badge) !important;
}
> .name {
width: 30%;
overflow: hidden;

View file

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

View file

@ -193,12 +193,12 @@ import {
import { v4 as uuid } from "uuid";
import XCommon from "./_common_/common.vue";
import {
deckStore,
addColumn as addColumnToStore,
loadDeck,
getProfiles,
renameProfile as renameProfile_,
deckStore,
deleteProfile as deleteProfile_,
getProfiles,
loadDeck,
renameProfile as renameProfile_,
} from "./deck/deck-store";
import DeckColumnCore from "@/ui/deck/column-core.vue";
import XSidebar from "@/ui/_common_/navbar.vue";
@ -253,7 +253,7 @@ function showSettings() {
os.pageWindow("/settings/deck");
}
let columnsEl = $ref<HTMLElement>();
const columnsEl = $ref<HTMLElement>();
const addColumn = async (ev) => {
const columns = [
@ -384,6 +384,27 @@ async function deleteProfile() {
}
</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>
.menu-enter-active,
.menu-leave-active {

View file

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

View file

@ -23,7 +23,8 @@
<script lang="ts" setup>
import {} from "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 * as os from "@/os";
import { i18n } from "@/i18n";
@ -38,7 +39,7 @@ const emit = defineEmits<{
(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) {
setChannel();

View file

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

View file

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

View file

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

View file

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

View file

@ -23,7 +23,8 @@
<script lang="ts" setup>
import {} from "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 * as os from "@/os";
import { i18n } from "@/i18n";
@ -38,7 +39,7 @@ const emit = defineEmits<{
(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) {
setList();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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