diff --git a/CHANGELOG.md b/CHANGELOG.md index 91d4edf12..bf7522493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,30 @@ You should also include the user name that made the change. --> +## 12.116.0 (2022/07/16) + +### Improvements +- Client: registry editor @syuilo +- Client: UIのブラッシュアップ @syuilo + +### Bugfixes +- Error During Migration Run to 12.111.x +- Server: TypeError: Cannot convert undefined or null to object @syuilo + +## 12.115.0 (2022/07/16) + +### Improvements +- Client: Deckのプロファイル切り替えを簡単に @syuilo +- Client: UIのブラッシュアップ @syuilo + +## 12.114.0 (2022/07/15) + +### Improvements +- RSSティッカーで表示順序をシャッフルできるように @syuilo + +### Bugfixes +- クライアントが起動しなくなることがある問題を修正 @syuilo + ## 12.113.0 (2022/07/13) ### Improvements diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 1289fba44..992eeb0f5 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -808,6 +808,7 @@ reverse: "اقلب" colored: "ملوّن" label: "التسمية" localOnly: "المحلي فقط" +account: "الحسابات" _emailUnavailable: used: "هذا البريد الإلكتروني مستخدم" format: "صيغة البريد الإلكتروني غير صالحة" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 6017566bb..f6547646c 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -848,6 +848,7 @@ reverse: "উল্টান" colored: "রঙ্গিন" label: "লেবেল" localOnly: "শুধুমাত্র লোকাল" +account: "অ্যাকাউন্টগুলি" _emailUnavailable: used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে" format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index dfa65198b..1ae93f6c4 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -887,6 +887,9 @@ beta: "Beta" enableAutoSensitive: "NSFW-Automarkierung" enableAutoSensitiveDescription: "Setzt soweit möglich durch Verwendung von Machine Learning automatisch NSFW-Markierungen für Medien, die NSFW-Anteile beinhalten. Auch wenn du diese Option deaktiviert hast, ist sie möglicherweise auf Instanzebene aktiviert." activeEmailValidationDescription: "Aktivert strengere Überprüfung von E-Mail-Adressen, d.h. Testen auf Wegwerfadressen und darauf, ob mit der Adresse tatsächlich kommuniziert werden kann. Ist dies deaktiviert, so wird nur das Format der E-Mail überprüft." +navbar: "Navigationsleiste" +shuffle: "Mischen" +account: "Benutzerkonto" _sensitiveMediaDetection: description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht." sensitivity: "Erkennungssensitivität" @@ -1700,6 +1703,8 @@ _deck: stackLeft: "Auf linke Spalte stapeln" popRight: "Nach rechts vom Stapel nehmen" profile: "Profil" + newProfile: "Neues Profil" + deleteProfile: "Profil löschen" introduction: "Erstelle eine auf dich zugeschneiderte Benutzeroberfläche durch das Aneinanderreihen von Spalten!" introduction2: "Klicke auf das + rechts um wann immer du möchtest neue Spalten hinzuzufügen." widgetsIntroduction: "Drücke bitte \"Widgets bearbeiten\" im Spaltenmenü und füge ein Widget hinzu." diff --git a/locales/en-US.yml b/locales/en-US.yml index f0a9924cd..a2ded5a45 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -887,6 +887,9 @@ beta: "Beta" enableAutoSensitive: "Automatic NSFW-Marking" enableAutoSensitiveDescription: "Allows automatic detection and marking of NSFW media through Machine Learning where possible. Even if this option is disabled, it may be enabled instance-wide." activeEmailValidationDescription: "Enables stricter validation of email addresses, which includes checking for disposable addresses and by whether it can actually be communicated with. When unchecked, only the format of the email is validated." +navbar: "Navigation bar" +shuffle: "Shuffle" +account: "Account" _sensitiveMediaDetection: description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server." sensitivity: "Detection sensitivity" @@ -1700,6 +1703,8 @@ _deck: stackLeft: "Stack with the left column" popRight: "Pop column to the right" profile: "Profile" + newProfile: "New profile" + deleteProfile: "Delete profile" introduction: "Create the perfect interface for you by arranging columns freely!" introduction2: "Click on the + on the right of the screen to add new colums whenever you want." widgetsIntroduction: "Please select \"Edit widgets\" in the column menu and add a widget." diff --git a/locales/es-ES.yml b/locales/es-ES.yml index a8f6aaa79..8383864ed 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -865,6 +865,7 @@ reverse: "Echar de un capirotazo" colored: "Color" label: "Etiqueta" localOnly: "Solo local" +account: "Cuentas" _emailUnavailable: used: "Ya fue usado" format: "Formato no válido." diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index c5f803bb8..b14d3d14b 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -843,6 +843,7 @@ reverse: "Inverser" colored: "Coloré" label: "Étiquette" localOnly: "Local seulement" +account: "Comptes" _emailUnavailable: used: "Non disponible" format: "Le format de cette adresse de courriel est invalide" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index d543fae86..315b690b0 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -852,6 +852,7 @@ reverse: "Balik" colored: "Diwarnai" label: "Label" localOnly: "Hanya lokal" +account: "Akun" _emailUnavailable: used: "Alamat surel ini telah digunakan" format: "Format tidak valid." diff --git a/locales/it-IT.yml b/locales/it-IT.yml index eb7478dd5..ac14bed13 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -814,6 +814,7 @@ reverse: "Inverti" colored: "Colorato" label: "Etichetta" localOnly: "Soltanto locale" +account: "Account" _emailUnavailable: used: "Email già in uso" format: "Formato email non valido" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 71f562233..d432f8a0e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -888,6 +888,9 @@ beta: "ベータ" enableAutoSensitive: "自動NSFW判定" enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにNSFWフラグを設定します。この機能をオフにしても、インスタンスによっては自動で設定されることがあります。" activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。" +navbar: "ナビゲーションバー" +shuffle: "シャッフル" +account: "アカウント" _sensitiveMediaDetection: description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" @@ -1764,6 +1767,8 @@ _deck: stackLeft: "左に重ねる" popRight: "右に出す" profile: "プロファイル" + newProfile: "新規プロファイル" + deleteProfile: "プロファイルを削除" introduction: "カラムを組み合わせて自分だけのインターフェイスを作りましょう!" introduction2: "画面の右にある + を押して、いつでもカラムを追加できます。" widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください" diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml index f0297c66a..29eca64c7 100644 --- a/locales/kab-KAB.yml +++ b/locales/kab-KAB.yml @@ -57,6 +57,7 @@ selectAccount: "Fren amiḍan" accounts: "Imiḍan" searchByGoogle: "Nadi" file: "Ifuyla" +account: "Imiḍan" _email: _follow: title: "Yeṭṭafaṛ-ik·em-id" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 781de6106..2ba05505c 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -886,6 +886,7 @@ beta: "베타" enableAutoSensitive: "자동 NSFW 탐지" enableAutoSensitiveDescription: "이용 가능할 경우 기계학습을 통해 자동으로 미디어 NSFW를 설정합니다. 이 기능을 해제하더라도, 인스턴스 정책에 따라 자동으로 설정될 수 있습니다." activeEmailValidationDescription: "유저가 입력한 메일 주소가 일회용 메일인지, 실제로 통신할 수 있는 지 엄격하게 검사합니다. 해제할 경우 이메일 형식에 대해서만 검사합니다." +account: "계정" _sensitiveMediaDetection: description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다." sensitivity: "탐지 민감도" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index af7d48ea7..41ba8fdd3 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -764,6 +764,7 @@ file: "Pliki" reverse: "Odwróć" colored: "Kolorowe" label: "Etykieta" +account: "Konta" _ffVisibility: public: "Publikuj" _ad: diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index c49cbe138..b0be6b4ff 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -842,6 +842,10 @@ reverse: "Переворот" colored: "Выделена цветом" label: "Метка" localOnly: "Локально" +beta: "Бета" +enableAutoSensitive: "Автоматическое определение NSFW" +enableAutoSensitiveDescription: "Если доступно, используйте машинное обучение для автоматической установки флага NSFW на носителе. Даже если эта функция отключена, она может быть установлена ​​автоматически в зависимости от инстанта." +account: "Учётные записи" _sensitiveMediaDetection: description: "Машинное обучение может быть использовано для автоматического обнаружения чувствительных медиа для модерации. Нагрузка на сервер увеличивается незначительно." setSensitiveFlagAutomatically: "Установить флаг NSFW" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index a5641e68f..2ac0a8566 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -883,6 +883,8 @@ beta: "Beta" enableAutoSensitive: "Automatická detekcia NSFW" enableAutoSensitiveDescription: "Ak je zapnuté, príznak NSFW sa na médiách automaticky nastaví pomocou strojového učenia. Aj keď je táto funkcia vypnutá, v niektorých prípadoch sa môže nastaviť automaticky." activeEmailValidationDescription: "Dôkladnejšie overí e-mailovú adresu používateľa tým, že zistí, či ide o vyradenú e-mailovú adresu a či sa s ňou dá skutočne komunikovať. Ak nie je začiarknuté, e-mailová adresa sa kontroluje len ako text." +navbar: "Navigačný panel" +account: "Účty" _sensitiveMediaDetection: description: "Strojové učenie sa použije na automatickú detekciu citlivých médií na účely ich moderovania. Mierne sa zvýši zaťaženie servera." sensitivity: "Citlivosť detekcie" @@ -1696,6 +1698,8 @@ _deck: stackLeft: "Priložiť do ľavého stĺpca" popRight: "Vybrať napravo" profile: "Profil" + newProfile: "Nový profil" + deleteProfile: "Odstrániť profil" introduction: "Kombinujte stĺpce a vytvorte si svoje vlastné rozhranie!" introduction2: "Stlačením tlačidla + v pravej časti obrazovky môžete kedykoľvek pridať stĺpce." widgetsIntroduction: "V ponuke stĺpca vyberte možnosť \"Upraviť widget\" a pridajte widget" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 1170185db..610a5da3d 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -820,8 +820,23 @@ ffVisibilityDescription: "ช่วยให้คุณสามารถกำ continueThread: "ดูความต่อเนื่องเธรด" deleteAccountConfirm: "การดำเนินการนี้จะลบบัญชีของคุณอย่างถาวรเลยนะ แน่ใจหรอดำเนินการ?" incorrectPassword: "รหัสผ่านไม่ถูกต้อง" +voteConfirm: "ยืนยันการโหวต \"{choice}\" มั้ย?" +hide: "ซ่อน" +leaveGroup: "ออกจากกลุ่ม" +leaveGroupConfirm: "คุณแน่ใจหรอว่าต้องการออกจาก \"{name}\"" +useDrawerReactionPickerForMobile: "แสดงผล ตัวเลือกปฏิกิริยาเป็นลิ้นชักบนมือถือ" +welcomeBackWithName: "ยินดีต้อนรับการกลับมานะค่ะ, {name}" +clickToFinishEmailVerification: "กรุณาคลิก [{ok}] เพื่อดำเนินการยืนยันอีเมลให้เสร็จสมบูรณ์นะ" +overridedDeviceKind: "ประเภทอุปกรณ์" +smartphone: "สมาร์ทโฟน" +tablet: "แท็บเล็ต" +auto: "อัตโนมัติ" +themeColor: "อินสแตนซ์ Ticker Color" +size: "ขนาด" +numberOfColumn: "จำนวนคอลัมน์" searchByGoogle: "ค้นหา" file: "ไฟล์" +account: "บัญชีผู้ใช้" _ffVisibility: public: "เผยแพร่" _ad: diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 068065487..90aa1da30 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -887,6 +887,9 @@ beta: "Beta" enableAutoSensitive: "Tự động đánh dấu NSFW" enableAutoSensitiveDescription: "Cho phép tự động phát hiện và đánh dấu media NSFW thông qua học máy, nếu có thể. Ngay cả khi tùy chọn này bị tắt, nó vẫn có thể được bật trên toàn máy chủ." activeEmailValidationDescription: "Cho phép xác minh địa chỉ email chặt chẽ hơn, bao gồm việc kiểm tra các địa chỉ dùng một lần và xem nó có thực sự được giao tiếp hay không. Khi bỏ chọn, chỉ định dạng của email được xác minh." +navbar: "Thanh điều hướng" +shuffle: "Xáo trộn" +account: "Tài khoản của bạn" _sensitiveMediaDetection: description: "Giảm nỗ lực kiểm duyệt máy chủ thông qua việc tự động nhận dạng media NSFW thông qua học máy. Điều này sẽ làm tăng một chút áp lực trên máy chủ." sensitivity: "Phát hiện nhạy cảm" @@ -1700,6 +1703,8 @@ _deck: stackLeft: "Xếp chồng với cột bên trái" popRight: "Xếp chồng với cột bên trái" profile: "Hồ sơ" + newProfile: "Hồ sơ mới" + deleteProfile: "Xóa hồ sơ" introduction: "Kết hợp các cột để tạo giao diện của riêng bạn!" introduction2: "Bạn có thể thêm cột bất kỳ lúc nào bằng cách nhấn + ở bên phải màn hình." widgetsIntroduction: "Chọn \"Sửa widget\" trong menu cột và thêm một widget." diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 26f745e65..e27b02081 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -203,6 +203,7 @@ done: "完成" processing: "正在处理" preview: "预览" default: "默认" +defaultValueIs: "默认值: {value}" noCustomEmojis: "没有自定义表情符号" noJobs: "没有任务" federating: "联合中" @@ -336,7 +337,7 @@ pinnedUsers: "置顶用户" pinnedUsersDescription: "在「发现」页面中使用换行标记想要置顶的用户。" pinnedPages: "固定页面" pinnedPagesDescription: "输入您要固定到实例首页的页面路径,以换行符分隔。" -pinnedClipId: "置顶的书签ID" +pinnedClipId: "置顶的便签ID" pinnedNotes: "已置顶的帖子" hcaptcha: "hCaptcha" enableHcaptcha: "启用 hCaptcha" @@ -640,10 +641,12 @@ random: "随机" system: "系统" switchUi: "切换界面" desktop: "桌面" -clip: "书签" +clip: "便签" createNew: "新建" optional: "可选" -createNewClip: "新建书签" +createNewClip: "新建便签" +unclip: "移除便签" +confirmToUnclipAlreadyClippedNote: "本帖已包含在便签\"{name}\"里。您想要将本帖从该便签中移除吗?" public: "公开" i18nInfo: "Misskey已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过{link}帮助翻译。" manageAccessTokens: "管理 Access Tokens" @@ -677,7 +680,7 @@ pageLikesCount: "页面点赞次数" pageLikedCount: "页面被点赞次数" contact: "联系人" useSystemFont: "使用系统默认字体" -clips: "书签" +clips: "便签" experimentalFeatures: "实验性功能" developer: "开发者" makeExplorable: "使账号可见。" @@ -857,6 +860,7 @@ requireAdminForView: "需要使用管理员账户登录才能查看。" isSystemAccount: "该账号由系统自动创建和管理。" typeToConfirm: "输入 {x} 以确认操作。" deleteAccount: "删除账户" +document: "文档" numberOfPageCache: "缓存页数" numberOfPageCacheDescription: "设置较高的值会更方便用户,但服务器负载和内存使用量会增加。" logoutConfirm: "是否确认登出?" @@ -867,6 +871,7 @@ reverse: "翻转" colored: "彩色" refreshInterval: "刷新间隔" label: "标签" +type: "类型" speed: "速度" slow: "慢" fast: "快" @@ -878,6 +883,9 @@ cannotUploadBecauseInappropriate: "因为可能含有不适宜的内容,无法 cannotUploadBecauseNoFreeSpace: "因为已无可用空间,无法上传。" beta: "测试" enableAutoSensitive: "自动 NSFW 识别" +navbar: "导航栏" +shuffle: "随机" +account: "账户" _sensitiveMediaDetection: description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。" sensitivity: "检测敏感度" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 945080ee4..4ee440c07 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -887,6 +887,8 @@ beta: "Beta" enableAutoSensitive: "自動NSFW判定" enableAutoSensitiveDescription: "如果可用,請利用機器學習在媒體上自動設置 NSFW 旗標。 即使關閉此功能,依實例而定也可能會自動設置。" activeEmailValidationDescription: "積極地驗證用戶的電子郵件地址,判斷它是否為免洗地址,或者它是否可以通信。 若關閉,則只會檢查字元是否正確。" +navbar: "導覽列" +account: "帳戶" _sensitiveMediaDetection: description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。" sensitivity: "檢測敏感度" @@ -1700,6 +1702,8 @@ _deck: stackLeft: "向左折疊" popRight: "向右彈出" profile: "個人檔案" + newProfile: "新建個人檔案" + deleteProfile: "刪除個人檔案" introduction: "組合欄位來製作屬於自己的介面吧!" introduction2: "您可以隨時透過按畫面右方的 + 來添加欄位。" widgetsIntroduction: "請從欄位的選單中,選擇「編輯小工具」來添加小工具" diff --git a/package.json b/package.json index 61740a1d3..464b02629 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.113.0-simkey-v1", + "version": "12.116.0-simkey-v1", "codename": "indigo", "repository": { "type": "git", @@ -41,7 +41,7 @@ "devDependencies": { "@types/gulp": "4.0.9", "@types/gulp-rename": "2.0.1", - "@typescript-eslint/parser": "5.30.0", + "@typescript-eslint/parser": "5.30.6", "cross-env": "7.0.3", "cypress": "10.3.0", "start-server-and-test": "1.14.0", diff --git a/packages/backend/migration/1651224615271-foreign-key.js b/packages/backend/migration/1651224615271-foreign-key.js index 44ba7fb6c..1f1510f84 100644 --- a/packages/backend/migration/1651224615271-foreign-key.js +++ b/packages/backend/migration/1651224615271-foreign-key.js @@ -28,7 +28,7 @@ export class foreignKeyReports1651224615271 { queryRunner.query(`CREATE INDEX "IDX_315c779174fe8247ab324f036e" ON "drive_file" ("isLink")`), queryRunner.query(`CREATE INDEX "IDX_f22169eb10657bded6d875ac8f" ON "note" ("channelId")`), - queryRunner.query(`CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId")`), + //queryRunner.query(`CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId")`), queryRunner.query(`DELETE FROM "abuse_user_report" WHERE "targetUserId" NOT IN (SELECT "id" FROM "user")`).then(() => { queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 5148cfd05..3cdd89a8b 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -16,6 +16,7 @@ export default class extends Channel { constructor(id: string, connection: Channel['connection']) { super(id, connection); this.onNote = this.onNote.bind(this); + this.emitTypers = this.emitTypers.bind(this); } public async init(params: any) { diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 1f4e97e16..c47dbd754 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -14,9 +14,11 @@ // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので (async () => { window.onerror = (e) => { + console.error(e); renderError('SOMETHING_HAPPENED', e); }; window.onunhandledrejection = (e) => { + console.error(e); renderError('SOMETHING_HAPPENED_IN_PROMISE', e); }; @@ -50,18 +52,30 @@ localStorage.setItem('localeVersion', v); } else { await checkUpdate(); - renderError('LOCALE_FETCH_FAILED'); + renderError('LOCALE_FETCH'); return; } } //#endregion //#region Script - import(`/assets/${CLIENT_ENTRY}`) - .catch(async e => { - await checkUpdate(); - renderError('APP_FETCH_FAILED', e); - }) + function importAppScript() { + import(`/assets/${CLIENT_ENTRY}`) + .catch(async e => { + await checkUpdate(); + console.error(e); + renderError('APP_IMPORT', e); + }); + } + + // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある + if (document.readyState !== 'loading') { + importAppScript(); + } else { + window.addEventListener('DOMContentLoaded', () => { + importAppScript(); + }); + } //#endregion //#region Theme @@ -115,35 +129,35 @@ let errorsElement = document.getElementById('errors'); if (!errorsElement) { - document.documentElement.innerHTML = ` + document.body.innerHTML = ` - - - + + +

An error has occurred!

-

Don't worry, it's (probably) not your fault.

+

Don't worry, it's (probably) not your fault.

If the problem persists after refreshing, please contact your instance's administrator.
You may also try the following options:

- - - + + +
- - - + + +
- - - + + +
`; @@ -272,17 +286,22 @@ // eslint-disable-next-line no-inner-declarations async function checkUpdate() { - // TODO: サーバーが落ちている場合などのエラーハンドリング - const res = await fetch('/api/meta', { - method: 'POST', - cache: 'no-cache' - }); + try { + const res = await fetch('/api/meta', { + method: 'POST', + cache: 'no-cache' + }); - const meta = await res.json(); + const meta = await res.json(); - if (meta.version != v) { - localStorage.setItem('v', meta.version); - refresh(); + if (meta.version != v) { + localStorage.setItem('v', meta.version); + refresh(); + } + } catch (e) { + console.error(e); + renderError('UPDATE_CHECK', e); + throw e; } } diff --git a/packages/client/package.json b/packages/client/package.json index 8d2efd0b9..94f6688dd 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -56,7 +56,6 @@ "random-seed": "0.3.0", "reflect-metadata": "0.1.13", "rndstr": "1.0.0", - "rollup": "2.76.0", "s-age": "1.1.2", "sass": "1.53.0", "seedrandom": "3.0.5", @@ -102,6 +101,7 @@ "@types/ws": "8.5.3", "@typescript-eslint/eslint-plugin": "5.30.6", "@typescript-eslint/parser": "5.30.6", + "rollup": "2.76.0", "cross-env": "7.0.3", "cypress": "10.3.0", "eslint": "8.19.0", diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index 6c2c8acad..9e2ef1b93 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -26,7 +26,8 @@ -
entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles() + (entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles(), ); watch(folder, () => emit('cd', folder.value)); @@ -232,7 +233,7 @@ function onDrop(ev: DragEvent): any { removeFile(file.id); os.api('drive/files/update', { fileId: file.id, - folderId: folder.value ? folder.value.id : null + folderId: folder.value ? folder.value.id : null, }); } //#endregion @@ -248,7 +249,7 @@ function onDrop(ev: DragEvent): any { removeFolder(droppedFolder.id); os.api('drive/folders/update', { folderId: droppedFolder.id, - parentId: folder.value ? folder.value.id : null + parentId: folder.value ? folder.value.id : null, }).then(() => { // noop }).catch(err => { @@ -256,13 +257,13 @@ function onDrop(ev: DragEvent): any { case 'detected-circular-definition': os.alert({ title: i18n.ts.unableToProcess, - text: i18n.ts.circularReferenceFolder + text: i18n.ts.circularReferenceFolder, }); break; default: os.alert({ type: 'error', - text: i18n.ts.somethingHappened + text: i18n.ts.somethingHappened, }); } }); @@ -278,17 +279,17 @@ function urlUpload() { os.inputText({ title: i18n.ts.uploadFromUrl, type: 'url', - placeholder: i18n.ts.uploadFromUrlDescription + placeholder: i18n.ts.uploadFromUrlDescription, }).then(({ canceled, result: url }) => { if (canceled || !url) return; os.api('drive/files/upload-from-url', { url: url, - folderId: folder.value ? folder.value.id : undefined + folderId: folder.value ? folder.value.id : undefined, }); os.alert({ title: i18n.ts.uploadFromUrlRequested, - text: i18n.ts.uploadFromUrlMayTakeTime + text: i18n.ts.uploadFromUrlMayTakeTime, }); }); } @@ -296,12 +297,12 @@ function urlUpload() { function createFolder() { os.inputText({ title: i18n.ts.createFolder, - placeholder: i18n.ts.folderName + placeholder: i18n.ts.folderName, }).then(({ canceled, result: name }) => { if (canceled) return; os.api('drive/folders/create', { name: name, - parentId: folder.value ? folder.value.id : undefined + parentId: folder.value ? folder.value.id : undefined, }).then(createdFolder => { addFolder(createdFolder, true); }); @@ -312,12 +313,12 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) { os.inputText({ title: i18n.ts.renameFolder, placeholder: i18n.ts.inputNewFolderName, - default: folderToRename.name + default: folderToRename.name, }).then(({ canceled, result: name }) => { if (canceled) return; os.api('drive/folders/update', { folderId: folderToRename.id, - name: name + name: name, }).then(updatedFolder => { // FIXME: 画面を更新するために自分自身に移動 move(updatedFolder); @@ -327,7 +328,7 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) { function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { os.api('drive/folders/delete', { - folderId: folderToDelete.id + folderId: folderToDelete.id, }).then(() => { // 削除時に親フォルダに移動 move(folderToDelete.parentId); @@ -337,15 +338,15 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { os.alert({ type: 'error', title: i18n.ts.unableToDelete, - text: i18n.ts.hasChildFilesOrFolders + text: i18n.ts.hasChildFilesOrFolders, }); break; default: os.alert({ type: 'error', - text: i18n.ts.unableToDelete + text: i18n.ts.unableToDelete, }); - } + } }); } @@ -411,7 +412,7 @@ function move(target?: Misskey.entities.DriveFolder) { fetching.value = true; os.api('drive/folders/show', { - folderId: target + folderId: target, }).then(folderToMove => { folder.value = folderToMove; hierarchyFolders.value = []; @@ -510,7 +511,7 @@ async function fetch() { const foldersPromise = os.api('drive/folders', { folderId: folder.value ? folder.value.id : null, - limit: foldersMax + 1 + limit: foldersMax + 1, }).then(fetchedFolders => { if (fetchedFolders.length === foldersMax + 1) { moreFolders.value = true; @@ -522,7 +523,7 @@ async function fetch() { const filesPromise = os.api('drive/files', { folderId: folder.value ? folder.value.id : null, type: props.type, - limit: filesMax + 1 + limit: filesMax + 1, }).then(fetchedFiles => { if (fetchedFiles.length === filesMax + 1) { moreFiles.value = true; @@ -549,7 +550,7 @@ function fetchMoreFiles() { folderId: folder.value ? folder.value.id : null, type: props.type, untilId: files.value[files.value.length - 1].id, - limit: max + 1 + limit: max + 1, }).then(files => { if (files.length === max + 1) { moreFiles.value = true; @@ -569,30 +570,30 @@ function getMenu() { ref: keepOriginal, }, null, { text: i18n.ts.addFile, - type: 'label' + type: 'label', }, { text: i18n.ts.upload, icon: 'fas fa-upload', - action: () => { selectLocalFile(); } + action: () => { selectLocalFile(); }, }, { text: i18n.ts.fromUrl, icon: 'fas fa-link', - action: () => { urlUpload(); } + action: () => { urlUpload(); }, }, null, { text: folder.value ? folder.value.name : i18n.ts.drive, - type: 'label' + type: 'label', }, folder.value ? { text: i18n.ts.renameFolder, icon: 'fas fa-i-cursor', - action: () => { renameFolder(folder.value); } + action: () => { renameFolder(folder.value); }, } : undefined, folder.value ? { text: i18n.ts.deleteFolder, icon: 'fas fa-trash-alt', - action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); } + action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); }, } : undefined, { text: i18n.ts.createFolder, icon: 'fas fa-folder-plus', - action: () => { createFolder(); } + action: () => { createFolder(); }, }]; } @@ -662,14 +663,14 @@ onBeforeUnmount(() => { > .path { display: inline-block; vertical-align: bottom; - line-height: 50px; + line-height: 42px; white-space: nowrap; > * { display: inline-block; margin: 0; padding: 0 8px; - line-height: 50px; + line-height: 42px; cursor: pointer; * { diff --git a/packages/client/src/components/form/input.vue b/packages/client/src/components/form/input.vue index 1cc391217..2a03d6a5d 100644 --- a/packages/client/src/components/form/input.vue +++ b/packages/client/src/components/form/input.vue @@ -77,9 +77,9 @@ const inputEl = ref(); const prefixEl = ref(); const suffixEl = ref(); const height = - props.small ? 38 : - props.large ? 42 : - 40; + props.small ? 36 : + props.large ? 40 : + 38; const focus = () => inputEl.value.focus(); const onInput = (ev: KeyboardEvent) => { diff --git a/packages/client/src/components/form/select.vue b/packages/client/src/components/form/select.vue index fe8c08cd6..78282dfdc 100644 --- a/packages/client/src/components/form/select.vue +++ b/packages/client/src/components/form/select.vue @@ -63,9 +63,9 @@ const prefixEl = ref(null); const suffixEl = ref(null); const container = ref(null); const height = - props.small ? 38 : - props.large ? 42 : - 40; + props.small ? 36 : + props.large ? 40 : + 38; const focus = () => inputEl.value.focus(); const onInput = (ev) => { diff --git a/packages/client/src/components/global/ad.vue b/packages/client/src/components/global/ad.vue index 180dabb2a..7725f776a 100644 --- a/packages/client/src/components/global/ad.vue +++ b/packages/client/src/components/global/ad.vue @@ -3,7 +3,7 @@ @@ -28,6 +28,7 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue') diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index e8e554d72..4d34ca9b8 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -23,7 +23,7 @@
- +
@@ -361,7 +361,6 @@ function onDrop(ev) { z-index: 1; width: var(--deckColumnHeaderHeight); line-height: var(--deckColumnHeaderHeight); - font-size: 16px; color: var(--faceTextButton); &:hover { diff --git a/packages/client/src/ui/deck/deck-store.ts b/packages/client/src/ui/deck/deck-store.ts index 3ec53ad55..67fcff480 100644 --- a/packages/client/src/ui/deck/deck-store.ts +++ b/packages/client/src/ui/deck/deck-store.ts @@ -72,18 +72,8 @@ export const loadDeck = async () => { return; } - deckStore.set('columns', [{ - id: 'a', - type: 'main', - name: i18n.ts._deck._columns.main, - width: 350, - }, { - id: 'b', - type: 'notifications', - name: i18n.ts._deck._columns.notifications, - width: 330, - }]); - deckStore.set('layout', [['a'], ['b']]); + deckStore.set('columns', []); + deckStore.set('layout', []); return; } throw err; @@ -105,6 +95,19 @@ export const saveDeck = throttle(1000, () => { }); }); +export async function getProfiles(): Promise { + return await api('i/registry/keys', { + scope: ['client', 'deck', 'profiles'], + }); +} + +export async function deleteProfile(key: string): Promise { + return await api('i/registry/remove', { + scope: ['client', 'deck', 'profiles'], + key: key, + }); +} + export function addColumn(column: Column) { if (column.name === undefined) column.name = null; deckStore.push('columns', column); diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue index 2edfb3f12..e4b5de991 100644 --- a/packages/client/src/ui/universal.vue +++ b/packages/client/src/ui/universal.vue @@ -61,17 +61,17 @@ import { defineAsyncComponent, provide, onMounted, computed, ref, watch, Compute import XCommon from './_common_/common.vue'; import { instanceName } from '@/config'; import { StickySidebar } from '@/scripts/sticky-sidebar'; -import XDrawerMenu from '@/ui/_common_/sidebar-for-mobile.vue'; +import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; -import { menuDef } from '@/menu'; +import { navbarItemDef } from '@/navbar'; import { i18n } from '@/i18n'; import { $i } from '@/account'; import { Router } from '@/nirax'; import { mainRouter } from '@/router'; import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue')); -const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/sidebar.vue')); +const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue')); const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); const DESKTOP_THRESHOLD = 1100; @@ -97,9 +97,9 @@ provideMetadataReceiver((info) => { }); const menuIndicated = computed(() => { - for (const def in menuDef) { + for (const def in navbarItemDef) { if (def === 'notifications') continue; // 通知は下にボタンとして表示されてるから - if (menuDef[def].indicated) return true; + if (navbarItemDef[def].indicated) return true; } return false; }); @@ -365,11 +365,11 @@ const wallpaper = localStorage.getItem('wallpaper') != null; height: calc(var(--vh, 1vh) * 100); width: 240px; box-sizing: border-box; + contain: strict; overflow: auto; overscroll-behavior: contain; - background: var(--bg); + background: var(--navBg); } - } diff --git a/packages/client/src/widgets/rss-ticker.vue b/packages/client/src/widgets/rss-ticker.vue index 06995bc86..c692c0c4f 100644 --- a/packages/client/src/widgets/rss-ticker.vue +++ b/packages/client/src/widgets/rss-ticker.vue @@ -26,6 +26,7 @@ import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import MkContainer from '@/components/ui/container.vue'; import { useInterval } from '@/scripts/use-interval'; +import { shuffle } from '@/scripts/shuffle'; const name = 'rssTicker'; @@ -34,6 +35,10 @@ const widgetPropsDef = { type: 'string' as const, default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', }, + shuffle: { + type: 'boolean' as const, + default: true, + }, refreshIntervalSec: { type: 'number' as const, default: 60, @@ -80,6 +85,9 @@ let key = $ref(0); const tick = () => { fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => { res.json().then(feed => { + if (widgetProps.shuffle) { + shuffle(feed.items); + } items.value = feed.items; fetching.value = false; key++; diff --git a/packages/client/vite.json5.ts b/packages/client/vite.json5.ts index 693ee7be0..0a37fbff4 100644 --- a/packages/client/vite.json5.ts +++ b/packages/client/vite.json5.ts @@ -6,33 +6,33 @@ import { createFilter, dataToEsm } from '@rollup/pluginutils'; import { RollupJsonOptions } from '@rollup/plugin-json'; export default function json5(options: RollupJsonOptions = {}): Plugin { - const filter = createFilter(options.include, options.exclude); - const indent = 'indent' in options ? options.indent : '\t'; + const filter = createFilter(options.include, options.exclude); + const indent = 'indent' in options ? options.indent : '\t'; - return { - name: 'json5', + return { + name: 'json5', - // eslint-disable-next-line no-shadow - transform(json, id) { - if (id.slice(-6) !== '.json5' || !filter(id)) return null; + // eslint-disable-next-line no-shadow + transform(json, id) { + if (id.slice(-6) !== '.json5' || !filter(id)) return null; - try { - const parsed = JSON5.parse(json); - return { - code: dataToEsm(parsed, { - preferConst: options.preferConst, - compact: options.compact, - namedExports: options.namedExports, - indent - }), - map: { mappings: '' } - }; - } catch (err) { - const message = 'Could not parse JSON file'; - const position = parseInt(/[\d]/.exec(err.message)[0], 10); - this.warn({ message, id, position }); - return null; - } - } - }; + try { + const parsed = JSON5.parse(json); + return { + code: dataToEsm(parsed, { + preferConst: options.preferConst, + compact: options.compact, + namedExports: options.namedExports, + indent, + }), + map: { mappings: '' }, + }; + } catch (err) { + const message = 'Could not parse JSON file'; + const position = parseInt(/[\d]/.exec(err.message)[0], 10); + this.warn({ message, id, position }); + return null; + } + }, + }; }